GL32 shaders and errors

- Update shaders to glsl 150
 - Objectfy errors
This commit is contained in:
Jozufozu 2021-09-20 19:27:04 -07:00
parent 5ce960c5d5
commit 0663218b67
27 changed files with 404 additions and 257 deletions

View file

@ -1,8 +1,12 @@
package com.jozufozu.flywheel.backend.gl; package com.jozufozu.flywheel.backend.gl;
public enum GLSLVersion { public enum GLSLVersion {
@Deprecated
V110(110), V110(110),
@Deprecated
V120(120), V120(120),
V150(150),
V330(330),
; ;
public final int version; public final int version;

View file

@ -29,14 +29,14 @@ public class InstancingProgramMetaData {
Optional<ShaderFunction> vertexFunc = file.findFunction("vertex"); Optional<ShaderFunction> vertexFunc = file.findFunction("vertex");
Optional<ShaderFunction> fragmentFunc = file.findFunction("fragment"); Optional<ShaderFunction> fragmentFunc = file.findFunction("fragment");
if (!fragmentFunc.isPresent()) { if (fragmentFunc.isEmpty()) {
ErrorReporter.generateMissingFunction(file, "fragment", "\"fragment\" function not defined"); ErrorReporter.generateMissingFunction(file, "fragment", "\"fragment\" function not defined");
} }
if (!vertexFunc.isPresent()) { if (vertexFunc.isEmpty()) {
ErrorReporter.generateFileError(file, "could not find \"vertex\" function"); ErrorReporter.generateFileError(file, "could not find \"vertex\" function");
} }
if (!fragmentFunc.isPresent() || !vertexFunc.isPresent()) { if (fragmentFunc.isEmpty() || vertexFunc.isEmpty()) {
throw new ShaderLoadingException(); throw new ShaderLoadingException();
} }
@ -64,19 +64,19 @@ public class InstancingProgramMetaData {
Optional<ShaderStruct> maybeVertex = file.findStruct(vertexName); Optional<ShaderStruct> maybeVertex = file.findStruct(vertexName);
Optional<ShaderStruct> maybeInstance = file.findStruct(instanceName); Optional<ShaderStruct> maybeInstance = file.findStruct(instanceName);
if (!maybeVertex.isPresent()) { if (maybeVertex.isEmpty()) {
ErrorReporter.generateMissingStruct(file, vertexName, "struct not defined"); ErrorReporter.generateMissingStruct(file, vertexName, "struct not defined");
} }
if (!maybeInterpolant.isPresent()) { if (maybeInterpolant.isEmpty()) {
ErrorReporter.generateMissingStruct(file, interpolantName, "struct not defined"); ErrorReporter.generateMissingStruct(file, interpolantName, "struct not defined");
} }
if (!maybeInstance.isPresent()) { if (maybeInstance.isEmpty()) {
ErrorReporter.generateMissingStruct(file, instanceName, "struct not defined"); ErrorReporter.generateMissingStruct(file, instanceName, "struct not defined");
} }
if (!maybeVertex.isPresent() || !maybeInterpolant.isPresent() || !maybeInstance.isPresent()) { if (maybeVertex.isEmpty() || maybeInterpolant.isEmpty() || maybeInstance.isEmpty()) {
throw new ShaderLoadingException(); throw new ShaderLoadingException();
} }

View file

@ -37,9 +37,9 @@ public class InstancingTemplate extends Template<InstancingProgramMetaData> {
public void vertexFooter(StringBuilder template, SourceFile file) { public void vertexFooter(StringBuilder template, SourceFile file) {
InstancingProgramMetaData data = getMetadata(file); InstancingProgramMetaData data = getMetadata(file);
Template.prefixFields(template, data.vertex, "attribute", "a_v_"); Template.prefixFields(template, data.vertex, "in", "a_v_");
Template.prefixFields(template, data.instance, "attribute", "a_i_"); Template.prefixFields(template, data.instance, "in", "a_i_");
Template.prefixFields(template, data.interpolant, "varying", "v2f_"); Template.prefixFields(template, data.interpolant, "out", "v2f_");
template.append("void main() {\n"); template.append("void main() {\n");
template.append(data.vertexName) template.append(data.vertexName)
@ -63,7 +63,7 @@ public class InstancingTemplate extends Template<InstancingProgramMetaData> {
public void fragmentFooter(StringBuilder template, SourceFile file) { public void fragmentFooter(StringBuilder template, SourceFile file) {
InstancingProgramMetaData data = getMetadata(file); InstancingProgramMetaData data = getMetadata(file);
Template.prefixFields(template, data.interpolant, "varying", "v2f_"); Template.prefixFields(template, data.interpolant, "in", "v2f_");
template.append("void main() {\n"); template.append("void main() {\n");
template.append(data.interpolantName) template.append(data.interpolantName)

View file

@ -26,15 +26,15 @@ public class OneShotProgramMetaData {
Optional<ShaderFunction> maybeVertexMain = file.findFunction("vertex"); Optional<ShaderFunction> maybeVertexMain = file.findFunction("vertex");
Optional<ShaderFunction> maybeFragmentMain = file.findFunction("fragment"); Optional<ShaderFunction> maybeFragmentMain = file.findFunction("fragment");
if (!maybeVertexMain.isPresent()) { if (maybeVertexMain.isEmpty()) {
ErrorReporter.generateFileError(file, "could not find \"vertex\" function"); ErrorReporter.generateFileError(file, "could not find \"vertex\" function");
} }
if (!maybeFragmentMain.isPresent()) { if (maybeFragmentMain.isEmpty()) {
ErrorReporter.generateMissingFunction(file, "fragment", "\"fragment\" function not defined"); ErrorReporter.generateMissingFunction(file, "fragment", "\"fragment\" function not defined");
} }
if (!maybeVertexMain.isPresent() || !maybeFragmentMain.isPresent()) { if (maybeVertexMain.isEmpty() || maybeFragmentMain.isEmpty()) {
throw new RuntimeException(); throw new RuntimeException();
} }
@ -62,13 +62,13 @@ public class OneShotProgramMetaData {
Optional<ShaderStruct> maybeInterpolant = file.findStruct(interpolantName); Optional<ShaderStruct> maybeInterpolant = file.findStruct(interpolantName);
Optional<ShaderStruct> maybeVertex = file.findStruct(vertexName); Optional<ShaderStruct> maybeVertex = file.findStruct(vertexName);
if (!maybeVertex.isPresent()) if (maybeVertex.isEmpty())
ErrorReporter.generateMissingStruct(file, vertexName, "struct not defined"); ErrorReporter.generateMissingStruct(file, vertexName, "struct not defined");
if (!maybeInterpolant.isPresent()) if (maybeInterpolant.isEmpty())
ErrorReporter.generateMissingStruct(file, interpolantName, "struct not defined"); ErrorReporter.generateMissingStruct(file, interpolantName, "struct not defined");
if (!maybeVertex.isPresent() || !maybeInterpolant.isPresent()) { if (maybeVertex.isEmpty() || maybeInterpolant.isEmpty()) {
throw new RuntimeException(); throw new RuntimeException();
} }

View file

@ -32,8 +32,8 @@ public class OneShotTemplate extends Template<OneShotProgramMetaData> {
public void vertexFooter(StringBuilder template, SourceFile file) { public void vertexFooter(StringBuilder template, SourceFile file) {
OneShotProgramMetaData data = getMetadata(file); OneShotProgramMetaData data = getMetadata(file);
Template.prefixFields(template, data.vertex, "attribute", "a_v_"); Template.prefixFields(template, data.vertex, "in", "a_v_");
Template.prefixFields(template, data.interpolant, "varying", "v2f_"); Template.prefixFields(template, data.interpolant, "out", "v2f_");
template.append("void main() {\n"); template.append("void main() {\n");
template.append(data.vertexName) template.append(data.vertexName)
@ -53,7 +53,7 @@ public class OneShotTemplate extends Template<OneShotProgramMetaData> {
public void fragmentFooter(StringBuilder template, SourceFile file) { public void fragmentFooter(StringBuilder template, SourceFile file) {
OneShotProgramMetaData data = getMetadata(file); OneShotProgramMetaData data = getMetadata(file);
Template.prefixFields(template, data.interpolant, "varying", "v2f_"); Template.prefixFields(template, data.interpolant, "in", "v2f_");
template.append("void main() {\n"); template.append("void main() {\n");
template.append(data.interpolant.name) template.append(data.interpolant.name)

View file

@ -25,10 +25,10 @@ public abstract class Template<D> {
private final Map<SourceFile, D> metadata = new HashMap<>(); private final Map<SourceFile, D> metadata = new HashMap<>();
private final Function<SourceFile, D> parser; private final Function<SourceFile, D> reader;
protected Template(Function<SourceFile, D> parser) { protected Template(Function<SourceFile, D> reader) {
this.parser = parser; this.reader = reader;
} }
/** /**
@ -46,11 +46,12 @@ public abstract class Template<D> {
public abstract Collection<ShaderInput> getShaderInputs(SourceFile file); public abstract Collection<ShaderInput> getShaderInputs(SourceFile file);
public D getMetadata(SourceFile file) { public D getMetadata(SourceFile file) {
return metadata.computeIfAbsent(file, parser); // lazily read files, cache results
return metadata.computeIfAbsent(file, reader);
} }
public GLSLVersion getVersion() { public GLSLVersion getVersion() {
return GLSLVersion.V120; return GLSLVersion.V150;
} }
public static void prefixFields(StringBuilder builder, ShaderStruct struct, String qualifier, String prefix) { public static void prefixFields(StringBuilder builder, ShaderStruct struct, String qualifier, String prefix) {

View file

@ -57,7 +57,7 @@ public class WorldShader {
.append(template.getVersion()) .append(template.getVersion())
.append('\n') .append('\n')
.append("#define ") .append("#define ")
.append(type.define) .append(type.define) // special case shader type declaration
.append('\n') .append('\n')
.append(defines != null ? defines : "") .append(defines != null ? defines : "")
.append(header) .append(header)

View file

@ -67,11 +67,10 @@ public class FileResolution {
try { try {
file = sources.findSource(fileLoc); file = sources.findSource(fileLoc);
} catch (RuntimeException error) { } catch (RuntimeException error) {
ErrorBuilder builder = new ErrorBuilder(); ErrorBuilder builder = ErrorBuilder.error(String.format("could not find source for file %s", fileLoc));
builder.error(String.format("could not find source for file %s", fileLoc));
// print the location of all places where this file was referenced // print the location of all places where this file was referenced
for (Span span : foundSpans) { for (Span span : foundSpans) {
builder.in(span.getSourceFile()) builder.pointAtFile(span.getSourceFile())
.pointAt(span, 2); .pointAt(span, 2);
} }
Backend.log.error(builder.build()); Backend.log.error(builder.build());

View file

@ -27,8 +27,8 @@ public class Index {
Collection<SourceFile> files = sources.values(); Collection<SourceFile> files = sources.values();
for (SourceFile file : files) { for (SourceFile file : files) {
file.getStructs().forEach(knownStructs::put); file.structs.forEach(knownStructs::put);
file.getFunctions().forEach(knownFunctions::put); file.functions.forEach(knownFunctions::put);
} }
} }

View file

@ -13,80 +13,65 @@ import com.google.common.collect.ImmutableMap;
import com.jozufozu.flywheel.backend.source.parse.Import; import com.jozufozu.flywheel.backend.source.parse.Import;
import com.jozufozu.flywheel.backend.source.parse.ShaderFunction; import com.jozufozu.flywheel.backend.source.parse.ShaderFunction;
import com.jozufozu.flywheel.backend.source.parse.ShaderStruct; import com.jozufozu.flywheel.backend.source.parse.ShaderStruct;
import com.jozufozu.flywheel.backend.source.span.CharPos;
import com.jozufozu.flywheel.backend.source.span.ErrorSpan; import com.jozufozu.flywheel.backend.source.span.ErrorSpan;
import com.jozufozu.flywheel.backend.source.span.Span; import com.jozufozu.flywheel.backend.source.span.Span;
import com.jozufozu.flywheel.backend.source.span.StringSpan; import com.jozufozu.flywheel.backend.source.span.StringSpan;
import com.jozufozu.flywheel.util.StringUtil;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
/**
* Immutable class representing a shader file.
*
* <p>
* This class parses shader files and generates what is effectively a high level AST of the source.
* </p>
*/
public class SourceFile { public class SourceFile {
private static final Pattern includePattern = Pattern.compile("#use \"(.*)\""); private static final Pattern includePattern = Pattern.compile("#use \"(.*)\"");
private static final Pattern newLine = Pattern.compile("(\\r\\n|\\r|\\n)");
// https://regexr.com/60n3d // https://regexr.com/60n3d
public static final Pattern functionDeclaration = Pattern.compile("(\\w+)\\s+(\\w+)\\s*\\(([\\w,\\s]*)\\)\\s*\\{"); public static final Pattern functionDeclaration = Pattern.compile("(\\w+)\\s+(\\w+)\\s*\\(([\\w,\\s]*)\\)\\s*\\{");
public final ResourceLocation name; public final ResourceLocation name;
public final ShaderSources parent; public final ShaderSources parent;
private final String source; public final String source;
private final CharSequence elided; /**
private final ImmutableList<String> lines; * Sections of the source that must be trimmed for compilation. Currently, it only contains the spans of imports.
*/
public final String elided;
private final IntList lineStarts; public final SourceLines lines;
// Function name -> Function object /**
private final ImmutableMap<String, ShaderFunction> functions; * Function lookup by name.
private final ImmutableMap<String, ShaderStruct> structs; */
public final ImmutableMap<String, ShaderFunction> functions;
// Includes ordered as defined in the source /**
private final ImmutableList<Import> imports; * Struct lookup by name.
*/
public final ImmutableMap<String, ShaderStruct> structs;
// Sections of the source that must be trimmed for compilation. /**
private final List<Span> elisions = new ArrayList<>(); * Includes ordered as defined in the source.
*/
public final ImmutableList<Import> imports;
public SourceFile(ShaderSources parent, ResourceLocation name, String source) { public SourceFile(ShaderSources parent, ResourceLocation name, String source) {
this.parent = parent; this.parent = parent;
this.name = name; this.name = name;
this.source = source; this.source = source;
this.lineStarts = createLineStarts(); this.lines = new SourceLines(source);
this.lines = createLineList(lineStarts);
this.imports = parseImports(); List<Span> elisions = new ArrayList<>();
this.imports = parseImports(elisions);
this.functions = parseFunctions(); this.functions = parseFunctions();
this.structs = parseStructs(); this.structs = parseStructs();
this.elided = createElidedSource(); this.elided = elideSource(source, elisions).toString();
}
public String getSource() {
return source;
}
public CharSequence getElidedSource() {
return elided;
}
public ShaderSources getParent() {
return parent;
}
public ImmutableMap<String, ShaderFunction> getFunctions() {
return functions;
}
public ImmutableMap<String, ShaderStruct> getStructs() {
return structs;
}
public ImmutableList<Import> getIncludes() {
return imports;
} }
/** /**
@ -96,11 +81,11 @@ public class SourceFile {
* @return null if no definition matches the name. * @return null if no definition matches the name.
*/ */
public Optional<ShaderStruct> findStruct(CharSequence name) { public Optional<ShaderStruct> findStruct(CharSequence name) {
ShaderStruct struct = getStructs().get(name.toString()); ShaderStruct struct = structs.get(name.toString());
if (struct != null) return Optional.of(struct); if (struct != null) return Optional.of(struct);
for (Import include : getIncludes()) { for (Import include : imports) {
Optional<ShaderStruct> externalStruct = include.getOptional() Optional<ShaderStruct> externalStruct = include.getOptional()
.flatMap(file -> file.findStruct(name)); .flatMap(file -> file.findStruct(name));
@ -117,11 +102,11 @@ public class SourceFile {
* @return Optional#empty() if no definition matches the name. * @return Optional#empty() if no definition matches the name.
*/ */
public Optional<ShaderFunction> findFunction(CharSequence name) { public Optional<ShaderFunction> findFunction(CharSequence name) {
ShaderFunction local = getFunctions().get(name.toString()); ShaderFunction local = functions.get(name.toString());
if (local != null) return Optional.of(local); if (local != null) return Optional.of(local);
for (Import include : getIncludes()) { for (Import include : imports) {
Optional<ShaderFunction> external = include.getOptional() Optional<ShaderFunction> external = include.getOptional()
.flatMap(file -> file.findFunction(name)); .flatMap(file -> file.findFunction(name));
@ -142,29 +127,12 @@ public class SourceFile {
} }
public void generateFinalSource(StringBuilder source) { public void generateFinalSource(StringBuilder source) {
for (Import include : getIncludes()) { for (Import include : imports) {
SourceFile file = include.getFile(); SourceFile file = include.getFile();
if (file != null) file.generateFinalSource(source); if (file != null) file.generateFinalSource(source);
} }
source.append(getElidedSource()); source.append(elided);
}
public CharPos getCharPos(int charPos) {
int lineNo = 0;
for (; lineNo < lineStarts.size(); lineNo++) {
int ls = lineStarts.getInt(lineNo);
if (charPos < ls) {
break;
}
}
lineNo -= 1;
int lineStart = lineStarts.getInt(lineNo);
return new CharPos(charPos, lineNo, charPos - lineStart);
} }
public String printSource() { public String printSource() {
@ -172,18 +140,13 @@ public class SourceFile {
builder.append("Source for shader '") builder.append("Source for shader '")
.append(name) .append(name)
.append("':\n"); .append("':\n")
int i = 1; .append(lines.printLinesWithNumbers());
for (String s : lines) {
builder.append(String.format("%1$4s: ", i++))
.append(s)
.append('\n');
}
return builder.toString(); return builder.toString();
} }
private CharSequence createElidedSource() { private static CharSequence elideSource(String source, List<Span> elisions) {
StringBuilder out = new StringBuilder(); StringBuilder out = new StringBuilder();
int lastEnd = 0; int lastEnd = 0;
@ -199,34 +162,6 @@ public class SourceFile {
return out; return out;
} }
/**
* Scan the source for line breaks, recording the position of the first character of each line.
*/
private IntList createLineStarts() {
IntList l = new IntArrayList();
l.add(0); // first line is always at position 0
Matcher matcher = newLine.matcher(source);
while (matcher.find()) {
l.add(matcher.end());
}
return l;
}
private ImmutableList<String> createLineList(IntList lines) {
ImmutableList.Builder<String> builder = ImmutableList.builder();
for (int i = 1; i < lines.size(); i++) {
int start = lines.getInt(i - 1);
int end = lines.getInt(i);
builder.add(StringUtil.trimEnd(source.substring(start, end)));
}
return builder.build();
}
/** /**
* Scan the source for function definitions and "parse" them into objects that contain properties of the function. * Scan the source for function definitions and "parse" them into objects that contain properties of the function.
*/ */
@ -284,8 +219,9 @@ public class SourceFile {
/** /**
* Scan the source for <code>#use "..."</code> directives. * Scan the source for <code>#use "..."</code> directives.
* Records the contents of the directive into an {@link Import} object, and marks the directive for elision. * Records the contents of the directive into an {@link Import} object, and marks the directive for elision.
* @param elisions
*/ */
private ImmutableList<Import> parseImports() { private ImmutableList<Import> parseImports(List<Span> elisions) {
Matcher uses = includePattern.matcher(source); Matcher uses = includePattern.matcher(source);
List<Import> imports = new ArrayList<>(); List<Import> imports = new ArrayList<>();
@ -321,11 +257,4 @@ public class SourceFile {
return -1; return -1;
} }
public int getLineCount() {
return lines.size();
}
public CharSequence getLine(int lineNo) {
return lines.get(lineNo);
}
} }

View file

@ -0,0 +1,97 @@
package com.jozufozu.flywheel.backend.source;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.source.span.CharPos;
import com.jozufozu.flywheel.util.StringUtil;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
public class SourceLines {
private static final Pattern newLine = Pattern.compile("(\\r\\n|\\r|\\n)");
/**
* 0-indexed line to char pos mapping.
*/
private final IntList lineStarts;
/**
* 0-indexed line lookup
*/
private final ImmutableList<String> lines;
public SourceLines(String source) {
this.lineStarts = createLineLookup(source);
this.lines = getLines(source, lineStarts);
}
/**
* Scan the source for line breaks, recording the position of the first character of each line.
* @param source
*/
private static IntList createLineLookup(String source) {
IntList l = new IntArrayList();
l.add(0); // first line is always at position 0
Matcher matcher = newLine.matcher(source);
while (matcher.find()) {
l.add(matcher.end());
}
return l;
}
private static ImmutableList<String> getLines(String source, IntList lines) {
ImmutableList.Builder<String> builder = ImmutableList.builder();
for (int i = 1; i < lines.size(); i++) {
int start = lines.getInt(i - 1);
int end = lines.getInt(i);
builder.add(StringUtil.trimEnd(source.substring(start, end)));
}
return builder.build();
}
public CharPos getCharPos(int charPos) {
int lineNo = 0;
for (; lineNo < lineStarts.size(); lineNo++) {
int ls = lineStarts.getInt(lineNo);
if (charPos < ls) {
break;
}
}
lineNo -= 1;
int lineStart = lineStarts.getInt(lineNo);
return new CharPos(charPos, lineNo, charPos - lineStart);
}
public int getLineCount() {
return lines.size();
}
public String getLine(int lineNo) {
return lines.get(lineNo);
}
public String printLinesWithNumbers() {
StringBuilder builder = new StringBuilder();
for (int i = 0, linesSize = lines.size(); i < linesSize; i++) {
builder.append(String.format("%1$4s: ", i + 1))
.append(lines.get(i))
.append('\n');
}
return builder.toString();
}
}

View file

@ -1,95 +1,79 @@
package com.jozufozu.flywheel.backend.source.error; package com.jozufozu.flywheel.backend.source.error;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.source.SourceFile; import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.backend.source.SourceLines;
import com.jozufozu.flywheel.backend.source.error.lines.ErrorLine;
import com.jozufozu.flywheel.backend.source.error.lines.FileLine;
import com.jozufozu.flywheel.backend.source.error.lines.HeaderLine;
import com.jozufozu.flywheel.backend.source.error.lines.SourceLine;
import com.jozufozu.flywheel.backend.source.error.lines.SpanHighlightLine;
import com.jozufozu.flywheel.backend.source.span.Span; import com.jozufozu.flywheel.backend.source.span.Span;
import com.jozufozu.flywheel.util.FlwUtil; import com.jozufozu.flywheel.util.FlwUtil;
public class ErrorBuilder { public class ErrorBuilder {
private final StringBuilder internal = new StringBuilder(); private final List<ErrorLine> lines = new ArrayList<>();
public ErrorBuilder error(CharSequence msg) { private final Level level;
internal.append("error: ")
.append(msg); public ErrorBuilder(Level level, CharSequence msg) {
return endLine(); this.level = level;
lines.add(new HeaderLine(level.toString(), msg));
} }
public ErrorBuilder note(CharSequence msg) { public static ErrorBuilder error(CharSequence msg) {
internal.append("note: ") return new ErrorBuilder(Level.ERROR, msg);
.append(msg);
return endLine();
} }
public ErrorBuilder hint(CharSequence msg) { public static ErrorBuilder warn(CharSequence msg) {
internal.append("hint: ") return new ErrorBuilder(Level.WARN, msg);
.append(msg);
return endLine();
} }
public ErrorBuilder in(SourceFile file) { public ErrorBuilder pointAtFile(SourceFile file) {
internal.append("--> ") lines.add(new FileLine(file.name.toString()));
.append(file.name);
return endLine();
}
public ErrorBuilder numberedLine(int no, CharSequence content) {
return line(String.valueOf(no), content);
}
public ErrorBuilder numberedLine(int no, int width, CharSequence content) {
String fmt = "%1$" + width + 's';
return line(String.format(fmt, no), content);
}
public ErrorBuilder line(CharSequence leftColumn, CharSequence rightColumn) {
internal.append(leftColumn)
.append(" | ")
.append(rightColumn);
return endLine();
}
public ErrorBuilder endLine() {
internal.append('\n');
return this; return this;
} }
public ErrorBuilder hintIncludeFor(@Nullable Span span, CharSequence msg) { public ErrorBuilder hintIncludeFor(@Nullable Span span, CharSequence msg) {
if (span == null) return this; if (span == null) return this;
hint("add " + span.getSourceFile().importStatement() + " " + msg) String builder = "add " + span.getSourceFile()
.in(span.getSourceFile()) .importStatement() + ' ' + msg;
.pointAt(span, 1);
return this; lines.add(new HeaderLine("hint", builder));
return this.pointAtFile(span.getSourceFile())
.pointAt(span, 1);
} }
public ErrorBuilder pointAt(Span span, int ctxLines) { public ErrorBuilder pointAt(Span span, int ctxLines) {
SourceFile file = span.getSourceFile();
if (span.lines() == 1) { if (span.lines() == 1) {
SourceLines lines = span.getSourceFile().lines;
int spanLine = span.firstLine(); int spanLine = span.firstLine();
int firstLine = Math.max(0, spanLine - ctxLines); int firstLine = Math.max(0, spanLine - ctxLines);
int lastLine = Math.min(file.getLineCount(), spanLine + ctxLines); int lastLine = Math.min(lines.getLineCount(), spanLine + ctxLines);
int digits = FlwUtil.numDigits(lastLine); int firstCol = span.getStart()
.col();
int firstCol = span.getStart().getCol(); int lastCol = span.getEnd()
int lastCol = span.getEnd().getCol(); .col();
for (int i = firstLine; i <= lastLine; i++) { for (int i = firstLine; i <= lastLine; i++) {
CharSequence line = file.getLine(i); CharSequence line = lines.getLine(i);
numberedLine(i + 1, digits, line); this.lines.add(SourceLine.numbered(i + 1, line.toString()));
if (i == spanLine) { if (i == spanLine) {
line(FlwUtil.repeatChar(' ', digits), generateUnderline(firstCol, lastCol)); this.lines.add(new SpanHighlightLine(firstCol, lastCol));
} }
} }
} }
@ -98,19 +82,23 @@ public class ErrorBuilder {
} }
public CharSequence build() { public CharSequence build() {
return internal;
int maxLength = -1;
for (ErrorLine line : lines) {
int length = line.neededMargin();
if (length > maxLength) maxLength = length;
} }
public CharSequence generateUnderline(int firstCol, int lastCol) { StringBuilder builder = new StringBuilder();
StringBuilder line = new StringBuilder(lastCol); for (ErrorLine line : lines) {
for (int i = 0; i < firstCol; i++) { int length = line.neededMargin();
line.append(' ');
builder.append(FlwUtil.repeatChar(' ', maxLength - length))
.append(line.build())
.append('\n');
} }
for (int i = firstCol; i < lastCol; i++) { return builder;
line.append('^');
}
return line;
} }
} }

View file

@ -13,10 +13,8 @@ public class ErrorReporter {
public static void generateSpanError(Span span, String message) { public static void generateSpanError(Span span, String message) {
SourceFile file = span.getSourceFile(); SourceFile file = span.getSourceFile();
ErrorBuilder builder = new ErrorBuilder(); CharSequence error = ErrorBuilder.error(message)
.pointAtFile(file)
CharSequence error = builder.error(message)
.in(file)
.pointAt(span, 2) .pointAt(span, 2)
.build(); .build();
@ -25,10 +23,8 @@ public class ErrorReporter {
public static void generateFileError(SourceFile file, String message) { public static void generateFileError(SourceFile file, String message) {
ErrorBuilder builder = new ErrorBuilder(); CharSequence error = ErrorBuilder.error(message)
.pointAtFile(file)
CharSequence error = builder.error(message)
.in(file)
.build(); .build();
Backend.log.error(error); Backend.log.error(error);
@ -43,10 +39,9 @@ public class ErrorReporter {
.stream() .stream()
.findFirst() .findFirst()
.map(ShaderStruct::getName); .map(ShaderStruct::getName);
ErrorBuilder builder = new ErrorBuilder();
ErrorBuilder error = builder.error(msg) ErrorBuilder error = ErrorBuilder.error(msg)
.in(file) .pointAtFile(file)
.pointAt(vertexName, 2) .pointAt(vertexName, 2)
.hintIncludeFor(span.orElse(null), hint); .hintIncludeFor(span.orElse(null), hint);
@ -62,10 +57,9 @@ public class ErrorReporter {
.stream() .stream()
.findFirst() .findFirst()
.map(ShaderFunction::getName); .map(ShaderFunction::getName);
ErrorBuilder builder = new ErrorBuilder();
ErrorBuilder error = builder.error(msg) ErrorBuilder error = ErrorBuilder.error(msg)
.in(file) .pointAtFile(file)
.hintIncludeFor(span.orElse(null), hint); .hintIncludeFor(span.orElse(null), hint);
Backend.log.error(error.build()); Backend.log.error(error.build());

View file

@ -0,0 +1,18 @@
package com.jozufozu.flywheel.backend.source.error;
public enum Level {
WARN("warn"),
ERROR("error"),
;
private final String error;
Level(String error) {
this.error = error;
}
@Override
public String toString() {
return error;
}
}

View file

@ -0,0 +1,18 @@
package com.jozufozu.flywheel.backend.source.error.lines;
public enum Divider {
BAR(" | "),
ARROW("-->"),
;
private final String s;
Divider(String s) {
this.s = s;
}
@Override
public String toString() {
return s;
}
}

View file

@ -0,0 +1,19 @@
package com.jozufozu.flywheel.backend.source.error.lines;
public interface ErrorLine {
default int neededMargin() {
return left().length();
}
default Divider divider() {
return Divider.BAR;
}
default String build() {
return left() + divider() + right();
}
String left();
String right();
}

View file

@ -0,0 +1,19 @@
package com.jozufozu.flywheel.backend.source.error.lines;
public record FileLine(String fileName) implements ErrorLine {
@Override
public String left() {
return "--";
}
@Override
public Divider divider() {
return Divider.ARROW;
}
@Override
public String right() {
return fileName;
}
}

View file

@ -0,0 +1,26 @@
package com.jozufozu.flywheel.backend.source.error.lines;
import com.jozufozu.flywheel.backend.source.error.Level;
public record HeaderLine(String level, CharSequence message) implements ErrorLine {
@Override
public int neededMargin() {
return 0;
}
@Override
public String build() {
return level + ": " + message;
}
@Override
public String left() {
return null;
}
@Override
public String right() {
return null;
}
}

View file

@ -0,0 +1,18 @@
package com.jozufozu.flywheel.backend.source.error.lines;
public record SourceLine(String number, String line) implements ErrorLine {
public static SourceLine numbered(int number, String line) {
return new SourceLine(Integer.toString(number), line);
}
@Override
public String left() {
return number;
}
@Override
public String right() {
return line;
}
}

View file

@ -0,0 +1,23 @@
package com.jozufozu.flywheel.backend.source.error.lines;
public class SpanHighlightLine implements ErrorLine {
private final String line;
public SpanHighlightLine(int firstCol, int lastCol) {
line = generateUnderline(firstCol, lastCol);
}
@Override
public String left() {
return "";
}
@Override
public String right() {
return line;
}
public static String generateUnderline(int firstCol, int lastCol) {
return " ".repeat(Math.max(0, firstCol)) + "^".repeat(Math.max(0, lastCol - firstCol));
}
}

View file

@ -3,27 +3,5 @@ package com.jozufozu.flywheel.backend.source.span;
/** /**
* A position in a file. * A position in a file.
*/ */
public class CharPos { public record CharPos(int pos, int line, int col) {
private final int idx;
private final int line;
private final int col;
public CharPos(int idx, int line, int col) {
this.idx = idx;
this.line = line;
this.col = col;
}
public int getPos() {
return idx;
}
public int getLine() {
return line;
}
public int getCol() {
return col;
}
} }

View file

@ -5,7 +5,11 @@ import java.util.regex.Matcher;
import com.jozufozu.flywheel.backend.source.SourceFile; import com.jozufozu.flywheel.backend.source.SourceFile;
/** /**
* A span of code in a {@link SourceFile}. * A segment of code in a {@link SourceFile}.
*
* <p>
* Spans are used for pretty-printing errors.
* </p>
*/ */
public abstract class Span implements CharSequence { public abstract class Span implements CharSequence {
@ -14,7 +18,7 @@ public abstract class Span implements CharSequence {
protected final CharPos end; protected final CharPos end;
public Span(SourceFile in, int start, int end) { public Span(SourceFile in, int start, int end) {
this(in, in.getCharPos(start), in.getCharPos(end)); this(in, in.lines.getCharPos(start), in.lines.getCharPos(end));
} }
public Span(SourceFile in, CharPos start, CharPos end) { public Span(SourceFile in, CharPos start, CharPos end) {
@ -42,14 +46,14 @@ public abstract class Span implements CharSequence {
* @return the string index at the (inclusive) beginning of this code segment. * @return the string index at the (inclusive) beginning of this code segment.
*/ */
public int getStartPos() { public int getStartPos() {
return start.getPos(); return start.pos();
} }
/** /**
* @return the string index at the (exclusive) end of this code segment. * @return the string index at the (exclusive) end of this code segment.
*/ */
public int getEndPos() { public int getEndPos() {
return end.getPos(); return end.pos();
} }
/** /**
@ -63,11 +67,11 @@ public abstract class Span implements CharSequence {
* @return how many lines this span spans. * @return how many lines this span spans.
*/ */
public int lines() { public int lines() {
return end.getLine() - start.getLine() + 1; return end.line() - start.line() + 1;
} }
public int firstLine() { public int firstLine() {
return start.getLine(); return start.line();
} }
/** /**
@ -89,7 +93,7 @@ public abstract class Span implements CharSequence {
@Override @Override
public char charAt(int index) { public char charAt(int index) {
return in.getSource().charAt(start.getPos() + index); return in.source.charAt(start.pos() + index);
} }
@Override @Override

View file

@ -14,13 +14,13 @@ public class StringSpan extends Span {
@Override @Override
public Span subSpan(int from, int to) { public Span subSpan(int from, int to) {
return new StringSpan(in, start.getPos() + from, start.getPos() + to); return new StringSpan(in, start.pos() + from, start.pos() + to);
} }
@Override @Override
public String get() { public String get() {
return in.getSource() return in.source
.substring(start.getPos(), end.getPos()); .substring(start.pos(), end.pos());
} }
@Override @Override

View file

@ -26,6 +26,8 @@ void FLWFinalizeWorldPos(inout vec4 worldPos) {
#elif defined(FRAGMENT_SHADER) #elif defined(FRAGMENT_SHADER)
out vec4 fragColor;
vec4 FLWBlockTexture(vec2 texCoords) { vec4 FLWBlockTexture(vec2 texCoords) {
vec4 cr = texture2D(uCrumbling, texCoords * uTextureScale); vec4 cr = texture2D(uCrumbling, texCoords * uTextureScale);
float diffuseAlpha = texture2D(uBlockAtlas, texCoords).a; float diffuseAlpha = texture2D(uBlockAtlas, texCoords).a;
@ -42,7 +44,11 @@ void FLWFinalizeColor(vec4 color) {
color.a = a; color.a = a;
#endif #endif
gl_FragColor = color; if (color.a < 0.1) {
discard;
}
fragColor = color;
} }
vec4 FLWLight(vec2 lightCoords) { vec4 FLWLight(vec2 lightCoords) {

View file

@ -1,4 +1,8 @@
varying float FragDistance; #if defined(VERTEX_SHADER)
out float FragDistance;
#elif defined(FRAGMENT_SHADER)
in float FragDistance;
#endif
uniform vec4 uFogColor; uniform vec4 uFogColor;
uniform vec2 uFogRange; uniform vec2 uFogRange;

View file

@ -34,6 +34,8 @@ void FLWFinalizeWorldPos(inout vec4 worldPos) {
//#endif //#endif
//#endif //#endif
out vec4 fragColor;
vec4 FLWBlockTexture(vec2 texCoords) { vec4 FLWBlockTexture(vec2 texCoords) {
return texture2D(uBlockAtlas, texCoords); return texture2D(uBlockAtlas, texCoords);
} }
@ -53,7 +55,7 @@ void FLWFinalizeColor(vec4 color) {
} }
#endif #endif
gl_FragColor = color; fragColor = color;
} }
vec4 FLWLight(vec2 lightCoords) { vec4 FLWLight(vec2 lightCoords) {