mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2024-12-28 16:06:28 +01:00
Naive parallelism
- Submit many tasks to executor and then wait for them all to complete. - Use WaitGroup - SBB no longer stores params, instead accepts Params arg to render - SBB keeps scratch variables local - CPUInstancer keeps track of default params - Separate #setup and #draw... functions in CPUInstancer - Combine DirectBufferBuilder#updateAfterWriting with #intoDirectConsumer - DirectVertexConsumer#split to distribute work
This commit is contained in:
parent
8b472f4f51
commit
a668a7c7ac
11 changed files with 179 additions and 64 deletions
|
@ -66,6 +66,10 @@ public abstract class AbstractInstancer<D extends InstanceData> implements Insta
|
||||||
return data.size();
|
return data.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getTotalVertexCount() {
|
||||||
|
return getModelVertexCount() * numInstances();
|
||||||
|
}
|
||||||
|
|
||||||
protected BitSet getDirtyBitSet() {
|
protected BitSet getDirtyBitSet() {
|
||||||
final int size = data.size();
|
final int size = data.size();
|
||||||
final BitSet dirtySet = new BitSet(size);
|
final BitSet dirtySet = new BitSet(size);
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.jozufozu.flywheel.backend.instancing.batching;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class BatchExecutor implements Executor {
|
||||||
|
private final Executor internal;
|
||||||
|
private final WaitGroup wg;
|
||||||
|
|
||||||
|
public BatchExecutor(Executor internal) {
|
||||||
|
this.internal = internal;
|
||||||
|
|
||||||
|
wg = new WaitGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(@NotNull Runnable command) {
|
||||||
|
wg.add(1);
|
||||||
|
internal.execute(() -> {
|
||||||
|
// wrapper function to decrement the wait group
|
||||||
|
try {
|
||||||
|
command.run();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
} finally {
|
||||||
|
wg.done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void await() throws InterruptedException {
|
||||||
|
wg.await();
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,7 +30,8 @@ public class BatchedMaterial<D extends InstanceData> implements Material<D> {
|
||||||
|
|
||||||
public void render(PoseStack stack, VertexConsumer buffer, FormatContext context) {
|
public void render(PoseStack stack, VertexConsumer buffer, FormatContext context) {
|
||||||
for (CPUInstancer<D> instancer : models.values()) {
|
for (CPUInstancer<D> instancer : models.values()) {
|
||||||
instancer.drawAll(stack, buffer, context);
|
instancer.setup(context);
|
||||||
|
instancer.drawAll(stack, buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.jozufozu.flywheel.backend.instancing.batching;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.api.InstanceData;
|
import com.jozufozu.flywheel.api.InstanceData;
|
||||||
import com.jozufozu.flywheel.api.MaterialGroup;
|
import com.jozufozu.flywheel.api.MaterialGroup;
|
||||||
|
@ -30,15 +31,20 @@ public class BatchedMaterialGroup implements MaterialGroup {
|
||||||
return (BatchedMaterial<D>) materials.computeIfAbsent(spec, BatchedMaterial::new);
|
return (BatchedMaterial<D>) materials.computeIfAbsent(spec, BatchedMaterial::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void render(PoseStack stack, MultiBufferSource source) {
|
public void render(PoseStack stack, MultiBufferSource source, Executor pool) {
|
||||||
VertexConsumer buffer = source.getBuffer(state);
|
VertexConsumer buffer = source.getBuffer(state);
|
||||||
|
|
||||||
if (buffer instanceof DirectBufferBuilder direct) {
|
if (buffer instanceof DirectBufferBuilder direct) {
|
||||||
DirectVertexConsumer consumer = direct.intoDirectConsumer(calculateNeededVertices());
|
DirectVertexConsumer consumer = direct.intoDirectConsumer(calculateNeededVertices());
|
||||||
|
FormatContext context = new FormatContext(consumer.hasOverlay());
|
||||||
|
|
||||||
renderInto(stack, consumer, new FormatContext(consumer.hasOverlay()));
|
for (BatchedMaterial<?> material : materials.values()) {
|
||||||
|
for (CPUInstancer<?> instancer : material.models.values()) {
|
||||||
|
instancer.setup(context);
|
||||||
|
|
||||||
direct.updateAfterWriting(consumer);
|
instancer.submitTasks(stack, pool, consumer);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
renderInto(stack, buffer, FormatContext.defaultContext());
|
renderInto(stack, buffer, FormatContext.defaultContext());
|
||||||
}
|
}
|
||||||
|
@ -48,7 +54,7 @@ public class BatchedMaterialGroup implements MaterialGroup {
|
||||||
int total = 0;
|
int total = 0;
|
||||||
for (BatchedMaterial<?> material : materials.values()) {
|
for (BatchedMaterial<?> material : materials.values()) {
|
||||||
for (CPUInstancer<?> instancer : material.models.values()) {
|
for (CPUInstancer<?> instancer : material.models.values()) {
|
||||||
total += instancer.getModelVertexCount() * instancer.numInstances();
|
total += instancer.getTotalVertexCount();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@ package com.jozufozu.flywheel.backend.instancing.batching;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ForkJoinPool;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.api.MaterialGroup;
|
import com.jozufozu.flywheel.api.MaterialGroup;
|
||||||
import com.jozufozu.flywheel.backend.RenderLayer;
|
import com.jozufozu.flywheel.backend.RenderLayer;
|
||||||
|
@ -22,11 +24,15 @@ public class BatchingEngine implements Engine {
|
||||||
|
|
||||||
protected final Map<RenderLayer, Map<RenderType, BatchedMaterialGroup>> layers;
|
protected final Map<RenderLayer, Map<RenderType, BatchedMaterialGroup>> layers;
|
||||||
|
|
||||||
|
private final BatchExecutor pool;
|
||||||
|
|
||||||
public BatchingEngine() {
|
public BatchingEngine() {
|
||||||
this.layers = new EnumMap<>(RenderLayer.class);
|
this.layers = new EnumMap<>(RenderLayer.class);
|
||||||
for (RenderLayer value : RenderLayer.values()) {
|
for (RenderLayer value : RenderLayer.values()) {
|
||||||
layers.put(value, new HashMap<>());
|
layers.put(value, new HashMap<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pool = new BatchExecutor(Executors.newWorkStealingPool(ForkJoinPool.getCommonPoolParallelism()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -50,7 +56,12 @@ public class BatchingEngine implements Engine {
|
||||||
for (Map.Entry<RenderType, BatchedMaterialGroup> entry : layers.get(event.getLayer()).entrySet()) {
|
for (Map.Entry<RenderType, BatchedMaterialGroup> entry : layers.get(event.getLayer()).entrySet()) {
|
||||||
BatchedMaterialGroup group = entry.getValue();
|
BatchedMaterialGroup group = entry.getValue();
|
||||||
|
|
||||||
group.render(stack, buffers);
|
group.render(stack, buffers, pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
pool.await();
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
stack.popPose();
|
stack.popPose();
|
||||||
|
|
|
@ -1,27 +1,51 @@
|
||||||
package com.jozufozu.flywheel.backend.instancing.batching;
|
package com.jozufozu.flywheel.backend.instancing.batching;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.api.InstanceData;
|
import com.jozufozu.flywheel.api.InstanceData;
|
||||||
import com.jozufozu.flywheel.api.struct.BatchingTransformer;
|
import com.jozufozu.flywheel.api.struct.BatchingTransformer;
|
||||||
import com.jozufozu.flywheel.api.struct.StructType;
|
import com.jozufozu.flywheel.api.struct.StructType;
|
||||||
import com.jozufozu.flywheel.backend.instancing.AbstractInstancer;
|
import com.jozufozu.flywheel.backend.instancing.AbstractInstancer;
|
||||||
|
import com.jozufozu.flywheel.backend.model.DirectVertexConsumer;
|
||||||
import com.jozufozu.flywheel.core.model.Model;
|
import com.jozufozu.flywheel.core.model.Model;
|
||||||
import com.jozufozu.flywheel.core.model.SuperByteBuffer;
|
import com.jozufozu.flywheel.core.model.SuperByteBuffer;
|
||||||
import com.mojang.blaze3d.vertex.PoseStack;
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
|
||||||
|
|
||||||
public class CPUInstancer<D extends InstanceData> extends AbstractInstancer<D> {
|
public class CPUInstancer<D extends InstanceData> extends AbstractInstancer<D> {
|
||||||
|
|
||||||
private final BatchingTransformer<D> transform;
|
private final BatchingTransformer<D> transform;
|
||||||
|
|
||||||
private final SuperByteBuffer sbb;
|
private final SuperByteBuffer sbb;
|
||||||
|
private final SuperByteBuffer.Params defaultParams;
|
||||||
|
|
||||||
public CPUInstancer(StructType<D> type, Model modelData) {
|
public CPUInstancer(StructType<D> type, Model modelData) {
|
||||||
super(type, modelData);
|
super(type, modelData);
|
||||||
|
|
||||||
sbb = new SuperByteBuffer(modelData);
|
sbb = new SuperByteBuffer(modelData);
|
||||||
|
defaultParams = SuperByteBuffer.Params.defaultParams();
|
||||||
transform = type.asBatched()
|
transform = type.asBatched()
|
||||||
.getTransformer();
|
.getTransformer();
|
||||||
|
|
||||||
|
if (transform == null) {
|
||||||
|
throw new NullPointerException("Cannot batch " + type.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void submitTasks(PoseStack stack, Executor pool, DirectVertexConsumer consumer) {
|
||||||
|
int instances = numInstances();
|
||||||
|
|
||||||
|
while (instances > 0) {
|
||||||
|
int end = instances;
|
||||||
|
instances -= 100;
|
||||||
|
int start = Math.max(instances, 0);
|
||||||
|
|
||||||
|
int verts = getModelVertexCount() * (end - start);
|
||||||
|
|
||||||
|
DirectVertexConsumer sub = consumer.split(verts);
|
||||||
|
|
||||||
|
pool.execute(() -> drawRange(stack, sub, start, end));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -29,23 +53,34 @@ public class CPUInstancer<D extends InstanceData> extends AbstractInstancer<D> {
|
||||||
// noop
|
// noop
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drawAll(PoseStack stack, VertexConsumer buffer, FormatContext context) {
|
private void drawRange(PoseStack stack, VertexConsumer buffer, int from, int to) {
|
||||||
if (transform == null) {
|
SuperByteBuffer.Params params = defaultParams.copy();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for (D d : data.subList(from, to)) {
|
||||||
|
transform.transform(d, params);
|
||||||
|
|
||||||
|
sbb.renderInto(params, stack, buffer);
|
||||||
|
|
||||||
|
params.load(defaultParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawAll(PoseStack stack, VertexConsumer buffer) {
|
||||||
|
SuperByteBuffer.Params params = defaultParams.copy();
|
||||||
|
for (D d : data) {
|
||||||
|
transform.transform(d, params);
|
||||||
|
|
||||||
|
sbb.renderInto(params, stack, buffer);
|
||||||
|
|
||||||
|
params.load(defaultParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup(FormatContext context) {
|
||||||
renderSetup();
|
renderSetup();
|
||||||
|
|
||||||
if (context.usesOverlay()) {
|
if (context.usesOverlay()) {
|
||||||
sbb.getDefaultParams().entityMode();
|
defaultParams.entityMode();
|
||||||
}
|
|
||||||
|
|
||||||
sbb.reset();
|
|
||||||
|
|
||||||
for (D d : data) {
|
|
||||||
transform.transform(d, sbb.getParams());
|
|
||||||
|
|
||||||
sbb.renderInto(stack, buffer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.jozufozu.flywheel.backend.instancing.batching;
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/29655531
|
||||||
|
public class WaitGroup {
|
||||||
|
|
||||||
|
private int jobs = 0;
|
||||||
|
|
||||||
|
public synchronized void add(int i) {
|
||||||
|
jobs += i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void done() {
|
||||||
|
if (--jobs == 0) {
|
||||||
|
notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void await() throws InterruptedException {
|
||||||
|
while (jobs > 0) {
|
||||||
|
wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,7 +2,5 @@ package com.jozufozu.flywheel.backend.model;
|
||||||
|
|
||||||
public interface DirectBufferBuilder {
|
public interface DirectBufferBuilder {
|
||||||
|
|
||||||
DirectVertexConsumer intoDirectConsumer(int neededVerts);
|
DirectVertexConsumer intoDirectConsumer(int vertexCount);
|
||||||
|
|
||||||
void updateAfterWriting(DirectVertexConsumer complete);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import com.mojang.blaze3d.vertex.VertexFormat;
|
||||||
import com.mojang.blaze3d.vertex.VertexFormatElement;
|
import com.mojang.blaze3d.vertex.VertexFormatElement;
|
||||||
|
|
||||||
public class DirectVertexConsumer implements VertexConsumer {
|
public class DirectVertexConsumer implements VertexConsumer {
|
||||||
|
|
||||||
public final VertexFormat format;
|
public final VertexFormat format;
|
||||||
private final int stride;
|
private final int stride;
|
||||||
public final int startPos;
|
public final int startPos;
|
||||||
|
@ -23,7 +22,6 @@ public class DirectVertexConsumer implements VertexConsumer {
|
||||||
private int uv2 = -1;
|
private int uv2 = -1;
|
||||||
|
|
||||||
private long vertexBase;
|
private long vertexBase;
|
||||||
private int vertexCount;
|
|
||||||
|
|
||||||
public DirectVertexConsumer(ByteBuffer buffer, VertexFormat format) {
|
public DirectVertexConsumer(ByteBuffer buffer, VertexFormat format) {
|
||||||
this.format = format;
|
this.format = format;
|
||||||
|
@ -49,13 +47,42 @@ public class DirectVertexConsumer implements VertexConsumer {
|
||||||
offset += element.getByteSize();
|
offset += element.getByteSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.vertexBase = MemoryUtil.memAddress(buffer);
|
this.vertexBase = MemoryUtil.memAddress(buffer, startPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DirectVertexConsumer(DirectVertexConsumer parent) {
|
||||||
|
this.format = parent.format;
|
||||||
|
this.stride = parent.stride;
|
||||||
|
this.startPos = parent.startPos;
|
||||||
|
this.position = parent.position;
|
||||||
|
this.normal = parent.normal;
|
||||||
|
this.color = parent.color;
|
||||||
|
this.uv = parent.uv;
|
||||||
|
this.uv1 = parent.uv1;
|
||||||
|
this.uv2 = parent.uv2;
|
||||||
|
|
||||||
|
this.vertexBase = parent.vertexBase;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasOverlay() {
|
public boolean hasOverlay() {
|
||||||
return uv1 >= 0;
|
return uv1 >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split off the head of this consumer into a new object and advance our write-pointer.
|
||||||
|
* @param vertexCount The number of vertices that must be written to the head.
|
||||||
|
* @return The head of this consumer.
|
||||||
|
*/
|
||||||
|
public DirectVertexConsumer split(int vertexCount) {
|
||||||
|
int bytes = vertexCount * stride;
|
||||||
|
|
||||||
|
DirectVertexConsumer head = new DirectVertexConsumer(this);
|
||||||
|
|
||||||
|
this.vertexBase += bytes;
|
||||||
|
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public VertexConsumer vertex(double x, double y, double z) {
|
public VertexConsumer vertex(double x, double y, double z) {
|
||||||
if (position < 0) return this;
|
if (position < 0) return this;
|
||||||
|
@ -117,7 +144,6 @@ public class DirectVertexConsumer implements VertexConsumer {
|
||||||
@Override
|
@Override
|
||||||
public void endVertex() {
|
public void endVertex() {
|
||||||
vertexBase += stride;
|
vertexBase += stride;
|
||||||
vertexCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -129,8 +155,4 @@ public class DirectVertexConsumer implements VertexConsumer {
|
||||||
public void unsetDefaultColor() {
|
public void unsetDefaultColor() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getVertexCount() {
|
|
||||||
return vertexCount;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,32 +22,22 @@ public class SuperByteBuffer {
|
||||||
private final Model model;
|
private final Model model;
|
||||||
private final ModelReader template;
|
private final ModelReader template;
|
||||||
|
|
||||||
private final Params defaultParams = Params.defaultParams();
|
|
||||||
private final Params params = defaultParams.copy();
|
|
||||||
|
|
||||||
public Params getDefaultParams() {
|
|
||||||
return defaultParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Params getParams() {
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Temporary
|
// Temporary
|
||||||
private static final Long2IntMap WORLD_LIGHT_CACHE = new Long2IntOpenHashMap();
|
private static final Long2IntMap WORLD_LIGHT_CACHE = new Long2IntOpenHashMap();
|
||||||
private final Vector4f pos = new Vector4f();
|
|
||||||
private final Vector3f normal = new Vector3f();
|
|
||||||
private final Vector4f lightPos = new Vector4f();
|
|
||||||
|
|
||||||
public SuperByteBuffer(Model model) {
|
public SuperByteBuffer(Model model) {
|
||||||
this.model = model;
|
this.model = model;
|
||||||
template = model.getReader();
|
template = model.getReader();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void renderInto(PoseStack input, VertexConsumer builder) {
|
public void renderInto(Params params, PoseStack input, VertexConsumer builder) {
|
||||||
if (isEmpty())
|
if (isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
Vector4f pos = new Vector4f();
|
||||||
|
Vector3f normal = new Vector3f();
|
||||||
|
Vector4f lightPos = new Vector4f();
|
||||||
|
|
||||||
Matrix4f modelMat = input.last()
|
Matrix4f modelMat = input.last()
|
||||||
.pose()
|
.pose()
|
||||||
.copy();
|
.copy();
|
||||||
|
@ -168,14 +158,6 @@ public class SuperByteBuffer {
|
||||||
|
|
||||||
builder.endVertex();
|
builder.endVertex();
|
||||||
}
|
}
|
||||||
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
public SuperByteBuffer reset() {
|
|
||||||
params.load(defaultParams);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
|
|
|
@ -38,21 +38,19 @@ public abstract class BufferBuilderMixin implements DirectBufferBuilder {
|
||||||
private int nextElementByte;
|
private int nextElementByte;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DirectVertexConsumer intoDirectConsumer(int neededVerts) {
|
public DirectVertexConsumer intoDirectConsumer(int vertexCount) {
|
||||||
ensureCapacity(neededVerts * this.format.getVertexSize());
|
int bytes = vertexCount * format.getVertexSize();
|
||||||
return new DirectVertexConsumer(this.buffer, this.format);
|
ensureCapacity(bytes);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
DirectVertexConsumer consumer = new DirectVertexConsumer(this.buffer, this.format);
|
||||||
public void updateAfterWriting(DirectVertexConsumer complete) {
|
|
||||||
int vertexCount = complete.getVertexCount();
|
|
||||||
int totalWrittenBytes = vertexCount * format.getVertexSize();
|
|
||||||
|
|
||||||
this.vertices += vertexCount;
|
this.vertices += vertexCount;
|
||||||
this.currentElement = format.getElements()
|
this.currentElement = format.getElements()
|
||||||
.get(0);
|
.get(0);
|
||||||
this.elementIndex = 0;
|
this.elementIndex = 0;
|
||||||
this.nextElementByte += totalWrittenBytes;
|
this.nextElementByte += bytes;
|
||||||
this.buffer.position(complete.startPos + totalWrittenBytes);
|
this.buffer.position(consumer.startPos + bytes);
|
||||||
|
|
||||||
|
return consumer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue