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;
|
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.Optional;
|
2023-05-09 07:43:55 +02:00
|
|
|
import java.util.Set;
|
2021-06-25 23:10:19 +02:00
|
|
|
|
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.Span;
|
|
|
|
import com.jozufozu.flywheel.glsl.span.StringSpan;
|
2023-11-18 20:46:04 +01:00
|
|
|
import com.jozufozu.flywheel.lib.util.Pair;
|
|
|
|
import com.jozufozu.flywheel.lib.util.ResourceUtil;
|
2021-06-25 23:10:19 +02:00
|
|
|
|
2023-05-15 01:41:37 +02:00
|
|
|
import net.minecraft.ResourceLocationException;
|
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);
|
|
|
|
|
2023-05-14 02:02:18 +02:00
|
|
|
var imports = Import.parseImports(source);
|
2021-06-25 23:10:19 +02:00
|
|
|
|
2023-05-09 07:43:55 +02:00
|
|
|
List<SourceFile> included = new ArrayList<>();
|
2023-05-14 02:02:18 +02:00
|
|
|
List<Pair<Span, LoadError>> failures = new ArrayList<>();
|
2023-05-09 07:43:55 +02:00
|
|
|
|
|
|
|
Set<String> seen = new HashSet<>();
|
|
|
|
for (Import i : imports) {
|
|
|
|
var fileSpan = i.file();
|
|
|
|
String string = fileSpan.toString();
|
|
|
|
if (!seen.add(string)) {
|
|
|
|
continue;
|
|
|
|
}
|
2023-05-15 01:41:37 +02:00
|
|
|
|
|
|
|
ResourceLocation location;
|
|
|
|
try {
|
|
|
|
location = ResourceUtil.defaultToFlywheelNamespace(string);
|
|
|
|
} catch (ResourceLocationException e) {
|
|
|
|
failures.add(Pair.of(fileSpan, new LoadError.MalformedInclude(e)));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
var result = sourceFinder.find(location);
|
|
|
|
|
2023-05-09 07:43:55 +02:00
|
|
|
if (result instanceof LoadResult.Success s) {
|
|
|
|
included.add(s.unwrap());
|
2023-05-14 02:02:18 +02:00
|
|
|
} else if (result instanceof LoadResult.Failure e) {
|
|
|
|
failures.add(Pair.of(fileSpan, e.error()));
|
2023-05-09 07:43:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!failures.isEmpty()) {
|
2023-05-14 02:02:18 +02:00
|
|
|
return new LoadResult.Failure(new LoadError.IncludeError(name, failures));
|
2023-05-09 07:43:55 +02:00
|
|
|
}
|
|
|
|
|
2023-05-14 02:02:18 +02:00
|
|
|
var functions = ShaderFunction.parseFunctions(source);
|
|
|
|
var structs = ShaderStruct.parseStructs(source);
|
|
|
|
var fields = ShaderField.parseFields(source);
|
2023-05-09 07:43:55 +02:00
|
|
|
|
|
|
|
var finalSource = generateFinalSource(imports, source);
|
2023-05-14 02:02:18 +02:00
|
|
|
return new 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;
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2023-05-14 02:02:18 +02:00
|
|
|
@NotNull
|
|
|
|
private static String generateFinalSource(ImmutableList<Import> imports, SourceLines source) {
|
|
|
|
var out = new StringBuilder();
|
2021-06-25 23:10:19 +02:00
|
|
|
|
2023-05-14 02:02:18 +02:00
|
|
|
int lastEnd = 0;
|
2021-07-15 00:20:49 +02:00
|
|
|
|
2023-05-14 02:02:18 +02:00
|
|
|
for (var include : imports) {
|
|
|
|
var loc = include.self();
|
2021-07-15 00:20:49 +02:00
|
|
|
|
2023-05-14 02:02:18 +02:00
|
|
|
out.append(source, lastEnd, loc.startIndex());
|
2021-07-15 00:20:49 +02:00
|
|
|
|
2023-05-14 02:02:18 +02:00
|
|
|
lastEnd = loc.endIndex();
|
2021-07-15 00:20:49 +02:00
|
|
|
}
|
|
|
|
|
2023-05-14 02:02:18 +02:00
|
|
|
out.append(source, lastEnd, source.length());
|
2022-07-09 17:32:28 +02:00
|
|
|
|
2023-05-14 02:02:18 +02:00
|
|
|
return out.toString();
|
2022-07-09 17:32:28 +02:00
|
|
|
}
|
2021-06-25 23:10:19 +02:00
|
|
|
}
|