mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-01-07 12:56:31 +01:00
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:
parent
d5e9e044ec
commit
be60eae9af
25 changed files with 271 additions and 461 deletions
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -3,4 +3,6 @@ package com.jozufozu.flywheel.backend.instancing.instancing;
|
|||
public interface Renderable {
|
||||
|
||||
void render();
|
||||
|
||||
boolean shouldRemove();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ public class ModelPart implements Mesh {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int vertexCount() {
|
||||
public int getVertexCount() {
|
||||
return vertices;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -8,6 +8,4 @@ public interface LayoutItem {
|
|||
|
||||
void provideAttributes(Consumer<VertexAttribute> consumer);
|
||||
|
||||
int getByteWidth();
|
||||
|
||||
}
|
||||
|
|
|
@ -14,9 +14,4 @@ public record MatrixItem(int rows, int cols) implements LayoutItem {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getByteWidth() {
|
||||
return GlNumericType.FLOAT.getByteWidth() * rows * cols;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -22,9 +22,4 @@ public class PrimitiveItem implements LayoutItem {
|
|||
consumer.accept(attribute);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getByteWidth() {
|
||||
return attribute.getByteWidth();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue