Compile everything on the fly

- Simplify game state system
 - Need some way to re-add errors on load.
 - Streamline shader compilation, reduce map lookups
 - Move pipeline package from backend to core
 - Simplify interfaces and remove unnecessary classes
This commit is contained in:
Jozufozu 2021-12-31 12:08:07 -08:00
parent aca216cf9a
commit 1b09c2c1eb
40 changed files with 269 additions and 514 deletions

View file

@ -1,15 +0,0 @@
package com.jozufozu.flywheel.api.shader;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
/**
* Represents a vertex format agnostic shader.
*/
public interface FlexibleShader<P extends GlProgram> {
/**
* Get a version of this shader that accepts the given VertexType as input.
*/
P get(VertexType type);
}

View file

@ -1,6 +1,5 @@
package com.jozufozu.flywheel.backend;
import com.jozufozu.flywheel.api.shader.FlexibleShader;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
@ -8,12 +7,7 @@ import net.minecraft.resources.ResourceLocation;
public interface ShaderContext<P extends GlProgram> {
default P getProgram(ResourceLocation loc, VertexType inputType) {
return this.getProgramSupplier(loc)
.get(inputType);
}
FlexibleShader<P> getProgramSupplier(ResourceLocation loc);
P getProgram(ResourceLocation loc, VertexType vertexType);
/**
* Load all programs associated with this context. This might be just one, if the context is very specialized.

View file

@ -1,16 +1,11 @@
package com.jozufozu.flywheel.backend.gl.shader;
import java.util.List;
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.pipeline.ShaderCompiler;
import com.jozufozu.flywheel.backend.source.ShaderLoadingException;
import com.jozufozu.flywheel.backend.source.error.ErrorBuilder;
import com.jozufozu.flywheel.backend.source.error.ErrorReporter;
import com.jozufozu.flywheel.core.pipeline.ShaderCompiler;
import net.minecraft.resources.ResourceLocation;
@ -30,27 +25,7 @@ public class GlShader extends GlObject {
String log = GL20.glGetShaderInfoLog(handle);
if (!log.isEmpty()) {
List<String> lines = log.lines()
.toList();
boolean needsSourceDump = false;
StringBuilder errors = new StringBuilder();
for (String line : lines) {
ErrorBuilder builder = env.parseCompilerError(line);
if (builder != null) {
errors.append(builder.build());
} else {
errors.append(line).append('\n');
needsSourceDump = true;
}
}
Backend.LOGGER.error("Errors compiling '" + name + "': \n" + errors);
if (needsSourceDump) {
// TODO: generated code gets its own "file"
ErrorReporter.printLines(source);
}
env.printShaderInfoLog(source, log, this.name);
}
if (GL20.glGetShaderi(handle, GL20.GL_COMPILE_STATUS) != GL20.GL_TRUE) {
@ -64,4 +39,5 @@ public class GlShader extends GlObject {
protected void deleteInternal(int handle) {
GL20.glDeleteShader(handle);
}
}

View file

@ -8,7 +8,6 @@ import java.util.stream.Stream;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.api.MaterialGroup;
import com.jozufozu.flywheel.api.shader.FlexibleShader;
import com.jozufozu.flywheel.backend.RenderLayer;
import com.jozufozu.flywheel.backend.gl.GlVertexArray;
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType;
@ -24,7 +23,6 @@ import net.minecraft.client.Camera;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
public class InstancingEngine<P extends WorldProgram> implements Engine {
@ -126,10 +124,6 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
}
}
public FlexibleShader<P> getProgram(ResourceLocation name) {
return context.getProgramSupplier(name);
}
@Override
public Vec3i getOriginCoordinate() {
return originCoordinate;

View file

@ -1,33 +0,0 @@
package com.jozufozu.flywheel.backend.pipeline;
import java.util.HashMap;
import java.util.Map;
import com.jozufozu.flywheel.api.shader.FlexibleShader;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.core.shader.ContextAwareProgram;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.core.shader.spec.ProgramSpec;
public class LazyCompiler<P extends WorldProgram> implements FlexibleShader<P> {
private final ShaderPipeline<P> pipeline;
private final ProgramSpec spec;
private final Map<VertexType, ContextAwareProgram<P>> cache = new HashMap<>();
public LazyCompiler(ShaderPipeline<P> pipeline, ProgramSpec spec) {
this.pipeline = pipeline;
this.spec = spec;
}
public void delete() {
cache.values().forEach(ContextAwareProgram::delete);
}
@Override
public P get(VertexType type) {
return cache.computeIfAbsent(type, t -> pipeline.compile(spec, t)).get();
}
}

View file

@ -1,6 +0,0 @@
package com.jozufozu.flywheel.backend.pipeline;
public class Shader {
}

View file

@ -1,38 +0,0 @@
package com.jozufozu.flywheel.backend.pipeline;
import java.util.Collection;
import java.util.stream.Collectors;
import com.jozufozu.flywheel.backend.source.parse.ShaderStruct;
import com.jozufozu.flywheel.backend.source.parse.StructField;
/**
* A single input to a shader.
*/
public class ShaderInput {
public final CharSequence name;
public final int attribCount;
public ShaderInput(CharSequence name, int attribCount) {
this.name = name;
this.attribCount = attribCount;
}
public ShaderInput withPrefix(CharSequence prefix) {
return new ShaderInput(prefix.toString() + name, attribCount);
}
public static Collection<ShaderInput> fromStruct(ShaderStruct struct, String prefix) {
return struct.getFields()
.stream()
.map(ShaderInput::from)
.map(a -> a.withPrefix(prefix))
.collect(Collectors.toList());
}
public static ShaderInput from(StructField field) {
int attributeCount = TypeHelper.getAttributeCount(field.type);
return new ShaderInput(field.name, attributeCount);
}
}

View file

@ -1,16 +0,0 @@
package com.jozufozu.flywheel.backend.pipeline;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.core.shader.ContextAwareProgram;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.core.shader.spec.ProgramSpec;
/**
* The main interface for compiling usable shaders from program specs.
* @param <P> the type of the program that this pipeline compiles.
*/
public interface ShaderPipeline<P extends WorldProgram> {
ContextAwareProgram<P> compile(ProgramSpec spec, VertexType vertexType);
}

View file

@ -1,40 +0,0 @@
package com.jozufozu.flywheel.backend.pipeline;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.source.FileResolution;
import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.core.shader.ContextAwareProgram;
import com.jozufozu.flywheel.core.shader.ExtensibleGlProgram;
import com.jozufozu.flywheel.core.shader.GameStateProgram;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.core.shader.spec.ProgramSpec;
import com.jozufozu.flywheel.core.shader.spec.ProgramState;
public class WorldShaderPipeline<P extends WorldProgram> implements ShaderPipeline<P> {
private final ExtensibleGlProgram.Factory<P> factory;
private final Template<?> template;
private final FileResolution header;
public WorldShaderPipeline(ExtensibleGlProgram.Factory<P> factory, Template<?> template, FileResolution header) {
this.factory = factory;
this.template = template;
this.header = header;
}
public ContextAwareProgram<P> compile(ProgramSpec spec, VertexType vertexType) {
SourceFile file = spec.getSource().getFile();
ShaderCompiler shader = new ShaderCompiler(spec.name, file, template, header, vertexType);
GameStateProgram.Builder<P> builder = GameStateProgram.builder(shader.compile(this.factory));
for (ProgramState variant : spec.getStates()) {
shader.setVariant(variant);
builder.withVariant(variant.context(), shader.compile(this.factory));
}
return builder.build();
}
}

View file

@ -3,8 +3,6 @@ package com.jozufozu.flywheel.backend.source;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.source.error.ErrorBuilder;
import com.jozufozu.flywheel.backend.source.span.Span;
@ -37,7 +35,6 @@ public class FileResolution {
return fileLoc;
}
@Nullable
public SourceFile getFile() {
return file;
}
@ -73,6 +70,7 @@ public class FileResolution {
.pointAt(span, 1);
}
Backend.LOGGER.error(builder.build());
throw new ShaderLoadingException();
}
}

