Simplifications

- Inline a handful of interfaces that had limited use
 - Removed IndexedModel/VBOModel as a first step in refactoring model uploads
 - InstancedModels/GPUInstancers support multiple VAOs
 - Fix padding issue
This commit is contained in:
Jozufozu 2022-05-11 16:32:27 -07:00
parent d5e9e044ec
commit be60eae9af
25 changed files with 271 additions and 461 deletions

View file

@ -1,5 +1,8 @@
package com.jozufozu.flywheel.backend.instancing.instancing; package com.jozufozu.flywheel.backend.instancing.instancing;
import java.util.HashSet;
import java.util.Set;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.Flywheel; import com.jozufozu.flywheel.Flywheel;
@ -46,13 +49,18 @@ public class GPUInstancer<D extends InstanceData> extends AbstractInstancer<D> {
return !anyToUpdate && !anyToRemove && glInstanceCount == 0; return !anyToUpdate && !anyToRemove && glInstanceCount == 0;
} }
private final Set<GlVertexArray> boundTo = new HashSet<>();
void renderSetup(GlVertexArray vao) { void renderSetup(GlVertexArray vao) {
if (anyToRemove) { if (anyToRemove) {
removeDeletedInstances(); removeDeletedInstances();
} }
vbo.bind(); vbo.bind();
if (!realloc(vao)) {
if (!realloc()) {
boundTo.clear();
if (anyToRemove) { if (anyToRemove) {
clearBufferTail(); clearBufferTail();
@ -65,6 +73,10 @@ public class GPUInstancer<D extends InstanceData> extends AbstractInstancer<D> {
glInstanceCount = data.size(); glInstanceCount = data.size();
} }
if (boundTo.add(vao)) {
bindInstanceAttributes(vao);
}
vbo.unbind(); vbo.unbind();
anyToRemove = anyToUpdate = false; anyToRemove = anyToUpdate = false;
@ -110,7 +122,7 @@ public class GPUInstancer<D extends InstanceData> extends AbstractInstancer<D> {
} }
} }
private boolean realloc(GlVertexArray vao) { private boolean realloc() {
int size = this.data.size(); int size = this.data.size();
int stride = instanceFormat.getStride(); int stride = instanceFormat.getStride();
int requiredSize = size * stride; int requiredSize = size * stride;
@ -127,8 +139,6 @@ public class GPUInstancer<D extends InstanceData> extends AbstractInstancer<D> {
glInstanceCount = size; glInstanceCount = size;
bindInstanceAttributes(vao);
return true; return true;
} }
return false; return false;

View file

@ -1,7 +1,6 @@
package com.jozufozu.flywheel.backend.instancing.instancing; package com.jozufozu.flywheel.backend.instancing.instancing;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -12,7 +11,7 @@ import com.jozufozu.flywheel.api.InstanceData;
import com.jozufozu.flywheel.api.Instancer; import com.jozufozu.flywheel.api.Instancer;
import com.jozufozu.flywheel.api.Material; import com.jozufozu.flywheel.api.Material;
import com.jozufozu.flywheel.api.struct.Instanced; import com.jozufozu.flywheel.api.struct.Instanced;
import com.jozufozu.flywheel.backend.model.MeshAllocator; import com.jozufozu.flywheel.backend.model.MeshPool;
import com.jozufozu.flywheel.core.model.ModelSupplier; import com.jozufozu.flywheel.core.model.ModelSupplier;
import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.RenderType;
@ -54,12 +53,6 @@ public class InstancedMaterial<D extends InstanceData> implements Material<D> {
.sum(); .sum();
} }
public boolean nothingToRender() {
return models.size() > 0 && models.values()
.stream()
.allMatch(InstancedModel::isEmpty);
}
public void delete() { public void delete() {
models.values().forEach(InstancedModel::delete); models.values().forEach(InstancedModel::delete);
models.clear(); models.clear();
@ -76,7 +69,7 @@ public class InstancedMaterial<D extends InstanceData> implements Material<D> {
.forEach(GPUInstancer::clear); .forEach(GPUInstancer::clear);
} }
public void init(MeshAllocator allocator) { public void init(MeshPool allocator) {
for (var instanced : uninitialized) { for (var instanced : uninitialized) {
var map = instanced.init(allocator); var map = instanced.init(allocator);
@ -86,14 +79,6 @@ public class InstancedMaterial<D extends InstanceData> implements Material<D> {
uninitialized.clear(); uninitialized.clear();
} }
public void renderIn(RenderType layer) {
renderables.get(layer).forEach(Renderable::render);
}
public boolean anythingToRender(RenderType type) {
return renderables.get(type).size() > 0;
}
private InstancedModel<D> createInstancer(ModelSupplier model) { private InstancedModel<D> createInstancer(ModelSupplier model) {
var instancer = new InstancedModel<>(new GPUInstancer<>(type), model); var instancer = new InstancedModel<>(new GPUInstancer<>(type), model);
uninitialized.add(instancer); uninitialized.add(instancer);

View file

@ -2,61 +2,88 @@ package com.jozufozu.flywheel.backend.instancing.instancing;
import java.util.Map; import java.util.Map;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.jozufozu.flywheel.api.InstanceData; import com.jozufozu.flywheel.api.InstanceData;
import com.jozufozu.flywheel.backend.gl.GlVertexArray; import com.jozufozu.flywheel.backend.gl.GlVertexArray;
import com.jozufozu.flywheel.backend.model.BufferedModel; import com.jozufozu.flywheel.backend.model.MeshPool;
import com.jozufozu.flywheel.backend.model.MeshAllocator; import com.jozufozu.flywheel.core.model.Mesh;
import com.jozufozu.flywheel.core.model.ModelSupplier; import com.jozufozu.flywheel.core.model.ModelSupplier;
import com.jozufozu.flywheel.util.Pair;
import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.RenderType;
public class InstancedModel<D extends InstanceData> { public class InstancedModel<D extends InstanceData> {
GPUInstancer<D> instancer; final GPUInstancer<D> instancer;
ModelSupplier model; final ModelSupplier model;
private Map<RenderType, Layer> layers;
@Nullable
private BufferedModel bufferedMesh;
@Nullable
private GlVertexArray vao;
public InstancedModel(GPUInstancer<D> instancer, ModelSupplier model) { public InstancedModel(GPUInstancer<D> instancer, ModelSupplier model) {
this.instancer = instancer; this.instancer = instancer;
this.model = model; this.model = model;
} }
public Map<RenderType, Renderable> init(MeshAllocator allocator) { public Map<RenderType, ? extends Renderable> init(MeshPool allocator) {
instancer.init(); instancer.init();
vao = new GlVertexArray(); layers = model.get()
.entrySet()
.stream()
.map(entry -> Pair.of(entry.getKey(), new Layer(allocator, entry.getKey(), entry.getValue())))
.collect(ImmutableMap.toImmutableMap(Pair::first, Pair::second));
bufferedMesh = allocator.alloc(model.get(), vao);
instancer.attributeBaseIndex = bufferedMesh.getAttributeCount();
vao.enableArrays(bufferedMesh.getAttributeCount() + instancer.instanceFormat.getAttributeCount());
return ImmutableMap.of(RenderType.solid(), this::render); return layers;
} }
public void render() { private class Layer implements Renderable {
if (invalid()) return;
vao.bind(); final RenderType type;
MeshPool.BufferedMesh bufferedMesh;
GlVertexArray vao;
instancer.renderSetup(vao); private Layer(MeshPool allocator, RenderType type, Mesh mesh) {
this.type = type;
if (instancer.glInstanceCount > 0) { vao = new GlVertexArray();
bufferedMesh.drawInstances(instancer.glInstanceCount); bufferedMesh = allocator.alloc(mesh, vao);
instancer.attributeBaseIndex = bufferedMesh.getAttributeCount();
vao.enableArrays(bufferedMesh.getAttributeCount() + instancer.instanceFormat.getAttributeCount());
} }
// persistent mapping sync point @Override
instancer.vbo.doneForThisFrame(); public void render() {
} if (invalid()) return;
private boolean invalid() { vao.bind();
return instancer.vbo == null || bufferedMesh == null || vao == null;
instancer.renderSetup(vao);
if (instancer.glInstanceCount > 0) {
bufferedMesh.drawInstances(instancer.glInstanceCount);
}
// persistent mapping sync point
instancer.vbo.doneForThisFrame();
}
@Override
public boolean shouldRemove() {
return invalid();
}
private boolean invalid() {
return instancer.vbo == null || bufferedMesh == null || vao == null;
}
public void delete() {
if (invalid()) return;
vao.delete();
bufferedMesh.delete();
vao = null;
bufferedMesh = null;
}
} }
public GPUInstancer<D> getInstancer() { public GPUInstancer<D> getInstancer() {
@ -71,19 +98,14 @@ public class InstancedModel<D extends InstanceData> {
return model.getVertexCount() * instancer.glInstanceCount; return model.getVertexCount() * instancer.glInstanceCount;
} }
public boolean isEmpty() {
return instancer.isEmpty();
}
public void delete() { public void delete() {
if (invalid()) return; if (instancer.vbo == null) return;
vao.delete();
bufferedMesh.delete();
instancer.vbo.delete(); instancer.vbo.delete();
vao = null;
bufferedMesh = null;
instancer.vbo = null; instancer.vbo = null;
for (var layer : layers.values()) {
layer.delete();
}
} }
} }

View file

@ -11,11 +11,8 @@ import javax.annotation.Nonnull;
import com.jozufozu.flywheel.api.InstanceData; import com.jozufozu.flywheel.api.InstanceData;
import com.jozufozu.flywheel.api.struct.Instanced; import com.jozufozu.flywheel.api.struct.Instanced;
import com.jozufozu.flywheel.api.struct.StructType; import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.backend.gl.versioned.GlCompat;
import com.jozufozu.flywheel.backend.instancing.Engine; import com.jozufozu.flywheel.backend.instancing.Engine;
import com.jozufozu.flywheel.backend.instancing.TaskEngine; import com.jozufozu.flywheel.backend.instancing.TaskEngine;
import com.jozufozu.flywheel.backend.model.FallbackAllocator;
import com.jozufozu.flywheel.backend.model.MeshAllocator;
import com.jozufozu.flywheel.backend.model.MeshPool; import com.jozufozu.flywheel.backend.model.MeshPool;
import com.jozufozu.flywheel.core.Formats; import com.jozufozu.flywheel.core.Formats;
import com.jozufozu.flywheel.core.RenderContext; import com.jozufozu.flywheel.core.RenderContext;
@ -41,7 +38,7 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
protected BlockPos originCoordinate = BlockPos.ZERO; protected BlockPos originCoordinate = BlockPos.ZERO;
protected final ProgramCompiler<P> context; protected final ProgramCompiler<P> context;
private MeshAllocator allocator; private MeshPool allocator;
protected final Map<Instanced<? extends InstanceData>, InstancedMaterial<?>> materials = new HashMap<>(); protected final Map<Instanced<? extends InstanceData>, InstancedMaterial<?>> materials = new HashMap<>();
@ -110,7 +107,10 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
for (Map.Entry<Instanced<? extends InstanceData>, InstancedMaterial<?>> entry : materials.entrySet()) { for (Map.Entry<Instanced<? extends InstanceData>, InstancedMaterial<?>> entry : materials.entrySet()) {
InstancedMaterial<?> material = entry.getValue(); InstancedMaterial<?> material = entry.getValue();
if (material.anythingToRender(type)) { var toRender = material.renderables.get(type);
toRender.removeIf(Renderable::shouldRemove);
if (!toRender.isEmpty()) {
Instanced<? extends InstanceData> instanceType = entry.getKey(); Instanced<? extends InstanceData> instanceType = entry.getKey();
setup(type, camX, camY, camZ, viewProjection, instanceType.getProgramSpec()); setup(type, camX, camY, camZ, viewProjection, instanceType.getProgramSpec());
@ -118,7 +118,7 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
instanceCount += material.getInstanceCount(); instanceCount += material.getInstanceCount();
vertexCount += material.getVertexCount(); vertexCount += material.getVertexCount();
material.renderIn(type); toRender.forEach(Renderable::render);
} }
} }
@ -165,7 +165,7 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
public void beginFrame(Camera info) { public void beginFrame(Camera info) {
checkOriginDistance(info); checkOriginDistance(info);
MeshAllocator allocator = getModelAllocator(); MeshPool allocator = getModelAllocator();
for (InstancedMaterial<?> material : materials.values()) { for (InstancedMaterial<?> material : materials.values()) {
material.init(allocator); material.init(allocator);
@ -173,10 +173,7 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
toRender.addAll(material.renderables.keySet()); toRender.addAll(material.renderables.keySet());
} }
if (allocator instanceof MeshPool pool) { allocator.flush();
// ...and then flush the model arena in case anything was marked for upload
pool.flush();
}
} }
@ -211,20 +208,17 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
info.add("Origin: " + originCoordinate.getX() + ", " + originCoordinate.getY() + ", " + originCoordinate.getZ()); info.add("Origin: " + originCoordinate.getX() + ", " + originCoordinate.getY() + ", " + originCoordinate.getZ());
} }
private MeshAllocator getModelAllocator() { private MeshPool getModelAllocator() {
if (allocator == null) { if (allocator == null) {
allocator = createAllocator(); allocator = createAllocator();
} }
return this.allocator; return this.allocator;
} }
private static MeshAllocator createAllocator() { private static MeshPool createAllocator() {
if (GlCompat.getInstance()
.onAMDWindows()) { // FIXME: Windows AMD Drivers don't like ..BaseVertex
return FallbackAllocator.INSTANCE; return new MeshPool(Formats.POS_TEX_NORMAL);
} else {
return new MeshPool(Formats.POS_TEX_NORMAL);
}
} }
@FunctionalInterface @FunctionalInterface

View file

@ -3,4 +3,6 @@ package com.jozufozu.flywheel.backend.instancing.instancing;
public interface Renderable { public interface Renderable {
void render(); void render();
boolean shouldRemove();
} }

View file

@ -1,52 +1,31 @@
package com.jozufozu.flywheel.backend.model; package com.jozufozu.flywheel.backend.model;
import com.jozufozu.flywheel.backend.gl.GlVertexArray; import com.jozufozu.flywheel.backend.gl.GlVertexArray;
import com.jozufozu.flywheel.core.model.Mesh; import com.jozufozu.flywheel.core.model.BlockMesh;
public class ArrayModelRenderer { public class ArrayModelRenderer {
private final Mesh mesh; protected final GlVertexArray vao;
protected GlVertexArray vao; protected final MeshPool.BufferedMesh mesh;
protected BufferedModel vbo;
protected boolean initialized;
public ArrayModelRenderer(Mesh mesh) { public ArrayModelRenderer(BlockMesh mesh, MeshPool meshPool) {
this.mesh = mesh; this.vao = new GlVertexArray();
this.mesh = meshPool.alloc(mesh, this.vao);
} }
/** /**
* Renders this model, checking first if there is anything to render. * Renders this model, checking first if there is anything to render.
*/ */
public void draw() { public void draw() {
if (!initialized) init(); if (mesh.isDeleted()) return;
if (!isValid()) return;
vao.bind(); vao.bind();
vbo.drawCall(); mesh.drawCall();
}
protected void init() {
initialized = true;
if (mesh.empty()) return;
this.vbo = new IndexedModel(mesh);
vao = new GlVertexArray();
// bind the model's vbo to our vao
this.vbo.setupState(vao);
GlVertexArray.unbind();
} }
public void delete() { public void delete() {
if (vbo != null) mesh.delete();
vbo.delete();
} }
protected boolean isValid() {
return vbo != null && vbo.valid();
}
} }

View file

@ -1,40 +0,0 @@
package com.jozufozu.flywheel.backend.model;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.gl.GlVertexArray;
import com.jozufozu.flywheel.core.layout.BufferLayout;
public interface BufferedModel {
VertexType getType();
int getVertexCount();
/**
* The VAO must be bound externally.
*/
void setupState(GlVertexArray vao);
void drawCall();
/**
* Draws many instances of this model, assuming the appropriate state is already bound.
*/
void drawInstances(int instanceCount);
boolean isDeleted();
void delete();
default BufferLayout getLayout() {
return getType().getLayout();
}
default boolean valid() {
return getVertexCount() > 0 && !isDeleted();
}
default int getAttributeCount() {
return getType().getLayout().getAttributeCount();
}
}

View file

@ -1,15 +0,0 @@
package com.jozufozu.flywheel.backend.model;
import com.jozufozu.flywheel.backend.gl.GlVertexArray;
import com.jozufozu.flywheel.core.model.Mesh;
public enum FallbackAllocator implements MeshAllocator {
INSTANCE;
@Override
public BufferedModel alloc(Mesh mesh, GlVertexArray vao) {
IndexedModel out = new IndexedModel(mesh);
out.setupState(vao);
return out;
}
}

View file

@ -1,97 +0,0 @@
package com.jozufozu.flywheel.backend.model;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL31;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.gl.GlPrimitive;
import com.jozufozu.flywheel.backend.gl.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.backend.gl.buffer.MappedGlBuffer;
import com.jozufozu.flywheel.core.model.Mesh;
/**
* An indexed triangle model. Just what the driver ordered.
*
* <br><em>This should be favored over a normal BufferedModel.</em>
*/
public class IndexedModel implements BufferedModel {
protected final Mesh mesh;
protected final GlPrimitive primitiveMode;
protected ElementBuffer ebo;
protected GlBuffer vbo;
protected boolean deleted;
public IndexedModel(Mesh mesh) {
this.mesh = mesh;
this.primitiveMode = GlPrimitive.TRIANGLES;
vbo = new MappedGlBuffer(GlBufferType.ARRAY_BUFFER);
vbo.bind();
// allocate the buffer on the gpu
vbo.ensureCapacity(mesh.size());
// mirror it in system memory, so we can write to it, and upload our model.
try (MappedBuffer buffer = vbo.getBuffer()) {
mesh.writeInto(buffer.unwrap());
} catch (Exception e) {
Flywheel.LOGGER.error(String.format("Error uploading model '%s':", mesh.name()), e);
}
vbo.unbind();
this.ebo = mesh.createEBO();
}
/**
* The VBO/VAO should be bound externally.
*/
public void setupState(GlVertexArray vao) {
vbo.bind();
vao.enableArrays(getAttributeCount());
vao.bindAttributes(0, getType().getLayout());
}
@Override
public void drawCall() {
ebo.bind();
GL20.glDrawElements(primitiveMode.glEnum, ebo.elementCount, ebo.eboIndexType.getGlEnum(), 0);
}
/**
* Draws many instances of this model, assuming the appropriate state is already bound.
*/
@Override
public void drawInstances(int instanceCount) {
if (!valid()) return;
ebo.bind();
GL31.glDrawElementsInstanced(primitiveMode.glEnum, ebo.elementCount, ebo.eboIndexType.getGlEnum(), 0, instanceCount);
}
public boolean isDeleted() {
return deleted;
}
@Override
public VertexType getType() {
return mesh.getType();
}
public int getVertexCount() {
return mesh.vertexCount();
}
public void delete() {
if (deleted) return;
deleted = true;
vbo.delete();
}
}

View file

@ -1,20 +0,0 @@
package com.jozufozu.flywheel.backend.model;
import com.jozufozu.flywheel.backend.gl.GlVertexArray;
import com.jozufozu.flywheel.core.model.Mesh;
public interface MeshAllocator {
/**
* Allocate a model.
*
* @param mesh The model to allocate.
* @param vao The vertex array object to attach the model to.
* @return A handle to the allocated model.
*/
BufferedModel alloc(Mesh mesh, GlVertexArray vao);
@FunctionalInterface
interface Callback {
void onAlloc(BufferedModel arenaModel);
}
}

View file

@ -16,13 +16,13 @@ import com.jozufozu.flywheel.backend.gl.buffer.MappedBuffer;
import com.jozufozu.flywheel.backend.gl.buffer.MappedGlBuffer; import com.jozufozu.flywheel.backend.gl.buffer.MappedGlBuffer;
import com.jozufozu.flywheel.core.model.Mesh; import com.jozufozu.flywheel.core.model.Mesh;
public class MeshPool implements MeshAllocator { public class MeshPool {
protected final VertexType vertexType; protected final VertexType vertexType;
private final List<PooledModel> models = new ArrayList<>(); private final List<BufferedMesh> models = new ArrayList<>();
private final List<PooledModel> pendingUpload = new ArrayList<>(); private final List<BufferedMesh> pendingUpload = new ArrayList<>();
private final GlBuffer vbo; private final GlBuffer vbo;
@ -53,10 +53,9 @@ public class MeshPool implements MeshAllocator {
* @param vao The vertex array object to attach the model to. * @param vao The vertex array object to attach the model to.
* @return A handle to the allocated model. * @return A handle to the allocated model.
*/ */
@Override public BufferedMesh alloc(Mesh mesh, GlVertexArray vao) {
public PooledModel alloc(Mesh mesh, GlVertexArray vao) { BufferedMesh bufferedModel = new BufferedMesh(vao, mesh, vertices);
PooledModel bufferedModel = new PooledModel(vao, mesh, vertices); vertices += mesh.getVertexCount();
vertices += mesh.vertexCount();
models.add(bufferedModel); models.add(bufferedModel);
pendingUpload.add(bufferedModel); pendingUpload.add(bufferedModel);
@ -84,17 +83,17 @@ public class MeshPool implements MeshAllocator {
private void processDeletions() { private void processDeletions() {
// remove deleted models // remove deleted models
models.removeIf(PooledModel::isDeleted); models.removeIf(BufferedMesh::isDeleted);
// re-evaluate first vertex for each model // re-evaluate first vertex for each model
int vertices = 0; int vertices = 0;
for (PooledModel model : models) { for (BufferedMesh model : models) {
if (model.first != vertices) if (model.first != vertices)
pendingUpload.add(model); pendingUpload.add(model);
model.first = vertices; model.first = vertices;
vertices += model.mesh.vertexCount(); vertices += model.mesh.getVertexCount();
} }
this.vertices = vertices; this.vertices = vertices;
@ -115,12 +114,12 @@ public class MeshPool implements MeshAllocator {
VertexWriter writer = vertexType.createWriter(buffer.unwrap()); VertexWriter writer = vertexType.createWriter(buffer.unwrap());
int vertices = 0; int vertices = 0;
for (PooledModel model : models) { for (BufferedMesh model : models) {
model.first = vertices; model.first = vertices;
model.buffer(writer); model.buffer(writer);
vertices += model.mesh.vertexCount(); vertices += model.mesh.getVertexCount();
} }
} catch (Exception e) { } catch (Exception e) {
@ -131,7 +130,7 @@ public class MeshPool implements MeshAllocator {
private void uploadPending() { private void uploadPending() {
try (MappedBuffer buffer = vbo.getBuffer()) { try (MappedBuffer buffer = vbo.getBuffer()) {
VertexWriter writer = vertexType.createWriter(buffer.unwrap()); VertexWriter writer = vertexType.createWriter(buffer.unwrap());
for (PooledModel model : pendingUpload) { for (BufferedMesh model : pendingUpload) {
model.buffer(writer); model.buffer(writer);
} }
pendingUpload.clear(); pendingUpload.clear();
@ -148,7 +147,7 @@ public class MeshPool implements MeshAllocator {
vbo.delete(); vbo.delete();
} }
public class PooledModel implements BufferedModel { public class BufferedMesh {
private final ElementBuffer ebo; private final ElementBuffer ebo;
private final GlVertexArray vao; private final GlVertexArray vao;
@ -158,38 +157,20 @@ public class MeshPool implements MeshAllocator {
private boolean deleted; private boolean deleted;
public PooledModel(GlVertexArray vao, Mesh mesh, int first) { public BufferedMesh(GlVertexArray vao, Mesh mesh, int first) {
this.vao = vao; this.vao = vao;
this.mesh = mesh; this.mesh = mesh;
this.first = first; this.first = first;
ebo = mesh.createEBO(); ebo = mesh.createEBO();
} }
@Override
public VertexType getType() {
return MeshPool.this.vertexType;
}
@Override
public int getVertexCount() {
return mesh.vertexCount();
}
@Override
public void setupState(GlVertexArray vao) {
vbo.bind();
vao.enableArrays(getAttributeCount());
vao.bindAttributes(0, vertexType.getLayout());
}
@Override
public void drawCall() { public void drawCall() {
ebo.bind();
GL32.glDrawElementsBaseVertex(GlPrimitive.TRIANGLES.glEnum, ebo.elementCount, ebo.eboIndexType.getGlEnum(), 0, first); GL32.glDrawElementsBaseVertex(GlPrimitive.TRIANGLES.glEnum, ebo.elementCount, ebo.eboIndexType.getGlEnum(), 0, first);
} }
@Override
public void drawInstances(int instanceCount) { public void drawInstances(int instanceCount) {
if (!valid()) return; if (mesh.getVertexCount() <= 0 || isDeleted()) return;
ebo.bind(); ebo.bind();
@ -198,12 +179,10 @@ public class MeshPool implements MeshAllocator {
GL32.glDrawElementsInstancedBaseVertex(GlPrimitive.TRIANGLES.glEnum, ebo.elementCount, ebo.eboIndexType.getGlEnum(), 0, instanceCount, first); GL32.glDrawElementsInstancedBaseVertex(GlPrimitive.TRIANGLES.glEnum, ebo.elementCount, ebo.eboIndexType.getGlEnum(), 0, instanceCount, first);
} }
@Override
public boolean isDeleted() { public boolean isDeleted() {
return deleted; return deleted;
} }
@Override
public void delete() { public void delete() {
setDirty(); setDirty();
anyToRemove = true; anyToRemove = true;
@ -214,7 +193,12 @@ public class MeshPool implements MeshAllocator {
writer.seekToVertex(first); writer.seekToVertex(first);
writer.writeVertexList(mesh.getReader()); writer.writeVertexList(mesh.getReader());
setupState(vao); vao.enableArrays(getAttributeCount());
vao.bindAttributes(0, vertexType.getLayout());
}
public int getAttributeCount() {
return MeshPool.this.vertexType.getLayout().getAttributeCount();
} }
} }

View file

@ -1,86 +0,0 @@
package com.jozufozu.flywheel.backend.model;
import static org.lwjgl.opengl.GL11.glDrawArrays;
import org.lwjgl.opengl.GL31;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.gl.GlPrimitive;
import com.jozufozu.flywheel.backend.gl.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.backend.gl.buffer.MappedGlBuffer;
import com.jozufozu.flywheel.core.model.Mesh;
public class VBOModel implements BufferedModel {
protected final Mesh mesh;
protected final GlPrimitive primitiveMode;
protected GlBuffer vbo;
protected boolean deleted;
public VBOModel(GlPrimitive primitiveMode, Mesh mesh) {
this.mesh = mesh;
this.primitiveMode = primitiveMode;
vbo = new MappedGlBuffer(GlBufferType.ARRAY_BUFFER);
vbo.bind();
// allocate the buffer on the gpu
vbo.ensureCapacity(mesh.size());
// mirror it in system memory, so we can write to it, and upload our model.
try (MappedBuffer buffer = vbo.getBuffer()) {
mesh.writeInto(buffer.unwrap());
} catch (Exception e) {
Flywheel.LOGGER.error(String.format("Error uploading model '%s':", mesh.name()), e);
}
vbo.unbind();
}
public boolean isDeleted() {
return deleted;
}
@Override
public VertexType getType() {
return mesh.getType();
}
public int getVertexCount() {
return mesh.vertexCount();
}
/**
* The VBO/VAO should be bound externally.
*/
public void setupState(GlVertexArray vao) {
vbo.bind();
vao.enableArrays(getAttributeCount());
vao.bindAttributes(0, getLayout());
}
public void drawCall() {
glDrawArrays(primitiveMode.glEnum, 0, mesh.vertexCount());
}
/**
* Draws many instances of this model, assuming the appropriate state is already bound.
*/
public void drawInstances(int instanceCount) {
if (!valid()) return;
GL31.glDrawArraysInstanced(primitiveMode.glEnum, 0, mesh.vertexCount(), instanceCount);
}
public void delete() {
if (deleted) return;
deleted = true;
vbo.delete();
}
}

View file

@ -1,7 +1,10 @@
package com.jozufozu.flywheel.core; package com.jozufozu.flywheel.core;
import java.util.Map;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import com.google.common.collect.ImmutableMap;
import com.jozufozu.flywheel.core.model.Mesh; import com.jozufozu.flywheel.core.model.Mesh;
import com.jozufozu.flywheel.core.model.ModelSupplier; import com.jozufozu.flywheel.core.model.ModelSupplier;
import com.jozufozu.flywheel.util.Lazy; import com.jozufozu.flywheel.util.Lazy;
@ -33,17 +36,12 @@ public class BasicModelSupplier implements ModelSupplier {
} }
@Override @Override
public Mesh get() { public Map<RenderType, Mesh> get() {
return supplier.get(); return ImmutableMap.of(renderType, supplier.get());
}
@Nonnull
public RenderType getRenderType() {
return renderType;
} }
public int getVertexCount() { public int getVertexCount() {
return supplier.map(Mesh::vertexCount) return supplier.map(Mesh::getVertexCount)
.orElse(0); .orElse(0);
} }

View file

@ -45,7 +45,7 @@ public class ModelPart implements Mesh {
} }
@Override @Override
public int vertexCount() { public int getVertexCount() {
return vertices; return vertices;
} }

View file

@ -23,17 +23,16 @@ public class BufferLayout {
private final int stride; private final int stride;
public BufferLayout(List<LayoutItem> layoutItems) { public BufferLayout(List<LayoutItem> layoutItems, int padding) {
ImmutableList.Builder<VertexAttribute> attributes = ImmutableList.builder(); ImmutableList.Builder<VertexAttribute> attributes = ImmutableList.builder();
stride = calculateStride(layoutItems);
for (LayoutItem item : layoutItems) { for (LayoutItem item : layoutItems) {
item.provideAttributes(attributes::add); item.provideAttributes(attributes::add);
} }
this.attributes = attributes.build(); this.attributes = attributes.build();
this.stride = calculateStride(this.attributes) + padding;
} }
public Collection<VertexAttribute> getAttributes() { public Collection<VertexAttribute> getAttributes() {
@ -52,9 +51,9 @@ public class BufferLayout {
return new Builder(); return new Builder();
} }
private static int calculateStride(List<LayoutItem> layoutItems) { private static int calculateStride(List<VertexAttribute> layoutItems) {
int stride = 0; int stride = 0;
for (LayoutItem spec : layoutItems) { for (VertexAttribute spec : layoutItems) {
stride += spec.getByteWidth(); stride += spec.getByteWidth();
} }
return stride; return stride;
@ -62,6 +61,7 @@ public class BufferLayout {
public static class Builder { public static class Builder {
private final ImmutableList.Builder<LayoutItem> allItems; private final ImmutableList.Builder<LayoutItem> allItems;
private int padding;
public Builder() { public Builder() {
allItems = ImmutableList.builder(); allItems = ImmutableList.builder();
@ -72,8 +72,13 @@ public class BufferLayout {
return this; return this;
} }
public Builder withPadding(int padding) {
this.padding = padding;
return this;
}
public BufferLayout build() { public BufferLayout build() {
return new BufferLayout(allItems.build()); return new BufferLayout(allItems.build(), padding);
} }
} }

View file

@ -19,7 +19,6 @@ public class CommonItems {
public static final PrimitiveItem LIGHT_SHORT = new PrimitiveItem(GlNumericType.USHORT, 2, true); public static final PrimitiveItem LIGHT_SHORT = new PrimitiveItem(GlNumericType.USHORT, 2, true);
public static final PrimitiveItem NORMALIZED_BYTE = new PrimitiveItem(GlNumericType.BYTE, 1, true); public static final PrimitiveItem NORMALIZED_BYTE = new PrimitiveItem(GlNumericType.BYTE, 1, true);
public static final LayoutItem PADDING_BYTE = new Padding(1);
public static final MatrixItem MAT3 = new MatrixItem(3, 3); public static final MatrixItem MAT3 = new MatrixItem(3, 3);
public static final MatrixItem MAT4 = new MatrixItem(4, 4); public static final MatrixItem MAT4 = new MatrixItem(4, 4);

View file

@ -8,6 +8,4 @@ public interface LayoutItem {
void provideAttributes(Consumer<VertexAttribute> consumer); void provideAttributes(Consumer<VertexAttribute> consumer);
int getByteWidth();
} }

View file

@ -14,9 +14,4 @@ public record MatrixItem(int rows, int cols) implements LayoutItem {
} }
} }
@Override
public int getByteWidth() {
return GlNumericType.FLOAT.getByteWidth() * rows * cols;
}
} }

View file

@ -1,19 +0,0 @@
package com.jozufozu.flywheel.core.layout;
import java.util.function.Consumer;
import com.jozufozu.flywheel.backend.gl.VertexAttribute;
record Padding(int bytes) implements LayoutItem {
@Override
public void provideAttributes(Consumer<VertexAttribute> consumer) {
}
@Override
public int getByteWidth() {
return bytes;
}
}

View file

@ -22,9 +22,4 @@ public class PrimitiveItem implements LayoutItem {
consumer.accept(attribute); consumer.accept(attribute);
} }
@Override
public int getByteWidth() {
return attribute.getByteWidth();
}
} }

View file

@ -39,7 +39,7 @@ public interface Mesh {
/** /**
* @return The number of vertices the model has. * @return The number of vertices the model has.
*/ */
default int vertexCount() { default int getVertexCount() {
return getReader().getVertexCount(); return getReader().getVertexCount();
} }
@ -59,14 +59,14 @@ public interface Mesh {
*/ */
default ElementBuffer createEBO() { default ElementBuffer createEBO() {
return QuadConverter.getInstance() return QuadConverter.getInstance()
.quads2Tris(vertexCount() / 4); .quads2Tris(getVertexCount() / 4);
} }
/** /**
* The size in bytes that this model's data takes up. * The size in bytes that this model's data takes up.
*/ */
default int size() { default int size() {
return getType().byteOffset(vertexCount()); return getType().byteOffset(getVertexCount());
} }
/** /**
@ -74,7 +74,7 @@ public interface Mesh {
* @return true if there are no vertices. * @return true if there are no vertices.
*/ */
default boolean empty() { default boolean empty() {
return vertexCount() == 0; return getVertexCount() == 0;
} }
default void writeInto(ByteBuffer buffer) { default void writeInto(ByteBuffer buffer) {

View file

@ -1,8 +1,12 @@
package com.jozufozu.flywheel.core.model; package com.jozufozu.flywheel.core.model;
import java.util.Map;
import net.minecraft.client.renderer.RenderType;
public interface ModelSupplier { public interface ModelSupplier {
Mesh get(); Map<RenderType, Mesh> get();
int getVertexCount(); int getVertexCount();
} }

View file

@ -0,0 +1,117 @@
package com.jozufozu.flywheel.core.model;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.function.Function;
import com.google.common.collect.ImmutableMap;
import com.jozufozu.flywheel.core.Formats;
import com.jozufozu.flywheel.core.virtual.VirtualEmptyBlockGetter;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.blaze3d.vertex.VertexFormat;
import net.minecraft.client.renderer.ItemBlockRenderTypes;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.block.ModelBlockRenderer;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraftforge.client.ForgeHooksClient;
import net.minecraftforge.client.model.data.EmptyModelData;
import net.minecraftforge.client.model.data.IModelData;
public class SeparatedWorldModelBuilder {
private PoseStack poseStack = new PoseStack();
private Map<BlockPos, IModelData> modelData = Collections.emptyMap();
private BlockAndTintGetter renderWorld = VirtualEmptyBlockGetter.INSTANCE;
private Collection<StructureTemplate.StructureBlockInfo> blocks = Collections.emptyList();
public Map<RenderType, Mesh> getMeshes() {
Map<RenderType, BufferBuilder> builders = new HashMap<>();
ModelBlockRenderer modelRenderer = ModelUtil.VANILLA_RENDERER.getModelRenderer();
buffer(modelRenderer, new Random(), type -> builders.computeIfAbsent(type, $ -> {
var out = new BufferBuilder(512);
out.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
return out;
}));
return builders.entrySet()
.stream()
.collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, e -> {
var b = e.getValue();
b.end();
return new BlockMesh(Formats.BLOCK.createReader(b), "");
}));
}
public void buffer(ModelBlockRenderer modelRenderer, Random random, Function<RenderType, VertexConsumer> consumer) {
ModelBlockRenderer.enableCaching();
for (StructureTemplate.StructureBlockInfo info : this.blocks) {
var state = info.state;
if (state.getRenderShape() != RenderShape.MODEL) continue;
var pos = info.pos;
var seed = state.getSeed(pos);
var data = this.modelData.getOrDefault(pos, EmptyModelData.INSTANCE);
var blockModel = ModelUtil.VANILLA_RENDERER.getBlockModel(state);
this.poseStack.pushPose();
this.poseStack.translate(pos.getX(), pos.getY(), pos.getZ());
for (RenderType type : RenderType.chunkBufferLayers()) {
if (!ItemBlockRenderTypes.canRenderInLayer(state, type)) {
continue;
}
var vertexConsumer = consumer.apply(type);
if (vertexConsumer == null) {
continue;
}
ForgeHooksClient.setRenderType(type);
modelRenderer.tesselateBlock(this.renderWorld, blockModel, state, pos, poseStack, vertexConsumer, true, random, seed, OverlayTexture.NO_OVERLAY, data);
}
this.poseStack.popPose();
}
ForgeHooksClient.setRenderType(null);
ModelBlockRenderer.clearCache();
}
public SeparatedWorldModelBuilder withRenderWorld(BlockAndTintGetter renderWorld) {
this.renderWorld = renderWorld;
return this;
}
public SeparatedWorldModelBuilder withBlocks(Collection<StructureTemplate.StructureBlockInfo> blocks) {
this.blocks = blocks;
return this;
}
public SeparatedWorldModelBuilder withModelData(Map<BlockPos, IModelData> modelData) {
this.modelData = modelData;
return this;
}
public SeparatedWorldModelBuilder withPoseStack(PoseStack poseStack) {
this.poseStack = poseStack;
return this;
}
}

View file

@ -77,7 +77,7 @@ public final class WorldModelBuilder implements Bufferable {
return this; return this;
} }
public BlockMesh finish() { public BlockMesh intoMesh(String name) {
return new BlockMesh(this, "name"); return new BlockMesh(this, name);
} }
} }

View file

@ -18,8 +18,8 @@ public class BlockVertex implements VertexType {
CommonItems.RGBA, CommonItems.RGBA,
CommonItems.UV, CommonItems.UV,
CommonItems.LIGHT_SHORT, CommonItems.LIGHT_SHORT,
CommonItems.NORMAL, CommonItems.NORMAL)
CommonItems.PADDING_BYTE) .withPadding(1)
.build(); .build();
@Override @Override