2023-03-30 21:59:09 +02:00
|
|
|
package com.jozufozu.flywheel.glsl;
|
2021-06-25 23:10:19 +02:00
|
|
|
|
2023-05-09 07:43:55 +02:00
|
|
|
import java.util.ArrayList;
|
2023-03-30 07:03:28 +02:00
|
|
|
import java.util.Collection;
|
|
|
|
import java.util.HashMap;
|
2023-05-09 07:43:55 +02:00
|
|
|
import java.util.HashSet;
|
2023-03-30 07:03:28 +02:00
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Optional;
|
2023-05-09 07:43:55 +02:00
|
|
|
import java.util.Set;
|
2021-06-25 23:10:19 +02:00
|
|
|
import java.util.regex.Matcher;
|
|
|
|
|
2023-05-09 07:43:55 +02:00
|
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
|
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
|
|
|
|
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
|
|
|
|
2023-05-09 07:43:55 +02:00
|
|
|
public final String finalSource;
|
|
|
|
|
|
|
|
private SourceFile(ResourceLocation name, SourceLines source, ImmutableMap<String, ShaderFunction> functions, ImmutableMap<String, ShaderStruct> structs, ImmutableList<Import> imports, ImmutableMap<String, ShaderField> fields, List<SourceFile> included, String finalSource) {
|
2021-06-25 23:10:19 +02:00
|
|
|
this.name = name;
|
2023-05-09 07:43:55 +02:00
|
|
|
this.source = source;
|
|
|
|
this.functions = functions;
|
|
|
|
this.structs = structs;
|
|
|
|
this.imports = imports;
|
|
|
|
this.fields = fields;
|
|
|
|
this.included = included;
|
|
|
|
this.finalSource = finalSource;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static LoadResult parse(ShaderSources sourceFinder, ResourceLocation name, String stringSource) {
|
|
|
|
var source = new SourceLines(name, stringSource);
|
|
|
|
|
|
|
|
var imports = parseImports(source);
|
2021-06-25 23:10:19 +02:00
|
|
|
|
2023-05-09 07:43:55 +02:00
|
|
|
List<SourceFile> included = new ArrayList<>();
|
|
|
|
List<LoadResult> failures = new ArrayList<>();
|
|
|
|
|
|
|
|
Set<String> seen = new HashSet<>();
|
|
|
|
for (Import i : imports) {
|
|
|
|
var fileSpan = i.file();
|
|
|
|
String string = fileSpan.toString();
|
|
|
|
if (!seen.add(string)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
var result = sourceFinder.find(new ResourceLocation(string));
|
|
|
|
if (result instanceof LoadResult.Success s) {
|
|
|
|
included.add(s.unwrap());
|
|
|
|
} else {
|
|
|
|
failures.add(result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!failures.isEmpty()) {
|
|
|
|
return new LoadResult.IncludeError(name, failures);
|
|
|
|
}
|
|
|
|
|
|
|
|
var functions = parseFunctions(source);
|
|
|
|
var structs = parseStructs(source);
|
|
|
|
var fields = parseFields(source);
|
|
|
|
|
|
|
|
var finalSource = generateFinalSource(imports, source);
|
|
|
|
return LoadResult.success(new SourceFile(name, source, functions, structs, imports, fields, included, finalSource));
|
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() {
|
2023-05-09 07:43:55 +02:00
|
|
|
return finalSource;
|
|
|
|
}
|
|
|
|
|
|
|
|
@NotNull
|
|
|
|
private static String generateFinalSource(ImmutableList<Import> imports, SourceLines source) {
|
|
|
|
var out = new StringBuilder();
|
|
|
|
|
|
|
|
int lastEnd = 0;
|
|
|
|
|
|
|
|
for (var include : imports) {
|
|
|
|
var loc = include.self();
|
|
|
|
|
|
|
|
out.append(source, lastEnd, loc.startIndex());
|
|
|
|
|
|
|
|
lastEnd = loc.endIndex();
|
|
|
|
}
|
|
|
|
|
|
|
|
out.append(source, lastEnd, source.length());
|
|
|
|
|
|
|
|
return out.toString();
|
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();
|
2023-05-09 07:43:55 +02:00
|
|
|
return new StringSpan(source, begin, 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++;
|
|
|
|
}
|
|
|
|
|
2023-05-09 07:43:55 +02:00
|
|
|
return new StringSpan(source, begin, 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();
|
|
|
|
}
|
|
|
|
|
2022-10-08 23:02:54 +02:00
|
|
|
@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.
|
|
|
|
*/
|
2023-05-09 07:43:55 +02:00
|
|
|
private static ImmutableList<Import> parseImports(SourceLines source) {
|
2022-10-08 23:02:54 +02:00
|
|
|
Matcher uses = Import.PATTERN.matcher(source);
|
|
|
|
|
|
|
|
var imports = ImmutableList.<Import>builder();
|
|
|
|
|
|
|
|
while (uses.find()) {
|
2023-05-09 07:43:55 +02:00
|
|
|
Span use = Span.fromMatcher(source, uses);
|
|
|
|
Span file = Span.fromMatcher(source, uses, 1);
|
2022-10-08 23:02:54 +02:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
2023-05-09 07:43:55 +02:00
|
|
|
private static ImmutableMap<String, ShaderFunction> parseFunctions(SourceLines 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()) {
|
2023-05-09 07:43:55 +02:00
|
|
|
Span type = Span.fromMatcher(source, matcher, 1);
|
|
|
|
Span name = Span.fromMatcher(source, matcher, 2);
|
|
|
|
Span args = Span.fromMatcher(source, 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) {
|
2023-05-09 07:43:55 +02:00
|
|
|
self = new StringSpan(source, matcher.start(), blockEnd + 1);
|
|
|
|
body = new StringSpan(source, blockStart, blockEnd);
|
2021-06-30 22:04:02 +02:00
|
|
|
} else {
|
2023-05-09 07:43:55 +02:00
|
|
|
self = new ErrorSpan(source, matcher.start(), matcher.end());
|
|
|
|
body = new ErrorSpan(source, blockStart);
|
2021-06-25 23:10:19 +02:00
|
|
|
}
|
|
|
|
|
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.
|
|
|
|
*/
|
2023-05-09 07:43:55 +02:00
|
|
|
private static ImmutableMap<String, ShaderStruct> parseStructs(SourceLines 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()) {
|
2023-05-09 07:43:55 +02:00
|
|
|
Span self = Span.fromMatcher(source, matcher);
|
|
|
|
Span name = Span.fromMatcher(source, matcher, 1);
|
|
|
|
Span body = Span.fromMatcher(source, matcher, 2);
|
|
|
|
Span variableName = Span.fromMatcher(source, 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.
|
|
|
|
*/
|
2023-05-09 07:43:55 +02:00
|
|
|
private static ImmutableMap<String, ShaderField> parseFields(SourceLines 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()) {
|
2023-05-09 07:43:55 +02:00
|
|
|
Span self = Span.fromMatcher(source, matcher);
|
|
|
|
Span location = Span.fromMatcher(source, matcher, 1);
|
|
|
|
Span decoration = Span.fromMatcher(source, matcher, 2);
|
|
|
|
Span type = Span.fromMatcher(source, matcher, 3);
|
|
|
|
Span name = Span.fromMatcher(source, matcher, 4);
|
2022-07-09 17:32:28 +02:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
2023-05-09 07:43:55 +02:00
|
|
|
private static int findEndOfBlock(CharSequence 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
|
|
|
|
2023-05-09 07:43:55 +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
|
|
|
}
|
|
|
|
}
|