Still loading

- Catch errors while loading complex source components
  - Allow compilers to progress when such an error occurs, but don't do
    any actual compilation.
- Tweak error messages.
- Make resource locations in shaders default to flywheel namespace
- Make PipelineCompiler somehow simpler yet more verbose
This commit is contained in:
Jozufozu 2023-05-14 16:41:37 -07:00
parent b5fad9bd06
commit 98ebe9d95a
21 changed files with 365 additions and 131 deletions

View file

@ -12,21 +12,18 @@ import com.jozufozu.flywheel.backend.compile.core.ProgramLinker;
import com.jozufozu.flywheel.backend.compile.core.ShaderCompiler;
import com.jozufozu.flywheel.gl.shader.GlProgram;
import com.jozufozu.flywheel.glsl.ShaderSources;
import com.jozufozu.flywheel.glsl.SourceFile;
import net.minecraft.resources.ResourceLocation;
public abstract class AbstractCompiler<K> {
protected final ShaderSources sources;
protected final SourceLoader sourceLoader;
protected final ShaderCompiler shaderCompiler;
protected final ProgramLinker programLinker;
private final ImmutableList<K> keys;
private final CompilerStats stats = new CompilerStats();
public AbstractCompiler(ShaderSources sources, ImmutableList<K> keys) {
this.sources = sources;
this.keys = keys;
sourceLoader = new SourceLoader(sources, stats);
shaderCompiler = new ShaderCompiler(stats);
programLinker = new ProgramLinker(stats);
}
@ -34,21 +31,16 @@ public abstract class AbstractCompiler<K> {
@Nullable
protected abstract GlProgram compile(K key);
@Nullable
protected SourceFile findOrReport(ResourceLocation rl) {
var out = sources.find(rl);
stats.loadResult(out);
return out.unwrap();
}
@Nullable
public Map<K, GlProgram> compileAndReportErrors() {
stats.start();
Map<K, GlProgram> out = new HashMap<>();
for (var key : keys) {
GlProgram glProgram = compile(key);
if (glProgram != null) {
if (out != null && glProgram != null) {
out.put(key, glProgram);
} else {
out = null; // Return null when a preloading error occurs.
}
}
stats.finish();

View file

@ -19,21 +19,27 @@ public class CullingCompiler extends AbstractCompiler<InstanceType<?>> {
private final UniformComponent uniformComponent;
private final SourceFile pipelineCompute;
public CullingCompiler(ShaderSources sources, ImmutableList<InstanceType<?>> keys, UniformComponent uniformComponent) {
public static CullingCompiler create(SourceLoader sourceLoader, ImmutableList<InstanceType<?>> keys, UniformComponent uniformComponent) {
var sourceFile = sourceLoader.find(Files.INDIRECT_CULL);
return new CullingCompiler(sourceLoader.sources, keys, uniformComponent, sourceFile);
}
private CullingCompiler(ShaderSources sources, ImmutableList<InstanceType<?>> keys, UniformComponent uniformComponent, SourceFile pipeline) {
super(sources, keys);
this.uniformComponent = uniformComponent;
pipelineCompute = sources.find(Files.INDIRECT_CULL)
.unwrap();
this.pipelineCompute = pipeline;
}
@Nullable
@Override
protected GlProgram compile(InstanceType<?> key) {
var instanceAssembly = new IndirectComponent(sources, key);
var instance = findOrReport(key.instanceShader());
var instanceAssembly = IndirectComponent.create(sourceLoader, key);
ResourceLocation rl = key.instanceShader();
var instance = sourceLoader.find(rl);
if (instance == null) {
if (instanceAssembly == null || instance == null || uniformComponent == null || pipelineCompute == null) {
return null;
}

View file

@ -7,6 +7,7 @@ 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.backend.compile.core.CompilerStats;
import com.jozufozu.flywheel.glsl.ShaderSources;
import com.jozufozu.flywheel.glsl.generate.FnSignature;
import com.jozufozu.flywheel.glsl.generate.GlslExpr;
@ -21,21 +22,25 @@ public class FlwPrograms {
public static void reload(ResourceManager resourceManager) {
var sources = new ShaderSources(resourceManager);
var preLoadStats = new CompilerStats();
var loadChecker = new SourceLoader(sources, preLoadStats);
var pipelineKeys = createPipelineKeys();
var uniformComponent = UniformComponent.builder(Flywheel.rl("uniforms"))
.sources(ShaderUniforms.REGISTRY.getAll()
.stream()
.map(ShaderUniforms::uniformShader)
.toList())
.build(sources);
var uniformComponent = createUniformComponent(loadChecker);
var vertexMaterialComponent = createVertexMaterialComponent(loadChecker);
var fragmentMaterialComponent = createFragmentMaterialComponent(loadChecker);
var vertexMaterialComponent = MaterialAdapterComponent.builder(Flywheel.rl("vertex_material_adapter"))
.materialSources(MaterialIndices.getAllVertexShaders())
.adapt(FnSignature.ofVoid("flw_materialVertex"))
.switchOn(GlslExpr.variable("_flw_materialVertexID"))
.build(sources);
InstancingPrograms.reload(loadChecker, pipelineKeys, uniformComponent, vertexMaterialComponent, fragmentMaterialComponent);
IndirectPrograms.reload(loadChecker, pipelineKeys, uniformComponent, vertexMaterialComponent, fragmentMaterialComponent);
var fragmentMaterialComponent = MaterialAdapterComponent.builder(Flywheel.rl("fragment_material_adapter"))
if (preLoadStats.errored()) {
Flywheel.LOGGER.error(preLoadStats.generateErrorLog());
}
}
private static MaterialAdapterComponent createFragmentMaterialComponent(SourceLoader loadChecker) {
return MaterialAdapterComponent.builder(Flywheel.rl("fragment_material_adapter"))
.materialSources(MaterialIndices.getAllFragmentShaders())
.adapt(FnSignature.ofVoid("flw_materialFragment"))
.adapt(FnSignature.create()
@ -49,10 +54,24 @@ public class FlwPrograms {
.arg("vec4", "color")
.build(), GlslExpr.variable("color"))
.switchOn(GlslExpr.variable("_flw_materialFragmentID"))
.build(sources);
.build(loadChecker);
}
InstancingPrograms.reload(sources, pipelineKeys, uniformComponent, vertexMaterialComponent, fragmentMaterialComponent);
IndirectPrograms.reload(sources, pipelineKeys, uniformComponent, vertexMaterialComponent, fragmentMaterialComponent);
private static MaterialAdapterComponent createVertexMaterialComponent(SourceLoader loadChecker) {
return MaterialAdapterComponent.builder(Flywheel.rl("vertex_material_adapter"))
.materialSources(MaterialIndices.getAllVertexShaders())
.adapt(FnSignature.ofVoid("flw_materialVertex"))
.switchOn(GlslExpr.variable("_flw_materialVertexID"))
.build(loadChecker);
}
private static UniformComponent createUniformComponent(SourceLoader loadChecker) {
return UniformComponent.builder(Flywheel.rl("uniforms"))
.sources(ShaderUniforms.REGISTRY.getAll()
.stream()
.map(ShaderUniforms::uniformShader)
.toList())
.build(loadChecker);
}
private static ImmutableList<PipelineProgramKey> createPipelineKeys() {

View file

@ -11,10 +11,9 @@ 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;
public class IndirectPrograms {
private static IndirectPrograms instance;
public static IndirectPrograms instance;
private final Map<PipelineProgramKey, GlProgram> pipeline;
private final Map<InstanceType<?>, GlProgram> culling;
@ -23,13 +22,10 @@ public class IndirectPrograms {
this.culling = culling;
}
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, vertexMaterialComponent, fragmentMaterialComponent, uniformComponent);
var cullingCompiler = new CullingCompiler(sources, createCullingKeys(), uniformComponent);
static void reload(SourceLoader loadChecker, ImmutableList<PipelineProgramKey> pipelineKeys, UniformComponent uniformComponent, MaterialAdapterComponent vertexMaterialComponent, MaterialAdapterComponent fragmentMaterialComponent) {
_delete();
var pipelineCompiler = PipelineCompiler.create(loadChecker, Pipelines.INDIRECT, pipelineKeys, uniformComponent, vertexMaterialComponent, fragmentMaterialComponent);
var cullingCompiler = CullingCompiler.create(loadChecker, createCullingKeys(), uniformComponent);
var pipelineResult = pipelineCompiler.compileAndReportErrors();
var cullingResult = cullingCompiler.compileAndReportErrors();
@ -58,6 +54,13 @@ public class IndirectPrograms {
return instance != null;
}
private static void _delete() {
if (instance != null) {
instance.delete();
instance = null;
}
}
public GlProgram getIndirectProgram(VertexType vertexType, InstanceType<?> instanceType, Context contextShader) {
return pipeline.get(new PipelineProgramKey(vertexType, instanceType, contextShader));
}

View file

@ -11,28 +11,24 @@ 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;
public class InstancingPrograms {
private static InstancingPrograms instance;
static InstancingPrograms instance;
private final Map<PipelineProgramKey, GlProgram> pipeline;
public InstancingPrograms(Map<PipelineProgramKey, GlProgram> pipeline) {
this.pipeline = pipeline;
}
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, vertexMaterialComponent, fragmentMaterialComponent, uniformComponent);
static void reload(SourceLoader loadChecker, ImmutableList<PipelineProgramKey> pipelineKeys, UniformComponent uniformComponent, MaterialAdapterComponent vertexMaterialComponent, MaterialAdapterComponent fragmentMaterialComponent) {
_delete();
var instancingCompiler = PipelineCompiler.create(loadChecker, Pipelines.INSTANCED_ARRAYS, pipelineKeys, uniformComponent, vertexMaterialComponent, fragmentMaterialComponent);
var result = instancingCompiler.compileAndReportErrors();
if (result != null) {
instance = new InstancingPrograms(result);
}
instancingCompiler.delete();
}
@ -45,6 +41,13 @@ public class InstancingPrograms {
return instance != null;
}
static void _delete() {
if (instance != null) {
instance.delete();
instance = null;
}
}
public GlProgram get(VertexType vertexType, InstanceType<?> instanceType, Context contextShader) {
return pipeline.get(new PipelineProgramKey(vertexType, instanceType, contextShader));
}

View file

@ -3,7 +3,6 @@ package com.jozufozu.flywheel.backend.compile;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.glsl.GLSLVersion;
import com.jozufozu.flywheel.glsl.ShaderSources;
import com.jozufozu.flywheel.glsl.SourceComponent;
import net.minecraft.resources.ResourceLocation;
@ -19,7 +18,8 @@ public record Pipeline(GLSLVersion glslVersion, ResourceLocation vertexShader, R
SourceComponent assemble(InstanceAssemblerContext context);
}
public record InstanceAssemblerContext(ShaderSources sources, VertexType vertexType, InstanceType<?> instanceType) {
public record InstanceAssemblerContext(SourceLoader sourceLoader, VertexType vertexType,
InstanceType<?> instanceType) {
}
public static Builder builder() {

View file

@ -1,6 +1,8 @@
package com.jozufozu.flywheel.backend.compile;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.jetbrains.annotations.Nullable;
@ -12,27 +14,56 @@ import com.jozufozu.flywheel.gl.shader.GlShader;
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 net.minecraft.resources.ResourceLocation;
public class PipelineCompiler extends AbstractCompiler<PipelineProgramKey> {
private final Pipeline pipeline;
private final MaterialAdapterComponent vertexMaterialComponent;
private final MaterialAdapterComponent fragmentMaterialComponent;
private final UniformComponent uniformComponent;
private final SourceFile pipelineFragment;
private final SourceFile pipelineVertex;
private final List<SourceComponent> vertexPrelude = new ArrayList<>();
private final List<SourceComponent> vertexPostlude = new ArrayList<>();
private final List<SourceComponent> fragmentPrelude = new ArrayList<>();
private final List<SourceComponent> fragmentPostlude = new ArrayList<>();
public PipelineCompiler(ShaderSources sources, ImmutableList<PipelineProgramKey> keys, Pipeline pipeline, MaterialAdapterComponent vertexMaterialComponent, MaterialAdapterComponent fragmentMaterialComponent, UniformComponent uniformComponent) {
public PipelineCompiler(ShaderSources sources, ImmutableList<PipelineProgramKey> keys, Pipeline pipeline) {
super(sources, keys);
this.pipeline = pipeline;
this.vertexMaterialComponent = vertexMaterialComponent;
this.fragmentMaterialComponent = fragmentMaterialComponent;
this.uniformComponent = uniformComponent;
}
pipelineFragment = this.sources.find(pipeline.fragmentShader())
.unwrap();
pipelineVertex = this.sources.find(pipeline.vertexShader())
.unwrap();
static PipelineCompiler create(SourceLoader sourceLoader, Pipeline pipeline, ImmutableList<PipelineProgramKey> pipelineKeys, UniformComponent uniformComponent, MaterialAdapterComponent vertexMaterialComponent, MaterialAdapterComponent fragmentMaterialComponent) {
var fragmentPipeline = sourceLoader.find(pipeline.fragmentShader());
var vertexPipeline = sourceLoader.find(pipeline.vertexShader());
return new PipelineCompiler(sourceLoader.sources, pipelineKeys, pipeline).addPrelude(uniformComponent)
.addFragmentPrelude(fragmentMaterialComponent)
.addVertexPrelude(vertexMaterialComponent)
.addFragmentPostlude(fragmentPipeline)
.addVertexPostlude(vertexPipeline);
}
public PipelineCompiler addPrelude(SourceComponent component) {
addVertexPrelude(component);
addFragmentPrelude(component);
return this;
}
public PipelineCompiler addVertexPrelude(SourceComponent component) {
vertexPrelude.add(component);
return this;
}
public PipelineCompiler addVertexPostlude(SourceComponent component) {
vertexPostlude.add(component);
return this;
}
public PipelineCompiler addFragmentPrelude(SourceComponent component) {
fragmentPrelude.add(component);
return this;
}
public PipelineCompiler addFragmentPostlude(SourceComponent component) {
fragmentPostlude.add(component);
return this;
}
@Nullable
@ -74,31 +105,54 @@ public class PipelineCompiler extends AbstractCompiler<PipelineProgramKey> {
@Nullable
private List<SourceComponent> getVertexComponents(PipelineProgramKey key) {
var instanceAssembly = pipeline.assembler()
.assemble(new Pipeline.InstanceAssemblerContext(sources, key.vertexType(), key.instanceType()));
.assemble(new Pipeline.InstanceAssemblerContext(sourceLoader, key.vertexType(), key.instanceType()));
var layout = findOrReport(key.vertexType()
var layout = sourceLoader.find(key.vertexType()
.layoutShader());
var instance = findOrReport(key.instanceType()
var instance = sourceLoader.find(key.instanceType()
.instanceShader());
var context = findOrReport(key.contextShader()
var context = sourceLoader.find(key.contextShader()
.vertexShader());
if (instanceAssembly == null || layout == null || instance == null || context == null) {
return null;
}
return ImmutableList.of(uniformComponent, vertexMaterialComponent, instanceAssembly, layout, instance, context, pipelineVertex);
// Check this here to do a full dry-run in case of a preloading error.
if (vertexPrelude.stream()
.anyMatch(Objects::isNull) || vertexPostlude.stream()
.anyMatch(Objects::isNull)) {
return null;
}
return ImmutableList.<SourceComponent>builder()
.addAll(vertexPrelude)
.add(instanceAssembly, layout, instance, context)
.addAll(vertexPostlude)
.build();
}
@Nullable
private List<SourceComponent> getFragmentComponents(PipelineProgramKey key) {
var context = findOrReport(key.contextShader()
.fragmentShader());
ResourceLocation rl = key.contextShader()
.fragmentShader();
var context = sourceLoader.find(rl);
if (context == null) {
return null;
}
return ImmutableList.of(uniformComponent, fragmentMaterialComponent, context, pipelineFragment);
// Check this here to do a full dry-run in case of a preloading error.
if (fragmentPrelude.stream()
.anyMatch(Objects::isNull) || fragmentPostlude.stream()
.anyMatch(Objects::isNull)) {
return null;
}
return ImmutableList.<SourceComponent>builder()
.addAll(fragmentPrelude)
.add(context)
.addAll(fragmentPostlude)
.build();
}
}

View file

@ -18,7 +18,7 @@ public final class Pipelines {
.glslVersion(GLSLVersion.V460)
.vertex(Files.INDIRECT_DRAW)
.fragment(Files.DRAW_FRAGMENT)
.assembler(IndirectComponent::new)
.assembler(IndirectComponent::create)
.build();
public static void init() {

View file

@ -0,0 +1,27 @@
package com.jozufozu.flywheel.backend.compile;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.backend.compile.core.CompilerStats;
import com.jozufozu.flywheel.glsl.ShaderSources;
import com.jozufozu.flywheel.glsl.SourceFile;
import net.minecraft.resources.ResourceLocation;
public class SourceLoader {
public final ShaderSources sources;
private final CompilerStats stats;
public SourceLoader(ShaderSources sources, CompilerStats stats) {
this.sources = sources;
this.stats = stats;
}
@Nullable
public SourceFile find(ResourceLocation location) {
var out = sources.find(location);
stats.loadResult(out);
return out.unwrap();
}
}

View file

@ -9,7 +9,7 @@ import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.layout.LayoutItem;
import com.jozufozu.flywheel.backend.compile.Pipeline;
import com.jozufozu.flywheel.backend.compile.Pipelines;
import com.jozufozu.flywheel.glsl.ShaderSources;
import com.jozufozu.flywheel.backend.compile.SourceLoader;
import com.jozufozu.flywheel.glsl.SourceComponent;
import com.jozufozu.flywheel.glsl.SourceFile;
import com.jozufozu.flywheel.glsl.generate.FnSignature;
@ -29,14 +29,23 @@ public class IndirectComponent implements SourceComponent {
private final List<LayoutItem> layoutItems;
private final ImmutableList<SourceFile> included;
public IndirectComponent(Pipeline.InstanceAssemblerContext ctx) {
this(ctx.sources(), ctx.instanceType());
private IndirectComponent(List<LayoutItem> layoutItems, ImmutableList<SourceFile> included) {
this.layoutItems = layoutItems;
this.included = included;
}
public IndirectComponent(ShaderSources sources, InstanceType<?> instanceType) {
this.layoutItems = instanceType.getLayout().layoutItems;
included = ImmutableList.of(sources.find(Pipelines.Files.UTIL_TYPES)
.unwrap());
public static IndirectComponent create(Pipeline.InstanceAssemblerContext ctx) {
return create(ctx.sourceLoader(), ctx.instanceType());
}
public static IndirectComponent create(SourceLoader sourceLoader, InstanceType<?> instanceType) {
var util = sourceLoader.find(Pipelines.Files.UTIL_TYPES);
if (util == null) {
return null;
}
return new IndirectComponent(instanceType.getLayout().layoutItems, ImmutableList.of(util));
}
@Override

View file

@ -5,13 +5,11 @@ import java.util.Collection;
import java.util.List;
import java.util.function.UnaryOperator;
import javax.annotation.Nonnull;
import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.jozufozu.flywheel.glsl.ShaderSources;
import com.jozufozu.flywheel.backend.compile.SourceLoader;
import com.jozufozu.flywheel.glsl.SourceComponent;
import com.jozufozu.flywheel.glsl.SourceFile;
import com.jozufozu.flywheel.glsl.generate.FnSignature;
@ -127,7 +125,7 @@ public class MaterialAdapterComponent implements SourceComponent {
return this;
}
public Builder adapt(FnSignature function, @Nonnull GlslExpr defaultReturn) {
public Builder adapt(FnSignature function, GlslExpr defaultReturn) {
adaptedFunctions.add(new AdaptedFn(function, defaultReturn));
return this;
}
@ -137,23 +135,31 @@ public class MaterialAdapterComponent implements SourceComponent {
return this;
}
public MaterialAdapterComponent build(ShaderSources sources) {
public MaterialAdapterComponent build(SourceLoader sources) {
if (switchArg == null) {
throw new NullPointerException("Switch argument must be set");
}
var transformed = ImmutableList.<StringSubstitutionSourceComponent>builder();
boolean errored = false;
int index = 0;
for (var rl : materialSources) {
SourceFile sourceFile = sources.find(rl)
.unwrap();
SourceFile sourceFile = sources.find(rl);
final int finalIndex = index;
var adapterMap = createAdapterMap(adaptedFunctions, fnName -> "_" + fnName + "_" + finalIndex);
transformed.add(new StringSubstitutionSourceComponent(sourceFile, adapterMap));
if (sourceFile != null) {
var adapterMap = createAdapterMap(adaptedFunctions, fnName -> "_" + fnName + "_" + finalIndex);
transformed.add(new StringSubstitutionSourceComponent(sourceFile, adapterMap));
} else {
errored = true;
}
index++;
}
if (errored) {
return null;
}
return new MaterialAdapterComponent(name, switchArg, adaptedFunctions, transformed.build());
}
}

View file

@ -5,7 +5,7 @@ import java.util.Collection;
import java.util.List;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.glsl.ShaderSources;
import com.jozufozu.flywheel.backend.compile.SourceLoader;
import com.jozufozu.flywheel.glsl.SourceComponent;
import com.jozufozu.flywheel.glsl.SourceFile;
import com.jozufozu.flywheel.glsl.generate.GlslBuilder;
@ -63,12 +63,21 @@ public class UniformComponent implements SourceComponent {
return this;
}
public UniformComponent build(ShaderSources sources) {
public UniformComponent build(SourceLoader sources) {
var out = ImmutableList.<SourceFile>builder();
for (var fileResolution : uniformShaders) {
out.add(sources.find(fileResolution)
.unwrap());
boolean errored = false;
for (ResourceLocation uniformShader : uniformShaders) {
SourceFile sourceFile = sources.find(uniformShader);
if (sourceFile != null) {
out.add(sourceFile);
} else {
errored = true;
}
}
if (errored) {
return null;
}
return new UniformComponent(name, out.build());

View file

@ -17,9 +17,9 @@ import com.jozufozu.flywheel.util.StringUtil;
public class CompilerStats {
private long compileStart;
private final Set<LoadError> loadErrors = new HashSet<>();
private final List<FailedCompilation> shaderErrors = new ArrayList<>();
private final List<String> programErrors = new ArrayList<>();
private final Set<LoadError> loadErrors = new HashSet<>();
private boolean errored = false;
private int shaderCount = 0;
@ -41,11 +41,21 @@ public class CompilerStats {
}
public String generateErrorLog() {
return """
%s
%s
%s
""".formatted(loadErrors(), compileErrors(), linkErrors());
String out = "";
if (!loadErrors.isEmpty()) {
out += "\nErrors loading sources:\n" + loadErrors();
}
if (!shaderErrors.isEmpty()) {
out += "\nShader compilation errors:\n" + compileErrors();
}
if (!programErrors.isEmpty()) {
out += "\nProgram link errors:\n" + linkErrors();
}
return out;
}
private String compileErrors() {

View file

@ -1,5 +1,6 @@
package com.jozufozu.flywheel.glsl;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
@ -8,6 +9,7 @@ import com.jozufozu.flywheel.glsl.error.ErrorBuilder;
import com.jozufozu.flywheel.glsl.span.Span;
import com.jozufozu.flywheel.util.Pair;
import net.minecraft.ResourceLocationException;
import net.minecraft.resources.ResourceLocation;
sealed public interface LoadError {
@ -33,7 +35,7 @@ sealed public interface LoadError {
@Override
public ErrorBuilder generateMessage() {
var out = ErrorBuilder.create()
.error("could not load shader due to errors in included files")
.error("could not load \"" + location + "\"")
.pointAtFile(location);
for (var innerError : innerErrors) {
@ -48,11 +50,24 @@ sealed public interface LoadError {
}
record IOError(ResourceLocation location, IOException exception) implements LoadError {
@Override
public ErrorBuilder generateMessage() {
if (exception instanceof FileNotFoundException) {
return ErrorBuilder.create()
.error("\"" + location + "\" was not found");
} else {
return ErrorBuilder.create()
.error("could not load \"" + location + "\" due to an IO error")
.note(exception.toString());
}
}
}
record MalformedInclude(ResourceLocationException exception) implements LoadError {
@Override
public ErrorBuilder generateMessage() {
return ErrorBuilder.create()
.error("could not load \"" + location + "\" due to an IO error")
.note(exception.getMessage());
.error(exception.toString());
}
}
}

View file

@ -7,8 +7,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.VisibleForTesting;
@ -38,7 +36,7 @@ public class ShaderSources {
this.manager = manager;
}
@Nonnull
@NotNull
public LoadResult find(ResourceLocation location) {
if (findStack.contains(location)) {
// Make a copy of the find stack with the offending location added on top to show the full path.
@ -66,7 +64,7 @@ public class ShaderSources {
return out;
}
@Nonnull
@NotNull
protected LoadResult load(ResourceLocation loc) {
try {
var resource = manager.getResource(ResourceUtil.prefixed(SHADER_DIR, loc));

View file

@ -18,7 +18,9 @@ import com.jozufozu.flywheel.glsl.parse.ShaderStruct;
import com.jozufozu.flywheel.glsl.span.Span;
import com.jozufozu.flywheel.glsl.span.StringSpan;
import com.jozufozu.flywheel.util.Pair;
import com.jozufozu.flywheel.util.ResourceUtil;
import net.minecraft.ResourceLocationException;
import net.minecraft.resources.ResourceLocation;
/**
@ -79,7 +81,17 @@ public class SourceFile implements SourceComponent {
if (!seen.add(string)) {
continue;
}
var result = sourceFinder.find(new ResourceLocation(string));
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);
if (result instanceof LoadResult.Success s) {
included.add(s.unwrap());
} else if (result instanceof LoadResult.Failure e) {
@ -212,5 +224,4 @@ public class SourceFile implements SourceComponent {
return out.toString();
}
}

View file

@ -5,10 +5,22 @@ import java.util.regex.Pattern;
import net.minecraft.resources.ResourceLocation;
public class ResourceUtil {
// Match the complement of alphanumeric and underscore.
private static final Pattern UNSAFE_CHARS = Pattern.compile("[^a-zA-Z0-9_]");
public static ResourceLocation defaultToFlywheelNamespace(String location) {
String[] astring = new String[]{"flywheel", location};
int i = location.indexOf(':');
if (i >= 0) {
astring[1] = location.substring(i + 1);
if (i >= 1) {
astring[0] = location.substring(0, i);
}
}
return new ResourceLocation(astring[0], astring[1]);
}
public static ResourceLocation subPath(ResourceLocation root, String subPath) {
return new ResourceLocation(root.getNamespace(), root.getPath() + subPath);
}

View file

@ -1,6 +1,6 @@
package com.jozufozu.flywheel.glsl;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.util.HashMap;
import java.util.Map;
@ -26,7 +26,7 @@ public class MockShaderSources extends ShaderSources {
protected LoadResult load(ResourceLocation loc) {
var maybeFound = sources.get(loc);
if (maybeFound == null) {
return new LoadResult.Failure(new LoadError.IOError(loc, new IOException("Mock source not found")));
return new LoadResult.Failure(new LoadError.IOError(loc, new FileNotFoundException(loc.toString())));
}
return SourceFile.parse(this, loc, maybeFound);
}

View file

@ -9,7 +9,6 @@ import java.util.List;
import org.jetbrains.annotations.NotNull;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.glsl.error.ErrorBuilder;
import net.minecraft.resources.ResourceLocation;
@ -30,14 +29,6 @@ public class TestBase {
return assertInstanceOf(clazz, failure.error());
}
@NotNull
public static ErrorBuilder assertErrorAndGetMessage(MockShaderSources sources, ResourceLocation loc) {
var result = sources.find(loc);
var failure = assertInstanceOf(LoadResult.Failure.class, result);
return failure.error()
.generateMessage();
}
static <E extends LoadError> E assertSimpleNestedErrorsToDepth(Class<E> finalErrType, LoadError err, int depth) {
var includeError = assertInstanceOf(LoadError.IncludeError.class, err);

View file

@ -1,12 +1,16 @@
package com.jozufozu.flywheel.glsl;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import com.jozufozu.flywheel.glsl.error.ErrorBuilder;
import net.minecraft.resources.ResourceLocation;
public class TestErrorMessages extends TestBase {
@BeforeAll
static void disableConsoleColors() {
@ -20,14 +24,49 @@ public class TestErrorMessages extends TestBase {
#include "flywheel:b.glsl"
""");
var aErr = assertErrorAndGetMessage(sources, FLW_A);
assertEquals("""
error: could not load shader due to errors in included files
assertErrorMatches("""
error: could not load "flywheel:a.glsl"
--> flywheel:a.glsl
1 | #include "flywheel:b.glsl"
| ^^^^^^^^^^^^^^^
| error: could not load "flywheel:b.glsl" due to an IO error
| note: Mock source not found""", aErr.build());
| error: "flywheel:b.glsl" was not found
""", sources, FLW_A);
}
@Test
void testNestedIncludeMsg() {
var sources = new MockShaderSources();
sources.add(FLW_A, """
#include "flywheel:b.glsl"
""");
sources.add(FLW_B, """
#include "flywheel:c.glsl"
""");
assertErrorMatches("""
error: could not load "flywheel:a.glsl"
--> flywheel:a.glsl
1 | #include "flywheel:b.glsl"
| ^^^^^^^^^^^^^^^
| error: could not load "flywheel:b.glsl"
| --> flywheel:b.glsl
| 1 | #include "flywheel:c.glsl"
| | ^^^^^^^^^^^^^^^
| | error: "flywheel:c.glsl" was not found
""", sources, FLW_A);
}
public static void assertErrorMatches(String expected, MockShaderSources sources, ResourceLocation loc) {
var message = assertErrorAndGetMessage(sources, loc).build();
assertEquals(expected.trim(), message.trim());
}
@NotNull
public static ErrorBuilder assertErrorAndGetMessage(MockShaderSources sources, ResourceLocation loc) {
var result = sources.find(loc);
var failure = assertInstanceOf(LoadResult.Failure.class, result);
return failure.error()
.generateMessage();
}
}

View file

@ -25,6 +25,21 @@ public class TestShaderSourceLoading extends TestBase {
findAndAssertError(LoadError.IOError.class, sources, FLW_A);
}
/**
* #includes should default to the flywheel namespace since minecraft shaders aren't relevant.
*/
@Test
void testNoNamespace() {
var sources = new MockShaderSources();
sources.add(FLW_A, """
#include "b.glsl"
""");
sources.add(FLW_B, "");
findAndAssertSuccess(sources, FLW_A);
sources.assertLoaded(FLW_B);
}
@Test
void testMissingInclude() {
var sources = new MockShaderSources();
@ -38,6 +53,21 @@ public class TestShaderSourceLoading extends TestBase {
assertEquals(FLW_B, ioErr.location());
}
@Test
void testMalformedInclude() {
var sources = new MockShaderSources();
sources.add(FLW_A, """
#include "evil - wow"
""");
var aErr = findAndAssertError(LoadError.IncludeError.class, sources, FLW_A);
var malformedInclude = assertSimpleNestedErrorsToDepth(LoadError.MalformedInclude.class, aErr, 1);
var message = malformedInclude.exception()
.getMessage();
assertEquals("Non [a-z0-9/._-] character in path of location: flywheel:evil - wow", message);
}
@Test
void testBasicInclude() {
var sources = new MockShaderSources();