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

View file

@ -1,7 +1,6 @@
package com.jozufozu.flywheel.backend.instancing.instancing;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
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.Material;
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 net.minecraft.client.renderer.RenderType;
@ -54,12 +53,6 @@ public class InstancedMaterial<D extends InstanceData> implements Material<D> {
.sum();
}
public boolean nothingToRender() {
return models.size() > 0 && models.values()
.stream()
.allMatch(InstancedModel::isEmpty);
}
public void delete() {
models.values().forEach(InstancedModel::delete);
models.clear();
@ -76,7 +69,7 @@ public class InstancedMaterial<D extends InstanceData> implements Material<D> {
.forEach(GPUInstancer::clear);
}
public void init(MeshAllocator allocator) {
public void init(MeshPool allocator) {
for (var instanced : uninitialized) {
var map = instanced.init(allocator);
@ -86,14 +79,6 @@ public class InstancedMaterial<D extends InstanceData> implements Material<D> {
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) {
var instancer = new InstancedModel<>(new GPUInstancer<>(type), model);
uninitialized.add(instancer);

View file

@ -2,61 +2,88 @@ package com.jozufozu.flywheel.backend.instancing.instancing;
import java.util.Map;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableMap;
import com.jozufozu.flywheel.api.InstanceData;
import com.jozufozu.flywheel.backend.gl.GlVertexArray;
import com.jozufozu.flywheel.backend.model.BufferedModel;
import com.jozufozu.flywheel.backend.model.MeshAllocator;
import com.jozufozu.flywheel.backend.model.MeshPool;
import com.jozufozu.flywheel.core.model.Mesh;
import com.jozufozu.flywheel.core.model.ModelSupplier;
import com.jozufozu.flywheel.util.Pair;
import net.minecraft.client.renderer.RenderType;
public class InstancedModel<D extends InstanceData> {
GPUInstancer<D> instancer;
ModelSupplier model;
@Nullable
private BufferedModel bufferedMesh;
@Nullable
private GlVertexArray vao;
final GPUInstancer<D> instancer;
final ModelSupplier model;
private Map<RenderType, Layer> layers;
public InstancedModel(GPUInstancer<D> instancer, ModelSupplier model) {
this.instancer = instancer;
this.model = model;
}
public Map<RenderType, Renderable> init(MeshAllocator allocator) {
public Map<RenderType, ? extends Renderable> init(MeshPool allocator) {
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() {
if (invalid()) return;
private class Layer implements Renderable {
vao.bind();
final RenderType type;
MeshPool.BufferedMesh bufferedMesh;
GlVertexArray vao;
instancer.renderSetup(vao);
if (instancer.glInstanceCount > 0) {
bufferedMesh.drawInstances(instancer.glInstanceCount);
private Layer(MeshPool allocator, RenderType type, Mesh mesh) {
this.type = type;
vao = new GlVertexArray();
bufferedMesh = allocator.alloc(mesh, vao);
instancer.attributeBaseIndex = bufferedMesh.getAttributeCount();
vao.enableArrays(bufferedMesh.getAttributeCount() + instancer.instanceFormat.getAttributeCount());
}
// persistent mapping sync point
instancer.vbo.doneForThisFrame();
}
@Override
public void render() {
if (invalid()) return;
private boolean invalid() {
return instancer.vbo == null || bufferedMesh == null || vao == null;
vao.bind();
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() {
@ -71,19 +98,14 @@ public class InstancedModel<D extends InstanceData> {
return model.getVertexCount() * instancer.glInstanceCount;
}
public boolean isEmpty() {
return instancer.isEmpty();
}
public void delete() {
if (invalid()) return;
if (instancer.vbo == null) return;
vao.delete();
bufferedMesh.delete();
instancer.vbo.delete();
vao = null;
bufferedMesh = 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.struct.Instanced;
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.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.core.Formats;
import com.jozufozu.flywheel.core.RenderContext;
@ -41,7 +38,7 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
protected BlockPos originCoordinate = BlockPos.ZERO;
protected final ProgramCompiler<P> context;
private MeshAllocator allocator;
private MeshPool allocator;
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()) {
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();
setup(type, camX, camY, camZ, viewProjection, instanceType.getProgramSpec());
@ -118,7 +118,7 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
instanceCount += material.getInstanceCount();
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) {
checkOriginDistance(info);
MeshAllocator allocator = getModelAllocator();
MeshPool allocator = getModelAllocator();
for (InstancedMaterial<?> material : materials.values()) {
material.init(allocator);
@ -173,10 +173,7 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
toRender.addAll(material.renderables.keySet());
}
if (allocator instanceof MeshPool pool) {
// ...and then flush the model arena in case anything was marked for upload
pool.flush();
}
allocator.flush();
}
@ -211,20 +208,17 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
info.add("Origin: " + originCoordinate.getX() + ", " + originCoordinate.getY() + ", " + originCoordinate.getZ());
}
private MeshAllocator getModelAllocator() {
private MeshPool getModelAllocator() {
if (allocator == null) {
allocator = createAllocator();
}
return this.allocator;
}
private static MeshAllocator createAllocator() {
if (GlCompat.getInstance()
.onAMDWindows()) {
return FallbackAllocator.INSTANCE;
} else {
return new MeshPool(Formats.POS_TEX_NORMAL);
}
private static MeshPool createAllocator() {
// FIXME: Windows AMD Drivers don't like ..BaseVertex
return new MeshPool(Formats.POS_TEX_NORMAL);
}
@FunctionalInterface

View file

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

View file

@ -1,52 +1,31 @@
package com.jozufozu.flywheel.backend.model;
import com.jozufozu.flywheel.backend.gl.GlVertexArray;
import com.jozufozu.flywheel.core.model.Mesh;
import com.jozufozu.flywheel.core.model.BlockMesh;
public class ArrayModelRenderer {
private final Mesh mesh;
protected GlVertexArray vao;
protected BufferedModel vbo;
protected boolean initialized;
protected final GlVertexArray vao;
protected final MeshPool.BufferedMesh mesh;
public ArrayModelRenderer(Mesh mesh) {
this.mesh = mesh;
public ArrayModelRenderer(BlockMesh mesh, MeshPool meshPool) {
this.vao = new GlVertexArray();
this.mesh = meshPool.alloc(mesh, this.vao);
}
/**
* Renders this model, checking first if there is anything to render.
*/
public void draw() {
if (!initialized) init();
if (!isValid()) return;
if (mesh.isDeleted()) return;
vao.bind();
vbo.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();
mesh.drawCall();
}
public void delete() {
if (vbo != null)
vbo.delete();
mesh.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.core.model.Mesh;
public class MeshPool implements MeshAllocator {
public class MeshPool {
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;
@ -53,10 +53,9 @@ public class MeshPool implements MeshAllocator {
* @param vao The vertex array object to attach the model to.
* @return A handle to the allocated model.
*/
@Override
public PooledModel alloc(Mesh mesh, GlVertexArray vao) {
PooledModel bufferedModel = new PooledModel(vao, mesh, vertices);
vertices += mesh.vertexCount();
public BufferedMesh alloc(Mesh mesh, GlVertexArray vao) {
BufferedMesh bufferedModel = new BufferedMesh(vao, mesh, vertices);
vertices += mesh.getVertexCount();
models.add(bufferedModel);
pendingUpload.add(bufferedModel);
@ -84,17 +83,17 @@ public class MeshPool implements MeshAllocator {
private void processDeletions() {
// remove deleted models
models.removeIf(PooledModel::isDeleted);
models.removeIf(BufferedMesh::isDeleted);
// re-evaluate first vertex for each model
int vertices = 0;
for (PooledModel model : models) {
for (BufferedMesh model : models) {
if (model.first != vertices)
pendingUpload.add(model);
model.first = vertices;
vertices += model.mesh.vertexCount();
vertices += model.mesh.getVertexCount();
}
this.vertices = vertices;
@ -115,12 +114,12 @@ public class MeshPool implements MeshAllocator {
VertexWriter writer = vertexType.createWriter(buffer.unwrap());
int vertices = 0;
for (PooledModel model : models) {
for (BufferedMesh model : models) {
model.first = vertices;
model.buffer(writer);
vertices += model.mesh.vertexCount();
vertices += model.mesh.getVertexCount();
}
} catch (Exception e) {
@ -131,7 +130,7 @@ public class MeshPool implements MeshAllocator {
private void uploadPending() {
try (MappedBuffer buffer = vbo.getBuffer()) {
VertexWriter writer = vertexType.createWriter(buffer.unwrap());
for (PooledModel model : pendingUpload) {
for (BufferedMesh model : pendingUpload) {
model.buffer(writer);
}
pendingUpload.clear();
@ -148,7 +147,7 @@ public class MeshPool implements MeshAllocator {
vbo.delete();
}
public class PooledModel implements BufferedModel {
public class BufferedMesh {
private final ElementBuffer ebo;
private final GlVertexArray vao;
@ -158,38 +157,20 @@ public class MeshPool implements MeshAllocator {
private boolean deleted;
public PooledModel(GlVertexArray vao, Mesh mesh, int first) {
public BufferedMesh(GlVertexArray vao, Mesh mesh, int first) {
this.vao = vao;
this.mesh = mesh;
this.first = first;
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() {
ebo.bind();
GL32.glDrawElementsBaseVertex(GlPrimitive.TRIANGLES.glEnum, ebo.elementCount, ebo.eboIndexType.getGlEnum(), 0, first);
}
@Override
public void drawInstances(int instanceCount) {
if (!valid()) return;
if (mesh.getVertexCount() <= 0 || isDeleted()) return;
ebo.bind();
@ -198,12 +179,10 @@ public class MeshPool implements MeshAllocator {
GL32.glDrawElementsInstancedBaseVertex(GlPrimitive.TRIANGLES.glEnum, ebo.elementCount, ebo.eboIndexType.getGlEnum(), 0, instanceCount, first);
}
@Override
public boolean isDeleted() {
return deleted;
}
@Override
public void delete() {
setDirty();
anyToRemove = true;
@ -214,7 +193,12 @@ public class MeshPool implements MeshAllocator {
writer.seekToVertex(first);
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;
import java.util.Map;
import javax.annotation.Nonnull;
import com.google.common.collect.ImmutableMap;
import com.jozufozu.flywheel.core.model.Mesh;
import com.jozufozu.flywheel.core.model.ModelSupplier;
import com.jozufozu.flywheel.util.Lazy;
@ -33,17 +36,12 @@ public class BasicModelSupplier implements ModelSupplier {
}
@Override
public Mesh get() {
return supplier.get();
}
@Nonnull
public RenderType getRenderType() {
return renderType;
public Map<RenderType, Mesh> get() {
return ImmutableMap.of(renderType, supplier.get());
}
public int getVertexCount() {
return supplier.map(Mesh::vertexCount)
return supplier.map(Mesh::getVertexCount)
.orElse(0);
}

View file

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

View file

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

View file

@ -8,6 +8,4 @@ public interface LayoutItem {
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);
}
@Override
public int getByteWidth() {
return attribute.getByteWidth();
}
}

View file

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

View file

@ -1,8 +1,12 @@
package com.jozufozu.flywheel.core.model;
import java.util.Map;
import net.minecraft.client.renderer.RenderType;
public interface ModelSupplier {
Mesh get();
Map<RenderType, Mesh> get();
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;
}
public BlockMesh finish() {
return new BlockMesh(this, "name");
public BlockMesh intoMesh(String name) {
return new BlockMesh(this, name);
}
}

View file

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