Structless instances

- Instance shaders define their own input variables
 - Input locations are transformed during compilation based on vertex type
This commit is contained in:
Jozufozu 2022-07-09 11:32:28 -04:00
parent 0e37a0586f
commit 5657e8669e
8 changed files with 159 additions and 110 deletions

View file

@ -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<VertexCompiler.Context, GlShader> {
// INSTANCE
int attributeBaseIndex = key.vertexType.getLayout()
.getAttributeCount();
var instanceShader = key.instanceShader;
instanceShader.generateFinalSource(index, finalSource);
var replacements = new ArrayList<Pair<Span, String>>();
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<VertexCompiler.Context, GlShader> {
// 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<VertexCompiler.Context, GlShader> {
}
}
protected String generateFooter(VertexType vertexType, ShaderStruct instance) {
ImmutableList<StructField> 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<StructField> 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();

View file

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

View file

@ -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<Import> imports;
public final ImmutableMap<String, ShaderField> fields;
private final List<Span> 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<Span> 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<Pair<Span, String>> 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<Span> elisions) {
private CharSequence replace(List<Pair<Span, String>> 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<String, ShaderFunction> parseFunctions() {
Matcher matcher = ShaderFunction.functionDeclaration.matcher(source);
Matcher matcher = ShaderFunction.PATTERN.matcher(source);
Map<String, ShaderFunction> 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<String, ShaderField> parseFields() {
Matcher matcher = ShaderField.PATTERN.matcher(source);
ImmutableMap.Builder<String, ShaderField> 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<Import> parseImports(ErrorReporter errorReporter, List<Span> elisions) {
private ImmutableList<Import> parseImports(ErrorReporter errorReporter) {
Matcher uses = Import.PATTERN.matcher(source);
Set<String> 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);

View file

@ -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*=");

View file

@ -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.
* </p>
*/
public abstract class Span implements CharSequence {
public abstract class Span implements CharSequence, Comparable<Span> {
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());
}
}

View file

@ -12,7 +12,7 @@ import com.jozufozu.flywheel.util.ResourceUtil;
import net.minecraft.resources.ResourceLocation;
public class InstanceShaders {
public static final BiConsumer<ErrorReporter, SourceFile> CHECK = SourceChecks.checkFunctionParameterTypeExists("flw_instanceVertex", 1, 0);
public static final BiConsumer<ErrorReporter, SourceFile> 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"));

View file

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

View file

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