diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/Pipelines.java b/src/main/java/com/jozufozu/flywheel/backend/compile/Pipelines.java index 895fe28f9..a5978677a 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/compile/Pipelines.java +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/Pipelines.java @@ -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") diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/component/InstancedArraysComponent.java b/src/main/java/com/jozufozu/flywheel/backend/compile/component/InstancedArraysComponent.java deleted file mode 100644 index 0c01606d1..000000000 --- a/src/main/java/com/jozufozu/flywheel/backend/compile/component/InstancedArraysComponent.java +++ /dev/null @@ -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 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)); - } -} diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/component/SamplerBufferComponent.java b/src/main/java/com/jozufozu/flywheel/backend/compile/component/SamplerBufferComponent.java new file mode 100644 index 000000000..4ad7a802a --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/component/SamplerBufferComponent.java @@ -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 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(); + 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 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 perElement) { + List 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 perElement) { + List 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 perElement) { + List 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 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); + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/DrawCall.java b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/DrawCall.java index 7578b5933..38d2824b4 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/DrawCall.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/DrawCall.java @@ -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(); diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/EboCache.java b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/EboCache.java deleted file mode 100644 index 94d53984f..000000000 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/EboCache.java +++ /dev/null @@ -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 quads = new ArrayList<>(); - private final Object2ReferenceMap 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); - } - } -} diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedCrumbling.java b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedCrumbling.java index 27adecff2..547283107 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedCrumbling.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedCrumbling.java @@ -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 crumblingBlocks, InstancingPrograms programs, TextureSource textureSource) { + public static void render(List 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> 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 drawCall : drawCalls) { + drawCall.accept(instanceTexture); + } + + TextureBinder.resetTextureBindings(); } } @@ -77,8 +85,8 @@ public class InstancedCrumbling { } } - private static Map>> doCrumblingSort(List instances) { - Map>> out = new HashMap<>(); + private static Map>>> doCrumblingSort(List instances) { + Map>>> 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)); } } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedInstancer.java b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedInstancer.java index 36d1f8e0d..5f7fce310 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedInstancer.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedInstancer.java @@ -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 extends AbstractInstancer { - private final List instanceAttributes; private final int instanceStride; - private final Set boundTo = new HashSet<>(); private final InstanceWriter writer; @Nullable private GlBuffer vbo; @@ -33,8 +28,8 @@ public class InstancedInstancer extends AbstractInstancer public InstancedInstancer(InstanceType 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 extends AbstractInstancer 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 extends AbstractInstancer public List drawCalls() { return drawCalls; } + + public void bind(TextureBuffer buffer) { + if (vbo == null) { + return; + } + + buffer.bind(vbo.handle()); + } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancingEngine.java b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancingEngine.java index 74e05798a..41747eea8 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancingEngine.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancingEngine.java @@ -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(); } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/textures/TextureBinder.java b/src/main/java/com/jozufozu/flywheel/backend/engine/textures/TextureBinder.java index eabe4c679..f16fe14f5 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/textures/TextureBinder.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/textures/TextureBinder.java @@ -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; /** diff --git a/src/main/java/com/jozufozu/flywheel/backend/gl/TextureBuffer.java b/src/main/java/com/jozufozu/flywheel/backend/gl/TextureBuffer.java new file mode 100644 index 000000000..b20ecd71e --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/gl/TextureBuffer.java @@ -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); + } +} diff --git a/src/main/resources/assets/flywheel/flywheel/internal/instancing/main.vert b/src/main/resources/assets/flywheel/flywheel/internal/instancing/main.vert index 8d449146d..ab330d1e1 100644 --- a/src/main/resources/assets/flywheel/flywheel/internal/instancing/main.vert +++ b/src/main/resources/assets/flywheel/flywheel/internal/instancing/main.vert @@ -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)); }