- Redo shader loading
 - Now loads an immutable SourceFile containing some metadata
 - Replace legacy compilation pipeline with improved new one using new api
 - Builtins are defined in one file, now "header"
 - New ErrorReporter/ErrorBuilder methods
 - Fancier shader loading errors
This commit is contained in:
Jozufozu 2021-08-08 22:33:32 -07:00
parent 9ea3344b6f
commit 392cfc9156
53 changed files with 766 additions and 1211 deletions

View file

@ -6,7 +6,7 @@ import java.util.List;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.pipeline.SourceFile; import com.jozufozu.flywheel.backend.pipeline.SourceFile;
import com.jozufozu.flywheel.backend.pipeline.error.ErrorReporter; import com.jozufozu.flywheel.backend.pipeline.error.ErrorBuilder;
import com.jozufozu.flywheel.backend.pipeline.span.Span; import com.jozufozu.flywheel.backend.pipeline.span.Span;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
@ -40,7 +40,17 @@ public class FileResolution {
try { try {
file = sources.source(fileLoc); file = sources.source(fileLoc);
} catch (RuntimeException error) { } catch (RuntimeException error) {
ErrorReporter.generateSpanError(foundSpans.get(0), "could not find source"); ErrorBuilder builder = new ErrorBuilder();
builder.error(String.format("could not find source for file %s", fileLoc));
for (Span span : foundSpans) {
builder.in(span.getSourceFile())
.pointAt(span, 2);
} }
Backend.log.error(builder.build());
}
}
void invalidate() {
} }
} }

View file

@ -0,0 +1,34 @@
package com.jozufozu.flywheel.backend;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.jozufozu.flywheel.backend.pipeline.SourceFile;
import com.jozufozu.flywheel.backend.pipeline.parse.ShaderStruct;
import net.minecraft.util.ResourceLocation;
/**
* Indexes many shader source definitions to allow for error fix suggestions.
*/
public class Index {
private final Multimap<String, ShaderStruct> knownNames = MultimapBuilder.hashKeys().hashSetValues().build();
public Index(Map<ResourceLocation, SourceFile> sources) {
Collection<SourceFile> files = sources.values();
for (SourceFile file : files) {
file.getStructs().forEach(knownNames::put);
}
}
public Collection<ShaderStruct> getStructDefinitionsMatching(CharSequence name) {
return knownNames.get(name.toString());
}
}

View file

@ -1,23 +1,11 @@
package com.jozufozu.flywheel.backend; package com.jozufozu.flywheel.backend;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.gl.GlObject;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram; 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.loading.Program;
import com.jozufozu.flywheel.backend.loading.Shader;
import com.jozufozu.flywheel.backend.pipeline.SourceFile;
import com.jozufozu.flywheel.core.shader.IMultiProgram; import com.jozufozu.flywheel.core.shader.IMultiProgram;
import com.jozufozu.flywheel.core.shader.spec.ProgramSpec;
import com.jozufozu.flywheel.core.shader.spec.ProgramState;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;

View file

