mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2024-12-30 17:06:32 +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 abstract void write(MappedBuffer buf);
|
||||||
|
|
||||||
public void markDirty() {
|
public void markDirty() {
|
||||||
owner.anyToUpdate = true;
|
owner.markDirty(this);
|
||||||
dirty = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void delete() {
|
public void delete() {
|
||||||
owner.anyToRemove = true;
|
owner.markRemoval(this);
|
||||||
removed = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,71 +1,10 @@
|
||||||
package com.jozufozu.flywheel.backend.instancing;
|
package com.jozufozu.flywheel.backend.instancing;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
public interface Instancer<D extends InstanceData> {
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return a handle to a new copy of this model.
|
* @return a handle to a new copy of this model.
|
||||||
*/
|
*/
|
||||||
public D createInstance() {
|
D createInstance();
|
||||||
return _add(factory.create(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy a data from another Instancer to this.
|
* 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.
|
* This has the effect of swapping out one model for another.
|
||||||
* @param inOther the data associated with a different model.
|
* @param inOther the data associated with a different model.
|
||||||
*/
|
*/
|
||||||
public void stealInstance(D inOther) {
|
void stealInstance(D inOther);
|
||||||
if (inOther.owner == this) return;
|
|
||||||
|
|
||||||
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.IDynamicInstance;
|
||||||
import com.jozufozu.flywheel.backend.instancing.AbstractInstance;
|
import com.jozufozu.flywheel.backend.instancing.AbstractInstance;
|
||||||
import com.jozufozu.flywheel.backend.instancing.ITickableInstance;
|
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.backend.material.MaterialManager;
|
||||||
import com.jozufozu.flywheel.core.Materials;
|
import com.jozufozu.flywheel.core.Materials;
|
||||||
import com.jozufozu.flywheel.core.materials.ModelData;
|
import com.jozufozu.flywheel.core.materials.ModelData;
|
||||||
|
@ -76,11 +76,11 @@ public abstract class TileEntityInstance<T extends TileEntity> extends AbstractI
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected InstanceMaterial<ModelData> getTransformMaterial() {
|
protected Material<ModelData> getTransformMaterial() {
|
||||||
return materialManager.defaultCutout().material(Materials.TRANSFORMED);
|
return materialManager.defaultCutout().material(Materials.TRANSFORMED);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected InstanceMaterial<OrientedData> getOrientedMaterial() {
|
protected Material<OrientedData> getOrientedMaterial() {
|
||||||
return materialManager.defaultCutout().material(Materials.ORIENTED);
|
return materialManager.defaultCutout().material(Materials.ORIENTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ import com.mojang.blaze3d.matrix.MatrixStack;
|
||||||
import net.minecraft.block.BlockState;
|
import net.minecraft.block.BlockState;
|
||||||
import net.minecraft.util.Direction;
|
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.
|
* 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.
|
* @param <D> The type representing the per instance data.
|
||||||
* @return A material you can use to render models.
|
* @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 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) {
|
public MaterialGroupImpl(MaterialManagerImpl<P> owner, IRenderState state) {
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
|
@ -38,8 +38,8 @@ public class MaterialGroupImpl<P extends WorldProgram> implements MaterialGroup
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
public <D extends InstanceData> InstanceMaterialImpl<D> material(MaterialSpec<D> spec) {
|
public <D extends InstanceData> MaterialImpl<D> material(MaterialSpec<D> spec) {
|
||||||
return (InstanceMaterialImpl<D>) materials.computeIfAbsent(spec, this::createInstanceMaterial);
|
return (MaterialImpl<D>) materials.computeIfAbsent(spec, this::createInstanceMaterial);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void render(Matrix4f viewProjection, double camX, double camY, double camZ) {
|
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() {
|
public void clear() {
|
||||||
materials.values().forEach(InstanceMaterialImpl::clear);
|
materials.values().forEach(MaterialImpl::clear);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void delete() {
|
public void delete() {
|
||||||
materials.values()
|
materials.values()
|
||||||
.forEach(InstanceMaterialImpl::delete);
|
.forEach(MaterialImpl::delete);
|
||||||
|
|
||||||
materials.clear();
|
materials.clear();
|
||||||
renderers.clear();
|
renderers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private InstanceMaterialImpl<?> createInstanceMaterial(MaterialSpec<?> type) {
|
private MaterialImpl<?> createInstanceMaterial(MaterialSpec<?> type) {
|
||||||
InstanceMaterialImpl<?> material = new InstanceMaterialImpl<>(type);
|
MaterialImpl<?> material = new MaterialImpl<>(type);
|
||||||
|
|
||||||
this.renderers.add(new MaterialRenderer<>(owner.getProgram(type.getProgramName()), material, this::setup));
|
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.Cache;
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.jozufozu.flywheel.backend.RenderWork;
|
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.InstanceData;
|
||||||
import com.jozufozu.flywheel.backend.instancing.Instancer;
|
import com.jozufozu.flywheel.backend.instancing.Instancer;
|
||||||
import com.jozufozu.flywheel.backend.model.ModelPool;
|
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.
|
* A collection of Instancers that all have the same format.
|
||||||
* @param <D>
|
* @param <D>
|
||||||
*/
|
*/
|
||||||
public class InstanceMaterialImpl<D extends InstanceData> implements InstanceMaterial<D> {
|
public class MaterialImpl<D extends InstanceData> implements Material<D> {
|
||||||
|
|
||||||
final ModelPool modelPool;
|
final ModelPool modelPool;
|
||||||
protected final Cache<Object, Instancer<D>> models;
|
protected final Cache<Object, GPUInstancer<D>> models;
|
||||||
protected final MaterialSpec<D> spec;
|
protected final MaterialSpec<D> spec;
|
||||||
|
|
||||||
public InstanceMaterialImpl(MaterialSpec<D> spec) {
|
public MaterialImpl(MaterialSpec<D> spec) {
|
||||||
this.spec = spec;
|
this.spec = spec;
|
||||||
|
|
||||||
modelPool = new ModelPool(spec.getModelFormat(), spec.getModelFormat().getStride() * 64);
|
modelPool = new ModelPool(spec.getModelFormat(), spec.getModelFormat().getStride() * 64);
|
||||||
this.models = CacheBuilder.newBuilder()
|
this.models = CacheBuilder.newBuilder()
|
||||||
.removalListener(notification -> {
|
.removalListener(notification -> {
|
||||||
Instancer<?> instancer = (Instancer<?>) notification.getValue();
|
GPUInstancer<?> instancer = (GPUInstancer<?>) notification.getValue();
|
||||||
RenderWork.enqueue(instancer::delete);
|
RenderWork.enqueue(instancer::delete);
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
|
@ -43,7 +44,7 @@ public class InstanceMaterialImpl<D extends InstanceData> implements InstanceMat
|
||||||
@Override
|
@Override
|
||||||
public Instancer<D> model(Object key, Supplier<IModel> modelSupplier) {
|
public Instancer<D> model(Object key, Supplier<IModel> modelSupplier) {
|
||||||
try {
|
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) {
|
} catch (ExecutionException e) {
|
||||||
throw new RuntimeException("error creating instancer", 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()
|
return models.size() > 0 && models.asMap()
|
||||||
.values()
|
.values()
|
||||||
.stream()
|
.stream()
|
||||||
.allMatch(Instancer::isEmpty);
|
.allMatch(GPUInstancer::isEmpty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void delete() {
|
public void delete() {
|
||||||
|
@ -67,7 +68,7 @@ public class InstanceMaterialImpl<D extends InstanceData> implements InstanceMat
|
||||||
public void clear() {
|
public void clear() {
|
||||||
models.asMap()
|
models.asMap()
|
||||||
.values()
|
.values()
|
||||||
.forEach(Instancer::clear);
|
.forEach(GPUInstancer::clear);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -4,6 +4,7 @@ import java.util.Collection;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import com.jozufozu.flywheel.backend.instancing.GPUInstancer;
|
||||||
import com.jozufozu.flywheel.backend.instancing.Instancer;
|
import com.jozufozu.flywheel.backend.instancing.Instancer;
|
||||||
import com.jozufozu.flywheel.core.shader.WorldProgram;
|
import com.jozufozu.flywheel.core.shader.WorldProgram;
|
||||||
|
|
||||||
|
@ -12,11 +13,11 @@ import net.minecraft.util.math.vector.Matrix4f;
|
||||||
public class MaterialRenderer<P extends WorldProgram> {
|
public class MaterialRenderer<P extends WorldProgram> {
|
||||||
|
|
||||||
protected final Supplier<P> program;
|
protected final Supplier<P> program;
|
||||||
protected final InstanceMaterialImpl<?> material;
|
protected final MaterialImpl<?> material;
|
||||||
|
|
||||||
protected final Consumer<P> setupFunc;
|
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.program = programSupplier;
|
||||||
this.material = material;
|
this.material = material;
|
||||||
this.setupFunc = setupFunc;
|
this.setupFunc = setupFunc;
|
||||||
|
@ -25,11 +26,11 @@ public class MaterialRenderer<P extends WorldProgram> {
|
||||||
public void render(Matrix4f viewProjection, double camX, double camY, double camZ) {
|
public void render(Matrix4f viewProjection, double camX, double camY, double camZ) {
|
||||||
if (material.nothingToRender()) return;
|
if (material.nothingToRender()) return;
|
||||||
|
|
||||||
Collection<? extends Instancer<?>> instancers = material.models.asMap()
|
Collection<? extends GPUInstancer<?>> instancers = material.models.asMap()
|
||||||
.values();
|
.values();
|
||||||
|
|
||||||
// initialize all uninitialized instancers...
|
// 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
|
// ...and then flush the model arena in case anything was marked for upload
|
||||||
material.modelPool.flush();
|
material.modelPool.flush();
|
||||||
|
|
||||||
|
@ -41,7 +42,7 @@ public class MaterialRenderer<P extends WorldProgram> {
|
||||||
|
|
||||||
setupFunc.accept(program);
|
setupFunc.accept(program);
|
||||||
|
|
||||||
instancers.forEach(Instancer::render);
|
instancers.forEach(GPUInstancer::render);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.jozufozu.flywheel.core.materials;
|
package com.jozufozu.flywheel.core.materials;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.backend.gl.buffer.MappedBuffer;
|
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.InstanceData;
|
||||||
import com.jozufozu.flywheel.backend.instancing.Instancer;
|
import com.jozufozu.flywheel.backend.instancing.Instancer;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.jozufozu.flywheel.core.materials;
|
package com.jozufozu.flywheel.core.materials;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.backend.gl.buffer.MappedBuffer;
|
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.backend.instancing.Instancer;
|
||||||
import com.jozufozu.flywheel.util.RenderUtil;
|
import com.jozufozu.flywheel.util.RenderUtil;
|
||||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.jozufozu.flywheel.core.materials;
|
package com.jozufozu.flywheel.core.materials;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.backend.gl.buffer.MappedBuffer;
|
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.backend.instancing.Instancer;
|
||||||
import com.jozufozu.flywheel.util.vec.Vec3;
|
import com.jozufozu.flywheel.util.vec.Vec3;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue