mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-02-06 18:24:59 +01:00
Fix* the errors
- *make the errors understandable without actually solving them - Straighten out shader compilation/error generation - Interpret errors in generated code - Compilation returns a result type instead of throwing an exception - Still need to reimplement source checks? - Make error messages prettier
This commit is contained in:
parent
99e4105e94
commit
7a080a5538
23 changed files with 647 additions and 395 deletions
|
@ -8,7 +8,6 @@ import com.jozufozu.flywheel.backend.RenderWork;
|
|||
import com.jozufozu.flywheel.backend.ShadersModHandler;
|
||||
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
|
||||
import com.jozufozu.flywheel.backend.instancing.batching.DrawBuffer;
|
||||
import com.jozufozu.flywheel.backend.instancing.compile.FlwCompiler;
|
||||
import com.jozufozu.flywheel.config.BackendTypeArgument;
|
||||
import com.jozufozu.flywheel.config.FlwCommands;
|
||||
import com.jozufozu.flywheel.config.FlwConfig;
|
||||
|
@ -80,7 +79,6 @@ public class Flywheel {
|
|||
forgeEventBus.addListener(FlwCommands::registerClientCommands);
|
||||
|
||||
forgeEventBus.addListener(EventPriority.HIGHEST, QuadConverter::onReloadRenderers);
|
||||
forgeEventBus.addListener(FlwCompiler::onReloadRenderers);
|
||||
forgeEventBus.addListener(Models::onReloadRenderers);
|
||||
forgeEventBus.addListener(DrawBuffer::onReloadRenderers);
|
||||
|
||||
|
|
|
@ -39,6 +39,10 @@ public class Loader implements ResourceManagerReloadListener {
|
|||
var errorReporter = new ErrorReporter();
|
||||
ShaderSources sources = new ShaderSources(errorReporter, manager);
|
||||
|
||||
if (FlwCompiler.INSTANCE != null) {
|
||||
FlwCompiler.INSTANCE.delete();
|
||||
}
|
||||
|
||||
FlwCompiler.INSTANCE = new FlwCompiler(sources);
|
||||
|
||||
ClientLevel level = Minecraft.getInstance().level;
|
||||
|
|
|
@ -1,38 +1,17 @@
|
|||
package com.jozufozu.flywheel.backend.gl.shader;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import com.jozufozu.flywheel.backend.Backend;
|
||||
import com.jozufozu.flywheel.backend.gl.GlObject;
|
||||
import com.jozufozu.flywheel.backend.gl.versioned.GlCompat;
|
||||
import com.jozufozu.flywheel.backend.instancing.compile.ShaderCompilationException;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public class GlShader extends GlObject {
|
||||
|
||||
public final ShaderType type;
|
||||
private final List<ResourceLocation> parts;
|
||||
private final String name;
|
||||
|
||||
public GlShader(String source, ShaderType type, List<ResourceLocation> parts) throws ShaderCompilationException {
|
||||
this.parts = parts;
|
||||
public GlShader(int handle, ShaderType type, String name) {
|
||||
this.type = type;
|
||||
int handle = GL20.glCreateShader(type.glEnum);
|
||||
|
||||
GlCompat.safeShaderSource(handle, source);
|
||||
GL20.glCompileShader(handle);
|
||||
|
||||
dumpSource(source, type);
|
||||
|
||||
if (GL20.glGetShaderi(handle, GL20.GL_COMPILE_STATUS) != GL20.GL_TRUE) {
|
||||
throw new ShaderCompilationException("Could not compile " + getName(), handle);
|
||||
}
|
||||
this.name = name;
|
||||
|
||||
setHandle(handle);
|
||||
}
|
||||
|
@ -42,26 +21,9 @@ public class GlShader extends GlObject {
|
|||
GL20.glDeleteShader(handle);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return parts.stream()
|
||||
.map(ResourceLocation::toString)
|
||||
.map(s -> s.replaceAll("/", "_")
|
||||
.replaceAll(":", "\\$"))
|
||||
.collect(Collectors.joining(";"));
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GlShader{" + type.name + handle() + " " + name + "}";
|
||||
}
|
||||
|
||||
private void dumpSource(String source, ShaderType type) {
|
||||
if (!Backend.DUMP_SHADER_SOURCE) {
|
||||
return;
|
||||
}
|
||||
|
||||
File dir = new File(Minecraft.getInstance().gameDirectory, "flywheel_sources");
|
||||
dir.mkdirs();
|
||||
File file = new File(dir, type.getFileName(getName()));
|
||||
try (FileWriter writer = new FileWriter(file)) {
|
||||
writer.write(source);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
package com.jozufozu.flywheel.backend.instancing.compile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import com.jozufozu.flywheel.backend.Backend;
|
||||
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
|
||||
import com.jozufozu.flywheel.backend.gl.shader.GlShader;
|
||||
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
|
||||
import com.jozufozu.flywheel.backend.gl.versioned.GlCompat;
|
||||
import com.jozufozu.flywheel.core.SourceComponent;
|
||||
import com.jozufozu.flywheel.core.source.SourceFile;
|
||||
import com.jozufozu.flywheel.util.StringUtil;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
/**
|
||||
* Builder style class for compiling shaders.
|
||||
* <p>
|
||||
* Keeps track of the source files and components used to compile a shader,
|
||||
* and interprets/pretty prints any errors that occur.
|
||||
*/
|
||||
public class Compilation {
|
||||
private final List<SourceFile> files = new ArrayList<>();
|
||||
private final List<ResourceLocation> componentNames = new ArrayList<>();
|
||||
private final StringBuilder generatedSource;
|
||||
private final StringBuilder fullSource;
|
||||
private final GLSLVersion glslVersion;
|
||||
private final ShaderType shaderType;
|
||||
private int generatedLines = 0;
|
||||
|
||||
public Compilation(GLSLVersion glslVersion, ShaderType shaderType) {
|
||||
this.generatedSource = new StringBuilder();
|
||||
this.fullSource = new StringBuilder(CompileUtil.generateHeader(glslVersion, shaderType));
|
||||
this.glslVersion = glslVersion;
|
||||
this.shaderType = shaderType;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CompilationResult compile() {
|
||||
int handle = GL20.glCreateShader(shaderType.glEnum);
|
||||
var source = fullSource.toString();
|
||||
|
||||
GlCompat.safeShaderSource(handle, source);
|
||||
GL20.glCompileShader(handle);
|
||||
|
||||
var shaderName = buildShaderName();
|
||||
dumpSource(source, shaderType.getFileName(shaderName));
|
||||
|
||||
if (compiledSuccessfully(handle)) {
|
||||
return CompilationResult.success(new GlShader(handle, shaderType, shaderName));
|
||||
}
|
||||
|
||||
var errorLog = GL20.glGetShaderInfoLog(handle);
|
||||
GL20.glDeleteShader(handle);
|
||||
return CompilationResult.failure(new FailedCompilation(shaderName, files, generatedSource.toString(), errorLog));
|
||||
}
|
||||
|
||||
public void enableExtension(String ext) {
|
||||
fullSource.append("#extension ")
|
||||
.append(ext)
|
||||
.append(" : enable\n");
|
||||
}
|
||||
|
||||
public void addComponentName(ResourceLocation name) {
|
||||
componentNames.add(name);
|
||||
}
|
||||
|
||||
public void appendComponent(SourceComponent component) {
|
||||
var source = component.source();
|
||||
|
||||
if (component instanceof SourceFile file) {
|
||||
fullSource.append(sourceHeader(file));
|
||||
} else {
|
||||
fullSource.append(generatedHeader(source, component.name()
|
||||
.toString()));
|
||||
}
|
||||
|
||||
fullSource.append(source);
|
||||
}
|
||||
|
||||
private String sourceHeader(SourceFile sourceFile) {
|
||||
return "#line " + 0 + ' ' + getOrCreateFileID(sourceFile) + " // " + sourceFile.name + '\n';
|
||||
}
|
||||
|
||||
private String generatedHeader(String generatedCode, String comment) {
|
||||
generatedSource.append(generatedCode);
|
||||
int lines = StringUtil.countLines(generatedCode);
|
||||
|
||||
// all generated code is put in file 0,
|
||||
var out = "#line " + generatedLines + ' ' + 0;
|
||||
|
||||
generatedLines += lines;
|
||||
|
||||
return out + " // (generated) " + comment + '\n';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an arbitrary file ID for use this compilation context, or generates one if missing.
|
||||
*
|
||||
* @param sourceFile The file to retrieve the ID for.
|
||||
* @return A file ID unique to the given sourceFile.
|
||||
*/
|
||||
private int getOrCreateFileID(SourceFile sourceFile) {
|
||||
int i = files.indexOf(sourceFile);
|
||||
if (i != -1) {
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
files.add(sourceFile);
|
||||
return files.size();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String buildShaderName() {
|
||||
var components = componentNames.stream()
|
||||
.map(ResourceLocation::toString)
|
||||
.map(s -> s.replaceAll("/", "_")
|
||||
.replaceAll(":", "\\$"))
|
||||
.collect(Collectors.joining(";"));
|
||||
return shaderType.name + glslVersion + ';' + components;
|
||||
}
|
||||
|
||||
private static void dumpSource(String source, String fileName) {
|
||||
if (!Backend.DUMP_SHADER_SOURCE) {
|
||||
return;
|
||||
}
|
||||
|
||||
File dir = new File(Minecraft.getInstance().gameDirectory, "flywheel_sources");
|
||||
dir.mkdirs();
|
||||
File file = new File(dir, fileName);
|
||||
try (FileWriter writer = new FileWriter(file)) {
|
||||
writer.write(source);
|
||||
} catch (Exception e) {
|
||||
Backend.LOGGER.error("Could not dump source.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean compiledSuccessfully(int handle) {
|
||||
return GL20.glGetShaderi(handle, GL20.GL_COMPILE_STATUS) == GL20.GL_TRUE;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package com.jozufozu.flywheel.backend.instancing.compile;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.jozufozu.flywheel.backend.gl.shader.GlShader;
|
||||
|
||||
public sealed interface CompilationResult {
|
||||
@Nullable
|
||||
default GlShader unwrap() {
|
||||
if (this instanceof Success s) {
|
||||
return s.shader();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
record Success(GlShader shader) implements CompilationResult {
|
||||
}
|
||||
|
||||
record Failure(FailedCompilation failure) implements CompilationResult {
|
||||
}
|
||||
|
||||
static CompilationResult success(GlShader program) {
|
||||
return new Success(program);
|
||||
}
|
||||
|
||||
static CompilationResult failure(FailedCompilation failure) {
|
||||
return new Failure(failure);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package com.jozufozu.flywheel.backend.instancing.compile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import com.jozufozu.flywheel.core.source.SourceFile;
|
||||
import com.jozufozu.flywheel.core.source.SourceLines;
|
||||
import com.jozufozu.flywheel.core.source.error.ErrorBuilder;
|
||||
import com.jozufozu.flywheel.core.source.span.Span;
|
||||
import com.jozufozu.flywheel.util.ConsoleColors;
|
||||
|
||||
public class FailedCompilation {
|
||||
private static final Pattern ERROR_LINE = Pattern.compile("(\\d+)\\((\\d+)\\) : (.*)");
|
||||
private final List<SourceFile> files;
|
||||
private final SourceLines generatedSource;
|
||||
private final String errorLog;
|
||||
private final String shaderName;
|
||||
|
||||
public FailedCompilation(String shaderName, List<SourceFile> files, String generatedSource, String errorLog) {
|
||||
this.shaderName = shaderName;
|
||||
this.files = files;
|
||||
this.generatedSource = new SourceLines(generatedSource);
|
||||
this.errorLog = errorLog;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return ConsoleColors.RED_BOLD_BRIGHT + "Failed to compile " + shaderName + ":\n" + errorString();
|
||||
}
|
||||
|
||||
public String errorString() {
|
||||
return errorStream().map(ErrorBuilder::build)
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Stream<ErrorBuilder> errorStream() {
|
||||
return errorLog.lines()
|
||||
.map(this::interpretErrorLine);
|
||||
}
|
||||
|
||||
private ErrorBuilder interpretErrorLine(String s) {
|
||||
Matcher matcher = ERROR_LINE.matcher(s);
|
||||
|
||||
if (matcher.find()) {
|
||||
int fileId = Integer.parseInt(matcher.group(1));
|
||||
int lineNo = Integer.parseInt(matcher.group(2));
|
||||
var msg = matcher.group(3);
|
||||
|
||||
if (fileId == 0) {
|
||||
return interpretGeneratedError(lineNo, msg);
|
||||
} else {
|
||||
return interpretSourceError(fileId, lineNo, msg);
|
||||
}
|
||||
}
|
||||
return ErrorBuilder.create()
|
||||
.error(s);
|
||||
}
|
||||
|
||||
private ErrorBuilder interpretSourceError(int fileId, int lineNo, String msg) {
|
||||
var sourceFile = files.get(fileId - 1);
|
||||
Span span = sourceFile.getLineSpanNoWhitespace(lineNo);
|
||||
|
||||
return ErrorBuilder.create()
|
||||
.error(msg)
|
||||
.pointAtFile(sourceFile)
|
||||
.pointAt(span, 1);
|
||||
}
|
||||
|
||||
private ErrorBuilder interpretGeneratedError(int lineNo, String msg) {
|
||||
return ErrorBuilder.create()
|
||||
.error(msg)
|
||||
.pointAtFile("[in generated source]")
|
||||
.pointAtLine(generatedSource, lineNo, 1)
|
||||
.note("This generally indicates a bug in Flywheel, not your shader code.");
|
||||
}
|
||||
}
|
|
@ -2,12 +2,10 @@ package com.jozufozu.flywheel.backend.instancing.compile;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
@ -20,7 +18,6 @@ import com.jozufozu.flywheel.api.vertex.VertexType;
|
|||
import com.jozufozu.flywheel.backend.Backend;
|
||||
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
|
||||
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
|
||||
import com.jozufozu.flywheel.backend.gl.shader.GlShader;
|
||||
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
|
||||
import com.jozufozu.flywheel.backend.instancing.indirect.IndirectComponent;
|
||||
import com.jozufozu.flywheel.core.BackendTypes;
|
||||
|
@ -29,85 +26,73 @@ import com.jozufozu.flywheel.core.Components;
|
|||
import com.jozufozu.flywheel.core.Pipelines;
|
||||
import com.jozufozu.flywheel.core.SourceComponent;
|
||||
import com.jozufozu.flywheel.core.pipeline.SimplePipeline;
|
||||
import com.jozufozu.flywheel.core.source.CompilationContext;
|
||||
import com.jozufozu.flywheel.core.source.ShaderLoadingException;
|
||||
import com.jozufozu.flywheel.core.source.ShaderSources;
|
||||
import com.jozufozu.flywheel.core.source.SourceFile;
|
||||
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
|
||||
import com.jozufozu.flywheel.util.StringUtil;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public class FlwCompiler {
|
||||
|
||||
public static FlwCompiler INSTANCE;
|
||||
|
||||
public static void onReloadRenderers(ReloadRenderersEvent t) {
|
||||
|
||||
}
|
||||
|
||||
final Map<PipelineContext, GlProgram> pipelinePrograms = new HashMap<>();
|
||||
final Map<StructType<?>, GlProgram> cullingPrograms = new HashMap<>();
|
||||
|
||||
boolean needsCrash = false;
|
||||
|
||||
final long compileStart = System.nanoTime();
|
||||
final Multimap<Set<UniformProvider>, PipelineContext> uniformProviderGroups = ArrayListMultimap.create();
|
||||
final List<PipelineContext> pipelineContexts = new ArrayList<>();
|
||||
|
||||
private final ShaderSources sources;
|
||||
private final VertexMaterialComponent vertexMaterialComponent;
|
||||
private final FragmentMaterialComponent fragmentMaterialComponent;
|
||||
private final List<PipelineContext> pipelineContexts;
|
||||
|
||||
|
||||
final ShaderCompiler shaderCompiler;
|
||||
final Multimap<Set<UniformProvider>, PipelineContext> uniformProviderGroups = ArrayListMultimap.create();
|
||||
final Map<PipelineContext, GlProgram> pipelinePrograms = new HashMap<>();
|
||||
final Map<StructType<?>, GlProgram> cullingPrograms = new HashMap<>();
|
||||
final List<FailedCompilation> errors = new ArrayList<>();
|
||||
|
||||
public FlwCompiler(ShaderSources sources) {
|
||||
this.shaderCompiler = new ShaderCompiler(errors::add);
|
||||
this.sources = sources;
|
||||
this.vertexMaterialComponent = new VertexMaterialComponent(sources, ComponentRegistry.materials.vertexSources());
|
||||
this.fragmentMaterialComponent = new FragmentMaterialComponent(sources, ComponentRegistry.materials.fragmentSources());
|
||||
|
||||
for (SimplePipeline pipelineShader : BackendTypes.availablePipelineShaders()) {
|
||||
for (StructType<?> structType : ComponentRegistry.structTypes) {
|
||||
for (VertexType vertexType : ComponentRegistry.vertexTypes) {
|
||||
// TODO: context ubershaders, or not?
|
||||
pipelineContexts.add(new PipelineContext(vertexType, structType, Components.WORLD, pipelineShader));
|
||||
}
|
||||
}
|
||||
}
|
||||
this.pipelineContexts = buildPipelineSet();
|
||||
|
||||
// TODO: analyze uniform providers and group them into sets; break up this ctor
|
||||
|
||||
for (PipelineContext context : pipelineContexts) {
|
||||
try {
|
||||
var glProgram = compilePipelineContext(context);
|
||||
pipelinePrograms.put(context, glProgram);
|
||||
} catch (ShaderCompilationException e) {
|
||||
needsCrash = true;
|
||||
Backend.LOGGER.error(e.errors);
|
||||
}
|
||||
compilePipelineContext(context);
|
||||
}
|
||||
|
||||
for (StructType<?> type : ComponentRegistry.structTypes) {
|
||||
try {
|
||||
var glProgram = compileComputeCuller(type);
|
||||
cullingPrograms.put(type, glProgram);
|
||||
} catch (ShaderCompilationException e) {
|
||||
needsCrash = true;
|
||||
Backend.LOGGER.error(e.errors);
|
||||
}
|
||||
compileComputeCuller(type);
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
private void finish() {
|
||||
long compileEnd = System.nanoTime();
|
||||
int programCount = pipelineContexts.size() + ComponentRegistry.structTypes.size();
|
||||
int shaderCount = shaderCompiler.shaderCount();
|
||||
int errorCount = errors.size();
|
||||
var elapsed = StringUtil.formatTime(compileEnd - compileStart);
|
||||
|
||||
Backend.LOGGER.info("Compiled " + pipelineContexts.size() + " programs in " + StringUtil.formatTime(compileEnd - compileStart));
|
||||
Backend.LOGGER.info("Compiled " + programCount + " programs and " + shaderCount + " shaders in " + elapsed + " with " + errorCount + " errors.");
|
||||
|
||||
if (needsCrash) {
|
||||
throw new ShaderLoadingException("Compilation failed");
|
||||
if (errorCount > 0) {
|
||||
var details = errors.stream()
|
||||
.map(FailedCompilation::getMessage)
|
||||
.collect(Collectors.joining("\n"));
|
||||
throw new ShaderLoadingException("Compilation failed.\n" + details);
|
||||
}
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
pipelinePrograms.values()
|
||||
.forEach(GlProgram::delete);
|
||||
cullingPrograms.values()
|
||||
.forEach(GlProgram::delete);
|
||||
shaderCompiler.delete();
|
||||
}
|
||||
|
||||
public GlProgram getPipelineProgram(VertexType vertexType, StructType<?> structType, ContextShader contextShader, SimplePipeline pipelineShader) {
|
||||
return pipelinePrograms.get(new PipelineContext(vertexType, structType, contextShader, pipelineShader));
|
||||
}
|
||||
|
@ -116,28 +101,36 @@ public class FlwCompiler {
|
|||
return cullingPrograms.get(structType);
|
||||
}
|
||||
|
||||
protected GlProgram compilePipelineContext(PipelineContext ctx) throws ShaderCompilationException {
|
||||
|
||||
private void compilePipelineContext(PipelineContext ctx) {
|
||||
var glslVersion = ctx.pipelineShader()
|
||||
.glslVersion();
|
||||
|
||||
var vertex = compileShader(glslVersion, ShaderType.VERTEX, getVertexComponents(ctx));
|
||||
var fragment = compileShader(glslVersion, ShaderType.FRAGMENT, getFragmentComponents(ctx));
|
||||
var vertex = shaderCompiler.compile(glslVersion, ShaderType.VERTEX, getVertexComponents(ctx));
|
||||
var fragment = shaderCompiler.compile(glslVersion, ShaderType.FRAGMENT, getFragmentComponents(ctx));
|
||||
|
||||
return ctx.contextShader()
|
||||
if (vertex == null || fragment == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
pipelinePrograms.put(ctx, ctx.contextShader()
|
||||
.factory()
|
||||
.create(new ProgramAssembler().attachShader(vertex)
|
||||
.attachShader(fragment)
|
||||
.link());
|
||||
.link()));
|
||||
}
|
||||
|
||||
protected GlProgram compileComputeCuller(StructType<?> structType) {
|
||||
private void compileComputeCuller(StructType<?> structType) {
|
||||
var result = shaderCompiler.compile(GLSLVersion.V460, ShaderType.COMPUTE, getComputeComponents(structType));
|
||||
|
||||
return new GlProgram(new ProgramAssembler().attachShader(compileShader(GLSLVersion.V460, ShaderType.COMPUTE, getComputeComponents(structType)))
|
||||
.link());
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
cullingPrograms.put(structType, new GlProgram(new ProgramAssembler().attachShader(result)
|
||||
.link()));
|
||||
}
|
||||
|
||||
ImmutableList<SourceComponent> getVertexComponents(PipelineContext ctx) {
|
||||
private ImmutableList<SourceComponent> getVertexComponents(PipelineContext ctx) {
|
||||
var instanceAssembly = ctx.pipelineShader()
|
||||
.assemble(new Pipeline.InstanceAssemblerContext(sources, ctx.vertexType(), ctx.structType()));
|
||||
|
||||
|
@ -157,7 +150,7 @@ public class FlwCompiler {
|
|||
return ImmutableList.of(vertexMaterialComponent, instanceAssembly, layout, instance, context, pipeline);
|
||||
}
|
||||
|
||||
ImmutableList<SourceComponent> getFragmentComponents(PipelineContext ctx) {
|
||||
private ImmutableList<SourceComponent> getFragmentComponents(PipelineContext ctx) {
|
||||
var context = sources.find(ctx.contextShader()
|
||||
.fragmentShader()
|
||||
.resourceLocation());
|
||||
|
@ -167,7 +160,7 @@ public class FlwCompiler {
|
|||
return ImmutableList.of(fragmentMaterialComponent, context, pipeline);
|
||||
}
|
||||
|
||||
@NotNull ImmutableList<SourceComponent> getComputeComponents(StructType<?> structType) {
|
||||
private ImmutableList<SourceComponent> getComputeComponents(StructType<?> structType) {
|
||||
var instanceAssembly = new IndirectComponent(sources, structType);
|
||||
var instance = sources.find(structType.getInstanceShader()
|
||||
.resourceLocation());
|
||||
|
@ -176,55 +169,16 @@ public class FlwCompiler {
|
|||
return ImmutableList.of(instanceAssembly, instance, pipeline);
|
||||
}
|
||||
|
||||
protected GlShader compileShader(GLSLVersion glslVersion, ShaderType shaderType, ImmutableList<SourceComponent> sourceComponents) {
|
||||
StringBuilder finalSource = new StringBuilder(CompileUtil.generateHeader(glslVersion, shaderType));
|
||||
finalSource.append("#extension GL_ARB_explicit_attrib_location : enable\n");
|
||||
finalSource.append("#extension GL_ARB_conservative_depth : enable\n");
|
||||
|
||||
var ctx = new CompilationContext();
|
||||
var names = ImmutableList.<ResourceLocation>builder();
|
||||
|
||||
for (var include : depthFirstInclude(sourceComponents)) {
|
||||
appendFinalSource(finalSource, ctx, include);
|
||||
private static List<PipelineContext> buildPipelineSet() {
|
||||
ImmutableList.Builder<PipelineContext> builder = ImmutableList.builder();
|
||||
for (SimplePipeline pipelineShader : BackendTypes.availablePipelineShaders()) {
|
||||
for (StructType<?> structType : ComponentRegistry.structTypes) {
|
||||
for (VertexType vertexType : ComponentRegistry.vertexTypes) {
|
||||
// TODO: context ubershaders, or not?
|
||||
builder.add(new PipelineContext(vertexType, structType, Components.WORLD, pipelineShader));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var component : sourceComponents) {
|
||||
appendFinalSource(finalSource, ctx, component);
|
||||
names.add(component.name());
|
||||
}
|
||||
|
||||
try {
|
||||
return new GlShader(finalSource.toString(), shaderType, names.build());
|
||||
} catch (ShaderCompilationException e) {
|
||||
throw e.withErrorLog(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
private static void appendFinalSource(StringBuilder finalSource, CompilationContext ctx, SourceComponent component) {
|
||||
var source = component.source();
|
||||
|
||||
if (component instanceof SourceFile file) {
|
||||
finalSource.append(ctx.sourceHeader(file));
|
||||
} else {
|
||||
finalSource.append(ctx.generatedHeader(source, component.name()
|
||||
.toString()));
|
||||
}
|
||||
|
||||
finalSource.append(source);
|
||||
}
|
||||
|
||||
protected static Set<SourceComponent> depthFirstInclude(ImmutableList<SourceComponent> root) {
|
||||
var included = new LinkedHashSet<SourceComponent>(); // linked to preserve order
|
||||
for (var component : root) {
|
||||
recursiveDepthFirstInclude(included, component);
|
||||
}
|
||||
return included;
|
||||
}
|
||||
|
||||
protected static void recursiveDepthFirstInclude(Set<SourceComponent> included, SourceComponent component) {
|
||||
for (var include : component.included()) {
|
||||
recursiveDepthFirstInclude(included, include);
|
||||
}
|
||||
included.addAll(component.included());
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
package com.jozufozu.flywheel.backend.instancing.compile;
|
||||
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import com.jozufozu.flywheel.core.source.CompilationContext;
|
||||
import com.jozufozu.flywheel.core.source.ShaderLoadingException;
|
||||
|
||||
public class ShaderCompilationException extends ShaderLoadingException {
|
||||
|
||||
private final int shaderHandle;
|
||||
|
||||
public String errors = "";
|
||||
|
||||
public ShaderCompilationException(String message, int shaderHandle) {
|
||||
super(message);
|
||||
this.shaderHandle = shaderHandle;
|
||||
}
|
||||
|
||||
public ShaderLoadingException withErrorLog(CompilationContext ctx) {
|
||||
if (this.shaderHandle == -1)
|
||||
return this;
|
||||
|
||||
this.errors = ctx.parseErrors(GL20.glGetShaderInfoLog(this.shaderHandle));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return super.getMessage() + '\n' + this.errors;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package com.jozufozu.flywheel.backend.instancing.compile;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
|
||||
import com.jozufozu.flywheel.backend.gl.shader.GlShader;
|
||||
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
|
||||
import com.jozufozu.flywheel.core.SourceComponent;
|
||||
|
||||
public class ShaderCompiler {
|
||||
private final Map<ShaderKey, CompilationResult> shaderCache = new HashMap<>();
|
||||
private final Consumer<FailedCompilation> errorConsumer;
|
||||
|
||||
public ShaderCompiler(Consumer<FailedCompilation> errorConsumer) {
|
||||
this.errorConsumer = errorConsumer;
|
||||
}
|
||||
|
||||
public int shaderCount() {
|
||||
return shaderCache.size();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public GlShader compile(GLSLVersion glslVersion, ShaderType shaderType, ImmutableList<SourceComponent> sourceComponents) {
|
||||
var key = new ShaderKey(glslVersion, shaderType, sourceComponents);
|
||||
var cached = shaderCache.get(key);
|
||||
if (cached != null) {
|
||||
return cached.unwrap();
|
||||
}
|
||||
|
||||
CompilationResult out = compileUncached(glslVersion, shaderType, sourceComponents);
|
||||
shaderCache.put(key, out);
|
||||
return unwrapAndReportError(out);
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
shaderCache.values()
|
||||
.stream()
|
||||
.map(CompilationResult::unwrap)
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(GlShader::delete);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private GlShader unwrapAndReportError(CompilationResult result) {
|
||||
if (result instanceof CompilationResult.Success s) {
|
||||
return s.shader();
|
||||
} else if (result instanceof CompilationResult.Failure f) {
|
||||
errorConsumer.accept(f.failure());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static CompilationResult compileUncached(GLSLVersion glslVersion, ShaderType shaderType, ImmutableList<SourceComponent> sourceComponents) {
|
||||
var ctx = new Compilation(glslVersion, shaderType);
|
||||
ctx.enableExtension("GL_ARB_explicit_attrib_location");
|
||||
ctx.enableExtension("GL_ARB_conservative_depth");
|
||||
|
||||
for (var include : depthFirstInclude(sourceComponents)) {
|
||||
ctx.appendComponent(include);
|
||||
}
|
||||
|
||||
for (var component : sourceComponents) {
|
||||
ctx.appendComponent(component);
|
||||
ctx.addComponentName(component.name());
|
||||
}
|
||||
|
||||
return ctx.compile();
|
||||
}
|
||||
|
||||
private static Set<SourceComponent> depthFirstInclude(ImmutableList<SourceComponent> root) {
|
||||
var included = new LinkedHashSet<SourceComponent>(); // linked to preserve order
|
||||
for (var component : root) {
|
||||
recursiveDepthFirstInclude(included, component);
|
||||
}
|
||||
return included;
|
||||
}
|
||||
|
||||
private static void recursiveDepthFirstInclude(Set<SourceComponent> included, SourceComponent component) {
|
||||
for (var include : component.included()) {
|
||||
recursiveDepthFirstInclude(included, include);
|
||||
}
|
||||
included.addAll(component.included());
|
||||
}
|
||||
|
||||
private record ShaderKey(GLSLVersion glslVersion, ShaderType shaderType,
|
||||
ImmutableList<SourceComponent> sourceComponents) {
|
||||
|
||||
}
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
package com.jozufozu.flywheel.core.source;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.jozufozu.flywheel.core.source.error.ErrorBuilder;
|
||||
import com.jozufozu.flywheel.core.source.span.Span;
|
||||
|
||||
public class CompilationContext {
|
||||
public final List<SourceFile> files = new ArrayList<>();
|
||||
|
||||
private String generatedSource = "";
|
||||
private int generatedLines = 0;
|
||||
|
||||
|
||||
public String sourceHeader(SourceFile sourceFile) {
|
||||
return "#line " + 0 + ' ' + getOrCreateFileID(sourceFile) + " // " + sourceFile.name + '\n';
|
||||
}
|
||||
|
||||
public String generatedHeader(String generatedCode, @Nullable String comment) {
|
||||
generatedSource += generatedCode;
|
||||
int lines = generatedCode.split("\n").length;
|
||||
|
||||
var out = "#line " + generatedLines + ' ' + 0;
|
||||
|
||||
generatedLines += lines;
|
||||
|
||||
if (comment == null) {
|
||||
comment = "";
|
||||
}
|
||||
|
||||
return out + " // (generated) " + comment + '\n';
|
||||
}
|
||||
|
||||
public boolean contains(SourceFile sourceFile) {
|
||||
return files.contains(sourceFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an arbitrary file ID for use this compilation context, or generates one if missing.
|
||||
*
|
||||
* @param sourceFile The file to retrieve the ID for.
|
||||
* @return A file ID unique to the given sourceFile.
|
||||
*/
|
||||
private int getOrCreateFileID(SourceFile sourceFile) {
|
||||
int i = files.indexOf(sourceFile);
|
||||
if (i != -1) {
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
files.add(sourceFile);
|
||||
return files.size();
|
||||
}
|
||||
|
||||
public Span getLineSpan(int fileId, int lineNo) {
|
||||
if (fileId == 0) {
|
||||
// TODO: Valid spans for generated code.
|
||||
return null;
|
||||
}
|
||||
return getFile(fileId).getLineSpanNoWhitespace(lineNo);
|
||||
}
|
||||
|
||||
private SourceFile getFile(int fileId) {
|
||||
return files.get(fileId - 1);
|
||||
}
|
||||
|
||||
public String parseErrors(String log) {
|
||||
List<String> lines = log.lines()
|
||||
.toList();
|
||||
|
||||
StringBuilder errors = new StringBuilder();
|
||||
for (String line : lines) {
|
||||
ErrorBuilder builder = parseCompilerError(line);
|
||||
|
||||
if (builder != null) {
|
||||
errors.append(builder.build());
|
||||
} else {
|
||||
errors.append(line).append('\n');
|
||||
}
|
||||
}
|
||||
return errors.toString();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ErrorBuilder parseCompilerError(String line) {
|
||||
try {
|
||||
return ErrorBuilder.fromLogLine(this, line);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -98,15 +98,15 @@ public class SourceFile implements SourceComponent {
|
|||
}
|
||||
|
||||
public Span getLineSpan(int lineNo) {
|
||||
int begin = source.getLineStart(lineNo);
|
||||
int end = begin + source.getLine(lineNo)
|
||||
int begin = source.lineStartIndex(lineNo);
|
||||
int end = begin + source.lineString(lineNo)
|
||||
.length();
|
||||
return new StringSpan(this, source.getCharPos(begin), source.getCharPos(end));
|
||||
}
|
||||
|
||||
public Span getLineSpanNoWhitespace(int line) {
|
||||
int begin = source.getLineStart(line);
|
||||
int end = begin + source.getLine(line)
|
||||
int begin = source.lineStartIndex(line);
|
||||
int end = begin + source.lineString(line)
|
||||
.length();
|
||||
|
||||
while (begin < end && Character.isWhitespace(source.charAt(begin))) {
|
||||
|
|
|
@ -11,6 +11,7 @@ import com.jozufozu.flywheel.util.StringUtil;
|
|||
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import it.unimi.dsi.fastutil.ints.IntLists;
|
||||
|
||||
public class SourceLines implements CharSequence {
|
||||
|
||||
|
@ -33,15 +34,15 @@ public class SourceLines implements CharSequence {
|
|||
this.lines = getLines(raw, lineStarts);
|
||||
}
|
||||
|
||||
public int getLineCount() {
|
||||
public int count() {
|
||||
return lines.size();
|
||||
}
|
||||
|
||||
public String getLine(int lineNo) {
|
||||
public String lineString(int lineNo) {
|
||||
return lines.get(lineNo);
|
||||
}
|
||||
|
||||
public int getLineStart(int lineNo) {
|
||||
public int lineStartIndex(int lineNo) {
|
||||
return lineStarts.getInt(lineNo);
|
||||
}
|
||||
|
||||
|
@ -76,9 +77,12 @@ public class SourceLines implements CharSequence {
|
|||
|
||||
/**
|
||||
* Scan the source for line breaks, recording the position of the first character of each line.
|
||||
* @param source
|
||||
*/
|
||||
private static IntList createLineLookup(String source) {
|
||||
if (source.isEmpty()) {
|
||||
return IntLists.emptyList();
|
||||
}
|
||||
|
||||
IntList l = new IntArrayList();
|
||||
l.add(0); // first line is always at position 0
|
||||
|
||||
|
@ -87,6 +91,7 @@ public class SourceLines implements CharSequence {
|
|||
while (matcher.find()) {
|
||||
l.add(matcher.end());
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
|
@ -121,4 +126,28 @@ public class SourceLines implements CharSequence {
|
|||
public int length() {
|
||||
return raw.length();
|
||||
}
|
||||
|
||||
public int lineStartCol(int spanLine) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int lineWidth(int spanLine) {
|
||||
return lines.get(spanLine)
|
||||
.length();
|
||||
}
|
||||
|
||||
public int lineStartColTrimmed(final int line) {
|
||||
final var lineString = lineString(line);
|
||||
final int end = lineString.length();
|
||||
|
||||
int col = 0;
|
||||
while (col < end && Character.isWhitespace(charAt(col))) {
|
||||
col++;
|
||||
}
|
||||
return col;
|
||||
}
|
||||
|
||||
public int lineStartPosTrimmed(final int line) {
|
||||
return lineStartIndex(line) + lineStartColTrimmed(line);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,12 +2,9 @@ package com.jozufozu.flywheel.core.source.error;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.jozufozu.flywheel.core.source.CompilationContext;
|
||||
import com.jozufozu.flywheel.core.source.SourceFile;
|
||||
import com.jozufozu.flywheel.core.source.SourceLines;
|
||||
import com.jozufozu.flywheel.core.source.error.lines.ErrorLine;
|
||||
|
@ -17,72 +14,61 @@ import com.jozufozu.flywheel.core.source.error.lines.SourceLine;
|
|||
import com.jozufozu.flywheel.core.source.error.lines.SpanHighlightLine;
|
||||
import com.jozufozu.flywheel.core.source.error.lines.TextLine;
|
||||
import com.jozufozu.flywheel.core.source.span.Span;
|
||||
import com.jozufozu.flywheel.util.FlwUtil;
|
||||
import com.jozufozu.flywheel.util.ConsoleColors;
|
||||
import com.jozufozu.flywheel.util.StringUtil;
|
||||
|
||||
public class ErrorBuilder {
|
||||
|
||||
private static final Pattern ERROR_LINE = Pattern.compile("(\\d+)\\((\\d+)\\) : (.*)");
|
||||
|
||||
private final List<ErrorLine> lines = new ArrayList<>();
|
||||
|
||||
public ErrorBuilder() {
|
||||
private ErrorBuilder() {
|
||||
|
||||
}
|
||||
|
||||
public static ErrorBuilder error(CharSequence msg) {
|
||||
return new ErrorBuilder()
|
||||
.header(ErrorLevel.ERROR, msg);
|
||||
public static ErrorBuilder create() {
|
||||
return new ErrorBuilder();
|
||||
}
|
||||
|
||||
public static ErrorBuilder compError(CharSequence msg) {
|
||||
return new ErrorBuilder()
|
||||
.extra(msg);
|
||||
public ErrorBuilder error(String msg) {
|
||||
return header(ErrorLevel.ERROR, msg);
|
||||
}
|
||||
|
||||
public static ErrorBuilder warn(CharSequence msg) {
|
||||
return new ErrorBuilder()
|
||||
.header(ErrorLevel.WARN, msg);
|
||||
public ErrorBuilder warn(String msg) {
|
||||
return header(ErrorLevel.WARN, msg);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ErrorBuilder fromLogLine(CompilationContext env, String s) {
|
||||
Matcher matcher = ERROR_LINE.matcher(s);
|
||||
|
||||
if (matcher.find()) {
|
||||
String fileId = matcher.group(1);
|
||||
String lineNo = matcher.group(2);
|
||||
String msg = matcher.group(3);
|
||||
Span span = env.getLineSpan(Integer.parseInt(fileId), Integer.parseInt(lineNo));
|
||||
|
||||
if (span == null) {
|
||||
return ErrorBuilder.compError("Error in generated code");
|
||||
}
|
||||
|
||||
return ErrorBuilder.compError(msg)
|
||||
.pointAtFile(span.getSourceFile())
|
||||
.pointAt(span, 1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
public ErrorBuilder hint(String msg) {
|
||||
return header(ErrorLevel.HINT, msg);
|
||||
}
|
||||
|
||||
public ErrorBuilder header(ErrorLevel level, CharSequence msg) {
|
||||
public ErrorBuilder note(String msg) {
|
||||
return header(ErrorLevel.NOTE, msg);
|
||||
}
|
||||
|
||||
public ErrorBuilder header(ErrorLevel level, String msg) {
|
||||
lines.add(new HeaderLine(level.toString(), msg));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ErrorBuilder extra(CharSequence msg) {
|
||||
lines.add(new TextLine(msg.toString()));
|
||||
public ErrorBuilder extra(String msg) {
|
||||
lines.add(new TextLine(msg));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ErrorBuilder pointAtFile(SourceFile file) {
|
||||
lines.add(new FileLine(file.name.toString()));
|
||||
return pointAtFile(file.name.toString());
|
||||
}
|
||||
|
||||
public ErrorBuilder pointAtFile(String file) {
|
||||
lines.add(new FileLine(file));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ErrorBuilder hintIncludeFor(@Nullable Span span, CharSequence msg) {
|
||||
if (span == null) return this;
|
||||
public ErrorBuilder hintIncludeFor(@Nullable Span span, String msg) {
|
||||
if (span == null) {
|
||||
return this;
|
||||
}
|
||||
|
||||
SourceFile sourceFile = span.getSourceFile();
|
||||
|
||||
String builder = "add " + sourceFile.importStatement() + ' ' + msg + "\n defined here:";
|
||||
|
@ -98,28 +84,37 @@ public class ErrorBuilder {
|
|||
}
|
||||
|
||||
public ErrorBuilder pointAt(Span span, int ctxLines) {
|
||||
|
||||
if (span.lines() == 1) {
|
||||
SourceLines lines = span.getSourceFile().source;
|
||||
|
||||
int spanLine = span.firstLine();
|
||||
int firstCol = span.getStart()
|
||||
.col();
|
||||
int lastCol = span.getEnd()
|
||||
.col();
|
||||
|
||||
int firstLine = Math.max(0, spanLine - ctxLines);
|
||||
int lastLine = Math.min(lines.getLineCount(), spanLine + ctxLines);
|
||||
pointAtLine(lines, spanLine, ctxLines, firstCol, lastCol);
|
||||
}
|
||||
|
||||
int firstCol = span.getStart()
|
||||
.col();
|
||||
int lastCol = span.getEnd()
|
||||
.col();
|
||||
return this;
|
||||
}
|
||||
|
||||
for (int i = firstLine; i <= lastLine; i++) {
|
||||
CharSequence line = lines.getLine(i);
|
||||
public ErrorBuilder pointAtLine(SourceLines lines, int spanLine, int ctxLines) {
|
||||
return pointAtLine(lines, spanLine, ctxLines, lines.lineStartColTrimmed(spanLine), lines.lineWidth(spanLine));
|
||||
}
|
||||
|
||||
this.lines.add(SourceLine.numbered(i + 1, line.toString()));
|
||||
public ErrorBuilder pointAtLine(SourceLines lines, int spanLine, int ctxLines, int firstCol, int lastCol) {
|
||||
int firstLine = Math.max(0, spanLine - ctxLines);
|
||||
int lastLine = Math.min(lines.count(), spanLine + ctxLines);
|
||||
|
||||
if (i == spanLine) {
|
||||
this.lines.add(new SpanHighlightLine(firstCol, lastCol));
|
||||
}
|
||||
|
||||
for (int i = firstLine; i <= lastLine; i++) {
|
||||
CharSequence line = lines.lineString(i);
|
||||
|
||||
this.lines.add(SourceLine.numbered(i + 1, line.toString()));
|
||||
|
||||
if (i == spanLine) {
|
||||
this.lines.add(new SpanHighlightLine(firstCol, lastCol));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,23 +122,25 @@ public class ErrorBuilder {
|
|||
}
|
||||
|
||||
public String build() {
|
||||
|
||||
int maxLength = -1;
|
||||
int maxMargin = -1;
|
||||
for (ErrorLine line : lines) {
|
||||
int length = line.neededMargin();
|
||||
int neededMargin = line.neededMargin();
|
||||
|
||||
if (length > maxLength) {
|
||||
maxLength = length;
|
||||
if (neededMargin > maxMargin) {
|
||||
maxMargin = neededMargin;
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append('\n');
|
||||
for (ErrorLine line : lines) {
|
||||
int length = line.neededMargin();
|
||||
int neededMargin = line.neededMargin();
|
||||
|
||||
builder.append(FlwUtil.repeatChar(' ', maxLength - length))
|
||||
.append(line.build())
|
||||
if (neededMargin >= 0) {
|
||||
builder.append(StringUtil.repeatChar(' ', maxMargin - neededMargin));
|
||||
}
|
||||
|
||||
builder.append(line.build())
|
||||
.append(ConsoleColors.RESET)
|
||||
.append('\n');
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package com.jozufozu.flywheel.core.source.error;
|
||||
|
||||
import com.jozufozu.flywheel.util.ConsoleColors;
|
||||
|
||||
public enum ErrorLevel {
|
||||
WARN("warn"),
|
||||
ERROR("error"),
|
||||
HINT("hint"),
|
||||
WARN(ConsoleColors.YELLOW + "warn"),
|
||||
ERROR(ConsoleColors.RED + "error"),
|
||||
HINT(ConsoleColors.WHITE_BRIGHT + "hint"),
|
||||
NOTE(ConsoleColors.WHITE_BRIGHT + "note"),
|
||||
;
|
||||
|
||||
private final String error;
|
||||
|
|
|
@ -9,6 +9,7 @@ import com.jozufozu.flywheel.core.source.ShaderLoadingException;
|
|||
import com.jozufozu.flywheel.core.source.SourceFile;
|
||||
import com.jozufozu.flywheel.core.source.span.Span;
|
||||
import com.jozufozu.flywheel.util.FlwUtil;
|
||||
import com.jozufozu.flywheel.util.StringUtil;
|
||||
|
||||
public class ErrorReporter {
|
||||
|
||||
|
@ -60,18 +61,17 @@ public class ErrorReporter {
|
|||
public ErrorBuilder generateSpanError(Span span, String message) {
|
||||
SourceFile file = span.getSourceFile();
|
||||
|
||||
return error(message)
|
||||
.pointAtFile(file)
|
||||
return error(message).pointAtFile(file)
|
||||
.pointAt(span, 2);
|
||||
}
|
||||
|
||||
public ErrorBuilder generateFileError(SourceFile file, String message) {
|
||||
return error(message)
|
||||
.pointAtFile(file);
|
||||
return error(message).pointAtFile(file);
|
||||
}
|
||||
|
||||
public ErrorBuilder error(CharSequence msg) {
|
||||
var out = ErrorBuilder.error(msg);
|
||||
public ErrorBuilder error(String msg) {
|
||||
var out = ErrorBuilder.create()
|
||||
.error(msg);
|
||||
reportedErrors.add(out);
|
||||
return out;
|
||||
}
|
||||
|
@ -88,9 +88,7 @@ public class ErrorReporter {
|
|||
return new ShaderLoadingException(allErrors);
|
||||
}
|
||||
|
||||
public static void printLines(CharSequence source) {
|
||||
String string = source.toString();
|
||||
|
||||
public static void printLines(String string) {
|
||||
List<String> lines = string.lines()
|
||||
.toList();
|
||||
|
||||
|
@ -103,7 +101,7 @@ public class ErrorReporter {
|
|||
for (int i = 0; i < size; i++) {
|
||||
|
||||
builder.append(i)
|
||||
.append(FlwUtil.repeatChar(' ', maxWidth - FlwUtil.numDigits(i)))
|
||||
.append(StringUtil.repeatChar(' ', maxWidth - FlwUtil.numDigits(i)))
|
||||
.append("| ")
|
||||
.append(lines.get(i))
|
||||
.append('\n');
|
||||
|
|
|
@ -2,7 +2,7 @@ package com.jozufozu.flywheel.core.source.error.lines;
|
|||
|
||||
public enum Divider {
|
||||
BAR(" | "),
|
||||
ARROW("-->"),
|
||||
ARROW("-> "),
|
||||
;
|
||||
|
||||
private final String s;
|
||||
|
|
|
@ -4,7 +4,7 @@ public record FileLine(String fileName) implements ErrorLine {
|
|||
|
||||
@Override
|
||||
public String left() {
|
||||
return "--";
|
||||
return "-";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -4,7 +4,7 @@ public record HeaderLine(String level, CharSequence message) implements ErrorLin
|
|||
|
||||
@Override
|
||||
public int neededMargin() {
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -8,8 +8,6 @@ import java.util.stream.Collectors;
|
|||
import com.jozufozu.flywheel.util.Pair;
|
||||
|
||||
public class GlslBuilder {
|
||||
public static final String INDENT = " ";
|
||||
|
||||
private final List<GlslRootElement> elements = new ArrayList<>();
|
||||
|
||||
public void define(String name, String value) {
|
||||
|
@ -39,16 +37,12 @@ public class GlslBuilder {
|
|||
|
||||
public String build() {
|
||||
return elements.stream()
|
||||
.map(GlslRootElement::minPrint)
|
||||
.map(GlslRootElement::prettyPrint)
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
public static String indent(int indentation) {
|
||||
return INDENT.repeat(indentation);
|
||||
}
|
||||
|
||||
public interface GlslRootElement {
|
||||
String minPrint();
|
||||
String prettyPrint();
|
||||
}
|
||||
|
||||
public enum Separators implements GlslRootElement {
|
||||
|
@ -62,14 +56,14 @@ public class GlslBuilder {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String minPrint() {
|
||||
public String prettyPrint() {
|
||||
return separator;
|
||||
}
|
||||
}
|
||||
|
||||
public record Define(String name, String value) implements GlslRootElement {
|
||||
@Override
|
||||
public String minPrint() {
|
||||
public String prettyPrint() {
|
||||
return "#define " + name + " " + value;
|
||||
}
|
||||
}
|
||||
|
@ -96,7 +90,7 @@ public class GlslBuilder {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String minPrint() {
|
||||
public String prettyPrint() {
|
||||
return "layout(location = " + binding + ") in " + type + " " + name + ";";
|
||||
}
|
||||
}
|
||||
|
@ -116,16 +110,16 @@ public class GlslBuilder {
|
|||
|
||||
private String buildFields() {
|
||||
return fields.stream()
|
||||
.map(p -> INDENT + p.first() + ' ' + p.second() + ';')
|
||||
.map(p -> p.first() + ' ' + p.second() + ';')
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
public String minPrint() {
|
||||
public String prettyPrint() {
|
||||
return """
|
||||
struct %s {
|
||||
%s
|
||||
};
|
||||
""".formatted(name, buildFields());
|
||||
""".formatted(name, buildFields().indent(4));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -160,12 +154,13 @@ public class GlslBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public String minPrint() {
|
||||
public String prettyPrint() {
|
||||
return """
|
||||
%s %s(%s) {
|
||||
%s
|
||||
}
|
||||
""".formatted(returnType, name, buildArguments(), body.prettyPrint());
|
||||
""".formatted(returnType, name, buildArguments(), body.prettyPrint()
|
||||
.indent(4));
|
||||
}
|
||||
|
||||
private String buildArguments() {
|
||||
|
|
|
@ -54,7 +54,7 @@ public interface GlslStmt extends LangItem {
|
|||
.prettyPrint();
|
||||
return """
|
||||
case %s:
|
||||
%s""".formatted(variant, block);
|
||||
%s""".formatted(variant, block.indent(4));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
77
src/main/java/com/jozufozu/flywheel/util/ConsoleColors.java
Normal file
77
src/main/java/com/jozufozu/flywheel/util/ConsoleColors.java
Normal file
|
@ -0,0 +1,77 @@
|
|||
package com.jozufozu.flywheel.util;
|
||||
|
||||
// https://stackoverflow.com/a/45444716
|
||||
public class ConsoleColors {
|
||||
// Reset
|
||||
public static final String RESET = "\033[0m"; // Text Reset
|
||||
|
||||
// Regular Colors
|
||||
public static final String BLACK = "\033[0;30m"; // BLACK
|
||||
public static final String RED = "\033[0;31m"; // RED
|
||||
public static final String GREEN = "\033[0;32m"; // GREEN
|
||||
public static final String YELLOW = "\033[0;33m"; // YELLOW
|
||||
public static final String BLUE = "\033[0;34m"; // BLUE
|
||||
public static final String PURPLE = "\033[0;35m"; // PURPLE
|
||||
public static final String CYAN = "\033[0;36m"; // CYAN
|
||||
public static final String WHITE = "\033[0;37m"; // WHITE
|
||||
|
||||
// Bold
|
||||
public static final String BLACK_BOLD = "\033[1;30m"; // BLACK
|
||||
public static final String RED_BOLD = "\033[1;31m"; // RED
|
||||
public static final String GREEN_BOLD = "\033[1;32m"; // GREEN
|
||||
public static final String YELLOW_BOLD = "\033[1;33m"; // YELLOW
|
||||
public static final String BLUE_BOLD = "\033[1;34m"; // BLUE
|
||||
public static final String PURPLE_BOLD = "\033[1;35m"; // PURPLE
|
||||
public static final String CYAN_BOLD = "\033[1;36m"; // CYAN
|
||||
public static final String WHITE_BOLD = "\033[1;37m"; // WHITE
|
||||
|
||||
// Underline
|
||||
public static final String BLACK_UNDERLINED = "\033[4;30m"; // BLACK
|
||||
public static final String RED_UNDERLINED = "\033[4;31m"; // RED
|
||||
public static final String GREEN_UNDERLINED = "\033[4;32m"; // GREEN
|
||||
public static final String YELLOW_UNDERLINED = "\033[4;33m"; // YELLOW
|
||||
public static final String BLUE_UNDERLINED = "\033[4;34m"; // BLUE
|
||||
public static final String PURPLE_UNDERLINED = "\033[4;35m"; // PURPLE
|
||||
public static final String CYAN_UNDERLINED = "\033[4;36m"; // CYAN
|
||||
public static final String WHITE_UNDERLINED = "\033[4;37m"; // WHITE
|
||||
|
||||
// Background
|
||||
public static final String BLACK_BACKGROUND = "\033[40m"; // BLACK
|
||||
public static final String RED_BACKGROUND = "\033[41m"; // RED
|
||||
public static final String GREEN_BACKGROUND = "\033[42m"; // GREEN
|
||||
public static final String YELLOW_BACKGROUND = "\033[43m"; // YELLOW
|
||||
public static final String BLUE_BACKGROUND = "\033[44m"; // BLUE
|
||||
public static final String PURPLE_BACKGROUND = "\033[45m"; // PURPLE
|
||||
public static final String CYAN_BACKGROUND = "\033[46m"; // CYAN
|
||||
public static final String WHITE_BACKGROUND = "\033[47m"; // WHITE
|
||||
|
||||
// High Intensity
|
||||
public static final String BLACK_BRIGHT = "\033[0;90m"; // BLACK
|
||||
public static final String RED_BRIGHT = "\033[0;91m"; // RED
|
||||
public static final String GREEN_BRIGHT = "\033[0;92m"; // GREEN
|
||||
public static final String YELLOW_BRIGHT = "\033[0;93m"; // YELLOW
|
||||
public static final String BLUE_BRIGHT = "\033[0;94m"; // BLUE
|
||||
public static final String PURPLE_BRIGHT = "\033[0;95m"; // PURPLE
|
||||
public static final String CYAN_BRIGHT = "\033[0;96m"; // CYAN
|
||||
public static final String WHITE_BRIGHT = "\033[0;97m"; // WHITE
|
||||
|
||||
// Bold High Intensity
|
||||
public static final String BLACK_BOLD_BRIGHT = "\033[1;90m"; // BLACK
|
||||
public static final String RED_BOLD_BRIGHT = "\033[1;91m"; // RED
|
||||
public static final String GREEN_BOLD_BRIGHT = "\033[1;92m"; // GREEN
|
||||
public static final String YELLOW_BOLD_BRIGHT = "\033[1;93m";// YELLOW
|
||||
public static final String BLUE_BOLD_BRIGHT = "\033[1;94m"; // BLUE
|
||||
public static final String PURPLE_BOLD_BRIGHT = "\033[1;95m";// PURPLE
|
||||
public static final String CYAN_BOLD_BRIGHT = "\033[1;96m"; // CYAN
|
||||
public static final String WHITE_BOLD_BRIGHT = "\033[1;97m"; // WHITE
|
||||
|
||||
// High Intensity backgrounds
|
||||
public static final String BLACK_BACKGROUND_BRIGHT = "\033[0;100m";// BLACK
|
||||
public static final String RED_BACKGROUND_BRIGHT = "\033[0;101m";// RED
|
||||
public static final String GREEN_BACKGROUND_BRIGHT = "\033[0;102m";// GREEN
|
||||
public static final String YELLOW_BACKGROUND_BRIGHT = "\033[0;103m";// YELLOW
|
||||
public static final String BLUE_BACKGROUND_BRIGHT = "\033[0;104m";// BLUE
|
||||
public static final String PURPLE_BACKGROUND_BRIGHT = "\033[0;105m"; // PURPLE
|
||||
public static final String CYAN_BACKGROUND_BRIGHT = "\033[0;106m"; // CYAN
|
||||
public static final String WHITE_BACKGROUND_BRIGHT = "\033[0;107m"; // WHITE
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package com.jozufozu.flywheel.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
@ -22,14 +21,6 @@ public class FlwUtil {
|
|||
return ((BlockEntityRenderDispatcherAccessor) mc.getBlockEntityRenderDispatcher()).flywheel$getRenderers();
|
||||
}
|
||||
|
||||
public static String repeatChar(char c, int n) {
|
||||
char[] arr = new char[n];
|
||||
|
||||
Arrays.fill(arr, c);
|
||||
|
||||
return new String(arr);
|
||||
}
|
||||
|
||||
public static PoseStack copyPoseStack(PoseStack stack) {
|
||||
PoseStack copy = new PoseStack();
|
||||
copy.last().pose().load(stack.last().pose());
|
||||
|
|
|
@ -23,6 +23,11 @@ public class StringUtil {
|
|||
|
||||
private static final NumberFormat THREE_DECIMAL_PLACES = new DecimalFormat("#0.000");
|
||||
|
||||
public static int countLines(String s) {
|
||||
return (int) s.lines()
|
||||
.count();
|
||||
}
|
||||
|
||||
public static String formatBytes(long bytes) {
|
||||
if (bytes < 1024) {
|
||||
return bytes + " B";
|
||||
|
@ -114,4 +119,12 @@ public class StringUtil {
|
|||
}
|
||||
return bytebuffer;
|
||||
}
|
||||
|
||||
public static String repeatChar(char c, int n) {
|
||||
char[] arr = new char[n];
|
||||
|
||||
Arrays.fill(arr, c);
|
||||
|
||||
return new String(arr);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue