From 07b93e4ec5cbf4915ff4dfb1d304ecfa9dde2583 Mon Sep 17 00:00:00 2001 From: Jozsef Date: Fri, 2 Jul 2021 12:34:12 -0700 Subject: [PATCH] More loading tweaks - Immutable function map - Parse #use directives on load - Recursive include gathering - More sane spec loading in WorldContext - StateSensitiveMultiProgram builder - Rename confusing game state things --- .../flywheel/backend/ShaderSources.java | 13 ++++- .../backend/gl/buffer/GlBufferUsage.java | 4 ++ .../flywheel/backend/pipeline/Includer.java | 44 ++++++++++++++ .../pipeline/PipelineProgramBuilder.java | 24 ++++++++ .../flywheel/backend/pipeline/SourceFile.java | 57 +++++++++++++++++-- .../backend/pipeline/WorldShaderPipeline.java | 11 +++- .../jozufozu/flywheel/core/WorldContext.java | 34 +++++++---- .../shader/StateSensitiveMultiProgram.java | 51 +++++++++-------- ...on.java => BooleanGameStateCondition.java} | 14 ++--- ...ondition.java => IGameStateCondition.java} | 6 +- .../core/shader/spec/ProgramState.java | 22 +++---- .../shader/spec/SpecificValueCondition.java | 8 +-- 12 files changed, 220 insertions(+), 68 deletions(-) create mode 100644 src/main/java/com/jozufozu/flywheel/backend/pipeline/Includer.java create mode 100644 src/main/java/com/jozufozu/flywheel/backend/pipeline/PipelineProgramBuilder.java rename src/main/java/com/jozufozu/flywheel/core/shader/spec/{BooleanContextCondition.java => BooleanGameStateCondition.java} (52%) rename src/main/java/com/jozufozu/flywheel/core/shader/spec/{IContextCondition.java => IGameStateCondition.java} (67%) diff --git a/src/main/java/com/jozufozu/flywheel/backend/ShaderSources.java b/src/main/java/com/jozufozu/flywheel/backend/ShaderSources.java index 5cffca486..c4485ebe5 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/ShaderSources.java +++ b/src/main/java/com/jozufozu/flywheel/backend/ShaderSources.java @@ -133,10 +133,12 @@ public class ShaderSources implements ISelectiveResourceReloadListener { } } + @Deprecated public void notifyError() { shouldCrash = true; } + @Deprecated @Nonnull public String getShaderSource(ResourceLocation loc) { String source = shaderSource.get(loc); @@ -172,12 +174,19 @@ public class ShaderSources implements ISelectiveResourceReloadListener { } } + @Deprecated public Shader source(ResourceLocation name, ShaderType type) { return new Shader(this, type, name, getShaderSource(name)); } - public static Stream lines(String s) { - return new BufferedReader(new StringReader(s)).lines(); + public SourceFile source(ResourceLocation name) { + SourceFile source = shaderSources.get(name); + + if (source == null) { + throw new ShaderLoadingException(String.format("shader '%s' does not exist", name)); + } + + return source; } public String readToString(InputStream is) { diff --git a/src/main/java/com/jozufozu/flywheel/backend/gl/buffer/GlBufferUsage.java b/src/main/java/com/jozufozu/flywheel/backend/gl/buffer/GlBufferUsage.java index e01f28afe..ef1a716fb 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/gl/buffer/GlBufferUsage.java +++ b/src/main/java/com/jozufozu/flywheel/backend/gl/buffer/GlBufferUsage.java @@ -2,6 +2,10 @@ package com.jozufozu.flywheel.backend.gl.buffer; import org.lwjgl.opengl.GL15; +/** + * Gives a hint to the driver about how you intend to use a buffer. For a detailed explanation, see + * this article. + */ public enum GlBufferUsage { STREAM_DRAW(GL15.GL_STREAM_DRAW), STREAM_READ(GL15.GL_STREAM_READ), diff --git a/src/main/java/com/jozufozu/flywheel/backend/pipeline/Includer.java b/src/main/java/com/jozufozu/flywheel/backend/pipeline/Includer.java new file mode 100644 index 000000000..3d9ca5160 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/pipeline/Includer.java @@ -0,0 +1,44 @@ +package com.jozufozu.flywheel.backend.pipeline; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.Stack; + +import com.google.common.collect.ImmutableList; +import com.jozufozu.flywheel.backend.ShaderSources; + +import net.minecraft.util.ResourceLocation; + +public class Includer { + + public static List recurseIncludes(SourceFile from) { + ShaderSources sources = from.getParent(); + + Set seen = new HashSet<>(); + + seen.add(from.name); + + List out = new ArrayList<>(); + + process(sources, seen, out, from); + + return out; + } + + private static void process(ShaderSources sources, Set seen, List out, SourceFile source) { + ImmutableList includes = source.getIncludes(); + + for (ResourceLocation include : includes) { + + if (seen.add(include)) { + SourceFile file = sources.source(include); + + process(sources, seen, out, file); + + out.add(file); + } + } + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/pipeline/PipelineProgramBuilder.java b/src/main/java/com/jozufozu/flywheel/backend/pipeline/PipelineProgramBuilder.java new file mode 100644 index 000000000..1842801ed --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/pipeline/PipelineProgramBuilder.java @@ -0,0 +1,24 @@ +package com.jozufozu.flywheel.backend.pipeline; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class PipelineProgramBuilder { + + private final List sources = new ArrayList<>(); + + public PipelineProgramBuilder() { + + } + + public PipelineProgramBuilder include(SourceFile file) { + sources.add(file); + return this; + } + + public PipelineProgramBuilder includeAll(Collection files) { + sources.addAll(files); + return this; + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/pipeline/SourceFile.java b/src/main/java/com/jozufozu/flywheel/backend/pipeline/SourceFile.java index a9fff8634..57be021c3 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/pipeline/SourceFile.java +++ b/src/main/java/com/jozufozu/flywheel/backend/pipeline/SourceFile.java @@ -2,12 +2,17 @@ package com.jozufozu.flywheel.backend.pipeline; import java.io.BufferedReader; import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.jozufozu.flywheel.backend.ShaderSources; import com.jozufozu.flywheel.backend.pipeline.parse.ShaderFunction; import com.jozufozu.flywheel.backend.pipeline.span.ErrorSpan; @@ -27,25 +32,63 @@ public class SourceFile { public final ResourceLocation name; private final String source; - private final ShaderSources loader; + private final ShaderSources parent; - private final Map functions = new HashMap<>(); + // function name -> function object + private final ImmutableMap functions; + private final ImmutableList includes; - public SourceFile(ShaderSources loader, ResourceLocation name, String source) { - this.loader = loader; + // Sections of the source that must be trimmed for compilation. + private final List elisions = new ArrayList<>(); + + public SourceFile(ShaderSources parent, ResourceLocation name, String source) { + this.parent = parent; this.name = name; this.source = source; - parseFunctions(); + functions = parseFunctions(); + includes = parseIncludes(); } public String getSource() { return source; } - protected void parseFunctions() { + public ShaderSources getParent() { + return parent; + } + + public ImmutableMap getFunctions() { + return functions; + } + + public ImmutableList getIncludes() { + return includes; + } + + private ImmutableList parseIncludes() { + Matcher uses = includePattern.matcher(source); + + List includes = new ArrayList<>(); + + while (uses.find()) { + Span use = Span.fromMatcher(this, uses); + + elisions.add(use); // we have to trim that later + + ResourceLocation loc = new ResourceLocation(uses.group(1)); // TODO: error gracefully + + includes.add(loc); + } + + return ImmutableList.copyOf(includes); + } + + private ImmutableMap parseFunctions() { Matcher matcher = functionDeclaration.matcher(source); + Map functions = new HashMap<>(); + while (matcher.find()) { Span type = Span.fromMatcher(this, matcher, 1); Span name = Span.fromMatcher(this, matcher, 2); @@ -68,6 +111,8 @@ public class SourceFile { functions.put(name.get(), function); } + + return ImmutableMap.copyOf(functions); } private int findEndOfBlock(int end) { diff --git a/src/main/java/com/jozufozu/flywheel/backend/pipeline/WorldShaderPipeline.java b/src/main/java/com/jozufozu/flywheel/backend/pipeline/WorldShaderPipeline.java index 3a6898f28..03383b6f8 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/pipeline/WorldShaderPipeline.java +++ b/src/main/java/com/jozufozu/flywheel/backend/pipeline/WorldShaderPipeline.java @@ -1,5 +1,7 @@ package com.jozufozu.flywheel.backend.pipeline; +import java.util.List; + import javax.annotation.Nullable; import com.jozufozu.flywheel.core.shader.IMultiProgram; @@ -8,7 +10,14 @@ import com.jozufozu.flywheel.core.shader.WorldProgram; public class WorldShaderPipeline