@ -3,9 +3,7 @@ package com.jozufozu.flywheel.backend;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -15,16 +13,11 @@ import com.google.common.collect.Lists;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType; import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher; import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.backend.loading.Shader;
import com.jozufozu.flywheel.backend.loading.ShaderLoadingException; import com.jozufozu.flywheel.backend.loading.ShaderLoadingException;
import com.jozufozu.flywheel.backend.pipeline.SourceFile; import com.jozufozu.flywheel.backend.pipeline.SourceFile;
import com.jozufozu.flywheel.backend.pipeline.WorldShaderPipeline;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.core.crumbling.CrumblingRenderer; import com.jozufozu.flywheel.core.crumbling.CrumblingRenderer;
import com.jozufozu.flywheel.core.shader.extension.IProgramExtension;
import com.jozufozu.flywheel.core.shader.spec.ProgramSpec; import com.jozufozu.flywheel.core.shader.spec.ProgramSpec;
import com.jozufozu.flywheel.event.GatherContextEvent; import com.jozufozu.flywheel.event.GatherContextEvent;
import com.jozufozu.flywheel.util.StreamUtil; import com.jozufozu.flywheel.util.StreamUtil;
@ -49,7 +42,6 @@ public class ShaderSources implements ISelectiveResourceReloadListener {
public static final ArrayList<String> EXTENSIONS = Lists.newArrayList(".vert", ".vsh", ".frag", ".fsh", ".glsl"); public static final ArrayList<String> EXTENSIONS = Lists.newArrayList(".vert", ".vsh", ".frag", ".fsh", ".glsl");
private static final Gson GSON = new GsonBuilder().create(); private static final Gson GSON = new GsonBuilder().create();
private final Map<ResourceLocation, String> shaderSource = new HashMap<>();
private final Map<ResourceLocation, SourceFile> shaderSources = new HashMap<>(); private final Map<ResourceLocation, SourceFile> shaderSources = new HashMap<>();
private final Map<ResourceLocation, FileResolution> resolutions = new HashMap<>(); private final Map<ResourceLocation, FileResolution> resolutions = new HashMap<>();
@ -57,6 +49,8 @@ public class ShaderSources implements ISelectiveResourceReloadListener {
private boolean shouldCrash; private boolean shouldCrash;
private final Backend backend; private final Backend backend;
public Index index;
public ShaderSources(Backend backend) { public ShaderSources(Backend backend) {
this.backend = backend; this.backend = backend;
IResourceManager manager = backend.minecraft.getResourceManager(); IResourceManager manager = backend.minecraft.getResourceManager();
@ -79,43 +73,24 @@ public class ShaderSources implements ISelectiveResourceReloadListener {
return resolutions.computeIfAbsent(fileLoc, FileResolution::new); return resolutions.computeIfAbsent(fileLoc, FileResolution::new);
} }
@Deprecated
public Shader source(ResourceLocation name, ShaderType type) {
return new Shader(this, type, name, getShaderSource(name));
}
@Deprecated @Deprecated
public void notifyError() { public void notifyError() {
shouldCrash = true; shouldCrash = true;
} }
@Deprecated
@Nonnull
public String getShaderSource(ResourceLocation loc) {
String source = shaderSource.get(loc);
if (source == null) {
throw new ShaderLoadingException(String.format("shader '%s' does not exist", loc));
}
return source;
}
@Override @Override
public void onResourceManagerReload(IResourceManager manager, Predicate<IResourceType> predicate) { public void onResourceManagerReload(IResourceManager manager, Predicate<IResourceType> predicate) {
if (predicate.test(VanillaResourceType.SHADERS)) { if (predicate.test(VanillaResourceType.SHADERS)) {
backend.refresh(); backend.refresh();
if (backend.gl20()) { if (backend.gl20()) {
shaderSource.clear();
shouldCrash = false; shouldCrash = false;
backend.clearContexts(); backend.clearContexts();
ModLoader.get() ModLoader.get()
.postEvent(new GatherContextEvent(backend)); .postEvent(new GatherContextEvent(backend));
resolutions.clear(); resolutions.values().forEach(FileResolution::invalidate);
loadProgramSpecs(manager); loadProgramSpecs(manager);
loadShaderSources(manager); loadShaderSources(manager);
@ -124,12 +99,6 @@ public class ShaderSources implements ISelectiveResourceReloadListener {
resolution.resolve(this); resolution.resolve(this);
} }
WorldShaderPipeline<WorldProgram> pl = new WorldShaderPipeline<>(this, WorldProgram::new);
// ResourceLocation name = new ResourceLocation(Flywheel.ID, "model.glsl");
// SourceFile source = source(name);
// pl.compile(name, source, Collections.emptyList());
for (IShaderContext<?> context : backend.allContexts()) { for (IShaderContext<?> context : backend.allContexts()) {
context.load(); context.load();
} }
@ -140,9 +109,6 @@ public class ShaderSources implements ISelectiveResourceReloadListener {
Backend.log.info("Loaded all shader programs."); Backend.log.info("Loaded all shader programs.");
// no need to hog all that memory
shaderSource.clear();
ClientWorld world = Minecraft.getInstance().level; ClientWorld world = Minecraft.getInstance().level;
if (Backend.isFlywheelWorld(world)) { if (Backend.isFlywheelWorld(world)) {
// TODO: looks like it might be good to have another event here // TODO: looks like it might be good to have another event here
@ -169,12 +135,13 @@ public class ShaderSources implements ISelectiveResourceReloadListener {
ResourceLocation name = ResourceUtil.removePrefixUnchecked(location, SHADER_DIR); ResourceLocation name = ResourceUtil.removePrefixUnchecked(location, SHADER_DIR);
shaderSource.put(name, source);
shaderSources.put(name, new SourceFile(this, name, source)); shaderSources.put(name, new SourceFile(this, name, source));
} catch (IOException e) { } catch (IOException e) {
} }
} }
index = new Index(shaderSources);
} }

View file

@ -8,7 +8,6 @@ import static org.lwjgl.opengl.GL20.glUseProgram;
import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GlObject; import com.jozufozu.flywheel.backend.gl.GlObject;
import com.jozufozu.flywheel.backend.loading.Program;
import com.jozufozu.flywheel.util.RenderUtil; import com.jozufozu.flywheel.util.RenderUtil;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;

View file

@ -5,7 +5,6 @@ import org.lwjgl.opengl.GL20;
import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GlObject; import com.jozufozu.flywheel.backend.gl.GlObject;
import com.jozufozu.flywheel.backend.gl.versioned.GlCompat; import com.jozufozu.flywheel.backend.gl.versioned.GlCompat;
import com.jozufozu.flywheel.backend.loading.Shader;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;

View file

@ -100,7 +100,15 @@ public class InstancedRenderRegistry {
return skipRender.getBoolean(o); return skipRender.getBoolean(o);
} }
public class TileConfig<T extends TileEntity> { public interface Config<CONFIG extends Config<CONFIG, FACTORY>, FACTORY> {
CONFIG factory(FACTORY rendererFactory);
CONFIG setSkipRender(boolean skipRender);
}
public class TileConfig<T extends TileEntity> implements Config<TileConfig<T>, ITileInstanceFactory<? super T>> {
private final TileEntityType<T> type; private final TileEntityType<T> type;
@ -112,14 +120,14 @@ public class InstancedRenderRegistry {
tiles.put(type, rendererFactory); tiles.put(type, rendererFactory);
return this; return this;
} }
public TileConfig<T> setSkipRender(boolean skipRender) { public TileConfig<T> setSkipRender(boolean skipRender) {
InstancedRenderRegistry.this.skipRender.put(type, skipRender); InstancedRenderRegistry.this.skipRender.put(type, skipRender);
return this; return this;
} }
}
public class EntityConfig<T extends Entity> { }
public class EntityConfig<T extends Entity> implements Config<EntityConfig<T>, IEntityInstanceFactory<? super T>> {
private final EntityType<T> type; private final EntityType<T> type;
@ -131,12 +139,12 @@ public class InstancedRenderRegistry {
entities.put(type, rendererFactory); entities.put(type, rendererFactory);
return this; return this;
} }
public EntityConfig<T> setSkipRender(boolean skipRender) { public EntityConfig<T> setSkipRender(boolean skipRender) {
InstancedRenderRegistry.this.skipRender.put(type, skipRender); InstancedRenderRegistry.this.skipRender.put(type, skipRender);
return this; return this;
} }
} }
} }

View file

@ -1,7 +0,0 @@
package com.jozufozu.flywheel.backend.loading;
@FunctionalInterface
public interface IProcessingStage {
void process(Shader shader);
}

View file

@ -1,41 +0,0 @@
package com.jozufozu.flywheel.backend.loading;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.ShaderSources;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import net.minecraft.util.ResourceLocation;
public class InstancedArraysTemplate extends ProgramTemplate {
public static final String vertexData = "VertexData";
public static final String instanceData = "InstanceData";
public static final String fragment = "Fragment";
public static final String vertexPrefix = "a_v_";
public static final String instancePrefix = "a_i_";
public static final String[] requiredVert = new String[]{instanceData, vertexData, fragment};
public static final String[] requiredFrag = {fragment};
public static final ResourceLocation vert = new ResourceLocation(Flywheel.ID, "template/instanced/instanced.vert");
public static final ResourceLocation frag = new ResourceLocation(Flywheel.ID, "template/instanced/instanced.frag");
public InstancedArraysTemplate(ShaderSources loader) {
super(loader);
templates.put(ShaderType.VERTEX, new ShaderTemplate(requiredVert, loader.getShaderSource(vert)));
templates.put(ShaderType.FRAGMENT, new ShaderTemplate(requiredFrag, loader.getShaderSource(frag)));
}
@Override
public void attachAttributes(Program builder) {
Shader shader = builder.attached.get(ShaderType.VERTEX);
shader.getTag(vertexData)
.addPrefixedAttributes(builder, vertexPrefix);
shader.getTag(instanceData)
.addPrefixedAttributes(builder, instancePrefix);
}
}

View file

@ -1,19 +0,0 @@
package com.jozufozu.flywheel.backend.loading;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.jozufozu.flywheel.backend.gl.GlNumericType;
public class LayoutTag {
public static final Pattern pattern = Pattern.compile("Layout\\((\\w+)(?:\\s*,\\s*(\\w*))?\\)");
final GlNumericType type;
final boolean normalized;
public LayoutTag(Matcher matcher) {
type = GlNumericType.byName(matcher.group(1));
normalized = Boolean.parseBoolean(matcher.group(2));
}
}

View file

@ -1,36 +0,0 @@
package com.jozufozu.flywheel.backend.loading;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.ShaderSources;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import net.minecraft.util.ResourceLocation;
public class ModelTemplate extends ProgramTemplate {
public static final String vertexData = "VertexData";
public static final String fragment = "Fragment";
public static final String vertexPrefix = "a_v_";
public static final String[] requiredVert = new String[]{vertexData, fragment};
public static final String[] requiredFrag = {fragment};
public static final ResourceLocation vert = new ResourceLocation(Flywheel.ID, "template/model/model.vert");
public static final ResourceLocation frag = new ResourceLocation(Flywheel.ID, "template/model/model.frag");
public ModelTemplate(ShaderSources loader) {
super(loader);
templates.put(ShaderType.VERTEX, new ShaderTemplate(requiredVert, loader.getShaderSource(vert)));
templates.put(ShaderType.FRAGMENT, new ShaderTemplate(requiredFrag, loader.getShaderSource(frag)));
}
@Override
public void attachAttributes(Program builder) {
Shader shader = builder.attached.get(ShaderType.VERTEX);
shader.getTag(vertexData)
.addPrefixedAttributes(builder, vertexPrefix);
}
}

View file

@ -1,30 +0,0 @@
package com.jozufozu.flywheel.backend.loading;
import java.util.EnumMap;
import java.util.Map;
import com.jozufozu.flywheel.backend.ShaderSources;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
public abstract class ProgramTemplate implements IProcessingStage {
protected final ShaderSources loader;
protected Map<ShaderType, ShaderTemplate> templates = new EnumMap<>(ShaderType.class);
public ProgramTemplate(ShaderSources loader) {
this.loader = loader;
}
@Override
public void process(Shader shader) {
ShaderTemplate template = templates.get(shader.type);
if (template == null) return;
shader.setSource(template.apply(shader));
}
public void attachAttributes(Program builder) {
}
}

View file

@ -23,40 +23,32 @@ import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
public class Program { public class ProtoProgram {
public final int program; public final int program;
public ResourceLocation name; public ResourceLocation name;
private int attributeIndex; private int attributeIndex;
public final Map<ShaderType, Shader> attached;
private final IntList shaders; private final IntList shaders;
public Program() { public ProtoProgram() {
this.program = glCreateProgram(); this.program = glCreateProgram();
attached = new EnumMap<>(ShaderType.class);
shaders = new IntArrayList(2); shaders = new IntArrayList(2);
} }
public Program attachShader(Shader shader, GlShader glShader) { public void attachShader(GlShader glShader) {
glAttachShader(this.program, glShader.handle()); glAttachShader(this.program, glShader.handle());
attached.put(shader.type, shader);
return this;
} }
public Program addAttribute(String name, int attributeCount) { public void addAttribute(String name, int attributeCount) {
glBindAttribLocation(this.program, attributeIndex, name); glBindAttribLocation(this.program, attributeIndex, name);
attributeIndex += attributeCount; attributeIndex += attributeCount;
return this;
} }
/** /**
* Links the attached shaders to this program. * Links the attached shaders to this program.
*/ */
public Program link(ResourceLocation name) { public ProtoProgram link(ResourceLocation name) {
this.name = name; this.name = name;
glLinkProgram(this.program); glLinkProgram(this.program);
@ -75,7 +67,7 @@ public class Program {
return this; return this;
} }
public Program deleteLinkedShaders() { public ProtoProgram deleteLinkedShaders() {
shaders.forEach((IntConsumer) GL20::glDeleteShader); shaders.forEach((IntConsumer) GL20::glDeleteShader);
return this; return this;
} }

View file

@ -1,160 +0,0 @@
package com.jozufozu.flywheel.backend.loading;
import java.io.BufferedReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.jozufozu.flywheel.backend.ShaderSources;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import net.minecraft.util.ResourceLocation;
public class Shader {
// #flwinclude <"valid_namespace:valid/path_to_file.glsl">
private static final Pattern includePattern = Pattern.compile("#flwinclude <\"([\\w\\d_]+:[\\w\\d_./]+)\">");
public static final Pattern versionDetector = Pattern.compile("#version[^\\n]*");
private static final Pattern decorator = Pattern.compile("#\\[([\\w_]*)]");
public final ResourceLocation name;
public ShaderType type;
private String source;
private final ShaderSources loader;
private boolean parsed = false;
final List<TaggedStruct> structs = new ArrayList<>(3);
final Map<String, TaggedStruct> tag2Struct = new HashMap<>();
final Map<String, TaggedStruct> name2Struct = new HashMap<>();
public Shader(ShaderSources loader, ShaderType type, ResourceLocation name, String source) {
this.loader = loader;
this.type = type;
this.name = name;
this.source = source;
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
public TaggedStruct getTag(String tag) {
checkIfParsed();
return tag2Struct.get(tag);
}
public TaggedStruct getStruct(String name) {
checkIfParsed();
return name2Struct.get(name);
}
private void checkIfParsed() {
if (!parsed) {
throw new IllegalStateException("tagged structs must be explicitly parsed before use");
}
}
public void defineAll(Collection<String> defines) {
Matcher matcher = versionDetector.matcher(source);
if (matcher.find()) {
StringBuffer sourceWithDefines = new StringBuffer();
String lines = defines.stream()
.map(it -> "#define " + it)
.collect(Collectors.joining("\n"));
matcher.appendReplacement(sourceWithDefines, matcher.group() + '\n' + lines);
matcher.appendTail(sourceWithDefines);
source = sourceWithDefines.toString();
}
}
public void parseStructs() {
Matcher structMatcher = TaggedStruct.taggedStruct.matcher(source);
StringBuffer strippedSrc = new StringBuffer();
while (structMatcher.find()) {
TaggedStruct struct = new TaggedStruct(structMatcher);
structs.add(struct);
String replacement = decorator.matcher(struct.source)
.replaceFirst("");
structMatcher.appendReplacement(strippedSrc, replacement);
tag2Struct.put(struct.tag, struct);
name2Struct.put(struct.name, struct);
}
structMatcher.appendTail(strippedSrc);
this.source = strippedSrc.toString();
parsed = true;
}
public void processIncludes() {
HashSet<ResourceLocation> seen = new HashSet<>();
seen.add(name);
source = includeRecursive(source, seen).collect(Collectors.joining("\n"));
}
private Stream<String> includeRecursive(String source, Set<ResourceLocation> seen) {
return lines(source).flatMap(line -> {
Matcher matcher = includePattern.matcher(line);
if (matcher.find()) {
String includeName = matcher.group(1);
ResourceLocation include = new ResourceLocation(includeName);
if (seen.add(include)) {
try {
return includeRecursive(loader.getShaderSource(include), seen);
} catch (ShaderLoadingException e) {
throw new ShaderLoadingException("could not resolve import: " + e.getMessage());
}
}
}
return Stream.of(line);
});
}
public String printSource() {
StringBuilder builder = new StringBuilder();
builder.append("Source for shader '")
.append(name)
.append("':\n");
int i = 1;
for (String s : source.split("\n")) {
builder.append(String.format("%1$4s: ", i++))
.append(s)
.append('\n');
}
return builder.toString();
}
public static Stream<String> lines(String s) {
return new BufferedReader(new StringReader(s)).lines();
}
}

View file

@ -1,120 +0,0 @@
package com.jozufozu.flywheel.backend.loading;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ShaderTemplate {
private static final String delimiter = "#flwbeginbody";
private static final Pattern headerFinder = Pattern.compile(delimiter);
private static final Pattern prefixer = Pattern.compile("#FLWPrefixFields\\((\\w+),\\s*(\\w+),\\s*([\\w\\d]+)\\)");
private static final Pattern assigner = Pattern.compile("#FLWAssignFields\\(([\\w\\d_]+),\\s*([\\w\\d_.]+),\\s*([\\w\\d_.]+)\\)");
final String[] requiredStructs;
final String header;
final String body;
public ShaderTemplate(String[] requiredStructs, String templateSrc) {
this.requiredStructs = requiredStructs;
Matcher matcher = headerFinder.matcher(templateSrc);
if (!matcher.find()) {
throw new RuntimeException("Shader template must have a header and footer delimited by '" + delimiter + "'");
}
this.header = templateSrc.substring(0, matcher.start());
this.body = templateSrc.substring(matcher.end());
}
public String apply(Shader shader) {
shader.parseStructs();
return header + shader.getSource() + processBody(shader);
}
public String processBody(Shader shader) {
String s = body;
List<String> missing = new ArrayList<>();
for (String name : requiredStructs) {
TaggedStruct struct = shader.getTag(name);
if (struct != null) {
s = s.replace(name, struct.name);
} else {
missing.add(name);
}
}
if (!missing.isEmpty()) {
String err = shader.name + " is missing: " + String.join(", ", missing);
throw new RuntimeException(err);
}
s = fillPrefixes(shader, s);
s = fillAssigns(shader, s);
return s;
}
private String fillPrefixes(Shader shader, String s) {
Matcher prefixMatches = prefixer.matcher(s);
StringBuffer out = new StringBuffer();
while (prefixMatches.find()) {
String structName = prefixMatches.group(1);
String modifier = prefixMatches.group(2);
String prefix = prefixMatches.group(3);
TaggedStruct struct = shader.getStruct(structName);
StringBuilder builder = new StringBuilder();
for (TaggedField field : struct.fields) {
builder.append(modifier);
builder.append(' ');
builder.append(field.getType());
builder.append(' ');
builder.append(prefix);
builder.append(field.getName());
builder.append(";\n");
}
prefixMatches.appendReplacement(out, builder.toString());
}
prefixMatches.appendTail(out);
return out.toString();
}
private String fillAssigns(Shader shader, String s) {
Matcher assignMatches = assigner.matcher(s);
StringBuffer out = new StringBuffer();
while (assignMatches.find()) {
String structName = assignMatches.group(1);
String lhs = assignMatches.group(2);
String rhs = assignMatches.group(3);
TaggedStruct struct = shader.getStruct(structName);
StringBuilder builder = new StringBuilder();
for (TaggedField field : struct.fields) {
builder.append(lhs);
builder.append(field.getName());
builder.append(" = ");
builder.append(rhs);
builder.append(field.getName());
builder.append(";\n");
}
assignMatches.appendReplacement(out, builder.toString());
}
assignMatches.appendTail(out);
return out.toString();
}
}

View file

@ -1,38 +0,0 @@
package com.jozufozu.flywheel.backend.loading;
import java.util.LinkedList;
public class ShaderTransformer {
private final LinkedList<IProcessingStage> stages = new LinkedList<>();
public ShaderTransformer() {
}
public ShaderTransformer pushStage(IProcessingStage stage) {
if (stage != null) {
stages.addLast(stage);
}
return this;
}
public ShaderTransformer popStage() {
stages.removeLast();
return this;
}
public ShaderTransformer prependStage(IProcessingStage stage) {
if (stage != null) {
stages.addFirst(stage);
}
return this;
}
public void transformSource(Shader shader) {
for (IProcessingStage stage : this.stages) {
stage.process(shader);
}
}
}

View file

@ -1,45 +0,0 @@
package com.jozufozu.flywheel.backend.loading;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TaggedField {
public static final Pattern fieldPattern = Pattern.compile("(?:#\\[([^\\n]*)]\\s*)?(\\S+)\\s*(\\S+);");
public String annotation;
public String name;
public String type;
public LayoutTag layout;
public TaggedField(Matcher fieldMatcher) {
annotation = fieldMatcher.group(1);
type = fieldMatcher.group(2);
name = fieldMatcher.group(3);
if (annotation != null) {
Matcher matcher = LayoutTag.pattern.matcher(annotation);
if (matcher.find()) {
layout = new LayoutTag(matcher);
}
}
}
public String getAnnotation() {
return annotation;
}
public String getName() {
return name;
}
public String getType() {
return type;
}
@Override
public String toString() {
return "TaggedField{" + "name='" + name + '\'' + ", type='" + type + '\'' + '}';
}
}

View file

@ -1,49 +0,0 @@
package com.jozufozu.flywheel.backend.loading;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TaggedStruct {
// https://regexr.com/5t207
public static final Pattern taggedStruct = Pattern.compile("#\\[(\\w*)]\\s*struct\\s+([\\w\\d]*)\\s*\\{([\\w\\d \\t#\\[\\](),;\\n]*)}\\s*;");
public int srcStart, srcEnd;
public String source;
public String tag;
public String name;
public String body;
List<TaggedField> fields = new ArrayList<>(4);
Map<String, String> fields2Types = new HashMap<>();
public TaggedStruct(Matcher foundMatcher) {
this.source = foundMatcher.group();
srcStart = foundMatcher.start();
srcEnd = foundMatcher.end();
tag = foundMatcher.group(1);
name = foundMatcher.group(2);
body = foundMatcher.group(3);
Matcher fielder = TaggedField.fieldPattern.matcher(body);
while (fielder.find()) {
fields.add(new TaggedField(fielder));
fields2Types.put(fielder.group(2), fielder.group(1));
}
}
public void addPrefixedAttributes(Program builder, String prefix) {
for (TaggedField field : fields) {
int attributeCount = TypeHelper.getAttributeCount(field.type);
builder.addAttribute(prefix + field.name, attributeCount);
}
}
}

View file

@ -0,0 +1,41 @@
package com.jozufozu.flywheel.backend.pipeline;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.backend.loading.ProtoProgram;
import com.jozufozu.flywheel.backend.pipeline.parse.ShaderStruct;
import com.jozufozu.flywheel.backend.pipeline.parse.StructField;
public interface ITemplate {
void generateTemplateSource(StringBuilder builder, ShaderType type, SourceFile file);
void attachAttributes(ProtoProgram program, SourceFile file);
static void prefixFields(StringBuilder builder, ShaderStruct struct, String qualifier, String prefix) {
ImmutableList<StructField> fields = struct.getFields();
for (StructField field : fields) {
builder.append(qualifier)
.append(' ')
.append(field.type)
.append(' ')
.append(prefix)
.append(field.name)
.append(";\n");
}
}
static void assignFields(StringBuilder builder, ShaderStruct struct, String prefix1, String prefix2) {
ImmutableList<StructField> fields = struct.getFields();
for (StructField field : fields) {
builder.append(prefix1)
.append(field.name)
.append(" = ")
.append(prefix2)
.append(field.name)
.append(";\n");
}
}
}

View file

@ -0,0 +1,86 @@
package com.jozufozu.flywheel.backend.pipeline;
import java.util.Optional;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.pipeline.error.ErrorReporter;
import com.jozufozu.flywheel.backend.pipeline.parse.ShaderFunction;
import com.jozufozu.flywheel.backend.pipeline.parse.ShaderStruct;
import com.jozufozu.flywheel.backend.pipeline.parse.Variable;
import com.jozufozu.flywheel.backend.pipeline.span.Span;
public class InstanceTemplateData {
public final SourceFile file;
public final ShaderFunction vertexMain;
public final ShaderFunction fragmentMain;
public final Span interpolantName;
public final Span vertexName;
public final Span instanceName;
public final ShaderStruct interpolant;
public final ShaderStruct vertex;
public final ShaderStruct instance;
public InstanceTemplateData(SourceFile file) {
this.file = file;
Optional<ShaderFunction> vertexFunc = file.findFunction("vertex");
Optional<ShaderFunction> fragmentFunc = file.findFunction("fragment");
if (!fragmentFunc.isPresent()) {
ErrorReporter.generateFileError(file, "could not find \"fragment\" function");
}
if (!vertexFunc.isPresent()) {
ErrorReporter.generateFileError(file, "could not find \"vertex\" function");
}
if (!fragmentFunc.isPresent() || !vertexFunc.isPresent()) {
throw new RuntimeException();
}
fragmentMain = fragmentFunc.get();
vertexMain = vertexFunc.get();
ImmutableList<Variable> parameters = fragmentMain.getParameters();
ImmutableList<Variable> vertexParams = vertexMain.getParameters();
if (parameters.size() != 1) {
ErrorReporter.generateSpanError(fragmentMain.getArgs(), "instancing requires fragment function to have 1 argument");
}
if (vertexParams.size() != 2) {
ErrorReporter.generateSpanError(vertexMain.getArgs(), "instancing requires vertex function to have 2 arguments");
throw new RuntimeException();
}
interpolantName = vertexMain.getType();
vertexName = vertexParams.get(0)
.typeName();
instanceName = vertexParams.get(1)
.typeName();
Optional<ShaderStruct> maybeInterpolant = file.findStruct(interpolantName);
Optional<ShaderStruct> maybeVertex = file.findStruct(vertexName);
Optional<ShaderStruct> maybeInstance = file.findStruct(instanceName);
if (!maybeVertex.isPresent()) {
ErrorReporter.generateMissingStruct(file, vertexName);
}
if (!maybeInterpolant.isPresent()) {
ErrorReporter.generateMissingStruct(file, interpolantName);
}
if (!maybeInstance.isPresent()) {
ErrorReporter.generateMissingStruct(file, instanceName);
}
if (!maybeVertex.isPresent() || !maybeInterpolant.isPresent() || !maybeInstance.isPresent()) {
throw new RuntimeException();
}
interpolant = maybeInterpolant.get();
vertex = maybeVertex.get();
instance = maybeInstance.get();
}
}

View file

@ -0,0 +1,76 @@
package com.jozufozu.flywheel.backend.pipeline;
import java.util.HashMap;
import java.util.Map;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.backend.loading.ProtoProgram;
public class InstancingTemplate implements ITemplate {
public static final InstancingTemplate INSTANCE = new InstancingTemplate();
private final Map<SourceFile, InstanceTemplateData> datas = new HashMap<>();
@Override
public void generateTemplateSource(StringBuilder builder, ShaderType type, SourceFile file) {
if (type == ShaderType.VERTEX) {
vertexFooter(builder, file);
} else if (type == ShaderType.FRAGMENT) {
fragmentFooter(builder, file);
}
}
@Override
public void attachAttributes(ProtoProgram program, SourceFile file) {
InstanceTemplateData data = getData(file);
data.vertex.addPrefixedAttributes(program, "a_v_");
data.instance.addPrefixedAttributes(program, "a_i_");
}
public InstanceTemplateData getData(SourceFile file) {
return datas.computeIfAbsent(file, InstanceTemplateData::new);
}
public void vertexFooter(StringBuilder template, SourceFile file) {
InstanceTemplateData data = getData(file);
ITemplate.prefixFields(template, data.vertex, "attribute", "a_v_");
ITemplate.prefixFields(template, data.instance, "attribute", "a_i_");
ITemplate.prefixFields(template, data.interpolant, "varying", "v2f_");
template.append("void main() {\n");
template.append(data.vertexName)
.append(" v;\n");
ITemplate.assignFields(template, data.vertex, "v.", "a_v_");
template.append(data.instanceName)
.append(" i;\n");
ITemplate.assignFields(template, data.instance, "i.", "a_i_");
template.append(data.interpolantName)
.append(" o = ")
.append(data.vertexMain.call("v", "i"))
.append(";\n");
ITemplate.assignFields(template, data.interpolant, "v2f_", "o.");
template.append('}');
}
public void fragmentFooter(StringBuilder template, SourceFile file) {
InstanceTemplateData data = getData(file);
ITemplate.prefixFields(template, data.interpolant, "varying", "v2f_");
template.append("void main() {\n");
template.append(data.interpolantName)
.append(" o;\n");
ITemplate.assignFields(template, data.interpolant, "o.", "v2f_");
template.append(data.fragmentMain.call("o"))
.append(";\n");
template.append('}');
}
}

View file

@ -1,137 +0,0 @@
package com.jozufozu.flywheel.backend.pipeline;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.ShaderSources;
import com.jozufozu.flywheel.backend.gl.GlObject;
import com.jozufozu.flywheel.backend.gl.shader.GlShader;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.backend.loading.Program;
import com.jozufozu.flywheel.backend.loading.ProgramTemplate;
import com.jozufozu.flywheel.backend.loading.Shader;
import com.jozufozu.flywheel.backend.loading.ShaderLoadingException;
import com.jozufozu.flywheel.backend.loading.ShaderTransformer;
import com.jozufozu.flywheel.core.shader.ExtensibleGlProgram;
import com.jozufozu.flywheel.core.shader.GameStateProgram;
import com.jozufozu.flywheel.core.shader.IMultiProgram;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.core.shader.spec.ProgramSpec;
import com.jozufozu.flywheel.core.shader.spec.ProgramState;
public class LegacyPipeline<P extends WorldProgram> implements IShaderPipeline<P> {
private static final String declaration = "#flwbuiltins";
private static final Pattern builtinPattern = Pattern.compile(declaration);
private final ShaderSources sources;
protected ShaderTransformer transformer;
private final ProgramTemplate template;
private final ExtensibleGlProgram.Factory<P> factory;
private final Map<ShaderType, String> builtinSources;
public LegacyPipeline(ShaderSources sources, ProgramTemplate template, ExtensibleGlProgram.Factory<P> factory, Map<ShaderType, String> builtinSources) {
this.sources = sources;
this.template = template;
this.factory = factory;
transformer = new ShaderTransformer().pushStage(this::injectBuiltins)
.pushStage(Shader::processIncludes)
.pushStage(template)
.pushStage(Shader::processIncludes);
this.builtinSources = builtinSources;
}
@Override
public IMultiProgram<P> compile(ProgramSpec spec) {
GameStateProgram.Builder<P> builder = GameStateProgram.builder(compile(spec, null));
for (ProgramState state : spec.states) {
builder.withVariant(state.getContext(), compile(spec, state));
}
return builder.build();
}
/**
* Ingests the given shaders, compiling them and linking them together after applying the transformer to the source.
*
* @param shaders What are the different shader stages that should be linked together?
* @return A program with all provided shaders attached
*/
protected static Program buildProgram(Shader... shaders) {
List<GlShader> compiled = new ArrayList<>(shaders.length);
try {
Program builder = new Program();
for (Shader shader : shaders) {
GlShader sh = new GlShader(shader.name, shader.type, shader.getSource());
compiled.add(sh);
builder.attachShader(shader, sh);
}
return builder;
} finally {
compiled.forEach(GlObject::delete);
}
}
public Program loadAndLink(ProgramSpec spec, @Nullable ProgramState state) {
Shader vertexFile = sources.source(spec.vert, ShaderType.VERTEX);
Shader fragmentFile = sources.source(spec.frag, ShaderType.FRAGMENT);
transformer.transformSource(vertexFile);
transformer.transformSource(fragmentFile);
if (state != null) {
vertexFile.defineAll(state.getDefines());
fragmentFile.defineAll(state.getDefines());
}
Program program = buildProgram(vertexFile, fragmentFile);
template.attachAttributes(program);
program.link(spec.name).deleteLinkedShaders();
String descriptor = program.program + ": " + spec.name;
if (state != null)
descriptor += "#" + state;
Backend.log.debug(descriptor);
return program;
}
private P compile(ProgramSpec spec, @Nullable ProgramState state) {
if (state != null) {
Program program = loadAndLink(spec, state);
return factory.create(program.name, program.program, state.getExtensions());
} else {
Program program = loadAndLink(spec, null);
return factory.create(program.name, program.program, null);
}
}
/**
* Replace #flwbuiltins with whatever expansion this context provides for the given shader.
*/
public void injectBuiltins(Shader shader) {
Matcher matcher = builtinPattern.matcher(shader.getSource());
if (matcher.find()) shader.setSource(matcher.replaceFirst(builtinSources.get(shader.type)));
else
throw new ShaderLoadingException(String.format("%s is missing %s, cannot use in World Context", shader.type.name, declaration));
}
}

View file

@ -0,0 +1,74 @@
package com.jozufozu.flywheel.backend.pipeline;
import java.util.Optional;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.pipeline.error.ErrorReporter;
import com.jozufozu.flywheel.backend.pipeline.parse.ShaderFunction;
import com.jozufozu.flywheel.backend.pipeline.parse.ShaderStruct;
import com.jozufozu.flywheel.backend.pipeline.parse.Variable;
import com.jozufozu.flywheel.backend.pipeline.span.Span;
public class OneShotData {
public final SourceFile file;
public final ShaderFunction vertexMain;
public final Span interpolantName;
public final Span vertexName;
public final ShaderStruct interpolant;
public final ShaderStruct vertex;
public final ShaderFunction fragmentMain;
public OneShotData(SourceFile file) {
this.file = file;
Optional<ShaderFunction> maybeVertexMain = file.findFunction("vertex");
Optional<ShaderFunction> maybeFragmentMain = file.findFunction("fragment");
if (!maybeVertexMain.isPresent()) {
ErrorReporter.generateFileError(file, "could not find \"vertex\" function");
throw new RuntimeException();
}
if (!maybeFragmentMain.isPresent()) {
ErrorReporter.generateFileError(file, "could not find \"fragment\" function");
throw new RuntimeException();
}
vertexMain = maybeVertexMain.get();
fragmentMain = maybeFragmentMain.get();
ImmutableList<Variable> parameters = fragmentMain.getParameters();
ImmutableList<Variable> vertexParameters = vertexMain.getParameters();
if (vertexParameters.size() != 1) {
ErrorReporter.generateSpanError(vertexMain.getArgs(), "a basic model requires vertex function to have one argument");
throw new RuntimeException();
}
if (parameters.size() != 1) {
ErrorReporter.generateSpanError(fragmentMain.getArgs(), "instancing requires fragment function to have 1 argument");
throw new RuntimeException();
}
interpolantName = vertexMain.getType();
vertexName = vertexParameters.get(0)
.typeName();
Optional<ShaderStruct> maybeInterpolant = file.findStruct(interpolantName);
Optional<ShaderStruct> maybeVertex = file.findStruct(vertexName);
if (!maybeVertex.isPresent())
ErrorReporter.generateMissingStruct(file, vertexName);
if (!maybeInterpolant.isPresent())
ErrorReporter.generateMissingStruct(file, interpolantName);
if (!maybeVertex.isPresent() || !maybeInterpolant.isPresent()) {
throw new RuntimeException();
}
interpolant = maybeInterpolant.get();
vertex = maybeVertex.get();
}
}

View file

@ -0,0 +1,72 @@
package com.jozufozu.flywheel.backend.pipeline;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.backend.loading.ProtoProgram;
public class OneShotTemplate implements ITemplate {
public static final OneShotTemplate INSTANCE = new OneShotTemplate();
private final Map<SourceFile, OneShotData> datas = new HashMap<>();
@Override
public void generateTemplateSource(StringBuilder builder, ShaderType type, SourceFile file) {
if (type == ShaderType.VERTEX) {
vertexFooter(builder, file);
} else if (type == ShaderType.FRAGMENT) {
fragmentFooter(builder, file);
}
}
@Override
public void attachAttributes(ProtoProgram program, SourceFile file) {
OneShotData data = getData(file);
data.vertex.addPrefixedAttributes(program, "a_v_");
}
public OneShotData getData(SourceFile file) {
return datas.computeIfAbsent(file, OneShotData::new);
}
public void vertexFooter(StringBuilder template, SourceFile file) {
OneShotData data = getData(file);
ITemplate.prefixFields(template, data.vertex, "attribute", "a_v_");
ITemplate.prefixFields(template, data.interpolant, "varying", "v2f_");
template.append("void main() {\n");
template.append(data.vertexName)
.append(" v;\n");
ITemplate.assignFields(template, data.vertex, "v.", "a_v_");
template.append(data.interpolantName)
.append(" o = ")
.append(data.vertexMain.call("v"))
.append(";\n");
ITemplate.assignFields(template, data.interpolant, "v2f_", "o.");
template.append('}');
}
public void fragmentFooter(StringBuilder template, SourceFile file) {
OneShotData data = getData(file);
ITemplate.prefixFields(template, data.interpolant, "varying", "v2f_");
template.append("void main() {\n");
template.append(data.interpolant.name)
.append(" o;\n");
ITemplate.assignFields(template, data.interpolant, "o.", "v2f_");
template.append(data.fragmentMain.call("o"))
.append(";\n");
template.append('}');
}
}

View file

@ -1,8 +1,8 @@
package com.jozufozu.flywheel.backend.pipeline; package com.jozufozu.flywheel.backend.pipeline;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.jozufozu.flywheel.backend.FileResolution;
import com.jozufozu.flywheel.backend.gl.shader.GlShader; import com.jozufozu.flywheel.backend.gl.shader.GlShader;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType; import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
@ -11,18 +11,19 @@ import net.minecraft.util.ResourceLocation;
public class ShaderBuilder { public class ShaderBuilder {
public final ResourceLocation name; public final ResourceLocation name;
public final Template template; public final ITemplate template;
public final FileResolution header;
private SourceFile mainFile; public SourceFile mainFile;
private GLSLVersion version; private GLSLVersion version;
private StringBuilder source; private StringBuilder source;
private StringBuilder defines; private StringBuilder defines;
private CharSequence footer;
public ShaderBuilder(ResourceLocation name, Template template) { public ShaderBuilder(ResourceLocation name, ITemplate template, FileResolution header) {
this.name = name; this.name = name;
this.template = template; this.template = template;
this.header = header;
} }
public ShaderBuilder setVersion(GLSLVersion version) { public ShaderBuilder setVersion(GLSLVersion version) {
@ -41,21 +42,13 @@ public class ShaderBuilder {
return this; return this;
} }
public ShaderBuilder setFooter(CharSequence footer) {
this.footer = footer;
return this;
}
public ShaderBuilder setMainSource(SourceFile file) { public ShaderBuilder setMainSource(SourceFile file) {
if (mainFile == file) return this; if (mainFile == file) return this;
mainFile = file; mainFile = file;
source = new StringBuilder(); source = new StringBuilder();
for (SourceFile includeFile : Includer.recurseIncludes(file)) { file.generateFinalSource(source);
source.append(includeFile.getElidedSource());
}
source.append(file.getElidedSource());
return this; return this;
} }
@ -70,9 +63,13 @@ public class ShaderBuilder {
.append("#define ") .append("#define ")
.append(type.define) .append(type.define)
.append('\n') .append('\n')
.append(defines != null ? defines : "") .append(defines != null ? defines : "");
.append(source) SourceFile file = header.getFile();
.append(template.footer(type, mainFile)); if (file != null) {
file.generateFinalSource(finalSource);
}
mainFile.generateFinalSource(finalSource);
template.generateTemplateSource(finalSource, type, mainFile);
return new GlShader(name, type, finalSource); return new GlShader(name, type, finalSource);
} }

View file

@ -1,14 +1,21 @@
package com.jozufozu.flywheel.backend.pipeline; package com.jozufozu.flywheel.backend.pipeline;
import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.jozufozu.flywheel.backend.FileResolution;
import com.jozufozu.flywheel.backend.ShaderSources; import com.jozufozu.flywheel.backend.ShaderSources;
import com.jozufozu.flywheel.backend.pipeline.parse.Import; import com.jozufozu.flywheel.backend.pipeline.parse.Import;
import com.jozufozu.flywheel.backend.pipeline.parse.ShaderFunction; import com.jozufozu.flywheel.backend.pipeline.parse.ShaderFunction;
@ -33,7 +40,7 @@ public class SourceFile {
public final ResourceLocation name; public final ResourceLocation name;
private final ShaderSources parent; public final ShaderSources parent;
private final String source; private final String source;
private final CharSequence elided; private final CharSequence elided;
private final ImmutableList<String> lines; private final ImmutableList<String> lines;
@ -89,6 +96,61 @@ public class SourceFile {
return imports; return imports;
} }
/**
* Search this file and recursively search all imports to find a struct definition matching the given name.
*
* @param name The name of the struct to find.
* @return null if no definition matches the name.
*/
public Optional<ShaderStruct> findStruct(CharSequence name) {
ShaderStruct struct = getStructs().get(name.toString());
if (struct != null) return Optional.of(struct);
for (Import include : getIncludes()) {
Optional<ShaderStruct> externalStruct = include.getOptional()
.flatMap(file -> file.findStruct(name));
if (externalStruct.isPresent()) return externalStruct;
}
return Optional.empty();
}
/**
* Search this file and recursively search all imports to find a function definition matching the given name.
*
* @param name The name of the function to find.
* @return Optional#empty() if no definition matches the name.
*/
public Optional<ShaderFunction> findFunction(CharSequence name) {
ShaderFunction local = getFunctions().get(name.toString());
if (local != null) return Optional.of(local);
for (Import include : getIncludes()) {
Optional<ShaderFunction> external = include.getOptional()
.flatMap(file -> file.findFunction(name));
if (external.isPresent()) return external;
}
return Optional.empty();
}
public CharSequence importStatement() {
return "#use " + '"' + name + '"';
}
public void generateFinalSource(StringBuilder source) {
for (Import include : getIncludes()) {
SourceFile file = include.getFile();
if (file != null) file.generateFinalSource(source);
}
source.append(getElidedSource());
}
public CharPos getCharPos(int charPos) { public CharPos getCharPos(int charPos) {
int lineNo = 0; int lineNo = 0;
for (; lineNo < lineStarts.size(); lineNo++) { for (; lineNo < lineStarts.size(); lineNo++) {

View file

@ -1,123 +0,0 @@
package com.jozufozu.flywheel.backend.pipeline;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.backend.pipeline.parse.ShaderFunction;
import com.jozufozu.flywheel.backend.pipeline.parse.ShaderStruct;
import com.jozufozu.flywheel.backend.pipeline.parse.StructField;
import com.jozufozu.flywheel.backend.pipeline.parse.Variable;
public class Template {
public CharSequence footer(ShaderType type, SourceFile file) {
switch (type) {
case VERTEX:
return vertexFooter(file);
case FRAGMENT:
return fragmentFooter(file);
}
return "";
}
public CharSequence vertexFooter(SourceFile file) {
ShaderFunction vertexMain = file.getFunctions()
.get("vertex");
ImmutableList<Variable> parameters = vertexMain.getParameters();
ShaderStruct interpolant = file.getStructs()
.get(vertexMain.returnType());
ShaderStruct vertex = file.getStructs()
.get(parameters.get(0)
.typeName()
.get());
ShaderStruct instance = file.getStructs()
.get(parameters.get(1)
.typeName()
.get());
StringBuilder template = new StringBuilder();
prefixFields(template, vertex, "attribute", "a_v_");
prefixFields(template, instance, "attribute", "a_i_");
prefixFields(template, interpolant, "varying", "v2f_");
template.append("void main() {\n");
template.append(vertex.name)
.append(" v;\n");
assignFields(template, vertex, "v.", "a_v_");
template.append(instance.name)
.append(" i;\n");
assignFields(template, instance, "i.", "a_i_");
template.append(interpolant.name)
.append(" o = ")
.append(vertexMain.call("v", "i"))
.append(";\n");
assignFields(template, interpolant, "v2f_", "o.");
template.append('}');
return template;
}
public CharSequence fragmentFooter(SourceFile file) {
ShaderFunction fragmentMain = file.getFunctions()
.get("fragment");
ImmutableList<Variable> parameters = fragmentMain.getParameters();
ShaderStruct interpolant = file.getStructs()
.get(parameters.get(0)
.typeName()
.get());
StringBuilder template = new StringBuilder();
prefixFields(template, interpolant, "varying", "v2f_");
template.append("void main() {\n");
template.append(interpolant.name)
.append(" o;\n");
assignFields(template, interpolant, "o.", "v2f_");
template.append(fragmentMain.call("o"))
.append(";\n");
template.append('}');
return template;
}
public static void prefixFields(StringBuilder builder, ShaderStruct struct, String qualifier, String prefix) {
ImmutableList<StructField> fields = struct.getFields();
for (StructField field : fields) {
builder.append(qualifier)
.append(' ')
.append(field.type)
.append(' ')
.append(prefix)
.append(field.name)
.append(";\n");
}
}
public static void assignFields(StringBuilder builder, ShaderStruct struct, String prefix1, String prefix2) {
ImmutableList<StructField> fields = struct.getFields();
for (StructField field : fields) {
builder.append(prefix1)
.append(field.name)
.append(" = ")
.append(prefix2)
.append(field.name)
.append(";\n");
}
}
}

View file

@ -12,9 +12,11 @@ import javax.annotation.Nullable;
import org.lwjgl.opengl.GL20; import org.lwjgl.opengl.GL20;
import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.FileResolution;
import com.jozufozu.flywheel.backend.ShaderSources; import com.jozufozu.flywheel.backend.ShaderSources;
import com.jozufozu.flywheel.backend.gl.shader.GlShader; import com.jozufozu.flywheel.backend.gl.shader.GlShader;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType; import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.backend.loading.ProtoProgram;
import com.jozufozu.flywheel.core.shader.ExtensibleGlProgram; import com.jozufozu.flywheel.core.shader.ExtensibleGlProgram;
import com.jozufozu.flywheel.core.shader.GameStateProgram; import com.jozufozu.flywheel.core.shader.GameStateProgram;
import com.jozufozu.flywheel.core.shader.IMultiProgram; import com.jozufozu.flywheel.core.shader.IMultiProgram;
@ -24,15 +26,20 @@ import com.jozufozu.flywheel.core.shader.spec.ProgramState;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
public class WorldShaderPipeline<P extends WorldProgram> { public class WorldShaderPipeline<P extends WorldProgram> implements IShaderPipeline<P> {
private final ShaderSources sources; private final ShaderSources sources;
private final ExtensibleGlProgram.Factory<P> factory; private final ExtensibleGlProgram.Factory<P> factory;
public WorldShaderPipeline(ShaderSources sources, ExtensibleGlProgram.Factory<P> factory) { private final ITemplate template;
private final FileResolution header;
public WorldShaderPipeline(ShaderSources sources, ExtensibleGlProgram.Factory<P> factory, ITemplate template, FileResolution header) {
this.sources = sources; this.sources = sources;
this.factory = factory; this.factory = factory;
this.template = template;
this.header = header;
} }
public IMultiProgram<P> compile(ProgramSpec spec) { public IMultiProgram<P> compile(ProgramSpec spec) {
@ -43,9 +50,9 @@ public class WorldShaderPipeline<P extends WorldProgram> {
} }
public IMultiProgram<P> compile(ResourceLocation name, SourceFile file, List<ProgramState> variants) { public IMultiProgram<P> compile(ResourceLocation name, SourceFile file, List<ProgramState> variants) {
ShaderBuilder shader = new ShaderBuilder(name, new Template()) ShaderBuilder shader = new ShaderBuilder(name, template, header)
.setMainSource(file) .setMainSource(file)
.setVersion(GLSLVersion.V120); .setVersion(GLSLVersion.V110);
GameStateProgram.Builder<P> builder = GameStateProgram.builder(compile(shader, name, null)); GameStateProgram.Builder<P> builder = GameStateProgram.builder(compile(shader, name, null));
@ -65,27 +72,20 @@ public class WorldShaderPipeline<P extends WorldProgram> {
GlShader vertex = shader.compile(name, ShaderType.VERTEX); GlShader vertex = shader.compile(name, ShaderType.VERTEX);
GlShader fragment = shader.compile(name, ShaderType.FRAGMENT); GlShader fragment = shader.compile(name, ShaderType.FRAGMENT);
int program = GL20.glCreateProgram(); ProtoProgram program = new ProtoProgram();
GL20.glAttachShader(program, vertex.handle()); program.attachShader(vertex);
GL20.glAttachShader(program, fragment.handle()); program.attachShader(fragment);
String log = glGetProgramInfoLog(program); template.attachAttributes(program, shader.mainFile);
if (!log.isEmpty()) { program.link(name);
Backend.log.debug("Program link log for " + name + ": " + log); program.deleteLinkedShaders();
}
int result = glGetProgrami(program, GL_LINK_STATUS);
if (result != GL_TRUE) {
throw new RuntimeException("Shader program linking failed, see log for details");
}
if (variant != null) { if (variant != null) {
return factory.create(name, program, variant.getExtensions()); return factory.create(name, program.program, variant.getExtensions());
} else { } else {
return factory.create(name, program, null); return factory.create(name, program.program, null);
} }
} }
} }

View file

@ -1,19 +1,36 @@
package com.jozufozu.flywheel.backend.pipeline.error; package com.jozufozu.flywheel.backend.pipeline.error;
import java.util.Optional;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.pipeline.SourceFile; import com.jozufozu.flywheel.backend.pipeline.SourceFile;
import com.jozufozu.flywheel.backend.pipeline.span.Span; import com.jozufozu.flywheel.backend.pipeline.span.Span;
import com.jozufozu.flywheel.util.FlwUtil;
public class ErrorBuilder { public class ErrorBuilder {
private final StringBuilder internal = new StringBuilder(); private final StringBuilder internal = new StringBuilder();
public ErrorBuilder header(CharSequence msg) { public ErrorBuilder error(CharSequence msg) {
internal.append("error: ") internal.append("error: ")
.append(msg); .append(msg);
return endLine(); return endLine();
} }
public ErrorBuilder errorIn(SourceFile file) { public ErrorBuilder note(CharSequence msg) {
internal.append("note: ")
.append(msg);
return endLine();
}
public ErrorBuilder hint(CharSequence msg) {
internal.append("hint: ")
.append(msg);
return endLine();
}
public ErrorBuilder in(SourceFile file) {
internal.append("--> ") internal.append("--> ")
.append(file.name); .append(file.name);
return endLine(); return endLine();
@ -37,7 +54,18 @@ public class ErrorBuilder {
return this; return this;
} }
public ErrorBuilder hintIncludeFor(Span span) {
if (span == null) return this;
hint("add " + span.getSourceFile().importStatement())
.in(span.getSourceFile())
.pointAt(span, 1);
return this;
}
public ErrorBuilder pointAt(Span span, int ctxLines) { public ErrorBuilder pointAt(Span span, int ctxLines) {
SourceFile file = span.getSourceFile(); SourceFile file = span.getSourceFile();
if (span.lines() == 1) { if (span.lines() == 1) {
@ -47,6 +75,8 @@ public class ErrorBuilder {
int firstLine = Math.max(0, spanLine - ctxLines); int firstLine = Math.max(0, spanLine - ctxLines);
int lastLine = Math.min(file.getLineCount(), spanLine + ctxLines); int lastLine = Math.min(file.getLineCount(), spanLine + ctxLines);
int digits = FlwUtil.numDigits(lastLine);
int firstCol = span.getStart().getCol(); int firstCol = span.getStart().getCol();
int lastCol = span.getEnd().getCol(); int lastCol = span.getEnd().getCol();
@ -57,7 +87,7 @@ public class ErrorBuilder {
numberedLine(i + 1, line); numberedLine(i + 1, line);
if (i == spanLine) { if (i == spanLine) {
line(" ", generateUnderline(firstCol, lastCol)); line(FlwUtil.repeatChar(' ', digits), generateUnderline(firstCol, lastCol));
} }
} }
} }

View file

@ -1,7 +1,10 @@
package com.jozufozu.flywheel.backend.pipeline.error; package com.jozufozu.flywheel.backend.pipeline.error;
import java.util.Optional;
import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.pipeline.SourceFile; import com.jozufozu.flywheel.backend.pipeline.SourceFile;
import com.jozufozu.flywheel.backend.pipeline.parse.ShaderStruct;
import com.jozufozu.flywheel.backend.pipeline.span.Span; import com.jozufozu.flywheel.backend.pipeline.span.Span;
public class ErrorReporter { public class ErrorReporter {
@ -11,11 +14,37 @@ public class ErrorReporter {
ErrorBuilder builder = new ErrorBuilder(); ErrorBuilder builder = new ErrorBuilder();
CharSequence error = builder.header(message) CharSequence error = builder.error(message)
.errorIn(file) .in(file)
.pointAt(span, 2) .pointAt(span, 2)
.build(); .build();
Backend.log.info(error); Backend.log.error(error);
}
public static void generateFileError(SourceFile file, String message) {
ErrorBuilder builder = new ErrorBuilder();
CharSequence error = builder.error(message)
.in(file)
.build();
Backend.log.error(error);
}
public static void generateMissingStruct(SourceFile file, Span vertexName) {
Optional<Span> span = file.parent.index.getStructDefinitionsMatching(vertexName)
.stream()
.findFirst()
.map(ShaderStruct::getName);
ErrorBuilder builder = new ErrorBuilder();
ErrorBuilder error = builder.error("struct not defined")
.in(file)
.pointAt(vertexName, 2)
.hintIncludeFor(span.orElse(null));
Backend.log.error(error.build());
} }
} }

View file

@ -2,6 +2,7 @@ package com.jozufozu.flywheel.backend.pipeline.parse;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -43,6 +44,14 @@ public class Import extends AbstractShaderElement {
return new ResourceLocation(""); return new ResourceLocation("");
} }
public FileResolution getResolution() {
return resolution;
}
public Optional<SourceFile> getOptional() {
return Optional.ofNullable(resolution.getFile());
}
@Nullable @Nullable
public SourceFile getFile() { public SourceFile getFile() {
return resolution.getFile(); return resolution.getFile();

View file

@ -29,6 +29,22 @@ public class ShaderFunction extends AbstractShaderElement {
this.parameters = parseArguments(); this.parameters = parseArguments();
} }
public Span getType() {
return type;
}
public Span getName() {
return name;
}
public Span getArgs() {
return args;
}
public Span getBody() {
return body;
}
public String call(String... args) { public String call(String... args) {
return name + "(" + String.join(", ", args) + ")"; return name + "(" + String.join(", ", args) + ")";
} }
@ -37,7 +53,7 @@ public class ShaderFunction extends AbstractShaderElement {
return parameters; return parameters;
} }
public String returnType() { public String returnTypeName() {
return type.get(); return type.get();
} }

View file

@ -1,14 +1,12 @@
package com.jozufozu.flywheel.backend.pipeline.parse; package com.jozufozu.flywheel.backend.pipeline.parse;
import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.jozufozu.flywheel.backend.loading.Program; import com.jozufozu.flywheel.backend.loading.ProtoProgram;
import com.jozufozu.flywheel.backend.loading.TypeHelper; import com.jozufozu.flywheel.backend.loading.TypeHelper;
import com.jozufozu.flywheel.backend.pipeline.error.ErrorReporter;
import com.jozufozu.flywheel.backend.pipeline.span.Span; import com.jozufozu.flywheel.backend.pipeline.span.Span;
public class ShaderStruct extends AbstractShaderElement { public class ShaderStruct extends AbstractShaderElement {
@ -30,6 +28,14 @@ public class ShaderStruct extends AbstractShaderElement {
this.fields2Types = createTypeLookup(); this.fields2Types = createTypeLookup();
} }
public Span getName() {
return name;
}
public Span getBody() {
return body;
}
public ImmutableList<StructField> getFields() { public ImmutableList<StructField> getFields() {
return fields; return fields;
} }
@ -59,7 +65,7 @@ public class ShaderStruct extends AbstractShaderElement {
return fields.build(); return fields.build();
} }
public void addPrefixedAttributes(Program builder, String prefix) { public void addPrefixedAttributes(ProtoProgram builder, String prefix) {
for (StructField field : fields) { for (StructField field : fields) {
int attributeCount = TypeHelper.getAttributeCount(field.type); int attributeCount = TypeHelper.getAttributeCount(field.type);

View file

@ -1,15 +1,16 @@
package com.jozufozu.flywheel.core; package com.jozufozu.flywheel.core;
import java.util.List;
import com.jozufozu.flywheel.Flywheel; import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.FileResolution;
import com.jozufozu.flywheel.backend.ResourceUtil;
import com.jozufozu.flywheel.backend.SpecMetaRegistry; import com.jozufozu.flywheel.backend.SpecMetaRegistry;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType; 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.core.crumbling.CrumblingProgram; import com.jozufozu.flywheel.core.crumbling.CrumblingProgram;
import com.jozufozu.flywheel.core.shader.WorldFog; import com.jozufozu.flywheel.core.shader.WorldFog;
import com.jozufozu.flywheel.core.shader.WorldProgram; import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.core.shader.extension.IProgramExtension;
import com.jozufozu.flywheel.core.shader.gamestate.FogStateProvider; import com.jozufozu.flywheel.core.shader.gamestate.FogStateProvider;
import com.jozufozu.flywheel.core.shader.gamestate.NormalDebugStateProvider; import com.jozufozu.flywheel.core.shader.gamestate.NormalDebugStateProvider;
import com.jozufozu.flywheel.event.GatherContextEvent; import com.jozufozu.flywheel.event.GatherContextEvent;
@ -33,13 +34,14 @@ public class Contexts {
SpecMetaRegistry.register(WorldFog.LINEAR); SpecMetaRegistry.register(WorldFog.LINEAR);
SpecMetaRegistry.register(WorldFog.EXP2); SpecMetaRegistry.register(WorldFog.EXP2);
CRUMBLING = backend.register(new WorldContext<>(backend, CrumblingProgram::new).withName(Names.CRUMBLING) FileResolution crumblingBuiltins = backend.sources.resolveFile(ResourceUtil.subPath(Names.CRUMBLING, ".glsl"));
.withBuiltin(ShaderType.FRAGMENT, Names.CRUMBLING, "/builtin.frag") FileResolution worldBuiltins = backend.sources.resolveFile(ResourceUtil.subPath(Names.WORLD, ".glsl"));
.withBuiltin(ShaderType.VERTEX, Names.CRUMBLING, "/builtin.vert"));
WORLD = backend.register(new WorldContext<>(backend, WorldProgram::new).withName(Names.WORLD) IShaderPipeline<CrumblingProgram> crumblingPipeline = new WorldShaderPipeline<>(backend.sources, CrumblingProgram::new, InstancingTemplate.INSTANCE, crumblingBuiltins);
.withBuiltin(ShaderType.FRAGMENT, Names.WORLD, "/builtin.frag") IShaderPipeline<WorldProgram> worldPipeline = new WorldShaderPipeline<>(backend.sources, WorldProgram::new, InstancingTemplate.INSTANCE, worldBuiltins);
.withBuiltin(ShaderType.VERTEX, Names.WORLD, "/builtin.vert"));
CRUMBLING = backend.register(new WorldContext<>(backend, crumblingPipeline).withName(Names.CRUMBLING));
WORLD = backend.register(new WorldContext<>(backend, worldPipeline).withName(Names.WORLD));
} }
public static class Names { public static class Names {

View file

@ -1,22 +1,12 @@
package com.jozufozu.flywheel.core; package com.jozufozu.flywheel.core;
import java.util.EnumMap;
import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.ResourceUtil;
import com.jozufozu.flywheel.backend.ShaderContext; import com.jozufozu.flywheel.backend.ShaderContext;
import com.jozufozu.flywheel.backend.ShaderSources;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.backend.loading.InstancedArraysTemplate;
import com.jozufozu.flywheel.backend.loading.ProgramTemplate;
import com.jozufozu.flywheel.backend.loading.ShaderLoadingException;
import com.jozufozu.flywheel.backend.material.MaterialSpec; import com.jozufozu.flywheel.backend.material.MaterialSpec;
import com.jozufozu.flywheel.backend.pipeline.IShaderPipeline; import com.jozufozu.flywheel.backend.pipeline.IShaderPipeline;
import com.jozufozu.flywheel.backend.pipeline.LegacyPipeline;
import com.jozufozu.flywheel.core.shader.ExtensibleGlProgram;
import com.jozufozu.flywheel.core.shader.WorldProgram; import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.core.shader.spec.ProgramSpec; import com.jozufozu.flywheel.core.shader.spec.ProgramSpec;
@ -25,24 +15,16 @@ import net.minecraft.util.ResourceLocation;
public class WorldContext<P extends WorldProgram> extends ShaderContext<P> { public class WorldContext<P extends WorldProgram> extends ShaderContext<P> {
protected ResourceLocation name; protected ResourceLocation name;
protected Supplier<Stream<ResourceLocation>> specStream; protected Supplier<Stream<ResourceLocation>> specStream;
protected TemplateFactory templateFactory;
private final Map<ShaderType, ResourceLocation> builtins = new EnumMap<>(ShaderType.class); public final IShaderPipeline<P> pipeline;
private final Map<ShaderType, String> builtinSources = new EnumMap<>(ShaderType.class);
private final ExtensibleGlProgram.Factory<P> factory; public WorldContext(Backend backend, IShaderPipeline<P> factory) {
public IShaderPipeline<P> pipeline;
public WorldContext(Backend backend, ExtensibleGlProgram.Factory<P> factory) {
super(backend); super(backend);
this.factory = factory; this.pipeline = factory;
specStream = () -> backend.allMaterials() specStream = () -> backend.allMaterials()
.stream() .stream()
.map(MaterialSpec::getProgramName); .map(MaterialSpec::getProgramName);
templateFactory = InstancedArraysTemplate::new;
} }
public WorldContext<P> withName(ResourceLocation name) { public WorldContext<P> withName(ResourceLocation name) {
@ -50,42 +32,16 @@ public class WorldContext<P extends WorldProgram> extends ShaderContext<P> {
return this; return this;
} }
public WorldContext<P> withBuiltin(ShaderType shaderType, ResourceLocation folder, String file) {
return withBuiltin(shaderType, ResourceUtil.subPath(folder, file));
}
public WorldContext<P> withBuiltin(ShaderType shaderType, ResourceLocation file) {
builtins.put(shaderType, file);
return this;
}
public WorldContext<P> withSpecStream(Supplier<Stream<ResourceLocation>> specStream) { public WorldContext<P> withSpecStream(Supplier<Stream<ResourceLocation>> specStream) {
this.specStream = specStream; this.specStream = specStream;
return this; return this;
} }
public WorldContext<P> withTemplateFactory(TemplateFactory templateFactory) {
this.templateFactory = templateFactory;
return this;
}
@Override @Override
public void load() { public void load() {
Backend.log.info("Loading context '{}'", name); Backend.log.info("Loading context '{}'", name);
try {
builtins.forEach((type, resourceLocation) -> builtinSources.put(type, backend.sources.getShaderSource(resourceLocation)));
} catch (ShaderLoadingException e) {
backend.sources.notifyError();
Backend.log.error(String.format("Could not find builtin: %s", e.getMessage()));
return;
}
pipeline = new LegacyPipeline<>(backend.sources, templateFactory.create(backend.sources), factory, builtinSources);
specStream.get() specStream.get()
.map(backend::getSpec) .map(backend::getSpec)
.forEach(this::loadSpec); .forEach(this::loadSpec);
@ -98,12 +54,9 @@ public class WorldContext<P extends WorldProgram> extends ShaderContext<P> {
Backend.log.debug("Loaded program {}", spec.name); Backend.log.debug("Loaded program {}", spec.name);
} catch (Exception e) { } catch (Exception e) {
Backend.log.error("Program '{}': {}", spec.name, e); Backend.log.error("Error loading program {}", spec.name);
Backend.log.error("", e);
backend.sources.notifyError(); backend.sources.notifyError();
} }
} }
public interface TemplateFactory {
ProgramTemplate create(ShaderSources loader);
}
} }

View file

@ -0,0 +1,55 @@
package com.jozufozu.flywheel.util;
import java.util.Arrays;
public class FlwUtil {
public static String repeatChar(char c, int n) {
char[] arr = new char[n];
Arrays.fill(arr, c);
return new String(arr);
}
public static int numDigits(int number) {
// cursed but allegedly the fastest algorithm, taken from https://www.baeldung.com/java-number-of-digits-in-int
if (number < 100000) {
if (number < 100) {
if (number < 10) {
return 1;
} else {
return 2;
}
} else {
if (number < 1000) {
return 3;
} else {
if (number < 10000) {
return 4;
} else {
return 5;
}
}
}
} else {
if (number < 10000000) {
if (number < 1000000) {
return 6;
} else {
return 7;
}
} else {
if (number < 100000000) {
return 8;
} else {
if (number < 1000000000) {
return 9;
} else {
return 10;
}
}
}
}
}
}

View file

@ -1,8 +1,13 @@
#flwbuiltins
#flwinclude <"flywheel:data/blockfragment.glsl"> struct BlockFrag {
vec2 texCoords;
vec4 color;
float diffuse;
vec2 light;
};
void FLWMain(BlockFrag r) { #if defined(FRAGMENT_SHADER)
void fragment(BlockFrag r) {
vec4 tex = FLWBlockTexture(r.texCoords); vec4 tex = FLWBlockTexture(r.texCoords);
vec4 color = vec4(tex.rgb * FLWLight(r.light).rgb * r.diffuse, tex.a) * r.color; vec4 color = vec4(tex.rgb * FLWLight(r.light).rgb * r.diffuse, tex.a) * r.color;
@ -15,3 +20,4 @@ void FLWMain(BlockFrag r) {
// flw_Tint = r.color; // flw_Tint = r.color;
FLWFinalizeColor(color); FLWFinalizeColor(color);
} }
#endif

View file

@ -1,10 +1,31 @@
#flwinclude <"flywheel:context/world/fog.glsl"> #use "flywheel:context/fog.glsl"
uniform float uTime;
uniform mat4 uViewProjection;
uniform vec3 uCameraPos;
uniform vec2 uTextureScale; uniform vec2 uTextureScale;
uniform sampler2D uBlockAtlas; uniform sampler2D uBlockAtlas;
uniform sampler2D uLightMap; uniform sampler2D uLightMap;
uniform sampler2D uCrumbling; uniform sampler2D uCrumbling;
uniform vec2 uWindowSize;
void FLWFinalizeNormal(inout vec3 normal) {
// noop
}
#if defined(VERTEX_SHADER)
void FLWFinalizeWorldPos(inout vec4 worldPos) {
#if defined(USE_FOG)
FragDistance = length(worldPos.xyz - uCameraPos);
#endif
gl_Position = uViewProjection * worldPos;
}
#elif defined(FRAGMENT_SHADER)
vec4 FLWBlockTexture(vec2 texCoords) { vec4 FLWBlockTexture(vec2 texCoords) {
vec4 cr = texture2D(uCrumbling, texCoords * uTextureScale); vec4 cr = texture2D(uCrumbling, texCoords * uTextureScale);
float diffuseAlpha = texture2D(uBlockAtlas, texCoords).a; float diffuseAlpha = texture2D(uBlockAtlas, texCoords).a;
@ -27,3 +48,4 @@ void FLWFinalizeColor(vec4 color) {
vec4 FLWLight(vec2 lightCoords) { vec4 FLWLight(vec2 lightCoords) {
return vec4(1.); return vec4(1.);
} }
#endif

View file

@ -1 +0,0 @@
#flwinclude <"flywheel:context/world/builtin.vert">

View file

@ -1,10 +1,31 @@
#flwinclude <"flywheel:context/world/fog.glsl"> #use "flywheel:context/fog.glsl"
#flwinclude <"flywheel:core/lightutil.glsl">
uniform float uTime;
uniform mat4 uViewProjection;
uniform vec3 uCameraPos;
uniform vec2 uTextureScale;
uniform sampler2D uBlockAtlas; uniform sampler2D uBlockAtlas;
uniform sampler2D uLightMap; uniform sampler2D uLightMap;
uniform vec2 uWindowSize; uniform vec2 uWindowSize;
void FLWFinalizeNormal(inout vec3 normal) {
// noop
}
#if defined(VERTEX_SHADER)
void FLWFinalizeWorldPos(inout vec4 worldPos) {
#if defined(USE_FOG)
FragDistance = length(worldPos.xyz - uCameraPos);
#endif
gl_Position = uViewProjection * worldPos;
}
#elif defined(FRAGMENT_SHADER)
#use "flywheel:core/lightutil.glsl"
vec4 FLWBlockTexture(vec2 texCoords) { vec4 FLWBlockTexture(vec2 texCoords) {
return texture2D(uBlockAtlas, texCoords); return texture2D(uBlockAtlas, texCoords);
} }
@ -24,3 +45,4 @@ void FLWFinalizeColor(vec4 color) {
vec4 FLWLight(vec2 lightCoords) { vec4 FLWLight(vec2 lightCoords) {
return texture2D(uLightMap, shiftLight(lightCoords)); return texture2D(uLightMap, shiftLight(lightCoords));
} }
#endif

View file

@ -1,19 +0,0 @@
uniform float uTime;
uniform mat4 uViewProjection;
uniform vec3 uCameraPos;
#if defined(USE_FOG)
varying float FragDistance;
#endif
void FLWFinalizeWorldPos(inout vec4 worldPos) {
#if defined(USE_FOG)
FragDistance = length(worldPos.xyz - uCameraPos);
#endif
gl_Position = uViewProjection * worldPos;
}
void FLWFinalizeNormal(inout vec3 normal) {
// noop
}

View file

@ -1,7 +0,0 @@
#[Fragment]
struct BlockFrag {
vec2 texCoords;
vec4 color;
float diffuse;
vec2 light;
};

View file

@ -1,4 +1,3 @@
#[VertexData]
struct Vertex { struct Vertex {
vec3 pos; vec3 pos;
vec3 normal; vec3 normal;

View file

@ -1,58 +0,0 @@
#use "flywheel:core/diffuse.glsl"
struct Instance {
vec2 light;
vec4 color;
mat4 transform;
mat3 normalMat;
};
struct Vertex {
vec3 pos;
vec3 normal;
vec2 texCoords;
};
struct BlockFrag {
vec2 texCoords;
vec4 color;
float diffuse;
vec2 light;
};
BlockFrag vertex(Vertex v, Instance i) {
vec4 worldPos = i.transform * vec4(v.pos, 1.);
vec3 norm = i.normalMat * v.normal;
FLWFinalizeWorldPos(worldPos);
FLWFinalizeNormal(norm);
norm = normalize(norm);
BlockFrag b;
b.diffuse = diffuse(norm);
b.texCoords = v.texCoords;
b.light = i.light;
#if defined(DEBUG_NORMAL)
b.color = vec4(norm, 1.);
#else
b.color = i.color;
#endif
return b;
}
void fragment(BlockFrag r) {
vec4 tex = FLWBlockTexture(r.texCoords);
vec4 color = vec4(tex.rgb * FLWLight(r.light).rgb * r.diffuse, tex.a) * r.color;
// flw_WorldPos = ;
// flw_Normal = ;
// flw_Albedo = tex.rgb;
// flw_Alpha = tex.a;
// flw_LightMap = r.light;
// flw_Tint = r.color;
FLWFinalizeColor(color);
}

View file

@ -1,10 +1,9 @@
#flwbuiltins #use "flywheel:core/diffuse.glsl"
#flwinclude <"flywheel:core/diffuse.glsl">
#flwinclude <"flywheel:data/modelvertex.glsl"> #use "flywheel:data/modelvertex.glsl"
#flwinclude <"flywheel:data/blockfragment.glsl">
#use "flywheel:block.frag"
#[InstanceData]
struct Instance { struct Instance {
vec2 light; vec2 light;
vec4 color; vec4 color;
@ -12,7 +11,8 @@ struct Instance {
mat3 normalMat; mat3 normalMat;
}; };
BlockFrag FLWMain(Vertex v, Instance i) { #if defined(VERTEX_SHADER)
BlockFrag vertex(Vertex v, Instance i) {
vec4 worldPos = i.transform * vec4(v.pos, 1.); vec4 worldPos = i.transform * vec4(v.pos, 1.);
vec3 norm = i.normalMat * v.normal; vec3 norm = i.normalMat * v.normal;
@ -33,3 +33,4 @@ BlockFrag FLWMain(Vertex v, Instance i) {
#endif #endif
return b; return b;
} }
#endif

View file

@ -1,9 +1,10 @@
#flwbuiltins #use "flywheel:core/matutils.glsl"
#flwinclude <"flywheel:core/matutils.glsl"> #use "flywheel:core/quaternion.glsl"
#flwinclude <"flywheel:core/quaternion.glsl"> #use "flywheel:core/diffuse.glsl"
#flwinclude <"flywheel:core/diffuse.glsl">
#use "flywheel:data/modelvertex.glsl"
#use "flywheel:block.frag"
#[InstanceData]
struct Oriented { struct Oriented {
vec2 light; vec2 light;
vec4 color; vec4 color;
@ -12,10 +13,8 @@ struct Oriented {
vec4 rotation; vec4 rotation;
}; };
#flwinclude <"flywheel:data/modelvertex.glsl"> #if defined(VERTEX_SHADER)
#flwinclude <"flywheel:data/blockfragment.glsl"> BlockFrag vertex(Vertex v, Oriented o) {
BlockFrag FLWMain(Vertex v, Oriented o) {
vec4 worldPos = vec4(rotateVertexByQuat(v.pos - o.pivot, o.rotation) + o.pivot + o.pos, 1.); vec4 worldPos = vec4(rotateVertexByQuat(v.pos - o.pivot, o.rotation) + o.pivot + o.pos, 1.);
vec3 norm = rotateVertexByQuat(v.normal, o.rotation); vec3 norm = rotateVertexByQuat(v.normal, o.rotation);
@ -34,3 +33,4 @@ BlockFrag FLWMain(Vertex v, Oriented o) {
#endif #endif
return b; return b;
} }
#endif

View file

@ -1,9 +1,7 @@
#flwbuiltins #use "flywheel:core/matutils.glsl"
#flwinclude <"flywheel:core/matutils.glsl"> #use "flywheel:core/quaternion.glsl"
#flwinclude <"flywheel:core/quaternion.glsl"> #use "flywheel:core/diffuse.glsl"
#flwinclude <"flywheel:core/diffuse.glsl">
#[InstanceData]
struct Oriented { struct Oriented {
// each vec 4 is 2 light coords packed <lo y, hi y> // each vec 4 is 2 light coords packed <lo y, hi y>
// x z // x z
@ -21,10 +19,10 @@ struct Oriented {
vec4 rotation; vec4 rotation;
}; };
#flwinclude <"flywheel:data/modelvertex.glsl"> #use "flywheel:data/modelvertex.glsl"
#flwinclude <"flywheel:data/blockfragment.glsl"> #use "flywheel:block.frag"
BlockFrag FLWMain(Vertex v, Oriented o) { BlockFrag vertex(Vertex v, Oriented o) {
vec4 worldPos = vec4(rotateVertexByQuat(v.pos - o.pivot, o.rotation) + o.pivot + o.pos, 1.); vec4 worldPos = vec4(rotateVertexByQuat(v.pos - o.pivot, o.rotation) + o.pivot + o.pos, 1.);
vec3 norm = rotateVertexByQuat(v.normal, o.rotation); vec3 norm = rotateVertexByQuat(v.normal, o.rotation);

View file

@ -1,19 +0,0 @@
#version 110
#flwbeginbody
#FLWPrefixFields(Fragment, varying, v2f_)
//vec3 flw_WorldPos;
//vec3 flw_Normal;
//vec3 flw_Albedo;
//float flw_Alpha;
//vec2 flw_LightMap;
//vec4 flw_Tint;
void main() {
Fragment f;
#FLWAssignFields(Fragment, f., v2f_)
FLWMain(f);
}

View file

@ -1,19 +0,0 @@
#version 110
#flwbeginbody
#FLWPrefixFields(VertexData, attribute, a_v_)
#FLWPrefixFields(InstanceData, attribute, a_i_)
#FLWPrefixFields(Fragment, varying, v2f_)
void main() {
VertexData v;
#FLWAssignFields(VertexData, v., a_v_)
InstanceData i;
#FLWAssignFields(InstanceData, i., a_i_)
Fragment o = FLWMain(v, i);
#FLWAssignFields(Fragment, v2f_, o.)
}

View file

@ -1,43 +0,0 @@
#version 450
#extension GL_NV_mesh_shader : require
layout(local_size_x=32) in;
layout(max_vertices=64, max_primitives=32) out;
layout (std430, binding = 1) buffer _vertices {
FLWVertexData vertices[];
} vb;
struct s_meshlet {
uint vertices[64];
uint indices[96];
uint vertex_count;
uint index_count;
};
layout (std430, binding = 2) buffer _meshlets {
s_meshlet meshlets[];
} mbuf;
layout (location = 0) out PerVertexData {
vec4 color;
} v_out[];// [max_vertices]
void main() {
uint mi = gl_WorkGroupID.x;
uint thread_id = gl_LocalInvocationID.x;
uint primIdx = thread_id * 3;
uint vertStartIdx = thread_id * 2;
gl_MeshVerticesNV[vertStartIdx + 0].gl_Position;
gl_MeshVerticesNV[vertStartIdx + 1].gl_Position;
gl_PrimitiveIndicesNV[primIdx + 0] = mbuf.meshlets[mi].indices[primIdx + 0];
gl_PrimitiveIndicesNV[primIdx + 1] = mbuf.meshlets[mi].indices[primIdx + 1];
gl_PrimitiveIndicesNV[primIdx + 2] = mbuf.meshlets[mi].indices[primIdx + 2];
gl_PrimitiveCountNV = mbuf.meshlets[mi].vertex_count / 2;
}

View file

@ -1,12 +0,0 @@
#version 110
#flwbeginbody
#FLWPrefixFields(Fragment, varying, v2f_)
void main() {
Fragment f;
#FLWAssignFields(Fragment, f., v2f_)
FLWMain(f);
}

View file

@ -1,15 +0,0 @@
#version 110
#flwbeginbody
#FLWPrefixFields(VertexData, attribute, a_v_)
#FLWPrefixFields(Fragment, varying, v2f_)
void main() {
VertexData v;
#FLWAssignFields(VertexData, v., a_v_)
Fragment o = FLWMain(v);
#FLWAssignFields(Fragment, v2f_, o.)
}