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:
Jozufozu 2024-02-22 13:46:11 -06:00
parent 17130e22ea
commit c6ed7c4132
11 changed files with 392 additions and 224 deletions

View file

@ -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")

View file

@ -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));
}
}

View file

@ -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);
}
}

View file

@ -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();

View file

@ -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);
}
}
}

View file

@ -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,9 +69,14 @@ 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);
TextureBinder.resetTextureBindings();
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));
}
}
}

View file

@ -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());
}
}

View file

@ -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();
}

View file

@ -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;
/**

View file

@ -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);
}
}

View file

@ -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));
}