diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/AbstractCompiler.java b/src/main/java/com/jozufozu/flywheel/backend/compile/AbstractCompiler.java index cfa1aead9..09924ca12 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/compile/AbstractCompiler.java +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/AbstractCompiler.java @@ -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 { - protected final ShaderSources sources; + protected final SourceLoader sourceLoader; protected final ShaderCompiler shaderCompiler; protected final ProgramLinker programLinker; private final ImmutableList keys; private final CompilerStats stats = new CompilerStats(); public AbstractCompiler(ShaderSources sources, ImmutableList 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 { @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 compileAndReportErrors() { stats.start(); Map 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(); diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/CullingCompiler.java b/src/main/java/com/jozufozu/flywheel/backend/compile/CullingCompiler.java index 919476a04..1af34e1ba 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/compile/CullingCompiler.java +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/CullingCompiler.java @@ -19,21 +19,27 @@ public class CullingCompiler extends AbstractCompiler> { private final UniformComponent uniformComponent; private final SourceFile pipelineCompute; - public CullingCompiler(ShaderSources sources, ImmutableList> keys, UniformComponent uniformComponent) { + public static CullingCompiler create(SourceLoader sourceLoader, ImmutableList> keys, UniformComponent uniformComponent) { + var sourceFile = sourceLoader.find(Files.INDIRECT_CULL); + + return new CullingCompiler(sourceLoader.sources, keys, uniformComponent, sourceFile); + } + + private CullingCompiler(ShaderSources sources, ImmutableList> 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; } diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/FlwPrograms.java b/src/main/java/com/jozufozu/flywheel/backend/compile/FlwPrograms.java index 72b34e7b3..082250e0e 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/compile/FlwPrograms.java +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/FlwPrograms.java @@ -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 createPipelineKeys() { diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/IndirectPrograms.java b/src/main/java/com/jozufozu/flywheel/backend/compile/IndirectPrograms.java index 5921b7a2a..94674924f 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/compile/IndirectPrograms.java +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/IndirectPrograms.java @@ -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 pipeline; private final Map, GlProgram> culling; @@ -23,13 +22,10 @@ public class IndirectPrograms { this.culling = culling; } - public static void reload(ShaderSources sources, ImmutableList 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 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)); } diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/InstancingPrograms.java b/src/main/java/com/jozufozu/flywheel/backend/compile/InstancingPrograms.java index ec4320a2d..a4c1a794e 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/compile/InstancingPrograms.java +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/InstancingPrograms.java @@ -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 pipeline; public InstancingPrograms(Map pipeline) { this.pipeline = pipeline; } - public static void reload(ShaderSources sources, ImmutableList 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 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)); } diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/Pipeline.java b/src/main/java/com/jozufozu/flywheel/backend/compile/Pipeline.java index cce3188ef..5f8bef101 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/compile/Pipeline.java +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/Pipeline.java @@ -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() { diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/PipelineCompiler.java b/src/main/java/com/jozufozu/flywheel/backend/compile/PipelineCompiler.java index 5d59e6176..6d858c148 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/compile/PipelineCompiler.java +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/PipelineCompiler.java @@ -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 { 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 vertexPrelude = new ArrayList<>(); + private final List vertexPostlude = new ArrayList<>(); + private final List fragmentPrelude = new ArrayList<>(); + private final List fragmentPostlude = new ArrayList<>(); - public PipelineCompiler(ShaderSources sources, ImmutableList keys, Pipeline pipeline, MaterialAdapterComponent vertexMaterialComponent, MaterialAdapterComponent fragmentMaterialComponent, UniformComponent uniformComponent) { + public PipelineCompiler(ShaderSources sources, ImmutableList 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 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 { @Nullable private List 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.builder() + .addAll(vertexPrelude) + .add(instanceAssembly, layout, instance, context) + .addAll(vertexPostlude) + .build(); } @Nullable private List 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.builder() + .addAll(fragmentPrelude) + .add(context) + .addAll(fragmentPostlude) + .build(); } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/Pipelines.java b/src/main/java/com/jozufozu/flywheel/backend/compile/Pipelines.java index b3dde931d..fa79d4f66 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/compile/Pipelines.java +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/Pipelines.java @@ -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() { diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/SourceLoader.java b/src/main/java/com/jozufozu/flywheel/backend/compile/SourceLoader.java new file mode 100644 index 000000000..aed055e28 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/SourceLoader.java @@ -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(); + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/component/IndirectComponent.java b/src/main/java/com/jozufozu/flywheel/backend/compile/component/IndirectComponent.java index 881f22840..240bb8df3 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/compile/component/IndirectComponent.java +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/component/IndirectComponent.java @@ -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 layoutItems; private final ImmutableList included; - public IndirectComponent(Pipeline.InstanceAssemblerContext ctx) { - this(ctx.sources(), ctx.instanceType()); + private IndirectComponent(List layoutItems, ImmutableList 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 diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/component/MaterialAdapterComponent.java b/src/main/java/com/jozufozu/flywheel/backend/compile/component/MaterialAdapterComponent.java index 7b9396900..ce834dd30 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/compile/component/MaterialAdapterComponent.java +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/component/MaterialAdapterComponent.java @@ -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.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()); } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/component/UniformComponent.java b/src/main/java/com/jozufozu/flywheel/backend/compile/component/UniformComponent.java index 610cd2de6..fae9cc9e6 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/compile/component/UniformComponent.java +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/component/UniformComponent.java @@ -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.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()); diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/core/CompilerStats.java b/src/main/java/com/jozufozu/flywheel/backend/compile/core/CompilerStats.java index 4ce56e402..cdaee24fe 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/compile/core/CompilerStats.java +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/core/CompilerStats.java @@ -17,9 +17,9 @@ import com.jozufozu.flywheel.util.StringUtil; public class CompilerStats { private long compileStart; + private final Set loadErrors = new HashSet<>(); private final List shaderErrors = new ArrayList<>(); private final List programErrors = new ArrayList<>(); - private final Set 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() { diff --git a/src/main/java/com/jozufozu/flywheel/glsl/LoadError.java b/src/main/java/com/jozufozu/flywheel/glsl/LoadError.java index a22764d9b..b31de80e5 100644 --- a/src/main/java/com/jozufozu/flywheel/glsl/LoadError.java +++ b/src/main/java/com/jozufozu/flywheel/glsl/LoadError.java @@ -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()); } } } diff --git a/src/main/java/com/jozufozu/flywheel/glsl/ShaderSources.java b/src/main/java/com/jozufozu/flywheel/glsl/ShaderSources.java index 7e9d351df..0bebd5e4f 100644 --- a/src/main/java/com/jozufozu/flywheel/glsl/ShaderSources.java +++ b/src/main/java/com/jozufozu/flywheel/glsl/ShaderSources.java @@ -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)); diff --git a/src/main/java/com/jozufozu/flywheel/glsl/SourceFile.java b/src/main/java/com/jozufozu/flywheel/glsl/SourceFile.java index 50d07bb00..cafc0a41b 100644 --- a/src/main/java/com/jozufozu/flywheel/glsl/SourceFile.java +++ b/src/main/java/com/jozufozu/flywheel/glsl/SourceFile.java @@ -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(); } - } diff --git a/src/main/java/com/jozufozu/flywheel/util/ResourceUtil.java b/src/main/java/com/jozufozu/flywheel/util/ResourceUtil.java index 1498752a0..0a3de1daa 100644 --- a/src/main/java/com/jozufozu/flywheel/util/ResourceUtil.java +++ b/src/main/java/com/jozufozu/flywheel/util/ResourceUtil.java @@ -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); } diff --git a/src/test/java/com/jozufozu/flywheel/glsl/MockShaderSources.java b/src/test/java/com/jozufozu/flywheel/glsl/MockShaderSources.java index 811898a61..2d686eadf 100644 --- a/src/test/java/com/jozufozu/flywheel/glsl/MockShaderSources.java +++ b/src/test/java/com/jozufozu/flywheel/glsl/MockShaderSources.java @@ -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); } diff --git a/src/test/java/com/jozufozu/flywheel/glsl/TestBase.java b/src/test/java/com/jozufozu/flywheel/glsl/TestBase.java index c354bb93e..7570c80f5 100644 --- a/src/test/java/com/jozufozu/flywheel/glsl/TestBase.java +++ b/src/test/java/com/jozufozu/flywheel/glsl/TestBase.java @@ -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 assertSimpleNestedErrorsToDepth(Class finalErrType, LoadError err, int depth) { var includeError = assertInstanceOf(LoadError.IncludeError.class, err); diff --git a/src/test/java/com/jozufozu/flywheel/glsl/TestErrorMessages.java b/src/test/java/com/jozufozu/flywheel/glsl/TestErrorMessages.java index 099ea80e3..1394a15ee 100644 --- a/src/test/java/com/jozufozu/flywheel/glsl/TestErrorMessages.java +++ b/src/test/java/com/jozufozu/flywheel/glsl/TestErrorMessages.java @@ -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(); } } diff --git a/src/test/java/com/jozufozu/flywheel/glsl/TestShaderSourceLoading.java b/src/test/java/com/jozufozu/flywheel/glsl/TestShaderSourceLoading.java index a23c90d20..61106a033 100644 --- a/src/test/java/com/jozufozu/flywheel/glsl/TestShaderSourceLoading.java +++ b/src/test/java/com/jozufozu/flywheel/glsl/TestShaderSourceLoading.java @@ -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();