{ @Nullable // TODO: temporary null return - public IMultiProgram

compile(SourceFile file) { + public P compile(SourceFile file) { + + PipelineProgramBuilder builder = new PipelineProgramBuilder(); + + builder.includeAll(Includer.recurseIncludes(file)); + + builder.include(file); + return null; } diff --git a/src/main/java/com/jozufozu/flywheel/core/WorldContext.java b/src/main/java/com/jozufozu/flywheel/core/WorldContext.java index a387fea1c..ca7016aa0 100644 --- a/src/main/java/com/jozufozu/flywheel/core/WorldContext.java +++ b/src/main/java/com/jozufozu/flywheel/core/WorldContext.java @@ -23,6 +23,8 @@ import com.jozufozu.flywheel.backend.loading.ShaderTransformer; import com.jozufozu.flywheel.core.shader.ExtensibleGlProgram; import com.jozufozu.flywheel.core.shader.StateSensitiveMultiProgram; import com.jozufozu.flywheel.core.shader.WorldProgram; +import com.jozufozu.flywheel.core.shader.spec.ProgramSpec; +import com.jozufozu.flywheel.core.shader.spec.ProgramState; import com.jozufozu.flywheel.util.WorldAttached; import net.minecraft.util.ResourceLocation; @@ -109,17 +111,7 @@ public class WorldContext

extends ShaderContext

{ specStream.get() .map(backend::getSpec) - .forEach(spec -> { - - try { - programs.put(spec.name, new StateSensitiveMultiProgram<>(factory, this, spec)); - - Backend.log.debug("Loaded program {}", spec.name); - } catch (Exception e) { - Backend.log.error("Program '{}': {}", spec.name, e); - backend.sources.notifyError(); - } - }); + .forEach(this::loadSpec); } @Override @@ -154,6 +146,26 @@ public class WorldContext

extends ShaderContext

{ throw new ShaderLoadingException(String.format("%s is missing %s, cannot use in World Context", shader.type.name, declaration)); } + private void loadSpec(ProgramSpec spec) { + + try { + StateSensitiveMultiProgram.Builder

builder = new StateSensitiveMultiProgram.Builder<>(factory.create(loadAndLink(spec, null))); + + for (ProgramState state : spec.states) { + Program variant = loadAndLink(spec, state); + + builder.withVariant(state.getContext(), factory.create(variant, state.getExtensions())); + } + + programs.put(spec.name, builder.build()); + + Backend.log.debug("Loaded program {}", spec.name); + } catch (Exception e) { + Backend.log.error("Program '{}': {}", spec.name, e); + backend.sources.notifyError(); + } + } + public interface TemplateFactory { ProgramTemplate create(ShaderSources loader); } diff --git a/src/main/java/com/jozufozu/flywheel/core/shader/StateSensitiveMultiProgram.java b/src/main/java/com/jozufozu/flywheel/core/shader/StateSensitiveMultiProgram.java index 945f5948c..dbd1e2dc4 100644 --- a/src/main/java/com/jozufozu/flywheel/core/shader/StateSensitiveMultiProgram.java +++ b/src/main/java/com/jozufozu/flywheel/core/shader/StateSensitiveMultiProgram.java @@ -3,39 +3,26 @@ package com.jozufozu.flywheel.core.shader; import java.util.ArrayList; import java.util.List; -import com.jozufozu.flywheel.backend.ShaderContext; +import com.google.common.collect.ImmutableList; import com.jozufozu.flywheel.backend.gl.shader.GlProgram; -import com.jozufozu.flywheel.backend.loading.Program; -import com.jozufozu.flywheel.core.shader.spec.IContextCondition; -import com.jozufozu.flywheel.core.shader.spec.ProgramSpec; -import com.jozufozu.flywheel.core.shader.spec.ProgramState; +import com.jozufozu.flywheel.core.shader.spec.IGameStateCondition; import com.jozufozu.flywheel.util.Pair; public class StateSensitiveMultiProgram

implements IMultiProgram

{ - List> variants; - P fallback; + private final List> variants; + private final P fallback; - public StateSensitiveMultiProgram(ExtensibleGlProgram.Factory

factory, ShaderContext

context, ProgramSpec p) { - variants = new ArrayList<>(p.states.size()); - - for (ProgramState state : p.states) { - - Program variant = context.loadAndLink(p, state); - - Pair pair = Pair.of(state.getContext(), factory.create(variant, state.getExtensions())); - - variants.add(pair); - } - - fallback = factory.create(context.loadAndLink(p, null)); + protected StateSensitiveMultiProgram(List> variants, P fallback) { + this.variants = variants; + this.fallback = fallback; } @Override public P get() { - for (Pair variant : variants) { + for (Pair variant : variants) { if (variant.getFirst() - .get()) return variant.getSecond(); + .isMet()) return variant.getSecond(); } return fallback; @@ -43,11 +30,29 @@ public class StateSensitiveMultiProgram

implements IMultiPr @Override public void delete() { - for (Pair variant : variants) { + for (Pair variant : variants) { variant.getSecond() .delete(); } fallback.delete(); } + + public static class Builder

{ + private final P fallback; + private final List> variants = new ArrayList<>(); + + public Builder(P fallback) { + this.fallback = fallback; + } + + public Builder

withVariant(IGameStateCondition condition, P program) { + variants.add(Pair.of(condition, program)); + return this; + } + + public IMultiProgram

build() { + return new StateSensitiveMultiProgram<>(ImmutableList.copyOf(variants), fallback); + } + } } diff --git a/src/main/java/com/jozufozu/flywheel/core/shader/spec/BooleanContextCondition.java b/src/main/java/com/jozufozu/flywheel/core/shader/spec/BooleanGameStateCondition.java similarity index 52% rename from src/main/java/com/jozufozu/flywheel/core/shader/spec/BooleanContextCondition.java rename to src/main/java/com/jozufozu/flywheel/core/shader/spec/BooleanGameStateCondition.java index a61a69f51..91534d176 100644 --- a/src/main/java/com/jozufozu/flywheel/core/shader/spec/BooleanContextCondition.java +++ b/src/main/java/com/jozufozu/flywheel/core/shader/spec/BooleanGameStateCondition.java @@ -5,18 +5,18 @@ import com.mojang.serialization.Codec; import net.minecraft.util.ResourceLocation; -public class BooleanContextCondition implements IContextCondition { +public class BooleanGameStateCondition implements IGameStateCondition { - public static final Codec BOOLEAN_SUGAR = IGameStateProvider.CODEC.xmap(gameContext -> { + public static final Codec BOOLEAN_SUGAR = IGameStateProvider.CODEC.xmap(gameContext -> { if (gameContext instanceof IBooleanStateProvider) { - return new BooleanContextCondition(((IBooleanStateProvider) gameContext)); + return new BooleanGameStateCondition(((IBooleanStateProvider) gameContext)); } return null; - }, IContextCondition::contextProvider); + }, IGameStateCondition::getStateProvider); protected final IBooleanStateProvider context; - public BooleanContextCondition(IBooleanStateProvider context) { + public BooleanGameStateCondition(IBooleanStateProvider context) { this.context = context; } @@ -26,12 +26,12 @@ public class BooleanContextCondition implements IContextCondition { } @Override - public IGameStateProvider contextProvider() { + public IGameStateProvider getStateProvider() { return context; } @Override - public boolean get() { + public boolean isMet() { return context.isTrue(); } } diff --git a/src/main/java/com/jozufozu/flywheel/core/shader/spec/IContextCondition.java b/src/main/java/com/jozufozu/flywheel/core/shader/spec/IGameStateCondition.java similarity index 67% rename from src/main/java/com/jozufozu/flywheel/core/shader/spec/IContextCondition.java rename to src/main/java/com/jozufozu/flywheel/core/shader/spec/IGameStateCondition.java index 5d6d7ee42..da51a2405 100644 --- a/src/main/java/com/jozufozu/flywheel/core/shader/spec/IContextCondition.java +++ b/src/main/java/com/jozufozu/flywheel/core/shader/spec/IGameStateCondition.java @@ -4,11 +4,11 @@ import com.jozufozu.flywheel.core.shader.gamestate.IGameStateProvider; import net.minecraft.util.ResourceLocation; -public interface IContextCondition { +public interface IGameStateCondition { ResourceLocation getID(); - IGameStateProvider contextProvider(); + IGameStateProvider getStateProvider(); - boolean get(); + boolean isMet(); } diff --git a/src/main/java/com/jozufozu/flywheel/core/shader/spec/ProgramState.java b/src/main/java/com/jozufozu/flywheel/core/shader/spec/ProgramState.java index 2e39487e0..79f3d7f23 100644 --- a/src/main/java/com/jozufozu/flywheel/core/shader/spec/ProgramState.java +++ b/src/main/java/com/jozufozu/flywheel/core/shader/spec/ProgramState.java @@ -13,10 +13,10 @@ import com.mojang.serialization.codecs.RecordCodecBuilder; public class ProgramState { // TODO: Use Codec.dispatch - private static final Codec WHEN = Codec.either(BooleanContextCondition.BOOLEAN_SUGAR, SpecificValueCondition.CODEC) + private static final Codec WHEN = Codec.either(BooleanGameStateCondition.BOOLEAN_SUGAR, SpecificValueCondition.CODEC) .flatXmap(either -> either.map(DataResult::success, DataResult::success), any -> { - if (any instanceof BooleanContextCondition) { - return DataResult.success(Either.left((BooleanContextCondition) any)); + if (any instanceof BooleanGameStateCondition) { + return DataResult.success(Either.left((BooleanGameStateCondition) any)); } if (any instanceof SpecificValueCondition) { @@ -27,24 +27,24 @@ public class ProgramState { }); public static final Codec CODEC = RecordCodecBuilder.create(state -> state.group(WHEN.fieldOf("when") - .forGetter(ProgramState::getContext), CodecUtil.oneOrMore(Codec.STRING) - .optionalFieldOf("define", Collections.emptyList()) - .forGetter(ProgramState::getDefines), CodecUtil.oneOrMore(IProgramExtension.CODEC) - .optionalFieldOf("extend", Collections.emptyList()) - .forGetter(ProgramState::getExtensions)) + .forGetter(ProgramState::getContext), CodecUtil.oneOrMore(Codec.STRING) + .optionalFieldOf("define", Collections.emptyList()) + .forGetter(ProgramState::getDefines), CodecUtil.oneOrMore(IProgramExtension.CODEC) + .optionalFieldOf("extend", Collections.emptyList()) + .forGetter(ProgramState::getExtensions)) .apply(state, ProgramState::new)); - private final IContextCondition context; + private final IGameStateCondition context; private final List defines; private final List extensions; - public ProgramState(IContextCondition context, List defines, List extensions) { + public ProgramState(IGameStateCondition context, List defines, List extensions) { this.context = context; this.defines = defines; this.extensions = extensions; } - public IContextCondition getContext() { + public IGameStateCondition getContext() { return context; } diff --git a/src/main/java/com/jozufozu/flywheel/core/shader/spec/SpecificValueCondition.java b/src/main/java/com/jozufozu/flywheel/core/shader/spec/SpecificValueCondition.java index 5570f1a72..d508feae6 100644 --- a/src/main/java/com/jozufozu/flywheel/core/shader/spec/SpecificValueCondition.java +++ b/src/main/java/com/jozufozu/flywheel/core/shader/spec/SpecificValueCondition.java @@ -6,10 +6,10 @@ import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.util.ResourceLocation; -public class SpecificValueCondition implements IContextCondition { +public class SpecificValueCondition implements IGameStateCondition { public static final Codec CODEC = RecordCodecBuilder.create(condition -> condition.group(IGameStateProvider.CODEC.fieldOf("provider") - .forGetter(SpecificValueCondition::contextProvider), Codec.STRING.fieldOf("value") + .forGetter(SpecificValueCondition::getStateProvider), Codec.STRING.fieldOf("value") .forGetter(SpecificValueCondition::getValue)) .apply(condition, SpecificValueCondition::new)); @@ -31,12 +31,12 @@ public class SpecificValueCondition implements IContextCondition { } @Override - public IGameStateProvider contextProvider() { + public IGameStateProvider getStateProvider() { return context; } @Override - public boolean get() { + public boolean isMet() { return required.equals(context.getValue() .toString()); }