Some decoupling

- New Loader class in charge of loading and compiling everything
 - ShaderSources now only loads sources
 - ShaderSources is immutable now
 - Resolver singleton in charge of managing name resolutions
 - ProgramSpecs go through Resolver
 - WorldShaderPipeline no longer needs reference to ShaderSources
This commit is contained in:
Jozufozu 2021-08-10 15:20:51 -07:00
parent 82ea5b1720
commit 8f13097a40
17 changed files with 235 additions and 165 deletions

View file

@ -37,6 +37,7 @@ public class Backend {
public final Minecraft minecraft;
public ShaderSources sources;
public Loader loader;
public GLCapabilities capabilities;
public GlCompat compat;
@ -55,7 +56,7 @@ public class Backend {
minecraft = Minecraft.getInstance();
if (minecraft == null) return;
sources = new ShaderSources(this);
loader = new Loader(this);
OptifineHandler.init();
}

View file

@ -0,0 +1,125 @@
package com.jozufozu.flywheel.backend;
import java.util.Collection;
import java.util.function.Predicate;
import com.google.gson.Gson;
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.spec.ProgramSpec;
import com.jozufozu.flywheel.event.GatherContextEvent;
import com.jozufozu.flywheel.util.ResourceUtil;
import com.jozufozu.flywheel.util.StreamUtil;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.JsonOps;
import net.minecraft.client.Minecraft;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.resources.IReloadableResourceManager;
import net.minecraft.resources.IResource;
import net.minecraft.resources.IResourceManager;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.ModLoader;
import net.minecraftforge.resource.IResourceType;
import net.minecraftforge.resource.ISelectiveResourceReloadListener;
import net.minecraftforge.resource.VanillaResourceType;
/**
* The main entity for loading shaders.
*
* <p>
* This class is responsible for invoking the loading, parsing, and compilation stages.
* </p>
*/
public class Loader implements ISelectiveResourceReloadListener {
public static final String PROGRAM_DIR = "flywheel/programs/";
private static final Gson GSON = new GsonBuilder().create();
private final Backend backend;
public boolean shouldCrash;
public Loader(Backend backend) {
this.backend = backend;
IResourceManager manager = backend.minecraft.getResourceManager();
if (manager instanceof IReloadableResourceManager) {
((IReloadableResourceManager) manager).registerReloadListener(this);
}
}
public void notifyError() {
shouldCrash = true;
}
@Override
public void onResourceManagerReload(IResourceManager manager, Predicate<IResourceType> predicate) {
if (predicate.test(VanillaResourceType.SHADERS)) {
backend.refresh();
if (backend.gl20()) {
shouldCrash = false;
backend._clearContexts();
Resolver.INSTANCE.invalidate();
ModLoader.get()
.postEvent(new GatherContextEvent(backend));
backend.sources = new ShaderSources(manager);
loadProgramSpecs(manager);
Resolver.INSTANCE.resolve(backend.sources);
for (IShaderContext<?> context : backend.allContexts()) {
context.load();
}
if (shouldCrash) {
throw new ShaderLoadingException("Could not load all shaders, see log for details");
}
Backend.log.info("Loaded all shader programs.");
ClientWorld world = Minecraft.getInstance().level;
if (Backend.isFlywheelWorld(world)) {
// TODO: looks like it might be good to have another event here
InstancedRenderDispatcher.loadAllInWorld(world);
CrumblingRenderer.reset();
}
}
}
}
private void loadProgramSpecs(IResourceManager manager) {
Collection<ResourceLocation> programSpecs = manager.listResources(PROGRAM_DIR, s -> s.endsWith(".json"));
for (ResourceLocation location : programSpecs) {
try {
IResource file = manager.getResource(location);
String s = StreamUtil.readToString(file.getInputStream());
ResourceLocation specName = ResourceUtil.trim(location, PROGRAM_DIR, ".json");
DataResult<Pair<ProgramSpec, JsonElement>> result = ProgramSpec.CODEC.decode(JsonOps.INSTANCE, GSON.fromJson(s, JsonElement.class));
ProgramSpec spec = result.get()
.orThrow()
.getFirst();
spec.setName(specName);
backend.register(spec);
} catch (Exception e) {
Backend.log.error(e);
}
}
}
}

View file

@ -3,6 +3,7 @@ package com.jozufozu.flywheel.backend.pipeline;
import java.util.Optional;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.source.ShaderLoadingException;
import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.backend.source.error.ErrorReporter;
import com.jozufozu.flywheel.backend.source.parse.ShaderFunction;
@ -36,7 +37,7 @@ public class InstancingProgramMetaData {
}
if (!fragmentFunc.isPresent() || !vertexFunc.isPresent()) {
throw new RuntimeException();
throw new ShaderLoadingException();
}
fragmentMain = fragmentFunc.get();
@ -50,7 +51,7 @@ public class InstancingProgramMetaData {
if (vertexParams.size() != 2) {
ErrorReporter.generateSpanError(vertexMain.getArgs(), "instancing requires vertex function to have 2 arguments");
throw new RuntimeException();
throw new ShaderLoadingException();
}
interpolantName = vertexMain.getType();
@ -76,7 +77,7 @@ public class InstancingProgramMetaData {
}
if (!maybeVertex.isPresent() || !maybeInterpolant.isPresent() || !maybeInstance.isPresent()) {
throw new RuntimeException();
throw new ShaderLoadingException();
}
interpolant = maybeInterpolant.get();

View file

@ -7,7 +7,6 @@ import java.util.List;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.source.FileResolution;
import com.jozufozu.flywheel.backend.source.ShaderSources;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.core.shader.ExtensibleGlProgram;
@ -21,15 +20,12 @@ import net.minecraft.util.ResourceLocation;
public class WorldShaderPipeline<P extends WorldProgram> implements IShaderPipeline<P> {
private final ShaderSources sources;
private final ExtensibleGlProgram.Factory<P> factory;
private final Template<?> template;
private final FileResolution header;
public WorldShaderPipeline(ShaderSources sources, ExtensibleGlProgram.Factory<P> factory, Template<?> template, FileResolution header) {
this.sources = sources;
public WorldShaderPipeline(ExtensibleGlProgram.Factory<P> factory, Template<?> template, FileResolution header) {
this.factory = factory;
this.template = template;
this.header = header;
@ -37,7 +33,7 @@ public class WorldShaderPipeline<P extends WorldProgram> implements IShaderPipel
public IMultiProgram<P> compile(ProgramSpec spec) {
SourceFile file = sources.source(spec.source);
SourceFile file = spec.getSource().getFile();
return compile(spec.name, file, spec.getStates());
}

View file

@ -26,12 +26,10 @@ public class FileResolution {
* Spans that have references that resolved to this.
*/
private final List<Span> foundSpans = new ArrayList<>();
private final ShaderSources parent;
private final ResourceLocation fileLoc;
private SourceFile file;
public FileResolution(ShaderSources parent, ResourceLocation fileLoc) {
this.parent = parent;
public FileResolution(ResourceLocation fileLoc) {
this.fileLoc = fileLoc;
}
@ -52,8 +50,9 @@ public class FileResolution {
* </p>
* @param span A span where this file is referenced.
*/
public void addSpan(Span span) {
public FileResolution addSpan(Span span) {
foundSpans.add(span);
return this;
}
/**
@ -63,10 +62,10 @@ public class FileResolution {
* Called after all files are loaded. If we can't find the file here, it doesn't exist.
* </p>
*/
void resolve() {
void resolve(ISourceHolder sources) {
try {
file = this.parent.source(fileLoc);
file = sources.findSource(fileLoc);
} catch (RuntimeException error) {
ErrorBuilder builder = new ErrorBuilder();
builder.error(String.format("could not find source for file %s", fileLoc));

View file

@ -0,0 +1,12 @@
package com.jozufozu.flywheel.backend.source;
import net.minecraft.util.ResourceLocation;
/**
* A minimal source file lookup function.
*/
@FunctionalInterface
public interface ISourceHolder {
SourceFile findSource(ResourceLocation name);
}

View file

@ -0,0 +1,47 @@
package com.jozufozu.flywheel.backend.source;
import java.util.HashMap;
import java.util.Map;
import net.minecraft.util.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.
* </p>
*/
public class Resolver {
public static final Resolver INSTANCE = new Resolver();
private final Map<ResourceLocation, FileResolution> resolutions = new HashMap<>();
public FileResolution findShader(ResourceLocation fileLoc) {
return resolutions.computeIfAbsent(fileLoc, FileResolution::new);
}
/**
* Try and resolve all referenced source files, printing errors if any aren't found.
*/
public void resolve(ISourceHolder sources) {
for (FileResolution resolution : resolutions.values()) {
resolution.resolve(sources);
}
}
/**
* Invalidates all FileResolutions.
*
* <p>
* Called on resource reload.
* </p>
*/
public void invalidate() {
resolutions.values().forEach(FileResolution::invalidate);
}
}

View file

@ -5,126 +5,27 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.IShaderContext;
import com.jozufozu.flywheel.backend.ResourceUtil;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.core.crumbling.CrumblingRenderer;
import com.jozufozu.flywheel.core.shader.spec.ProgramSpec;
import com.jozufozu.flywheel.event.GatherContextEvent;
import com.jozufozu.flywheel.util.ResourceUtil;
import com.jozufozu.flywheel.util.StreamUtil;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.JsonOps;
import net.minecraft.client.Minecraft;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.resources.IReloadableResourceManager;
import net.minecraft.resources.IResource;
import net.minecraft.resources.IResourceManager;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.ModLoader;
import net.minecraftforge.resource.IResourceType;
import net.minecraftforge.resource.ISelectiveResourceReloadListener;
import net.minecraftforge.resource.VanillaResourceType;
/**
* The main entity for loading shaders.
*
* <p>
* This class is responsible for invoking the loading, parsing, and compilation stages.
* </p>
* The main object for loading and parsing source files.
*/
public class ShaderSources implements ISelectiveResourceReloadListener {
public class ShaderSources implements ISourceHolder {
public static final String SHADER_DIR = "flywheel/shaders/";
public static final String PROGRAM_DIR = "flywheel/programs/";
public static final ArrayList<String> EXTENSIONS = Lists.newArrayList(".vert", ".vsh", ".frag", ".fsh", ".glsl");
private static final Gson GSON = new GsonBuilder().create();
private final Map<ResourceLocation, SourceFile> shaderSources = new HashMap<>();
private final Map<ResourceLocation, FileResolution> resolutions = new HashMap<>();
public final Index index;
private boolean shouldCrash;
private final Backend backend;
public Index index;
public ShaderSources(Backend backend) {
this.backend = backend;
IResourceManager manager = backend.minecraft.getResourceManager();
if (manager instanceof IReloadableResourceManager) {
((IReloadableResourceManager) manager).registerReloadListener(this);
}
}
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 FileResolution resolveFile(ResourceLocation fileLoc) {
return resolutions.computeIfAbsent(fileLoc, l -> new FileResolution(this, l));
}
@Deprecated
public void notifyError() {
shouldCrash = true;
}
@Override
public void onResourceManagerReload(IResourceManager manager, Predicate<IResourceType> predicate) {
if (predicate.test(VanillaResourceType.SHADERS)) {
backend.refresh();
if (backend.gl20()) {
shouldCrash = false;
backend._clearContexts();
resolutions.values().forEach(FileResolution::invalidate);
shaderSources.clear();
ModLoader.get()
.postEvent(new GatherContextEvent(backend));
loadProgramSpecs(manager);
loadShaderSources(manager);
for (FileResolution resolution : resolutions.values()) {
resolution.resolve();
}
for (IShaderContext<?> context : backend.allContexts()) {
context.load();
}
if (shouldCrash) {
throw new ShaderLoadingException("Could not load all shaders, see log for details");
}
Backend.log.info("Loaded all shader programs.");
ClientWorld world = Minecraft.getInstance().level;
if (Backend.isFlywheelWorld(world)) {
// TODO: looks like it might be good to have another event here
InstancedRenderDispatcher.loadAllInWorld(world);
CrumblingRenderer.reset();
}
}
}
}
private void loadShaderSources(IResourceManager manager) {
public ShaderSources(IResourceManager manager) {
Collection<ResourceLocation> allShaders = manager.listResources(SHADER_DIR, s -> {
for (String ext : EXTENSIONS) {
if (s.endsWith(ext)) return true;
@ -149,30 +50,14 @@ public class ShaderSources implements ISelectiveResourceReloadListener {
index = new Index(shaderSources);
}
@Override
public SourceFile findSource(ResourceLocation name) {
SourceFile source = shaderSources.get(name);
private void loadProgramSpecs(IResourceManager manager) {
Collection<ResourceLocation> programSpecs = manager.listResources(PROGRAM_DIR, s -> s.endsWith(".json"));
for (ResourceLocation location : programSpecs) {
try {
IResource file = manager.getResource(location);
String s = StreamUtil.readToString(file.getInputStream());
ResourceLocation specName = ResourceUtil.trim(location, PROGRAM_DIR, ".json");
DataResult<Pair<ProgramSpec, JsonElement>> result = ProgramSpec.CODEC.decode(JsonOps.INSTANCE, GSON.fromJson(s, JsonElement.class));
ProgramSpec spec = result.get()
.orThrow()
.getFirst();
spec.setName(specName);
backend.register(spec);
} catch (Exception e) {
Backend.log.error(e);
}
if (source == null) {
throw new ShaderLoadingException(String.format("shader '%s' does not exist", name));
}
return source;
}
}

View file

@ -294,7 +294,7 @@ public class SourceFile {
Span use = Span.fromMatcher(this, uses);
Span file = Span.fromMatcher(this, uses, 1);
imports.add(new Import(parent, use, file));
imports.add(new Import(Resolver.INSTANCE, use, file));
elisions.add(use); // we have to trim that later
}

View file

@ -1,5 +1,7 @@
package com.jozufozu.flywheel.backend.source.error;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.backend.source.span.Span;
import com.jozufozu.flywheel.util.FlwUtil;
@ -55,7 +57,7 @@ public class ErrorBuilder {
return this;
}
public ErrorBuilder hintIncludeFor(Span span, CharSequence msg) {
public ErrorBuilder hintIncludeFor(@Nullable Span span, CharSequence msg) {
if (span == null) return this;
hint("add " + span.getSourceFile().importStatement() + " " + msg)

View file

@ -7,7 +7,7 @@ import java.util.Optional;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.source.FileResolution;
import com.jozufozu.flywheel.backend.source.ShaderSources;
import com.jozufozu.flywheel.backend.source.Resolver;
import com.jozufozu.flywheel.backend.source.error.ErrorReporter;
import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.backend.source.span.Span;
@ -21,15 +21,13 @@ public class Import extends AbstractShaderElement {
private final Span file;
private final FileResolution resolution;
private final ResourceLocation fileLoc;
public Import(ShaderSources parent, Span self, Span file) {
public Import(Resolver resolver, Span self, Span file) {
super(self);
this.file = file;
fileLoc = toRL(file);
resolution = parent.resolveFile(fileLoc);
resolution.addSpan(file);
resolution = resolver.findShader(toRL(file))
.addSpan(file);
IMPORTS.add(this);
}

View file

@ -3,11 +3,12 @@ package com.jozufozu.flywheel.core;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.source.FileResolution;
import com.jozufozu.flywheel.backend.ResourceUtil;
import com.jozufozu.flywheel.util.ResourceUtil;
import com.jozufozu.flywheel.backend.SpecMetaRegistry;
import com.jozufozu.flywheel.backend.pipeline.IShaderPipeline;
import com.jozufozu.flywheel.backend.pipeline.InstancingTemplate;
import com.jozufozu.flywheel.backend.pipeline.WorldShaderPipeline;
import com.jozufozu.flywheel.backend.source.Resolver;
import com.jozufozu.flywheel.core.crumbling.CrumblingProgram;
import com.jozufozu.flywheel.core.shader.WorldFog;
import com.jozufozu.flywheel.core.shader.WorldProgram;
@ -34,11 +35,11 @@ public class Contexts {
SpecMetaRegistry.register(WorldFog.LINEAR);
SpecMetaRegistry.register(WorldFog.EXP2);
FileResolution crumblingBuiltins = backend.sources.resolveFile(ResourceUtil.subPath(Names.CRUMBLING, ".glsl"));
FileResolution worldBuiltins = backend.sources.resolveFile(ResourceUtil.subPath(Names.WORLD, ".glsl"));
FileResolution crumblingBuiltins = Resolver.INSTANCE.findShader(ResourceUtil.subPath(Names.CRUMBLING, ".glsl"));
FileResolution worldBuiltins = Resolver.INSTANCE.findShader(ResourceUtil.subPath(Names.WORLD, ".glsl"));
IShaderPipeline<CrumblingProgram> crumblingPipeline = new WorldShaderPipeline<>(backend.sources, CrumblingProgram::new, InstancingTemplate.INSTANCE, crumblingBuiltins);
IShaderPipeline<WorldProgram> worldPipeline = new WorldShaderPipeline<>(backend.sources, WorldProgram::new, InstancingTemplate.INSTANCE, worldBuiltins);
IShaderPipeline<CrumblingProgram> crumblingPipeline = new WorldShaderPipeline<>(CrumblingProgram::new, InstancingTemplate.INSTANCE, crumblingBuiltins);
IShaderPipeline<WorldProgram> worldPipeline = new WorldShaderPipeline<>(WorldProgram::new, InstancingTemplate.INSTANCE, worldBuiltins);
CRUMBLING = backend.register(WorldContext.builder(backend, Names.CRUMBLING).build(crumblingPipeline));
WORLD = backend.register(WorldContext.builder(backend, Names.WORLD).build(worldPipeline));

View file

@ -49,7 +49,7 @@ public class WorldContext<P extends WorldProgram> implements IShaderContext<P> {
} catch (Exception e) {
Backend.log.error("Error loading program {}", spec.name);
Backend.log.error("", e);
backend.sources.notifyError();
backend.loader.notifyError();
}
}

View file

@ -3,6 +3,8 @@ package com.jozufozu.flywheel.core.shader.spec;
import java.util.Collections;
import java.util.List;
import com.jozufozu.flywheel.backend.source.FileResolution;
import com.jozufozu.flywheel.backend.source.Resolver;
import com.jozufozu.flywheel.backend.source.SourceFile;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
@ -27,19 +29,19 @@ public class ProgramSpec {
// TODO: Block model style inheritance?
public static final Codec<ProgramSpec> CODEC = RecordCodecBuilder.create(instance -> instance.group(
ResourceLocation.CODEC.fieldOf("source")
.forGetter(ProgramSpec::getSource),
.forGetter(ProgramSpec::getSourceLoc),
ProgramState.CODEC.listOf()
.optionalFieldOf("states", Collections.emptyList())
.forGetter(ProgramSpec::getStates))
.apply(instance, ProgramSpec::new));
public ResourceLocation name;
public final ResourceLocation source;
public final FileResolution source;
public final List<ProgramState> states;
public ProgramSpec(ResourceLocation source, List<ProgramState> states) {
this.source = source;
this.source = Resolver.INSTANCE.findShader(source);
this.states = states;
}
@ -47,7 +49,11 @@ public class ProgramSpec {
this.name = name;
}
public ResourceLocation getSource() {
public ResourceLocation getSourceLoc() {
return source.getFileLoc();
}
public FileResolution getSource() {
return source;
}

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.backend;
package com.jozufozu.flywheel.util;
import net.minecraft.util.ResourceLocation;

View file

@ -1,7 +1,5 @@
#use "flywheel:core/diffuse.glsl"
#use "flywheel:data/modelvertex.glsl"
#use "flywheel:block.frag"
struct Instance {

View file

@ -1,4 +1,3 @@
#use "flywheel:core/matutils.glsl"
#use "flywheel:core/quaternion.glsl"
#use "flywheel:core/diffuse.glsl"