mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-01-14 08:16:13 +01:00
Textures your buffer
- Use texture buffers instead of instanced arrays - Fixes issues with instance layouts using int elements - Fixes attribute binding hardware limitations - Create one texture for the entire instancing engine and bind each instancer's vbo when it's time to draw - Generate glsl to fetch FlwInstances from a usamplerBuffer according to the instance layout - Use RGBA32UI to get the most data from each texel fetch - Align instance stride to 16 to avoid complexity in unpacking
This commit is contained in:
parent
17130e22ea
commit
c6ed7c4132
11 changed files with 392 additions and 224 deletions
|
@ -2,7 +2,7 @@ package com.jozufozu.flywheel.backend.compile;
|
|||
|
||||
import com.jozufozu.flywheel.Flywheel;
|
||||
import com.jozufozu.flywheel.backend.compile.component.IndirectComponent;
|
||||
import com.jozufozu.flywheel.backend.compile.component.InstancedArraysComponent;
|
||||
import com.jozufozu.flywheel.backend.compile.component.SamplerBufferComponent;
|
||||
import com.jozufozu.flywheel.backend.glsl.GlslVersion;
|
||||
|
||||
public final class Pipelines {
|
||||
|
@ -13,7 +13,7 @@ public final class Pipelines {
|
|||
.fragmentMain(Flywheel.rl("internal/instancing/main.frag"))
|
||||
.vertexApiImpl(Flywheel.rl("internal/instancing/api_impl.vert"))
|
||||
.fragmentApiImpl(Flywheel.rl("internal/instancing/api_impl.frag"))
|
||||
.assembler(InstancedArraysComponent::new)
|
||||
.assembler(SamplerBufferComponent::create)
|
||||
.build();
|
||||
public static final Pipeline INDIRECT = Pipeline.builder()
|
||||
.compilerMarker("indirect")
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
package com.jozufozu.flywheel.backend.compile.component;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import com.jozufozu.flywheel.Flywheel;
|
||||
import com.jozufozu.flywheel.api.layout.Layout;
|
||||
import com.jozufozu.flywheel.backend.compile.LayoutInterpreter;
|
||||
import com.jozufozu.flywheel.backend.compile.Pipeline;
|
||||
import com.jozufozu.flywheel.backend.glsl.SourceComponent;
|
||||
import com.jozufozu.flywheel.backend.glsl.generate.FnSignature;
|
||||
import com.jozufozu.flywheel.backend.glsl.generate.GlslBlock;
|
||||
import com.jozufozu.flywheel.backend.glsl.generate.GlslBuilder;
|
||||
import com.jozufozu.flywheel.backend.glsl.generate.GlslExpr;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public class InstancedArraysComponent implements SourceComponent {
|
||||
private static final String ATTRIBUTE_PREFIX = "_flw_i_";
|
||||
private static final String STRUCT_NAME = "FlwInstance";
|
||||
private static final String UNPACK_FN_NAME = "_flw_unpackInstance";
|
||||
|
||||
private final Layout layout;
|
||||
private final int baseIndex;
|
||||
|
||||
public InstancedArraysComponent(Pipeline.InstanceAssemblerContext ctx) {
|
||||
this.layout = ctx.instanceType()
|
||||
.layout();
|
||||
this.baseIndex = ctx.baseAttribute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends SourceComponent> included() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation name() {
|
||||
return Flywheel.rl("generated_instanced_arrays");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String source() {
|
||||
var builder = new GlslBuilder();
|
||||
|
||||
generateVertexInput(builder);
|
||||
|
||||
builder.blankLine();
|
||||
|
||||
var structBuilder = builder.struct();
|
||||
structBuilder.setName(STRUCT_NAME);
|
||||
|
||||
for (var element : layout.elements()) {
|
||||
structBuilder.addField(LayoutInterpreter.typeName(element.type()), element.name());
|
||||
}
|
||||
|
||||
builder.blankLine();
|
||||
|
||||
// unpacking function
|
||||
builder.function()
|
||||
.signature(FnSignature.of(STRUCT_NAME, UNPACK_FN_NAME))
|
||||
.body(this::generateUnpackingBody);
|
||||
|
||||
builder.blankLine();
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private void generateVertexInput(GlslBuilder builder) {
|
||||
int i = baseIndex;
|
||||
for (var element : layout.elements()) {
|
||||
var type = element.type();
|
||||
|
||||
builder.vertexInput()
|
||||
.binding(i)
|
||||
.type(LayoutInterpreter.typeName(type))
|
||||
.name(ATTRIBUTE_PREFIX + element.name());
|
||||
|
||||
i += LayoutInterpreter.attributeCount(type);
|
||||
}
|
||||
}
|
||||
|
||||
private void generateUnpackingBody(GlslBlock b) {
|
||||
var fields = layout.elements()
|
||||
.stream()
|
||||
.map(it -> new GlslExpr.Variable(ATTRIBUTE_PREFIX + it.name()))
|
||||
.toList();
|
||||
b.ret(GlslExpr.call(STRUCT_NAME, fields));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,322 @@
|
|||
package com.jozufozu.flywheel.backend.compile.component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.jozufozu.flywheel.Flywheel;
|
||||
import com.jozufozu.flywheel.api.instance.InstanceType;
|
||||
import com.jozufozu.flywheel.api.layout.FloatRepr;
|
||||
import com.jozufozu.flywheel.api.layout.IntegerRepr;
|
||||
import com.jozufozu.flywheel.api.layout.Layout;
|
||||
import com.jozufozu.flywheel.api.layout.MatrixElementType;
|
||||
import com.jozufozu.flywheel.api.layout.ScalarElementType;
|
||||
import com.jozufozu.flywheel.api.layout.UnsignedIntegerRepr;
|
||||
import com.jozufozu.flywheel.api.layout.VectorElementType;
|
||||
import com.jozufozu.flywheel.backend.compile.LayoutInterpreter;
|
||||
import com.jozufozu.flywheel.backend.compile.Pipeline;
|
||||
import com.jozufozu.flywheel.backend.glsl.SourceComponent;
|
||||
import com.jozufozu.flywheel.backend.glsl.generate.FnSignature;
|
||||
import com.jozufozu.flywheel.backend.glsl.generate.GlslBlock;
|
||||
import com.jozufozu.flywheel.backend.glsl.generate.GlslBuilder;
|
||||
import com.jozufozu.flywheel.backend.glsl.generate.GlslExpr;
|
||||
import com.jozufozu.flywheel.backend.glsl.generate.GlslStmt;
|
||||
import com.jozufozu.flywheel.lib.math.MoreMath;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public class SamplerBufferComponent implements SourceComponent {
|
||||
private static final String STRUCT_NAME = "FlwInstance";
|
||||
private static final String UNPACK_FN_NAME = "_flw_unpackInstance";
|
||||
private static final String UNPACK_ARG = "index";
|
||||
|
||||
private final Layout layout;
|
||||
|
||||
public SamplerBufferComponent(InstanceType<?> type) {
|
||||
this.layout = type.layout();
|
||||
}
|
||||
|
||||
public static SamplerBufferComponent create(Pipeline.InstanceAssemblerContext ctx) {
|
||||
return create(ctx.instanceType());
|
||||
}
|
||||
|
||||
public static SamplerBufferComponent create(InstanceType<?> instanceType) {
|
||||
return new SamplerBufferComponent(instanceType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends SourceComponent> included() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation name() {
|
||||
return Flywheel.rl("generated_indirect");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String source() {
|
||||
return generateIndirect();
|
||||
}
|
||||
|
||||
public String generateIndirect() {
|
||||
var builder = new GlslBuilder();
|
||||
|
||||
generateInstanceStruct(builder);
|
||||
|
||||
builder.blankLine();
|
||||
|
||||
builder._addRaw("uniform usamplerBuffer _flw_instances;");
|
||||
|
||||
builder.blankLine();
|
||||
|
||||
generateUnpacking(builder);
|
||||
|
||||
builder.blankLine();
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private void generateInstanceStruct(GlslBuilder builder) {
|
||||
var instance = builder.struct();
|
||||
instance.setName(STRUCT_NAME);
|
||||
for (var element : layout.elements()) {
|
||||
instance.addField(LayoutInterpreter.typeName(element.type()), element.name());
|
||||
}
|
||||
}
|
||||
|
||||
private void generateUnpacking(GlslBuilder builder) {
|
||||
var block = new GlslBlock();
|
||||
|
||||
var texels = MoreMath.ceilingDiv(layout.byteSize(), 16);
|
||||
|
||||
block.add(GlslStmt.raw("int base = " + UNPACK_ARG + " * " + texels + ";"));
|
||||
|
||||
for (int i = 0; i < texels; i++) {
|
||||
// Fetch all the texels for the given instance ahead of time to simplify the unpacking generators.
|
||||
block.add(GlslStmt.raw("uvec4 u" + i + " = texelFetch(_flw_instances, base + " + i + ");"));
|
||||
}
|
||||
|
||||
var unpackArgs = new ArrayList<GlslExpr>();
|
||||
int uintOffset = 0;
|
||||
for (Layout.Element element : layout.elements()) {
|
||||
unpackArgs.add(unpackElement(element, uintOffset));
|
||||
uintOffset += MoreMath.ceilingDiv(element.type().byteSize(), 4);
|
||||
}
|
||||
|
||||
block.ret(GlslExpr.call(STRUCT_NAME, unpackArgs));
|
||||
|
||||
builder.function()
|
||||
.signature(FnSignature.create()
|
||||
.returnType(STRUCT_NAME)
|
||||
.name(UNPACK_FN_NAME)
|
||||
.arg("int", UNPACK_ARG)
|
||||
.build())
|
||||
.body(block);
|
||||
}
|
||||
|
||||
public static GlslExpr access(int uintOffset) {
|
||||
return GlslExpr.variable("u" + (uintOffset >> 2))
|
||||
.swizzle(String.valueOf("xyzw".charAt(uintOffset & 3)));
|
||||
}
|
||||
|
||||
// TODO: deduplicate this with IndirectComponent somehow?
|
||||
|
||||
public static GlslExpr unpackElement(Layout.Element element, int uintOffset) {
|
||||
// FIXME: I don't think we're unpacking signed byte/short values correctly
|
||||
// FIXME: we definitely don't consider endianness. this all assumes little endian which works on my machine.
|
||||
var type = element.type();
|
||||
|
||||
if (type instanceof ScalarElementType scalar) {
|
||||
return unpackScalar(uintOffset, scalar);
|
||||
} else if (type instanceof VectorElementType vector) {
|
||||
return unpackVector(uintOffset, vector);
|
||||
} else if (type instanceof MatrixElementType matrix) {
|
||||
return unpackMatrix(uintOffset, matrix);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unknown type " + type);
|
||||
}
|
||||
|
||||
private static GlslExpr unpackScalar(int uintOffset, ScalarElementType scalar) {
|
||||
var repr = scalar.repr();
|
||||
|
||||
if (repr instanceof IntegerRepr intRepr) {
|
||||
return unpackIntScalar(uintOffset, intRepr);
|
||||
} else if (repr instanceof UnsignedIntegerRepr unsignedIntegerRepr) {
|
||||
return unpackUnsignedScalar(uintOffset, unsignedIntegerRepr);
|
||||
} else if (repr instanceof FloatRepr floatRepr) {
|
||||
return unpackFloatScalar(uintOffset, floatRepr);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unknown repr " + repr);
|
||||
}
|
||||
|
||||
private static GlslExpr unpackIntScalar(int uintOffset, IntegerRepr intRepr) {
|
||||
return switch (intRepr) {
|
||||
case BYTE -> unpackScalar(uintOffset, e -> e.and(0xFF)
|
||||
.cast("int"));
|
||||
case SHORT -> unpackScalar(uintOffset, e -> e.and(0xFFFF)
|
||||
.cast("int"));
|
||||
case INT -> unpackScalar(uintOffset);
|
||||
};
|
||||
}
|
||||
|
||||
private static GlslExpr unpackUnsignedScalar(int uintOffset, UnsignedIntegerRepr repr) {
|
||||
return switch (repr) {
|
||||
case UNSIGNED_BYTE -> unpackScalar(uintOffset, e -> e.and(0xFF));
|
||||
case UNSIGNED_SHORT -> unpackScalar(uintOffset, e -> e.and(0xFFFF));
|
||||
case UNSIGNED_INT -> unpackScalar(uintOffset);
|
||||
};
|
||||
}
|
||||
|
||||
private static GlslExpr unpackFloatScalar(int uintOffset, FloatRepr repr) {
|
||||
return switch (repr) {
|
||||
case BYTE -> unpackScalar(uintOffset, e -> e.and(0xFF)
|
||||
.cast("int")
|
||||
.cast("float"));
|
||||
case NORMALIZED_BYTE -> unpackScalar(uintOffset, e -> e.callFunction("unpackSnorm4x8")
|
||||
.swizzle("x"));
|
||||
case UNSIGNED_BYTE -> unpackScalar(uintOffset, e -> e.and(0xFF)
|
||||
.cast("float"));
|
||||
case NORMALIZED_UNSIGNED_BYTE ->
|
||||
unpackScalar(uintOffset, e -> e.callFunction("unpackUnorm4x8")
|
||||
.swizzle("x"));
|
||||
case SHORT -> unpackScalar(uintOffset, e -> e.and(0xFFFF)
|
||||
.cast("int")
|
||||
.cast("float"));
|
||||
case NORMALIZED_SHORT -> unpackScalar(uintOffset, e -> e.callFunction("unpackSnorm2x16")
|
||||
.swizzle("x"));
|
||||
case UNSIGNED_SHORT -> unpackScalar(uintOffset, e -> e.and(0xFFFF)
|
||||
.cast("float"));
|
||||
case NORMALIZED_UNSIGNED_SHORT ->
|
||||
unpackScalar(uintOffset, e -> e.callFunction("unpackUnorm2x16")
|
||||
.swizzle("x"));
|
||||
case INT -> unpackScalar(uintOffset, e -> e.cast("int").cast("float"));
|
||||
case NORMALIZED_INT -> unpackScalar(uintOffset, e -> e.div(2147483647f)
|
||||
.clamp(-1, 1));
|
||||
case UNSIGNED_INT -> unpackScalar(uintOffset, e -> e.cast("float"));
|
||||
case NORMALIZED_UNSIGNED_INT -> unpackScalar(uintOffset, e -> e.div(4294967295f));
|
||||
case FLOAT -> unpackScalar(uintOffset, e -> e.callFunction("uintBitsToFloat"));
|
||||
};
|
||||
}
|
||||
|
||||
private static GlslExpr unpackScalar(int uintOffset) {
|
||||
return unpackScalar(uintOffset, Function.identity());
|
||||
}
|
||||
|
||||
private static GlslExpr unpackScalar(int uintOffset, Function<GlslExpr, GlslExpr> perElement) {
|
||||
return perElement.apply(access(uintOffset));
|
||||
}
|
||||
|
||||
private static GlslExpr unpackVector(int uintOffset, VectorElementType vector) {
|
||||
var repr = vector.repr();
|
||||
|
||||
int size = vector.size();
|
||||
|
||||
if (repr instanceof IntegerRepr intRepr) {
|
||||
return unpackIntVector(uintOffset, intRepr, size);
|
||||
} else if (repr instanceof UnsignedIntegerRepr unsignedIntegerRepr) {
|
||||
return unpackUnsignedVector(uintOffset, unsignedIntegerRepr, size);
|
||||
} else if (repr instanceof FloatRepr floatRepr) {
|
||||
return unpackFloatVector(uintOffset, floatRepr, size);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unknown repr " + repr);
|
||||
}
|
||||
|
||||
private static GlslExpr unpackIntVector(int uintOffset, IntegerRepr repr, int size) {
|
||||
return switch (repr) {
|
||||
case BYTE -> unpackByteBacked(uintOffset, size, "ivec" + size, e -> e.cast("int"));
|
||||
case SHORT -> unpackShortBacked(uintOffset, size, "ivec" + size, e -> e.cast("int"));
|
||||
case INT -> unpack(uintOffset, size, "ivec" + size);
|
||||
};
|
||||
}
|
||||
|
||||
private static GlslExpr unpackUnsignedVector(int uintOffset, UnsignedIntegerRepr unsignedIntegerRepr, int size) {
|
||||
return switch (unsignedIntegerRepr) {
|
||||
case UNSIGNED_BYTE -> unpackByteBacked(uintOffset, size, "uvec" + size, Function.identity());
|
||||
case UNSIGNED_SHORT -> unpackShortBacked(uintOffset, size, "uvec" + size, Function.identity());
|
||||
case UNSIGNED_INT -> unpack(uintOffset, size, "uvec" + size);
|
||||
};
|
||||
}
|
||||
|
||||
private static GlslExpr unpackFloatVector(int uintOffset, FloatRepr floatRepr, int size) {
|
||||
return switch (floatRepr) {
|
||||
case NORMALIZED_BYTE -> unpackByteBacked(uintOffset, size, "vec" + size, e -> e.div(127).clamp(-1, 1));
|
||||
case NORMALIZED_UNSIGNED_BYTE -> unpackByteBacked(uintOffset, size, "vec" + size, e -> e.div(255));
|
||||
case NORMALIZED_SHORT -> unpackShortBacked(uintOffset, size, "vec" + size, e -> e.div(32727).clamp(-1, 1));
|
||||
case NORMALIZED_UNSIGNED_SHORT -> unpackShortBacked(uintOffset, size, "vec" + size, e -> e.div(65535));
|
||||
case NORMALIZED_INT -> unpack(uintOffset, size, "vec" + size, e -> e.div(2147483647f)
|
||||
.clamp(-1, 1));
|
||||
case NORMALIZED_UNSIGNED_INT ->
|
||||
unpack(uintOffset, size, "vec" + size, e -> e.div(4294967295f));
|
||||
case BYTE -> unpackByteBacked(uintOffset, size, "vec" + size, e -> e.cast("int")
|
||||
.cast("float"));
|
||||
case UNSIGNED_BYTE -> unpackByteBacked(uintOffset, size, "vec" + size, e -> e.cast("float"));
|
||||
case SHORT -> unpackShortBacked(uintOffset, size, "vec" + size, e -> e.cast("int")
|
||||
.cast("float"));
|
||||
case UNSIGNED_SHORT -> unpackShortBacked(uintOffset, size, "vec" + size, e -> e.cast("float"));
|
||||
case INT -> unpack(uintOffset, size, "vec" + size, e -> e.cast("float"));
|
||||
case UNSIGNED_INT -> unpack(uintOffset, size, "vec" + size, e -> e.cast("int").cast("float"));
|
||||
case FLOAT -> unpack(uintOffset, size, "vec" + size, e -> e.callFunction("uintBitsToFloat"));
|
||||
};
|
||||
}
|
||||
|
||||
private static GlslExpr unpackByteBacked(int uintOffset, int size, String outType, Function<GlslExpr, GlslExpr> perElement) {
|
||||
List<GlslExpr> args = new ArrayList<>();
|
||||
for (int i = 0; i < size; i++) {
|
||||
int bitPos = i * 8;
|
||||
var element = access(uintOffset)
|
||||
.and(0xFF << bitPos)
|
||||
.rsh(bitPos);
|
||||
args.add(perElement.apply(element));
|
||||
}
|
||||
return GlslExpr.call(outType, args);
|
||||
}
|
||||
|
||||
private static GlslExpr unpackShortBacked(int uintOffset, int size, String outType, Function<GlslExpr, GlslExpr> perElement) {
|
||||
List<GlslExpr> args = new ArrayList<>();
|
||||
for (int i = 0; i < size; i++) {
|
||||
int bitPos = (i % 2) * 16;
|
||||
int wordOffset = i / 2;
|
||||
var element = access(uintOffset + wordOffset)
|
||||
.and(0xFFFF << bitPos)
|
||||
.rsh(bitPos);
|
||||
args.add(perElement.apply(element));
|
||||
}
|
||||
return GlslExpr.call(outType, args);
|
||||
}
|
||||
|
||||
private static GlslExpr unpack(int uintOffset, int size, String outType) {
|
||||
return unpack(uintOffset, size, outType, Function.identity());
|
||||
}
|
||||
|
||||
private static GlslExpr unpack(int uintOffset, int size, String outType, Function<GlslExpr, GlslExpr> perElement) {
|
||||
List<GlslExpr> args = new ArrayList<>();
|
||||
for (int i = 0; i < size; i++) {
|
||||
args.add(access(uintOffset + i)
|
||||
.transform(perElement));
|
||||
}
|
||||
return GlslExpr.call(outType, args);
|
||||
}
|
||||
|
||||
private static GlslExpr unpackMatrix(int uintOffset, MatrixElementType matrix) {
|
||||
var repr = matrix.repr();
|
||||
|
||||
int rows = matrix.rows();
|
||||
int columns = matrix.columns();
|
||||
|
||||
int columnWordSize = MoreMath.ceilingDiv(rows * repr.byteSize(), 4);
|
||||
|
||||
List<GlslExpr> args = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < columns; i++) {
|
||||
args.add(unpackFloatVector(uintOffset + i * columnWordSize, repr, rows));
|
||||
}
|
||||
|
||||
return GlslExpr.call("mat" + columns + "x" + rows, args);
|
||||
}
|
||||
}
|
|
@ -2,9 +2,9 @@ package com.jozufozu.flywheel.backend.engine.instancing;
|
|||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.jozufozu.flywheel.backend.InternalVertex;
|
||||
import com.jozufozu.flywheel.backend.engine.InstanceHandleImpl;
|
||||
import com.jozufozu.flywheel.backend.engine.MeshPool;
|
||||
import com.jozufozu.flywheel.backend.gl.TextureBuffer;
|
||||
import com.jozufozu.flywheel.backend.gl.array.GlVertexArray;
|
||||
|
||||
public class DrawCall {
|
||||
|
@ -31,12 +31,12 @@ public class DrawCall {
|
|||
return deleted;
|
||||
}
|
||||
|
||||
public void render() {
|
||||
public void render(TextureBuffer buffer) {
|
||||
if (mesh.invalid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
instancer.bindIfNeeded(vao, InternalVertex.ATTRIBUTE_COUNT);
|
||||
instancer.bind(buffer);
|
||||
mesh.setup(vao);
|
||||
|
||||
vao.bindForDraw();
|
||||
|
@ -44,7 +44,7 @@ public class DrawCall {
|
|||
mesh.draw(instancer.instanceCount());
|
||||
}
|
||||
|
||||
public void renderOne(InstanceHandleImpl impl) {
|
||||
public void renderOne(TextureBuffer buffer, InstanceHandleImpl impl) {
|
||||
if (mesh.invalid()) {
|
||||
return;
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ public class DrawCall {
|
|||
|
||||
var vao = lazyScratchVao();
|
||||
|
||||
instancer.bindRaw(vao, InternalVertex.ATTRIBUTE_COUNT, impl.index);
|
||||
instancer.bind(buffer);
|
||||
mesh.setup(vao);
|
||||
|
||||
vao.bindForDraw();
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
package com.jozufozu.flywheel.backend.engine.instancing;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import com.jozufozu.flywheel.api.model.IndexSequence;
|
||||
import com.jozufozu.flywheel.backend.gl.GlNumericType;
|
||||
import com.jozufozu.flywheel.backend.gl.buffer.Buffer;
|
||||
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferUsage;
|
||||
import com.jozufozu.flywheel.lib.memory.FlwMemoryTracker;
|
||||
import com.jozufozu.flywheel.lib.model.QuadIndexSequence;
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ReferenceMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
|
||||
|
||||
public class EboCache {
|
||||
private final List<Entry> quads = new ArrayList<>();
|
||||
private final Object2ReferenceMap<Key, Entry> others = new Object2ReferenceOpenHashMap<>();
|
||||
|
||||
public void invalidate() {
|
||||
quads.forEach(Entry::delete);
|
||||
others.values()
|
||||
.forEach(Entry::delete);
|
||||
}
|
||||
|
||||
public int get(IndexSequence indexSequence, int indexCount) {
|
||||
if (indexSequence == QuadIndexSequence.INSTANCE) {
|
||||
return getQuads(indexCount);
|
||||
} else {
|
||||
return others.computeIfAbsent(new Key(indexSequence, indexCount), Key::create).ebo;
|
||||
}
|
||||
}
|
||||
|
||||
private int getQuads(int indexCount) {
|
||||
// Use an existing quad EBO if there's one big enough.
|
||||
for (Entry quadEbo : quads) {
|
||||
if (quadEbo.gpuSize >= indexCount * GlNumericType.UINT.byteWidth()) {
|
||||
return quadEbo.ebo;
|
||||
}
|
||||
}
|
||||
// If not, create a new one.
|
||||
var out = Entry.create(QuadIndexSequence.INSTANCE, indexCount);
|
||||
quads.add(out);
|
||||
return out.ebo;
|
||||
}
|
||||
|
||||
private record Key(IndexSequence provider, int indexCount) {
|
||||
private Entry create() {
|
||||
return Entry.create(provider, indexCount);
|
||||
}
|
||||
}
|
||||
|
||||
private record Entry(int ebo, int gpuSize) {
|
||||
private static Entry create(IndexSequence provider, int indexCount) {
|
||||
int byteSize = indexCount * GlNumericType.UINT.byteWidth();
|
||||
var ebo = Buffer.IMPL.create();
|
||||
|
||||
final long ptr = MemoryUtil.nmemAlloc(byteSize);
|
||||
provider.fill(ptr, indexCount);
|
||||
|
||||
Buffer.IMPL.data(ebo, byteSize, ptr, GlBufferUsage.STATIC_DRAW.glEnum);
|
||||
FlwMemoryTracker._allocGPUMemory(byteSize);
|
||||
|
||||
MemoryUtil.nmemFree(ptr);
|
||||
|
||||
return new Entry(ebo, byteSize);
|
||||
}
|
||||
|
||||
private void delete() {
|
||||
GlStateManager._glDeleteBuffers(this.ebo);
|
||||
FlwMemoryTracker._freeGPUMemory(this.gpuSize);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import java.util.ArrayList;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.jozufozu.flywheel.api.backend.Engine;
|
||||
import com.jozufozu.flywheel.api.context.TextureSource;
|
||||
|
@ -15,6 +16,8 @@ import com.jozufozu.flywheel.backend.engine.MaterialRenderState;
|
|||
import com.jozufozu.flywheel.backend.engine.textures.TextureBinder;
|
||||
import com.jozufozu.flywheel.backend.engine.uniform.Uniforms;
|
||||
import com.jozufozu.flywheel.backend.gl.GlStateTracker;
|
||||
import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
|
||||
import com.jozufozu.flywheel.backend.gl.TextureBuffer;
|
||||
import com.jozufozu.flywheel.lib.context.ContextShaders;
|
||||
import com.jozufozu.flywheel.lib.context.Contexts;
|
||||
import com.jozufozu.flywheel.lib.material.SimpleMaterial;
|
||||
|
@ -24,7 +27,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
|||
import net.minecraft.client.resources.model.ModelBakery;
|
||||
|
||||
public class InstancedCrumbling {
|
||||
public static void render(List<Engine.CrumblingBlock> crumblingBlocks, InstancingPrograms programs, TextureSource textureSource) {
|
||||
public static void render(List<Engine.CrumblingBlock> crumblingBlocks, InstancingPrograms programs, TextureSource textureSource, TextureBuffer instanceTexture) {
|
||||
// Sort draw calls into buckets, so we don't have to do as many shader binds.
|
||||
var byShaderState = doCrumblingSort(crumblingBlocks);
|
||||
|
||||
|
@ -56,7 +59,7 @@ public class InstancedCrumbling {
|
|||
|
||||
MaterialRenderState.setup(crumblingMaterial);
|
||||
|
||||
for (Int2ObjectMap.Entry<List<Runnable>> progressEntry : byProgress.int2ObjectEntrySet()) {
|
||||
for (var progressEntry : byProgress.int2ObjectEntrySet()) {
|
||||
var drawCalls = progressEntry.getValue();
|
||||
|
||||
if (drawCalls.isEmpty()) {
|
||||
|
@ -66,7 +69,12 @@ public class InstancedCrumbling {
|
|||
var context = Contexts.CRUMBLING.get(progressEntry.getIntKey());
|
||||
context.prepare(crumblingMaterial, program, textureSource);
|
||||
|
||||
drawCalls.forEach(Runnable::run);
|
||||
GlTextureUnit.T3.makeActive();
|
||||
program.setSamplerBinding("_flw_instances", 3);
|
||||
|
||||
for (Consumer<TextureBuffer> drawCall : drawCalls) {
|
||||
drawCall.accept(instanceTexture);
|
||||
}
|
||||
|
||||
TextureBinder.resetTextureBindings();
|
||||
}
|
||||
|
@ -77,8 +85,8 @@ public class InstancedCrumbling {
|
|||
}
|
||||
}
|
||||
|
||||
private static Map<ShaderState, Int2ObjectMap<List<Runnable>>> doCrumblingSort(List<Engine.CrumblingBlock> instances) {
|
||||
Map<ShaderState, Int2ObjectMap<List<Runnable>>> out = new HashMap<>();
|
||||
private static Map<ShaderState, Int2ObjectMap<List<Consumer<TextureBuffer>>>> doCrumblingSort(List<Engine.CrumblingBlock> instances) {
|
||||
Map<ShaderState, Int2ObjectMap<List<Consumer<TextureBuffer>>>> out = new HashMap<>();
|
||||
|
||||
for (Engine.CrumblingBlock triple : instances) {
|
||||
int progress = triple.progress();
|
||||
|
@ -101,7 +109,7 @@ public class InstancedCrumbling {
|
|||
for (DrawCall draw : instancer.drawCalls()) {
|
||||
out.computeIfAbsent(draw.shaderState, $ -> new Int2ObjectArrayMap<>())
|
||||
.computeIfAbsent(progress, $ -> new ArrayList<>())
|
||||
.add(() -> draw.renderOne(impl));
|
||||
.add(buf -> draw.renderOne(buf, impl));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
package com.jozufozu.flywheel.backend.engine.instancing;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
@ -12,18 +10,15 @@ import com.jozufozu.flywheel.api.instance.Instance;
|
|||
import com.jozufozu.flywheel.api.instance.InstanceType;
|
||||
import com.jozufozu.flywheel.api.instance.InstanceWriter;
|
||||
import com.jozufozu.flywheel.backend.engine.AbstractInstancer;
|
||||
import com.jozufozu.flywheel.backend.engine.LayoutAttributes;
|
||||
import com.jozufozu.flywheel.backend.gl.array.GlVertexArray;
|
||||
import com.jozufozu.flywheel.backend.gl.array.VertexAttribute;
|
||||
import com.jozufozu.flywheel.backend.gl.TextureBuffer;
|
||||
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
|
||||
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferUsage;
|
||||
import com.jozufozu.flywheel.lib.math.MoreMath;
|
||||
import com.jozufozu.flywheel.lib.memory.MemoryBlock;
|
||||
|
||||
public class InstancedInstancer<I extends Instance> extends AbstractInstancer<I> {
|
||||
private final List<VertexAttribute> instanceAttributes;
|
||||
private final int instanceStride;
|
||||
|
||||
private final Set<GlVertexArray> boundTo = new HashSet<>();
|
||||
private final InstanceWriter<I> writer;
|
||||
@Nullable
|
||||
private GlBuffer vbo;
|
||||
|
@ -33,8 +28,8 @@ public class InstancedInstancer<I extends Instance> extends AbstractInstancer<I>
|
|||
public InstancedInstancer(InstanceType<I> type, Context context) {
|
||||
super(type, context);
|
||||
var layout = type.layout();
|
||||
instanceAttributes = LayoutAttributes.attributes(layout);
|
||||
instanceStride = layout.byteSize();
|
||||
// Align to one texel in the texture buffer
|
||||
instanceStride = MoreMath.align16(layout.byteSize());
|
||||
writer = type.writer();
|
||||
}
|
||||
|
||||
|
@ -111,35 +106,6 @@ public class InstancedInstancer<I extends Instance> extends AbstractInstancer<I>
|
|||
return capacity > vbo.size();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Bind this instancer's vbo to the given vao if it hasn't already been bound.
|
||||
* @param vao The vao to bind to.
|
||||
* @param startAttrib The first attribute to bind. This method will bind attributes in the half open range
|
||||
* {@code [startAttrib, startAttrib + instanceFormat.getAttributeCount())}.
|
||||
*/
|
||||
public void bindIfNeeded(GlVertexArray vao, int startAttrib) {
|
||||
if (!boundTo.add(vao)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bindRaw(vao, startAttrib, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind this instancer's vbo to the given vao with the given base instance to calculate the binding offset.
|
||||
* @param vao The vao to bind to.
|
||||
* @param startAttrib The first attribute to bind. This method will bind attributes in the half open range
|
||||
* {@code [startAttrib, startAttrib + instanceFormat.getAttributeCount())}.
|
||||
* @param baseInstance The base instance to calculate the binding offset from.
|
||||
*/
|
||||
public void bindRaw(GlVertexArray vao, int startAttrib, int baseInstance) {
|
||||
long offset = (long) baseInstance * instanceStride;
|
||||
vao.bindVertexBuffer(1, vbo.handle(), offset, instanceStride);
|
||||
vao.setBindingDivisor(1, 1);
|
||||
vao.bindAttributes(1, startAttrib, instanceAttributes);
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
if (vbo == null) {
|
||||
return;
|
||||
|
@ -159,4 +125,12 @@ public class InstancedInstancer<I extends Instance> extends AbstractInstancer<I>
|
|||
public List<DrawCall> drawCalls() {
|
||||
return drawCalls;
|
||||
}
|
||||
|
||||
public void bind(TextureBuffer buffer) {
|
||||
if (vbo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
buffer.bind(vbo.handle());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ import com.jozufozu.flywheel.backend.engine.textures.TextureBinder;
|
|||
import com.jozufozu.flywheel.backend.engine.textures.TextureSourceImpl;
|
||||
import com.jozufozu.flywheel.backend.engine.uniform.Uniforms;
|
||||
import com.jozufozu.flywheel.backend.gl.GlStateTracker;
|
||||
import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
|
||||
import com.jozufozu.flywheel.backend.gl.TextureBuffer;
|
||||
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
|
||||
import com.jozufozu.flywheel.lib.task.Flag;
|
||||
import com.jozufozu.flywheel.lib.task.NamedFlag;
|
||||
|
@ -28,6 +30,7 @@ import com.jozufozu.flywheel.lib.task.SyncedPlan;
|
|||
public class InstancingEngine extends AbstractEngine {
|
||||
private final InstancingPrograms programs;
|
||||
private final TextureSourceImpl textures = new TextureSourceImpl();
|
||||
private final TextureBuffer instanceTexture = new TextureBuffer();
|
||||
private final InstancedDrawManager drawManager = new InstancedDrawManager();
|
||||
private final Flag flushFlag = new NamedFlag("flushed");
|
||||
|
||||
|
@ -74,7 +77,7 @@ public class InstancingEngine extends AbstractEngine {
|
|||
// Need to wait for flush before we can inspect instancer state.
|
||||
executor.syncUntil(flushFlag::isRaised);
|
||||
|
||||
InstancedCrumbling.render(crumblingBlocks, programs, textures);
|
||||
InstancedCrumbling.render(crumblingBlocks, programs, textures, instanceTexture);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -86,6 +89,7 @@ public class InstancingEngine extends AbstractEngine {
|
|||
public void delete() {
|
||||
drawManager.delete();
|
||||
programs.release();
|
||||
instanceTexture.delete();
|
||||
}
|
||||
|
||||
private void render(InstancedDrawManager.DrawSet drawSet) {
|
||||
|
@ -110,8 +114,12 @@ public class InstancingEngine extends AbstractEngine {
|
|||
context.prepare(material, program, textures);
|
||||
MaterialRenderState.setup(material);
|
||||
|
||||
GlTextureUnit.T3.makeActive();
|
||||
|
||||
program.setSamplerBinding("_flw_instances", 3);
|
||||
|
||||
for (var drawCall : drawCalls) {
|
||||
drawCall.render();
|
||||
drawCall.render(instanceTexture);
|
||||
}
|
||||
TextureBinder.resetTextureBindings();
|
||||
}
|
||||
|
|
|
@ -16,8 +16,9 @@ public class TextureBinder {
|
|||
// 0 is reserved for diffuse
|
||||
// 1 is overlay
|
||||
// 2 is light
|
||||
// 3..n are for whatever else the context needs
|
||||
private static final int baseSamplerUnit = 3;
|
||||
// 3 is the instance buffer
|
||||
// 4..n are for whatever else the context needs
|
||||
private static final int baseSamplerUnit = 4;
|
||||
private static int nextSamplerUnit = baseSamplerUnit;
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package com.jozufozu.flywheel.backend.gl;
|
||||
|
||||
import org.lwjgl.opengl.GL32;
|
||||
|
||||
public class TextureBuffer extends GlObject {
|
||||
public static final int MAX_TEXELS = GL32.glGetInteger(GL32.GL_MAX_TEXTURE_BUFFER_SIZE);
|
||||
public static final int MAX_BYTES = MAX_TEXELS * 16; // 4 channels * 4 bytes
|
||||
|
||||
public TextureBuffer() {
|
||||
handle(GL32.glGenTextures());
|
||||
}
|
||||
|
||||
public void bind(int buffer) {
|
||||
GL32.glBindTexture(GL32.GL_TEXTURE_BUFFER, handle());
|
||||
GL32.glTexBuffer(GL32.GL_TEXTURE_BUFFER, GL32.GL_RGBA32UI, buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteInternal(int handle) {
|
||||
GL32.glDeleteTextures(handle);
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ void main() {
|
|||
_flw_uberMaterialVertexIndex = _flw_packedMaterial.x;
|
||||
_flw_unpackMaterialProperties(_flw_packedMaterial.w, flw_material);
|
||||
|
||||
FlwInstance instance = _flw_unpackInstance();
|
||||
FlwInstance instance = _flw_unpackInstance(gl_InstanceID);
|
||||
|
||||
_flw_main(instance, uint(gl_InstanceID));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue