The end of issues with big endianness

- Fix unpacking of some layout elements (only for instancing)
  - All byte backed and short backed elements (incorrect assumption that
system is little endian)
  - All signed byte and signed short elements (missing sign extension)
  - Matrices with byte backed and short backed reprs in some cases
(incorrect assumption that matrix rows are 4 byte aligned)
  - IntegerRepr.INT scalar (invalid implicit cast)
  - FloatRepr.NORMALIZED_INT scalar (missing cast to signed int)
  - IntegerRepr.INT vector (invalid implicit cast)
  - FloatRepr.NORMALIZED_INT vector (missing cast to signed int)
  - FloatRepr.NORMALIZED_SHORT vector (incorrect divisor)
- Add explicit casts from uint to float since some drivers have a bug
where uint over float division is invalid
- Lower GLSL requirement of instancing from 420 to 330
- Fix lib instance writers assuming little endian when writing overlay
and light
- Move overlay clamping to vertex input shader and clamp to 15 instead
of 10
- Create abstract InstanceAssemblerComponent class
- Change return type of SourceComponent.name() to String and improve
implementations
This commit is contained in:
PepperCode1 2024-03-03 11:16:38 -08:00
parent bf3b8ea89b
commit 2bb9092d49
20 changed files with 719 additions and 727 deletions

View File

