2023-03-30 21:59:09 +02:00
|
|
|
package com.jozufozu.flywheel.glsl;
|
2021-06-25 23:10:19 +02:00
|
|
|
|
2023-03-30 07:03:28 +02:00
|
|
|
import java.util.Collection;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Optional;
|
2021-06-25 23:10:19 +02:00
|
|
|
import java.util.regex.Matcher;
|
|
|
|
|
2021-07-02 21:34:12 +02:00
|
|
|
import com.google.common.collect.ImmutableList;
|
|
|
|
import com.google.common.collect.ImmutableMap;
|
2023-03-30 21:59:09 +02:00
|
|
|
import com.jozufozu.flywheel.glsl.parse.Import;
|
|
|
|
import com.jozufozu.flywheel.glsl.parse.ShaderField;
|
|
|
|
import com.jozufozu.flywheel.glsl.parse.ShaderFunction;
|
|
|
|
import com.jozufozu.flywheel.glsl.parse.ShaderStruct;
|
|
|
|
import com.jozufozu.flywheel.glsl.span.ErrorSpan;
|
|
|
|
import com.jozufozu.flywheel.glsl.span.Span;
|
|
|
|
import com.jozufozu.flywheel.glsl.span.StringSpan;
|
2021-06-25 23:10:19 +02:00
|
|
|
|
2021-09-15 08:45:29 +02:00
|
|
|
import net.minecraft.resources.ResourceLocation;
|
2021-06-25 23:10:19 +02:00
|
|
|
|
2021-09-21 04:27:04 +02:00
|
|
|
/**
|
|
|
|
* Immutable class representing a shader file.
|
|
|
|
*
|
|
|
|
* <p>
|
2022-08-30 06:21:52 +02:00
|
|
|
* This class parses shader files and generates what is effectively a high level AST of the source.
|
2021-09-21 04:27:04 +02:00
|
|
|
* </p>
|
|
|
|
*/
|
2022-08-30 06:21:52 +02:00
|
|
|
public class SourceFile implements SourceComponent {
|
2021-06-25 23:10:19 +02:00
|
|
|
public final ResourceLocation name;
|
2021-07-06 21:38:32 +02:00
|
|
|
|
2021-08-09 07:33:32 +02:00
|
|
|
public final ShaderSources parent;
|
2021-06-25 23:10:19 +02:00
|
|
|
|
2022-10-08 23:02:54 +02:00
|
|
|
public final SourceLines source;
|
2021-07-06 21:38:32 +02:00
|
|
|
|
2021-09-21 04:27:04 +02:00
|
|
|
/**
|
|
|
|
* Function lookup by name.
|
|
|
|
*/
|
|
|
|
public final ImmutableMap<String, ShaderFunction> functions;
|
2021-07-06 21:38:32 +02:00
|
|
|
|
2021-09-21 04:27:04 +02:00
|
|
|
/**
|
|
|
|
* Struct lookup by name.
|
|
|
|
*/
|
|
|
|
public final ImmutableMap<String, ShaderStruct> structs;
|
2021-06-25 23:10:19 +02:00
|
|
|
|
2021-09-21 04:27:04 +02:00
|
|
|
/**
|
|
|
|
* Includes ordered as defined in the source.
|
|
|
|
*/
|
|
|
|
public final ImmutableList<Import> imports;
|
2022-07-09 17:32:28 +02:00
|
|
|
public final ImmutableMap<String, ShaderField> fields;
|
2021-07-02 21:34:12 +02:00
|
|
|
|
2022-10-08 23:02:54 +02:00
|
|
|
public final List<SourceFile> included;
|
2022-07-23 02:24:41 +02:00
|
|
|
|
2022-10-08 23:02:54 +02:00
|
|
|
public SourceFile(ShaderSources sourceFinder, ResourceLocation name, String source) {
|
|
|
|
this.parent = sourceFinder;
|
2021-06-25 23:10:19 +02:00
|
|
|
this.name = name;
|
|
|
|
|
2022-10-08 23:02:54 +02:00
|
|
|
this.source = new SourceLines(source);
|
2021-07-06 21:38:32 +02:00
|
|
|
|
2022-10-08 23:02:54 +02:00
|
|
|
this.imports = parseImports(source);
|
|
|
|
this.functions = parseFunctions(source);
|
|
|
|
this.structs = parseStructs(source);
|
|
|
|
this.fields = parseFields(source);
|
|
|
|
|
|
|
|
this.included = imports.stream()
|
|
|
|
.map(i -> i.file)
|
|
|
|
.map(Span::toString)
|
|
|
|
.distinct()
|
|
|
|
.<SourceFile>mapMulti((file, sink) -> {
|
|
|
|
try {
|
|
|
|
var loc = new ResourceLocation(file);
|
|
|
|
var sourceFile = sourceFinder.find(loc);
|
2023-03-25 21:41:45 +01:00
|
|
|
sink.accept(sourceFile);
|
2022-10-08 23:02:54 +02:00
|
|
|
} catch (Exception ignored) {
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.toList();
|
2021-07-15 00:20:49 +02:00
|
|
|
}
|
|
|
|
|
2022-08-30 06:21:52 +02:00
|
|
|
@Override
|
|
|
|
public Collection<? extends SourceComponent> included() {
|
2022-10-08 23:02:54 +02:00
|
|
|
return included;
|
2022-08-30 06:21:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-10-08 23:02:54 +02:00
|
|
|
public String source() {
|
|
|
|
return this.genFinalSource();
|
2022-08-30 06:21:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ResourceLocation name() {
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
2022-10-08 23:02:54 +02:00
|
|
|
public Span getLineSpan(int lineNo) {
|
2022-10-12 05:16:15 +02:00
|
|
|
int begin = source.lineStartIndex(lineNo);
|
|
|
|
int end = begin + source.lineString(lineNo)
|
2022-10-08 23:02:54 +02:00
|
|
|
.length();
|
|
|
|
return new StringSpan(this, source.getCharPos(begin), source.getCharPos(end));
|
2021-12-29 01:37:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public Span getLineSpanNoWhitespace(int line) {
|
2022-10-12 05:16:15 +02:00
|
|
|
int begin = source.lineStartIndex(line);
|
|
|
|
int end = begin + source.lineString(line)
|
2022-10-08 23:02:54 +02:00
|
|
|
.length();
|
2021-12-29 01:37:10 +01:00
|
|
|
|
|
|
|
while (begin < end && Character.isWhitespace(source.charAt(begin))) {
|
|
|
|
begin++;
|
|
|
|
}
|
|
|
|
|
2022-10-08 23:02:54 +02:00
|
|
|
return new StringSpan(this, source.getCharPos(begin), source.getCharPos(end));
|
2021-12-29 01:37:10 +01:00
|
|
|
}
|
|
|
|
|
2021-08-09 07:33:32 +02:00
|
|
|
/**
|
|
|
|
* Search this file and recursively search all imports to find a struct definition matching the given name.
|
|
|
|
*
|
|
|
|
* @param name The name of the struct to find.
|
|
|
|
* @return null if no definition matches the name.
|
|
|
|
*/
|
2023-03-25 21:41:45 +01:00
|
|
|
public Optional<ShaderStruct> findStructByName(String name) {
|
2022-07-23 02:24:41 +02:00
|
|
|
ShaderStruct struct = structs.get(name);
|
2021-08-09 07:33:32 +02:00
|
|
|
|
2023-03-25 21:41:45 +01:00
|
|
|
if (struct != null) {
|
|
|
|
return Optional.of(struct);
|
|
|
|
}
|
2021-08-09 07:33:32 +02:00
|
|
|
|
2022-10-08 23:02:54 +02:00
|
|
|
for (var include : included) {
|
2022-08-30 06:21:52 +02:00
|
|
|
var external = include.structs.get(name);
|
2021-08-09 07:33:32 +02:00
|
|
|
|
2022-07-23 02:24:41 +02:00
|
|
|
if (external != null) {
|
|
|
|
return Optional.of(external);
|
|
|
|
}
|
2021-08-09 07:33:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return Optional.empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Search this file and recursively search all imports to find a function definition matching the given name.
|
|
|
|
*
|
|
|
|
* @param name The name of the function to find.
|
|
|
|
* @return Optional#empty() if no definition matches the name.
|
|
|
|
*/
|
2022-07-23 02:24:41 +02:00
|
|
|
public Optional<ShaderFunction> findFunction(String name) {
|
|
|
|
ShaderFunction local = functions.get(name);
|
2021-08-09 07:33:32 +02:00
|
|
|
|
|
|
|
if (local != null) return Optional.of(local);
|
|
|
|
|
2022-10-08 23:02:54 +02:00
|
|
|
for (var include : included) {
|
2022-08-30 06:21:52 +02:00
|
|
|
var external = include.functions.get(name);
|
2021-08-09 07:33:32 +02:00
|
|
|
|
2022-07-23 02:24:41 +02:00
|
|
|
if (external != null) {
|
|
|
|
return Optional.of(external);
|
|
|
|
}
|
2021-08-09 07:33:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return Optional.empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
public CharSequence importStatement() {
|
|
|
|
return "#use " + '"' + name + '"';
|
|
|
|
}
|
|
|
|
|
2021-07-06 21:38:32 +02:00
|
|
|
public String printSource() {
|
2022-10-08 23:02:54 +02:00
|
|
|
return "Source for shader '" + name + "':\n" + source.printLinesWithNumbers();
|
2021-07-06 21:38:32 +02:00
|
|
|
}
|
|
|
|
|
2022-10-08 23:02:54 +02:00
|
|
|
private String genFinalSource() {
|
2021-08-05 21:16:17 +02:00
|
|
|
StringBuilder out = new StringBuilder();
|
2021-07-06 21:38:32 +02:00
|
|
|
|
2021-08-05 21:16:17 +02:00
|
|
|
int lastEnd = 0;
|
2021-07-06 21:38:32 +02:00
|
|
|
|
2022-08-30 06:21:52 +02:00
|
|
|
for (var include : imports) {
|
|
|
|
var loc = include.self;
|
2021-07-06 21:38:32 +02:00
|
|
|
|
2022-07-09 17:32:28 +02:00
|
|
|
out.append(this.source, lastEnd, loc.getStartPos());
|
|
|
|
|
|
|
|
lastEnd = loc.getEndPos();
|
2021-07-06 21:38:32 +02:00
|
|
|
}
|
|
|
|
|
2022-07-09 17:32:28 +02:00
|
|
|
out.append(this.source, lastEnd, this.source.length());
|
2021-08-05 21:16:17 +02:00
|
|
|
|
2022-10-08 23:02:54 +02:00
|
|
|
return out.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String toString() {
|
|
|
|
return name.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean equals(Object o) {
|
|
|
|
// SourceFiles are only equal by reference.
|
|
|
|
return this == o;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int hashCode() {
|
|
|
|
return System.identityHashCode(this);
|
2021-07-02 21:34:12 +02:00
|
|
|
}
|
|
|
|
|
2022-10-08 23:02:54 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Scan the source for {@code #use "..."} directives.
|
|
|
|
* Records the contents of the directive into an {@link Import} object, and marks the directive for elision.
|
|
|
|
*/
|
|
|
|
private ImmutableList<Import> parseImports(String source) {
|
|
|
|
Matcher uses = Import.PATTERN.matcher(source);
|
|
|
|
|
|
|
|
var imports = ImmutableList.<Import>builder();
|
|
|
|
|
|
|
|
while (uses.find()) {
|
|
|
|
Span use = Span.fromMatcher(this, uses);
|
|
|
|
Span file = Span.fromMatcher(this, uses, 1);
|
|
|
|
|
|
|
|
imports.add(new Import(use, file));
|
|
|
|
}
|
|
|
|
|
|
|
|
return imports.build();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-07-06 21:38:32 +02:00
|
|
|
/**
|
|
|
|
* Scan the source for function definitions and "parse" them into objects that contain properties of the function.
|
|
|
|
*/
|
2022-10-08 23:02:54 +02:00
|
|
|
private ImmutableMap<String, ShaderFunction> parseFunctions(String source) {
|
2022-07-09 17:32:28 +02:00
|
|
|
Matcher matcher = ShaderFunction.PATTERN.matcher(source);
|
2021-06-25 23:10:19 +02:00
|
|
|
|
2021-07-02 21:34:12 +02:00
|
|
|
Map<String, ShaderFunction> functions = new HashMap<>();
|
|
|
|
|
2021-06-25 23:10:19 +02:00
|
|
|
while (matcher.find()) {
|
2021-06-30 20:42:33 +02:00
|
|
|
Span type = Span.fromMatcher(this, matcher, 1);
|
|
|
|
Span name = Span.fromMatcher(this, matcher, 2);
|
|
|
|
Span args = Span.fromMatcher(this, matcher, 3);
|
2021-06-25 23:10:19 +02:00
|
|
|
|
|
|
|
int blockStart = matcher.end();
|
2022-10-08 23:02:54 +02:00
|
|
|
int blockEnd = findEndOfBlock(source, blockStart);
|
2021-06-25 23:10:19 +02:00
|
|
|
|
|
|
|
Span self;
|
|
|
|
Span body;
|
|
|
|
if (blockEnd > blockStart) {
|
2021-06-30 20:42:33 +02:00
|
|
|
self = new StringSpan(this, matcher.start(), blockEnd + 1);
|
2021-06-25 23:10:19 +02:00
|
|
|
body = new StringSpan(this, blockStart, blockEnd);
|
2021-06-30 22:04:02 +02:00
|
|
|
} else {
|
2021-06-25 23:10:19 +02:00
|
|
|
self = new ErrorSpan(this, matcher.start(), matcher.end());
|
|
|
|
body = new ErrorSpan(this, blockStart);
|
|
|
|
}
|
|
|
|
|
2021-06-30 20:42:33 +02:00
|
|
|
ShaderFunction function = new ShaderFunction(self, type, name, args, body);
|
2021-06-25 23:10:19 +02:00
|
|
|
|
2021-06-30 20:42:33 +02:00
|
|
|
functions.put(name.get(), function);
|
2021-06-25 23:10:19 +02:00
|
|
|
}
|
2021-07-02 21:34:12 +02:00
|
|
|
|
|
|
|
return ImmutableMap.copyOf(functions);
|
2021-06-25 23:10:19 +02:00
|
|
|
}
|
|
|
|
|
2021-07-15 00:20:49 +02:00
|
|
|
/**
|
|
|
|
* Scan the source for function definitions and "parse" them into objects that contain properties of the function.
|
|
|
|
*/
|
2022-10-08 23:02:54 +02:00
|
|
|
private ImmutableMap<String, ShaderStruct> parseStructs(String source) {
|
2022-05-15 10:58:33 +02:00
|
|
|
Matcher matcher = ShaderStruct.PATTERN.matcher(source);
|
2021-07-15 00:20:49 +02:00
|
|
|
|
2021-08-07 10:00:32 +02:00
|
|
|
ImmutableMap.Builder<String, ShaderStruct> structs = ImmutableMap.builder();
|
2021-07-15 00:20:49 +02:00
|
|
|
while (matcher.find()) {
|
|
|
|
Span self = Span.fromMatcher(this, matcher);
|
|
|
|
Span name = Span.fromMatcher(this, matcher, 1);
|
|
|
|
Span body = Span.fromMatcher(this, matcher, 2);
|
2023-03-25 21:41:45 +01:00
|
|
|
Span variableName = Span.fromMatcher(this, matcher, 3);
|
2021-07-15 00:20:49 +02:00
|
|
|
|
2023-03-25 21:41:45 +01:00
|
|
|
ShaderStruct shaderStruct = new ShaderStruct(self, name, body, variableName);
|
2021-07-15 00:20:49 +02:00
|
|
|
|
2021-08-07 10:00:32 +02:00
|
|
|
structs.put(name.get(), shaderStruct);
|
2021-07-15 00:20:49 +02:00
|
|
|
}
|
|
|
|
|
2021-08-07 10:00:32 +02:00
|
|
|
return structs.build();
|
2021-07-15 00:20:49 +02:00
|
|
|
}
|
|
|
|
|
2022-07-09 17:32:28 +02:00
|
|
|
/**
|
|
|
|
* Scan the source for function definitions and "parse" them into objects that contain properties of the function.
|
|
|
|
*/
|
2022-10-08 23:02:54 +02:00
|
|
|
private ImmutableMap<String, ShaderField> parseFields(String source) {
|
2022-07-09 17:32:28 +02:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2021-07-06 21:38:32 +02:00
|
|
|
/**
|
|
|
|
* Given the position of an opening brace, scans through the source for a paired closing brace.
|
|
|
|
*/
|
2022-10-08 23:02:54 +02:00
|
|
|
private static int findEndOfBlock(String source, int start) {
|
2021-07-06 21:38:32 +02:00
|
|
|
int blockDepth = 0;
|
|
|
|
for (int i = start + 1; i < source.length(); i++) {
|
|
|
|
char ch = source.charAt(i);
|
2021-07-05 21:09:13 +02:00
|
|
|
|
2021-07-06 21:38:32 +02:00
|
|
|
if (ch == '{') blockDepth++;
|
|
|
|
else if (ch == '}') blockDepth--;
|
2021-07-05 21:09:13 +02:00
|
|
|
|
2021-07-06 21:38:32 +02:00
|
|
|
if (blockDepth < 0) {
|
|
|
|
return i;
|
2021-07-05 21:09:13 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-06 21:38:32 +02:00
|
|
|
return -1;
|
2021-06-25 23:10:19 +02:00
|
|
|
}
|
|
|
|
}
|