mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-01-07 12:56:31 +01:00
Structless instances
- Instance shaders define their own input variables - Input locations are transformed during compilation based on vertex type
This commit is contained in:
parent
0e37a0586f
commit
5657e8669e
8 changed files with 159 additions and 110 deletions
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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*=");
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue