Almost sane shaders

- No more ShaderContext. Programs are directly retrieved through ProgramCompilers.
 - Templates don't need generics
 - Remove ExtensibleGlProgram
This commit is contained in:
Jozufozu 2022-01-07 22:46:29 -08:00
parent 06e1d5a901
commit bfe123d167
38 changed files with 339 additions and 437 deletions

View file

@ -1,9 +1,7 @@
package com.jozufozu.flywheel.backend;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
@ -40,7 +38,6 @@ public class Backend {
public GlCompat compat;
public final Loader loader;
private final List<ShaderContext<?>> contexts = new ArrayList<>();
private final Map<ResourceLocation, StructType<?>> materialRegistry = new HashMap<>();
private final Map<ResourceLocation, ProgramSpec> programSpecRegistry = new HashMap<>();
@ -74,14 +71,6 @@ public class Backend {
return spec;
}
/**
* Register a shader context.
*/
public <C extends ShaderContext<?>> C register(C spec) {
contexts.add(spec);
return spec;
}
/**
* Register an instancing material.
*/
@ -96,6 +85,7 @@ public class Backend {
return spec;
}
@Nullable
public ProgramSpec getSpec(ResourceLocation name) {
return programSpecRegistry.get(name);
}
@ -118,10 +108,6 @@ public class Backend {
return programSpecRegistry.values();
}
public Collection<ShaderContext<?>> allContexts() {
return contexts;
}
public static boolean isOn() {
return getInstance().engine != FlwEngine.OFF;
}
@ -157,8 +143,6 @@ public class Backend {
void _clearContexts() {
GameStateRegistry.clear();
programSpecRegistry.clear();
contexts.forEach(ShaderContext::delete);
contexts.clear();
materialRegistry.clear();
}

View file

@ -7,7 +7,6 @@ import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.backend.source.Resolver;
import com.jozufozu.flywheel.backend.source.ShaderLoadingException;
import com.jozufozu.flywheel.backend.source.ShaderSources;
import com.jozufozu.flywheel.core.crumbling.CrumblingRenderer;
import com.jozufozu.flywheel.core.shader.ProgramSpec;
@ -39,7 +38,6 @@ public class Loader implements ResourceManagerReloadListener {
private static final Gson GSON = new GsonBuilder().create();
private final Backend backend;
private boolean shouldCrash;
private boolean firstLoad = true;
@ -56,15 +54,10 @@ public class Loader implements ResourceManagerReloadListener {
}
}
public void notifyError() {
shouldCrash = true;
}
@Override
public void onResourceManagerReload(ResourceManager manager) {
backend.refresh();
shouldCrash = false;
backend._clearContexts();
Resolver.INSTANCE.invalidate();
@ -75,17 +68,9 @@ public class Loader implements ResourceManagerReloadListener {
loadProgramSpecs(manager);
Resolver.INSTANCE.resolve(sources);
Resolver.INSTANCE.run(sources);
for (ShaderContext<?> context : backend.allContexts()) {
context.load();
}
if (shouldCrash) {
throw new ShaderLoadingException("Could not load all shaders, see log for details");
}
Backend.LOGGER.info("Loaded all shader programs.");
Backend.LOGGER.info("Loaded all shader sources.");
ClientLevel world = Minecraft.getInstance().level;
if (Backend.canUseInstancing(world)) {

View file

@ -1,18 +0,0 @@
package com.jozufozu.flywheel.backend;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import net.minecraft.resources.ResourceLocation;
public interface ShaderContext<P extends GlProgram> {
P getProgram(ResourceLocation loc, VertexType vertexType, RenderLayer layer);
/**
* Load all programs associated with this context. This might be just one, if the context is very specialized.
*/
void load();
void delete();
}

View file

@ -7,6 +7,8 @@ import static org.lwjgl.opengl.GL20.glUniformMatrix4fv;
import java.nio.FloatBuffer;
import javax.annotation.Nonnull;
import org.lwjgl.system.MemoryStack;
import com.jozufozu.flywheel.backend.Backend;
@ -87,4 +89,13 @@ public abstract class GlProgram extends GlObject {
public String toString() {
return "program " + name;
}
/**
* A factory interface to create a {@link GlProgram}.
*/
public interface Factory<P extends GlProgram> {
@Nonnull
P create(ResourceLocation name, int handle);
}
}

View file

@ -5,7 +5,7 @@ import org.lwjgl.opengl.GL20;
import com.jozufozu.flywheel.backend.gl.GlObject;
import com.jozufozu.flywheel.backend.gl.versioned.GlCompat;
import com.jozufozu.flywheel.backend.source.ShaderLoadingException;
import com.jozufozu.flywheel.core.pipeline.ShaderCompiler;
import com.jozufozu.flywheel.core.compile.ShaderCompiler;
import net.minecraft.resources.ResourceLocation;

View file

@ -16,4 +16,8 @@ public enum ShaderType {
this.define = define;
this.glEnum = glEnum;
}
public String getDefineStatement() {
return "#define " + define + "\n";
}
}

View file

@ -13,6 +13,7 @@ import com.jozufozu.flywheel.backend.model.FallbackAllocator;
import com.jozufozu.flywheel.backend.model.ModelAllocator;
import com.jozufozu.flywheel.backend.model.ModelPool;
import com.jozufozu.flywheel.core.Formats;
import com.jozufozu.flywheel.core.compile.ProgramContext;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.util.Textures;
import com.mojang.math.Matrix4f;
@ -78,8 +79,8 @@ public class InstancedMaterialGroup<P extends WorldProgram> implements MaterialG
InstancedMaterial<?> material = entry.getValue();
if (material.nothingToRender()) continue;
P program = owner.context.getProgram(entry.getKey()
.getProgramSpec(), Formats.POS_TEX_NORMAL, layer);
P program = owner.context.getProgram(ProgramContext.create(entry.getKey()
.getProgramSpec(), Formats.POS_TEX_NORMAL, layer));
program.bind();
program.uploadViewProjection(viewProjection);

View file

@ -13,7 +13,7 @@ import com.jozufozu.flywheel.backend.gl.GlVertexArray;
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType;
import com.jozufozu.flywheel.backend.instancing.Engine;
import com.jozufozu.flywheel.backend.instancing.TaskEngine;
import com.jozufozu.flywheel.core.WorldContext;
import com.jozufozu.flywheel.core.compile.ProgramCompiler;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.event.RenderLayerEvent;
import com.jozufozu.flywheel.util.WeakHashSet;
@ -31,7 +31,7 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
protected BlockPos originCoordinate = BlockPos.ZERO;
protected final WorldContext<P> context;
protected final ProgramCompiler<P> context;
protected final GroupFactory<P> groupFactory;
protected final boolean ignoreOriginCoordinate;
@ -39,15 +39,11 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
private final WeakHashSet<OriginShiftListener> listeners;
public InstancingEngine(WorldContext<P> context, TaskEngine taskEngine) {
this(context, InstancedMaterialGroup::new, false);
}
public static <P extends WorldProgram> Builder<P> builder(WorldContext<P> context) {
public static <P extends WorldProgram> Builder<P> builder(ProgramCompiler<P> context) {
return new Builder<>(context);
}
public InstancingEngine(WorldContext<P> context, GroupFactory<P> groupFactory, boolean ignoreOriginCoordinate) {
public InstancingEngine(ProgramCompiler<P> context, GroupFactory<P> groupFactory, boolean ignoreOriginCoordinate) {
this.context = context;
this.ignoreOriginCoordinate = ignoreOriginCoordinate;
@ -180,11 +176,11 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
}
public static class Builder<P extends WorldProgram> {
protected final WorldContext<P> context;
protected final ProgramCompiler<P> context;
protected GroupFactory<P> groupFactory = InstancedMaterialGroup::new;
protected boolean ignoreOriginCoordinate;
public Builder(WorldContext<P> context) {
public Builder(ProgramCompiler<P> context) {
this.context = context;
}

View file

@ -2,6 +2,7 @@ package com.jozufozu.flywheel.backend.source;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.source.error.ErrorBuilder;
@ -21,13 +22,13 @@ import net.minecraft.resources.ResourceLocation;
public class FileResolution {
/**
* Spans that have references that resolved to this.
* Extra info about where this resolution is required. Includes ProgramSpecs and shader Spans.
*/
private final List<Span> foundSpans = new ArrayList<>();
private final List<Consumer<ErrorBuilder>> extraCrashInfoProviders = new ArrayList<>();
private final ResourceLocation fileLoc;
private SourceFile file;
public FileResolution(ResourceLocation fileLoc) {
FileResolution(ResourceLocation fileLoc) {
this.fileLoc = fileLoc;
}
@ -35,6 +36,10 @@ public class FileResolution {
return fileLoc;
}
/**
* Non-null if this file is resolved because there would have been a crash otherwise.
* @return The file that this resolution resolves to.
*/
public SourceFile getFile() {
return file;
}
@ -48,34 +53,44 @@ public class FileResolution {
* @param span A span where this file is referenced.
*/
public FileResolution addSpan(Span span) {
foundSpans.add(span);
extraCrashInfoProviders.add(builder -> builder.pointAtFile(span.getSourceFile())
.pointAt(span, 1));
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.
*/
void resolve(SourceFinder sources) {
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));
// print the location of all places where this file was referenced
for (Span span : foundSpans) {
builder.pointAtFile(span.getSourceFile())
.pointAt(span, 1);
for (Consumer<ErrorBuilder> consumer : extraCrashInfoProviders) {
consumer.accept(builder);
}
Backend.LOGGER.error(builder.build());
throw new ShaderLoadingException();
return false;
}
// Let the GC do its thing
extraCrashInfoProviders.clear();
return true;
}
void invalidate() {
foundSpans.clear();
extraCrashInfoProviders.clear();
file = null;
}
}

View file

@ -9,29 +9,51 @@ import net.minecraft.resources.ResourceLocation;
* Manages deferred file resolution.
*
* <p>
* Interns all referenced files in all sources, duplicating the final lookups and allowing for more dev-friendly
* error reporting.
* </p><p>
* See {@link FileResolution} for more information.
* 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 findShader(ResourceLocation fileLoc) {
return resolutions.computeIfAbsent(fileLoc, FileResolution::new);
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 resolve(SourceFinder sources) {
public void run(SourceFinder sources) {
boolean needsCrash = false;
for (FileResolution resolution : resolutions.values()) {
resolution.resolve(sources);
if (!resolution.resolve(sources)) {
needsCrash = true;
}
}
if (needsCrash) {
throw new ShaderLoadingException("Failed to resolve all source files, see log for details");
}
hasRun = true;
}
/**
@ -43,5 +65,6 @@ public class Resolver {
*/
public void invalidate() {
resolutions.values().forEach(FileResolution::invalidate);
hasRun = false;
}
}

View file

@ -16,7 +16,7 @@ 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 com.jozufozu.flywheel.core.compile.FileIndex;
import net.minecraft.resources.ResourceLocation;
@ -138,7 +138,7 @@ public class SourceFile {
return "#use " + '"' + name + '"';
}
public void generateFinalSource(ShaderCompiler env, StringBuilder source) {
public void generateFinalSource(FileIndex env, StringBuilder source) {
for (Import include : imports) {
SourceFile file = include.getFile();

View file

@ -16,7 +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.core.compile.ShaderCompiler;
import com.jozufozu.flywheel.util.FlwUtil;
public class ErrorBuilder {
@ -127,6 +127,7 @@ public class ErrorBuilder {
}
StringBuilder builder = new StringBuilder();
builder.append('\n');
for (ErrorLine line : lines) {
int length = line.neededMargin();

View file

@ -26,7 +26,7 @@ public class Import extends AbstractShaderElement {
super(self);
this.file = file;
resolution = resolver.findShader(toRL(file))
resolution = resolver.get(toRL(file))
.addSpan(file);
IMPORTS.add(this);

View file

@ -1,13 +1,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.source.FileResolution;
import com.jozufozu.flywheel.backend.source.Resolver;
import com.jozufozu.flywheel.core.compile.ProgramCompiler;
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.NormalDebugStateProvider;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.event.GatherContextEvent;
@ -20,22 +18,17 @@ import net.minecraftforge.api.distmarker.OnlyIn;
@OnlyIn(Dist.CLIENT)
public class Contexts {
public static WorldContext<WorldProgram> WORLD;
public static WorldContext<CrumblingProgram> CRUMBLING;
public static ProgramCompiler<WorldProgram> WORLD;
public static ProgramCompiler<CrumblingProgram> CRUMBLING;
public static void flwInit(GatherContextEvent event) {
Backend backend = event.getBackend();
GameStateRegistry.register(NormalDebugStateProvider.INSTANCE);
FileResolution crumblingBuiltins = Resolver.INSTANCE.findShader(ResourceUtil.subPath(Names.CRUMBLING, ".glsl"));
FileResolution worldBuiltins = Resolver.INSTANCE.findShader(ResourceUtil.subPath(Names.WORLD, ".glsl"));
FileResolution worldBuiltins = Resolver.INSTANCE.get(ResourceUtil.subPath(Names.WORLD, ".glsl"));
FileResolution crumblingBuiltins = Resolver.INSTANCE.get(ResourceUtil.subPath(Names.CRUMBLING, ".glsl"));
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));
WORLD = Templates.INSTANCING.programCompiler(WorldProgram::new, worldBuiltins);
CRUMBLING = Templates.INSTANCING.programCompiler(CrumblingProgram::new, crumblingBuiltins);
}
public static class Names {

View file

@ -28,5 +28,6 @@ public class Materials {
public static class Names {
public static final ResourceLocation MODEL = Flywheel.rl("model");
public static final ResourceLocation ORIENTED = Flywheel.rl("oriented");
public static final ResourceLocation PASSTHRU = Flywheel.rl("passthru");
}
}

View file

@ -1,12 +1,12 @@
package com.jozufozu.flywheel.core;
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.core.pipeline.InstancingTemplateData;
import com.jozufozu.flywheel.core.pipeline.OneShotTemplateData;
import com.jozufozu.flywheel.core.pipeline.Template;
import com.jozufozu.flywheel.core.compile.InstancingTemplateData;
import com.jozufozu.flywheel.core.compile.OneShotTemplateData;
import com.jozufozu.flywheel.core.compile.Template;
public class Templates {
public static final Template<InstancingTemplateData> INSTANCING = new Template<>(GLSLVersion.V330, InstancingTemplateData::new);
public static final Template<OneShotTemplateData> ONE_SHOT = new Template<>(GLSLVersion.V150, OneShotTemplateData::new);
public static final Template INSTANCING = new Template(GLSLVersion.V330, InstancingTemplateData::new);
public static final Template ONE_SHOT = new Template(GLSLVersion.V150, OneShotTemplateData::new);
}

View file

@ -1,109 +0,0 @@
package com.jozufozu.flywheel.core;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.stream.Stream;
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.RenderLayer;
import com.jozufozu.flywheel.backend.ShaderContext;
import com.jozufozu.flywheel.backend.source.ShaderLoadingException;
import com.jozufozu.flywheel.core.pipeline.CachingCompiler;
import com.jozufozu.flywheel.core.pipeline.CompilationContext;
import com.jozufozu.flywheel.core.pipeline.PipelineCompiler;
import com.jozufozu.flywheel.core.shader.ProgramSpec;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import net.minecraft.resources.ResourceLocation;
public class WorldContext<P extends WorldProgram> implements ShaderContext<P> {
public final Backend backend;
protected final Map<ResourceLocation, ProgramSpec> programs = new HashMap<>();
protected final ResourceLocation name;
protected final Supplier<Stream<ResourceLocation>> specStream;
private final CachingCompiler<P> programCache;
public WorldContext(Backend backend, ResourceLocation name, Supplier<Stream<ResourceLocation>> specStream, PipelineCompiler<P> pipeline) {
this.backend = backend;
this.name = name;
this.specStream = specStream;
this.programCache = new CachingCompiler<>(pipeline);
}
@Override
public void load() {
Backend.LOGGER.info("Loading context '{}'", name);
specStream.get()
.map(backend::getSpec)
.forEach(this::loadSpec);
}
private void loadSpec(ProgramSpec spec) {
try {
programs.put(spec.name, spec);
Backend.LOGGER.debug("Loaded program {}", spec.name);
} catch (Exception e) {
Backend.LOGGER.error("Error loading program {}", spec.name);
if (!(e instanceof ShaderLoadingException)) {
Backend.LOGGER.error("", e);
}
backend.loader.notifyError();
}
}
@Override
public P getProgram(ResourceLocation loc, VertexType vertexType, RenderLayer layer) {
ProgramSpec spec = programs.get(loc);
if (spec == null) {
throw new NullPointerException("Cannot compile shader because '" + loc + "' is not recognized.");
}
return programCache.getProgram(CompilationContext.create(vertexType, layer, spec));
}
@Override
public void delete() {
programCache.invalidate();
programs.clear();
}
public static Builder builder(Backend backend, ResourceLocation name) {
return new Builder(backend, name);
}
public static class Builder {
private final Backend backend;
private final ResourceLocation name;
private Supplier<Stream<ResourceLocation>> specStream;
public Builder(Backend backend, ResourceLocation name) {
this.backend = backend;
this.name = name;
}
public Builder setSpecStream(Supplier<Stream<ResourceLocation>> specStream) {
this.specStream = specStream;
return this;
}
public <P extends WorldProgram> WorldContext<P> build(PipelineCompiler<P> pipeline) {
if (specStream == null) {
specStream = () -> backend.allMaterials()
.stream()
.map(t -> t instanceof Instanced<?> i ? i : null)
.filter(Objects::nonNull)
.map(Instanced::getProgramSpec);
}
return new WorldContext<>(backend, name, specStream, pipeline);
}
}
}

View file

@ -0,0 +1,13 @@
package com.jozufozu.flywheel.core.compile;
import com.jozufozu.flywheel.backend.source.SourceFile;
public interface FileIndex {
/**
* 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.
*/
int getFileID(SourceFile sourceFile);
}

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.core.pipeline;
package com.jozufozu.flywheel.core.compile;
import java.util.Optional;
@ -134,7 +134,7 @@ public class InstancingTemplateData implements TemplateData {
));
}
public void fragmentFooter(StringBuilder template, ShaderCompiler shader) {
public void fragmentFooter(StringBuilder template, FileIndex shader) {
Template.prefixFields(template, interpolant, "in", "v2f_");
template.append(String.format("""

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.core.pipeline;
package com.jozufozu.flywheel.core.compile;
import java.util.Optional;
@ -99,7 +99,7 @@ public class OneShotTemplateData implements TemplateData {
""");
}
public void fragmentFooter(StringBuilder template, ShaderCompiler file) {
public void fragmentFooter(StringBuilder template, FileIndex file) {
Template.prefixFields(template, interpolant, "in", "v2f_");
template.append(String.format("""

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.core.pipeline;
package com.jozufozu.flywheel.core.compile;
import static org.lwjgl.opengl.GL11.GL_TRUE;
import static org.lwjgl.opengl.GL20.GL_LINK_STATUS;
@ -11,9 +11,8 @@ import static org.lwjgl.opengl.GL20.glLinkProgram;
import java.util.List;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.backend.gl.shader.GlShader;
import com.jozufozu.flywheel.core.shader.ExtensibleGlProgram;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.resources.ResourceLocation;
@ -61,7 +60,7 @@ public class ProgramAssembler {
return this;
}
public <P extends WorldProgram> P build(ExtensibleGlProgram.Factory<P> factory) {
public <P extends GlProgram> P build(GlProgram.Factory<P> factory) {
return factory.create(name, program);
}
}

View file

@ -0,0 +1,58 @@
package com.jozufozu.flywheel.core.compile;
import java.util.HashMap;
import java.util.Map;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.backend.source.FileResolution;
/**
* A caching compiler.
*
* <p>
* 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>
*/
public class ProgramCompiler<P extends GlProgram> {
protected final Map<ProgramContext, P> cache = new HashMap<>();
private final GlProgram.Factory<P> factory;
private final Template template;
private final FileResolution header;
public ProgramCompiler(GlProgram.Factory<P> factory, Template template, FileResolution header) {
this.factory = factory;
this.template = template;
this.header = header;
}
/**
* Get or compile a spec to the given vertex type, accounting for all game state conditions specified by the spec.
*
* @param ctx The context of compilation.
* @return A compiled GlProgram.
*/
public P getProgram(ProgramContext ctx) {
return cache.computeIfAbsent(ctx, this::compile);
}
public void invalidate() {
cache.values().forEach(P::delete);
cache.clear();
}
private P compile(ProgramContext ctx) {
ShaderCompiler compiler = new ShaderCompiler(ctx, template, header);
return new ProgramAssembler(compiler.name)
.attachShader(compiler.compile(ShaderType.VERTEX))
.attachShader(compiler.compile(ShaderType.FRAGMENT))
.link()
.deleteLinkedShaders()
.build(this.factory);
}
}

View file

@ -0,0 +1,71 @@
package com.jozufozu.flywheel.core.compile;
import java.util.Objects;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.RenderLayer;
import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.core.shader.ProgramSpec;
import net.minecraft.resources.ResourceLocation;
/**
* Represents the entire context of a program's usage.
*
* @param alphaDiscard Alpha threshold below which pixels are discarded.
* @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 ProgramContext(float alphaDiscard, VertexType vertexType, ProgramSpec spec, long ctx) {
/**
* Creates a compilation context for the given program, vertex type and render layer.
*
* @param programName The name of the program to use.
* @param vertexType The vertex type to use.
* @param layer If cutout, the alpha discard threshold is 0.1, otherwise 0.
* @return A compilation context.
*/
public static ProgramContext create(ResourceLocation programName, VertexType vertexType, @Nullable RenderLayer layer) {
ProgramSpec spec = Backend.getInstance()
.getSpec(programName);
if (spec == null) {
throw new NullPointerException("Cannot compile shader because '" + programName + "' is not recognized.");
}
return new ProgramContext(getAlphaDiscard(layer), vertexType, spec, spec.getCurrentStateID());
}
/**
* Gets the alpha discard threshold for the given render layer.
*
* @param layer The render layer to get the alpha discard threshold for.
* @return The alpha discard threshold.
*/
public static float getAlphaDiscard(@Nullable RenderLayer layer) {
return layer == RenderLayer.CUTOUT ? 0.1f : 0f;
}
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;
ProgramContext that = (ProgramContext) o;
// override for instance equality on vertexType
return alphaDiscard == that.alphaDiscard && ctx == that.ctx && vertexType == that.vertexType && spec.equals(that.spec);
}
@Override
public int hashCode() {
return Objects.hash(alphaDiscard, vertexType, spec, ctx);
}
}

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.core.pipeline;
package com.jozufozu.flywheel.core.compile;
import java.util.ArrayList;
import java.util.List;
@ -7,7 +7,7 @@ import javax.annotation.Nullable;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.RenderLayer;
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.source.FileResolution;
@ -15,27 +15,50 @@ 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 net.minecraft.resources.ResourceLocation;
public class ShaderCompiler {
/**
* Compiles a shader program.
*/
public class ShaderCompiler implements FileIndex {
/**
* The name of the file responsible for this compilation.
*/
public final ResourceLocation name;
public final Template<?> template;
/**
* The template we'll be using to generate the final source.
*/
public final Template template;
private final FileResolution header;
/**
* Extra {@code #define}s to be added to the shader.
*/
private final List<String> defines;
private final RenderLayer layer;
/**
* Alpha threshold below which pixels are discarded.
*/
private final float alphaDiscard;
/**
* The vertex type to use.
*/
public final VertexType vertexType;
/**
* The main file to compile.
*/
public final SourceFile mainFile;
private final List<SourceFile> files = new ArrayList<>();
public ShaderCompiler(CompilationContext context, Template<?> template, FileResolution header) {
public ShaderCompiler(ProgramContext context, Template template, FileResolution header) {
this.name = context.spec().name;
this.template = template;
this.header = header;
@ -43,7 +66,7 @@ public class ShaderCompiler {
this.defines = context.spec()
.getDefines(context.ctx());
this.vertexType = context.vertexType();
layer = context.layer();
this.alphaDiscard = context.alphaDiscard();
}
public GlShader compile(ShaderType type) {
@ -55,11 +78,9 @@ public class ShaderCompiler {
.append('\n')
.append("#extension GL_ARB_explicit_attrib_location : enable\n")
.append("#extension GL_ARB_conservative_depth : enable\n")
.append("#define ")
.append(type.define) // special case shader type declaration
.append('\n');
.append(type.getDefineStatement()); // special case shader type declaration
if (layer == RenderLayer.CUTOUT) {
if (alphaDiscard > 0) {
finalSource.append("#define ALPHA_DISCARD 0.1\n");
}
@ -91,7 +112,7 @@ public class ShaderCompiler {
return new GlShader(this, type, finalSource.toString());
}
public <P extends WorldProgram> P compile(ExtensibleGlProgram.Factory<P> worldShaderPipeline) {
public <P extends WorldProgram> P compile(GlProgram.Factory<P> worldShaderPipeline) {
return new ProgramAssembler(this.name)
.attachShader(compile(ShaderType.VERTEX))
.attachShader(compile(ShaderType.FRAGMENT))
@ -105,6 +126,7 @@ public class ShaderCompiler {
* @param sourceFile The file to retrieve the ID for.
* @return A file ID unique to the given sourceFile.
*/
@Override
public int getFileID(SourceFile sourceFile) {
int i = files.indexOf(sourceFile);
if (i != -1) {

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.core.pipeline;
package com.jozufozu.flywheel.core.compile;
import java.util.HashMap;
import java.util.Map;
@ -6,6 +6,8 @@ import java.util.function.Function;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.backend.source.FileResolution;
import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.backend.source.parse.ShaderStruct;
import com.jozufozu.flywheel.backend.source.parse.StructField;
@ -17,25 +19,35 @@ import com.jozufozu.flywheel.backend.source.parse.StructField;
* Shader files are written somewhat abstractly. Subclasses of Template handle those abstractions, using SourceFile
* metadata to generate shader code that OpenGL can use to call into our shader programs.
* </p>
* @param <D> Holds metadata, generates errors.
*/
public class Template<D extends TemplateData> {
public class Template {
private final Map<SourceFile, D> metadata = new HashMap<>();
private final Map<SourceFile, TemplateData> metadata = new HashMap<>();
private final Function<SourceFile, D> reader;
private final Function<SourceFile, TemplateData> reader;
private final GLSLVersion glslVersion;
public Template(GLSLVersion glslVersion, Function<SourceFile, D> reader) {
public Template(GLSLVersion glslVersion, Function<SourceFile, TemplateData> reader) {
this.reader = reader;
this.glslVersion = glslVersion;
}
public D getMetadata(SourceFile file) {
public TemplateData getMetadata(SourceFile file) {
// lazily read files, cache results
return metadata.computeIfAbsent(file, reader);
}
/**
* Creates a program compiler using this template.
* @param factory A factory to add meaning to compiled programs.
* @param header The header file to use for the program.
* @param <P> The type of program to compile.
* @return A program compiler.
*/
public <P extends GlProgram> ProgramCompiler<P> programCompiler(GlProgram.Factory<P> factory, FileResolution header) {
return new ProgramCompiler<>(factory, this, header);
}
public GLSLVersion getVersion() {
return glslVersion;
}

View file

@ -1,10 +1,10 @@
package com.jozufozu.flywheel.core.pipeline;
package com.jozufozu.flywheel.core.compile;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
public interface TemplateData {
void vertexFooter(StringBuilder builder, ShaderCompiler file);
void fragmentFooter(StringBuilder builder, ShaderCompiler file);
void fragmentFooter(StringBuilder builder, FileIndex file);
/**
* Generate the necessary glue code here.

View file

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

View file

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

View file

@ -1,35 +0,0 @@
package com.jozufozu.flywheel.core.pipeline;
import java.util.HashMap;
import java.util.Map;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
/**
* 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 context The context of compilation.
* @return A compiled GlProgram.
*/
public P getProgram(CompilationContext context) {
return cache.computeIfAbsent(context, this.pipeline::compile);
}
public void invalidate() {
cache.values().forEach(P::delete);
cache.clear();
}
}

View file

@ -1,41 +0,0 @@
package com.jozufozu.flywheel.core.pipeline;
import java.util.Objects;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.RenderLayer;
import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.core.shader.ProgramSpec;
/**
* Represents the entire context of a program's usage.
*
* @param layer
* @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(RenderLayer layer, VertexType vertexType, ProgramSpec spec, long ctx) {
public static CompilationContext create(VertexType vertexType, RenderLayer layer, ProgramSpec spec) {
return new CompilationContext(layer, vertexType, spec, spec.getCurrentStateID());
}
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 layer == that.layer && ctx == that.ctx && vertexType == that.vertexType && spec.equals(that.spec);
}
@Override
public int hashCode() {
return Objects.hash(layer, vertexType, spec, ctx);
}
}

View file

@ -1,13 +0,0 @@
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,33 +0,0 @@
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,63 +0,0 @@
package com.jozufozu.flywheel.core.shader;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nonnull;
import com.jozufozu.flywheel.backend.ShaderContext;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import net.minecraft.resources.ResourceLocation;
/**
* A shader program that be arbitrarily "extended". This class can take in any number of program extensions, and
* will initialize them and then call their {@link ExtensionInstance#bind() bind} function every subsequent time this
* program is bound. An "extension" is something that interacts with the shader program in a way that is invisible to
* the caller using the program. This is used by some programs to implement the different fog modes. Other uses might
* include binding extra textures to allow for blocks to have normal maps, for example. As the extensions are
* per-program, this also allows for same extra specialization within a
* {@link ShaderContext ShaderContext}.
*/
public class ExtensibleGlProgram extends GlProgram {
protected final List<ExtensionInstance> extensions = new ArrayList<>();
public ExtensibleGlProgram(ResourceLocation name, int handle) {
super(name, handle);
}
@Override
public void bind() {
super.bind();
extensions.forEach(ExtensionInstance::bind);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("program ")
.append(name)
.append('[');
for (ExtensionInstance extension : extensions) {
builder.append(extension)
.append('+');
}
builder.append(']');
return builder.toString();
}
/**
* A factory interface to create {@link GlProgram}s parameterized by a list of extensions. This doesn't necessarily
* have to return an {@link ExtensibleGlProgram} if implementors want more flexibility for whatever reason.
*/
public interface Factory<P extends GlProgram> {
@Nonnull
P create(ResourceLocation name, int handle);
}
}

View file

@ -43,12 +43,13 @@ public class ProgramSpec {
public final ImmutableList<ProgramState> states;
public ProgramSpec(ResourceLocation source, List<ProgramState> states) {
this.source = Resolver.INSTANCE.findShader(source);
this.source = Resolver.INSTANCE.get(source);
this.states = ImmutableList.copyOf(states);
}
public void setName(ResourceLocation name) {
this.name = name;
this.source.addSpec(name);
}
public ResourceLocation getSourceLoc() {

View file

@ -4,6 +4,7 @@ import static org.lwjgl.opengl.GL20.glUniform1f;
import static org.lwjgl.opengl.GL20.glUniform2f;
import static org.lwjgl.opengl.GL20.glUniform3f;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.util.AnimationTickHolder;
import com.mojang.blaze3d.platform.Window;
import com.mojang.math.Matrix4f;
@ -11,11 +12,12 @@ import com.mojang.math.Matrix4f;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
public class WorldProgram extends ExtensibleGlProgram {
public class WorldProgram extends GlProgram {
protected final int uTime = getUniformLocation("uTime");
protected final int uViewProjection = getUniformLocation("uViewProjection");
protected final int uCameraPos = getUniformLocation("uCameraPos");
protected final int uWindowSize = getUniformLocation("uWindowSize");
private final WorldFog fog;
protected int uBlockAtlas;
protected int uLightMap;
@ -23,7 +25,7 @@ public class WorldProgram extends ExtensibleGlProgram {
public WorldProgram(ResourceLocation name, int handle) {
super(name, handle);
this.extensions.add(new WorldFog(this));
fog = new WorldFog(this);
super.bind();
registerSamplers();
@ -66,7 +68,7 @@ public class WorldProgram extends ExtensibleGlProgram {
@Override
public void bind() {
super.bind();
fog.bind();
uploadWindowSize();
uploadTime(AnimationTickHolder.getRenderTime());
}

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.core.shader;
import javax.annotation.ParametersAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;

View file

@ -0,0 +1,9 @@
{
"source": "flywheel:passthru.vert",
"states": [
{
"when": "flywheel:normal_debug",
"define": "DEBUG_NORMAL"
}
]
}

View file

@ -0,0 +1,7 @@
#use "flywheel:block.frag"
#if defined(VERTEX_SHADER)
void vertex(inout Vertex v) {
}
#endif