View file

@ -10,13 +10,13 @@ import java.util.regex.Pattern;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.jozufozu.flywheel.backend.pipeline.ShaderCompiler;
import com.jozufozu.flywheel.backend.source.parse.Import;
import com.jozufozu.flywheel.backend.source.parse.ShaderFunction;
import com.jozufozu.flywheel.backend.source.parse.ShaderStruct;
import com.jozufozu.flywheel.backend.source.span.ErrorSpan;
import com.jozufozu.flywheel.backend.source.span.Span;
import com.jozufozu.flywheel.backend.source.span.StringSpan;
import com.jozufozu.flywheel.core.pipeline.ShaderCompiler;
import net.minecraft.resources.ResourceLocation;
@ -138,12 +138,6 @@ public class SourceFile {
return "#use " + '"' + name + '"';
}
public CharSequence generateFinalSource(ShaderCompiler env) {
StringBuilder builder = new StringBuilder();
generateFinalSource(env, builder);
return builder;
}
public void generateFinalSource(ShaderCompiler env, StringBuilder source) {
for (Import include : imports) {
SourceFile file = include.getFile();
@ -151,24 +145,16 @@ public class SourceFile {
if (file != null) file.generateFinalSource(env, source);
}
int i = env.allocateFile(this);
source.append("#line ")
.append(0)
.append(' ')
.append(i)
.append(env.getFileID(this))
.append('\n');
source.append(elided);
}
public String printSource() {
StringBuilder builder = new StringBuilder();
builder.append("Source for shader '")
.append(name)
.append("':\n")
.append(lines.printLinesWithNumbers());
return builder.toString();
return "Source for shader '" + name + "':\n" + lines.printLinesWithNumbers();
}
private static CharSequence elideSource(String source, List<Span> elisions) {

View file

@ -7,7 +7,6 @@ import java.util.regex.Pattern;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.pipeline.ShaderCompiler;
import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.backend.source.SourceLines;
import com.jozufozu.flywheel.backend.source.error.lines.ErrorLine;
@ -17,6 +16,7 @@ import com.jozufozu.flywheel.backend.source.error.lines.SourceLine;
import com.jozufozu.flywheel.backend.source.error.lines.SpanHighlightLine;
import com.jozufozu.flywheel.backend.source.error.lines.TextLine;
import com.jozufozu.flywheel.backend.source.span.Span;
import com.jozufozu.flywheel.core.pipeline.ShaderCompiler;
import com.jozufozu.flywheel.util.FlwUtil;
public class ErrorBuilder {

View file

@ -3,11 +3,11 @@ package com.jozufozu.flywheel.core;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.GameStateRegistry;
import com.jozufozu.flywheel.backend.pipeline.ShaderPipeline;
import com.jozufozu.flywheel.backend.pipeline.WorldShaderPipeline;
import com.jozufozu.flywheel.backend.source.FileResolution;
import com.jozufozu.flywheel.backend.source.Resolver;
import com.jozufozu.flywheel.core.crumbling.CrumblingProgram;
import com.jozufozu.flywheel.core.pipeline.PipelineCompiler;
import com.jozufozu.flywheel.core.pipeline.WorldCompiler;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.core.shader.gamestate.NormalDebugStateProvider;
import com.jozufozu.flywheel.event.GatherContextEvent;
@ -31,8 +31,8 @@ public class Contexts {
FileResolution crumblingBuiltins = Resolver.INSTANCE.findShader(ResourceUtil.subPath(Names.CRUMBLING, ".glsl"));
FileResolution worldBuiltins = Resolver.INSTANCE.findShader(ResourceUtil.subPath(Names.WORLD, ".glsl"));
ShaderPipeline<CrumblingProgram> crumblingPipeline = new WorldShaderPipeline<>(CrumblingProgram::new, Templates.INSTANCING, crumblingBuiltins);
ShaderPipeline<WorldProgram> worldPipeline = new WorldShaderPipeline<>(WorldProgram::new, Templates.INSTANCING, worldBuiltins);
PipelineCompiler<CrumblingProgram> crumblingPipeline = new WorldCompiler<>(CrumblingProgram::new, Templates.INSTANCING, crumblingBuiltins);
PipelineCompiler<WorldProgram> worldPipeline = new WorldCompiler<>(WorldProgram::new, Templates.INSTANCING, worldBuiltins);
CRUMBLING = backend.register(WorldContext.builder(backend, Names.CRUMBLING).build(crumblingPipeline));
WORLD = backend.register(WorldContext.builder(backend, Names.WORLD).build(worldPipeline));

View file

@ -1,9 +1,9 @@
package com.jozufozu.flywheel.core;
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.backend.pipeline.InstancingTemplateData;
import com.jozufozu.flywheel.backend.pipeline.OneShotTemplateData;
import com.jozufozu.flywheel.backend.pipeline.Template;
import com.jozufozu.flywheel.core.pipeline.InstancingTemplateData;
import com.jozufozu.flywheel.core.pipeline.OneShotTemplateData;
import com.jozufozu.flywheel.core.pipeline.Template;
public class Templates {

View file

@ -6,13 +6,13 @@ import java.util.Objects;
import java.util.function.Supplier;
import java.util.stream.Stream;
import com.jozufozu.flywheel.api.shader.FlexibleShader;
import com.jozufozu.flywheel.api.struct.Instanced;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.ShaderContext;
import com.jozufozu.flywheel.backend.pipeline.LazyCompiler;
import com.jozufozu.flywheel.backend.pipeline.ShaderPipeline;
import com.jozufozu.flywheel.backend.source.ShaderLoadingException;
import com.jozufozu.flywheel.core.pipeline.CachingCompiler;
import com.jozufozu.flywheel.core.pipeline.PipelineCompiler;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.core.shader.spec.ProgramSpec;
@ -20,17 +20,16 @@ import net.minecraft.resources.ResourceLocation;
public class WorldContext<P extends WorldProgram> implements ShaderContext<P> {
public final Backend backend;
protected final Map<ResourceLocation, LazyCompiler<P>> programs = new HashMap<>();
protected final Map<ResourceLocation, ProgramSpec> programs = new HashMap<>();
protected final ResourceLocation name;
protected final Supplier<Stream<ResourceLocation>> specStream;
private final CachingCompiler<P> programCache;
public final ShaderPipeline<P> pipeline;
public WorldContext(Backend backend, ResourceLocation name, Supplier<Stream<ResourceLocation>> specStream, ShaderPipeline<P> pipeline) {
public WorldContext(Backend backend, ResourceLocation name, Supplier<Stream<ResourceLocation>> specStream, PipelineCompiler<P> pipeline) {
this.backend = backend;
this.name = name;
this.specStream = specStream;
this.pipeline = pipeline;
this.programCache = new CachingCompiler<>(pipeline);
}
@Override
@ -46,7 +45,7 @@ public class WorldContext<P extends WorldProgram> implements ShaderContext<P> {
private void loadSpec(ProgramSpec spec) {
try {
programs.put(spec.name, new LazyCompiler<>(pipeline, spec));
programs.put(spec.name, spec);
Backend.LOGGER.debug("Loaded program {}", spec.name);
} catch (Exception e) {
@ -59,14 +58,19 @@ public class WorldContext<P extends WorldProgram> implements ShaderContext<P> {
}
@Override
public FlexibleShader<P> getProgramSupplier(ResourceLocation spec) {
return programs.get(spec);
public P getProgram(ResourceLocation loc, VertexType vertexType) {
ProgramSpec spec = programs.get(loc);
if (spec == null) {
throw new NullPointerException("Cannot compile shader because '" + loc + "' is not recognized.");
}
return programCache.getProgram(spec, vertexType);
}
@Override
public void delete() {
programs.values()
.forEach(LazyCompiler::delete);
programCache.invalidate();
programs.clear();
}
@ -89,7 +93,7 @@ public class WorldContext<P extends WorldProgram> implements ShaderContext<P> {
return this;
}
public <P extends WorldProgram> WorldContext<P> build(ShaderPipeline<P> pipeline) {
public <P extends WorldProgram> WorldContext<P> build(PipelineCompiler<P> pipeline) {
if (specStream == null) {
specStream = () -> backend.allMaterials()
.stream()

View file

@ -1,6 +1,7 @@
package com.jozufozu.flywheel.core.materials;
import com.jozufozu.flywheel.api.InstanceData;
import com.jozufozu.flywheel.util.Color;
import net.minecraft.client.renderer.LightTexture;
@ -33,6 +34,15 @@ public abstract class BasicData extends InstanceData implements FlatLit<BasicDat
return LightTexture.pack(this.blockLight, this.skyLight);
}
public BasicData setColor(Color color) {
this.r = (byte) color.getRed();
this.g = (byte) color.getGreen();
this.b = (byte) color.getBlue();
this.a = (byte) color.getAlpha();
markDirty();
return this;
}
public BasicData setColor(int color) {
return setColor(color, false);
}

View file

@ -0,0 +1,38 @@
package com.jozufozu.flywheel.core.pipeline;
import java.util.HashMap;
import java.util.Map;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.core.shader.spec.ProgramSpec;
/**
* Lazily compiles shader programs, caching the results.
*
* @param <P> The class that the PipelineCompiler outputs.
*/
public class CachingCompiler<P extends GlProgram> {
protected final Map<CompilationContext, P> cache = new HashMap<>();
private final PipelineCompiler<P> pipeline;
public CachingCompiler(PipelineCompiler<P> pipeline) {
this.pipeline = pipeline;
}
/**
* Get or compile a spec to the given vertex type, accounting for all game state conditions specified by the spec.
*
* @param spec The ProgramSpec to target.
* @param vertexType The VertexType to target.
* @return A compiled GlProgram.
*/
public P getProgram(ProgramSpec spec, VertexType vertexType) {
return cache.computeIfAbsent(new CompilationContext(vertexType, spec, spec.getCurrentStateID()), this.pipeline::compile);
}
public void invalidate() {
cache.values().forEach(P::delete);
cache.clear();
}
}

View file

@ -0,0 +1,35 @@
package com.jozufozu.flywheel.core.pipeline;
import java.util.Objects;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.core.shader.spec.ProgramSpec;
/**
* Represents the entire context of a program's usage.
*
* @param vertexType The vertexType the program should be adapted for.
* @param spec The generic program name.
* @param ctx An ID representing the state at the time of usage.
*/
public record CompilationContext(VertexType vertexType, ProgramSpec spec, long ctx) {
public SourceFile getFile() {
return spec().getSource();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CompilationContext that = (CompilationContext) o;
// override for instance equality on vertexType
return ctx == that.ctx && vertexType == that.vertexType && spec.equals(that.spec);
}
@Override
public int hashCode() {
return Objects.hash(vertexType, spec, ctx);
}
}

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.backend.pipeline;
package com.jozufozu.flywheel.core.pipeline;
import java.util.Optional;
@ -107,7 +107,7 @@ public class InstancingTemplateData implements TemplateData {
.append("a_i_")
.append(field.name)
.append(";\n");
attributeBinding += ShaderInput.from(field).attribCount;
attributeBinding += TypeHelper.getAttributeCount(field.type);
}
Template.prefixFields(template, interpolant, "out", "v2f_");

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.backend.pipeline;
package com.jozufozu.flywheel.core.pipeline;
import java.util.Optional;

View file

@ -0,0 +1,13 @@
package com.jozufozu.flywheel.core.pipeline;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
/**
* The main interface for compiling usable shaders from program specs.
* @param <P> the type of the program that this pipeline compiles.
*/
public interface PipelineCompiler<P extends GlProgram> {
P compile(CompilationContext vertexType);
}

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.backend.pipeline;
package com.jozufozu.flywheel.core.pipeline;
import static org.lwjgl.opengl.GL11.GL_TRUE;
import static org.lwjgl.opengl.GL20.GL_LINK_STATUS;

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.backend.pipeline;
package com.jozufozu.flywheel.core.pipeline;
import java.util.ArrayList;
import java.util.List;
@ -6,15 +6,16 @@ import java.util.List;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.shader.GlShader;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.backend.source.FileResolution;
import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.backend.source.error.ErrorBuilder;
import com.jozufozu.flywheel.backend.source.error.ErrorReporter;
import com.jozufozu.flywheel.backend.source.span.Span;
import com.jozufozu.flywheel.core.shader.ExtensibleGlProgram;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.core.shader.spec.ProgramState;
import net.minecraft.resources.ResourceLocation;
@ -24,27 +25,22 @@ public class ShaderCompiler {
public final Template<?> template;
private final FileResolution header;
@Nullable
private ProgramState variant;
private final List<String> defines;
public final VertexType vertexType;
public SourceFile mainFile;
public final SourceFile mainFile;
public ShaderCompiler(ResourceLocation name, SourceFile mainSource, Template<?> template, FileResolution header, VertexType vertexType) {
this.name = name;
private final List<SourceFile> files = new ArrayList<>();
public ShaderCompiler(CompilationContext usage, Template<?> template, FileResolution header) {
this.name = usage.spec().name;
this.template = template;
this.header = header;
this.mainFile = mainSource;
this.vertexType = vertexType;
}
public ShaderCompiler setMainSource(SourceFile file) {
if (mainFile == file) return this;
mainFile = file;
return this;
this.mainFile = usage.getFile();
this.defines = usage.spec()
.getDefines(usage.ctx());
this.vertexType = usage.vertexType();
}
public GlShader compile(ShaderType type) {
@ -60,13 +56,10 @@ public class ShaderCompiler {
.append(type.define) // special case shader type declaration
.append('\n');
ProgramState variant = getVariant();
if (variant != null) {
for (String def : variant.defines()) {
finalSource.append("#define ")
.append(def)
.append('\n');
}
for (String def : defines) {
finalSource.append("#define ")
.append(def)
.append('\n');
}
if (type == ShaderType.VERTEX) {
@ -83,9 +76,7 @@ public class ShaderCompiler {
}
files.clear();
if (header.getFile() != null) {
header.getFile().generateFinalSource(this, finalSource);
}
header.getFile().generateFinalSource(this, finalSource);
mainFile.generateFinalSource(this, finalSource);
template.getMetadata(mainFile).generateFooter(finalSource, type, this);
@ -93,18 +84,21 @@ public class ShaderCompiler {
return new GlShader(this, type, finalSource.toString());
}
@Nullable
public ProgramState getVariant() {
return variant;
public <P extends WorldProgram> P compile(ExtensibleGlProgram.Factory<P> worldShaderPipeline) {
return new ProgramAssembler(this.name)
.attachShader(compile(ShaderType.VERTEX))
.attachShader(compile(ShaderType.FRAGMENT))
.link()
.deleteLinkedShaders()
.build(worldShaderPipeline);
}
public void setVariant(@Nullable ProgramState variant) {
this.variant = variant;
}
private final List<SourceFile> files = new ArrayList<>();
public int allocateFile(SourceFile 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.
*/
public int getFileID(SourceFile sourceFile) {
int i = files.indexOf(sourceFile);
if (i != -1) {
return i;
@ -121,8 +115,32 @@ public class ShaderCompiler {
return file.getLineSpanNoWhitespace(lineNo);
}
public void printShaderInfoLog(String source, String log, ResourceLocation name) {
List<String> lines = log.lines()
.toList();
boolean needsSourceDump = false;
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');
needsSourceDump = true;
}
}
Backend.LOGGER.error("Errors compiling '" + name + "': \n" + errors);
if (needsSourceDump) {
// TODO: generated code gets its own "file"
ErrorReporter.printLines(source);
}
}
@Nullable
public ErrorBuilder parseCompilerError(String line) {
private ErrorBuilder parseCompilerError(String line) {
try {
ErrorBuilder error = ErrorBuilder.fromLogLine(this, line);
if (error != null) {
@ -133,13 +151,4 @@ public class ShaderCompiler {
return null;
}
public <P extends WorldProgram> P compile(ExtensibleGlProgram.Factory<P> worldShaderPipeline) {
return new ProgramAssembler(this.name)
.attachShader(compile(ShaderType.VERTEX))
.attachShader(compile(ShaderType.FRAGMENT))
.link()
.deleteLinkedShaders()
.build(worldShaderPipeline);
}
}

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.backend.pipeline;
package com.jozufozu.flywheel.core.pipeline;
import java.util.HashMap;
import java.util.Map;

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.backend.pipeline;
package com.jozufozu.flywheel.core.pipeline;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.backend.pipeline;
package com.jozufozu.flywheel.core.pipeline;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

View file

@ -0,0 +1,33 @@
package com.jozufozu.flywheel.core.pipeline;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.backend.source.FileResolution;
import com.jozufozu.flywheel.core.shader.ExtensibleGlProgram;
import com.jozufozu.flywheel.core.shader.WorldProgram;
public class WorldCompiler<P extends WorldProgram> implements PipelineCompiler<P> {
private final ExtensibleGlProgram.Factory<P> factory;
private final Template<?> template;
private final FileResolution header;
public WorldCompiler(ExtensibleGlProgram.Factory<P> factory, Template<?> template, FileResolution header) {
this.factory = factory;
this.template = template;
this.header = header;
}
@Override
public P compile(CompilationContext usage) {
ShaderCompiler compiler = new ShaderCompiler(usage, template, header);
return new ProgramAssembler(compiler.name)
.attachShader(compiler.compile(ShaderType.VERTEX))
.attachShader(compiler.compile(ShaderType.FRAGMENT))
.link()
.deleteLinkedShaders()
.build(this.factory);
}
}

View file

@ -1,5 +1,5 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.backend.pipeline;
package com.jozufozu.flywheel.core.pipeline;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -1,26 +0,0 @@
package com.jozufozu.flywheel.core.shader;
import java.util.function.Supplier;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
/**
* Encapsulates any number of shader programs for use in similar contexts.
* Allows the implementor to choose which shader program to use based on arbitrary state.
*
* @param <P>
*/
public interface ContextAwareProgram<P extends GlProgram> extends Supplier<P> {
/**
* Get the shader program most suited for the current game state.
*
* @return The one true program.
*/
P get();
/**
* Delete all shader programs encapsulated by your implementation.
*/
void delete();
}

View file

@ -1,62 +0,0 @@
package com.jozufozu.flywheel.core.shader;
import java.util.ArrayList;
import java.util.List;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.core.shader.spec.IGameStateCondition;
import com.jozufozu.flywheel.util.Pair;
public class GameStateProgram<P extends GlProgram> implements ContextAwareProgram<P> {
private final List<Pair<IGameStateCondition, P>> variants;
private final P fallback;
protected GameStateProgram(List<Pair<IGameStateCondition, P>> variants, P fallback) {
this.variants = variants;
this.fallback = fallback;
}
@Override
public P get() {
for (Pair<IGameStateCondition, P> variant : variants) {
if (variant.first()
.isMet()) return variant.second();
}
return fallback;
}
@Override
public void delete() {
for (Pair<IGameStateCondition, P> variant : variants) {
variant.second()
.delete();
}
fallback.delete();
}
public static <P extends GlProgram> Builder<P> builder(P fallback) {
return new Builder<>(fallback);
}
public static class Builder<P extends GlProgram> {
private final P fallback;
private final List<Pair<IGameStateCondition, P>> variants = new ArrayList<>();
public Builder(P fallback) {
this.fallback = fallback;
}
public Builder<P> withVariant(IGameStateCondition condition, P program) {
variants.add(Pair.of(condition, program));
return this;
}
public ContextAwareProgram<P> build() {
return new GameStateProgram<>(ImmutableList.copyOf(variants), fallback);
}
}
}

View file

@ -11,5 +11,5 @@ public interface IGameStateProvider {
ResourceLocation getID();
Object getValue();
boolean isTrue();
}

View file

@ -2,11 +2,10 @@ package com.jozufozu.flywheel.core.shader.gamestate;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.config.FlwConfig;
import com.jozufozu.flywheel.core.shader.spec.IBooleanStateProvider;
import net.minecraft.resources.ResourceLocation;
public class NormalDebugStateProvider implements IBooleanStateProvider {
public class NormalDebugStateProvider implements IGameStateProvider {
public static final NormalDebugStateProvider INSTANCE = new NormalDebugStateProvider();
public static final ResourceLocation NAME = Flywheel.rl("normal_debug");

View file

@ -1,37 +0,0 @@
package com.jozufozu.flywheel.core.shader.spec;
import com.jozufozu.flywheel.core.shader.gamestate.IGameStateProvider;
import com.mojang.serialization.Codec;
import net.minecraft.resources.ResourceLocation;
public class BooleanGameStateCondition implements IGameStateCondition {
public static final Codec<BooleanGameStateCondition> BOOLEAN_SUGAR = IGameStateProvider.CODEC.xmap(gameContext -> {
if (gameContext instanceof IBooleanStateProvider) {
return new BooleanGameStateCondition(((IBooleanStateProvider) gameContext));
}
return null;
}, IGameStateCondition::getStateProvider);
protected final IBooleanStateProvider context;
public BooleanGameStateCondition(IBooleanStateProvider context) {
this.context = context;
}
@Override
public ResourceLocation getID() {
return context.getID();
}
@Override
public IGameStateProvider getStateProvider() {
return context;
}
@Override
public boolean isMet() {
return context.isTrue();
}
}

View file

@ -1,13 +0,0 @@
package com.jozufozu.flywheel.core.shader.spec;
import com.jozufozu.flywheel.core.shader.gamestate.IGameStateProvider;
public interface IBooleanStateProvider extends IGameStateProvider {
boolean isTrue();
@Override
default Boolean getValue() {
return isTrue();
}
}

View file

@ -1,14 +0,0 @@
package com.jozufozu.flywheel.core.shader.spec;
import com.jozufozu.flywheel.core.shader.gamestate.IGameStateProvider;
import net.minecraft.resources.ResourceLocation;
public interface IGameStateCondition {
ResourceLocation getID();
IGameStateProvider getStateProvider();
boolean isMet();
}

View file

@ -1,8 +1,10 @@
package com.jozufozu.flywheel.core.shader.spec;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.source.FileResolution;
import com.jozufozu.flywheel.backend.source.Resolver;
import com.jozufozu.flywheel.backend.source.SourceFile;
@ -38,11 +40,11 @@ public class ProgramSpec {
public ResourceLocation name;
public final FileResolution source;
public final List<ProgramState> states;
public final ImmutableList<ProgramState> states;
public ProgramSpec(ResourceLocation source, List<ProgramState> states) {
this.source = Resolver.INSTANCE.findShader(source);
this.states = states;
this.states = ImmutableList.copyOf(states);
}
public void setName(ResourceLocation name) {
@ -53,12 +55,40 @@ public class ProgramSpec {
return source.getFileLoc();
}
public FileResolution getSource() {
return source;
public SourceFile getSource() {
return source.getFile();
}
public List<ProgramState> getStates() {
public ImmutableList<ProgramState> getStates() {
return states;
}
/**
* Calculate a unique ID representing the current game state.
*/
public long getCurrentStateID() {
long ctx = 0;
for (ProgramState state : states) {
if (state.context().isTrue()) {
ctx |= 1;
}
ctx <<= 1;
}
return ctx;
}
/**
* Given the stateID, get a list of defines to include at the top of a compiling program.
*/
public List<String> getDefines(long stateID) {
List<String> defines = new ArrayList<>();
for (ProgramState state : states) {
if ((stateID & 1) == 1) {
defines.addAll(state.defines());
}
stateID >>= 1;
}
return defines;
}
}

View file

@ -3,29 +3,14 @@ package com.jozufozu.flywheel.core.shader.spec;
import java.util.Collections;
import java.util.List;
import com.jozufozu.flywheel.core.shader.gamestate.IGameStateProvider;
import com.jozufozu.flywheel.util.CodecUtil;
import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.codecs.RecordCodecBuilder;
public record ProgramState(IGameStateCondition context, List<String> defines) {
public record ProgramState(IGameStateProvider context, List<String> defines) {
// TODO: Use Codec.dispatch
private static final Codec<IGameStateCondition> WHEN = Codec.either(BooleanGameStateCondition.BOOLEAN_SUGAR, SpecificValueCondition.CODEC)
.flatXmap(either -> either.map(DataResult::success, DataResult::success), any -> {
if (any instanceof BooleanGameStateCondition) {
return DataResult.success(Either.left((BooleanGameStateCondition) any));
}
if (any instanceof SpecificValueCondition) {
return DataResult.success(Either.right((SpecificValueCondition) any));
}
return DataResult.error("unknown context condition");
});
public static final Codec<ProgramState> CODEC = RecordCodecBuilder.create(state -> state.group(WHEN.fieldOf("when")
public static final Codec<ProgramState> CODEC = RecordCodecBuilder.create(state -> state.group(IGameStateProvider.CODEC.fieldOf("when")
.forGetter(ProgramState::context), CodecUtil.oneOrMore(Codec.STRING)
.optionalFieldOf("define", Collections.emptyList())
.forGetter(ProgramState::defines))

View file

@ -1,43 +0,0 @@
package com.jozufozu.flywheel.core.shader.spec;
import com.jozufozu.flywheel.core.shader.gamestate.IGameStateProvider;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.resources.ResourceLocation;
public class SpecificValueCondition implements IGameStateCondition {
public static final Codec<SpecificValueCondition> CODEC = RecordCodecBuilder.create(condition -> condition.group(IGameStateProvider.CODEC.fieldOf("provider")
.forGetter(SpecificValueCondition::getStateProvider), Codec.STRING.fieldOf("value")
.forGetter(SpecificValueCondition::getValue))
.apply(condition, SpecificValueCondition::new));
private final String required;
private final IGameStateProvider context;
public SpecificValueCondition(IGameStateProvider context, String required) {
this.required = required;
this.context = context;
}
@Override
public ResourceLocation getID() {
return context.getID();
}
public String getValue() {
return required;
}
@Override
public IGameStateProvider getStateProvider() {
return context;
}
@Override
public boolean isMet() {
return required.equals(context.getValue()
.toString());
}
}

View file

@ -2,10 +2,7 @@
"source": "flywheel:model.vert",
"states": [
{
"when": {
"provider": "flywheel:normal_debug",
"value": "true"
},
"when": "flywheel:normal_debug",
"define": "DEBUG_NORMAL"
}
]

View file

@ -2,10 +2,7 @@
"source": "flywheel:oriented.vert",
"states": [
{
"when": {
"provider": "flywheel:normal_debug",
"value": "true"
},
"when": "flywheel:normal_debug",
"define": "DEBUG_NORMAL"
}
]