mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-01-07 12:56:31 +01:00
Shader sanity
- Switch #use to #include - Stop ignoring errors from within shader compilers - Track load errors in CompilerStats - Move LoadError to separate sealed interface, LoadResult.Failure wraps a LoadError - Move SourceFile member parsing methods to respective classes - Add tests for SourceFile loading - Start work on tests for error messages
This commit is contained in:
parent
d1e54d65da
commit
d27929c307
42 changed files with 731 additions and 412 deletions
|
@ -12,6 +12,9 @@ import com.jozufozu.flywheel.backend.compile.core.ProgramLinker;
|
||||||
import com.jozufozu.flywheel.backend.compile.core.ShaderCompiler;
|
import com.jozufozu.flywheel.backend.compile.core.ShaderCompiler;
|
||||||
import com.jozufozu.flywheel.gl.shader.GlProgram;
|
import com.jozufozu.flywheel.gl.shader.GlProgram;
|
||||||
import com.jozufozu.flywheel.glsl.ShaderSources;
|
import com.jozufozu.flywheel.glsl.ShaderSources;
|
||||||
|
import com.jozufozu.flywheel.glsl.SourceFile;
|
||||||
|
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
public abstract class AbstractCompiler<K> {
|
public abstract class AbstractCompiler<K> {
|
||||||
protected final ShaderSources sources;
|
protected final ShaderSources sources;
|
||||||
|
@ -31,6 +34,13 @@ public abstract class AbstractCompiler<K> {
|
||||||
@Nullable
|
@Nullable
|
||||||
protected abstract GlProgram compile(K key);
|
protected abstract GlProgram compile(K key);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
protected SourceFile findOrReport(ResourceLocation rl) {
|
||||||
|
var out = sources.find(rl);
|
||||||
|
stats.loadResult(out);
|
||||||
|
return out.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public Map<K, GlProgram> compileAndReportErrors() {
|
public Map<K, GlProgram> compileAndReportErrors() {
|
||||||
stats.start();
|
stats.start();
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package com.jozufozu.flywheel.backend.compile;
|
package com.jozufozu.flywheel.backend.compile;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
@ -13,7 +11,6 @@ import com.jozufozu.flywheel.gl.shader.GlProgram;
|
||||||
import com.jozufozu.flywheel.gl.shader.ShaderType;
|
import com.jozufozu.flywheel.gl.shader.ShaderType;
|
||||||
import com.jozufozu.flywheel.glsl.GLSLVersion;
|
import com.jozufozu.flywheel.glsl.GLSLVersion;
|
||||||
import com.jozufozu.flywheel.glsl.ShaderSources;
|
import com.jozufozu.flywheel.glsl.ShaderSources;
|
||||||
import com.jozufozu.flywheel.glsl.SourceComponent;
|
|
||||||
import com.jozufozu.flywheel.glsl.SourceFile;
|
import com.jozufozu.flywheel.glsl.SourceFile;
|
||||||
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
@ -33,7 +30,14 @@ public class CullingCompiler extends AbstractCompiler<InstanceType<?>> {
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
protected GlProgram compile(InstanceType<?> key) {
|
protected GlProgram compile(InstanceType<?> key) {
|
||||||
var computeComponents = getComputeComponents(key);
|
var instanceAssembly = new IndirectComponent(sources, key);
|
||||||
|
var instance = findOrReport(key.instanceShader());
|
||||||
|
|
||||||
|
if (instance == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var computeComponents = ImmutableList.of(uniformComponent, instanceAssembly, instance, pipelineCompute);
|
||||||
var compute = shaderCompiler.compile(GLSLVersion.V460, ShaderType.COMPUTE, computeComponents);
|
var compute = shaderCompiler.compile(GLSLVersion.V460, ShaderType.COMPUTE, computeComponents);
|
||||||
|
|
||||||
if (compute == null) {
|
if (compute == null) {
|
||||||
|
@ -43,15 +47,6 @@ public class CullingCompiler extends AbstractCompiler<InstanceType<?>> {
|
||||||
return programLinker.link(compute);
|
return programLinker.link(compute);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<SourceComponent> getComputeComponents(InstanceType<?> instanceType) {
|
|
||||||
var instanceAssembly = new IndirectComponent(sources, instanceType);
|
|
||||||
ResourceLocation key = instanceType.instanceShader();
|
|
||||||
var instance = sources.find(key)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
return ImmutableList.of(uniformComponent, instanceAssembly, instance, pipelineCompute);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class Files {
|
private static final class Files {
|
||||||
public static final ResourceLocation INDIRECT_CULL = Flywheel.rl("internal/indirect_cull.glsl");
|
public static final ResourceLocation INDIRECT_CULL = Flywheel.rl("internal/indirect_cull.glsl");
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import com.google.common.collect.ImmutableList;
|
||||||
import com.jozufozu.flywheel.backend.compile.component.MaterialAdapterComponent;
|
import com.jozufozu.flywheel.backend.compile.component.MaterialAdapterComponent;
|
||||||
import com.jozufozu.flywheel.backend.compile.component.UniformComponent;
|
import com.jozufozu.flywheel.backend.compile.component.UniformComponent;
|
||||||
import com.jozufozu.flywheel.gl.shader.GlProgram;
|
import com.jozufozu.flywheel.gl.shader.GlProgram;
|
||||||
|
import com.jozufozu.flywheel.gl.shader.GlShader;
|
||||||
import com.jozufozu.flywheel.gl.shader.ShaderType;
|
import com.jozufozu.flywheel.gl.shader.ShaderType;
|
||||||
import com.jozufozu.flywheel.glsl.ShaderSources;
|
import com.jozufozu.flywheel.glsl.ShaderSources;
|
||||||
import com.jozufozu.flywheel.glsl.SourceComponent;
|
import com.jozufozu.flywheel.glsl.SourceComponent;
|
||||||
|
@ -37,10 +38,8 @@ public class PipelineCompiler extends AbstractCompiler<PipelineProgramKey> {
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
protected GlProgram compile(PipelineProgramKey key) {
|
protected GlProgram compile(PipelineProgramKey key) {
|
||||||
var glslVersion = pipeline.glslVersion();
|
GlShader vertex = compileVertex(key);
|
||||||
|
GlShader fragment = compileFragment(key);
|
||||||
var vertex = shaderCompiler.compile(glslVersion, ShaderType.VERTEX, getVertexComponents(key));
|
|
||||||
var fragment = shaderCompiler.compile(glslVersion, ShaderType.FRAGMENT, getFragmentComponents(key));
|
|
||||||
|
|
||||||
if (vertex == null || fragment == null) {
|
if (vertex == null || fragment == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -52,27 +51,54 @@ public class PipelineCompiler extends AbstractCompiler<PipelineProgramKey> {
|
||||||
return glProgram;
|
return glProgram;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private GlShader compileVertex(PipelineProgramKey key) {
|
||||||
|
var vertexComponents = getVertexComponents(key);
|
||||||
|
if (vertexComponents == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return shaderCompiler.compile(pipeline.glslVersion(), ShaderType.VERTEX, vertexComponents);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private GlShader compileFragment(PipelineProgramKey key) {
|
||||||
|
var fragmentComponents = getFragmentComponents(key);
|
||||||
|
if (fragmentComponents == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return shaderCompiler.compile(pipeline.glslVersion(), ShaderType.FRAGMENT, fragmentComponents);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
private List<SourceComponent> getVertexComponents(PipelineProgramKey key) {
|
private List<SourceComponent> getVertexComponents(PipelineProgramKey key) {
|
||||||
var instanceAssembly = pipeline.assembler()
|
var instanceAssembly = pipeline.assembler()
|
||||||
.assemble(new Pipeline.InstanceAssemblerContext(sources, key.vertexType(), key.instanceType()));
|
.assemble(new Pipeline.InstanceAssemblerContext(sources, key.vertexType(), key.instanceType()));
|
||||||
|
|
||||||
var layout = sources.find(key.vertexType()
|
var layout = findOrReport(key.vertexType()
|
||||||
.layoutShader())
|
.layoutShader());
|
||||||
.unwrap();
|
var instance = findOrReport(key.instanceType()
|
||||||
var instance = sources.find(key.instanceType()
|
.instanceShader());
|
||||||
.instanceShader())
|
var context = findOrReport(key.contextShader()
|
||||||
.unwrap();
|
.vertexShader());
|
||||||
var context = sources.find(key.contextShader()
|
|
||||||
.vertexShader())
|
if (instanceAssembly == null || layout == null || instance == null || context == null) {
|
||||||
.unwrap();
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return ImmutableList.of(uniformComponent, vertexMaterialComponent, instanceAssembly, layout, instance, context, pipelineVertex);
|
return ImmutableList.of(uniformComponent, vertexMaterialComponent, instanceAssembly, layout, instance, context, pipelineVertex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
private List<SourceComponent> getFragmentComponents(PipelineProgramKey key) {
|
private List<SourceComponent> getFragmentComponents(PipelineProgramKey key) {
|
||||||
var context = sources.find(key.contextShader()
|
var context = findOrReport(key.contextShader()
|
||||||
.fragmentShader())
|
.fragmentShader());
|
||||||
.unwrap();
|
|
||||||
|
if (context == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return ImmutableList.of(uniformComponent, fragmentMaterialComponent, context, pipelineFragment);
|
return ImmutableList.of(uniformComponent, fragmentMaterialComponent, context, pipelineFragment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
package com.jozufozu.flywheel.backend.compile.core;
|
package com.jozufozu.flywheel.backend.compile.core;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.Flywheel;
|
import com.jozufozu.flywheel.Flywheel;
|
||||||
|
import com.jozufozu.flywheel.glsl.LoadError;
|
||||||
|
import com.jozufozu.flywheel.glsl.LoadResult;
|
||||||
|
import com.jozufozu.flywheel.glsl.error.ErrorBuilder;
|
||||||
import com.jozufozu.flywheel.util.StringUtil;
|
import com.jozufozu.flywheel.util.StringUtil;
|
||||||
|
|
||||||
public class CompilerStats {
|
public class CompilerStats {
|
||||||
|
@ -12,6 +19,8 @@ public class CompilerStats {
|
||||||
|
|
||||||
private final List<FailedCompilation> shaderErrors = new ArrayList<>();
|
private final List<FailedCompilation> shaderErrors = new ArrayList<>();
|
||||||
private final List<String> programErrors = new ArrayList<>();
|
private final List<String> programErrors = new ArrayList<>();
|
||||||
|
private final Set<LoadError> loadErrors = new HashSet<>();
|
||||||
|
|
||||||
private boolean errored = false;
|
private boolean errored = false;
|
||||||
private int shaderCount = 0;
|
private int shaderCount = 0;
|
||||||
private int programCount = 0;
|
private int programCount = 0;
|
||||||
|
@ -32,8 +41,28 @@ public class CompilerStats {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String generateErrorLog() {
|
public String generateErrorLog() {
|
||||||
return String.join("\n", programErrors) + '\n' + shaderErrors.stream()
|
return """
|
||||||
.map(FailedCompilation::getMessage)
|
%s
|
||||||
|
%s
|
||||||
|
%s
|
||||||
|
""".formatted(loadErrors(), compileErrors(), linkErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String compileErrors() {
|
||||||
|
return shaderErrors.stream()
|
||||||
|
.map(FailedCompilation::generateMessage)
|
||||||
|
.collect(Collectors.joining("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private String linkErrors() {
|
||||||
|
return String.join("\n", programErrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String loadErrors() {
|
||||||
|
return loadErrors.stream()
|
||||||
|
.map(LoadError::generateMessage)
|
||||||
|
.map(ErrorBuilder::build)
|
||||||
.collect(Collectors.joining("\n"));
|
.collect(Collectors.joining("\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,4 +81,11 @@ public class CompilerStats {
|
||||||
}
|
}
|
||||||
programCount++;
|
programCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void loadResult(LoadResult loadResult) {
|
||||||
|
if (loadResult instanceof LoadResult.Failure f) {
|
||||||
|
loadErrors.add(f.error());
|
||||||
|
errored = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ public class FailedCompilation {
|
||||||
this.errorLog = errorLog;
|
this.errorLog = errorLog;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMessage() {
|
public String generateMessage() {
|
||||||
return ConsoleColors.RED_BOLD_BRIGHT + "Failed to compile " + shaderName + ":\n" + errorString();
|
return ConsoleColors.RED_BOLD_BRIGHT + "Failed to compile " + shaderName + ":\n" + errorString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.gl.shader.GlShader;
|
import com.jozufozu.flywheel.gl.shader.GlShader;
|
||||||
|
@ -24,10 +23,6 @@ public class ShaderCompiler {
|
||||||
this.stats = stats;
|
this.stats = stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int shaderCount() {
|
|
||||||
return shaderCache.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public GlShader compile(GLSLVersion glslVersion, ShaderType shaderType, List<SourceComponent> sourceComponents) {
|
public GlShader compile(GLSLVersion glslVersion, ShaderType shaderType, List<SourceComponent> sourceComponents) {
|
||||||
var key = new ShaderKey(glslVersion, shaderType, sourceComponents);
|
var key = new ShaderKey(glslVersion, shaderType, sourceComponents);
|
||||||
|
@ -36,7 +31,13 @@ public class ShaderCompiler {
|
||||||
return cached.unwrap();
|
return cached.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
ShaderResult out = compileUncached(new Compilation(glslVersion, shaderType), sourceComponents);
|
Compilation ctx = new Compilation(glslVersion, shaderType);
|
||||||
|
ctx.enableExtension("GL_ARB_explicit_attrib_location");
|
||||||
|
ctx.enableExtension("GL_ARB_conservative_depth");
|
||||||
|
|
||||||
|
expand(sourceComponents, ctx::appendComponent);
|
||||||
|
|
||||||
|
ShaderResult out = ctx.compile();
|
||||||
shaderCache.put(key, out);
|
shaderCache.put(key, out);
|
||||||
stats.shaderResult(out);
|
stats.shaderResult(out);
|
||||||
return out.unwrap();
|
return out.unwrap();
|
||||||
|
@ -50,16 +51,6 @@ public class ShaderCompiler {
|
||||||
.forEach(GlShader::delete);
|
.forEach(GlShader::delete);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private ShaderResult compileUncached(Compilation ctx, List<SourceComponent> sourceComponents) {
|
|
||||||
ctx.enableExtension("GL_ARB_explicit_attrib_location");
|
|
||||||
ctx.enableExtension("GL_ARB_conservative_depth");
|
|
||||||
|
|
||||||
expand(sourceComponents, ctx::appendComponent);
|
|
||||||
|
|
||||||
return ctx.compile();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void expand(List<SourceComponent> rootSources, Consumer<SourceComponent> 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
|
var included = new LinkedHashSet<SourceComponent>(); // use hash set to deduplicate. linked to preserve order
|
||||||
for (var component : rootSources) {
|
for (var component : rootSources) {
|
||||||
|
|
58
src/main/java/com/jozufozu/flywheel/glsl/LoadError.java
Normal file
58
src/main/java/com/jozufozu/flywheel/glsl/LoadError.java
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package com.jozufozu.flywheel.glsl;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import com.jozufozu.flywheel.glsl.error.ErrorBuilder;
|
||||||
|
import com.jozufozu.flywheel.glsl.span.Span;
|
||||||
|
import com.jozufozu.flywheel.util.Pair;
|
||||||
|
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
|
sealed public interface LoadError {
|
||||||
|
ErrorBuilder generateMessage();
|
||||||
|
|
||||||
|
record CircularDependency(ResourceLocation offender, List<ResourceLocation> stack) implements LoadError {
|
||||||
|
public String format() {
|
||||||
|
return stack.stream()
|
||||||
|
.dropWhile(l -> !l.equals(offender))
|
||||||
|
.map(ResourceLocation::toString)
|
||||||
|
.collect(Collectors.joining(" -> "));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ErrorBuilder generateMessage() {
|
||||||
|
return ErrorBuilder.create()
|
||||||
|
.error("files are circularly dependent")
|
||||||
|
.note(format());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record IncludeError(ResourceLocation location, List<Pair<Span, LoadError>> innerErrors) implements LoadError {
|
||||||
|
@Override
|
||||||
|
public ErrorBuilder generateMessage() {
|
||||||
|
var out = ErrorBuilder.create()
|
||||||
|
.error("could not load shader due to errors in included files")
|
||||||
|
.pointAtFile(location);
|
||||||
|
|
||||||
|
for (var innerError : innerErrors) {
|
||||||
|
var err = innerError.second()
|
||||||
|
.generateMessage();
|
||||||
|
out.pointAt(innerError.first())
|
||||||
|
.nested(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record IOError(ResourceLocation location, IOException exception) implements LoadError {
|
||||||
|
@Override
|
||||||
|
public ErrorBuilder generateMessage() {
|
||||||
|
return ErrorBuilder.create()
|
||||||
|
.error("could not load \"" + location + "\" due to an IO error")
|
||||||
|
.note(exception.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +1,14 @@
|
||||||
package com.jozufozu.flywheel.glsl;
|
package com.jozufozu.flywheel.glsl;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
|
||||||
|
|
||||||
public sealed interface LoadResult {
|
public sealed interface LoadResult {
|
||||||
static LoadResult success(SourceFile sourceFile) {
|
@Nullable
|
||||||
return new Success(sourceFile);
|
default SourceFile unwrap() {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable SourceFile unwrap();
|
|
||||||
|
|
||||||
record Success(SourceFile source) implements LoadResult {
|
record Success(SourceFile source) implements LoadResult {
|
||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
|
@ -23,17 +17,6 @@ public sealed interface LoadResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
record IOError(ResourceLocation location, IOException exception) implements LoadResult {
|
record Failure(LoadError error) implements LoadResult {
|
||||||
@Override
|
|
||||||
public SourceFile unwrap() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
record IncludeError(ResourceLocation location, List<LoadResult> innerFailures) implements LoadResult {
|
|
||||||
@Override
|
|
||||||
public SourceFile unwrap() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
package com.jozufozu.flywheel.glsl;
|
|
||||||
|
|
||||||
public class ShaderLoadingException extends RuntimeException {
|
|
||||||
|
|
||||||
public ShaderLoadingException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ShaderLoadingException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,11 +4,14 @@ import java.io.IOException;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.util.ResourceUtil;
|
import com.jozufozu.flywheel.util.ResourceUtil;
|
||||||
import com.jozufozu.flywheel.util.StringUtil;
|
import com.jozufozu.flywheel.util.StringUtil;
|
||||||
|
|
||||||
|
@ -23,7 +26,8 @@ public class ShaderSources {
|
||||||
|
|
||||||
private final ResourceManager manager;
|
private final ResourceManager manager;
|
||||||
|
|
||||||
private final Map<ResourceLocation, LoadResult> cache = new HashMap<>();
|
@VisibleForTesting
|
||||||
|
protected final Map<ResourceLocation, LoadResult> cache = new HashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks where we are in the mutual recursion to detect circular imports.
|
* Tracks where we are in the mutual recursion to detect circular imports.
|
||||||
|
@ -36,19 +40,34 @@ public class ShaderSources {
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public LoadResult find(ResourceLocation location) {
|
public LoadResult find(ResourceLocation location) {
|
||||||
pushFindStack(location);
|
if (findStack.contains(location)) {
|
||||||
|
// Make a copy of the find stack with the offending location added on top to show the full path.
|
||||||
|
findStack.addLast(location);
|
||||||
|
var copy = List.copyOf(findStack);
|
||||||
|
findStack.removeLast();
|
||||||
|
return new LoadResult.Failure(new LoadError.CircularDependency(location, copy));
|
||||||
|
}
|
||||||
|
findStack.addLast(location);
|
||||||
|
|
||||||
|
LoadResult out = _find(location);
|
||||||
|
|
||||||
|
findStack.removeLast();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private LoadResult _find(ResourceLocation location) {
|
||||||
// Can't use computeIfAbsent because mutual recursion causes ConcurrentModificationExceptions
|
// Can't use computeIfAbsent because mutual recursion causes ConcurrentModificationExceptions
|
||||||
var out = cache.get(location);
|
var out = cache.get(location);
|
||||||
if (out == null) {
|
if (out == null) {
|
||||||
out = load(location);
|
out = load(location);
|
||||||
cache.put(location, out);
|
cache.put(location, out);
|
||||||
}
|
}
|
||||||
popFindStack();
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private LoadResult load(ResourceLocation loc) {
|
protected LoadResult load(ResourceLocation loc) {
|
||||||
try {
|
try {
|
||||||
var resource = manager.getResource(ResourceUtil.prefixed(SHADER_DIR, loc));
|
var resource = manager.getResource(ResourceUtil.prefixed(SHADER_DIR, loc));
|
||||||
|
|
||||||
|
@ -56,28 +75,7 @@ public class ShaderSources {
|
||||||
|
|
||||||
return SourceFile.parse(this, loc, sourceString);
|
return SourceFile.parse(this, loc, sourceString);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
return new LoadResult.IOError(loc, e);
|
return new LoadResult.Failure(new LoadError.IOError(loc, e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateRecursiveImportException(ResourceLocation location) {
|
|
||||||
findStack.add(location);
|
|
||||||
String path = findStack.stream()
|
|
||||||
.dropWhile(l -> !l.equals(location))
|
|
||||||
.map(ResourceLocation::toString)
|
|
||||||
.collect(Collectors.joining(" -> "));
|
|
||||||
findStack.clear();
|
|
||||||
throw new ShaderLoadingException("recursive import: " + path);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void pushFindStack(ResourceLocation location) {
|
|
||||||
if (findStack.contains(location)) {
|
|
||||||
generateRecursiveImportException(location);
|
|
||||||
}
|
|
||||||
findStack.addLast(location);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void popFindStack() {
|
|
||||||
findStack.removeLast();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,10 @@ package com.jozufozu.flywheel.glsl;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Matcher;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
@ -18,9 +15,9 @@ import com.jozufozu.flywheel.glsl.parse.Import;
|
||||||
import com.jozufozu.flywheel.glsl.parse.ShaderField;
|
import com.jozufozu.flywheel.glsl.parse.ShaderField;
|
||||||
import com.jozufozu.flywheel.glsl.parse.ShaderFunction;
|
import com.jozufozu.flywheel.glsl.parse.ShaderFunction;
|
||||||
import com.jozufozu.flywheel.glsl.parse.ShaderStruct;
|
import com.jozufozu.flywheel.glsl.parse.ShaderStruct;
|
||||||
import com.jozufozu.flywheel.glsl.span.ErrorSpan;
|
|
||||||
import com.jozufozu.flywheel.glsl.span.Span;
|
import com.jozufozu.flywheel.glsl.span.Span;
|
||||||
import com.jozufozu.flywheel.glsl.span.StringSpan;
|
import com.jozufozu.flywheel.glsl.span.StringSpan;
|
||||||
|
import com.jozufozu.flywheel.util.Pair;
|
||||||
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
|
@ -70,10 +67,10 @@ public class SourceFile implements SourceComponent {
|
||||||
public static LoadResult parse(ShaderSources sourceFinder, ResourceLocation name, String stringSource) {
|
public static LoadResult parse(ShaderSources sourceFinder, ResourceLocation name, String stringSource) {
|
||||||
var source = new SourceLines(name, stringSource);
|
var source = new SourceLines(name, stringSource);
|
||||||
|
|
||||||
var imports = parseImports(source);
|
var imports = Import.parseImports(source);
|
||||||
|
|
||||||
List<SourceFile> included = new ArrayList<>();
|
List<SourceFile> included = new ArrayList<>();
|
||||||
List<LoadResult> failures = new ArrayList<>();
|
List<Pair<Span, LoadError>> failures = new ArrayList<>();
|
||||||
|
|
||||||
Set<String> seen = new HashSet<>();
|
Set<String> seen = new HashSet<>();
|
||||||
for (Import i : imports) {
|
for (Import i : imports) {
|
||||||
|
@ -85,20 +82,20 @@ public class SourceFile implements SourceComponent {
|
||||||
var result = sourceFinder.find(new ResourceLocation(string));
|
var result = sourceFinder.find(new ResourceLocation(string));
|
||||||
if (result instanceof LoadResult.Success s) {
|
if (result instanceof LoadResult.Success s) {
|
||||||
included.add(s.unwrap());
|
included.add(s.unwrap());
|
||||||
} else {
|
} else if (result instanceof LoadResult.Failure e) {
|
||||||
failures.add(result);
|
failures.add(Pair.of(fileSpan, e.error()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!failures.isEmpty()) {
|
if (!failures.isEmpty()) {
|
||||||
return new LoadResult.IncludeError(name, failures);
|
return new LoadResult.Failure(new LoadError.IncludeError(name, failures));
|
||||||
}
|
}
|
||||||
|
|
||||||
var functions = parseFunctions(source);
|
var functions = ShaderFunction.parseFunctions(source);
|
||||||
var structs = parseStructs(source);
|
var structs = ShaderStruct.parseStructs(source);
|
||||||
var fields = parseFields(source);
|
var fields = ShaderField.parseFields(source);
|
||||||
|
|
||||||
var finalSource = generateFinalSource(imports, source);
|
var finalSource = generateFinalSource(imports, source);
|
||||||
return LoadResult.success(new SourceFile(name, source, functions, structs, imports, fields, included, finalSource));
|
return new LoadResult.Success(new SourceFile(name, source, functions, structs, imports, fields, included, finalSource));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -111,25 +108,6 @@ public class SourceFile implements SourceComponent {
|
||||||
return finalSource;
|
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
|
@Override
|
||||||
public ResourceLocation name() {
|
public ResourceLocation name() {
|
||||||
return name;
|
return name;
|
||||||
|
@ -216,119 +194,23 @@ public class SourceFile implements SourceComponent {
|
||||||
return System.identityHashCode(this);
|
return System.identityHashCode(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@NotNull
|
||||||
* Scan the source for {@code #use "..."} directives.
|
private static String generateFinalSource(ImmutableList<Import> imports, SourceLines source) {
|
||||||
* Records the contents of the directive into an {@link Import} object, and marks the directive for elision.
|
var out = new StringBuilder();
|
||||||
*/
|
|
||||||
private static ImmutableList<Import> parseImports(SourceLines source) {
|
|
||||||
Matcher uses = Import.PATTERN.matcher(source);
|
|
||||||
|
|
||||||
var imports = ImmutableList.<Import>builder();
|
int lastEnd = 0;
|
||||||
|
|
||||||
while (uses.find()) {
|
for (var include : imports) {
|
||||||
Span use = Span.fromMatcher(source, uses);
|
var loc = include.self();
|
||||||
Span file = Span.fromMatcher(source, uses, 1);
|
|
||||||
|
|
||||||
imports.add(new Import(use, file));
|
out.append(source, lastEnd, loc.startIndex());
|
||||||
|
|
||||||
|
lastEnd = loc.endIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
return imports.build();
|
out.append(source, lastEnd, source.length());
|
||||||
|
|
||||||
|
return out.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Scan the source for function definitions and "parse" them into objects that contain properties of the function.
|
|
||||||
*/
|
|
||||||
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(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);
|
|
||||||
|
|
||||||
Span self;
|
|
||||||
Span body;
|
|
||||||
if (blockEnd > blockStart) {
|
|
||||||
self = new StringSpan(source, matcher.start(), blockEnd + 1);
|
|
||||||
body = new StringSpan(source, blockStart, blockEnd);
|
|
||||||
} else {
|
|
||||||
self = new ErrorSpan(source, matcher.start(), matcher.end());
|
|
||||||
body = new ErrorSpan(source, blockStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderFunction function = new ShaderFunction(self, type, name, args, body);
|
|
||||||
|
|
||||||
functions.put(name.get(), function);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ImmutableMap.copyOf(functions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scan the source for function definitions and "parse" them into objects that contain properties of the function.
|
|
||||||
*/
|
|
||||||
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(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);
|
|
||||||
|
|
||||||
structs.put(name.get(), shaderStruct);
|
|
||||||
}
|
|
||||||
|
|
||||||
return structs.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scan the source for function definitions and "parse" them into objects that contain properties of the function.
|
|
||||||
*/
|
|
||||||
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(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));
|
|
||||||
}
|
|
||||||
|
|
||||||
return fields.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given the position of an opening brace, scans through the source for a paired closing brace.
|
|
||||||
*/
|
|
||||||
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 (blockDepth < 0) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,19 @@ package com.jozufozu.flywheel.glsl.error;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.jetbrains.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.glsl.SourceFile;
|
import com.jozufozu.flywheel.glsl.SourceFile;
|
||||||
import com.jozufozu.flywheel.glsl.SourceLines;
|
import com.jozufozu.flywheel.glsl.SourceLines;
|
||||||
import com.jozufozu.flywheel.glsl.error.lines.ErrorLine;
|
import com.jozufozu.flywheel.glsl.error.lines.ErrorLine;
|
||||||
import com.jozufozu.flywheel.glsl.error.lines.FileLine;
|
import com.jozufozu.flywheel.glsl.error.lines.FileLine;
|
||||||
import com.jozufozu.flywheel.glsl.error.lines.HeaderLine;
|
import com.jozufozu.flywheel.glsl.error.lines.HeaderLine;
|
||||||
|
import com.jozufozu.flywheel.glsl.error.lines.NestedLine;
|
||||||
import com.jozufozu.flywheel.glsl.error.lines.SourceLine;
|
import com.jozufozu.flywheel.glsl.error.lines.SourceLine;
|
||||||
import com.jozufozu.flywheel.glsl.error.lines.SpanHighlightLine;
|
import com.jozufozu.flywheel.glsl.error.lines.SpanHighlightLine;
|
||||||
import com.jozufozu.flywheel.glsl.error.lines.TextLine;
|
import com.jozufozu.flywheel.glsl.error.lines.TextLine;
|
||||||
|
@ -17,7 +22,12 @@ import com.jozufozu.flywheel.glsl.span.Span;
|
||||||
import com.jozufozu.flywheel.util.ConsoleColors;
|
import com.jozufozu.flywheel.util.ConsoleColors;
|
||||||
import com.jozufozu.flywheel.util.StringUtil;
|
import com.jozufozu.flywheel.util.StringUtil;
|
||||||
|
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
public class ErrorBuilder {
|
public class ErrorBuilder {
|
||||||
|
// set to false for testing
|
||||||
|
@VisibleForTesting
|
||||||
|
public static boolean CONSOLE_COLORS = true;
|
||||||
|
|
||||||
private final List<ErrorLine> lines = new ArrayList<>();
|
private final List<ErrorLine> lines = new ArrayList<>();
|
||||||
|
|
||||||
|
@ -56,11 +66,15 @@ public class ErrorBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ErrorBuilder pointAtFile(SourceFile file) {
|
public ErrorBuilder pointAtFile(SourceFile file) {
|
||||||
return pointAtFile(file.name.toString());
|
return pointAtFile(file.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ErrorBuilder pointAtFile(SourceLines source) {
|
public ErrorBuilder pointAtFile(SourceLines source) {
|
||||||
return pointAtFile(source.name.toString());
|
return pointAtFile(source.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ErrorBuilder pointAtFile(ResourceLocation file) {
|
||||||
|
return pointAtFile(file.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ErrorBuilder pointAtFile(String file) {
|
public ErrorBuilder pointAtFile(String file) {
|
||||||
|
@ -126,6 +140,34 @@ public class ErrorBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String build() {
|
public String build() {
|
||||||
|
Stream<String> lineStream = getLineStream();
|
||||||
|
|
||||||
|
if (CONSOLE_COLORS) {
|
||||||
|
lineStream = lineStream.map(line -> line + ConsoleColors.RESET);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lineStream.collect(Collectors.joining("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Stream<String> getLineStream() {
|
||||||
|
int maxMargin = calculateMargin();
|
||||||
|
|
||||||
|
return lines.stream()
|
||||||
|
.map(line -> addPaddingToLine(maxMargin, line));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String addPaddingToLine(int maxMargin, ErrorLine errorLine) {
|
||||||
|
int neededMargin = errorLine.neededMargin();
|
||||||
|
|
||||||
|
if (neededMargin >= 0) {
|
||||||
|
return StringUtil.repeatChar(' ', maxMargin - neededMargin) + errorLine.build();
|
||||||
|
} else {
|
||||||
|
return errorLine.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int calculateMargin() {
|
||||||
int maxMargin = -1;
|
int maxMargin = -1;
|
||||||
for (ErrorLine line : lines) {
|
for (ErrorLine line : lines) {
|
||||||
int neededMargin = line.neededMargin();
|
int neededMargin = line.neededMargin();
|
||||||
|
@ -134,20 +176,12 @@ public class ErrorBuilder {
|
||||||
maxMargin = neededMargin;
|
maxMargin = neededMargin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return maxMargin;
|
||||||
|
}
|
||||||
|
|
||||||
StringBuilder builder = new StringBuilder();
|
public void nested(ErrorBuilder err) {
|
||||||
for (ErrorLine line : lines) {
|
err.getLineStream()
|
||||||
int neededMargin = line.neededMargin();
|
.map(NestedLine::new)
|
||||||
|
.forEach(lines::add);
|
||||||
if (neededMargin >= 0) {
|
|
||||||
builder.append(StringUtil.repeatChar(' ', maxMargin - neededMargin));
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.append(line.build())
|
|
||||||
.append(ConsoleColors.RESET)
|
|
||||||
.append('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,20 +3,26 @@ package com.jozufozu.flywheel.glsl.error;
|
||||||
import com.jozufozu.flywheel.util.ConsoleColors;
|
import com.jozufozu.flywheel.util.ConsoleColors;
|
||||||
|
|
||||||
public enum ErrorLevel {
|
public enum ErrorLevel {
|
||||||
WARN(ConsoleColors.YELLOW + "warn"),
|
WARN(ConsoleColors.YELLOW, "warn"),
|
||||||
ERROR(ConsoleColors.RED + "error"),
|
ERROR(ConsoleColors.RED, "error"),
|
||||||
HINT(ConsoleColors.WHITE_BRIGHT + "hint"),
|
HINT(ConsoleColors.WHITE_BRIGHT, "hint"),
|
||||||
NOTE(ConsoleColors.WHITE_BRIGHT + "note"),
|
NOTE(ConsoleColors.WHITE_BRIGHT, "note"),
|
||||||
;
|
;
|
||||||
|
|
||||||
|
private final String color;
|
||||||
private final String error;
|
private final String error;
|
||||||
|
|
||||||
ErrorLevel(String error) {
|
ErrorLevel(String color, String error) {
|
||||||
|
this.color = color;
|
||||||
this.error = error;
|
this.error = error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return error;
|
if (ErrorBuilder.CONSOLE_COLORS) {
|
||||||
|
return color + error;
|
||||||
|
} else {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,115 +0,0 @@
|
||||||
package com.jozufozu.flywheel.glsl.error;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
|
|
||||||
import com.jozufozu.flywheel.glsl.ShaderLoadingException;
|
|
||||||
import com.jozufozu.flywheel.glsl.SourceFile;
|
|
||||||
import com.jozufozu.flywheel.glsl.span.Span;
|
|
||||||
import com.jozufozu.flywheel.lib.math.MoreMath;
|
|
||||||
import com.jozufozu.flywheel.util.StringUtil;
|
|
||||||
import com.mojang.logging.LogUtils;
|
|
||||||
|
|
||||||
public class ErrorReporter {
|
|
||||||
private static final Logger LOGGER = LogUtils.getLogger();
|
|
||||||
|
|
||||||
private final List<ErrorBuilder> reportedErrors = new ArrayList<>();
|
|
||||||
|
|
||||||
public void generateMissingStruct(SourceFile file, Span vertexName, CharSequence msg) {
|
|
||||||
generateMissingStruct(file, vertexName, msg, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void generateMissingStruct(SourceFile file, Span vertexName, CharSequence msg, CharSequence hint) {
|
|
||||||
// Optional<Span> span = file.parent.index.getStructDefinitionsMatching(vertexName)
|
|
||||||
// .stream()
|
|
||||||
// .findFirst()
|
|
||||||
// .map(ShaderStruct::getName);
|
|
||||||
//
|
|
||||||
// this.error(msg)
|
|
||||||
// .pointAtFile(file)
|
|
||||||
// .pointAt(vertexName, 1)
|
|
||||||
// .hintIncludeFor(span.orElse(null), hint);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void generateMissingFunction(SourceFile file, CharSequence functionName, CharSequence msg) {
|
|
||||||
generateMissingFunction(file, functionName, msg, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void generateMissingFunction(SourceFile file, CharSequence functionName, CharSequence msg, CharSequence hint) {
|
|
||||||
// Optional<Span> span = file.parent.index.getFunctionDefinitionsMatching(functionName)
|
|
||||||
// .stream()
|
|
||||||
// .findFirst()
|
|
||||||
// .map(ShaderFunction::getName);
|
|
||||||
//
|
|
||||||
// this.error(msg)
|
|
||||||
// .pointAtFile(file)
|
|
||||||
// .hintIncludeFor(span.orElse(null), hint);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ErrorBuilder generateFunctionArgumentCountError(String name, int requiredArguments, Span span) {
|
|
||||||
var msg = '"' + name + "\" function must ";
|
|
||||||
|
|
||||||
if (requiredArguments == 0) {
|
|
||||||
msg += "not have any arguments";
|
|
||||||
} else {
|
|
||||||
msg += "have exactly " + requiredArguments + " argument" + (requiredArguments == 1 ? "" : "s");
|
|
||||||
}
|
|
||||||
|
|
||||||
return generateSpanError(span, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ErrorBuilder generateSpanError(Span span, String message) {
|
|
||||||
var file = span.source();
|
|
||||||
|
|
||||||
return error(message).pointAtFile(file)
|
|
||||||
.pointAt(span, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ErrorBuilder generateFileError(SourceFile file, String message) {
|
|
||||||
return error(message).pointAtFile(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ErrorBuilder error(String msg) {
|
|
||||||
var out = ErrorBuilder.create()
|
|
||||||
.error(msg);
|
|
||||||
reportedErrors.add(out);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasErrored() {
|
|
||||||
return !reportedErrors.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ShaderLoadingException dump() {
|
|
||||||
var allErrors = reportedErrors.stream()
|
|
||||||
.map(ErrorBuilder::build)
|
|
||||||
.collect(Collectors.joining());
|
|
||||||
|
|
||||||
return new ShaderLoadingException(allErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void printLines(String string) {
|
|
||||||
List<String> lines = string.lines()
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
int size = lines.size();
|
|
||||||
|
|
||||||
int maxWidth = MoreMath.numDigits(size) + 1;
|
|
||||||
|
|
||||||
StringBuilder builder = new StringBuilder().append('\n');
|
|
||||||
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
|
|
||||||
builder.append(i)
|
|
||||||
.append(StringUtil.repeatChar(' ', maxWidth - MoreMath.numDigits(i)))
|
|
||||||
.append("| ")
|
|
||||||
.append(lines.get(i))
|
|
||||||
.append('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER.error(builder.toString());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.jozufozu.flywheel.glsl.error.lines;
|
||||||
|
|
||||||
|
public record NestedLine(String right) implements ErrorLine {
|
||||||
|
@Override
|
||||||
|
public String right() {
|
||||||
|
return right;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
@MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault
|
||||||
|
package com.jozufozu.flywheel.glsl;
|
||||||
|
|
||||||
|
import javax.annotation.ParametersAreNonnullByDefault;
|
||||||
|
|
||||||
|
import net.minecraft.MethodsReturnNonnullByDefault;
|
|
@ -1,9 +1,31 @@
|
||||||
package com.jozufozu.flywheel.glsl.parse;
|
package com.jozufozu.flywheel.glsl.parse;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.jozufozu.flywheel.glsl.SourceLines;
|
||||||
import com.jozufozu.flywheel.glsl.span.Span;
|
import com.jozufozu.flywheel.glsl.span.Span;
|
||||||
|
|
||||||
public record Import(Span self, Span file) {
|
public record Import(Span self, Span file) {
|
||||||
public static final Pattern PATTERN = Pattern.compile("^\\s*#\\s*use\\s+\"(.*)\"", Pattern.MULTILINE);
|
public static final Pattern PATTERN = Pattern.compile("^\\s*#\\s*include\\s+\"(.*)\"", Pattern.MULTILINE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan the source for {@code #use "..."} directives.
|
||||||
|
* Records the contents of the directive into an {@link Import} object, and marks the directive for elision.
|
||||||
|
*/
|
||||||
|
public static ImmutableList<Import> parseImports(SourceLines source) {
|
||||||
|
Matcher uses = PATTERN.matcher(source);
|
||||||
|
|
||||||
|
var imports = ImmutableList.<Import>builder();
|
||||||
|
|
||||||
|
while (uses.find()) {
|
||||||
|
Span use = Span.fromMatcher(source, uses);
|
||||||
|
Span file = Span.fromMatcher(source, uses, 1);
|
||||||
|
|
||||||
|
imports.add(new Import(use, file));
|
||||||
|
}
|
||||||
|
|
||||||
|
return imports.build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
package com.jozufozu.flywheel.glsl.parse;
|
package com.jozufozu.flywheel.glsl.parse;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.jozufozu.flywheel.glsl.SourceLines;
|
||||||
import com.jozufozu.flywheel.glsl.span.Span;
|
import com.jozufozu.flywheel.glsl.span.Span;
|
||||||
|
|
||||||
public class ShaderField {
|
public class ShaderField {
|
||||||
|
@ -24,6 +27,26 @@ public class ShaderField {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan the source for function definitions and "parse" them into objects that contain properties of the function.
|
||||||
|
*/
|
||||||
|
public static ImmutableMap<String, ShaderField> parseFields(SourceLines source) {
|
||||||
|
Matcher matcher = PATTERN.matcher(source);
|
||||||
|
|
||||||
|
ImmutableMap.Builder<String, ShaderField> fields = ImmutableMap.builder();
|
||||||
|
while (matcher.find()) {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields.build();
|
||||||
|
}
|
||||||
|
|
||||||
public enum Decoration {
|
public enum Decoration {
|
||||||
IN,
|
IN,
|
||||||
OUT,
|
OUT,
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
package com.jozufozu.flywheel.glsl.parse;
|
package com.jozufozu.flywheel.glsl.parse;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.jozufozu.flywheel.glsl.SourceLines;
|
||||||
|
import com.jozufozu.flywheel.glsl.span.ErrorSpan;
|
||||||
import com.jozufozu.flywheel.glsl.span.Span;
|
import com.jozufozu.flywheel.glsl.span.Span;
|
||||||
|
import com.jozufozu.flywheel.glsl.span.StringSpan;
|
||||||
|
|
||||||
public class ShaderFunction {
|
public class ShaderFunction {
|
||||||
// https://regexr.com/60n3d
|
// https://regexr.com/60n3d
|
||||||
|
@ -32,6 +38,62 @@ public class ShaderFunction {
|
||||||
this.parameters = parseArguments();
|
this.parameters = parseArguments();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan the source for function definitions and "parse" them into objects that contain properties of the function.
|
||||||
|
*/
|
||||||
|
public static ImmutableMap<String, ShaderFunction> parseFunctions(SourceLines source) {
|
||||||
|
Matcher matcher = PATTERN.matcher(source);
|
||||||
|
|
||||||
|
Map<String, ShaderFunction> functions = new HashMap<>();
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
Span self;
|
||||||
|
Span body;
|
||||||
|
if (blockEnd > blockStart) {
|
||||||
|
self = new StringSpan(source, matcher.start(), blockEnd + 1);
|
||||||
|
body = new StringSpan(source, blockStart, blockEnd);
|
||||||
|
} else {
|
||||||
|
self = new ErrorSpan(source, matcher.start(), matcher.end());
|
||||||
|
body = new ErrorSpan(source, blockStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderFunction function = new ShaderFunction(self, type, name, args, body);
|
||||||
|
|
||||||
|
functions.put(name.get(), function);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ImmutableMap.copyOf(functions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the position of an opening brace, scans through the source for a paired closing brace.
|
||||||
|
*/
|
||||||
|
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 (blockDepth < 0) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
public Span getType() {
|
public Span getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import java.util.regex.Pattern;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.jozufozu.flywheel.glsl.SourceLines;
|
||||||
import com.jozufozu.flywheel.glsl.span.Span;
|
import com.jozufozu.flywheel.glsl.span.Span;
|
||||||
|
|
||||||
public class ShaderStruct {
|
public class ShaderStruct {
|
||||||
|
@ -29,6 +30,27 @@ public class ShaderStruct {
|
||||||
this.fields2Types = createTypeLookup();
|
this.fields2Types = createTypeLookup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan the source for function definitions and "parse" them into objects that contain properties of the function.
|
||||||
|
*/
|
||||||
|
public static ImmutableMap<String, ShaderStruct> parseStructs(SourceLines source) {
|
||||||
|
Matcher matcher = PATTERN.matcher(source);
|
||||||
|
|
||||||
|
ImmutableMap.Builder<String, ShaderStruct> structs = ImmutableMap.builder();
|
||||||
|
while (matcher.find()) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
structs.put(name.get(), shaderStruct);
|
||||||
|
}
|
||||||
|
|
||||||
|
return structs.build();
|
||||||
|
}
|
||||||
|
|
||||||
public Span getName() {
|
public Span getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#use "flywheel:api/vertex.glsl"
|
#include "flywheel:api/vertex.glsl"
|
||||||
#use "flywheel:util/fog.glsl"
|
#include "flywheel:util/fog.glsl"
|
||||||
|
|
||||||
void flw_contextVertex() {
|
void flw_contextVertex() {
|
||||||
flw_distance = fog_distance(flw_vertexPos.xyz, flywheel.cameraPos.xyz, flywheel.fogShape);
|
flw_distance = fog_distance(flw_vertexPos.xyz, flywheel.cameraPos.xyz, flywheel.fogShape);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#use "flywheel:api/fragment.glsl"
|
#include "flywheel:api/fragment.glsl"
|
||||||
|
|
||||||
uniform sampler2D flw_diffuseTex;
|
uniform sampler2D flw_diffuseTex;
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
#use "flywheel:context/common.vert"
|
#include "flywheel:context/common.vert"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#use "flywheel:api/fragment.glsl"
|
#include "flywheel:api/fragment.glsl"
|
||||||
|
|
||||||
// optimize discard usage
|
// optimize discard usage
|
||||||
#ifdef ALPHA_DISCARD
|
#ifdef ALPHA_DISCARD
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
#use "flywheel:context/common.vert"
|
#include "flywheel:context/common.vert"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#use "flywheel:api/vertex.glsl"
|
#include "flywheel:api/vertex.glsl"
|
||||||
#use "flywheel:util/quaternion.glsl"
|
#include "flywheel:util/quaternion.glsl"
|
||||||
|
|
||||||
void flw_transformBoundingSphere(in FlwInstance i, inout vec3 center, inout float radius) {
|
void flw_transformBoundingSphere(in FlwInstance i, inout vec3 center, inout float radius) {
|
||||||
vec4 rotation = i.rotation;
|
vec4 rotation = i.rotation;
|
||||||
|
@ -9,7 +9,7 @@ void flw_transformBoundingSphere(in FlwInstance i, inout vec3 center, inout floa
|
||||||
center = rotateVertexByQuat(center - pivot, rotation) + pivot + pos;
|
center = rotateVertexByQuat(center - pivot, rotation) + pivot + pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef VERTEX_SHADER
|
#ifdef VERTEX_SHADER
|
||||||
void flw_instanceVertex(in FlwInstance i) {
|
void flw_instanceVertex(in FlwInstance i) {
|
||||||
flw_vertexPos = vec4(rotateVertexByQuat(flw_vertexPos.xyz - i.pivot, i.rotation) + i.pivot + i.position, 1.0);
|
flw_vertexPos = vec4(rotateVertexByQuat(flw_vertexPos.xyz - i.pivot, i.rotation) + i.pivot + i.position, 1.0);
|
||||||
flw_vertexNormal = rotateVertexByQuat(flw_vertexNormal, i.rotation);
|
flw_vertexNormal = rotateVertexByQuat(flw_vertexNormal, i.rotation);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#use "flywheel:api/vertex.glsl"
|
#include "flywheel:api/vertex.glsl"
|
||||||
|
|
||||||
void flw_transformBoundingSphere(in FlwInstance i, inout vec3 center, inout float radius) {
|
void flw_transformBoundingSphere(in FlwInstance i, inout vec3 center, inout float radius) {
|
||||||
mat4 pose = i.pose;
|
mat4 pose = i.pose;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#use "flywheel:api/fragment.glsl"
|
#include "flywheel:api/fragment.glsl"
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
flw_initFragment();
|
flw_initFragment();
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#define FLW_SUBGROUP_SIZE 32
|
#define FLW_SUBGROUP_SIZE 32
|
||||||
layout(local_size_x = FLW_SUBGROUP_SIZE) in;
|
layout(local_size_x = FLW_SUBGROUP_SIZE) in;
|
||||||
|
|
||||||
#use "flywheel:util/types.glsl"
|
#include "flywheel:util/types.glsl"
|
||||||
#use "flywheel:internal/indirect_draw_command.glsl"
|
#include "flywheel:internal/indirect_draw_command.glsl"
|
||||||
|
|
||||||
// populated by instancers
|
// populated by instancers
|
||||||
layout(std430, binding = 0) restrict readonly buffer ObjectBuffer {
|
layout(std430, binding = 0) restrict readonly buffer ObjectBuffer {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#use "flywheel:api/vertex.glsl"
|
#include "flywheel:api/vertex.glsl"
|
||||||
#use "flywheel:internal/indirect_draw_command.glsl"
|
#include "flywheel:internal/indirect_draw_command.glsl"
|
||||||
|
|
||||||
layout(std430, binding = 0) restrict readonly buffer ObjectBuffer {
|
layout(std430, binding = 0) restrict readonly buffer ObjectBuffer {
|
||||||
FlwPackedInstance objects[];
|
FlwPackedInstance objects[];
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#use "flywheel:util/types.glsl"
|
#include "flywheel:util/types.glsl"
|
||||||
|
|
||||||
struct MeshDrawCommand {
|
struct MeshDrawCommand {
|
||||||
uint indexCount;
|
uint indexCount;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#use "flywheel:api/vertex.glsl"
|
#include "flywheel:api/vertex.glsl"
|
||||||
|
|
||||||
uniform uvec2 _flw_materialID_instancing;
|
uniform uvec2 _flw_materialID_instancing;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#use "flywheel:api/vertex.glsl"
|
#include "flywheel:api/vertex.glsl"
|
||||||
|
|
||||||
layout(location = 0) in vec3 _flw_v_pos;
|
layout(location = 0) in vec3 _flw_v_pos;
|
||||||
layout(location = 1) in vec4 _flw_v_color;
|
layout(location = 1) in vec4 _flw_v_color;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#use "flywheel:api/vertex.glsl"
|
#include "flywheel:api/vertex.glsl"
|
||||||
|
|
||||||
layout(location = 0) in vec3 _flw_v_pos;
|
layout(location = 0) in vec3 _flw_v_pos;
|
||||||
layout(location = 1) in vec2 _flw_v_texCoord;
|
layout(location = 1) in vec2 _flw_v_texCoord;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#use "flywheel:api/fragment.glsl"
|
#include "flywheel:api/fragment.glsl"
|
||||||
#use "flywheel:util/fog.glsl"
|
#include "flywheel:util/fog.glsl"
|
||||||
|
|
||||||
void flw_materialFragment() {
|
void flw_materialFragment() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#use "flywheel:api/fragment.glsl"
|
#include "flywheel:api/fragment.glsl"
|
||||||
#use "flywheel:util/fog.glsl"
|
#include "flywheel:util/fog.glsl"
|
||||||
|
|
||||||
void flw_materialFragment() {
|
void flw_materialFragment() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#use "flywheel:api/vertex.glsl"
|
#include "flywheel:api/vertex.glsl"
|
||||||
|
|
||||||
void flw_materialVertex() {
|
void flw_materialVertex() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#use "flywheel:api/vertex.glsl"
|
#include "flywheel:api/vertex.glsl"
|
||||||
#use "flywheel:util/diffuse.glsl"
|
#include "flywheel:util/diffuse.glsl"
|
||||||
|
|
||||||
void flw_materialVertex() {
|
void flw_materialVertex() {
|
||||||
flw_vertexNormal = normalize(flw_vertexNormal);
|
flw_vertexNormal = normalize(flw_vertexNormal);
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
package com.jozufozu.flywheel.glsl;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.packs.resources.ResourceManager;
|
||||||
|
|
||||||
|
public class MockShaderSources extends ShaderSources {
|
||||||
|
private final Map<ResourceLocation, String> sources = new HashMap<>();
|
||||||
|
|
||||||
|
public MockShaderSources() {
|
||||||
|
super(ResourceManager.Empty.INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(ResourceLocation loc, String source) {
|
||||||
|
sources.put(loc, source);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
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 SourceFile.parse(this, loc, maybeFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoadResult assertLoaded(ResourceLocation loc) {
|
||||||
|
Assertions.assertTrue(cache.containsKey(loc), "Expected " + loc + " to be cached");
|
||||||
|
return cache.get(loc);
|
||||||
|
}
|
||||||
|
}
|
67
src/test/java/com/jozufozu/flywheel/glsl/TestBase.java
Normal file
67
src/test/java/com/jozufozu/flywheel/glsl/TestBase.java
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package com.jozufozu.flywheel.glsl;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
public class TestBase {
|
||||||
|
public static final ResourceLocation FLW_A = Flywheel.rl("a.glsl");
|
||||||
|
public static final ResourceLocation FLW_B = Flywheel.rl("b.glsl");
|
||||||
|
public static final ResourceLocation FLW_C = Flywheel.rl("c.glsl");
|
||||||
|
|
||||||
|
public static <T> T assertSingletonList(List<T> list) {
|
||||||
|
assertEquals(1, list.size());
|
||||||
|
return list.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static <E extends LoadError> E findAndAssertError(Class<E> clazz, MockShaderSources sources, ResourceLocation loc) {
|
||||||
|
var result = sources.find(loc);
|
||||||
|
var failure = assertInstanceOf(LoadResult.Failure.class, result);
|
||||||
|
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);
|
||||||
|
|
||||||
|
var pair = assertSingletonList(includeError.innerErrors());
|
||||||
|
for (int i = 1; i < depth; i++) {
|
||||||
|
includeError = assertInstanceOf(LoadError.IncludeError.class, pair.second());
|
||||||
|
pair = assertSingletonList(includeError.innerErrors());
|
||||||
|
}
|
||||||
|
return assertInstanceOf(finalErrType, pair.second());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static SourceFile findAndAssertSuccess(MockShaderSources sources, ResourceLocation loc) {
|
||||||
|
var result = sources.find(loc);
|
||||||
|
return assertSuccessAndUnwrap(loc, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static SourceFile assertSuccessAndUnwrap(ResourceLocation expectedName, LoadResult result) {
|
||||||
|
assertInstanceOf(LoadResult.Success.class, result);
|
||||||
|
|
||||||
|
var file = result.unwrap();
|
||||||
|
assertNotNull(file);
|
||||||
|
assertEquals(expectedName, file.name);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.jozufozu.flywheel.glsl;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import com.jozufozu.flywheel.glsl.error.ErrorBuilder;
|
||||||
|
|
||||||
|
public class TestErrorMessages extends TestBase {
|
||||||
|
@BeforeAll
|
||||||
|
static void disableConsoleColors() {
|
||||||
|
ErrorBuilder.CONSOLE_COLORS = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMissingIncludeMsg() {
|
||||||
|
var sources = new MockShaderSources();
|
||||||
|
sources.add(FLW_A, """
|
||||||
|
#include "flywheel:b.glsl"
|
||||||
|
""");
|
||||||
|
|
||||||
|
var aErr = assertErrorAndGetMessage(sources, FLW_A);
|
||||||
|
|
||||||
|
assertEquals("""
|
||||||
|
error: could not load shader due to errors in included files
|
||||||
|
--> 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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
package com.jozufozu.flywheel.glsl;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.jozufozu.flywheel.glsl.parse.Import;
|
||||||
|
|
||||||
|
public class TestShaderSourceLoading extends TestBase {
|
||||||
|
@Test
|
||||||
|
void testSimpleFind() {
|
||||||
|
var sources = new MockShaderSources();
|
||||||
|
sources.add(FLW_A, "");
|
||||||
|
|
||||||
|
SourceFile file = findAndAssertSuccess(sources, FLW_A);
|
||||||
|
|
||||||
|
assertEquals("", file.finalSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMissingFileAtRoot() {
|
||||||
|
var sources = new MockShaderSources();
|
||||||
|
findAndAssertError(LoadError.IOError.class, sources, FLW_A);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMissingInclude() {
|
||||||
|
var sources = new MockShaderSources();
|
||||||
|
sources.add(FLW_A, """
|
||||||
|
#include "flywheel:b.glsl"
|
||||||
|
""");
|
||||||
|
|
||||||
|
var aErr = findAndAssertError(LoadError.IncludeError.class, sources, FLW_A);
|
||||||
|
|
||||||
|
var ioErr = assertSimpleNestedErrorsToDepth(LoadError.IOError.class, aErr, 1);
|
||||||
|
assertEquals(FLW_B, ioErr.location());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBasicInclude() {
|
||||||
|
var sources = new MockShaderSources();
|
||||||
|
sources.add(FLW_A, """
|
||||||
|
#include "flywheel:b.glsl"
|
||||||
|
""");
|
||||||
|
sources.add(FLW_B, "");
|
||||||
|
|
||||||
|
SourceFile a = findAndAssertSuccess(sources, FLW_A);
|
||||||
|
sources.assertLoaded(FLW_B);
|
||||||
|
|
||||||
|
var includeB = assertSingletonList(a.imports);
|
||||||
|
assertEquals(FLW_B.toString(), includeB.file()
|
||||||
|
.toString());
|
||||||
|
|
||||||
|
assertEquals("""
|
||||||
|
|
||||||
|
""", a.finalSource, "Include statements should be elided.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRedundantInclude() {
|
||||||
|
var sources = new MockShaderSources();
|
||||||
|
sources.add(FLW_A, """
|
||||||
|
#include "flywheel:b.glsl"
|
||||||
|
#include "flywheel:b.glsl"
|
||||||
|
""");
|
||||||
|
sources.add(FLW_B, "");
|
||||||
|
|
||||||
|
SourceFile a = findAndAssertSuccess(sources, FLW_A);
|
||||||
|
sources.assertLoaded(FLW_B);
|
||||||
|
|
||||||
|
assertEquals(2, a.imports.size());
|
||||||
|
for (Import include : a.imports) {
|
||||||
|
assertEquals(FLW_B.toString(), include.file()
|
||||||
|
.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("""
|
||||||
|
|
||||||
|
|
||||||
|
""", a.finalSource, "Both include statements should be elided.");
|
||||||
|
|
||||||
|
LoadResult bResult = sources.assertLoaded(FLW_B);
|
||||||
|
SourceFile b = assertSuccessAndUnwrap(FLW_B, bResult);
|
||||||
|
|
||||||
|
assertEquals(ImmutableList.of(b), a.included);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSelfInclude() {
|
||||||
|
var sources = new MockShaderSources();
|
||||||
|
sources.add(FLW_A, """
|
||||||
|
#include "flywheel:a.glsl"
|
||||||
|
""");
|
||||||
|
|
||||||
|
var aErr = findAndAssertError(LoadError.IncludeError.class, sources, FLW_A);
|
||||||
|
|
||||||
|
var shouldBeRecursiveIncludePair = assertSingletonList(aErr.innerErrors());
|
||||||
|
|
||||||
|
var circularDependency = assertInstanceOf(LoadError.CircularDependency.class, shouldBeRecursiveIncludePair.second());
|
||||||
|
assertEquals(ImmutableList.of(FLW_A, FLW_A), circularDependency.stack());
|
||||||
|
assertEquals(FLW_A, circularDependency.offender());
|
||||||
|
|
||||||
|
assertEquals(FLW_A.toString(), shouldBeRecursiveIncludePair.first()
|
||||||
|
.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void test2LayerCircularDependency() {
|
||||||
|
var sources = new MockShaderSources();
|
||||||
|
sources.add(FLW_A, """
|
||||||
|
#include "flywheel:b.glsl"
|
||||||
|
""");
|
||||||
|
sources.add(FLW_B, """
|
||||||
|
#include "flywheel:a.glsl"
|
||||||
|
""");
|
||||||
|
|
||||||
|
var aErr = findAndAssertError(LoadError.IncludeError.class, sources, FLW_A);
|
||||||
|
sources.assertLoaded(FLW_B);
|
||||||
|
|
||||||
|
var recursiveInclude = assertSimpleNestedErrorsToDepth(LoadError.CircularDependency.class, aErr, 2);
|
||||||
|
assertEquals(ImmutableList.of(FLW_A, FLW_B, FLW_A), recursiveInclude.stack());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void test3LayerCircularDependency() {
|
||||||
|
var sources = new MockShaderSources();
|
||||||
|
sources.add(FLW_A, """
|
||||||
|
#include "flywheel:b.glsl"
|
||||||
|
""");
|
||||||
|
sources.add(FLW_B, """
|
||||||
|
#include "flywheel:c.glsl"
|
||||||
|
""");
|
||||||
|
sources.add(FLW_C, """
|
||||||
|
#include "flywheel:a.glsl"
|
||||||
|
""");
|
||||||
|
|
||||||
|
var aErr = findAndAssertError(LoadError.IncludeError.class, sources, FLW_A);
|
||||||
|
sources.assertLoaded(FLW_B);
|
||||||
|
sources.assertLoaded(FLW_C);
|
||||||
|
|
||||||
|
var recursiveInclude = assertSimpleNestedErrorsToDepth(LoadError.CircularDependency.class, aErr, 3);
|
||||||
|
assertEquals(ImmutableList.of(FLW_A, FLW_B, FLW_C, FLW_A), recursiveInclude.stack());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue