From 5657e8669e43ac2bbdf4fddf945fd24502e2dc98 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sat, 9 Jul 2022 11:32:28 -0400 Subject: [PATCH] Structless instances - Instance shaders define their own input variables - Input locations are transformed during compilation based on vertex type --- .../flywheel/core/compile/VertexCompiler.java | 95 ++++++------------- .../flywheel/core/source/ShaderField.java | 43 +++++++++ .../flywheel/core/source/SourceFile.java | 76 +++++++++++---- .../core/source/parse/ShaderFunction.java | 2 +- .../flywheel/core/source/span/Span.java | 9 +- .../core/structs/InstanceShaders.java | 2 +- .../flywheel/flywheel/instance/oriented.vert | 22 ++--- .../flywheel/instance/transformed.vert | 20 ++-- 8 files changed, 159 insertions(+), 110 deletions(-) create mode 100644 src/main/java/com/jozufozu/flywheel/core/source/ShaderField.java diff --git a/src/main/java/com/jozufozu/flywheel/core/compile/VertexCompiler.java b/src/main/java/com/jozufozu/flywheel/core/compile/VertexCompiler.java index 66a9352c1..1c6c2c7d5 100644 --- a/src/main/java/com/jozufozu/flywheel/core/compile/VertexCompiler.java +++ b/src/main/java/com/jozufozu/flywheel/core/compile/VertexCompiler.java @@ -1,5 +1,7 @@ package com.jozufozu.flywheel.core.compile; +import java.util.ArrayList; + import com.google.common.collect.ImmutableList; import com.jozufozu.flywheel.api.vertex.VertexType; import com.jozufozu.flywheel.backend.gl.GLSLVersion; @@ -8,9 +10,12 @@ import com.jozufozu.flywheel.backend.gl.shader.ShaderType; import com.jozufozu.flywheel.core.shader.StateSnapshot; import com.jozufozu.flywheel.core.source.FileIndex; import com.jozufozu.flywheel.core.source.FileResolution; +import com.jozufozu.flywheel.core.source.ShaderField; import com.jozufozu.flywheel.core.source.SourceFile; import com.jozufozu.flywheel.core.source.parse.ShaderStruct; import com.jozufozu.flywheel.core.source.parse.StructField; +import com.jozufozu.flywheel.core.source.span.Span; +import com.jozufozu.flywheel.util.Pair; /** * Handles compilation and deletion of vertex shaders. @@ -43,8 +48,21 @@ public class VertexCompiler extends Memoizer { // INSTANCE + int attributeBaseIndex = key.vertexType.getLayout() + .getAttributeCount(); + var instanceShader = key.instanceShader; - instanceShader.generateFinalSource(index, finalSource); + var replacements = new ArrayList>(); + for (ShaderField field : instanceShader.fields.values()) { + if (field.decoration != ShaderField.Decoration.IN) { + continue; + } + + int location = Integer.parseInt(field.location.get()); + int newLocation = location + attributeBaseIndex; + replacements.add(Pair.of(field.location, Integer.toString(newLocation))); + } + instanceShader.generateFinalSource(index, finalSource, replacements); // MATERIAL @@ -58,11 +76,17 @@ public class VertexCompiler extends Memoizer { // MAIN - var instanceStruct = instanceShader.findFunction("flw_instanceVertex") - .flatMap(f -> f.getParameterType(0) - .findStruct()) - .orElseThrow(); - finalSource.append(generateFooter(key.vertexType, instanceStruct)); + finalSource.append(""" + void main() { + flw_layoutVertex(); + + flw_instanceVertex(); + + flw_materialVertex(); + + flw_contextVertex(); + } + """); try { return new GlShader(finalSource.toString(), ShaderType.VERTEX, ImmutableList.of(layoutShader.name, instanceShader.name, materialShader.name, contextShaderSource.name), shaderConstants); @@ -71,65 +95,6 @@ public class VertexCompiler extends Memoizer { } } - protected String generateFooter(VertexType vertexType, ShaderStruct instance) { - ImmutableList fields = instance.getFields(); - - int attributeBinding = vertexType.getLayout() - .getAttributeCount(); - - StringBuilder footer = new StringBuilder(); - - for (StructField field : fields) { - footer.append("layout(location = ") - .append(attributeBinding) - .append(") in") - .append(' ') - .append(field.type) - .append(' ') - .append("_flw_a_i_") - .append(field.name) - .append(";\n"); - attributeBinding += CompileUtil.getAttributeCount(field.type); - } - footer.append('\n'); - - footer.append(String.format(""" - void main() { - flw_layoutVertex(); - - %s instance; - %s - flw_instanceVertex(instance); - - flw_materialVertex(); - - flw_contextVertex(); - } - """, - instance.name, - assignFields(instance, "instance.", "_flw_a_i_") - )); - - return footer.toString(); - } - - protected static StringBuilder assignFields(ShaderStruct struct, String prefix1, String prefix2) { - ImmutableList fields = struct.getFields(); - - StringBuilder builder = new StringBuilder(); - - for (StructField field : fields) { - builder.append(prefix1) - .append(field.name) - .append(" = ") - .append(prefix2) - .append(field.name) - .append(";\n"); - } - - return builder; - } - @Override protected void _destroy(GlShader value) { value.delete(); diff --git a/src/main/java/com/jozufozu/flywheel/core/source/ShaderField.java b/src/main/java/com/jozufozu/flywheel/core/source/ShaderField.java new file mode 100644 index 000000000..9c61684ac --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/core/source/ShaderField.java @@ -0,0 +1,43 @@ +package com.jozufozu.flywheel.core.source; + +import java.util.regex.Pattern; + +import org.jetbrains.annotations.Nullable; + +import com.jozufozu.flywheel.core.source.parse.AbstractShaderElement; +import com.jozufozu.flywheel.core.source.span.Span; + +public class ShaderField extends AbstractShaderElement { + public static final Pattern PATTERN = Pattern.compile("layout\\s+\\(location\\s+=\\s+(\\d+)\\)\\s+(in|out)\\s+([\\w\\d]+)\\s+" + "([\\w\\d]+)"); + + public final Span location; + public final @Nullable Decoration decoration; + public final Span type; + public final Span name; + + public ShaderField(Span self, Span location, Span inOut, Span type, Span name) { + super(self); + + this.location = location; + this.decoration = Decoration.fromSpan(inOut); + this.type = type; + this.name = name; + } + + public enum Decoration { + IN, + OUT, + FLAT, + ; + + @Nullable + public static Decoration fromSpan(Span span) { + return switch (span.toString()) { + case "in" -> IN; + case "out" -> OUT; + case "flat" -> FLAT; + default -> null; + }; + } + } +} diff --git a/src/main/java/com/jozufozu/flywheel/core/source/SourceFile.java b/src/main/java/com/jozufozu/flywheel/core/source/SourceFile.java index 7332ae7cf..72b05f946 100644 --- a/src/main/java/com/jozufozu/flywheel/core/source/SourceFile.java +++ b/src/main/java/com/jozufozu/flywheel/core/source/SourceFile.java @@ -1,6 +1,8 @@ package com.jozufozu.flywheel.core.source; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -18,6 +20,7 @@ import com.jozufozu.flywheel.core.source.parse.ShaderStruct; import com.jozufozu.flywheel.core.source.span.ErrorSpan; import com.jozufozu.flywheel.core.source.span.Span; import com.jozufozu.flywheel.core.source.span.StringSpan; +import com.jozufozu.flywheel.util.Pair; import net.minecraft.resources.ResourceLocation; @@ -34,10 +37,6 @@ public class SourceFile { public final ShaderSources parent; public final String source; - /** - * Sections of the source that must be trimmed for compilation. Currently, it only contains the spans of imports. - */ - public final String elided; public final SourceLines lines; @@ -55,6 +54,8 @@ public class SourceFile { * Includes ordered as defined in the source. */ public final ImmutableList imports; + public final ImmutableMap fields; + private final List elisions; public SourceFile(ErrorReporter errorReporter, ShaderSources parent, ResourceLocation name, String source) { this.parent = parent; @@ -63,13 +64,12 @@ public class SourceFile { this.lines = new SourceLines(source); - List elisions = new ArrayList<>(); + this.elisions = new ArrayList<>(); - this.imports = parseImports(errorReporter, elisions); + this.imports = parseImports(errorReporter); this.functions = parseFunctions(); this.structs = parseStructs(); - - this.elided = elideSource(source, elisions).toString(); + this.fields = parseFields(); } public Span getLineSpan(int line) { @@ -136,11 +136,15 @@ public class SourceFile { } public void generateFinalSource(FileIndex env, StringBuilder source) { + generateFinalSource(env, source, Collections.emptyList()); + } + + public void generateFinalSource(FileIndex env, StringBuilder source, List> replacements) { for (Import include : imports) { SourceFile file = include.getFile(); if (file != null && !env.exists(file)) { - file.generateFinalSource(env, source); + file.generateFinalSource(env, source, replacements); } } @@ -151,7 +155,13 @@ public class SourceFile { .append(" // ") .append(name) .append('\n'); - source.append(elided); + + var replacementsAndElisions = new ArrayList<>(replacements); + for (Span elision : elisions) { + replacementsAndElisions.add(Pair.of(elision, "")); + } + + source.append(this.replace(replacementsAndElisions)); source.append('\n'); } @@ -159,18 +169,27 @@ public class SourceFile { return "Source for shader '" + name + "':\n" + lines.printLinesWithNumbers(); } - private static CharSequence elideSource(String source, List elisions) { + private CharSequence replace(List> replacements) { StringBuilder out = new StringBuilder(); int lastEnd = 0; - for (Span elision : elisions) { - out.append(source, lastEnd, elision.getStartPos()); + replacements.sort(Comparator.comparing(Pair::first)); - lastEnd = elision.getEndPos(); + for (var replacement : replacements) { + var loc = replacement.first(); + + if (loc.getSourceFile() != this) { + continue; + } + + out.append(this.source, lastEnd, loc.getStartPos()); + out.append(replacement.second()); + + lastEnd = loc.getEndPos(); } - out.append(source, lastEnd, source.length()); + out.append(this.source, lastEnd, this.source.length()); return out; } @@ -179,7 +198,7 @@ public class SourceFile { * Scan the source for function definitions and "parse" them into objects that contain properties of the function. */ private ImmutableMap parseFunctions() { - Matcher matcher = ShaderFunction.functionDeclaration.matcher(source); + Matcher matcher = ShaderFunction.PATTERN.matcher(source); Map functions = new HashMap<>(); @@ -229,12 +248,31 @@ public class SourceFile { return structs.build(); } + /** + * Scan the source for function definitions and "parse" them into objects that contain properties of the function. + */ + private ImmutableMap parseFields() { + Matcher matcher = ShaderField.PATTERN.matcher(source); + + ImmutableMap.Builder fields = ImmutableMap.builder(); + while (matcher.find()) { + Span self = Span.fromMatcher(this, matcher); + Span location = Span.fromMatcher(this, matcher, 1); + Span decoration = Span.fromMatcher(this, matcher, 2); + Span type = Span.fromMatcher(this, matcher, 3); + Span name = Span.fromMatcher(this, matcher, 4); + + fields.put(location.get(), new ShaderField(self, location, decoration, type, name)); + } + + return fields.build(); + } + /** * Scan the source for {@code #use "..."} directives. * Records the contents of the directive into an {@link Import} object, and marks the directive for elision. - * @param elisions */ - private ImmutableList parseImports(ErrorReporter errorReporter, List elisions) { + private ImmutableList parseImports(ErrorReporter errorReporter) { Matcher uses = Import.PATTERN.matcher(source); Set importedFiles = new HashSet<>(); @@ -253,7 +291,7 @@ public class SourceFile { } } - elisions.add(use); // we have to trim that later + this.elisions.add(use); // we have to trim that later } return ImmutableList.copyOf(imports); diff --git a/src/main/java/com/jozufozu/flywheel/core/source/parse/ShaderFunction.java b/src/main/java/com/jozufozu/flywheel/core/source/parse/ShaderFunction.java index 134d0214f..38dda848c 100644 --- a/src/main/java/com/jozufozu/flywheel/core/source/parse/ShaderFunction.java +++ b/src/main/java/com/jozufozu/flywheel/core/source/parse/ShaderFunction.java @@ -10,7 +10,7 @@ import com.jozufozu.flywheel.core.source.span.Span; public class ShaderFunction extends AbstractShaderElement { // https://regexr.com/60n3d - public static final Pattern functionDeclaration = Pattern.compile("(\\w+)\\s+(\\w+)\\s*\\(([\\w,\\s]*)\\)\\s*\\{"); + public static final Pattern PATTERN = Pattern.compile("(\\w+)\\s+(\\w+)\\s*\\(([\\w,\\s]*)\\)\\s*\\{"); public static final Pattern argument = Pattern.compile("(?:(inout|in|out) )?(\\w+)\\s+(\\w+)"); public static final Pattern assignment = Pattern.compile("(\\w+)\\s*="); diff --git a/src/main/java/com/jozufozu/flywheel/core/source/span/Span.java b/src/main/java/com/jozufozu/flywheel/core/source/span/Span.java index 81b8e848a..7491f0cf4 100644 --- a/src/main/java/com/jozufozu/flywheel/core/source/span/Span.java +++ b/src/main/java/com/jozufozu/flywheel/core/source/span/Span.java @@ -3,6 +3,8 @@ package com.jozufozu.flywheel.core.source.span; import java.util.Optional; import java.util.regex.Matcher; +import org.jetbrains.annotations.NotNull; + import com.jozufozu.flywheel.core.source.SourceFile; import com.jozufozu.flywheel.core.source.parse.ShaderFunction; import com.jozufozu.flywheel.core.source.parse.ShaderStruct; @@ -14,7 +16,7 @@ import com.jozufozu.flywheel.core.source.parse.ShaderStruct; * Spans are used for pretty-printing errors. *

*/ -public abstract class Span implements CharSequence { +public abstract class Span implements CharSequence, Comparable { protected final SourceFile in; protected final CharPos start; @@ -138,4 +140,9 @@ public abstract class Span implements CharSequence { } return in.findFunction(this); } + + @Override + public int compareTo(@NotNull Span o) { + return Integer.compareUnsigned(getStartPos(), o.getStartPos()); + } } diff --git a/src/main/java/com/jozufozu/flywheel/core/structs/InstanceShaders.java b/src/main/java/com/jozufozu/flywheel/core/structs/InstanceShaders.java index 0b253cfeb..f86faaee2 100644 --- a/src/main/java/com/jozufozu/flywheel/core/structs/InstanceShaders.java +++ b/src/main/java/com/jozufozu/flywheel/core/structs/InstanceShaders.java @@ -12,7 +12,7 @@ import com.jozufozu.flywheel.util.ResourceUtil; import net.minecraft.resources.ResourceLocation; public class InstanceShaders { - public static final BiConsumer CHECK = SourceChecks.checkFunctionParameterTypeExists("flw_instanceVertex", 1, 0); + public static final BiConsumer CHECK = SourceChecks.checkFunctionArity("flw_instanceVertex", 0); public static final FileResolution TRANSFORMED = create(ResourceUtil.subPath(Names.TRANSFORMED, ".vert")); public static final FileResolution ORIENTED = create(ResourceUtil.subPath(Names.ORIENTED, ".vert")); diff --git a/src/main/resources/assets/flywheel/flywheel/instance/oriented.vert b/src/main/resources/assets/flywheel/flywheel/instance/oriented.vert index c9e5b5cff..dc49c75ca 100644 --- a/src/main/resources/assets/flywheel/flywheel/instance/oriented.vert +++ b/src/main/resources/assets/flywheel/flywheel/instance/oriented.vert @@ -2,17 +2,15 @@ #use "flywheel:util/light.glsl" #use "flywheel:util/quaternion.glsl" -struct Oriented { - vec2 light; - vec4 color; - vec3 pos; - vec3 pivot; - vec4 rotation; -}; +layout (location = 0) in vec2 oriented_light; +layout (location = 1) in vec4 oriented_color; +layout (location = 2) in vec3 oriented_pos; +layout (location = 3) in vec3 oriented_pivot; +layout (location = 4) in vec4 oriented_rotation; -void flw_instanceVertex(Oriented oriented) { - flw_vertexPos = vec4(rotateVertexByQuat(flw_vertexPos.xyz - oriented.pivot, oriented.rotation) + oriented.pivot + oriented.pos, 1.0); - flw_vertexNormal = rotateVertexByQuat(flw_vertexNormal, oriented.rotation); - flw_vertexColor = oriented.color; - flw_vertexLight = shiftLight(oriented.light); +void flw_instanceVertex() { + flw_vertexPos = vec4(rotateVertexByQuat(flw_vertexPos.xyz - oriented_pivot, oriented_rotation) + oriented_pivot + oriented_pos, 1.0); + flw_vertexNormal = rotateVertexByQuat(flw_vertexNormal, oriented_rotation); + flw_vertexColor = oriented_color; + flw_vertexLight = shiftLight(oriented_light); } diff --git a/src/main/resources/assets/flywheel/flywheel/instance/transformed.vert b/src/main/resources/assets/flywheel/flywheel/instance/transformed.vert index cea24a54a..d5fe6fc62 100644 --- a/src/main/resources/assets/flywheel/flywheel/instance/transformed.vert +++ b/src/main/resources/assets/flywheel/flywheel/instance/transformed.vert @@ -1,16 +1,14 @@ #use "flywheel:api/vertex.glsl" #use "flywheel:util/light.glsl" -struct Instance { - vec2 light; - vec4 color; - mat4 transform; - mat3 normalMat; -}; +layout (location = 0) in vec2 transformed_light; +layout (location = 1) in vec4 transformed_color; +layout (location = 2) in mat4 transformed_pose; +layout (location = 6) in mat3 transformed_normal; -void flw_instanceVertex(Instance instance) { - flw_vertexPos = instance.transform * flw_vertexPos; - flw_vertexNormal = instance.normalMat * flw_vertexNormal; - flw_vertexColor = instance.color; - flw_vertexLight = shiftLight(instance.light); +void flw_instanceVertex() { + flw_vertexPos = transformed_pose * flw_vertexPos; + flw_vertexNormal = transformed_normal * flw_vertexNormal; + flw_vertexColor = transformed_color; + flw_vertexLight = shiftLight(transformed_light); }