@ -67,7 +67,7 @@ public final class FlwPrograms {
@Nullable
private static UberShaderComponent createVertexMaterialComponent(SourceLoader loadChecker) {
return UberShaderComponent.builder(Flywheel.rl("uber_material_vertex"))
return UberShaderComponent.builder(Flywheel.rl("material_vertex"))
.materialSources(ShaderIndices.materialVertex()
.all())
.adapt(FnSignature.ofVoid("flw_materialVertex"))
@ -77,7 +77,7 @@ public final class FlwPrograms {
@Nullable
private static UberShaderComponent createFragmentMaterialComponent(SourceLoader loadChecker) {
return UberShaderComponent.builder(Flywheel.rl("uber_material_fragment"))
return UberShaderComponent.builder(Flywheel.rl("material_fragment"))
.materialSources(ShaderIndices.materialFragment()
.all())
.adapt(FnSignature.ofVoid("flw_materialFragment"))
@ -87,7 +87,7 @@ public final class FlwPrograms {
@Nullable
private static UberShaderComponent createFogComponent(SourceLoader loadChecker) {
return UberShaderComponent.builder(Flywheel.rl("uber_fog"))
return UberShaderComponent.builder(Flywheel.rl("fog"))
.materialSources(ShaderIndices.fog()
.all())
.adapt(FnSignature.create()
@ -101,7 +101,7 @@ public final class FlwPrograms {
@Nullable
private static UberShaderComponent createCutoutComponent(SourceLoader loadChecker) {
return UberShaderComponent.builder(Flywheel.rl("uber_cutout"))
return UberShaderComponent.builder(Flywheel.rl("cutout"))
.materialSources(ShaderIndices.cutout()
.all())
.adapt(FnSignature.create()

View File

@ -8,7 +8,7 @@ import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.backend.compile.component.IndirectComponent;
import com.jozufozu.flywheel.backend.compile.component.StructInstanceComponent;
import com.jozufozu.flywheel.backend.compile.core.CompilationHarness;
import com.jozufozu.flywheel.backend.compile.core.Compile;
import com.jozufozu.flywheel.backend.gl.GlCompat;
@ -78,7 +78,7 @@ public class IndirectPrograms extends AtomicReferenceCounted {
.nameMapper(instanceType -> "culling/" + ResourceUtil.toDebugFileNameNoExtension(instanceType.cullShader()))
.define("_FLW_SUBGROUP_SIZE", GlCompat.SUBGROUP_SIZE)
.withResource(CULL_SHADER_HEADER)
.withComponent(IndirectComponent::create)
.withComponent(StructInstanceComponent::create)
.withResource(InstanceType::cullShader)
.withResource(CULL_SHADER_MAIN))
.postLink((key, program) -> program.setUniformBlockBinding("_FlwFrameUniforms", 0))

View File

@ -2,8 +2,8 @@ package com.jozufozu.flywheel.backend.compile;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.Samplers;
import com.jozufozu.flywheel.backend.compile.component.IndirectComponent;
import com.jozufozu.flywheel.backend.compile.component.SamplerBufferComponent;
import com.jozufozu.flywheel.backend.compile.component.BufferTextureInstanceComponent;
import com.jozufozu.flywheel.backend.compile.component.StructInstanceComponent;
import com.jozufozu.flywheel.backend.glsl.GlslVersion;
public final class Pipelines {
@ -14,7 +14,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(SamplerBufferComponent::create)
.assembler(BufferTextureInstanceComponent::create)
.onLink(program -> program.setSamplerBinding("_flw_instances", Samplers.INSTANCE_BUFFER))
.build();
public static final Pipeline INDIRECT = Pipeline.builder()
@ -24,7 +24,7 @@ public final class Pipelines {
.fragmentMain(Flywheel.rl("internal/indirect/main.frag"))
.vertexApiImpl(Flywheel.rl("internal/indirect/api_impl.vert"))
.fragmentApiImpl(Flywheel.rl("internal/indirect/api_impl.frag"))
.assembler(IndirectComponent::create)
.assembler(StructInstanceComponent::create)
.onLink($ -> {
})
.build();

View File

@ -0,0 +1,292 @@
package com.jozufozu.flywheel.backend.compile.component;
import java.util.ArrayList;
import java.util.EnumMap;
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.ValueRepr;
import com.jozufozu.flywheel.api.layout.VectorElementType;
import com.jozufozu.flywheel.backend.compile.Pipeline;
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;
public class BufferTextureInstanceComponent extends InstanceAssemblerComponent {
private static final String UNPACK_ARG = "index";
private static final String[] SWIZZLE_SELECTORS = { "x", "y", "z", "w" };
// Each function receives a uint expression as the input.
// For byte unpacking, the lowest 8 bits contain the value. For short unpacking, the lowest 16 bits contain the value.
// In both cases, all other bits are 0.
private static final EnumMap<IntegerRepr, Function<GlslExpr, GlslExpr>> INT_UNPACKING_FUNCS = new EnumMap<>(IntegerRepr.class);
private static final EnumMap<UnsignedIntegerRepr, Function<GlslExpr, GlslExpr>> UINT_UNPACKING_FUNCS = new EnumMap<>(UnsignedIntegerRepr.class);
private static final EnumMap<FloatRepr, Function<GlslExpr, GlslExpr>> FLOAT_UNPACKING_FUNCS = new EnumMap<>(FloatRepr.class);
static {
INT_UNPACKING_FUNCS.put(IntegerRepr.BYTE, e -> signExtendByte(e).cast("int"));
INT_UNPACKING_FUNCS.put(IntegerRepr.SHORT, e -> signExtendShort(e).cast("int"));
INT_UNPACKING_FUNCS.put(IntegerRepr.INT, e -> e.cast("int"));
UINT_UNPACKING_FUNCS.put(UnsignedIntegerRepr.UNSIGNED_BYTE, Function.identity());
UINT_UNPACKING_FUNCS.put(UnsignedIntegerRepr.UNSIGNED_SHORT, Function.identity());
UINT_UNPACKING_FUNCS.put(UnsignedIntegerRepr.UNSIGNED_INT, Function.identity());
FLOAT_UNPACKING_FUNCS.put(FloatRepr.BYTE, e -> signExtendByte(e).cast("int").cast("float"));
FLOAT_UNPACKING_FUNCS.put(FloatRepr.NORMALIZED_BYTE, e -> signExtendByte(e).cast("int").cast("float").div(127f).clamp(-1, 1));
FLOAT_UNPACKING_FUNCS.put(FloatRepr.UNSIGNED_BYTE, e -> e.cast("float"));
FLOAT_UNPACKING_FUNCS.put(FloatRepr.NORMALIZED_UNSIGNED_BYTE, e -> e.cast("float").div(255f));
FLOAT_UNPACKING_FUNCS.put(FloatRepr.SHORT, e -> signExtendShort(e).cast("int").cast("float"));
FLOAT_UNPACKING_FUNCS.put(FloatRepr.NORMALIZED_SHORT, e -> signExtendShort(e).cast("int").cast("float").div(32767f).clamp(-1, 1));
FLOAT_UNPACKING_FUNCS.put(FloatRepr.UNSIGNED_SHORT, e -> e.cast("float"));
FLOAT_UNPACKING_FUNCS.put(FloatRepr.NORMALIZED_UNSIGNED_SHORT, e -> e.cast("float").div(65535f));
FLOAT_UNPACKING_FUNCS.put(FloatRepr.INT, e -> e.cast("int").cast("float"));
FLOAT_UNPACKING_FUNCS.put(FloatRepr.NORMALIZED_INT, e -> e.cast("int").cast("float").div(2147483647f).clamp(-1, 1));
FLOAT_UNPACKING_FUNCS.put(FloatRepr.UNSIGNED_INT, e -> e.cast("float"));
FLOAT_UNPACKING_FUNCS.put(FloatRepr.NORMALIZED_UNSIGNED_INT, e -> e.cast("float").div(4294967295f));
FLOAT_UNPACKING_FUNCS.put(FloatRepr.FLOAT, e -> e.callFunction("uintBitsToFloat")); // FIXME: GLSL 330+
}
public BufferTextureInstanceComponent(InstanceType<?> type) {
super(type);
}
public static BufferTextureInstanceComponent create(InstanceType<?> type) {
return new BufferTextureInstanceComponent(type);
}
public static BufferTextureInstanceComponent create(Pipeline.InstanceAssemblerContext ctx) {
return create(ctx.instanceType());
}
// https://graphics.stanford.edu/~seander/bithacks.html#FixedSignExtend
// Assumes bits higher than sign bit are zero
private static GlslExpr signExtendByte(GlslExpr e) {
return e.xor(0x80).sub(0x80);
}
private static GlslExpr signExtendShort(GlslExpr e) {
return e.xor(0x8000).sub(0x8000);
}
@Override
public String name() {
return Flywheel.rl("buffer_texture_instance_assembler").toString();
}
@Override
protected void generateUnpacking(GlslBuilder builder) {
var block = new GlslBlock();
// TODO: don't require writing to be 16 byte aligned
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));
// Element byte size is always a multiple of 4
uintOffset += element.type().byteSize() / 4;
}
block.ret(GlslExpr.call(STRUCT_NAME, unpackArgs));
builder._addRaw("uniform usamplerBuffer _flw_instances;");
builder.blankLine();
builder.function()
.signature(FnSignature.create()
.returnType(STRUCT_NAME)
.name(UNPACK_FN_NAME)
.arg("int", UNPACK_ARG)
.build())
.body(block);
}
private static GlslExpr unpackElement(Layout.Element element, int uintOffset) {
var type = element.type();
if (type instanceof ScalarElementType scalar) {
return unpackScalar(scalar, uintOffset);
} else if (type instanceof VectorElementType vector) {
return unpackVector(vector, uintOffset);
} else if (type instanceof MatrixElementType matrix) {
return unpackMatrix(matrix, uintOffset);
}
throw new IllegalArgumentException("Unknown type " + type);
}
private static GlslExpr unpackScalar(ScalarElementType type, int uintOffset) {
var repr = type.repr();
Function<GlslExpr, GlslExpr> unpackingFunc;
if (repr instanceof IntegerRepr intRepr) {
unpackingFunc = INT_UNPACKING_FUNCS.get(intRepr);
} else if (repr instanceof UnsignedIntegerRepr uintRepr) {
unpackingFunc = UINT_UNPACKING_FUNCS.get(uintRepr);
} else if (repr instanceof FloatRepr floatRepr) {
unpackingFunc = FLOAT_UNPACKING_FUNCS.get(floatRepr);
} else {
throw new IllegalArgumentException("Unknown repr " + repr);
}
if (isByteBacked(repr)) {
return unpackByteBackedScalar(uintOffset, unpackingFunc);
} else if (isShortBacked(repr)) {
return unpackShortBackedScalar(uintOffset, unpackingFunc);
} else {
return unpackIntBackedScalar(uintOffset, unpackingFunc);
}
}
private static GlslExpr unpackVector(VectorElementType type, int uintOffset) {
var repr = type.repr();
int size = type.size();
Function<GlslExpr, GlslExpr> unpackingFunc;
String outType;
if (repr instanceof IntegerRepr intRepr) {
unpackingFunc = INT_UNPACKING_FUNCS.get(intRepr);
outType = "ivec" + size;
} else if (repr instanceof UnsignedIntegerRepr uintRepr) {
unpackingFunc = UINT_UNPACKING_FUNCS.get(uintRepr);
outType = "uvec" + size;
} else if (repr instanceof FloatRepr floatRepr) {
unpackingFunc = FLOAT_UNPACKING_FUNCS.get(floatRepr);
outType = "vec" + size;
} else {
throw new IllegalArgumentException("Unknown repr " + repr);
}
if (isByteBacked(repr)) {
return unpackByteBackedVector(outType, size, uintOffset, unpackingFunc);
} else if (isShortBacked(repr)) {
return unpackShortBackedVector(outType, size, uintOffset, unpackingFunc);
} else {
return unpackIntBackedVector(outType, size, uintOffset, unpackingFunc);
}
}
private static GlslExpr unpackMatrix(MatrixElementType type, int uintOffset) {
var repr = type.repr();
int rows = type.rows();
int columns = type.columns();
Function<GlslExpr, GlslExpr> unpackingFunc = FLOAT_UNPACKING_FUNCS.get(repr);
String outType = "mat" + columns + "x" + rows;
int size = rows * columns;
if (isByteBacked(repr)) {
return unpackByteBackedVector(outType, size, uintOffset, unpackingFunc);
} else if (isShortBacked(repr)) {
return unpackShortBackedVector(outType, size, uintOffset, unpackingFunc);
} else {
return unpackIntBackedVector(outType, size, uintOffset, unpackingFunc);
}
}
private static boolean isByteBacked(ValueRepr repr) {
return repr.byteSize() == Byte.BYTES;
}
private static boolean isShortBacked(ValueRepr repr) {
return repr.byteSize() == Short.BYTES;
}
private static GlslExpr unpackByteBackedScalar(int uintOffset, Function<GlslExpr, GlslExpr> perElement) {
GlslExpr e;
if (BIG_ENDIAN) {
e = access(uintOffset)
.rsh(24)
.and(0xFF);
} else {
e = access(uintOffset)
.and(0xFF);
}
return perElement.apply(e);
}
private static GlslExpr unpackShortBackedScalar(int uintOffset, Function<GlslExpr, GlslExpr> perElement) {
GlslExpr e;
if (BIG_ENDIAN) {
e = access(uintOffset)
.rsh(16)
.and(0xFFFF);
} else {
e = access(uintOffset)
.and(0xFFFF);
}
return perElement.apply(e);
}
private static GlslExpr unpackIntBackedScalar(int uintOffset, Function<GlslExpr, GlslExpr> perElement) {
return perElement.apply(access(uintOffset));
}
private static GlslExpr unpackByteBackedVector(String outType, int size, int uintOffset, Function<GlslExpr, GlslExpr> perElement) {
List<GlslExpr> args = new ArrayList<>();
for (int i = 0; i < size; i++) {
// Vectors cannot contain more than 4 elements, but matrix unpacking treats the matrix as a long vector, which for mat4x4 would be the equivalent of a vec16.
int bitPos = (i % 4) * 8;
if (BIG_ENDIAN) {
bitPos = 24 - bitPos;
}
int wordOffset = i / 4;
var element = access(uintOffset + wordOffset)
.rsh(bitPos)
.and(0xFF);
args.add(perElement.apply(element));
}
return GlslExpr.call(outType, args);
}
private static GlslExpr unpackShortBackedVector(String outType, int size, int uintOffset, Function<GlslExpr, GlslExpr> perElement) {
List<GlslExpr> args = new ArrayList<>();
for (int i = 0; i < size; i++) {
int bitPos = (i % 2) * 16;
if (BIG_ENDIAN) {
bitPos = 16 - bitPos;
}
int wordOffset = i / 2;
var element = access(uintOffset + wordOffset)
.rsh(bitPos)
.and(0xFFFF);
args.add(perElement.apply(element));
}
return GlslExpr.call(outType, args);
}
private static GlslExpr unpackIntBackedVector(String outType, int size, int uintOffset, Function<GlslExpr, GlslExpr> perElement) {
List<GlslExpr> args = new ArrayList<>();
for (int i = 0; i < size; i++) {
args.add(perElement.apply(access(uintOffset + i)));
}
return GlslExpr.call(outType, args);
}
private static GlslExpr access(int uintOffset) {
return GlslExpr.variable("u" + (uintOffset >> 2))
.swizzle(SWIZZLE_SELECTORS[uintOffset & 3]);
}
}

View File

@ -1,349 +0,0 @@
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.GlslStruct;
import net.minecraft.resources.ResourceLocation;
public class IndirectComponent implements SourceComponent {
private static final String UNPACK_ARG = "p";
private static final GlslExpr.Variable UNPACKING_VARIABLE = GlslExpr.variable(UNPACK_ARG);
private static final String STRUCT_NAME = "FlwInstance";
private static final String PACKED_STRUCT_NAME = "FlwPackedInstance";
private static final String UNPACK_FN_NAME = "_flw_unpackInstance";
private final Layout layout;
public IndirectComponent(InstanceType<?> type) {
this.layout = type.layout();
}
public static IndirectComponent create(Pipeline.InstanceAssemblerContext ctx) {
return create(ctx.instanceType());
}
public static IndirectComponent create(InstanceType<?> instanceType) {
return new IndirectComponent(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();
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 packed = builder.struct();
packed.setName(PACKED_STRUCT_NAME);
var unpackArgs = new ArrayList<GlslExpr>();
for (Layout.Element element : layout.elements()) {
unpackArgs.add(unpackElement(element, packed));
}
var block = new GlslBlock();
block.ret(GlslExpr.call(STRUCT_NAME, unpackArgs));
builder.blankLine();
builder.function()
.signature(FnSignature.create()
.returnType(STRUCT_NAME)
.name(UNPACK_FN_NAME)
.arg(PACKED_STRUCT_NAME, UNPACK_ARG)
.build())
.body(block);
}
public static GlslExpr unpackElement(Layout.Element element, GlslStruct packed) {
// 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();
var name = element.name();
if (type instanceof ScalarElementType scalar) {
return unpackScalar(name, packed, scalar);
} else if (type instanceof VectorElementType vector) {
return unpackVector(name, packed, vector);
} else if (type instanceof MatrixElementType matrix) {
return unpackMatrix(name, packed, matrix);
}
throw new IllegalArgumentException("Unknown type " + type);
}
private static GlslExpr unpackScalar(String fieldName, GlslStruct packed, ScalarElementType scalar) {
var repr = scalar.repr();
if (repr instanceof IntegerRepr intRepr) {
return unpackIntScalar(fieldName, intRepr, packed);
} else if (repr instanceof UnsignedIntegerRepr unsignedIntegerRepr) {
return unpackUnsignedScalar(fieldName, unsignedIntegerRepr, packed);
} else if (repr instanceof FloatRepr floatRepr) {
return unpackFloatScalar(fieldName, floatRepr, packed);
}
throw new IllegalArgumentException("Unknown repr " + repr);
}
private static GlslExpr unpackIntScalar(String fieldName, IntegerRepr intRepr, GlslStruct packed) {
return switch (intRepr) {
case BYTE -> unpackScalar(fieldName, packed, "uint", e -> e.and(0xFF)
.cast("int"));
case SHORT -> unpackScalar(fieldName, packed, "uint", e -> e.and(0xFFFF)
.cast("int"));
case INT -> unpackScalar(fieldName, packed, "int");
};
}
private static GlslExpr unpackUnsignedScalar(String fieldName, UnsignedIntegerRepr repr, GlslStruct packed) {
return switch (repr) {
case UNSIGNED_BYTE -> unpackScalar(fieldName, packed, "uint", e -> e.and(0xFF));
case UNSIGNED_SHORT -> unpackScalar(fieldName, packed, "uint", e -> e.and(0xFFFF));
case UNSIGNED_INT -> unpackScalar(fieldName, packed, "uint");
};
}
private static GlslExpr unpackFloatScalar(String fieldName, FloatRepr repr, GlslStruct packed) {
return switch (repr) {
case BYTE -> unpackScalar(fieldName, packed, "uint", e -> e.and(0xFF)
.cast("int")
.cast("float"));
case NORMALIZED_BYTE -> unpackScalar(fieldName, packed, "uint", e -> e.callFunction("unpackSnorm4x8")
.swizzle("x"));
case UNSIGNED_BYTE -> unpackScalar(fieldName, packed, "uint", e -> e.and(0xFF)
.cast("float"));
case NORMALIZED_UNSIGNED_BYTE ->
unpackScalar(fieldName, packed, "uint", e -> e.callFunction("unpackUnorm4x8")
.swizzle("x"));
case SHORT -> unpackScalar(fieldName, packed, "uint", e -> e.and(0xFFFF)
.cast("int")
.cast("float"));
case NORMALIZED_SHORT -> unpackScalar(fieldName, packed, "uint", e -> e.callFunction("unpackSnorm2x16")
.swizzle("x"));
case UNSIGNED_SHORT -> unpackScalar(fieldName, packed, "uint", e -> e.and(0xFFFF)
.cast("float"));
case NORMALIZED_UNSIGNED_SHORT ->
unpackScalar(fieldName, packed, "uint", e -> e.callFunction("unpackUnorm2x16")
.swizzle("x"));
case INT -> unpackScalar(fieldName, packed, "int", e -> e.cast("float"));
case NORMALIZED_INT -> unpackScalar(fieldName, packed, "int", e -> e.div(2147483647f)
.clamp(-1, 1));
case UNSIGNED_INT -> unpackScalar(fieldName, packed, "uint", e -> e.cast("float"));
case NORMALIZED_UNSIGNED_INT -> unpackScalar(fieldName, packed, "uint", e -> e.div(4294967295f));
case FLOAT -> unpackScalar(fieldName, packed, "float");
};
}
private static GlslExpr unpackScalar(String fieldName, GlslStruct packed, String packedType) {
return unpackScalar(fieldName, packed, packedType, Function.identity());
}
private static GlslExpr unpackScalar(String fieldName, GlslStruct packed, String packedType, Function<GlslExpr, GlslExpr> perElement) {
packed.addField(packedType, fieldName);
return perElement.apply(UNPACKING_VARIABLE.access(fieldName));
}
private static GlslExpr unpackVector(String fieldName, GlslStruct packed, VectorElementType vector) {
var repr = vector.repr();
int size = vector.size();
if (repr instanceof IntegerRepr intRepr) {
return unpackIntVector(fieldName, intRepr, packed, size);
} else if (repr instanceof UnsignedIntegerRepr unsignedIntegerRepr) {
return unpackUnsignedVector(fieldName, unsignedIntegerRepr, packed, size);
} else if (repr instanceof FloatRepr floatRepr) {
return unpackFloatVector(fieldName, floatRepr, packed, size);
}
throw new IllegalArgumentException("Unknown repr " + repr);
}
private static GlslExpr unpackIntVector(String fieldName, IntegerRepr repr, GlslStruct packed, int size) {
return switch (repr) {
case BYTE -> unpackByteBacked(fieldName, packed, size, "ivec" + size, e -> e.cast("int"));
case SHORT -> unpackShortBacked(fieldName, packed, size, "ivec" + size, e -> e.cast("int"));
case INT -> unpack(fieldName, packed, size, "int", "ivec" + size);
};
}
private static GlslExpr unpackUnsignedVector(String fieldName, UnsignedIntegerRepr unsignedIntegerRepr, GlslStruct packed, int size) {
return switch (unsignedIntegerRepr) {
case UNSIGNED_BYTE -> unpackByteBacked(fieldName, packed, size, "uvec" + size, e -> e.cast("uint"));
case UNSIGNED_SHORT -> unpackShortBacked(fieldName, packed, size, "uvec" + size, e -> e.cast("uint"));
case UNSIGNED_INT -> unpack(fieldName, packed, size, "uint", "uvec" + size);
};
}
private static GlslExpr unpackFloatVector(String fieldName, FloatRepr floatRepr, GlslStruct packed, int size) {
return switch (floatRepr) {
case NORMALIZED_BYTE -> unpackByteBuiltin(fieldName, packed, size, "unpackSnorm4x8");
case NORMALIZED_UNSIGNED_BYTE -> unpackByteBuiltin(fieldName, packed, size, "unpackUnorm4x8");
case NORMALIZED_SHORT -> unpackShortBuiltin(fieldName, packed, size, "unpackSnorm2x16");
case NORMALIZED_UNSIGNED_SHORT -> unpackShortBuiltin(fieldName, packed, size, "unpackUnorm2x16");
case NORMALIZED_INT -> unpack(fieldName, packed, size, "int", "vec" + size, e -> e.div(2147483647f)
.clamp(-1, 1));
case NORMALIZED_UNSIGNED_INT ->
unpack(fieldName, packed, size, "uint", "vec" + size, e -> e.div(4294967295f));
case BYTE -> unpackByteBacked(fieldName, packed, size, "vec" + size, e -> e.cast("int")
.cast("float"));
case UNSIGNED_BYTE -> unpackByteBacked(fieldName, packed, size, "vec" + size, e -> e.cast("float"));
case SHORT -> unpackShortBacked(fieldName, packed, size, "vec" + size, e -> e.cast("int")
.cast("float"));
case UNSIGNED_SHORT -> unpackShortBacked(fieldName, packed, size, "vec" + size, e -> e.cast("float"));
case INT -> unpack(fieldName, packed, size, "int", "vec" + size, e -> e.cast("float"));
case UNSIGNED_INT -> unpack(fieldName, packed, size, "float", "vec" + size, e -> e.cast("float"));
case FLOAT -> unpack(fieldName, packed, size, "float", "vec" + size);
};
}
private static GlslExpr unpackByteBacked(String fieldName, GlslStruct packed, int size, String outType, Function<GlslExpr, GlslExpr> perElement) {
packed.addField("uint", fieldName);
List<GlslExpr> args = new ArrayList<>();
for (int i = 0; i < size; i++) {
int bitPos = i * 8;
var element = UNPACKING_VARIABLE.access(fieldName)
.and(0xFF << bitPos)
.rsh(bitPos);
args.add(perElement.apply(element));
}
return GlslExpr.call(outType, args);
}
private static GlslExpr unpackShortBacked(String fieldName, GlslStruct packed, int size, String outType, Function<GlslExpr, GlslExpr> perElement) {
List<GlslExpr> args = new ArrayList<>();
for (int i = 0; i < size; i++) {
int unpackField = i / 2;
int bitPos = (i % 2) * 16;
var name = fieldName + "_" + unpackField;
if (bitPos == 0) {
// First time we're seeing this field, add it to the struct.
packed.addField("uint", name);
}
var element = UNPACKING_VARIABLE.access(name)
.and(0xFFFF << bitPos)
.rsh(bitPos);
args.add(perElement.apply(element));
}
return GlslExpr.call(outType, args);
}
private static GlslExpr unpack(String fieldName, GlslStruct packed, int size, String backingType, String outType) {
return unpack(fieldName, packed, size, backingType, outType, Function.identity());
}
private static GlslExpr unpack(String fieldName, GlslStruct packed, int size, String backingType, String outType, Function<GlslExpr, GlslExpr> perElement) {
List<GlslExpr> args = new ArrayList<>();
for (int i = 0; i < size; i++) {
var name = fieldName + "_" + i;
packed.addField(backingType, name);
args.add(UNPACKING_VARIABLE.access(name)
.transform(perElement));
}
return GlslExpr.call(outType, args);
}
private static GlslExpr unpackByteBuiltin(String fieldName, GlslStruct packed, int size, String func) {
packed.addField("uint", fieldName);
GlslExpr expr = UNPACKING_VARIABLE.access(fieldName)
.callFunction(func);
return switch (size) {
case 2 -> expr.swizzle("xy");
case 3 -> expr.swizzle("xyz");
case 4 -> expr;
default -> throw new IllegalArgumentException("Invalid vector size " + size);
};
}
private static GlslExpr unpackShortBuiltin(String fieldName, GlslStruct packed, int size, String func) {
if (size == 2) {
packed.addField("uint", fieldName);
return UNPACKING_VARIABLE.access(fieldName)
.callFunction(func);
} else {
var name0 = fieldName + "_" + 0;
var name1 = fieldName + "_" + 1;
packed.addField("uint", name0);
packed.addField("uint", name1);
GlslExpr xy = UNPACKING_VARIABLE.access(name0)
.callFunction(func);
GlslExpr zw = UNPACKING_VARIABLE.access(name1)
.callFunction(func);
if (size == 3) {
return GlslExpr.call("vec3", List.of(xy.swizzle("xy"), zw.swizzle("x")));
} else {
return GlslExpr.call("vec4", List.of(xy, zw));
}
}
}
private static GlslExpr unpackMatrix(String name, GlslStruct packed, MatrixElementType matrix) {
var repr = matrix.repr();
int rows = matrix.rows();
int columns = matrix.columns();
List<GlslExpr> args = new ArrayList<>();
for (int i = 0; i < columns; i++) {
args.add(unpackFloatVector(name + "_" + i, repr, packed, rows));
}
return GlslExpr.call("mat" + columns + "x" + rows, args);
}
}

View File

@ -0,0 +1,49 @@
package com.jozufozu.flywheel.backend.compile.component;
import java.nio.ByteOrder;
import java.util.Collection;
import java.util.Collections;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.layout.Layout;
import com.jozufozu.flywheel.backend.compile.LayoutInterpreter;
import com.jozufozu.flywheel.backend.glsl.SourceComponent;
import com.jozufozu.flywheel.backend.glsl.generate.GlslBuilder;
public abstract class InstanceAssemblerComponent implements SourceComponent {
protected static final String STRUCT_NAME = "FlwInstance";
protected static final String UNPACK_FN_NAME = "_flw_unpackInstance";
protected static final boolean BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
protected final Layout layout;
public InstanceAssemblerComponent(InstanceType<?> type) {
layout = type.layout();
}
@Override
public Collection<? extends SourceComponent> included() {
return Collections.emptyList();
}
@Override
public String source() {
var builder = new GlslBuilder();
generateInstanceStruct(builder);
builder.blankLine();
generateUnpacking(builder);
builder.blankLine();
return builder.build();
}
protected 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());
}
}
protected abstract void generateUnpacking(GlslBuilder builder);
}

View File

@ -1,322 +0,0 @@
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_instancing");
}
@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

@ -3,20 +3,19 @@ package com.jozufozu.flywheel.backend.compile.component;
import java.util.Collection;
import java.util.Map;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.glsl.SourceComponent;
import net.minecraft.resources.ResourceLocation;
public final class StringSubstitutionSourceComponent implements SourceComponent {
public final class StringSubstitutionComponent implements SourceComponent {
private final SourceComponent source;
private final Map<String, String> replacements;
private final String sourceString;
public StringSubstitutionSourceComponent(SourceComponent source, String find, String replace) {
public StringSubstitutionComponent(SourceComponent source, String find, String replace) {
this(source, Map.of(find, replace));
}
public StringSubstitutionSourceComponent(SourceComponent source, Map<String, String> replacements) {
public StringSubstitutionComponent(SourceComponent source, Map<String, String> replacements) {
this.source = source;
this.replacements = replacements;
this.sourceString = source.source();
@ -42,8 +41,8 @@ public final class StringSubstitutionSourceComponent implements SourceComponent
}
@Override
public ResourceLocation name() {
return source.name().withSuffix("_string_substitution");
public String name() {
return Flywheel.rl("string_substitution").toString() + " / " + source.name();
}
@Override

View File

@ -0,0 +1,308 @@
package com.jozufozu.flywheel.backend.compile.component;
import java.util.ArrayList;
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.Pipeline;
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.GlslStruct;
// TODO: Use a uvec4[] instead of a FlwPackedInstance[] in the SSBO to store data and reuse unpacking code from BufferTextureInstanceComponent.
// Unpacking should be moved from BufferTextureInstanceComponent to InstanceAssemblerComponent and this class should be renamed to SsboInstanceComponent. Further abstraction may be possible.
// Currently, some of the unpacking code generated by this class is incorrect. It is correct in BufferTextureInstanceComponent.
public class StructInstanceComponent extends InstanceAssemblerComponent {
private static final String UNPACK_ARG = "p";
private static final GlslExpr.Variable UNPACKING_VARIABLE = GlslExpr.variable(UNPACK_ARG);
private static final String PACKED_STRUCT_NAME = "FlwPackedInstance";
public StructInstanceComponent(InstanceType<?> type) {
super(type);
}
public static StructInstanceComponent create(InstanceType<?> type) {
return new StructInstanceComponent(type);
}
public static StructInstanceComponent create(Pipeline.InstanceAssemblerContext ctx) {
return create(ctx.instanceType());
}
@Override
public String name() {
return Flywheel.rl("struct_instance_assembler").toString();
}
@Override
protected void generateUnpacking(GlslBuilder builder) {
var packed = builder.struct();
packed.setName(PACKED_STRUCT_NAME);
var unpackArgs = new ArrayList<GlslExpr>();
for (Layout.Element element : layout.elements()) {
unpackArgs.add(unpackElement(element, packed));
}
var block = new GlslBlock();
block.ret(GlslExpr.call(STRUCT_NAME, unpackArgs));
builder.blankLine();
builder.function()
.signature(FnSignature.create()
.returnType(STRUCT_NAME)
.name(UNPACK_FN_NAME)
.arg(PACKED_STRUCT_NAME, UNPACK_ARG)
.build())
.body(block);
}
private static GlslExpr unpackElement(Layout.Element element, GlslStruct packed) {
// 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();
var name = element.name();
if (type instanceof ScalarElementType scalar) {
return unpackScalar(scalar, name, packed);
} else if (type instanceof VectorElementType vector) {
return unpackVector(vector, name, packed);
} else if (type instanceof MatrixElementType matrix) {
return unpackMatrix(matrix, name, packed);
}
throw new IllegalArgumentException("Unknown type " + type);
}
private static GlslExpr unpackScalar(ScalarElementType type, String fieldName, GlslStruct packed) {
var repr = type.repr();
if (repr instanceof IntegerRepr intRepr) {
return unpackIntScalar(intRepr, fieldName, packed);
} else if (repr instanceof UnsignedIntegerRepr uintRepr) {
return unpackUintScalar(uintRepr, fieldName, packed);
} else if (repr instanceof FloatRepr floatRepr) {
return unpackFloatScalar(floatRepr, fieldName, packed);
}
throw new IllegalArgumentException("Unknown repr " + repr);
}
private static GlslExpr unpackIntScalar(IntegerRepr repr, String fieldName, GlslStruct packed) {
return switch (repr) {
case BYTE -> unpackScalar("uint", fieldName, packed, e -> e.and(0xFF)
.cast("int"));
case SHORT -> unpackScalar("uint", fieldName, packed, e -> e.and(0xFFFF)
.cast("int"));
case INT -> unpackScalar("int", fieldName, packed);
};
}
private static GlslExpr unpackUintScalar(UnsignedIntegerRepr repr, String fieldName, GlslStruct packed) {
return switch (repr) {
case UNSIGNED_BYTE -> unpackScalar("uint", fieldName, packed, e -> e.and(0xFF));
case UNSIGNED_SHORT -> unpackScalar("uint", fieldName, packed, e -> e.and(0xFFFF));
case UNSIGNED_INT -> unpackScalar("uint", fieldName, packed);
};
}
private static GlslExpr unpackFloatScalar(FloatRepr repr, String fieldName, GlslStruct packed) {
return switch (repr) {
case BYTE -> unpackScalar("uint", fieldName, packed, e -> e.and(0xFF)
.cast("int")
.cast("float"));
case NORMALIZED_BYTE -> unpackScalar("uint", fieldName, packed, e -> e.callFunction("unpackSnorm4x8")
.swizzle("x"));
case UNSIGNED_BYTE -> unpackScalar("uint", fieldName, packed, e -> e.and(0xFF)
.cast("float"));
case NORMALIZED_UNSIGNED_BYTE ->
unpackScalar("uint", fieldName, packed, e -> e.callFunction("unpackUnorm4x8")
.swizzle("x"));
case SHORT -> unpackScalar("uint", fieldName, packed, e -> e.and(0xFFFF)
.cast("int")
.cast("float"));
case NORMALIZED_SHORT -> unpackScalar("uint", fieldName, packed, e -> e.callFunction("unpackSnorm2x16")
.swizzle("x"));
case UNSIGNED_SHORT -> unpackScalar("uint", fieldName, packed, e -> e.and(0xFFFF)
.cast("float"));
case NORMALIZED_UNSIGNED_SHORT ->
unpackScalar("uint", fieldName, packed, e -> e.callFunction("unpackUnorm2x16")
.swizzle("x"));
case INT -> unpackScalar("int", fieldName, packed, e -> e.cast("float"));
case NORMALIZED_INT -> unpackScalar("int", fieldName, packed, e -> e.div(2147483647f)
.clamp(-1, 1));
case UNSIGNED_INT -> unpackScalar("uint", fieldName, packed, e -> e.cast("float"));
case NORMALIZED_UNSIGNED_INT -> unpackScalar("uint", fieldName, packed, e -> e.div(4294967295f));
case FLOAT -> unpackScalar("float", fieldName, packed);
};
}
private static GlslExpr unpackScalar(String packedType, String fieldName, GlslStruct packed) {
return unpackScalar(packedType, fieldName, packed, Function.identity());
}
private static GlslExpr unpackScalar(String packedType, String fieldName, GlslStruct packed, Function<GlslExpr, GlslExpr> perElement) {
packed.addField(packedType, fieldName);
return perElement.apply(UNPACKING_VARIABLE.access(fieldName));
}
private static GlslExpr unpackVector(VectorElementType type, String fieldName, GlslStruct packed) {
var repr = type.repr();
int size = type.size();
if (repr instanceof IntegerRepr intRepr) {
return unpackIntVector(intRepr, size, fieldName, packed);
} else if (repr instanceof UnsignedIntegerRepr uintRepr) {
return unpackUintVector(uintRepr, size, fieldName, packed);
} else if (repr instanceof FloatRepr floatRepr) {
return unpackFloatVector(floatRepr, size, fieldName, packed);
}
throw new IllegalArgumentException("Unknown repr " + repr);
}
private static GlslExpr unpackIntVector(IntegerRepr repr, int size, String fieldName, GlslStruct packed) {
return switch (repr) {
case BYTE -> unpackByteBackedVector("ivec" + size, size, fieldName, packed, e -> e.cast("int"));
case SHORT -> unpackShortBackedVector("ivec" + size, size, fieldName, packed, e -> e.cast("int"));
case INT -> unpackVector("ivec" + size, size, "int", fieldName, packed);
};
}
private static GlslExpr unpackUintVector(UnsignedIntegerRepr repr, int size, String fieldName, GlslStruct packed) {
return switch (repr) {
case UNSIGNED_BYTE -> unpackByteBackedVector("uvec" + size, size, fieldName, packed, e -> e.cast("uint"));
case UNSIGNED_SHORT -> unpackShortBackedVector("uvec" + size, size, fieldName, packed, e -> e.cast("uint"));
case UNSIGNED_INT -> unpackVector("uvec" + size, size, "uint", fieldName, packed);
};
}
private static GlslExpr unpackFloatVector(FloatRepr repr, int size, String fieldName, GlslStruct packed) {
return switch (repr) {
case BYTE -> unpackByteBackedVector("vec" + size, size, fieldName, packed, e -> e.cast("int")
.cast("float"));
case NORMALIZED_BYTE -> unpackByteBuiltinVector(size, fieldName, packed, "unpackSnorm4x8");
case UNSIGNED_BYTE -> unpackByteBackedVector("vec" + size, size, fieldName, packed, e -> e.cast("float"));
case NORMALIZED_UNSIGNED_BYTE -> unpackByteBuiltinVector(size, fieldName, packed, "unpackUnorm4x8");
case SHORT -> unpackShortBackedVector("vec" + size, size, fieldName, packed, e -> e.cast("int")
.cast("float"));
case NORMALIZED_SHORT -> unpackShortBuiltinVector(size, fieldName, packed, "unpackSnorm2x16");
case UNSIGNED_SHORT -> unpackShortBackedVector("vec" + size, size, fieldName, packed, e -> e.cast("float"));
case NORMALIZED_UNSIGNED_SHORT -> unpackShortBuiltinVector(size, fieldName, packed, "unpackUnorm2x16");
case INT -> unpackVector("vec" + size, size, "int", fieldName, packed, e -> e.cast("float"));
case NORMALIZED_INT -> unpackVector("vec" + size, size, "int", fieldName, packed, e -> e.div(2147483647f)
.clamp(-1, 1));
case UNSIGNED_INT -> unpackVector("vec" + size, size, "float", fieldName, packed, e -> e.cast("float"));
case NORMALIZED_UNSIGNED_INT ->
unpackVector("vec" + size, size, "uint", fieldName, packed, e -> e.div(4294967295f));
case FLOAT -> unpackVector("vec" + size, size, "float", fieldName, packed);
};
}
private static GlslExpr unpackByteBackedVector(String outType, int size, String fieldName, GlslStruct packed, Function<GlslExpr, GlslExpr> perElement) {
packed.addField("uint", fieldName);
List<GlslExpr> args = new ArrayList<>();
for (int i = 0; i < size; i++) {
int bitPos = i * 8;
var element = UNPACKING_VARIABLE.access(fieldName)
.rsh(bitPos)
.and(0xFF);
args.add(perElement.apply(element));
}
return GlslExpr.call(outType, args);
}
private static GlslExpr unpackShortBackedVector(String outType, int size, String fieldName, GlslStruct packed, Function<GlslExpr, GlslExpr> perElement) {
List<GlslExpr> args = new ArrayList<>();
for (int i = 0; i < size; i++) {
int unpackField = i / 2;
int bitPos = (i % 2) * 16;
var name = fieldName + "_" + unpackField;
if (bitPos == 0) {
// First time we're seeing this field, add it to the struct.
packed.addField("uint", name);
}
var element = UNPACKING_VARIABLE.access(name)
.rsh(bitPos)
.and(0xFFFF);
args.add(perElement.apply(element));
}
return GlslExpr.call(outType, args);
}
private static GlslExpr unpackByteBuiltinVector(int size, String fieldName, GlslStruct packed, String func) {
packed.addField("uint", fieldName);
GlslExpr expr = UNPACKING_VARIABLE.access(fieldName)
.callFunction(func);
return switch (size) {
case 2 -> expr.swizzle("xy");
case 3 -> expr.swizzle("xyz");
case 4 -> expr;
default -> throw new IllegalArgumentException("Invalid vector size " + size);
};
}
private static GlslExpr unpackShortBuiltinVector(int size, String fieldName, GlslStruct packed, String func) {
if (size == 2) {
packed.addField("uint", fieldName);
return UNPACKING_VARIABLE.access(fieldName)
.callFunction(func);
} else {
var name0 = fieldName + "_" + 0;
var name1 = fieldName + "_" + 1;
packed.addField("uint", name0);
packed.addField("uint", name1);
GlslExpr xy = UNPACKING_VARIABLE.access(name0)
.callFunction(func);
GlslExpr zw = UNPACKING_VARIABLE.access(name1)
.callFunction(func);
if (size == 3) {
return GlslExpr.call("vec3", List.of(xy.swizzle("xy"), zw.swizzle("x")));
} else {
return GlslExpr.call("vec4", List.of(xy, zw));
}
}
}
private static GlslExpr unpackVector(String outType, int size, String backingType, String fieldName, GlslStruct packed) {
return unpackVector(outType, size, backingType, fieldName, packed, Function.identity());
}
private static GlslExpr unpackVector(String outType, int size, String backingType, String fieldName, GlslStruct packed, Function<GlslExpr, GlslExpr> perElement) {
List<GlslExpr> args = new ArrayList<>();
for (int i = 0; i < size; i++) {
var name = fieldName + "_" + i;
packed.addField(backingType, name);
args.add(perElement.apply(UNPACKING_VARIABLE.access(name)));
}
return GlslExpr.call(outType, args);
}
private static GlslExpr unpackMatrix(MatrixElementType type, String name, GlslStruct packed) {
var repr = type.repr();
int rows = type.rows();
int columns = type.columns();
List<GlslExpr> args = new ArrayList<>();
for (int i = 0; i < columns; i++) {
args.add(unpackFloatVector(repr, rows, name + "_" + i, packed));
}
return GlslExpr.call("mat" + columns + "x" + rows, args);
}
}

View File

@ -9,6 +9,7 @@ import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.compile.core.SourceLoader;
import com.jozufozu.flywheel.backend.glsl.SourceComponent;
import com.jozufozu.flywheel.backend.glsl.SourceFile;
@ -24,9 +25,9 @@ public class UberShaderComponent implements SourceComponent {
private final ResourceLocation name;
private final GlslExpr switchArg;
private final List<AdaptedFn> functionsToAdapt;
private final List<StringSubstitutionSourceComponent> adaptedComponents;
private final List<StringSubstitutionComponent> adaptedComponents;
private UberShaderComponent(ResourceLocation name, GlslExpr switchArg, List<AdaptedFn> functionsToAdapt, List<StringSubstitutionSourceComponent> adaptedComponents) {
private UberShaderComponent(ResourceLocation name, GlslExpr switchArg, List<AdaptedFn> functionsToAdapt, List<StringSubstitutionComponent> adaptedComponents) {
this.name = name;
this.switchArg = switchArg;
this.functionsToAdapt = functionsToAdapt;
@ -38,8 +39,8 @@ public class UberShaderComponent implements SourceComponent {
}
@Override
public ResourceLocation name() {
return name;
public String name() {
return Flywheel.rl("uber_shader").toString() + " / " + name;
}
@Override
@ -141,7 +142,7 @@ public class UberShaderComponent implements SourceComponent {
throw new NullPointerException("Switch argument must be set");
}
var transformed = ImmutableList.<StringSubstitutionSourceComponent>builder();
var transformed = ImmutableList.<StringSubstitutionComponent>builder();
boolean errored = false;
int index = 0;
@ -150,7 +151,7 @@ public class UberShaderComponent implements SourceComponent {
final int finalIndex = index;
if (sourceFile != null) {
var adapterMap = createAdapterMap(adaptedFunctions, fnName -> "_" + fnName + "_" + finalIndex);
transformed.add(new StringSubstitutionSourceComponent(sourceFile, adapterMap));
transformed.add(new StringSubstitutionComponent(sourceFile, adapterMap));
} else {
errored = true;
}
@ -163,7 +164,6 @@ public class UberShaderComponent implements SourceComponent {
return new UberShaderComponent(name, switchArg, adaptedFunctions, transformed.build());
}
}
private static ImmutableMap<String, String> createAdapterMap(List<AdaptedFn> adaptedFunctions, UnaryOperator<String> nameAdapter) {
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
@ -177,3 +177,4 @@ public class UberShaderComponent implements SourceComponent {
return builder.build();
}
}
}

View File

@ -95,7 +95,7 @@ public class Compilation {
fullSource.append("\n#line 0 ")
.append(fileId)
.append(" // ")
.append(file.name)
.append(file.name())
.append('\n');
} else {
// Add extra newline to keep line numbers consistent

View File

@ -2,12 +2,10 @@ package com.jozufozu.flywheel.backend.glsl;
import java.util.Collection;
import net.minecraft.resources.ResourceLocation;
public interface SourceComponent {
Collection<? extends SourceComponent> included();
String source();
ResourceLocation name();
String name();
}

View File

@ -125,8 +125,8 @@ public class SourceFile implements SourceComponent {
}
@Override
public ResourceLocation name() {
return name;
public String name() {
return name.toString();
}
public Span getLineSpan(int lineNo) {

View File

@ -1,9 +1,11 @@
package com.jozufozu.flywheel.backend.glsl.generate;
public enum BinOp {
BITWISE_AND("&"),
RIGHT_SHIFT(">>"),
DIVIDE("/"),
SUBTRACT("-"),
RIGHT_SHIFT(">>"),
BITWISE_AND("&"),
BITWISE_XOR("^"),
// TODO: add more as we need them
;

View File

@ -95,8 +95,12 @@ public interface GlslExpr {
return f.apply(this);
}
default GlslExpr and(int mask) {
return new Binary(this, uintHexLiteral(mask), BinOp.BITWISE_AND);
default GlslExpr div(float v) {
return new Binary(this, floatLiteral(v), BinOp.DIVIDE);
}
default GlslExpr sub(int v) {
return new Binary(this, uintLiteral(v), BinOp.SUBTRACT);
}
default GlslExpr rsh(int by) {
@ -106,8 +110,12 @@ public interface GlslExpr {
return new Binary(this, uintLiteral(by), BinOp.RIGHT_SHIFT);
}
default GlslExpr div(float v) {
return new Binary(this, floatLiteral(v), BinOp.DIVIDE);
default GlslExpr and(int mask) {
return new Binary(this, uintHexLiteral(mask), BinOp.BITWISE_AND);
}
default GlslExpr xor(int mask) {
return new Binary(this, uintHexLiteral(mask), BinOp.BITWISE_XOR);
}
default GlslExpr clamp(float from, float to) {

View File

@ -24,8 +24,10 @@ public final class InstanceTypes {
MemoryUtil.memPutByte(ptr + 1, instance.g);
MemoryUtil.memPutByte(ptr + 2, instance.b);
MemoryUtil.memPutByte(ptr + 3, instance.a);
MemoryUtil.memPutInt(ptr + 4, instance.overlay);
MemoryUtil.memPutInt(ptr + 8, (int) instance.blockLight | (int) instance.skyLight << 16);
MemoryUtil.memPutShort(ptr + 4, (short) (instance.overlay & 0xFFFF));
MemoryUtil.memPutShort(ptr + 6, (short) (instance.overlay >> 16 & 0xFFFF));
MemoryUtil.memPutShort(ptr + 8, (short) (Byte.toUnsignedInt(instance.blockLight) << 4));
MemoryUtil.memPutShort(ptr + 10, (short) (Byte.toUnsignedInt(instance.skyLight) << 4));
MatrixMath.writeUnsafe(instance.model, ptr + 12);
MatrixMath.writeUnsafe(instance.normal, ptr + 76);
})
@ -47,8 +49,10 @@ public final class InstanceTypes {
MemoryUtil.memPutByte(ptr + 1, instance.g);
MemoryUtil.memPutByte(ptr + 2, instance.b);
MemoryUtil.memPutByte(ptr + 3, instance.a);
MemoryUtil.memPutInt(ptr + 4, instance.overlay);
MemoryUtil.memPutInt(ptr + 8, (int) instance.blockLight | (int) instance.skyLight << 16);
MemoryUtil.memPutShort(ptr + 4, (short) (instance.overlay & 0xFFFF));
MemoryUtil.memPutShort(ptr + 6, (short) (instance.overlay >> 16 & 0xFFFF));
MemoryUtil.memPutShort(ptr + 8, (short) (Byte.toUnsignedInt(instance.blockLight) << 4));
MemoryUtil.memPutShort(ptr + 10, (short) (Byte.toUnsignedInt(instance.skyLight) << 4));
MemoryUtil.memPutFloat(ptr + 12, instance.posX);
MemoryUtil.memPutFloat(ptr + 16, instance.posY);
MemoryUtil.memPutFloat(ptr + 20, instance.posZ);

View File

@ -5,5 +5,6 @@ void flw_instanceVertex(in FlwInstance i) {
flw_vertexNormal = rotateByQuaternion(flw_vertexNormal, i.rotation);
flw_vertexColor *= i.color;
flw_vertexOverlay = i.overlay;
flw_vertexLight = i.light / 15.;
// Some drivers have a bug where uint over float division is invalid, so use an explicit cast.
flw_vertexLight = vec2(i.light) / 256.0;
}

View File

@ -3,5 +3,6 @@ void flw_instanceVertex(in FlwInstance i) {
flw_vertexNormal = i.normal * flw_vertexNormal;
flw_vertexColor *= i.color;
flw_vertexOverlay = i.overlay;
flw_vertexLight = i.light / 15.;
// Some drivers have a bug where uint over float division is invalid, so use an explicit cast.
flw_vertexLight = vec2(i.light) / 256.0;
}

View File

@ -33,15 +33,12 @@ void _flw_main() {
}
if (flw_material.useOverlay) {
// Need to clamp the overlay texture coords to sane coordinates because integer vertex attributes explode on
// some drivers for some draw calls. This should only effect instances that don't write to overlay, but
// the internal vertex format is unfortunately subject to these issues.
vec4 overlayColor = texelFetch(flw_overlayTex, clamp(flw_fragOverlay, 0, 10), 0);
vec4 overlayColor = texelFetch(flw_overlayTex, flw_fragOverlay, 0);
color.rgb = mix(overlayColor.rgb, color.rgb, overlayColor.a);
}
if (flw_material.useLight) {
vec4 lightColor = texture(flw_lightTex, (flw_fragLight * 15.0 + 0.5) / 16.0);
vec4 lightColor = texture(flw_lightTex, clamp(flw_fragLight, 0.5 / 16.0, 15.5 / 16.0));
color *= lightColor;
}

View File

@ -9,7 +9,10 @@ void _flw_layoutVertex() {
flw_vertexPos = vec4(_flw_a_pos, 1.0);
flw_vertexColor = _flw_a_color;
flw_vertexTexCoord = _flw_a_texCoord;
flw_vertexOverlay = _flw_a_overlay;
// Need to clamp the overlay texture coords to sane coordinates because integer vertex attributes explode on
// some drivers for some draw calls. This should only effect instances that don't write to overlay, but
// the internal vertex format is unfortunately subject to these issues.
flw_vertexOverlay = clamp(_flw_a_overlay, 0, 15);
flw_vertexLight = _flw_a_light / 256.0;
flw_vertexNormal = _flw_a_normal;
}