Cleaner compilation

- Check for errors immediately after loading shaders
   - Done through FileResolution
   - Add class SourceChecks to
 - Throw away GatherContextEvent, do everything on client init
 - Unify FileResolution and Resolver, PartialModel style
 - Pass around error reporter to detect errors during load
This commit is contained in:
Jozufozu 2022-05-17 12:56:09 -07:00
parent a7a716b469
commit c65992ec0e
51 changed files with 443 additions and 551 deletions

View file

@ -76,15 +76,16 @@ public class Flywheel {
forgeEventBus.<ReloadRenderersEvent>addListener(ProgramCompiler::invalidateAll);
forgeEventBus.addListener(Models::onReload);
modEventBus.addListener(LayoutShaders::flwInit);
modEventBus.addListener(InstanceShaders::flwInit);
modEventBus.addListener(MaterialShaders::flwInit);
modEventBus.addListener(Contexts::flwInit);
modEventBus.addListener(PartialModel::onModelRegistry);
modEventBus.addListener(PartialModel::onModelBake);
modEventBus.addListener(StitchedSprite::onTextureStitchPre);
modEventBus.addListener(StitchedSprite::onTextureStitchPost);
LayoutShaders.init();
InstanceShaders.init();
Contexts.init();
MaterialShaders.init();
VanillaInstances.init();
// https://github.com/Jozufozu/Flywheel/issues/69

View file

@ -1,18 +1,17 @@
package com.jozufozu.flywheel.backend;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.core.GameStateRegistry;
import com.jozufozu.flywheel.core.crumbling.CrumblingRenderer;
import com.jozufozu.flywheel.core.source.Resolver;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.core.source.ShaderLoadingException;
import com.jozufozu.flywheel.core.source.ShaderSources;
import com.jozufozu.flywheel.event.GatherContextEvent;
import com.jozufozu.flywheel.core.source.error.ErrorReporter;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.server.packs.resources.ReloadableResourceManager;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.ResourceManagerReloadListener;
import net.minecraftforge.fml.ModLoader;
/**
* The main entity for loading shaders.
@ -22,7 +21,6 @@ import net.minecraftforge.fml.ModLoader;
* </p>
*/
public class Loader implements ResourceManagerReloadListener {
private boolean firstLoad = true;
Loader() {
// Can be null when running datagenerators due to the unfortunate time we call this
@ -39,15 +37,15 @@ public class Loader implements ResourceManagerReloadListener {
public void onResourceManagerReload(ResourceManager manager) {
Backend.refresh();
GameStateRegistry._clear();
var errorReporter = new ErrorReporter();
ShaderSources sources = new ShaderSources(errorReporter, manager);
Resolver.INSTANCE.invalidate();
ModLoader.get()
.postEvent(new GatherContextEvent(firstLoad));
FileResolution.run(errorReporter, sources);
ShaderSources sources = new ShaderSources(manager);
Resolver.INSTANCE.run(sources);
if (errorReporter.hasErrored()) {
errorReporter.dump();
throw new ShaderLoadingException("Failed to resolve all source files, see log for details");
}
Backend.LOGGER.info("Loaded all shader sources.");
@ -58,6 +56,5 @@ public class Loader implements ResourceManagerReloadListener {
CrumblingRenderer.reset();
}
firstLoad = false;
}
}

View file

@ -1,8 +1,16 @@
package com.jozufozu.flywheel.backend.gl;
public enum GLSLVersion {
V110(110),
V120(120),
V130(130),
V140(140),
V150(150),
V330(330),
V400(400),
V410(410),
V420(420),
V430(430),
;
public final int version;

View file

@ -1,34 +1,37 @@
package com.jozufozu.flywheel.backend.gl.shader;
import java.io.File;
import java.io.FileWriter;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.lwjgl.opengl.GL20;
import com.jozufozu.flywheel.backend.gl.GlObject;
import com.jozufozu.flywheel.backend.gl.versioned.GlCompat;
import com.jozufozu.flywheel.core.shader.ShaderConstants;
import com.jozufozu.flywheel.core.source.ShaderLoadingException;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
public class GlShader extends GlObject {
public final ResourceLocation name;
public final ShaderType type;
private final List<ResourceLocation> parts;
private final ShaderConstants constants;
public GlShader(ResourceLocation name, ShaderType type, String source) {
this.name = name;
public GlShader(String source, ShaderType type, List<ResourceLocation> parts, ShaderConstants constants) {
this.parts = parts;
this.type = type;
this.constants = constants;
int handle = GL20.glCreateShader(type.glEnum);
GlCompat.safeShaderSource(handle, source);
GL20.glCompileShader(handle);
// File dir = new File(Minecraft.getInstance().gameDirectory, "flywheel_sources");
// dir.mkdirs();
// File file = new File(dir, name.toString().replaceAll("[:/]", "_"));
// try (FileWriter writer = new FileWriter(file)) {
// writer.write(source);
// } catch (Exception e) {
// e.printStackTrace();
// }
dumpSource(source, type);
// String log = GL20.glGetShaderInfoLog(handle);
//
@ -38,7 +41,7 @@ public class GlShader extends GlObject {
// }
if (GL20.glGetShaderi(handle, GL20.GL_COMPILE_STATUS) != GL20.GL_TRUE) {
throw new ShaderLoadingException("Could not compile " + name + ". See log for details.");
throw new ShaderLoadingException("Could not compile " + getName() + ". See log for details.");
}
setHandle(handle);
@ -49,4 +52,22 @@ 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(";")) + ';' + Integer.toHexString(constants.hashCode());
}
private void dumpSource(String source, ShaderType type) {
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();
}
}
}

View file

@ -3,21 +3,27 @@ package com.jozufozu.flywheel.backend.gl.shader;
import org.lwjgl.opengl.GL20;
public enum ShaderType {
VERTEX("vertex", "VERTEX_SHADER", GL20.GL_VERTEX_SHADER),
FRAGMENT("fragment", "FRAGMENT_SHADER", GL20.GL_FRAGMENT_SHADER),
VERTEX("vertex", "VERTEX_SHADER", "vert", GL20.GL_VERTEX_SHADER),
FRAGMENT("fragment", "FRAGMENT_SHADER", "frag", GL20.GL_FRAGMENT_SHADER),
;
public final String name;
public final String define;
public final String extension;
public final int glEnum;
ShaderType(String name, String define, int glEnum) {
ShaderType(String name, String define, String extension, int glEnum) {
this.name = name;
this.define = define;
this.extension = extension;
this.glEnum = glEnum;
}
public String getDefineStatement() {
return "#define " + define + "\n";
}
public String getFileName(String baseName) {
return baseName + "." + extension;
}
}

View file

@ -20,7 +20,6 @@ import com.jozufozu.flywheel.core.CoreShaderInfoMap.CoreShaderInfo;
import com.jozufozu.flywheel.core.GameStateRegistry;
import com.jozufozu.flywheel.core.RenderContext;
import com.jozufozu.flywheel.core.compile.ProgramCompiler;
import com.jozufozu.flywheel.core.compile.ProgramContext;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.core.vertex.Formats;
import com.jozufozu.flywheel.util.Textures;
@ -155,7 +154,7 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
alphaDiscard = 0;
}
P program = context.getProgram(new ProgramContext(Formats.POS_TEX_NORMAL, instanceType.getInstanceShader(), material.getVertexShader(), material.getFragmentShader(), alphaDiscard, coreShaderInfo.fogType(), GameStateRegistry.takeSnapshot()));
P program = context.getProgram(new ProgramCompiler.Context(Formats.POS_TEX_NORMAL, instanceType.getInstanceShader(), material.getVertexShader(), material.getFragmentShader(), alphaDiscard, coreShaderInfo.fogType(), GameStateRegistry.takeSnapshot()));
program.bind();
program.uploadUniforms(camX, camY, camZ, viewProjection, level);

View file

@ -1,13 +1,13 @@
package com.jozufozu.flywheel.core;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.core.compile.ProgramCompiler;
import com.jozufozu.flywheel.core.crumbling.CrumblingProgram;
import com.jozufozu.flywheel.core.shader.NormalDebugStateProvider;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.core.source.Resolver;
import com.jozufozu.flywheel.event.GatherContextEvent;
import com.jozufozu.flywheel.core.source.SourceChecks;
import com.jozufozu.flywheel.util.ResourceUtil;
import net.minecraft.resources.ResourceLocation;
@ -17,16 +17,23 @@ public class Contexts {
public static ProgramCompiler<WorldProgram> WORLD;
public static ProgramCompiler<CrumblingProgram> CRUMBLING;
public static void flwInit(GatherContextEvent event) {
public static void init() {
GameStateRegistry.register(NormalDebugStateProvider.INSTANCE);
FileResolution worldVertex = Resolver.INSTANCE.get(ResourceUtil.subPath(Names.WORLD, ".vert"));
FileResolution worldFragment = Resolver.INSTANCE.get(ResourceUtil.subPath(Names.WORLD, ".frag"));
FileResolution crumblingVertex = Resolver.INSTANCE.get(ResourceUtil.subPath(Names.CRUMBLING, ".vert"));
FileResolution crumblingFragment = Resolver.INSTANCE.get(ResourceUtil.subPath(Names.CRUMBLING, ".frag"));
var checkFrag = SourceChecks.checkFunctionArity("flw_contextFragment", 0);
var checkVert = SourceChecks.checkFunctionArity("flw_contextVertex", 0);
WORLD = ProgramCompiler.create(WorldProgram::new, worldVertex, worldFragment);
CRUMBLING = ProgramCompiler.create(CrumblingProgram::new, crumblingVertex, crumblingFragment);
var worldVertex = FileResolution.get(ResourceUtil.subPath(Names.WORLD, ".vert"))
.validateWith(checkVert);
var worldFragment = FileResolution.get(ResourceUtil.subPath(Names.WORLD, ".frag"))
.validateWith(checkFrag);
var crumblingVertex = FileResolution.get(ResourceUtil.subPath(Names.CRUMBLING, ".vert"))
.validateWith(checkVert);
var crumblingFragment = FileResolution.get(ResourceUtil.subPath(Names.CRUMBLING, ".frag"))
.validateWith(checkFrag);
WORLD = ProgramCompiler.create(WorldProgram::new, worldVertex, worldFragment, GLSLVersion.V330);
CRUMBLING = ProgramCompiler.create(CrumblingProgram::new, crumblingVertex, crumblingFragment, GLSLVersion.V330);
}
public static class Names {

View file

@ -51,8 +51,4 @@ public class GameStateRegistry {
}
return shaderConstants;
}
public static void _clear() {
PROVIDERS.clear();
}
}

View file

@ -2,9 +2,14 @@ package com.jozufozu.flywheel.core.compile;
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.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.core.source.SourceFile;
public class CompileUtil {
@ -45,4 +50,11 @@ public class CompileUtil {
return 1;
}
@NotNull
public static String generateDebugName(SourceFile... stages) {
return Stream.of(stages)
.map(SourceFile::toString)
.collect(Collectors.joining(" -> "));
}
}

View file

@ -1,7 +1,6 @@
package com.jozufozu.flywheel.core.compile;
import java.util.Objects;
import java.util.Optional;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
@ -12,92 +11,57 @@ import com.jozufozu.flywheel.core.shader.ShaderConstants;
import com.jozufozu.flywheel.core.shader.StateSnapshot;
import com.jozufozu.flywheel.core.source.FileIndexImpl;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.core.source.ShaderLoadingException;
import com.jozufozu.flywheel.core.source.SourceFile;
import com.jozufozu.flywheel.core.source.error.ErrorReporter;
import com.jozufozu.flywheel.core.source.parse.ShaderFunction;
import com.jozufozu.flywheel.core.source.parse.Variable;
/**
* Handles compilation and deletion of fragment shaders.
*/
public class FragmentCompiler extends Memoizer<FragmentCompiler.Context, GlShader> {
private final FileResolution contextShader;
private final GLSLVersion glslVersion;
public FragmentCompiler(FileResolution contextShader) {
public FragmentCompiler(FileResolution contextShader, GLSLVersion glslVersion) {
this.contextShader = contextShader;
this.glslVersion = glslVersion;
}
@Override
protected GlShader _create(Context key) {
StringBuilder finalSource = new StringBuilder();
finalSource.append(CompileUtil.generateHeader(GLSLVersion.V150, ShaderType.FRAGMENT));
finalSource.append(CompileUtil.generateHeader(glslVersion, ShaderType.FRAGMENT));
key.getShaderConstants().writeInto(finalSource);
ShaderConstants shaderConstants = key.getShaderConstants();
shaderConstants.writeInto(finalSource);
finalSource.append('\n');
FileIndexImpl index = new FileIndexImpl();
//
// MATERIAL
SourceFile materialShader = key.materialShader;
Optional<ShaderFunction> maybeMaterialFragment = materialShader.findFunction("flw_materialFragment");
if (maybeMaterialFragment.isEmpty()) {
ErrorReporter.generateMissingFunction(materialShader, "flw_materialFragment", "\"flw_materialFragment\" function not defined");
throw new ShaderLoadingException();
}
ShaderFunction materialFragment = maybeMaterialFragment.get();
ImmutableList<Variable> params = materialFragment.getParameters();
if (params.size() != 0) {
ErrorReporter.generateSpanError(materialFragment.getArgs(), "\"flw_materialFragment\" function must not have any arguments");
throw new ShaderLoadingException();
}
materialShader.generateFinalSource(index, finalSource);
//
// CONTEXT
SourceFile contextShaderSource = contextShader.getFile();
Optional<ShaderFunction> maybeContextFragment = contextShaderSource.findFunction("flw_contextFragment");
if (maybeContextFragment.isEmpty()) {
ErrorReporter.generateMissingFunction(contextShaderSource, "flw_contextFragment", "\"flw_contextFragment\" function not defined");
throw new ShaderLoadingException();
}
ShaderFunction contextFragment = maybeContextFragment.get();
params = contextFragment.getParameters();
if (params.size() != 0) {
ErrorReporter.generateSpanError(contextFragment.getArgs(), "\"flw_contextFragment\" function must not have any arguments");
throw new ShaderLoadingException();
}
contextShaderSource.generateFinalSource(index, finalSource);
//
// MAIN
finalSource.append(generateFooter());
return new GlShader(contextShader.getFile().name, ShaderType.FRAGMENT, finalSource.toString());
return new GlShader(finalSource.toString(), ShaderType.FRAGMENT, ImmutableList.of(materialShader.name, contextShaderSource.name), shaderConstants);
}
protected String generateFooter() {
StringBuilder footer = new StringBuilder();
footer.append("""
return """
void main() {
flw_materialFragment();
flw_contextFragment();
}
"""
);
return footer.toString();
""";
}
@Override
@ -107,34 +71,12 @@ public class FragmentCompiler extends Memoizer<FragmentCompiler.Context, GlShade
/**
* Represents the conditions under which a shader is compiled.
* @param materialShader The fragment material shader source.
* @param alphaDiscard Alpha threshold below which fragments are discarded.
* @param fogType Which type of fog should be applied.
* @param ctx The shader constants to apply.
*/
public static final class Context {
/**
* The fragment material shader source.
*/
private final SourceFile materialShader;
/**
* Alpha threshold below which fragments are discarded.
*/
private final float alphaDiscard;
/**
* Which type of fog should be applied.
*/
private final FogType fogType;
/**
* The shader constants to apply.
*/
private final StateSnapshot ctx;
public Context(SourceFile materialShader, float alphaDiscard, FogType fogType, StateSnapshot ctx) {
this.materialShader = materialShader;
this.alphaDiscard = alphaDiscard;
this.fogType = fogType;
this.ctx = ctx;
}
public record Context(SourceFile materialShader, float alphaDiscard, FogType fogType, StateSnapshot ctx) {
public ShaderConstants getShaderConstants() {
ShaderConstants shaderConstants = ctx.getShaderConstants();
@ -146,23 +88,5 @@ public class FragmentCompiler extends Memoizer<FragmentCompiler.Context, GlShade
return shaderConstants;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (Context) obj;
return materialShader == that.materialShader && Objects.equals(this.ctx, that.ctx) && Float.floatToIntBits(this.alphaDiscard) == Float.floatToIntBits(that.alphaDiscard) && fogType == that.fogType;
}
@Override
public int hashCode() {
return Objects.hash(materialShader, alphaDiscard, fogType, ctx);
}
@Override
public String toString() {
return "Context[" + "materialShader=" + materialShader + ", " + "alphaDiscard=" + alphaDiscard + ", " + "fogType=" + fogType + ", " + "ctx=" + ctx + ']';
}
}
}

View file

@ -21,8 +21,6 @@ public class ProgramAssembler {
public final int program;
private final ResourceLocation name;
private final List<GlShader> shaders = new ObjectArrayList<>();
public ProgramAssembler(ResourceLocation name) {
this.name = name;
this.program = glCreateProgram();
@ -49,13 +47,7 @@ public class ProgramAssembler {
return this;
}
public ProgramAssembler deleteLinkedShaders() {
shaders.forEach(GlShader::delete);
return this;
}
public ProgramAssembler attachShader(GlShader glShader) {
shaders.add(glShader);
glAttachShader(this.program, glShader.handle());
return this;
}

View file

@ -3,7 +3,11 @@ package com.jozufozu.flywheel.core.compile;
import java.util.ArrayList;
import java.util.List;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.core.CoreShaderInfoMap;
import com.jozufozu.flywheel.core.shader.StateSnapshot;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
@ -14,8 +18,11 @@ import com.jozufozu.flywheel.event.ReloadRenderersEvent;
* This class is responsible for compiling programs on the fly. An instance of this class will keep a cache of
* compiled programs, and will only compile a program if it is not already in the cache.
* </p>
* <p>
* A ProgramCompiler is also responsible for deleting programs and shaders on renderer reload.
* </p>
*/
public class ProgramCompiler<P extends GlProgram> extends Memoizer<ProgramContext, P> {
public class ProgramCompiler<P extends GlProgram> extends Memoizer<ProgramCompiler.Context, P> {
private static final List<ProgramCompiler<?>> ALL_COMPILERS = new ArrayList<>();
@ -39,8 +46,8 @@ public class ProgramCompiler<P extends GlProgram> extends Memoizer<ProgramContex
* @param <P> The type of program to compile.
* @return A program compiler.
*/
public static <P extends GlProgram> ProgramCompiler<P> create(GlProgram.Factory<P> factory, FileResolution vertexContextShader, FileResolution fragmentContextShader) {
return new ProgramCompiler<>(factory, new VertexCompiler(vertexContextShader), new FragmentCompiler(fragmentContextShader));
public static <P extends GlProgram> ProgramCompiler<P> create(GlProgram.Factory<P> factory, FileResolution vertexContextShader, FileResolution fragmentContextShader, GLSLVersion glslVersion) {
return new ProgramCompiler<>(factory, new VertexCompiler(vertexContextShader, glslVersion), new FragmentCompiler(fragmentContextShader, glslVersion));
}
/**
@ -49,7 +56,7 @@ public class ProgramCompiler<P extends GlProgram> extends Memoizer<ProgramContex
* @param ctx The context of compilation.
* @return A compiled GlProgram.
*/
public P getProgram(ProgramContext ctx) {
public P getProgram(Context ctx) {
return super.get(ctx);
}
@ -61,10 +68,10 @@ public class ProgramCompiler<P extends GlProgram> extends Memoizer<ProgramContex
}
@Override
protected P _create(ProgramContext ctx) {
return new ProgramAssembler(ctx.instanceShader.getFileLoc())
.attachShader(vertexCompiler.get(new VertexCompiler.Context(ctx.vertexType, ctx.instanceShader.getFile(), ctx.vertexMaterialShader.getFile(), ctx.ctx)))
.attachShader(fragmentCompiler.get(new FragmentCompiler.Context(ctx.fragmentMaterialShader.getFile(), ctx.alphaDiscard, ctx.fogType, ctx.ctx)))
protected P _create(Context ctx) {
return new ProgramAssembler(ctx.instanceShader().getFileLoc())
.attachShader(vertexCompiler.get(new VertexCompiler.Context(ctx.vertexType(), ctx.instanceShader().getFile(), ctx.vertexMaterialShader().getFile(), ctx.ctx())))
.attachShader(fragmentCompiler.get(new FragmentCompiler.Context(ctx.fragmentMaterialShader().getFile(), ctx.alphaDiscard(), ctx.fogType(), ctx.ctx())))
.link()
.build(this.factory);
}
@ -77,4 +84,19 @@ public class ProgramCompiler<P extends GlProgram> extends Memoizer<ProgramContex
public static void invalidateAll(ReloadRenderersEvent ignored) {
ALL_COMPILERS.forEach(ProgramCompiler::invalidate);
}
/**
* Represents the entire context of a program's usage.
*
* @param vertexType The vertexType the program should be adapted for.
* @param instanceShader The instance shader to use.
* @param vertexMaterialShader The vertex material shader to use.
* @param fragmentMaterialShader The fragment material shader to use.
* @param alphaDiscard Alpha threshold below which pixels are discarded.
* @param fogType Which type of fog should be applied.
* @param ctx A snapshot of the game state.
*/
public record Context(VertexType vertexType, FileResolution instanceShader, FileResolution vertexMaterialShader,
FileResolution fragmentMaterialShader, float alphaDiscard, CoreShaderInfoMap.CoreShaderInfo.FogType fogType, StateSnapshot ctx) {
}
}

View file

@ -1,59 +0,0 @@
package com.jozufozu.flywheel.core.compile;
import java.util.Objects;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.core.CoreShaderInfoMap.CoreShaderInfo.FogType;
import com.jozufozu.flywheel.core.shader.StateSnapshot;
import com.jozufozu.flywheel.core.source.FileResolution;
/**
* Represents the entire context of a program's usage.
*/
public final class ProgramContext {
public final VertexType vertexType;
public final FileResolution instanceShader;
public final FileResolution vertexMaterialShader;
public final FileResolution fragmentMaterialShader;
public final float alphaDiscard;
public final FogType fogType;
public final StateSnapshot ctx;
/**
* @param vertexType The vertexType the program should be adapted for.
* @param instanceShader The instance shader to use.
* @param vertexMaterialShader The vertex material shader to use.
* @param fragmentMaterialShader The fragment material shader to use.
* @param alphaDiscard Alpha threshold below which pixels are discarded.
* @param fogType Which type of fog should be applied.
* @param ctx A snapshot of the game state.
*/
public ProgramContext(VertexType vertexType, FileResolution instanceShader, FileResolution vertexMaterialShader, FileResolution fragmentMaterialShader, float alphaDiscard, FogType fogType, StateSnapshot ctx) {
this.vertexType = vertexType;
this.instanceShader = instanceShader;
this.vertexMaterialShader = vertexMaterialShader;
this.fragmentMaterialShader = fragmentMaterialShader;
this.alphaDiscard = alphaDiscard;
this.fogType = fogType;
this.ctx = ctx;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
var that = (ProgramContext) o;
return vertexType == that.vertexType && instanceShader == that.instanceShader && vertexMaterialShader == that.vertexMaterialShader && fragmentMaterialShader == that.fragmentMaterialShader && ctx.equals(that.ctx) && Float.floatToIntBits(alphaDiscard) == Float.floatToIntBits(that.alphaDiscard) && fogType == that.fogType;
}
@Override
public int hashCode() {
return Objects.hash(vertexType, instanceShader, vertexMaterialShader, fragmentMaterialShader, alphaDiscard, fogType, ctx);
}
@Override
public String toString() {
return "ProgramContext{" + "vertexType=" + vertexType + ", instanceShader=" + instanceShader + ", vertexMaterialShader=" + vertexMaterialShader + ", fragmentMaterialShader=" + fragmentMaterialShader + ", alphaDiscard=" + alphaDiscard + ", fogType=" + fogType + ", ctx=" + ctx + '}';
}
}

View file

@ -1,8 +1,5 @@
package com.jozufozu.flywheel.core.compile;
import java.util.Objects;
import java.util.Optional;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
@ -11,132 +8,63 @@ import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.core.shader.StateSnapshot;
import com.jozufozu.flywheel.core.source.FileIndexImpl;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.core.source.ShaderLoadingException;
import com.jozufozu.flywheel.core.source.SourceFile;
import com.jozufozu.flywheel.core.source.error.ErrorReporter;
import com.jozufozu.flywheel.core.source.parse.ShaderFunction;
import com.jozufozu.flywheel.core.source.parse.ShaderStruct;
import com.jozufozu.flywheel.core.source.parse.StructField;
import com.jozufozu.flywheel.core.source.parse.Variable;
import com.jozufozu.flywheel.core.source.span.Span;
/**
* Handles compilation and deletion of vertex shaders.
*/
public class VertexCompiler extends Memoizer<VertexCompiler.Context, GlShader> {
private final FileResolution contextShader;
private final GLSLVersion glslVersion;
public VertexCompiler(FileResolution contextShader) {
public VertexCompiler(FileResolution contextShader, GLSLVersion glslVersion) {
this.contextShader = contextShader;
this.glslVersion = glslVersion;
}
@Override
protected GlShader _create(Context key) {
StringBuilder finalSource = new StringBuilder();
finalSource.append(CompileUtil.generateHeader(GLSLVersion.V330, ShaderType.VERTEX));
finalSource.append(CompileUtil.generateHeader(glslVersion, ShaderType.VERTEX));
key.ctx.getShaderConstants().writeInto(finalSource);
var shaderConstants = key.ctx.getShaderConstants();
shaderConstants.writeInto(finalSource);
finalSource.append('\n');
FileIndexImpl index = new FileIndexImpl();
var index = new FileIndexImpl();
//
SourceFile layoutShader = key.vertexType.getLayoutShader().getFile();
Optional<ShaderFunction> maybeLayoutVertex = layoutShader.findFunction("flw_layoutVertex");
if (maybeLayoutVertex.isEmpty()) {
ErrorReporter.generateMissingFunction(layoutShader, "flw_layoutVertex", "\"flw_layoutVertex\" function not defined");
throw new ShaderLoadingException();
}
ShaderFunction layoutVertex = maybeLayoutVertex.get();
ImmutableList<Variable> params = layoutVertex.getParameters();
if (params.size() != 0) {
ErrorReporter.generateSpanError(layoutVertex.getArgs(), "\"flw_layoutVertex\" function must not have any arguments");
throw new ShaderLoadingException();
}
// LAYOUT
var layoutShader = key.vertexType.getLayoutShader().getFile();
layoutShader.generateFinalSource(index, finalSource);
//
SourceFile instanceShader = key.instanceShader;
Optional<ShaderFunction> maybeInstanceVertex = instanceShader.findFunction("flw_instanceVertex");
if (maybeInstanceVertex.isEmpty()) {
ErrorReporter.generateMissingFunction(instanceShader, "flw_instanceVertex", "\"flw_instanceVertex\" function not defined");
throw new ShaderLoadingException();
}
ShaderFunction instanceVertex = maybeInstanceVertex.get();
params = instanceVertex.getParameters();
if (params.size() != 1) {
ErrorReporter.generateSpanError(instanceVertex.getArgs(), "\"flw_instanceVertex\" function must have exactly 1 argument");
throw new ShaderLoadingException();
}
Span instanceName = params.get(0).type;
Optional<ShaderStruct> maybeInstance = instanceShader.findStruct(instanceName);
if (maybeInstance.isEmpty()) {
ErrorReporter.generateMissingStruct(instanceShader, instanceName, "instance struct not defined");
throw new ShaderLoadingException();
}
ShaderStruct instance = maybeInstance.get();
// INSTANCE
var instanceShader = key.instanceShader;
instanceShader.generateFinalSource(index, finalSource);
//
SourceFile materialShader = key.materialShader;
Optional<ShaderFunction> maybeMaterialVertex = materialShader.findFunction("flw_materialVertex");
if (maybeMaterialVertex.isEmpty()) {
ErrorReporter.generateMissingFunction(materialShader, "flw_materialVertex", "\"flw_materialVertex\" function not defined");
throw new ShaderLoadingException();
}
ShaderFunction materialVertex = maybeMaterialVertex.get();
params = materialVertex.getParameters();
if (params.size() != 0) {
ErrorReporter.generateSpanError(materialVertex.getArgs(), "\"flw_materialVertex\" function must not have any arguments");
throw new ShaderLoadingException();
}
// MATERIAL
var materialShader = key.materialShader;
materialShader.generateFinalSource(index, finalSource);
//
SourceFile contextShaderSource = contextShader.getFile();
Optional<ShaderFunction> maybeContextVertex = contextShaderSource.findFunction("flw_contextVertex");
if (maybeContextVertex.isEmpty()) {
ErrorReporter.generateMissingFunction(contextShaderSource, "flw_contextVertex", "\"flw_contextVertex\" function not defined");
throw new ShaderLoadingException();
}
ShaderFunction contextVertex = maybeContextVertex.get();
params = contextVertex.getParameters();
if (params.size() != 0) {
ErrorReporter.generateSpanError(contextVertex.getArgs(), "\"flw_contextVertex\" function must not have any arguments");
throw new ShaderLoadingException();
}
// CONTEXT
var contextShaderSource = contextShader.getFile();
contextShaderSource.generateFinalSource(index, finalSource);
//
// MAIN
finalSource.append(generateFooter(key.vertexType, instance));
var instanceStruct = instanceShader.findFunction("flw_instanceVertex")
.flatMap(f -> f.getParameterType(0)
.findStruct())
.orElseThrow();
finalSource.append(generateFooter(key.vertexType, instanceStruct));
return new GlShader(instanceShader.name, ShaderType.VERTEX, finalSource.toString());
return new GlShader(finalSource.toString(), ShaderType.VERTEX, ImmutableList.of(layoutShader.name, instanceShader.name, materialShader.name, contextShaderSource.name), shaderConstants);
}
protected String generateFooter(VertexType vertexType, ShaderStruct instance) {
@ -203,45 +131,12 @@ public class VertexCompiler extends Memoizer<VertexCompiler.Context, GlShader> {
value.delete();
}
public static class Context {
/**
* The vertex type to use.
*/
private final VertexType vertexType;
/**
* The instance shader source.
*/
private final SourceFile instanceShader;
/**
* The vertex material shader source.
*/
private final SourceFile materialShader;
/**
* The shader constants to apply.
*/
private final StateSnapshot ctx;
public Context(VertexType vertexType, SourceFile instanceShader, SourceFile materialShader, StateSnapshot ctx) {
this.vertexType = vertexType;
this.instanceShader = instanceShader;
this.materialShader = materialShader;
this.ctx = ctx;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
var that = (Context) o;
return vertexType == that.vertexType && instanceShader == that.instanceShader && materialShader == that.materialShader && ctx.equals(that.ctx);
}
@Override
public int hashCode() {
return Objects.hash(vertexType, instanceShader, materialShader, ctx);
}
/**
* @param vertexType The vertex type to use.
* @param instanceShader The instance shader source.
* @param materialShader The vertex material shader source.
* @param ctx The shader constants to apply.
*/
public record Context(VertexType vertexType, SourceFile instanceShader, SourceFile materialShader, StateSnapshot ctx) {
}
}

View file

@ -2,8 +2,7 @@ package com.jozufozu.flywheel.core.material;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.core.source.Resolver;
import com.jozufozu.flywheel.event.GatherContextEvent;
import com.jozufozu.flywheel.core.source.SourceChecks;
import com.jozufozu.flywheel.util.ResourceUtil;
import net.minecraft.resources.ResourceLocation;
@ -13,10 +12,16 @@ public class MaterialShaders {
public static FileResolution DEFAULT_FRAGMENT;
public static FileResolution SHADED_VERTEX;
public static void flwInit(GatherContextEvent event) {
DEFAULT_VERTEX = Resolver.INSTANCE.get(ResourceUtil.subPath(Names.DEFAULT, ".vert"));
DEFAULT_FRAGMENT = Resolver.INSTANCE.get(ResourceUtil.subPath(Names.DEFAULT, ".frag"));
SHADED_VERTEX = Resolver.INSTANCE.get(ResourceUtil.subPath(Names.SHADED, ".vert"));
public static void init() {
var checkVert = SourceChecks.checkFunctionArity("flw_materialVertex", 0);
var checkFrag = SourceChecks.checkFunctionArity("flw_materialFragment", 0);
DEFAULT_VERTEX = FileResolution.get(ResourceUtil.subPath(Names.DEFAULT, ".vert"))
.validateWith(checkVert);
DEFAULT_FRAGMENT = FileResolution.get(ResourceUtil.subPath(Names.DEFAULT, ".frag"))
.validateWith(checkFrag);
SHADED_VERTEX = FileResolution.get(ResourceUtil.subPath(Names.SHADED, ".vert"))
.validateWith(checkVert);
}
public static class Names {

View file

@ -3,6 +3,7 @@ package com.jozufozu.flywheel.core.shader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* A class for manipulating a list of {@code #define} directives.
@ -52,4 +53,17 @@ public class ShaderConstants {
acc.append('\n');
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ShaderConstants that = (ShaderConstants) o;
return Objects.equals(definitions, that.definitions);
}
@Override
public int hashCode() {
return Objects.hash(definitions);
}
}

View file

@ -33,7 +33,7 @@ public class FileIndexImpl implements FileIndex {
@Override
public boolean exists(SourceFile sourceFile) {
return files.indexOf(sourceFile) != -1;
return files.contains(sourceFile);
}
@Override
@ -68,10 +68,7 @@ public class FileIndexImpl implements FileIndex {
@Nullable
private ErrorBuilder parseCompilerError(String line) {
try {
ErrorBuilder error = ErrorBuilder.fromLogLine(this, line);
if (error != null) {
return error;
}
return ErrorBuilder.fromLogLine(this, line);
} catch (Exception ignored) {
}

View file

@ -1,11 +1,13 @@
package com.jozufozu.flywheel.core.source;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.function.Consumer;
import java.util.Map;
import java.util.function.BiConsumer;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.core.source.error.ErrorBuilder;
import com.jozufozu.flywheel.core.source.error.ErrorReporter;
import com.jozufozu.flywheel.core.source.span.Span;
import net.minecraft.resources.ResourceLocation;
@ -21,17 +23,73 @@ import net.minecraft.resources.ResourceLocation;
*/
public class FileResolution {
private static final Map<ResourceLocation, FileResolution> ALL = new HashMap<>();
private static boolean tooLate = false;
/**
* Extra info about where this resolution is required. Includes ProgramSpecs and shader Spans.
* Extra info about where this resolution is required. Includes shader Spans.
*/
private final List<Consumer<ErrorBuilder>> extraCrashInfoProviders = new ArrayList<>();
private final List<Span> neededAt = new ArrayList<>();
private final List<BiConsumer<ErrorReporter, SourceFile>> checks = new ArrayList<>();
private final ResourceLocation fileLoc;
private SourceFile file;
FileResolution(ResourceLocation fileLoc) {
this.fileLoc = fileLoc;
}
public static FileResolution get(ResourceLocation file) {
if (!tooLate) {
return ALL.computeIfAbsent(file, FileResolution::new);
} else {
// Lock the map after resolution has run.
FileResolution fileResolution = ALL.get(file);
// ...so crash immediately if the file isn't found.
if (fileResolution == null) {
throw new ShaderLoadingException("could not find source for file: " + file);
}
return fileResolution;
}
}
/**
* Try and resolve all referenced source files, printing errors if any aren't found.
*/
public static void run(ErrorReporter errorReporter, SourceFinder sources) {
for (FileResolution resolution : ALL.values()) {
resolution.resolveAndCheck(errorReporter, sources);
}
tooLate = true;
}
private void resolveAndCheck(ErrorReporter errorReporter, SourceFinder sources) {
file = sources.findSource(fileLoc);
if (file == null) {
ErrorBuilder builder = errorReporter.error(String.format("could not find source for file %s", fileLoc));
for (Span location : neededAt) {
builder.pointAtFile(location.getSourceFile())
.pointAt(location, 1);
}
} else {
runChecks(errorReporter);
}
// Let the GC do its thing
neededAt.clear();
}
private void runChecks(ErrorReporter errorReporter) {
for (var check : checks) {
check.accept(errorReporter, file);
}
}
public ResourceLocation getFileLoc() {
return fileLoc;
}
@ -53,45 +111,13 @@ public class FileResolution {
* @param span A span where this file is referenced.
*/
public FileResolution addSpan(Span span) {
extraCrashInfoProviders.add(builder -> builder.pointAtFile(span.getSourceFile())
.pointAt(span, 1));
neededAt.add(span);
return this;
}
public void addSpec(ResourceLocation name) {
extraCrashInfoProviders.add(builder -> builder.extra("needed by spec: " + name + ".json"));
}
/**
* Check to see if this file actually resolves to something.
*
* <p>
* Called after all files are loaded. If we can't find the file here, it doesn't exist.
* </p>
*
* @return True if this file is resolved.
*/
boolean resolve(SourceFinder sources) {
file = sources.findSource(fileLoc);
if (file == null) {
ErrorBuilder builder = ErrorBuilder.error(String.format("could not find source for file %s", fileLoc));
for (Consumer<ErrorBuilder> consumer : extraCrashInfoProviders) {
consumer.accept(builder);
}
Backend.LOGGER.error(builder.build());
return false;
}
// Let the GC do its thing
extraCrashInfoProviders.clear();
return true;
}
void invalidate() {
extraCrashInfoProviders.clear();
file = null;
public FileResolution validateWith(BiConsumer<ErrorReporter, SourceFile> check) {
checks.add(check);
return this;
}
@Override

View file

@ -1,70 +0,0 @@
package com.jozufozu.flywheel.core.source;
import java.util.HashMap;
import java.util.Map;
import net.minecraft.resources.ResourceLocation;
/**
* Manages deferred file resolution.
*
* <p>
* Interns all file names in shader sources and program specs, deduplicating the final lookups and allowing for more
* dev-friendly error reporting.
* </p>
*
* @see FileResolution
*/
public class Resolver {
public static final Resolver INSTANCE = new Resolver();
private final Map<ResourceLocation, FileResolution> resolutions = new HashMap<>();
private boolean hasRun = false;
public FileResolution get(ResourceLocation file) {
if (!hasRun) {
return resolutions.computeIfAbsent(file, FileResolution::new);
} else {
// Lock the map after resolution has run.
FileResolution fileResolution = resolutions.get(file);
// ...so crash immediately if the file isn't found.
if (fileResolution == null) {
throw new RuntimeException("could not find source for file: " + file);
}
return fileResolution;
}
}
/**
* Try and resolve all referenced source files, printing errors if any aren't found.
*/
public void run(SourceFinder sources) {
boolean needsCrash = false;
for (FileResolution resolution : resolutions.values()) {
if (!resolution.resolve(sources)) {
needsCrash = true;
}
}
if (needsCrash) {
throw new ShaderLoadingException("Failed to resolve all source files, see log for details");
}
hasRun = true;
}
/**
* Invalidates all FileResolutions.
*
* <p>
* Called on resource reload.
* </p>
*/
public void invalidate() {
resolutions.values().forEach(FileResolution::invalidate);
hasRun = false;
}
}

View file

@ -2,9 +2,6 @@ package com.jozufozu.flywheel.core.source;
public class ShaderLoadingException extends RuntimeException {
public ShaderLoadingException() {
}
public ShaderLoadingException(String message) {
super(message);
}

View file

@ -9,6 +9,7 @@ import java.util.Map;
import org.jetbrains.annotations.Nullable;
import com.google.common.collect.Lists;
import com.jozufozu.flywheel.core.source.error.ErrorReporter;
import com.jozufozu.flywheel.util.ResourceUtil;
import com.jozufozu.flywheel.util.StringUtil;
@ -20,14 +21,14 @@ import net.minecraft.server.packs.resources.ResourceManager;
* The main object for loading and parsing source files.
*/
public class ShaderSources implements SourceFinder {
public static final String SHADER_DIR = "flywheel/shaders/";
public static final String SHADER_DIR = "flywheel/";
public static final ArrayList<String> EXTENSIONS = Lists.newArrayList(".vert", ".vsh", ".frag", ".fsh", ".glsl");
private final Map<ResourceLocation, SourceFile> shaderSources = new HashMap<>();
public final Index index;
public ShaderSources(ResourceManager manager) {
public ShaderSources(ErrorReporter errorReporter, ResourceManager manager) {
Collection<ResourceLocation> allShaders = manager.listResources(SHADER_DIR, s -> {
for (String ext : EXTENSIONS) {
if (s.endsWith(ext)) return true;
@ -41,7 +42,7 @@ public class ShaderSources implements SourceFinder {
ResourceLocation name = ResourceUtil.removePrefixUnchecked(location, SHADER_DIR);
shaderSources.put(name, new SourceFile(this, name, source));
shaderSources.put(name, new SourceFile(errorReporter, this, name, source));
} catch (IOException e) {
//
}

View file

@ -0,0 +1,57 @@
package com.jozufozu.flywheel.core.source;
import java.util.Optional;
import java.util.function.BiConsumer;
import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.core.source.error.ErrorReporter;
import com.jozufozu.flywheel.core.source.parse.ShaderFunction;
import com.jozufozu.flywheel.core.source.parse.Variable;
public class SourceChecks {
public static BiConsumer<ErrorReporter, SourceFile> checkFunctionArity(String name, int arity) {
return (errorReporter, file) -> checkFunctionArity(errorReporter, file, name, arity);
}
public static BiConsumer<ErrorReporter, SourceFile> checkFunctionParameterTypeExists(String name, int arity, int param) {
return (errorReporter, file) -> {
var func = checkFunctionArity(errorReporter, file, name, arity);
if (func == null) {
return;
}
var maybeStruct = func.getParameterType(param)
.findStruct();
if (maybeStruct.isEmpty()) {
errorReporter.generateMissingStruct(file, func.getParameterType(param), "struct not defined");
}
};
}
/**
* @return {@code null} if the function doesn't exist, or if the function has the wrong arity.
*/
@Nullable
private static ShaderFunction checkFunctionArity(ErrorReporter errorReporter, SourceFile file, String name, int arity) {
Optional<ShaderFunction> maybeFunc = file.findFunction(name);
if (maybeFunc.isEmpty()) {
errorReporter.generateMissingFunction(file, name, "\"" + name + "\" function not defined");
return null;
}
ShaderFunction func = maybeFunc.get();
ImmutableList<Variable> params = func.getParameters();
if (params.size() != arity) {
errorReporter.generateFunctionArgumentCountError(name, arity, func.getArgs());
return null;
}
return func;
}
}

View file

@ -11,9 +11,11 @@ import java.util.regex.Matcher;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.jozufozu.flywheel.core.source.error.ErrorReporter;
import com.jozufozu.flywheel.core.source.parse.Import;
import com.jozufozu.flywheel.core.source.parse.ShaderFunction;
import com.jozufozu.flywheel.core.source.parse.ShaderStruct;
import com.jozufozu.flywheel.core.source.parse.Variable;
import com.jozufozu.flywheel.core.source.span.ErrorSpan;
import com.jozufozu.flywheel.core.source.span.Span;
import com.jozufozu.flywheel.core.source.span.StringSpan;
@ -55,7 +57,7 @@ public class SourceFile {
*/
public final ImmutableList<Import> imports;
public SourceFile(ShaderSources parent, ResourceLocation name, String source) {
public SourceFile(ErrorReporter errorReporter, ShaderSources parent, ResourceLocation name, String source) {
this.parent = parent;
this.name = name;
this.source = source;
@ -64,7 +66,7 @@ public class SourceFile {
List<Span> elisions = new ArrayList<>();
this.imports = parseImports(elisions);
this.imports = parseImports(errorReporter, elisions);
this.functions = parseFunctions();
this.structs = parseStructs();
@ -233,7 +235,7 @@ public class SourceFile {
* Records the contents of the directive into an {@link Import} object, and marks the directive for elision.
* @param elisions
*/
private ImmutableList<Import> parseImports(List<Span> elisions) {
private ImmutableList<Import> parseImports(ErrorReporter errorReporter, List<Span> elisions) {
Matcher uses = Import.PATTERN.matcher(source);
Set<String> importedFiles = new HashSet<>();
@ -245,9 +247,9 @@ public class SourceFile {
String fileName = file.get();
if (importedFiles.add(fileName)) {
Import import1 = Import.create(Resolver.INSTANCE, use, file);
if (import1 != null) {
imports.add(import1);
var checked = Import.create(errorReporter, use, file);
if (checked != null) {
imports.add(checked);
}
}
@ -280,4 +282,15 @@ public class SourceFile {
public String toString() {
return name.toString();
}
@Override
public boolean equals(Object o) {
// SourceFiles are only equal by reference.
return this == o;
}
@Override
public int hashCode() {
return System.identityHashCode(this);
}
}

View file

@ -1,5 +1,6 @@
package com.jozufozu.flywheel.core.source.error;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@ -13,58 +14,78 @@ import com.jozufozu.flywheel.util.FlwUtil;
public class ErrorReporter {
public static void generateSpanError(Span span, String message) {
SourceFile file = span.getSourceFile();
private final List<ErrorBuilder> reportedErrors = new ArrayList<>();
String error = ErrorBuilder.error(message)
.pointAtFile(file)
.pointAt(span, 2)
.build();
Backend.LOGGER.error(error);
}
public static void generateFileError(SourceFile file, String message) {
String error = ErrorBuilder.error(message)
.pointAtFile(file)
.build();
Backend.LOGGER.error(error);
}
public static void generateMissingStruct(SourceFile file, Span vertexName, CharSequence msg) {
public void generateMissingStruct(SourceFile file, Span vertexName, CharSequence msg) {
generateMissingStruct(file, vertexName, msg, "");
}
public static void generateMissingStruct(SourceFile file, Span vertexName, CharSequence msg, CharSequence hint) {
public void generateMissingStruct(SourceFile file, Span vertexName, CharSequence msg, CharSequence hint) {
Optional<Span> span = file.parent.index.getStructDefinitionsMatching(vertexName)
.stream()
.findFirst()
.map(ShaderStruct::getName);
ErrorBuilder error = ErrorBuilder.error(msg)
this.error(msg)
.pointAtFile(file)
.pointAt(vertexName, 1)
.hintIncludeFor(span.orElse(null), hint);
Backend.LOGGER.error(error.build());
}
public static void generateMissingFunction(SourceFile file, CharSequence functionName, CharSequence msg) {
public void generateMissingFunction(SourceFile file, CharSequence functionName, CharSequence msg) {
generateMissingFunction(file, functionName, msg, "");
}
public static void generateMissingFunction(SourceFile file, CharSequence functionName, CharSequence msg, CharSequence hint) {
public void generateMissingFunction(SourceFile file, CharSequence functionName, CharSequence msg, CharSequence hint) {
Optional<Span> span = file.parent.index.getFunctionDefinitionsMatching(functionName)
.stream()
.findFirst()
.map(ShaderFunction::getName);
ErrorBuilder error = ErrorBuilder.error(msg)
this.error(msg)
.pointAtFile(file)
.hintIncludeFor(span.orElse(null), hint);
}
Backend.LOGGER.error(error.build());
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) {
SourceFile file = span.getSourceFile();
return error(message)
.pointAtFile(file)
.pointAt(span, 2);
}
public ErrorBuilder generateFileError(SourceFile file, String message) {
return error(message)
.pointAtFile(file);
}
public ErrorBuilder error(CharSequence msg) {
var out = ErrorBuilder.error(msg);
reportedErrors.add(out);
return out;
}
public boolean hasErrored() {
return !reportedErrors.isEmpty();
}
public void dump() {
for (var error : reportedErrors) {
Backend.LOGGER.error(error.build());
}
}
public static void printLines(CharSequence source) {

View file

@ -6,7 +6,6 @@ import java.util.regex.Pattern;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.core.source.Resolver;
import com.jozufozu.flywheel.core.source.SourceFile;
import com.jozufozu.flywheel.core.source.error.ErrorReporter;
import com.jozufozu.flywheel.core.source.span.Span;
@ -26,16 +25,16 @@ public class Import extends AbstractShaderElement {
}
@Nullable
public static Import create(Resolver resolver, Span self, Span file) {
public static Import create(ErrorReporter errorReporter, Span self, Span file) {
ResourceLocation fileLocation;
try {
fileLocation = new ResourceLocation(file.get());
} catch (ResourceLocationException e) {
ErrorReporter.generateSpanError(file, "malformed source location");
errorReporter.generateSpanError(file, "malformed source location");
return null;
}
return new Import(self, resolver.get(fileLocation), file);
return new Import(self, FileResolution.get(fileLocation), file);
}
public FileResolution getResolution() {

View file

@ -52,6 +52,10 @@ public class ShaderFunction extends AbstractShaderElement {
return name + "(" + String.join(", ", args) + ")";
}
public Span getParameterType(int index) {
return parameters.get(index).type;
}
public ImmutableList<Variable> getParameters() {
return parameters;
}

View file

@ -1,8 +1,11 @@
package com.jozufozu.flywheel.core.source.span;
import java.util.Optional;
import java.util.regex.Matcher;
import com.jozufozu.flywheel.core.source.SourceFile;
import com.jozufozu.flywheel.core.source.parse.ShaderFunction;
import com.jozufozu.flywheel.core.source.parse.ShaderStruct;
/**
* A segment of code in a {@link SourceFile}.
@ -121,4 +124,18 @@ public abstract class Span implements CharSequence {
public static Span fromMatcher(Span superSpan, Matcher m) {
return superSpan.subSpan(m.start(), m.end());
}
public Optional<ShaderStruct> findStruct() {
if (isErr()) {
return Optional.empty();
}
return in.findStruct(this);
}
public Optional<ShaderFunction> findFunction() {
if (isErr()) {
return Optional.empty();
}
return in.findFunction(this);
}
}

View file

@ -2,8 +2,7 @@ package com.jozufozu.flywheel.core.structs;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.core.source.Resolver;
import com.jozufozu.flywheel.event.GatherContextEvent;
import com.jozufozu.flywheel.core.source.SourceChecks;
import com.jozufozu.flywheel.util.ResourceUtil;
import net.minecraft.resources.ResourceLocation;
@ -12,9 +11,13 @@ public class InstanceShaders {
public static FileResolution MODEL;
public static FileResolution ORIENTED;
public static void flwInit(GatherContextEvent event) {
MODEL = Resolver.INSTANCE.get(ResourceUtil.subPath(Names.MODEL, ".vert"));
ORIENTED = Resolver.INSTANCE.get(ResourceUtil.subPath(Names.ORIENTED, ".vert"));
public static void init() {
var check = SourceChecks.checkFunctionParameterTypeExists("flw_instanceVertex", 1, 0);
MODEL = FileResolution.get(ResourceUtil.subPath(Names.MODEL, ".vert"))
.validateWith(check);
ORIENTED = FileResolution.get(ResourceUtil.subPath(Names.ORIENTED, ".vert"))
.validateWith(check);
}
public static class Names {

View file

@ -2,8 +2,7 @@ package com.jozufozu.flywheel.core.vertex;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.core.source.Resolver;
import com.jozufozu.flywheel.event.GatherContextEvent;
import com.jozufozu.flywheel.core.source.SourceChecks;
import com.jozufozu.flywheel.util.ResourceUtil;
import net.minecraft.resources.ResourceLocation;
@ -12,9 +11,14 @@ public class LayoutShaders {
public static FileResolution BLOCK;
public static FileResolution POS_TEX_NORMAL;
public static void flwInit(GatherContextEvent event) {
BLOCK = Resolver.INSTANCE.get(ResourceUtil.subPath(Names.BLOCK, ".vert"));
POS_TEX_NORMAL = Resolver.INSTANCE.get(ResourceUtil.subPath(Names.POS_TEX_NORMAL, ".vert"));
public static void init() {
var check = SourceChecks.checkFunctionArity("flw_layoutVertex", 0);
BLOCK = FileResolution.get(ResourceUtil.subPath(Names.BLOCK, ".vert"))
.validateWith(check);
POS_TEX_NORMAL = FileResolution.get(ResourceUtil.subPath(Names.POS_TEX_NORMAL, ".vert"))
.validateWith(check);
}
public static class Names {

View file

@ -6,6 +6,7 @@ import com.jozufozu.flywheel.light.LightUpdater;
import com.jozufozu.flywheel.util.WorldAttached;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.event.TickEvent;
@ -32,8 +33,10 @@ public class ForgeEvents {
@SubscribeEvent
public static void tickLight(TickEvent.ClientTickEvent e) {
if (e.phase == TickEvent.Phase.END && Backend.isGameActive())
LightUpdater.get(Minecraft.getInstance().level).tick();
if (e.phase == TickEvent.Phase.END && Backend.isGameActive()) {
LightUpdater.get(Minecraft.getInstance().level)
.tick();
}
}
}

View file

@ -1,20 +0,0 @@
package com.jozufozu.flywheel.event;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
public class GatherContextEvent extends Event implements IModBusEvent {
private final boolean firstLoad;
public GatherContextEvent(boolean firstLoad) {
this.firstLoad = firstLoad;
}
/**
* @return true iff it is the first time the event is fired.
*/
public boolean isFirstLoad() {
return firstLoad;
}
}