mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2024-12-27 15:36:24 +01:00
Instancer interface
- Entire material/model system finally cleaned up
This commit is contained in:
parent
7e65eaa00d
commit
aa83cd5c60
12 changed files with 361 additions and 324 deletions
|
@ -0,0 +1,325 @@
|
|||
package com.jozufozu.flywheel.backend.instancing;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
|
||||
import com.jozufozu.flywheel.backend.Backend;
|
||||
import com.jozufozu.flywheel.backend.gl.GlVertexArray;
|
||||
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
|
||||
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.model.ModelAllocator;
|
||||
import com.jozufozu.flywheel.backend.model.IBufferedModel;
|
||||
import com.jozufozu.flywheel.core.model.IModel;
|
||||
import com.jozufozu.flywheel.util.AttribUtil;
|
||||
|
||||
/**
|
||||
* An instancer is how you interact with an instanced model.
|
||||
* <p>
|
||||
* Instanced models can have many copies, and on most systems it's very fast to draw all of the copies at once.
|
||||
* There is no limit to how many copies an instanced model can have.
|
||||
* Each copy is represented by an InstanceData object.
|
||||
* </p>
|
||||
* <p>
|
||||
* When you call {@link #createInstance()} you are given an InstanceData object that you can manipulate however
|
||||
* you want. The changes you make to the InstanceData object are automatically made visible, and persistent.
|
||||
* Changing the position of your InstanceData object every frame means that that copy of the model will be in a
|
||||
* different position in the world each frame. Setting the position of your InstanceData once and not touching it
|
||||
* again means that your model will be in the same position in the world every frame. This persistence is useful
|
||||
* because it means the properties of your model don't have to be re-evaluated every frame.
|
||||
* </p>
|
||||
*
|
||||
* @param <D> the data that represents a copy of the instanced model.
|
||||
*/
|
||||
public class GPUInstancer<D extends InstanceData> implements Instancer<D> {
|
||||
|
||||
private final ModelAllocator modelAllocator;
|
||||
private final IModel modelData;
|
||||
private final VertexFormat instanceFormat;
|
||||
private final IInstanceFactory<D> factory;
|
||||
|
||||
private IBufferedModel model;
|
||||
private GlVertexArray vao;
|
||||
private GlBuffer instanceVBO;
|
||||
private int glBufferSize = -1;
|
||||
private int glInstanceCount = 0;
|
||||
private boolean deleted;
|
||||
private boolean initialized;
|
||||
|
||||
private final ArrayList<D> data = new ArrayList<>();
|
||||
|
||||
boolean anyToRemove;
|
||||
boolean anyToUpdate;
|
||||
|
||||
public GPUInstancer(ModelAllocator modelAllocator, IModel model, IInstanceFactory<D> factory, VertexFormat instanceFormat) {
|
||||
this.modelAllocator = modelAllocator;
|
||||
this.modelData = model;
|
||||
this.factory = factory;
|
||||
this.instanceFormat = instanceFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a handle to a new copy of this model.
|
||||
*/
|
||||
@Override
|
||||
public D createInstance() {
|
||||
return _add(factory.create(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a data from another Instancer to this.
|
||||
*
|
||||
* This has the effect of swapping out one model for another.
|
||||
* @param inOther the data associated with a different model.
|
||||
*/
|
||||
@Override
|
||||
public void stealInstance(D inOther) {
|
||||
if (inOther.owner == this) return;
|
||||
|
||||
inOther.delete();
|
||||
// sike, we want to keep it, changing the owner reference will still delete it in the other
|
||||
inOther.removed = false;
|
||||
_add(inOther);
|
||||
}
|
||||
|
||||
public void render() {
|
||||
if (invalid()) return;
|
||||
|
||||
vao.bind();
|
||||
renderSetup();
|
||||
|
||||
if (glInstanceCount > 0) model.drawInstances(glInstanceCount);
|
||||
|
||||
// persistent mapping sync point
|
||||
instanceVBO.doneForThisFrame();
|
||||
|
||||
vao.unbind();
|
||||
}
|
||||
|
||||
private boolean invalid() {
|
||||
return deleted || model == null;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
if (isInitialized()) return;
|
||||
|
||||
initialized = true;
|
||||
|
||||
vao = new GlVertexArray();
|
||||
|
||||
model = modelAllocator.alloc(modelData, arenaModel -> {
|
||||
vao.bind();
|
||||
|
||||
model.setupState();
|
||||
|
||||
vao.unbind();
|
||||
});
|
||||
|
||||
vao.bind();
|
||||
|
||||
instanceVBO = GlBuffer.requestPersistent(GlBufferType.ARRAY_BUFFER);
|
||||
AttribUtil.enableArrays(model.getAttributeCount() + instanceFormat.getAttributeCount());
|
||||
|
||||
vao.unbind();
|
||||
}
|
||||
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return !anyToUpdate && !anyToRemove && glInstanceCount == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all instance data without freeing resources.
|
||||
*/
|
||||
public void clear() {
|
||||
data.clear();
|
||||
anyToRemove = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Free acquired resources. All other Instancer methods are undefined behavior after calling delete.
|
||||
*/
|
||||
public void delete() {
|
||||
if (invalid()) return;
|
||||
|
||||
deleted = true;
|
||||
|
||||
model.delete();
|
||||
|
||||
instanceVBO.delete();
|
||||
vao.delete();
|
||||
}
|
||||
|
||||
private D _add(D instanceData) {
|
||||
instanceData.owner = this;
|
||||
|
||||
instanceData.dirty = true;
|
||||
anyToUpdate = true;
|
||||
synchronized (data) {
|
||||
data.add(instanceData);
|
||||
}
|
||||
|
||||
return instanceData;
|
||||
}
|
||||
|
||||
protected void renderSetup() {
|
||||
if (anyToRemove) {
|
||||
removeDeletedInstances();
|
||||
}
|
||||
|
||||
instanceVBO.bind();
|
||||
if (!realloc()) {
|
||||
|
||||
if (anyToRemove) {
|
||||
clearBufferTail();
|
||||
}
|
||||
|
||||
if (anyToUpdate) {
|
||||
updateBuffer();
|
||||
}
|
||||
|
||||
glInstanceCount = data.size();
|
||||
}
|
||||
|
||||
instanceVBO.unbind();
|
||||
|
||||
anyToRemove = anyToUpdate = false;
|
||||
}
|
||||
|
||||
private void clearBufferTail() {
|
||||
int size = data.size();
|
||||
final int offset = size * instanceFormat.getStride();
|
||||
final int length = glBufferSize - offset;
|
||||
if (length > 0) {
|
||||
instanceVBO.getBuffer(offset, length)
|
||||
.putByteArray(new byte[length])
|
||||
.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBuffer() {
|
||||
final int size = data.size();
|
||||
|
||||
if (size <= 0) return;
|
||||
|
||||
final int stride = instanceFormat.getStride();
|
||||
final BitSet dirtySet = getDirtyBitSet();
|
||||
|
||||
if (dirtySet.isEmpty()) return;
|
||||
|
||||
final int firstDirty = dirtySet.nextSetBit(0);
|
||||
final int lastDirty = dirtySet.previousSetBit(size);
|
||||
|
||||
final int offset = firstDirty * stride;
|
||||
final int length = (1 + lastDirty - firstDirty) * stride;
|
||||
|
||||
if (length > 0) {
|
||||
MappedBuffer mapped = instanceVBO.getBuffer(offset, length);
|
||||
|
||||
dirtySet.stream()
|
||||
.forEach(i -> {
|
||||
final D d = data.get(i);
|
||||
|
||||
mapped.position(i * stride);
|
||||
d.write(mapped);
|
||||
});
|
||||
mapped.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private BitSet getDirtyBitSet() {
|
||||
final int size = data.size();
|
||||
final BitSet dirtySet = new BitSet(size);
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
D element = data.get(i);
|
||||
if (element.dirty) {
|
||||
dirtySet.set(i);
|
||||
|
||||
element.dirty = false;
|
||||
}
|
||||
}
|
||||
return dirtySet;
|
||||
}
|
||||
|
||||
private boolean realloc() {
|
||||
int size = this.data.size();
|
||||
int stride = instanceFormat.getStride();
|
||||
int requiredSize = size * stride;
|
||||
if (requiredSize > glBufferSize) {
|
||||
glBufferSize = requiredSize + stride * 16;
|
||||
instanceVBO.alloc(glBufferSize);
|
||||
|
||||
MappedBuffer buffer = instanceVBO.getBuffer(0, glBufferSize);
|
||||
for (D datum : data) {
|
||||
datum.write(buffer);
|
||||
}
|
||||
buffer.flush();
|
||||
|
||||
glInstanceCount = size;
|
||||
|
||||
informAttribDivisors();
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void removeDeletedInstances() {
|
||||
// Figure out which elements are to be removed.
|
||||
final int oldSize = this.data.size();
|
||||
int removeCount = 0;
|
||||
final BitSet removeSet = new BitSet(oldSize);
|
||||
for (int i = 0; i < oldSize; i++) {
|
||||
final D element = this.data.get(i);
|
||||
if (element.removed || element.owner != this) {
|
||||
removeSet.set(i);
|
||||
removeCount++;
|
||||
}
|
||||
}
|
||||
|
||||
final int newSize = oldSize - removeCount;
|
||||
|
||||
// shift surviving elements left over the spaces left by removed elements
|
||||
for (int i = 0, j = 0; (i < oldSize) && (j < newSize); i++, j++) {
|
||||
i = removeSet.nextClearBit(i);
|
||||
|
||||
if (i != j) {
|
||||
D element = data.get(i);
|
||||
data.set(j, element);
|
||||
element.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
anyToUpdate = true;
|
||||
|
||||
data.subList(newSize, oldSize)
|
||||
.clear();
|
||||
|
||||
}
|
||||
|
||||
private void informAttribDivisors() {
|
||||
int staticAttributes = model.getAttributeCount();
|
||||
instanceFormat.vertexAttribPointers(staticAttributes);
|
||||
|
||||
for (int i = 0; i < instanceFormat.getAttributeCount(); i++) {
|
||||
Backend.getInstance().compat.instancedArrays.vertexAttribDivisor(i + staticAttributes, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markDirty(InstanceData instanceData) {
|
||||
anyToUpdate = true;
|
||||
instanceData.dirty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markRemoval(InstanceData instanceData) {
|
||||
anyToRemove = true;
|
||||
instanceData.removed = true;
|
||||
}
|
||||
}
|
|
@ -16,13 +16,11 @@ public abstract class InstanceData {
|
|||
public abstract void write(MappedBuffer buf);
|
||||
|
||||
public void markDirty() {
|
||||
owner.anyToUpdate = true;
|
||||
dirty = true;
|
||||
owner.markDirty(this);
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
owner.anyToRemove = true;
|
||||
removed = true;
|
||||
owner.markRemoval(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,71 +1,10 @@
|
|||
package com.jozufozu.flywheel.backend.instancing;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
|
||||
import com.jozufozu.flywheel.backend.Backend;
|
||||
import com.jozufozu.flywheel.backend.gl.GlVertexArray;
|
||||
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
|
||||
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.material.MaterialSpec;
|
||||
import com.jozufozu.flywheel.backend.model.ModelAllocator;
|
||||
import com.jozufozu.flywheel.backend.model.IBufferedModel;
|
||||
import com.jozufozu.flywheel.core.model.IModel;
|
||||
import com.jozufozu.flywheel.util.AttribUtil;
|
||||
|
||||
/**
|
||||
* An instancer is how you interact with an instanced model.
|
||||
* <p>
|
||||
* Instanced models can have many copies, and on most systems it's very fast to draw all of the copies at once.
|
||||
* There is no limit to how many copies an instanced model can have.
|
||||
* Each copy is represented by an InstanceData object.
|
||||
* </p>
|
||||
* <p>
|
||||
* When you call {@link #createInstance()} you are given an InstanceData object that you can manipulate however
|
||||
* you want. The changes you make to the InstanceData object are automatically made visible, and persistent.
|
||||
* Changing the position of your InstanceData object every frame means that that copy of the model will be in a
|
||||
* different position in the world each frame. Setting the position of your InstanceData once and not touching it
|
||||
* again means that your model will be in the same position in the world every frame. This persistence is useful
|
||||
* because it means the properties of your model don't have to be re-evaluated every frame.
|
||||
* </p>
|
||||
*
|
||||
* @param <D> the data that represents a copy of the instanced model.
|
||||
*/
|
||||
public class Instancer<D extends InstanceData> {
|
||||
|
||||
private final ModelAllocator modelAllocator;
|
||||
private final IModel modelData;
|
||||
private final VertexFormat instanceFormat;
|
||||
private final IInstanceFactory<D> factory;
|
||||
|
||||
private IBufferedModel model;
|
||||
private GlVertexArray vao;
|
||||
private GlBuffer instanceVBO;
|
||||
private int glBufferSize = -1;
|
||||
private int glInstanceCount = 0;
|
||||
private boolean deleted;
|
||||
private boolean initialized;
|
||||
|
||||
private final ArrayList<D> data = new ArrayList<>();
|
||||
|
||||
boolean anyToRemove;
|
||||
boolean anyToUpdate;
|
||||
|
||||
public Instancer(ModelAllocator modelAllocator, IModel model, IInstanceFactory<D> factory, VertexFormat instanceFormat) {
|
||||
this.modelAllocator = modelAllocator;
|
||||
this.modelData = model;
|
||||
this.factory = factory;
|
||||
this.instanceFormat = instanceFormat;
|
||||
}
|
||||
|
||||
public interface Instancer<D extends InstanceData> {
|
||||
/**
|
||||
* @return a handle to a new copy of this model.
|
||||
*/
|
||||
public D createInstance() {
|
||||
return _add(factory.create(this));
|
||||
}
|
||||
D createInstance();
|
||||
|
||||
/**
|
||||
* Copy a data from another Instancer to this.
|
||||
|
@ -73,239 +12,9 @@ public class Instancer<D extends InstanceData> {
|
|||
* This has the effect of swapping out one model for another.
|
||||
* @param inOther the data associated with a different model.
|
||||
*/
|
||||
public void stealInstance(D inOther) {
|
||||
if (inOther.owner == this) return;
|
||||
void stealInstance(D inOther);
|
||||
|
||||
inOther.owner.anyToRemove = true;
|
||||
_add(inOther);
|
||||
}
|
||||
|
||||
public void render() {
|
||||
if (invalid()) return;
|
||||
|
||||
vao.bind();
|
||||
renderSetup();
|
||||
|
||||
if (glInstanceCount > 0) model.drawInstances(glInstanceCount);
|
||||
|
||||
// persistent mapping sync point
|
||||
instanceVBO.doneForThisFrame();
|
||||
|
||||
vao.unbind();
|
||||
}
|
||||
|
||||
private boolean invalid() {
|
||||
return deleted || model == null;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
if (isInitialized()) return;
|
||||
|
||||
initialized = true;
|
||||
|
||||
vao = new GlVertexArray();
|
||||
|
||||
model = modelAllocator.alloc(modelData, arenaModel -> {
|
||||
vao.bind();
|
||||
|
||||
model.setupState();
|
||||
|
||||
vao.unbind();
|
||||
});
|
||||
|
||||
vao.bind();
|
||||
|
||||
instanceVBO = GlBuffer.requestPersistent(GlBufferType.ARRAY_BUFFER);
|
||||
AttribUtil.enableArrays(model.getAttributeCount() + instanceFormat.getAttributeCount());
|
||||
|
||||
vao.unbind();
|
||||
}
|
||||
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return !anyToUpdate && !anyToRemove && glInstanceCount == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all instance data without freeing resources.
|
||||
*/
|
||||
public void clear() {
|
||||
data.clear();
|
||||
anyToRemove = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Free acquired resources. All other Instancer methods are undefined behavior after calling delete.
|
||||
*/
|
||||
public void delete() {
|
||||
if (invalid()) return;
|
||||
|
||||
deleted = true;
|
||||
|
||||
model.delete();
|
||||
|
||||
instanceVBO.delete();
|
||||
vao.delete();
|
||||
}
|
||||
|
||||
private D _add(D instanceData) {
|
||||
instanceData.owner = this;
|
||||
|
||||
instanceData.dirty = true;
|
||||
anyToUpdate = true;
|
||||
synchronized (data) {
|
||||
data.add(instanceData);
|
||||
}
|
||||
|
||||
return instanceData;
|
||||
}
|
||||
|
||||
protected void renderSetup() {
|
||||
if (anyToRemove) {
|
||||
removeDeletedInstances();
|
||||
}
|
||||
|
||||
instanceVBO.bind();
|
||||
if (!realloc()) {
|
||||
|
||||
if (anyToRemove) {
|
||||
clearBufferTail();
|
||||
}
|
||||
|
||||
if (anyToUpdate) {
|
||||
updateBuffer();
|
||||
}
|
||||
|
||||
glInstanceCount = data.size();
|
||||
}
|
||||
|
||||
instanceVBO.unbind();
|
||||
|
||||
anyToRemove = anyToUpdate = false;
|
||||
}
|
||||
|
||||
private void clearBufferTail() {
|
||||
int size = data.size();
|
||||
final int offset = size * instanceFormat.getStride();
|
||||
final int length = glBufferSize - offset;
|
||||
if (length > 0) {
|
||||
instanceVBO.getBuffer(offset, length)
|
||||
.putByteArray(new byte[length])
|
||||
.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBuffer() {
|
||||
final int size = data.size();
|
||||
|
||||
if (size <= 0) return;
|
||||
|
||||
final int stride = instanceFormat.getStride();
|
||||
final BitSet dirtySet = getDirtyBitSet();
|
||||
|
||||
if (dirtySet.isEmpty()) return;
|
||||
|
||||
final int firstDirty = dirtySet.nextSetBit(0);
|
||||
final int lastDirty = dirtySet.previousSetBit(size);
|
||||
|
||||
final int offset = firstDirty * stride;
|
||||
final int length = (1 + lastDirty - firstDirty) * stride;
|
||||
|
||||
if (length > 0) {
|
||||
MappedBuffer mapped = instanceVBO.getBuffer(offset, length);
|
||||
|
||||
dirtySet.stream()
|
||||
.forEach(i -> {
|
||||
final D d = data.get(i);
|
||||
|
||||
mapped.position(i * stride);
|
||||
d.write(mapped);
|
||||
});
|
||||
mapped.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private BitSet getDirtyBitSet() {
|
||||
final int size = data.size();
|
||||
final BitSet dirtySet = new BitSet(size);
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
D element = data.get(i);
|
||||
if (element.dirty) {
|
||||
dirtySet.set(i);
|
||||
|
||||
element.dirty = false;
|
||||
}
|
||||
}
|
||||
return dirtySet;
|
||||
}
|
||||
|
||||
private boolean realloc() {
|
||||
int size = this.data.size();
|
||||
int stride = instanceFormat.getStride();
|
||||
int requiredSize = size * stride;
|
||||
if (requiredSize > glBufferSize) {
|
||||
glBufferSize = requiredSize + stride * 16;
|
||||
instanceVBO.alloc(glBufferSize);
|
||||
|
||||
MappedBuffer buffer = instanceVBO.getBuffer(0, glBufferSize);
|
||||
for (D datum : data) {
|
||||
datum.write(buffer);
|
||||
}
|
||||
buffer.flush();
|
||||
|
||||
glInstanceCount = size;
|
||||
|
||||
informAttribDivisors();
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void removeDeletedInstances() {
|
||||
// Figure out which elements are to be removed.
|
||||
final int oldSize = this.data.size();
|
||||
int removeCount = 0;
|
||||
final BitSet removeSet = new BitSet(oldSize);
|
||||
for (int i = 0; i < oldSize; i++) {
|
||||
final D element = this.data.get(i);
|
||||
if (element.removed || element.owner != this) {
|
||||
removeSet.set(i);
|
||||
removeCount++;
|
||||
}
|
||||
}
|
||||
|
||||
final int newSize = oldSize - removeCount;
|
||||
|
||||
// shift surviving elements left over the spaces left by removed elements
|
||||
for (int i = 0, j = 0; (i < oldSize) && (j < newSize); i++, j++) {
|
||||
i = removeSet.nextClearBit(i);
|
||||
|
||||
if (i != j) {
|
||||
D element = data.get(i);
|
||||
data.set(j, element);
|
||||
element.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
anyToUpdate = true;
|
||||
|
||||
data.subList(newSize, oldSize)
|
||||
.clear();
|
||||
|
||||
}
|
||||
|
||||
private void informAttribDivisors() {
|
||||
int staticAttributes = model.getAttributeCount();
|
||||
instanceFormat.vertexAttribPointers(staticAttributes);
|
||||
|
||||
for (int i = 0; i < instanceFormat.getAttributeCount(); i++) {
|
||||
Backend.getInstance().compat.instancedArrays.vertexAttribDivisor(i + staticAttributes, 1);
|
||||
}
|
||||
}
|
||||
void markDirty(InstanceData instanceData);
|
||||
|
||||
void markRemoval(InstanceData instanceData);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package com.jozufozu.flywheel.backend.instancing.tile;
|
|||
import com.jozufozu.flywheel.backend.instancing.IDynamicInstance;
|
||||
import com.jozufozu.flywheel.backend.instancing.AbstractInstance;
|
||||
import com.jozufozu.flywheel.backend.instancing.ITickableInstance;
|
||||
import com.jozufozu.flywheel.backend.material.InstanceMaterial;
|
||||
import com.jozufozu.flywheel.backend.material.Material;
|
||||
import com.jozufozu.flywheel.backend.material.MaterialManager;
|
||||
import com.jozufozu.flywheel.core.Materials;
|
||||
import com.jozufozu.flywheel.core.materials.ModelData;
|
||||
|
@ -76,11 +76,11 @@ public abstract class TileEntityInstance<T extends TileEntity> extends AbstractI
|
|||
return pos;
|
||||
}
|
||||
|
||||
protected InstanceMaterial<ModelData> getTransformMaterial() {
|
||||
protected Material<ModelData> getTransformMaterial() {
|
||||
return materialManager.defaultCutout().material(Materials.TRANSFORMED);
|
||||
}
|
||||
|
||||
protected InstanceMaterial<OrientedData> getOrientedMaterial() {
|
||||
protected Material<OrientedData> getOrientedMaterial() {
|
||||
return materialManager.defaultCutout().material(Materials.ORIENTED);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import com.mojang.blaze3d.matrix.MatrixStack;
|
|||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.util.Direction;
|
||||
|
||||
public interface InstanceMaterial<D extends InstanceData> {
|
||||
public interface Material<D extends InstanceData> {
|
||||
/**
|
||||
* Get an instancer for the given model. Calling this method twice with the same key will return the same instancer.
|
||||
*
|
|
@ -10,5 +10,5 @@ public interface MaterialGroup {
|
|||
* @param <D> The type representing the per instance data.
|
||||
* @return A material you can use to render models.
|
||||
*/
|
||||
<D extends InstanceData> InstanceMaterial<D> material(MaterialSpec<D> spec);
|
||||
<D extends InstanceData> Material<D> material(MaterialSpec<D> spec);
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ public class MaterialGroupImpl<P extends WorldProgram> implements MaterialGroup
|
|||
|
||||
private final ArrayList<MaterialRenderer<P>> renderers = new ArrayList<>();
|
||||
|
||||
private final Map<MaterialSpec<?>, InstanceMaterialImpl<?>> materials = new HashMap<>();
|
||||
private final Map<MaterialSpec<?>, MaterialImpl<?>> materials = new HashMap<>();
|
||||
|
||||
public MaterialGroupImpl(MaterialManagerImpl<P> owner, IRenderState state) {
|
||||
this.owner = owner;
|
||||
|
@ -38,8 +38,8 @@ public class MaterialGroupImpl<P extends WorldProgram> implements MaterialGroup
|
|||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <D extends InstanceData> InstanceMaterialImpl<D> material(MaterialSpec<D> spec) {
|
||||
return (InstanceMaterialImpl<D>) materials.computeIfAbsent(spec, this::createInstanceMaterial);
|
||||
public <D extends InstanceData> MaterialImpl<D> material(MaterialSpec<D> spec) {
|
||||
return (MaterialImpl<D>) materials.computeIfAbsent(spec, this::createInstanceMaterial);
|
||||
}
|
||||
|
||||
public void render(Matrix4f viewProjection, double camX, double camY, double camZ) {
|
||||
|
@ -53,19 +53,19 @@ public class MaterialGroupImpl<P extends WorldProgram> implements MaterialGroup
|
|||
}
|
||||
|
||||
public void clear() {
|
||||
materials.values().forEach(InstanceMaterialImpl::clear);
|
||||
materials.values().forEach(MaterialImpl::clear);
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
materials.values()
|
||||
.forEach(InstanceMaterialImpl::delete);
|
||||
.forEach(MaterialImpl::delete);
|
||||
|
||||
materials.clear();
|
||||
renderers.clear();
|
||||
}
|
||||
|
||||
private InstanceMaterialImpl<?> createInstanceMaterial(MaterialSpec<?> type) {
|
||||
InstanceMaterialImpl<?> material = new InstanceMaterialImpl<>(type);
|
||||
private MaterialImpl<?> createInstanceMaterial(MaterialSpec<?> type) {
|
||||
MaterialImpl<?> material = new MaterialImpl<>(type);
|
||||
|
||||
this.renderers.add(new MaterialRenderer<>(owner.getProgram(type.getProgramName()), material, this::setup));
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import java.util.function.Supplier;
|
|||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.jozufozu.flywheel.backend.RenderWork;
|
||||
import com.jozufozu.flywheel.backend.instancing.GPUInstancer;
|
||||
import com.jozufozu.flywheel.backend.instancing.InstanceData;
|
||||
import com.jozufozu.flywheel.backend.instancing.Instancer;
|
||||
import com.jozufozu.flywheel.backend.model.ModelPool;
|
||||
|
@ -15,19 +16,19 @@ import com.jozufozu.flywheel.core.model.IModel;
|
|||
* A collection of Instancers that all have the same format.
|
||||
* @param <D>
|
||||
*/
|
||||
public class InstanceMaterialImpl<D extends InstanceData> implements InstanceMaterial<D> {
|
||||
public class MaterialImpl<D extends InstanceData> implements Material<D> {
|
||||
|
||||
final ModelPool modelPool;
|
||||
protected final Cache<Object, Instancer<D>> models;
|
||||
protected final Cache<Object, GPUInstancer<D>> models;
|
||||
protected final MaterialSpec<D> spec;
|
||||
|
||||
public InstanceMaterialImpl(MaterialSpec<D> spec) {
|
||||
public MaterialImpl(MaterialSpec<D> spec) {
|
||||
this.spec = spec;
|
||||
|
||||
modelPool = new ModelPool(spec.getModelFormat(), spec.getModelFormat().getStride() * 64);
|
||||
this.models = CacheBuilder.newBuilder()
|
||||
.removalListener(notification -> {
|
||||
Instancer<?> instancer = (Instancer<?>) notification.getValue();
|
||||
GPUInstancer<?> instancer = (GPUInstancer<?>) notification.getValue();
|
||||
RenderWork.enqueue(instancer::delete);
|
||||
})
|
||||
.build();
|
||||
|
@ -43,7 +44,7 @@ public class InstanceMaterialImpl<D extends InstanceData> implements InstanceMat
|
|||
@Override
|
||||
public Instancer<D> model(Object key, Supplier<IModel> modelSupplier) {
|
||||
try {
|
||||
return models.get(key, () -> new Instancer<>(modelPool, modelSupplier.get(), spec.getInstanceFactory(), spec.getInstanceFormat()));
|
||||
return models.get(key, () -> new GPUInstancer<>(modelPool, modelSupplier.get(), spec.getInstanceFactory(), spec.getInstanceFormat()));
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException("error creating instancer", e);
|
||||
}
|
||||
|
@ -53,7 +54,7 @@ public class InstanceMaterialImpl<D extends InstanceData> implements InstanceMat
|
|||
return models.size() > 0 && models.asMap()
|
||||
.values()
|
||||
.stream()
|
||||
.allMatch(Instancer::isEmpty);
|
||||
.allMatch(GPUInstancer::isEmpty);
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
|
@ -67,7 +68,7 @@ public class InstanceMaterialImpl<D extends InstanceData> implements InstanceMat
|
|||
public void clear() {
|
||||
models.asMap()
|
||||
.values()
|
||||
.forEach(Instancer::clear);
|
||||
.forEach(GPUInstancer::clear);
|
||||
}
|
||||
|
||||
}
|
|
@ -4,6 +4,7 @@ import java.util.Collection;
|
|||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.jozufozu.flywheel.backend.instancing.GPUInstancer;
|
||||
import com.jozufozu.flywheel.backend.instancing.Instancer;
|
||||
import com.jozufozu.flywheel.core.shader.WorldProgram;
|
||||
|
||||
|
@ -12,11 +13,11 @@ import net.minecraft.util.math.vector.Matrix4f;
|
|||
public class MaterialRenderer<P extends WorldProgram> {
|
||||
|
||||
protected final Supplier<P> program;
|
||||
protected final InstanceMaterialImpl<?> material;
|
||||
protected final MaterialImpl<?> material;
|
||||
|
||||
protected final Consumer<P> setupFunc;
|
||||
|
||||
public MaterialRenderer(Supplier<P> programSupplier, InstanceMaterialImpl<?> material, Consumer<P> setupFunc) {
|
||||
public MaterialRenderer(Supplier<P> programSupplier, MaterialImpl<?> material, Consumer<P> setupFunc) {
|
||||
this.program = programSupplier;
|
||||
this.material = material;
|
||||
this.setupFunc = setupFunc;
|
||||
|
@ -25,11 +26,11 @@ public class MaterialRenderer<P extends WorldProgram> {
|
|||
public void render(Matrix4f viewProjection, double camX, double camY, double camZ) {
|
||||
if (material.nothingToRender()) return;
|
||||
|
||||
Collection<? extends Instancer<?>> instancers = material.models.asMap()
|
||||
Collection<? extends GPUInstancer<?>> instancers = material.models.asMap()
|
||||
.values();
|
||||
|
||||
// initialize all uninitialized instancers...
|
||||
instancers.forEach(Instancer::init);
|
||||
instancers.forEach(GPUInstancer::init);
|
||||
// ...and then flush the model arena in case anything was marked for upload
|
||||
material.modelPool.flush();
|
||||
|
||||
|
@ -41,7 +42,7 @@ public class MaterialRenderer<P extends WorldProgram> {
|
|||
|
||||
setupFunc.accept(program);
|
||||
|
||||
instancers.forEach(Instancer::render);
|
||||
instancers.forEach(GPUInstancer::render);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.jozufozu.flywheel.core.materials;
|
||||
|
||||
import com.jozufozu.flywheel.backend.gl.buffer.MappedBuffer;
|
||||
import com.jozufozu.flywheel.backend.instancing.GPUInstancer;
|
||||
import com.jozufozu.flywheel.backend.instancing.InstanceData;
|
||||
import com.jozufozu.flywheel.backend.instancing.Instancer;
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.jozufozu.flywheel.core.materials;
|
||||
|
||||
import com.jozufozu.flywheel.backend.gl.buffer.MappedBuffer;
|
||||
import com.jozufozu.flywheel.backend.instancing.GPUInstancer;
|
||||
import com.jozufozu.flywheel.backend.instancing.Instancer;
|
||||
import com.jozufozu.flywheel.util.RenderUtil;
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.jozufozu.flywheel.core.materials;
|
||||
|
||||
import com.jozufozu.flywheel.backend.gl.buffer.MappedBuffer;
|
||||
import com.jozufozu.flywheel.backend.instancing.GPUInstancer;
|
||||
import com.jozufozu.flywheel.backend.instancing.Instancer;
|
||||
import com.jozufozu.flywheel.util.vec.Vec3;
|
||||
|
||||
|
|
Loading…
Reference in a new issue