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
This commit is contained in:
Jozsef 2021-07-02 12:34:12 -07:00
parent 959434e01a
commit 15d5396bc9
12 changed files with 220 additions and 68 deletions

View file

@ -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<String> 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) {

View file

@ -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
* <a href="https://www.khronos.org/opengl/wiki/Buffer_Object#Buffer_Object_Usage">this article</a>.
*/
public enum GlBufferUsage {
STREAM_DRAW(GL15.GL_STREAM_DRAW),
STREAM_READ(GL15.GL_STREAM_READ),

View file

@ -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<SourceFile> recurseIncludes(SourceFile from) {
ShaderSources sources = from.getParent();
Set<ResourceLocation> seen = new HashSet<>();
seen.add(from.name);
List<SourceFile> out = new ArrayList<>();
process(sources, seen, out, from);
return out;
}
private static void process(ShaderSources sources, Set<ResourceLocation> seen, List<SourceFile> out, SourceFile source) {
ImmutableList<ResourceLocation> includes = source.getIncludes();
for (ResourceLocation include : includes) {
if (seen.add(include)) {
SourceFile file = sources.source(include);
process(sources, seen, out, file);
out.add(file);
}
}
}
}

View file

@ -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<SourceFile> sources = new ArrayList<>();
public PipelineProgramBuilder() {
}
public PipelineProgramBuilder include(SourceFile file) {
sources.add(file);
return this;
}
public PipelineProgramBuilder includeAll(Collection<? extends SourceFile> files) {
sources.addAll(files);
return this;
}
}

View file

@ -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<String, ShaderFunction> functions = new HashMap<>();
// function name -> function object
private final ImmutableMap<String, ShaderFunction> functions;
private final ImmutableList<ResourceLocation> 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<Span> 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<String, ShaderFunction> getFunctions() {
return functions;
}
public ImmutableList<ResourceLocation> getIncludes() {
return includes;
}
private ImmutableList<ResourceLocation> parseIncludes() {
Matcher uses = includePattern.matcher(source);
List<ResourceLocation> 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<String, ShaderFunction> parseFunctions() {
Matcher matcher = functionDeclaration.matcher(source);
Map<String, ShaderFunction> 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) {

View file

@ -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<P extends WorldProgram> {
@Nullable // TODO: temporary null return
public IMultiProgram<P> compile(SourceFile file) {
public P compile(SourceFile file) {
PipelineProgramBuilder builder = new PipelineProgramBuilder();
builder.includeAll(Includer.recurseIncludes(file));
builder.include(file);
return null;
}

View file

@ -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<P extends WorldProgram> extends ShaderContext<P> {
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<P extends WorldProgram> extends ShaderContext<P> {
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<P> 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);
}

View file

@ -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<P extends GlProgram> implements IMultiProgram<P> {
List<Pair<IContextCondition, P>> variants;
P fallback;
private final List<Pair<IGameStateCondition, P>> variants;
private final P fallback;
public StateSensitiveMultiProgram(ExtensibleGlProgram.Factory<P> factory, ShaderContext<P> context, ProgramSpec p) {
variants = new ArrayList<>(p.states.size());
for (ProgramState state : p.states) {
Program variant = context.loadAndLink(p, state);
Pair<IContextCondition, P> pair = Pair.of(state.getContext(), factory.create(variant, state.getExtensions()));
variants.add(pair);
}
fallback = factory.create(context.loadAndLink(p, null));
protected StateSensitiveMultiProgram(List<Pair<IGameStateCondition, P>> variants, P fallback) {
this.variants = variants;
this.fallback = fallback;
}
@Override
public P get() {
for (Pair<IContextCondition, P> variant : variants) {
for (Pair<IGameStateCondition, P> variant : variants) {
if (variant.getFirst()
.get()) return variant.getSecond();
.isMet()) return variant.getSecond();
}
return fallback;
@ -43,11 +30,29 @@ public class StateSensitiveMultiProgram<P extends GlProgram> implements IMultiPr
@Override
public void delete() {
for (Pair<IContextCondition, P> variant : variants) {
for (Pair<IGameStateCondition, P> variant : variants) {
variant.getSecond()
.delete();
}
fallback.delete();
}
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 IMultiProgram<P> build() {
return new StateSensitiveMultiProgram<>(ImmutableList.copyOf(variants), fallback);
}
}
}

View file

@ -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<BooleanContextCondition> BOOLEAN_SUGAR = IGameStateProvider.CODEC.xmap(gameContext -> {
public static final Codec<BooleanGameStateCondition> 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();
}
}

View file

@ -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();
}

View file

@ -13,10 +13,10 @@ import com.mojang.serialization.codecs.RecordCodecBuilder;
public class ProgramState {
// TODO: Use Codec.dispatch
private static final Codec<IContextCondition> WHEN = Codec.either(BooleanContextCondition.BOOLEAN_SUGAR, SpecificValueCondition.CODEC)
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 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<ProgramState> 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<String> defines;
private final List<IProgramExtension> extensions;
public ProgramState(IContextCondition context, List<String> defines, List<IProgramExtension> extensions) {
public ProgramState(IGameStateCondition context, List<String> defines, List<IProgramExtension> extensions) {
this.context = context;
this.defines = defines;
this.extensions = extensions;
}
public IContextCondition getContext() {
public IGameStateCondition getContext() {
return context;
}

View file

@ -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<SpecificValueCondition> 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());
}