A lot to unwrap

- Make ShaderSources return a LoadResult
- Refactor SourceFile loading to do all parsing outside the ctor
- Don't immediately swallow import errors, instead forward them
- Change Span to reference SourceLines instead of SourceFile
- Still WIP, but functional at this stage
This commit is contained in:
Jozufozu 2023-05-08 22:43:55 -07:00
parent d44c973bcc
commit 639b2185ab
21 changed files with 314 additions and 267 deletions

View file

@ -1,5 +1,7 @@
package com.jozufozu.flywheel.backend.compile;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList;
@ -24,7 +26,8 @@ public class CullingCompiler extends AbstractCompiler<InstanceType<?>> {
super(sources, keys);
this.uniformComponent = uniformComponent;
pipelineCompute = sources.find(Files.INDIRECT_CULL);
pipelineCompute = sources.find(Files.INDIRECT_CULL)
.unwrap();
}
@Nullable
@ -40,9 +43,11 @@ public class CullingCompiler extends AbstractCompiler<InstanceType<?>> {
return programLinker.link(compute);
}
private ImmutableList<SourceComponent> getComputeComponents(InstanceType<?> instanceType) {
private List<SourceComponent> getComputeComponents(InstanceType<?> instanceType) {
var instanceAssembly = new IndirectComponent(sources, instanceType);
var instance = sources.find(instanceType.instanceShader());
ResourceLocation key = instanceType.instanceShader();
var instance = sources.find(key)
.unwrap();
return ImmutableList.of(uniformComponent, instanceAssembly, instance, pipelineCompute);
}

View file

@ -5,9 +5,13 @@ import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.uniform.ShaderUniforms;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.compile.component.MaterialAdapterComponent;
import com.jozufozu.flywheel.backend.compile.component.UniformComponent;
import com.jozufozu.flywheel.glsl.ShaderSources;
import com.jozufozu.flywheel.glsl.generate.FnSignature;
import com.jozufozu.flywheel.glsl.generate.GlslExpr;
import com.jozufozu.flywheel.lib.context.Contexts;
import com.jozufozu.flywheel.lib.material.MaterialIndices;
import net.minecraft.server.packs.resources.ResourceManager;
@ -25,8 +29,30 @@ public class FlwPrograms {
.toList())
.build(sources);
InstancingPrograms.reload(sources, pipelineKeys, uniformComponent);
IndirectPrograms.reload(sources, pipelineKeys, uniformComponent);
var vertexMaterialComponent = MaterialAdapterComponent.builder(Flywheel.rl("vertex_material_adapter"))
.materialSources(MaterialIndices.getAllVertexShaders())
.adapt(FnSignature.ofVoid("flw_materialVertex"))
.switchOn(GlslExpr.variable("_flw_materialVertexID"))
.build(sources);
var fragmentMaterialComponent = MaterialAdapterComponent.builder(Flywheel.rl("fragment_material_adapter"))
.materialSources(MaterialIndices.getAllFragmentShaders())
.adapt(FnSignature.ofVoid("flw_materialFragment"))
.adapt(FnSignature.create()
.returnType("bool")
.name("flw_discardPredicate")
.arg("vec4", "color")
.build(), GlslExpr.literal(false))
.adapt(FnSignature.create()
.returnType("vec4")
.name("flw_fogFilter")
.arg("vec4", "color")
.build(), GlslExpr.variable("color"))
.switchOn(GlslExpr.variable("_flw_materialFragmentID"))
.build(sources);
InstancingPrograms.reload(sources, pipelineKeys, uniformComponent, vertexMaterialComponent, fragmentMaterialComponent);
IndirectPrograms.reload(sources, pipelineKeys, uniformComponent, vertexMaterialComponent, fragmentMaterialComponent);
}
private static ImmutableList<PipelineProgramKey> createPipelineKeys() {

View file

@ -8,6 +8,7 @@ import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.compile.component.MaterialAdapterComponent;
import com.jozufozu.flywheel.backend.compile.component.UniformComponent;
import com.jozufozu.flywheel.gl.shader.GlProgram;
import com.jozufozu.flywheel.glsl.ShaderSources;
@ -22,12 +23,12 @@ public class IndirectPrograms {
this.culling = culling;
}
public static void reload(ShaderSources sources, ImmutableList<PipelineProgramKey> pipelineKeys, UniformComponent uniformComponent) {
public static void reload(ShaderSources sources, ImmutableList<PipelineProgramKey> pipelineKeys, UniformComponent uniformComponent, MaterialAdapterComponent vertexMaterialComponent, MaterialAdapterComponent fragmentMaterialComponent) {
if (instance != null) {
instance.delete();
instance = null;
}
var pipelineCompiler = new PipelineCompiler(sources, pipelineKeys, Pipelines.INDIRECT, uniformComponent);
var pipelineCompiler = new PipelineCompiler(sources, pipelineKeys, Pipelines.INDIRECT, vertexMaterialComponent, fragmentMaterialComponent, uniformComponent);
var cullingCompiler = new CullingCompiler(sources, createCullingKeys(), uniformComponent);
var pipelineResult = pipelineCompiler.compileAndReportErrors();

View file

@ -8,6 +8,7 @@ import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.compile.component.MaterialAdapterComponent;
import com.jozufozu.flywheel.backend.compile.component.UniformComponent;
import com.jozufozu.flywheel.gl.shader.GlProgram;
import com.jozufozu.flywheel.glsl.ShaderSources;
@ -20,12 +21,12 @@ public class InstancingPrograms {
this.pipeline = pipeline;
}
public static void reload(ShaderSources sources, ImmutableList<PipelineProgramKey> pipelineKeys, UniformComponent uniformComponent) {
public static void reload(ShaderSources sources, ImmutableList<PipelineProgramKey> pipelineKeys, UniformComponent uniformComponent, MaterialAdapterComponent vertexMaterialComponent, MaterialAdapterComponent fragmentMaterialComponent) {
if (instance != null) {
instance.delete();
instance = null;
}
var instancingCompiler = new PipelineCompiler(sources, pipelineKeys, Pipelines.INSTANCED_ARRAYS, uniformComponent);
var instancingCompiler = new PipelineCompiler(sources, pipelineKeys, Pipelines.INSTANCED_ARRAYS, vertexMaterialComponent, fragmentMaterialComponent, uniformComponent);
var result = instancingCompiler.compileAndReportErrors();
if (result != null) {

View file

@ -1,9 +1,10 @@
package com.jozufozu.flywheel.backend.compile;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.compile.component.MaterialAdapterComponent;
import com.jozufozu.flywheel.backend.compile.component.UniformComponent;
import com.jozufozu.flywheel.gl.shader.GlProgram;
@ -11,9 +12,6 @@ import com.jozufozu.flywheel.gl.shader.ShaderType;
import com.jozufozu.flywheel.glsl.ShaderSources;
import com.jozufozu.flywheel.glsl.SourceComponent;
import com.jozufozu.flywheel.glsl.SourceFile;
import com.jozufozu.flywheel.glsl.generate.FnSignature;
import com.jozufozu.flywheel.glsl.generate.GlslExpr;
import com.jozufozu.flywheel.lib.material.MaterialIndices;
public class PipelineCompiler extends AbstractCompiler<PipelineProgramKey> {
private final Pipeline pipeline;
@ -23,34 +21,17 @@ public class PipelineCompiler extends AbstractCompiler<PipelineProgramKey> {
private final SourceFile pipelineFragment;
private final SourceFile pipelineVertex;
public PipelineCompiler(ShaderSources sources, ImmutableList<PipelineProgramKey> keys, Pipeline pipeline, UniformComponent uniformComponent) {
public PipelineCompiler(ShaderSources sources, ImmutableList<PipelineProgramKey> keys, Pipeline pipeline, MaterialAdapterComponent vertexMaterialComponent, MaterialAdapterComponent fragmentMaterialComponent, UniformComponent uniformComponent) {
super(sources, keys);
this.pipeline = pipeline;
this.vertexMaterialComponent = vertexMaterialComponent;
this.fragmentMaterialComponent = fragmentMaterialComponent;
this.uniformComponent = uniformComponent;
vertexMaterialComponent = MaterialAdapterComponent.builder(Flywheel.rl("vertex_material_adapter"))
.materialSources(MaterialIndices.getAllVertexShaders())
.adapt(FnSignature.ofVoid("flw_materialVertex"))
.switchOn(GlslExpr.variable("_flw_materialVertexID"))
.build(sources);
fragmentMaterialComponent = MaterialAdapterComponent.builder(Flywheel.rl("fragment_material_adapter"))
.materialSources(MaterialIndices.getAllFragmentShaders())
.adapt(FnSignature.ofVoid("flw_materialFragment"))
.adapt(FnSignature.create()
.returnType("bool")
.name("flw_discardPredicate")
.arg("vec4", "color")
.build(), GlslExpr.literal(false))
.adapt(FnSignature.create()
.returnType("vec4")
.name("flw_fogFilter")
.arg("vec4", "color")
.build(), GlslExpr.variable("color"))
.switchOn(GlslExpr.variable("_flw_materialFragmentID"))
.build(sources);
pipelineFragment = sources.find(pipeline.fragmentShader());
pipelineVertex = sources.find(pipeline.vertexShader());
pipelineFragment = this.sources.find(pipeline.fragmentShader())
.unwrap();
pipelineVertex = this.sources.find(pipeline.vertexShader())
.unwrap();
}
@Nullable
@ -71,23 +52,27 @@ public class PipelineCompiler extends AbstractCompiler<PipelineProgramKey> {
return glProgram;
}
private ImmutableList<SourceComponent> getVertexComponents(PipelineProgramKey key) {
private List<SourceComponent> getVertexComponents(PipelineProgramKey key) {
var instanceAssembly = pipeline.assembler()
.assemble(new Pipeline.InstanceAssemblerContext(sources, key.vertexType(), key.instanceType()));
var layout = sources.find(key.vertexType()
.layoutShader());
.layoutShader())
.unwrap();
var instance = sources.find(key.instanceType()
.instanceShader());
.instanceShader())
.unwrap();
var context = sources.find(key.contextShader()
.vertexShader());
.vertexShader())
.unwrap();
return ImmutableList.of(uniformComponent, vertexMaterialComponent, instanceAssembly, layout, instance, context, pipelineVertex);
}
private ImmutableList<SourceComponent> getFragmentComponents(PipelineProgramKey key) {
private List<SourceComponent> getFragmentComponents(PipelineProgramKey key) {
var context = sources.find(key.contextShader()
.fragmentShader());
.fragmentShader())
.unwrap();
return ImmutableList.of(uniformComponent, fragmentMaterialComponent, context, pipelineFragment);
}
}

View file

@ -1,66 +1,55 @@
package com.jozufozu.flywheel.backend.compile;
import java.util.Optional;
import java.util.function.BiConsumer;
import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.glsl.SourceFile;
import com.jozufozu.flywheel.glsl.error.ErrorReporter;
import com.jozufozu.flywheel.glsl.parse.ShaderFunction;
import com.jozufozu.flywheel.glsl.parse.ShaderVariable;
// TODO: recycle to be invoked by the shader compiler
public class SourceChecks {
public static final BiConsumer<ErrorReporter, SourceFile> LAYOUT_VERTEX = checkFunctionArity("flw_layoutVertex", 0);
public static final BiConsumer<ErrorReporter, SourceFile> INSTANCE_VERTEX = checkFunctionParameterTypeExists("flw_instanceVertex", 1, 0);
public static final BiConsumer<ErrorReporter, SourceFile> MATERIAL_VERTEX = checkFunctionArity("flw_materialVertex", 0);
public static final BiConsumer<ErrorReporter, SourceFile> MATERIAL_FRAGMENT = checkFunctionArity("flw_materialFragment", 0);
public static final BiConsumer<ErrorReporter, SourceFile> CONTEXT_VERTEX = checkFunctionArity("flw_contextVertex", 0);
public static final BiConsumer<ErrorReporter, SourceFile> CONTEXT_FRAGMENT = checkFunctionArity("flw_contextFragment", 0).andThen(checkFunctionArity("flw_initFragment", 0));
public static final BiConsumer<ErrorReporter, SourceFile> PIPELINE = checkFunctionArity("main", 0);
public static BiConsumer<ErrorReporter, SourceFile> checkFunctionArity(String name, int arity) {
return (errorReporter, file) -> checkFunctionArity(errorReporter, file, name, arity);
}
public static BiConsumer<ErrorReporter, SourceFile> checkFunctionParameterTypeExists(String name, int arity, int param) {
return (errorReporter, file) -> {
var func = checkFunctionArity(errorReporter, file, name, arity);
if (func == null) {
return;
}
var maybeStruct = func.getParameterType(param)
.findStruct();
if (maybeStruct.isEmpty()) {
errorReporter.generateMissingStruct(file, func.getParameterType(param), "struct not defined");
}
};
}
/**
* @return {@code null} if the function doesn't exist, or if the function has the wrong arity.
*/
@Nullable
private static ShaderFunction checkFunctionArity(ErrorReporter errorReporter, SourceFile file, String name, int arity) {
Optional<ShaderFunction> maybeFunc = file.findFunction(name);
if (maybeFunc.isEmpty()) {
errorReporter.generateMissingFunction(file, name, "\"" + name + "\" function not defined");
return null;
}
ShaderFunction func = maybeFunc.get();
ImmutableList<ShaderVariable> params = func.getParameters();
if (params.size() != arity) {
errorReporter.generateFunctionArgumentCountError(name, arity, func.getArgs());
return null;
}
return func;
}
// public static final BiConsumer<ErrorReporter, SourceFile> LAYOUT_VERTEX = checkFunctionArity("flw_layoutVertex", 0);
// public static final BiConsumer<ErrorReporter, SourceFile> INSTANCE_VERTEX = checkFunctionParameterTypeExists("flw_instanceVertex", 1, 0);
// public static final BiConsumer<ErrorReporter, SourceFile> MATERIAL_VERTEX = checkFunctionArity("flw_materialVertex", 0);
// public static final BiConsumer<ErrorReporter, SourceFile> MATERIAL_FRAGMENT = checkFunctionArity("flw_materialFragment", 0);
// public static final BiConsumer<ErrorReporter, SourceFile> CONTEXT_VERTEX = checkFunctionArity("flw_contextVertex", 0);
// public static final BiConsumer<ErrorReporter, SourceFile> CONTEXT_FRAGMENT = checkFunctionArity("flw_contextFragment", 0).andThen(checkFunctionArity("flw_initFragment", 0));
// public static final BiConsumer<ErrorReporter, SourceFile> PIPELINE = checkFunctionArity("main", 0);
//
// public static BiConsumer<ErrorReporter, SourceFile> checkFunctionArity(String name, int arity) {
// return (errorReporter, file) -> checkFunctionArity(errorReporter, file, name, arity);
// }
//
// public static BiConsumer<ErrorReporter, SourceFile> checkFunctionParameterTypeExists(String name, int arity, int param) {
// return (errorReporter, file) -> {
// var func = checkFunctionArity(errorReporter, file, name, arity);
//
// if (func == null) {
// return;
// }
//
// var maybeStruct = func.getParameterType(param)
// .findStruct();
//
// if (maybeStruct.isEmpty()) {
// errorReporter.generateMissingStruct(file, func.getParameterType(param), "struct not defined");
// }
// };
// }
//
// /**
// * @return {@code null} if the function doesn't exist, or if the function has the wrong arity.
// */
// @Nullable
// private static ShaderFunction checkFunctionArity(ErrorReporter errorReporter, SourceFile file, String name, int arity) {
// Optional<ShaderFunction> maybeFunc = file.findFunction(name);
//
// if (maybeFunc.isEmpty()) {
// errorReporter.generateMissingFunction(file, name, "\"" + name + "\" function not defined");
// return null;
// }
//
// ShaderFunction func = maybeFunc.get();
// ImmutableList<ShaderVariable> params = func.getParameters();
// if (params.size() != arity) {
// errorReporter.generateFunctionArgumentCountError(name, arity, func.getArgs());
// return null;
// }
//
// return func;
// }
}

View file

@ -35,7 +35,8 @@ public class IndirectComponent implements SourceComponent {
public IndirectComponent(ShaderSources sources, InstanceType<?> instanceType) {
this.layoutItems = instanceType.getLayout().layoutItems;
included = ImmutableList.of(sources.find(Pipelines.Files.UTIL_TYPES));
included = ImmutableList.of(sources.find(Pipelines.Files.UTIL_TYPES)
.unwrap());
}
@Override

View file

@ -13,6 +13,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.jozufozu.flywheel.glsl.ShaderSources;
import com.jozufozu.flywheel.glsl.SourceComponent;
import com.jozufozu.flywheel.glsl.SourceFile;
import com.jozufozu.flywheel.glsl.generate.FnSignature;
import com.jozufozu.flywheel.glsl.generate.GlslBlock;
import com.jozufozu.flywheel.glsl.generate.GlslBuilder;
@ -145,7 +146,8 @@ public class MaterialAdapterComponent implements SourceComponent {
int index = 0;
for (var rl : materialSources) {
var sourceFile = sources.find(rl);
SourceFile sourceFile = sources.find(rl)
.unwrap();
final int finalIndex = index;
var adapterMap = createAdapterMap(adaptedFunctions, fnName -> "_" + fnName + "_" + finalIndex);
transformed.add(new StringSubstitutionSourceComponent(sourceFile, adapterMap));

View file

@ -67,7 +67,8 @@ public class UniformComponent implements SourceComponent {
var out = ImmutableList.<SourceFile>builder();
for (var fileResolution : uniformShaders) {
out.add(sources.find(fileResolution));
out.add(sources.find(fileResolution)
.unwrap());
}
return new UniformComponent(name, out.build());

View file

@ -8,6 +8,7 @@ import java.util.stream.Stream;
import org.jetbrains.annotations.NotNull;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.glsl.SourceFile;
import com.jozufozu.flywheel.glsl.SourceLines;
import com.jozufozu.flywheel.glsl.error.ErrorBuilder;
@ -15,7 +16,10 @@ import com.jozufozu.flywheel.glsl.span.Span;
import com.jozufozu.flywheel.util.ConsoleColors;
import com.jozufozu.flywheel.util.StringUtil;
import net.minecraft.resources.ResourceLocation;
public class FailedCompilation {
public static final ResourceLocation GENERATED_SOURCE_NAME = Flywheel.rl("generated_source");
private static final Pattern ERROR_LINE = Pattern.compile("(\\d+)\\((\\d+)\\) : (.*)");
private final List<SourceFile> files;
private final SourceLines generatedSource;
@ -25,7 +29,7 @@ public class FailedCompilation {
public FailedCompilation(String shaderName, List<SourceFile> files, String generatedSource, String errorLog) {
this.shaderName = shaderName;
this.files = files;
this.generatedSource = new SourceLines(generatedSource);
this.generatedSource = new SourceLines(GENERATED_SOURCE_NAME, generatedSource);
this.errorLog = errorLog;
}

View file

@ -2,6 +2,7 @@ package com.jozufozu.flywheel.backend.compile.core;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@ -10,7 +11,6 @@ import java.util.function.Consumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.gl.shader.GlShader;
import com.jozufozu.flywheel.gl.shader.ShaderType;
import com.jozufozu.flywheel.glsl.GLSLVersion;
@ -29,7 +29,7 @@ public class ShaderCompiler {
}
@Nullable
public GlShader compile(GLSLVersion glslVersion, ShaderType shaderType, ImmutableList<SourceComponent> sourceComponents) {
public GlShader compile(GLSLVersion glslVersion, ShaderType shaderType, List<SourceComponent> sourceComponents) {
var key = new ShaderKey(glslVersion, shaderType, sourceComponents);
var cached = shaderCache.get(key);
if (cached != null) {
@ -51,7 +51,7 @@ public class ShaderCompiler {
}
@NotNull
private ShaderResult compileUncached(Compilation ctx, ImmutableList<SourceComponent> sourceComponents) {
private ShaderResult compileUncached(Compilation ctx, List<SourceComponent> sourceComponents) {
ctx.enableExtension("GL_ARB_explicit_attrib_location");
ctx.enableExtension("GL_ARB_conservative_depth");
@ -60,14 +60,14 @@ public class ShaderCompiler {
return ctx.compile();
}
private static void expand(ImmutableList<SourceComponent> rootSources, Consumer<SourceComponent> out) {
var included = new LinkedHashSet<SourceComponent>(); // use hash set to deduplicate. linked to preserve order
for (var component : rootSources) {
recursiveDepthFirstInclude(included, component);
included.add(component);
}
included.forEach(out);
}
private static void expand(List<SourceComponent> rootSources, Consumer<SourceComponent> out) {
var included = new LinkedHashSet<SourceComponent>(); // use hash set to deduplicate. linked to preserve order
for (var component : rootSources) {
recursiveDepthFirstInclude(included, component);
included.add(component);
}
included.forEach(out);
}
private static void recursiveDepthFirstInclude(Set<SourceComponent> included, SourceComponent component) {
for (var include : component.included()) {
@ -76,7 +76,6 @@ public class ShaderCompiler {
included.addAll(component.included());
}
private record ShaderKey(GLSLVersion glslVersion, ShaderType shaderType,
ImmutableList<SourceComponent> sourceComponents) {
private record ShaderKey(GLSLVersion glslVersion, ShaderType shaderType, List<SourceComponent> sourceComponents) {
}
}

View file

@ -0,0 +1,39 @@
package com.jozufozu.flywheel.glsl;
import java.io.IOException;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import net.minecraft.resources.ResourceLocation;
public sealed interface LoadResult {
static LoadResult success(SourceFile sourceFile) {
return new Success(sourceFile);
}
@Nullable SourceFile unwrap();
record Success(SourceFile source) implements LoadResult {
@Override
@NotNull
public SourceFile unwrap() {
return source;
}
}
record IOError(ResourceLocation location, IOException exception) implements LoadResult {
@Override
public SourceFile unwrap() {
return null;
}
}
record IncludeError(ResourceLocation location, List<LoadResult> innerFailures) implements LoadResult {
@Override
public SourceFile unwrap() {
return null;
}
}
}

View file

@ -23,7 +23,7 @@ public class ShaderSources {
private final ResourceManager manager;
private final Map<ResourceLocation, SourceFile> cache = new HashMap<>();
private final Map<ResourceLocation, LoadResult> cache = new HashMap<>();
/**
* Tracks where we are in the mutual recursion to detect circular imports.
@ -35,7 +35,7 @@ public class ShaderSources {
}
@Nonnull
public SourceFile find(ResourceLocation location) {
public LoadResult find(ResourceLocation location) {
pushFindStack(location);
// Can't use computeIfAbsent because mutual recursion causes ConcurrentModificationExceptions
var out = cache.get(location);
@ -48,15 +48,15 @@ public class ShaderSources {
}
@Nonnull
private SourceFile load(ResourceLocation loc) {
private LoadResult load(ResourceLocation loc) {
try {
var resource = manager.getResource(ResourceUtil.prefixed(SHADER_DIR, loc));
var sourceString = StringUtil.readToString(resource.getInputStream());
return new SourceFile(this, loc, sourceString);
} catch (IOException ioException) {
throw new ShaderLoadingException("Could not load shader " + loc, ioException);
return SourceFile.parse(this, loc, sourceString);
} catch (IOException e) {
return new LoadResult.IOError(loc, e);
}
}

View file

@ -1,12 +1,17 @@
package com.jozufozu.flywheel.glsl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import org.jetbrains.annotations.NotNull;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.jozufozu.flywheel.glsl.parse.Import;
@ -29,8 +34,6 @@ import net.minecraft.resources.ResourceLocation;
public class SourceFile implements SourceComponent {
public final ResourceLocation name;
public final ShaderSources parent;
public final SourceLines source;
/**
@ -51,30 +54,51 @@ public class SourceFile implements SourceComponent {
public final List<SourceFile> included;
public SourceFile(ShaderSources sourceFinder, ResourceLocation name, String source) {
this.parent = sourceFinder;
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) {
this.name = name;
this.source = source;
this.functions = functions;
this.structs = structs;
this.imports = imports;
this.fields = fields;
this.included = included;
this.finalSource = finalSource;
}
this.source = new SourceLines(source);
public static LoadResult parse(ShaderSources sourceFinder, ResourceLocation name, String stringSource) {
var source = new SourceLines(name, stringSource);
this.imports = parseImports(source);
this.functions = parseFunctions(source);
this.structs = parseStructs(source);
this.fields = parseFields(source);
var imports = parseImports(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);
sink.accept(sourceFile);
} catch (Exception ignored) {
}
})
.toList();
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));
}
@Override
@ -84,7 +108,26 @@ public class SourceFile implements SourceComponent {
@Override
public String source() {
return this.genFinalSource();
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();
}
@Override
@ -96,7 +139,7 @@ public class SourceFile implements SourceComponent {
int begin = source.lineStartIndex(lineNo);
int end = begin + source.lineString(lineNo)
.length();
return new StringSpan(this, source.getCharPos(begin), source.getCharPos(end));
return new StringSpan(source, begin, end);
}
public Span getLineSpanNoWhitespace(int line) {
@ -108,7 +151,7 @@ public class SourceFile implements SourceComponent {
begin++;
}
return new StringSpan(this, source.getCharPos(begin), source.getCharPos(end));
return new StringSpan(source, begin, end);
}
/**
@ -157,32 +200,6 @@ public class SourceFile implements SourceComponent {
return Optional.empty();
}
public CharSequence importStatement() {
return "#use " + '"' + name + '"';
}
public String printSource() {
return "Source for shader '" + name + "':\n" + source.printLinesWithNumbers();
}
private String genFinalSource() {
StringBuilder out = new StringBuilder();
int lastEnd = 0;
for (var include : imports) {
var loc = include.self;
out.append(this.source, lastEnd, loc.getStartPos());
lastEnd = loc.getEndPos();
}
out.append(this.source, lastEnd, this.source.length());
return out.toString();
}
@Override
public String toString() {
return name.toString();
@ -199,19 +216,18 @@ public class SourceFile implements SourceComponent {
return System.identityHashCode(this);
}
/**
* 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) {
private static ImmutableList<Import> parseImports(SourceLines 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);
Span use = Span.fromMatcher(source, uses);
Span file = Span.fromMatcher(source, uses, 1);
imports.add(new Import(use, file));
}
@ -219,19 +235,18 @@ public class SourceFile implements SourceComponent {
return imports.build();
}
/**
* Scan the source for function definitions and "parse" them into objects that contain properties of the function.
*/
private ImmutableMap<String, ShaderFunction> parseFunctions(String source) {
private static ImmutableMap<String, ShaderFunction> parseFunctions(SourceLines source) {
Matcher matcher = ShaderFunction.PATTERN.matcher(source);
Map<String, ShaderFunction> functions = new HashMap<>();
while (matcher.find()) {
Span type = Span.fromMatcher(this, matcher, 1);
Span name = Span.fromMatcher(this, matcher, 2);
Span args = Span.fromMatcher(this, matcher, 3);
Span type = Span.fromMatcher(source, matcher, 1);
Span name = Span.fromMatcher(source, matcher, 2);
Span args = Span.fromMatcher(source, matcher, 3);
int blockStart = matcher.end();
int blockEnd = findEndOfBlock(source, blockStart);
@ -239,11 +254,11 @@ public class SourceFile implements SourceComponent {
Span self;
Span body;
if (blockEnd > blockStart) {
self = new StringSpan(this, matcher.start(), blockEnd + 1);
body = new StringSpan(this, blockStart, blockEnd);
self = new StringSpan(source, matcher.start(), blockEnd + 1);
body = new StringSpan(source, blockStart, blockEnd);
} else {
self = new ErrorSpan(this, matcher.start(), matcher.end());
body = new ErrorSpan(this, blockStart);
self = new ErrorSpan(source, matcher.start(), matcher.end());
body = new ErrorSpan(source, blockStart);
}
ShaderFunction function = new ShaderFunction(self, type, name, args, body);
@ -257,15 +272,15 @@ public class SourceFile implements SourceComponent {
/**
* Scan the source for function definitions and "parse" them into objects that contain properties of the function.
*/
private ImmutableMap<String, ShaderStruct> parseStructs(String source) {
private static ImmutableMap<String, ShaderStruct> parseStructs(SourceLines source) {
Matcher matcher = ShaderStruct.PATTERN.matcher(source);
ImmutableMap.Builder<String, ShaderStruct> structs = ImmutableMap.builder();
while (matcher.find()) {
Span self = Span.fromMatcher(this, matcher);
Span name = Span.fromMatcher(this, matcher, 1);
Span body = Span.fromMatcher(this, matcher, 2);
Span variableName = Span.fromMatcher(this, matcher, 3);
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);
ShaderStruct shaderStruct = new ShaderStruct(self, name, body, variableName);
@ -278,16 +293,16 @@ public class SourceFile implements SourceComponent {
/**
* Scan the source for function definitions and "parse" them into objects that contain properties of the function.
*/
private ImmutableMap<String, ShaderField> parseFields(String source) {
private static ImmutableMap<String, ShaderField> parseFields(SourceLines source) {
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);
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);
fields.put(location.get(), new ShaderField(self, location, decoration, type, name));
}
@ -298,13 +313,16 @@ public class SourceFile implements SourceComponent {
/**
* Given the position of an opening brace, scans through the source for a paired closing brace.
*/
private static int findEndOfBlock(String source, int start) {
private static int findEndOfBlock(CharSequence source, int start) {
int blockDepth = 0;
for (int i = start + 1; i < source.length(); i++) {
char ch = source.charAt(i);
if (ch == '{') blockDepth++;
else if (ch == '}') blockDepth--;
if (ch == '{') {
blockDepth++;
} else if (ch == '}') {
blockDepth--;
}
if (blockDepth < 0) {
return i;

View file

@ -11,11 +11,13 @@ import com.jozufozu.flywheel.glsl.span.CharPos;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.ints.IntLists;
import net.minecraft.resources.ResourceLocation;
public class SourceLines implements CharSequence {
private static final Pattern newLine = Pattern.compile("(\\r\\n|\\r|\\n)");
public final ResourceLocation name;
/**
* 0-indexed line to char pos mapping.
*/
@ -27,7 +29,8 @@ public class SourceLines implements CharSequence {
private final ImmutableList<String> lines;
public final String raw;
public SourceLines(String raw) {
public SourceLines(ResourceLocation name, String raw) {
this.name = name;
this.raw = raw;
this.lineStarts = createLineLookup(raw);
this.lines = getLines(raw, lineStarts);

View file

@ -59,6 +59,10 @@ public class ErrorBuilder {
return pointAtFile(file.name.toString());
}
public ErrorBuilder pointAtFile(SourceLines source) {
return pointAtFile(source.name.toString());
}
public ErrorBuilder pointAtFile(String file) {
lines.add(new FileLine(file));
return this;
@ -69,13 +73,13 @@ public class ErrorBuilder {
return this;
}
SourceFile sourceFile = span.getSourceFile();
var source = span.source();
String builder = "add " + sourceFile.importStatement() + ' ' + msg + "\n defined here:";
String builder = "add " + "#use " + '"' + source.name + '"' + ' ' + msg + "\n defined here:";
header(ErrorLevel.HINT, builder);
return this.pointAtFile(sourceFile)
return this.pointAtFile(source)
.pointAt(span, 0);
}
@ -85,12 +89,12 @@ public class ErrorBuilder {
public ErrorBuilder pointAt(Span span, int ctxLines) {
if (span.lines() == 1) {
SourceLines lines = span.getSourceFile().source;
SourceLines lines = span.source();
int spanLine = span.firstLine();
int firstCol = span.getStart()
int firstCol = span.start()
.col();
int lastCol = span.getEnd()
int lastCol = span.end()
.col();
pointAtLine(lines, spanLine, ctxLines, firstCol, lastCol);

View file

@ -62,7 +62,7 @@ public class ErrorReporter {
}
public ErrorBuilder generateSpanError(Span span, String message) {
SourceFile file = span.getSourceFile();
var file = span.source();
return error(message).pointAtFile(file)
.pointAt(span, 2);

View file

@ -4,16 +4,6 @@ import java.util.regex.Pattern;
import com.jozufozu.flywheel.glsl.span.Span;
public class Import {
public record Import(Span self, Span file) {
public static final Pattern PATTERN = Pattern.compile("^\\s*#\\s*use\\s+\"(.*)\"", Pattern.MULTILINE);
public final Span self;
public final Span file;
public Import(Span self, Span file) {
this.self = self;
this.file = file;
}
}

View file

@ -1,20 +1,20 @@
package com.jozufozu.flywheel.glsl.span;
import com.jozufozu.flywheel.glsl.SourceFile;
import com.jozufozu.flywheel.glsl.SourceLines;
/**
* Represents a (syntactically) malformed segment of code.
*/
public class ErrorSpan extends Span {
public ErrorSpan(SourceFile in, int loc) {
public ErrorSpan(SourceLines in, int loc) {
super(in, loc, loc);
}
public ErrorSpan(SourceFile in, int start, int end) {
public ErrorSpan(SourceLines in, int start, int end) {
super(in, start, end);
}
public ErrorSpan(SourceFile in, CharPos start, CharPos end) {
public ErrorSpan(SourceLines in, CharPos start, CharPos end) {
super(in, start, end);
}

View file

@ -1,13 +1,11 @@
package com.jozufozu.flywheel.glsl.span;
import java.util.Optional;
import java.util.regex.Matcher;
import org.jetbrains.annotations.NotNull;
import com.jozufozu.flywheel.glsl.SourceFile;
import com.jozufozu.flywheel.glsl.parse.ShaderFunction;
import com.jozufozu.flywheel.glsl.parse.ShaderStruct;
import com.jozufozu.flywheel.glsl.SourceLines;
/**
* A segment of code in a {@link SourceFile}.
@ -17,16 +15,15 @@ import com.jozufozu.flywheel.glsl.parse.ShaderStruct;
* </p>
*/
public abstract class Span implements CharSequence, Comparable<Span> {
protected final SourceFile in;
protected final SourceLines in;
protected final CharPos start;
protected final CharPos end;
public Span(SourceFile in, int start, int end) {
this(in, in.source.getCharPos(start), in.source.getCharPos(end));
public Span(SourceLines in, int start, int end) {
this(in, in.getCharPos(start), in.getCharPos(end));
}
public Span(SourceFile in, CharPos start, CharPos end) {
public Span(SourceLines in, CharPos start, CharPos end) {
this.in = in;
this.start = start;
this.end = end;
@ -35,29 +32,29 @@ public abstract class Span implements CharSequence, Comparable<Span> {
/**
* @return The file that contains this code segment.
*/
public SourceFile getSourceFile() {
public SourceLines source() {
return in;
}
public CharPos getStart() {
public CharPos start() {
return start;
}
public CharPos getEnd() {
public CharPos end() {
return end;
}
/**
* @return the string index at the (inclusive) beginning of this code segment.
*/
public int getStartPos() {
public int startIndex() {
return start.pos();
}
/**
* @return the string index at the (exclusive) end of this code segment.
*/
public int getEndPos() {
public int endIndex() {
return end.pos();
}
@ -93,12 +90,12 @@ public abstract class Span implements CharSequence, Comparable<Span> {
@Override
public int length() {
return getEndPos() - getStartPos();
return endIndex() - startIndex();
}
@Override
public char charAt(int index) {
return in.source.charAt(start.pos() + index);
return in.charAt(start.pos() + index);
}
@Override
@ -111,7 +108,7 @@ public abstract class Span implements CharSequence, Comparable<Span> {
return get();
}
public static Span fromMatcher(SourceFile src, Matcher m, int group) {
public static Span fromMatcher(SourceLines src, Matcher m, int group) {
return new StringSpan(src, m.start(group), m.end(group));
}
@ -119,7 +116,7 @@ public abstract class Span implements CharSequence, Comparable<Span> {
return superSpan.subSpan(m.start(group), m.end(group));
}
public static Span fromMatcher(SourceFile src, Matcher m) {
public static Span fromMatcher(SourceLines src, Matcher m) {
return new StringSpan(src, m.start(), m.end());
}
@ -127,22 +124,8 @@ public abstract class Span implements CharSequence, Comparable<Span> {
return superSpan.subSpan(m.start(), m.end());
}
public Optional<ShaderStruct> findStruct() {
if (isErr()) {
return Optional.empty();
}
return in.findStructByName(this.toString());
}
public Optional<ShaderFunction> findFunction() {
if (isErr()) {
return Optional.empty();
}
return in.findFunction(this.toString());
}
@Override
public int compareTo(@NotNull Span o) {
return Integer.compareUnsigned(getStartPos(), o.getStartPos());
return Integer.compareUnsigned(startIndex(), o.startIndex());
}
}

View file

@ -1,14 +1,10 @@
package com.jozufozu.flywheel.glsl.span;
import com.jozufozu.flywheel.glsl.SourceFile;
import com.jozufozu.flywheel.glsl.SourceLines;
public class StringSpan extends Span {
public StringSpan(SourceFile in, int start, int end) {
super(in, start, end);
}
public StringSpan(SourceFile in, CharPos start, CharPos end) {
public StringSpan(SourceLines in, int start, int end) {
super(in, start, end);
}
@ -19,7 +15,7 @@ public class StringSpan extends Span {
@Override
public String get() {
return in.source.raw.substring(start.pos(), end.pos());
return in.raw.substring(start.pos(), end.pos());
}
@Override