A heck of a lot

- Reorganize everything
 - Isolate SourceFile related things
 - Should consider decoupling ShaderLoader from resource loading
 - Document a lot of newer things
 - Index functions
 - Awkward WorldContext builder
 - Template responsible for providing shader inputs
 - Template is now an abstract class
 - Template provides GLSL version
 - ProgramSpecs now only accept one file
This commit is contained in:
Jozufozu 2021-08-10 02:06:22 -07:00
parent 3c24abe837
commit 82ea5b1720
48 changed files with 668 additions and 559 deletions

View file

@ -16,6 +16,7 @@ import org.lwjgl.opengl.GLCapabilities;
import com.jozufozu.flywheel.backend.gl.versioned.GlCompat; import com.jozufozu.flywheel.backend.gl.versioned.GlCompat;
import com.jozufozu.flywheel.backend.instancing.InstanceData; import com.jozufozu.flywheel.backend.instancing.InstanceData;
import com.jozufozu.flywheel.backend.material.MaterialSpec; import com.jozufozu.flywheel.backend.material.MaterialSpec;
import com.jozufozu.flywheel.backend.source.ShaderSources;
import com.jozufozu.flywheel.config.FlwConfig; import com.jozufozu.flywheel.config.FlwConfig;
import com.jozufozu.flywheel.core.shader.spec.ProgramSpec; import com.jozufozu.flywheel.core.shader.spec.ProgramSpec;
@ -59,14 +60,6 @@ public class Backend {
OptifineHandler.init(); OptifineHandler.init();
} }
void clearContexts() {
SpecMetaRegistry.clear();
programSpecRegistry.clear();
contexts.forEach(IShaderContext::delete);
contexts.clear();
materialRegistry.clear();
}
/** /**
* Get a string describing the Flywheel backend. When there are eventually multiple backends * Get a string describing the Flywheel backend. When there are eventually multiple backends
* (Meshlet, MDI, GL31 Draw Instanced are planned), this will name which one is in use. * (Meshlet, MDI, GL31 Draw Instanced are planned), this will name which one is in use.
@ -98,7 +91,7 @@ public class Backend {
/** /**
* Register a shader context. * Register a shader context.
*/ */
public <C extends ShaderContext<?>> C register(C spec) { public <C extends IShaderContext<?>> C register(C spec) {
contexts.add(spec); contexts.add(spec);
return spec; return spec;
} }
@ -201,6 +194,17 @@ public class Backend {
RenderWork.enqueue(Minecraft.getInstance().levelRenderer::allChanged); RenderWork.enqueue(Minecraft.getInstance().levelRenderer::allChanged);
} }
/**
* INTERNAL USE ONLY
*/
public void _clearContexts() {
SpecMetaRegistry.clear();
programSpecRegistry.clear();
contexts.forEach(IShaderContext::delete);
contexts.clear();
materialRegistry.clear();
}
public static void init() { public static void init() {
} }
} }

View file

@ -1,56 +0,0 @@
package com.jozufozu.flywheel.backend;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.pipeline.SourceFile;
import com.jozufozu.flywheel.backend.pipeline.error.ErrorBuilder;
import com.jozufozu.flywheel.backend.pipeline.span.Span;
import net.minecraft.util.ResourceLocation;
public class FileResolution {
private final List<Span> foundSpans = new ArrayList<>();
private final ResourceLocation fileLoc;
private SourceFile file;
public FileResolution(ResourceLocation fileLoc) {
this.fileLoc = fileLoc;
}
public ResourceLocation getFileLoc() {
return fileLoc;
}
@Nullable
public SourceFile getFile() {
return file;
}
public void addSpan(Span span) {
foundSpans.add(span);
}
public void resolve(ShaderSources sources) {
try {
file = sources.source(fileLoc);
} catch (RuntimeException error) {
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

@ -1,34 +0,0 @@
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,33 +0,0 @@
package com.jozufozu.flywheel.backend;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.core.shader.IMultiProgram;
import net.minecraft.util.ResourceLocation;
public abstract class ShaderContext<P extends GlProgram> implements IShaderContext<P> {
protected final Map<ResourceLocation, IMultiProgram<P>> programs = new HashMap<>();
public final Backend backend;
public ShaderContext(Backend backend) {
this.backend = backend;
}
@Override
public Supplier<P> getProgramSupplier(ResourceLocation spec) {
return programs.get(spec);
}
@Override
public void delete() {
programs.values()
.forEach(IMultiProgram::delete);
programs.clear();
}
}

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.backend.pipeline; package com.jozufozu.flywheel.backend.gl;
public enum GLSLVersion { public enum GLSLVersion {
V110(110), V110(110),

View file

@ -152,6 +152,14 @@ public abstract class InstanceManager<T> implements MaterialManager.OriginShiftL
} }
} }
public void queueUpdate(T obj) {
if (!Backend.getInstance()
.canUseInstancing()) return;
synchronized (queuedUpdates) {
queuedUpdates.add(obj);
}
}
/** /**
* Update the instance associated with an object. * Update the instance associated with an object.
* *
@ -185,14 +193,6 @@ public abstract class InstanceManager<T> implements MaterialManager.OriginShiftL
} }
} }
public void queueUpdate(T obj) {
if (!Backend.getInstance()
.canUseInstancing()) return;
synchronized (queuedUpdates) {
queuedUpdates.add(obj);
}
}
public void onLightUpdate(T obj) { public void onLightUpdate(T obj) {
if (!Backend.getInstance() if (!Backend.getInstance()
.canUseInstancing()) return; .canUseInstancing()) return;

View file

@ -27,6 +27,18 @@ public class InstancedRenderDispatcher {
private static final WorldAttached<InstanceWorld> instanceWorlds = new WorldAttached<>($ -> new InstanceWorld()); private static final WorldAttached<InstanceWorld> instanceWorlds = new WorldAttached<>($ -> new InstanceWorld());
/**
* Call this when you want to invoke
* @param te
*/
public static void enqueueUpdate(TileEntity te) {
getTiles(te.getLevel()).queueUpdate(te);
}
public static void enqueueUpdate(Entity entity) {
getEntities(entity.level).queueUpdate(entity);
}
@Nonnull @Nonnull
public static InstanceManager<TileEntity> getTiles(IWorld world) { public static InstanceManager<TileEntity> getTiles(IWorld world) {
return instanceWorlds.get(world) return instanceWorlds.get(world)
@ -52,14 +64,6 @@ public class InstancedRenderDispatcher {
instanceWorlds.get(world).tick(); instanceWorlds.get(world).tick();
} }
public static void enqueueUpdate(TileEntity te) {
getTiles(te.getLevel()).queueUpdate(te);
}
public static void enqueueUpdate(Entity entity) {
getEntities(entity.level).queueUpdate(entity);
}
@SubscribeEvent @SubscribeEvent
public static void onBeginFrame(BeginFrameEvent event) { public static void onBeginFrame(BeginFrameEvent event) {
instanceWorlds.get(event.getWorld()).beginFrame(event); instanceWorlds.get(event.getWorld()).beginFrame(event);

View file

@ -4,6 +4,10 @@ import com.jozufozu.flywheel.core.shader.IMultiProgram;
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;
/**
* The main interface for compiling usable shaders from program specs.
* @param <P> the type of the program that this pipeline compiles.
*/
public interface IShaderPipeline<P extends WorldProgram> { public interface IShaderPipeline<P extends WorldProgram> {
IMultiProgram<P> compile(ProgramSpec spec); IMultiProgram<P> compile(ProgramSpec spec);

View file

@ -1,41 +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.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

@ -1,40 +0,0 @@
package com.jozufozu.flywheel.backend.pipeline;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.pipeline.parse.Import;
import net.minecraft.util.ResourceLocation;
public class Includer {
public static List<SourceFile> recurseIncludes(SourceFile from) {
Set<ResourceLocation> seen = new HashSet<>();
seen.add(from.name);
List<SourceFile> out = new ArrayList<>();
process(seen, out, from);
return out;
}
private static void process(Set<ResourceLocation> seen, List<SourceFile> out, SourceFile source) {
ImmutableList<Import> imports = source.getIncludes();
for (Import use : imports) {
SourceFile file = use.getFile();
if (file != null && seen.add(file.name)) {
process(seen, out, file);
out.add(file);
}
}
}
}

View file

@ -3,13 +3,14 @@ package com.jozufozu.flywheel.backend.pipeline;
import java.util.Optional; import java.util.Optional;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.pipeline.error.ErrorReporter; import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.backend.pipeline.parse.ShaderFunction; import com.jozufozu.flywheel.backend.source.error.ErrorReporter;
import com.jozufozu.flywheel.backend.pipeline.parse.ShaderStruct; import com.jozufozu.flywheel.backend.source.parse.ShaderFunction;
import com.jozufozu.flywheel.backend.pipeline.parse.Variable; import com.jozufozu.flywheel.backend.source.parse.ShaderStruct;
import com.jozufozu.flywheel.backend.pipeline.span.Span; import com.jozufozu.flywheel.backend.source.parse.Variable;
import com.jozufozu.flywheel.backend.source.span.Span;
public class InstanceTemplateData { public class InstancingProgramMetaData {
public final SourceFile file; public final SourceFile file;
public final ShaderFunction vertexMain; public final ShaderFunction vertexMain;
@ -21,15 +22,14 @@ public class InstanceTemplateData {
public final ShaderStruct vertex; public final ShaderStruct vertex;
public final ShaderStruct instance; public final ShaderStruct instance;
public InstanceTemplateData(SourceFile file) { public InstancingProgramMetaData(SourceFile file) {
this.file = file; this.file = file;
Optional<ShaderFunction> vertexFunc = file.findFunction("vertex"); Optional<ShaderFunction> vertexFunc = file.findFunction("vertex");
Optional<ShaderFunction> fragmentFunc = file.findFunction("fragment"); Optional<ShaderFunction> fragmentFunc = file.findFunction("fragment");
if (!fragmentFunc.isPresent()) { if (!fragmentFunc.isPresent()) {
ErrorReporter.generateFileError(file, "could not find \"fragment\" function"); ErrorReporter.generateMissingFunction(file, "fragment", "\"fragment\" function not defined");
} }
if (!vertexFunc.isPresent()) { if (!vertexFunc.isPresent()) {
ErrorReporter.generateFileError(file, "could not find \"vertex\" function"); ErrorReporter.generateFileError(file, "could not find \"vertex\" function");
@ -64,15 +64,15 @@ public class InstanceTemplateData {
Optional<ShaderStruct> maybeInstance = file.findStruct(instanceName); Optional<ShaderStruct> maybeInstance = file.findStruct(instanceName);
if (!maybeVertex.isPresent()) { if (!maybeVertex.isPresent()) {
ErrorReporter.generateMissingStruct(file, vertexName); ErrorReporter.generateMissingStruct(file, vertexName, "struct not defined");
} }
if (!maybeInterpolant.isPresent()) { if (!maybeInterpolant.isPresent()) {
ErrorReporter.generateMissingStruct(file, interpolantName); ErrorReporter.generateMissingStruct(file, interpolantName, "struct not defined");
} }
if (!maybeInstance.isPresent()) { if (!maybeInstance.isPresent()) {
ErrorReporter.generateMissingStruct(file, instanceName); ErrorReporter.generateMissingStruct(file, instanceName, "struct not defined");
} }
if (!maybeVertex.isPresent() || !maybeInterpolant.isPresent() || !maybeInstance.isPresent()) { if (!maybeVertex.isPresent() || !maybeInterpolant.isPresent() || !maybeInstance.isPresent()) {

View file

@ -1,16 +1,19 @@
package com.jozufozu.flywheel.backend.pipeline; package com.jozufozu.flywheel.backend.pipeline;
import java.util.HashMap; import java.util.ArrayList;
import java.util.Map; import java.util.Collection;
import java.util.List;
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.backend.source.SourceFile;
public class InstancingTemplate implements ITemplate { public class InstancingTemplate extends Template<InstancingProgramMetaData> {
public static final InstancingTemplate INSTANCE = new InstancingTemplate(); public static final InstancingTemplate INSTANCE = new InstancingTemplate();
private final Map<SourceFile, InstanceTemplateData> datas = new HashMap<>(); public InstancingTemplate() {
super(InstancingProgramMetaData::new);
}
@Override @Override
public void generateTemplateSource(StringBuilder builder, ShaderType type, SourceFile file) { public void generateTemplateSource(StringBuilder builder, ShaderType type, SourceFile file) {
@ -22,51 +25,50 @@ public class InstancingTemplate implements ITemplate {
} }
@Override @Override
public void attachAttributes(ProtoProgram program, SourceFile file) { public Collection<ShaderInput> getShaderInputs(SourceFile file) {
InstanceTemplateData data = getData(file); InstancingProgramMetaData data = getMetadata(file);
data.vertex.addPrefixedAttributes(program, "a_v_");
data.instance.addPrefixedAttributes(program, "a_i_");
}
public InstanceTemplateData getData(SourceFile file) { List<ShaderInput> inputs = new ArrayList<>(ShaderInput.fromStruct(data.vertex, "a_v_"));
return datas.computeIfAbsent(file, InstanceTemplateData::new); inputs.addAll(ShaderInput.fromStruct(data.instance, "a_i_"));
return inputs;
} }
public void vertexFooter(StringBuilder template, SourceFile file) { public void vertexFooter(StringBuilder template, SourceFile file) {
InstanceTemplateData data = getData(file); InstancingProgramMetaData data = getMetadata(file);
ITemplate.prefixFields(template, data.vertex, "attribute", "a_v_"); Template.prefixFields(template, data.vertex, "attribute", "a_v_");
ITemplate.prefixFields(template, data.instance, "attribute", "a_i_"); Template.prefixFields(template, data.instance, "attribute", "a_i_");
ITemplate.prefixFields(template, data.interpolant, "varying", "v2f_"); Template.prefixFields(template, data.interpolant, "varying", "v2f_");
template.append("void main() {\n"); template.append("void main() {\n");
template.append(data.vertexName) template.append(data.vertexName)
.append(" v;\n"); .append(" v;\n");
ITemplate.assignFields(template, data.vertex, "v.", "a_v_"); Template.assignFields(template, data.vertex, "v.", "a_v_");
template.append(data.instanceName) template.append(data.instanceName)
.append(" i;\n"); .append(" i;\n");
ITemplate.assignFields(template, data.instance, "i.", "a_i_"); Template.assignFields(template, data.instance, "i.", "a_i_");
template.append(data.interpolantName) template.append(data.interpolantName)
.append(" o = ") .append(" o = ")
.append(data.vertexMain.call("v", "i")) .append(data.vertexMain.call("v", "i"))
.append(";\n"); .append(";\n");
ITemplate.assignFields(template, data.interpolant, "v2f_", "o."); Template.assignFields(template, data.interpolant, "v2f_", "o.");
template.append('}'); template.append('}');
} }
public void fragmentFooter(StringBuilder template, SourceFile file) { public void fragmentFooter(StringBuilder template, SourceFile file) {
InstanceTemplateData data = getData(file); InstancingProgramMetaData data = getMetadata(file);
ITemplate.prefixFields(template, data.interpolant, "varying", "v2f_"); Template.prefixFields(template, data.interpolant, "varying", "v2f_");
template.append("void main() {\n"); template.append("void main() {\n");
template.append(data.interpolantName) template.append(data.interpolantName)
.append(" o;\n"); .append(" o;\n");
ITemplate.assignFields(template, data.interpolant, "o.", "v2f_"); Template.assignFields(template, data.interpolant, "o.", "v2f_");
template.append(data.fragmentMain.call("o")) template.append(data.fragmentMain.call("o"))
.append(";\n"); .append(";\n");

View file

@ -3,13 +3,14 @@ package com.jozufozu.flywheel.backend.pipeline;
import java.util.Optional; import java.util.Optional;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.pipeline.error.ErrorReporter; import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.backend.pipeline.parse.ShaderFunction; import com.jozufozu.flywheel.backend.source.error.ErrorReporter;
import com.jozufozu.flywheel.backend.pipeline.parse.ShaderStruct; import com.jozufozu.flywheel.backend.source.parse.ShaderFunction;
import com.jozufozu.flywheel.backend.pipeline.parse.Variable; import com.jozufozu.flywheel.backend.source.parse.ShaderStruct;
import com.jozufozu.flywheel.backend.pipeline.span.Span; import com.jozufozu.flywheel.backend.source.parse.Variable;
import com.jozufozu.flywheel.backend.source.span.Span;
public class OneShotData { public class OneShotProgramMetaData {
public final SourceFile file; public final SourceFile file;
public final ShaderFunction vertexMain; public final ShaderFunction vertexMain;
@ -19,7 +20,7 @@ public class OneShotData {
public final ShaderStruct vertex; public final ShaderStruct vertex;
public final ShaderFunction fragmentMain; public final ShaderFunction fragmentMain;
public OneShotData(SourceFile file) { public OneShotProgramMetaData(SourceFile file) {
this.file = file; this.file = file;
Optional<ShaderFunction> maybeVertexMain = file.findFunction("vertex"); Optional<ShaderFunction> maybeVertexMain = file.findFunction("vertex");
@ -27,29 +28,32 @@ public class OneShotData {
if (!maybeVertexMain.isPresent()) { if (!maybeVertexMain.isPresent()) {
ErrorReporter.generateFileError(file, "could not find \"vertex\" function"); ErrorReporter.generateFileError(file, "could not find \"vertex\" function");
throw new RuntimeException();
} }
if (!maybeFragmentMain.isPresent()) { if (!maybeFragmentMain.isPresent()) {
ErrorReporter.generateFileError(file, "could not find \"fragment\" function"); ErrorReporter.generateMissingFunction(file, "fragment", "\"fragment\" function not defined");
}
if (!maybeVertexMain.isPresent() || !maybeFragmentMain.isPresent()) {
throw new RuntimeException(); throw new RuntimeException();
} }
vertexMain = maybeVertexMain.get(); vertexMain = maybeVertexMain.get();
fragmentMain = maybeFragmentMain.get(); fragmentMain = maybeFragmentMain.get();
ImmutableList<Variable> parameters = fragmentMain.getParameters(); ImmutableList<Variable> fragmentParameters = fragmentMain.getParameters();
ImmutableList<Variable> vertexParameters = vertexMain.getParameters(); ImmutableList<Variable> vertexParameters = vertexMain.getParameters();
if (vertexParameters.size() != 1) { if (vertexParameters.size() != 1) {
ErrorReporter.generateSpanError(vertexMain.getArgs(), "a basic model requires vertex function to have one argument"); ErrorReporter.generateSpanError(vertexMain.getArgs(), "a basic model requires vertex function to have one argument");
throw new RuntimeException();
} }
if (parameters.size() != 1) { if (fragmentParameters.size() != 1) {
ErrorReporter.generateSpanError(fragmentMain.getArgs(), "instancing requires fragment function to have 1 argument"); ErrorReporter.generateSpanError(fragmentMain.getArgs(), "fragment function must have exactly one argument");
throw new RuntimeException();
} }
if (vertexParameters.size() != 1 || fragmentParameters.size() != 1) {
throw new RuntimeException();
}
interpolantName = vertexMain.getType(); interpolantName = vertexMain.getType();
vertexName = vertexParameters.get(0) vertexName = vertexParameters.get(0)
@ -59,10 +63,10 @@ public class OneShotData {
Optional<ShaderStruct> maybeVertex = file.findStruct(vertexName); Optional<ShaderStruct> maybeVertex = file.findStruct(vertexName);
if (!maybeVertex.isPresent()) if (!maybeVertex.isPresent())
ErrorReporter.generateMissingStruct(file, vertexName); ErrorReporter.generateMissingStruct(file, vertexName, "struct not defined");
if (!maybeInterpolant.isPresent()) if (!maybeInterpolant.isPresent())
ErrorReporter.generateMissingStruct(file, interpolantName); ErrorReporter.generateMissingStruct(file, interpolantName, "struct not defined");
if (!maybeVertex.isPresent() || !maybeInterpolant.isPresent()) { if (!maybeVertex.isPresent() || !maybeInterpolant.isPresent()) {
throw new RuntimeException(); throw new RuntimeException();

View file

@ -1,18 +1,17 @@
package com.jozufozu.flywheel.backend.pipeline; package com.jozufozu.flywheel.backend.pipeline;
import java.util.HashMap; import java.util.Collection;
import java.util.Map;
import java.util.Optional;
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.backend.source.SourceFile;
public class OneShotTemplate implements ITemplate { public class OneShotTemplate extends Template<OneShotProgramMetaData> {
public static final OneShotTemplate INSTANCE = new OneShotTemplate(); public static final OneShotTemplate INSTANCE = new OneShotTemplate();
public OneShotTemplate() {
private final Map<SourceFile, OneShotData> datas = new HashMap<>(); super(OneShotProgramMetaData::new);
}
@Override @Override
public void generateTemplateSource(StringBuilder builder, ShaderType type, SourceFile file) { public void generateTemplateSource(StringBuilder builder, ShaderType type, SourceFile file) {
@ -24,45 +23,42 @@ public class OneShotTemplate implements ITemplate {
} }
@Override @Override
public void attachAttributes(ProtoProgram program, SourceFile file) { public Collection<ShaderInput> getShaderInputs(SourceFile file) {
OneShotData data = getData(file); OneShotProgramMetaData data = getMetadata(file);
data.vertex.addPrefixedAttributes(program, "a_v_");
}
public OneShotData getData(SourceFile file) { return ShaderInput.fromStruct(data.vertex, "a_v_");
return datas.computeIfAbsent(file, OneShotData::new);
} }
public void vertexFooter(StringBuilder template, SourceFile file) { public void vertexFooter(StringBuilder template, SourceFile file) {
OneShotData data = getData(file); OneShotProgramMetaData data = getMetadata(file);
ITemplate.prefixFields(template, data.vertex, "attribute", "a_v_"); Template.prefixFields(template, data.vertex, "attribute", "a_v_");
ITemplate.prefixFields(template, data.interpolant, "varying", "v2f_"); Template.prefixFields(template, data.interpolant, "varying", "v2f_");
template.append("void main() {\n"); template.append("void main() {\n");
template.append(data.vertexName) template.append(data.vertexName)
.append(" v;\n"); .append(" v;\n");
ITemplate.assignFields(template, data.vertex, "v.", "a_v_"); Template.assignFields(template, data.vertex, "v.", "a_v_");
template.append(data.interpolantName) template.append(data.interpolantName)
.append(" o = ") .append(" o = ")
.append(data.vertexMain.call("v")) .append(data.vertexMain.call("v"))
.append(";\n"); .append(";\n");
ITemplate.assignFields(template, data.interpolant, "v2f_", "o."); Template.assignFields(template, data.interpolant, "v2f_", "o.");
template.append('}'); template.append('}');
} }
public void fragmentFooter(StringBuilder template, SourceFile file) { public void fragmentFooter(StringBuilder template, SourceFile file) {
OneShotData data = getData(file); OneShotProgramMetaData data = getMetadata(file);
ITemplate.prefixFields(template, data.interpolant, "varying", "v2f_"); Template.prefixFields(template, data.interpolant, "varying", "v2f_");
template.append("void main() {\n"); template.append("void main() {\n");
template.append(data.interpolant.name) template.append(data.interpolant.name)
.append(" o;\n"); .append(" o;\n");
ITemplate.assignFields(template, data.interpolant, "o.", "v2f_"); Template.assignFields(template, data.interpolant, "o.", "v2f_");
template.append(data.fragmentMain.call("o")) template.append(data.fragmentMain.call("o"))
.append(";\n"); .append(";\n");

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.backend.loading; package com.jozufozu.flywheel.backend.pipeline;
import static org.lwjgl.opengl.GL20.GL_LINK_STATUS; import static org.lwjgl.opengl.GL20.GL_LINK_STATUS;
import static org.lwjgl.opengl.GL20.GL_TRUE; import static org.lwjgl.opengl.GL20.GL_TRUE;
@ -9,53 +9,48 @@ import static org.lwjgl.opengl.GL20.glGetProgramInfoLog;
import static org.lwjgl.opengl.GL20.glGetProgrami; import static org.lwjgl.opengl.GL20.glGetProgrami;
import static org.lwjgl.opengl.GL20.glLinkProgram; import static org.lwjgl.opengl.GL20.glLinkProgram;
import java.util.EnumMap; import java.util.List;
import java.util.Map;
import java.util.function.IntConsumer;
import org.lwjgl.opengl.GL20;
import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.Backend;
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 it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.util.ResourceLocation;
public class ProtoProgram { public class ProtoProgram {
public final int program; public final int program;
public final WorldShader parent;
public ResourceLocation name;
private int attributeIndex; private int attributeIndex;
private final IntList shaders; private final List<GlShader> shaders;
public ProtoProgram() { public ProtoProgram(WorldShader parent) {
this.parent = parent;
this.program = glCreateProgram(); this.program = glCreateProgram();
shaders = new IntArrayList(2); shaders = new ObjectArrayList<>();
} }
public void attachShader(GlShader glShader) { public ProtoProgram compilePart(ShaderType type) {
glAttachShader(this.program, glShader.handle()); GlShader shader = parent.compile(type);
} attachShader(shader);
return this;
public void addAttribute(String name, int attributeCount) {
glBindAttribLocation(this.program, attributeIndex, name);
attributeIndex += attributeCount;
} }
/** /**
* Links the attached shaders to this program. * Links the attached shaders to this program.
*/ */
public ProtoProgram link(ResourceLocation name) { public ProtoProgram link() {
this.name = name;
parent.template.getShaderInputs(parent.mainFile)
.forEach(this::addAttribute);
glLinkProgram(this.program); glLinkProgram(this.program);
String log = glGetProgramInfoLog(this.program); String log = glGetProgramInfoLog(this.program);
if (!log.isEmpty()) { if (!log.isEmpty()) {
Backend.log.debug("Program link log for " + name + ": " + log); Backend.log.debug("Program link log for " + parent.name + ": " + log);
} }
int result = glGetProgrami(this.program, GL_LINK_STATUS); int result = glGetProgrami(this.program, GL_LINK_STATUS);
@ -68,7 +63,18 @@ public class ProtoProgram {
} }
public ProtoProgram deleteLinkedShaders() { public ProtoProgram deleteLinkedShaders() {
shaders.forEach((IntConsumer) GL20::glDeleteShader); shaders.forEach(GlShader::delete);
return this; return this;
} }
private void attachShader(GlShader glShader) {
shaders.add(glShader);
glAttachShader(this.program, glShader.handle());
}
private void addAttribute(ShaderInput shaderInput) {
glBindAttribLocation(this.program, attributeIndex, shaderInput.name);
attributeIndex += shaderInput.attribCount;
}
} }

View file

@ -0,0 +1,38 @@
package com.jozufozu.flywheel.backend.pipeline;
import java.util.Collection;
import java.util.stream.Collectors;
import com.jozufozu.flywheel.backend.source.parse.ShaderStruct;
import com.jozufozu.flywheel.backend.source.parse.StructField;
/**
* A single input to a shader.
*/
public class ShaderInput {
public final CharSequence name;
public final int attribCount;
public ShaderInput(CharSequence name, int attribCount) {
this.name = name;
this.attribCount = attribCount;
}
public ShaderInput withPrefix(CharSequence prefix) {
return new ShaderInput(prefix.toString() + name, attribCount);
}
public static Collection<ShaderInput> fromStruct(ShaderStruct struct, String prefix) {
return struct.getFields()
.stream()
.map(ShaderInput::from)
.map(a -> a.withPrefix(prefix))
.collect(Collectors.toList());
}
public static ShaderInput from(StructField field) {
int attributeCount = TypeHelper.getAttributeCount(field.type);
return new ShaderInput(field.name, attributeCount);
}
}

View file

@ -0,0 +1,82 @@
package com.jozufozu.flywheel.backend.pipeline;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.backend.source.parse.ShaderStruct;
import com.jozufozu.flywheel.backend.source.parse.StructField;
/**
* A class that generates glsl glue code given a SourceFile.
*
* <p>
* Shader files are written somewhat abstractly. Subclasses of Template handle those abstractions, using SourceFile
* metadata to generate shader code that OpenGL can use to call into our shader programs.
* </p>
* @param <D> Holds metadata, generates errors.
*/
public abstract class Template<D> {
private final Map<SourceFile, D> metadata = new HashMap<>();
private final Function<SourceFile, D> parser;
protected Template(Function<SourceFile, D> parser) {
this.parser = parser;
}
/**
* Generate the necessary glue code here.
*
* <p>
* See {@link InstancingTemplate} and {@link OneShotTemplate} for examples.
* </p>
* @param builder The builder to generate the source into.
* @param type The shader stage glue code is needed for.
* @param file The SourceFile with user written code.
*/
public abstract void generateTemplateSource(StringBuilder builder, ShaderType type, SourceFile file);
public abstract Collection<ShaderInput> getShaderInputs(SourceFile file);
public D getMetadata(SourceFile file) {
return metadata.computeIfAbsent(file, parser);
}
public GLSLVersion getVersion() {
return GLSLVersion.V120;
}
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

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

View file

@ -1,37 +1,35 @@
package com.jozufozu.flywheel.backend.pipeline; package com.jozufozu.flywheel.backend.pipeline;
import java.util.List; import java.util.List;
import java.util.Optional;
import com.jozufozu.flywheel.backend.FileResolution; import com.jozufozu.flywheel.backend.source.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;
import com.jozufozu.flywheel.backend.source.SourceFile;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
public class ShaderBuilder { public class WorldShader {
public final ResourceLocation name; public final ResourceLocation name;
public final ITemplate template; public final Template<?> template;
public final FileResolution header; public final CharSequence header;
public SourceFile mainFile; public SourceFile mainFile;
private GLSLVersion version;
private StringBuilder source; private CharSequence source;
private StringBuilder defines; private StringBuilder defines;
public ShaderBuilder(ResourceLocation name, ITemplate template, FileResolution header) { public WorldShader(ResourceLocation name, Template<?> template, FileResolution header) {
this.name = name; this.name = name;
this.template = template; this.template = template;
this.header = header; this.header = Optional.ofNullable(header.getFile())
.map(SourceFile::generateFinalSource)
.orElse("");
} }
public ShaderBuilder setVersion(GLSLVersion version) { public WorldShader setDefines(List<String> defs) {
this.version = version;
return this;
}
public ShaderBuilder setDefines(List<String> defs) {
defines = new StringBuilder(); defines = new StringBuilder();
for (String def : defs) { for (String def : defs) {
@ -42,35 +40,37 @@ public class ShaderBuilder {
return this; return this;
} }
public ShaderBuilder setMainSource(SourceFile file) { public WorldShader setMainSource(SourceFile file) {
if (mainFile == file) return this; if (mainFile == file) return this;
mainFile = file; mainFile = file;
source = new StringBuilder(); source = file.generateFinalSource();
file.generateFinalSource(source);
return this; return this;
} }
public GlShader compile(ResourceLocation name, ShaderType type) { public GlShader compile(ShaderType type) {
StringBuilder finalSource = new StringBuilder(); StringBuilder finalSource = new StringBuilder();
finalSource.append("#version ") finalSource.append("#version ")
.append(version) .append(template.getVersion())
.append('\n') .append('\n')
.append("#define ") .append("#define ")
.append(type.define) .append(type.define)
.append('\n') .append('\n')
.append(defines != null ? defines : ""); .append(defines != null ? defines : "")
SourceFile file = header.getFile(); .append(header)
if (file != null) { .append('\n')
file.generateFinalSource(finalSource); .append(source)
} .append('\n');
mainFile.generateFinalSource(finalSource);
template.generateTemplateSource(finalSource, type, mainFile); template.generateTemplateSource(finalSource, type, mainFile);
return new GlShader(name, type, finalSource); return new GlShader(name, type, finalSource);
} }
public ProtoProgram createProgram() {
return new ProtoProgram(this);
}
} }

View file

@ -1,22 +1,15 @@
package com.jozufozu.flywheel.backend.pipeline; package com.jozufozu.flywheel.backend.pipeline;
import static org.lwjgl.opengl.GL11.GL_TRUE;
import static org.lwjgl.opengl.GL20.GL_LINK_STATUS;
import static org.lwjgl.opengl.GL20.glGetProgramInfoLog; import static org.lwjgl.opengl.GL20.glGetProgramInfoLog;
import static org.lwjgl.opengl.GL20.glGetProgrami;
import java.util.List; import java.util.List;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.lwjgl.opengl.GL20; import com.jozufozu.flywheel.backend.source.FileResolution;
import com.jozufozu.flywheel.backend.source.ShaderSources;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.FileResolution;
import com.jozufozu.flywheel.backend.ShaderSources;
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.backend.source.SourceFile;
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;
@ -32,10 +25,10 @@ public class WorldShaderPipeline<P extends WorldProgram> implements IShaderPipel
private final ExtensibleGlProgram.Factory<P> factory; private final ExtensibleGlProgram.Factory<P> factory;
private final ITemplate template; private final Template<?> template;
private final FileResolution header; private final FileResolution header;
public WorldShaderPipeline(ShaderSources sources, ExtensibleGlProgram.Factory<P> factory, ITemplate template, FileResolution header) { public WorldShaderPipeline(ShaderSources sources, ExtensibleGlProgram.Factory<P> factory, Template<?> template, FileResolution header) {
this.sources = sources; this.sources = sources;
this.factory = factory; this.factory = factory;
this.template = template; this.template = template;
@ -44,48 +37,40 @@ public class WorldShaderPipeline<P extends WorldProgram> implements IShaderPipel
public IMultiProgram<P> compile(ProgramSpec spec) { public IMultiProgram<P> compile(ProgramSpec spec) {
SourceFile file = sources.source(spec.vert); SourceFile file = sources.source(spec.source);
return compile(spec.name, file, spec.getStates()); return compile(spec.name, file, spec.getStates());
} }
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, template, header) WorldShader shader = new WorldShader(name, template, header)
.setMainSource(file) .setMainSource(file);
.setVersion(GLSLVersion.V110);
GameStateProgram.Builder<P> builder = GameStateProgram.builder(compile(shader, name, null)); GameStateProgram.Builder<P> builder = GameStateProgram.builder(compile(shader, null));
for (ProgramState variant : variants) { for (ProgramState variant : variants) {
builder.withVariant(variant.getContext(), compile(shader, name, variant)); builder.withVariant(variant.getContext(), compile(shader, variant));
} }
return builder.build(); return builder.build();
} }
private P compile(ShaderBuilder shader, ResourceLocation name, @Nullable ProgramState variant) { private P compile(WorldShader shader, @Nullable ProgramState variant) {
if (variant != null) { if (variant != null) {
shader.setDefines(variant.getDefines()); shader.setDefines(variant.getDefines());
} }
GlShader vertex = shader.compile(name, ShaderType.VERTEX); ProtoProgram program = shader.createProgram()
GlShader fragment = shader.compile(name, ShaderType.FRAGMENT); .compilePart(ShaderType.VERTEX)
.compilePart(ShaderType.FRAGMENT)
ProtoProgram program = new ProtoProgram(); .link()
.deleteLinkedShaders();
program.attachShader(vertex);
program.attachShader(fragment);
template.attachAttributes(program, shader.mainFile);
program.link(name);
program.deleteLinkedShaders();
if (variant != null) { if (variant != null) {
return factory.create(name, program.program, variant.getExtensions()); return factory.create(shader.name, program.program, variant.getExtensions());
} else { } else {
return factory.create(name, program.program, null); return factory.create(shader.name, program.program, null);
} }
} }
} }

View file

@ -1,50 +0,0 @@
package com.jozufozu.flywheel.backend.pipeline.error;
import java.util.Optional;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.pipeline.SourceFile;
import com.jozufozu.flywheel.backend.pipeline.parse.ShaderStruct;
import com.jozufozu.flywheel.backend.pipeline.span.Span;
public class ErrorReporter {
public static void generateSpanError(Span span, String message) {
SourceFile file = span.getSourceFile();
ErrorBuilder builder = new ErrorBuilder();
CharSequence error = builder.error(message)
.in(file)
.pointAt(span, 2)
.build();
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

@ -0,0 +1,86 @@
package com.jozufozu.flywheel.backend.source;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.source.error.ErrorBuilder;
import com.jozufozu.flywheel.backend.source.span.Span;
import net.minecraft.util.ResourceLocation;
/**
* A reference to a source file that might not be loaded when the owning object is created.
*
* <p>
* FileResolutions are used primarily while parsing import statements. {@link FileResolution#file} is initially
* null, but will be populated later on, after <em>all</em> SourceFiles are loaded (assuming
* {@link FileResolution#fileLoc} references an actual file).
* </p>
*/
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;
this.fileLoc = fileLoc;
}
public ResourceLocation getFileLoc() {
return fileLoc;
}
@Nullable
public SourceFile getFile() {
return file;
}
/**
* Store the given span so this resolution can know all the places that reference the file.
*
* <p>
* Used for error reporting.
* </p>
* @param span A span where this file is referenced.
*/
public void addSpan(Span span) {
foundSpans.add(span);
}
/**
* Check to see if this file actually resolves to something.
*
* <p>
* Called after all files are loaded. If we can't find the file here, it doesn't exist.
* </p>
*/
void resolve() {
try {
file = this.parent.source(fileLoc);
} catch (RuntimeException error) {
ErrorBuilder builder = new ErrorBuilder();
builder.error(String.format("could not find source for file %s", fileLoc));
// print the location of all places where this file was referenced
for (Span span : foundSpans) {
builder.in(span.getSourceFile())
.pointAt(span, 2);
}
Backend.log.error(builder.build());
}
}
void invalidate() {
foundSpans.clear();
file = null;
}
}

View file

@ -0,0 +1,42 @@
package com.jozufozu.flywheel.backend.source;
import java.util.Collection;
import java.util.Map;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.jozufozu.flywheel.backend.source.parse.ShaderFunction;
import com.jozufozu.flywheel.backend.source.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> knownStructs = MultimapBuilder.hashKeys()
.hashSetValues()
.build();
private final Multimap<String, ShaderFunction> knownFunctions = MultimapBuilder.hashKeys()
.hashSetValues()
.build();
public Index(Map<ResourceLocation, SourceFile> sources) {
Collection<SourceFile> files = sources.values();
for (SourceFile file : files) {
file.getStructs().forEach(knownStructs::put);
file.getFunctions().forEach(knownFunctions::put);
}
}
public Collection<ShaderStruct> getStructDefinitionsMatching(CharSequence name) {
return knownStructs.get(name.toString());
}
public Collection<ShaderFunction> getFunctionDefinitionsMatching(CharSequence name) {
return knownFunctions.get(name.toString());
}
}

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.backend.loading; package com.jozufozu.flywheel.backend.source;
public class ShaderLoadingException extends RuntimeException { public class ShaderLoadingException extends RuntimeException {
public ShaderLoadingException() { public ShaderLoadingException() {

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.backend; package com.jozufozu.flywheel.backend.source;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -7,16 +7,14 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Predicate; import java.util.function.Predicate;
import javax.annotation.Nonnull;
import com.google.common.collect.Lists; 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.backend.gl.shader.ShaderType; 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.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.backend.loading.ShaderLoadingException;
import com.jozufozu.flywheel.backend.pipeline.SourceFile;
import com.jozufozu.flywheel.core.crumbling.CrumblingRenderer; import com.jozufozu.flywheel.core.crumbling.CrumblingRenderer;
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;
@ -36,6 +34,13 @@ import net.minecraftforge.resource.IResourceType;
import net.minecraftforge.resource.ISelectiveResourceReloadListener; import net.minecraftforge.resource.ISelectiveResourceReloadListener;
import net.minecraftforge.resource.VanillaResourceType; 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 ShaderSources implements ISelectiveResourceReloadListener { public class ShaderSources implements ISelectiveResourceReloadListener {
public static final String SHADER_DIR = "flywheel/shaders/"; public static final String SHADER_DIR = "flywheel/shaders/";
public static final String PROGRAM_DIR = "flywheel/programs/"; public static final String PROGRAM_DIR = "flywheel/programs/";
@ -70,7 +75,7 @@ public class ShaderSources implements ISelectiveResourceReloadListener {
} }
public FileResolution resolveFile(ResourceLocation fileLoc) { public FileResolution resolveFile(ResourceLocation fileLoc) {
return resolutions.computeIfAbsent(fileLoc, FileResolution::new); return resolutions.computeIfAbsent(fileLoc, l -> new FileResolution(this, l));
} }
@Deprecated @Deprecated
@ -86,17 +91,17 @@ public class ShaderSources implements ISelectiveResourceReloadListener {
if (backend.gl20()) { if (backend.gl20()) {
shouldCrash = false; shouldCrash = false;
backend.clearContexts(); backend._clearContexts();
resolutions.values().forEach(FileResolution::invalidate);
shaderSources.clear();
ModLoader.get() ModLoader.get()
.postEvent(new GatherContextEvent(backend)); .postEvent(new GatherContextEvent(backend));
resolutions.values().forEach(FileResolution::invalidate);
loadProgramSpecs(manager); loadProgramSpecs(manager);
loadShaderSources(manager); loadShaderSources(manager);
for (FileResolution resolution : resolutions.values()) { for (FileResolution resolution : resolutions.values()) {
resolution.resolve(this); resolution.resolve();
} }
for (IShaderContext<?> context : backend.allContexts()) { for (IShaderContext<?> context : backend.allContexts()) {

View file

@ -1,29 +1,22 @@
package com.jozufozu.flywheel.backend.pipeline; package com.jozufozu.flywheel.backend.source;
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.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.source.parse.Import;
import com.jozufozu.flywheel.backend.ShaderSources; import com.jozufozu.flywheel.backend.source.parse.ShaderFunction;
import com.jozufozu.flywheel.backend.pipeline.parse.Import; import com.jozufozu.flywheel.backend.source.parse.ShaderStruct;
import com.jozufozu.flywheel.backend.pipeline.parse.ShaderFunction; import com.jozufozu.flywheel.backend.source.span.CharPos;
import com.jozufozu.flywheel.backend.pipeline.parse.ShaderStruct; import com.jozufozu.flywheel.backend.source.span.ErrorSpan;
import com.jozufozu.flywheel.backend.pipeline.span.CharPos; import com.jozufozu.flywheel.backend.source.span.Span;
import com.jozufozu.flywheel.backend.pipeline.span.ErrorSpan; import com.jozufozu.flywheel.backend.source.span.StringSpan;
import com.jozufozu.flywheel.backend.pipeline.span.Span;
import com.jozufozu.flywheel.backend.pipeline.span.StringSpan;
import com.jozufozu.flywheel.util.StringUtil; import com.jozufozu.flywheel.util.StringUtil;
import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntArrayList;
@ -142,6 +135,12 @@ public class SourceFile {
return "#use " + '"' + name + '"'; return "#use " + '"' + name + '"';
} }
public CharSequence generateFinalSource() {
StringBuilder builder = new StringBuilder();
generateFinalSource(builder);
return builder;
}
public void generateFinalSource(StringBuilder source) { public void generateFinalSource(StringBuilder source) {
for (Import include : getIncludes()) { for (Import include : getIncludes()) {
SourceFile file = include.getFile(); SourceFile file = include.getFile();

View file

@ -1,11 +1,7 @@
package com.jozufozu.flywheel.backend.pipeline.error; package com.jozufozu.flywheel.backend.source.error;
import java.util.Optional; import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.backend.source.span.Span;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.pipeline.SourceFile;
import com.jozufozu.flywheel.backend.pipeline.span.Span;
import com.jozufozu.flywheel.util.FlwUtil; import com.jozufozu.flywheel.util.FlwUtil;
public class ErrorBuilder { public class ErrorBuilder {
@ -40,6 +36,11 @@ public class ErrorBuilder {
return line(String.valueOf(no), content); return line(String.valueOf(no), content);
} }
public ErrorBuilder numberedLine(int no, int width, CharSequence content) {
String fmt = "%1$" + width + 's';
return line(String.format(fmt, no), content);
}
public ErrorBuilder line(CharSequence leftColumn, CharSequence rightColumn) { public ErrorBuilder line(CharSequence leftColumn, CharSequence rightColumn) {
internal.append(leftColumn) internal.append(leftColumn)
@ -54,10 +55,10 @@ public class ErrorBuilder {
return this; return this;
} }
public ErrorBuilder hintIncludeFor(Span span) { public ErrorBuilder hintIncludeFor(Span span, CharSequence msg) {
if (span == null) return this; if (span == null) return this;
hint("add " + span.getSourceFile().importStatement()) hint("add " + span.getSourceFile().importStatement() + " " + msg)
.in(span.getSourceFile()) .in(span.getSourceFile())
.pointAt(span, 1); .pointAt(span, 1);
@ -77,14 +78,13 @@ public class ErrorBuilder {
int digits = FlwUtil.numDigits(lastLine); 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();
for (int i = firstLine; i <= lastLine; i++) { for (int i = firstLine; i <= lastLine; i++) {
CharSequence line = file.getLine(i); CharSequence line = file.getLine(i);
numberedLine(i + 1, line); numberedLine(i + 1, digits, line);
if (i == spanLine) { if (i == spanLine) {
line(FlwUtil.repeatChar(' ', digits), generateUnderline(firstCol, lastCol)); line(FlwUtil.repeatChar(' ', digits), generateUnderline(firstCol, lastCol));

View file

@ -0,0 +1,73 @@
package com.jozufozu.flywheel.backend.source.error;
import java.util.Optional;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.backend.source.parse.ShaderFunction;
import com.jozufozu.flywheel.backend.source.parse.ShaderStruct;
import com.jozufozu.flywheel.backend.source.span.Span;
public class ErrorReporter {
public static void generateSpanError(Span span, String message) {
SourceFile file = span.getSourceFile();
ErrorBuilder builder = new ErrorBuilder();
CharSequence error = builder.error(message)
.in(file)
.pointAt(span, 2)
.build();
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, CharSequence msg) {
generateMissingStruct(file, vertexName, msg, "");
}
public static void generateMissingStruct(SourceFile file, Span vertexName, CharSequence msg, CharSequence hint) {
Optional<Span> span = file.parent.index.getStructDefinitionsMatching(vertexName)
.stream()
.findFirst()
.map(ShaderStruct::getName);
ErrorBuilder builder = new ErrorBuilder();
ErrorBuilder error = builder.error(msg)
.in(file)
.pointAt(vertexName, 2)
.hintIncludeFor(span.orElse(null), hint);
Backend.log.error(error.build());
}
public static void generateMissingFunction(SourceFile file, CharSequence functionName, CharSequence msg) {
generateMissingFunction(file, functionName, msg, "");
}
public static void generateMissingFunction(SourceFile file, CharSequence functionName, CharSequence msg, CharSequence hint) {
Optional<Span> span = file.parent.index.getFunctionDefinitionsMatching(functionName)
.stream()
.findFirst()
.map(ShaderFunction::getName);
ErrorBuilder builder = new ErrorBuilder();
ErrorBuilder error = builder.error(msg)
.in(file)
.hintIncludeFor(span.orElse(null), hint);
Backend.log.error(error.build());
}
}

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.backend.source.error;
import javax.annotation.ParametersAreNonnullByDefault;
import mcp.MethodsReturnNonnullByDefault;

View file

@ -1,5 +1,5 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.backend.loading; package com.jozufozu.flywheel.backend.source;
import javax.annotation.ParametersAreNonnullByDefault; import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -1,6 +1,6 @@
package com.jozufozu.flywheel.backend.pipeline.parse; package com.jozufozu.flywheel.backend.source.parse;
import com.jozufozu.flywheel.backend.pipeline.span.Span; import com.jozufozu.flywheel.backend.source.span.Span;
public abstract class AbstractShaderElement { public abstract class AbstractShaderElement {

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.backend.pipeline.parse; package com.jozufozu.flywheel.backend.source.parse;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -6,11 +6,11 @@ import java.util.Optional;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.FileResolution; import com.jozufozu.flywheel.backend.source.FileResolution;
import com.jozufozu.flywheel.backend.ShaderSources; import com.jozufozu.flywheel.backend.source.ShaderSources;
import com.jozufozu.flywheel.backend.pipeline.error.ErrorReporter; import com.jozufozu.flywheel.backend.source.error.ErrorReporter;
import com.jozufozu.flywheel.backend.pipeline.SourceFile; import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.backend.pipeline.span.Span; import com.jozufozu.flywheel.backend.source.span.Span;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;

View file

@ -1,11 +1,11 @@
package com.jozufozu.flywheel.backend.pipeline.parse; package com.jozufozu.flywheel.backend.source.parse;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.pipeline.span.Span; import com.jozufozu.flywheel.backend.source.span.Span;
public class ShaderFunction extends AbstractShaderElement { public class ShaderFunction extends AbstractShaderElement {

View file

@ -1,13 +1,11 @@
package com.jozufozu.flywheel.backend.pipeline.parse; package com.jozufozu.flywheel.backend.source.parse;
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.ProtoProgram; import com.jozufozu.flywheel.backend.source.span.Span;
import com.jozufozu.flywheel.backend.loading.TypeHelper;
import com.jozufozu.flywheel.backend.pipeline.span.Span;
public class ShaderStruct extends AbstractShaderElement { public class ShaderStruct extends AbstractShaderElement {
@ -65,14 +63,6 @@ public class ShaderStruct extends AbstractShaderElement {
return fields.build(); return fields.build();
} }
public void addPrefixedAttributes(ProtoProgram builder, String prefix) {
for (StructField field : fields) {
int attributeCount = TypeHelper.getAttributeCount(field.type);
builder.addAttribute(prefix + field.name, attributeCount);
}
}
@Override @Override
public String toString() { public String toString() {
return "struct " + name; return "struct " + name;

View file

@ -1,9 +1,8 @@
package com.jozufozu.flywheel.backend.pipeline.parse; package com.jozufozu.flywheel.backend.source.parse;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import com.jozufozu.flywheel.backend.pipeline.error.ErrorReporter; import com.jozufozu.flywheel.backend.source.span.Span;
import com.jozufozu.flywheel.backend.pipeline.span.Span;
public class StructField extends AbstractShaderElement { public class StructField extends AbstractShaderElement {
public static final Pattern fieldPattern = Pattern.compile("(\\S+)\\s*(\\S+);"); public static final Pattern fieldPattern = Pattern.compile("(\\S+)\\s*(\\S+);");

View file

@ -1,7 +1,6 @@
package com.jozufozu.flywheel.backend.pipeline.parse; package com.jozufozu.flywheel.backend.source.parse;
import com.jozufozu.flywheel.backend.pipeline.error.ErrorReporter; import com.jozufozu.flywheel.backend.source.span.Span;
import com.jozufozu.flywheel.backend.pipeline.span.Span;
public class Variable extends AbstractShaderElement { public class Variable extends AbstractShaderElement {

View file

@ -1,6 +1,6 @@
@ParametersAreNonnullByDefault @ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.backend.pipeline.parse; package com.jozufozu.flywheel.backend.source.parse;
import javax.annotation.ParametersAreNonnullByDefault; import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.backend.pipeline.span; package com.jozufozu.flywheel.backend.source.span;
/** /**
* A position in a file. * A position in a file.

View file

@ -1,6 +1,6 @@
package com.jozufozu.flywheel.backend.pipeline.span; package com.jozufozu.flywheel.backend.source.span;
import com.jozufozu.flywheel.backend.pipeline.SourceFile; import com.jozufozu.flywheel.backend.source.SourceFile;
/** /**
* Represents a (syntactically) malformed segment of code. * Represents a (syntactically) malformed segment of code.

View file

@ -1,8 +1,8 @@
package com.jozufozu.flywheel.backend.pipeline.span; package com.jozufozu.flywheel.backend.source.span;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import com.jozufozu.flywheel.backend.pipeline.SourceFile; import com.jozufozu.flywheel.backend.source.SourceFile;
/** /**
* A span of code in a {@link SourceFile}. * A span of code in a {@link SourceFile}.

View file

@ -1,6 +1,6 @@
package com.jozufozu.flywheel.backend.pipeline.span; package com.jozufozu.flywheel.backend.source.span;
import com.jozufozu.flywheel.backend.pipeline.SourceFile; import com.jozufozu.flywheel.backend.source.SourceFile;
public class StringSpan extends Span { public class StringSpan extends Span {

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.backend.source.span;
import javax.annotation.ParametersAreNonnullByDefault;
import mcp.MethodsReturnNonnullByDefault;

View file

@ -2,7 +2,7 @@ package com.jozufozu.flywheel.core;
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.source.FileResolution;
import com.jozufozu.flywheel.backend.ResourceUtil; import com.jozufozu.flywheel.backend.ResourceUtil;
import com.jozufozu.flywheel.backend.SpecMetaRegistry; import com.jozufozu.flywheel.backend.SpecMetaRegistry;
import com.jozufozu.flywheel.backend.pipeline.IShaderPipeline; import com.jozufozu.flywheel.backend.pipeline.IShaderPipeline;
@ -40,8 +40,8 @@ public class Contexts {
IShaderPipeline<CrumblingProgram> crumblingPipeline = new WorldShaderPipeline<>(backend.sources, CrumblingProgram::new, InstancingTemplate.INSTANCE, crumblingBuiltins); 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<WorldProgram> worldPipeline = new WorldShaderPipeline<>(backend.sources, WorldProgram::new, InstancingTemplate.INSTANCE, worldBuiltins);
CRUMBLING = backend.register(new WorldContext<>(backend, crumblingPipeline).withName(Names.CRUMBLING)); CRUMBLING = backend.register(WorldContext.builder(backend, Names.CRUMBLING).build(crumblingPipeline));
WORLD = backend.register(new WorldContext<>(backend, worldPipeline).withName(Names.WORLD)); WORLD = backend.register(WorldContext.builder(backend, Names.WORLD).build(worldPipeline));
} }
public static class Names { public static class Names {

View file

@ -1,40 +1,33 @@
package com.jozufozu.flywheel.core; package com.jozufozu.flywheel.core;
import java.util.HashMap;
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.ShaderContext; import com.jozufozu.flywheel.backend.IShaderContext;
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.core.shader.IMultiProgram;
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;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
public class WorldContext<P extends WorldProgram> extends ShaderContext<P> { public class WorldContext<P extends WorldProgram> implements IShaderContext<P> {
protected ResourceLocation name; public final Backend backend;
protected Supplier<Stream<ResourceLocation>> specStream; protected final Map<ResourceLocation, IMultiProgram<P>> programs = new HashMap<>();
protected final ResourceLocation name;
protected final Supplier<Stream<ResourceLocation>> specStream;
public final IShaderPipeline<P> pipeline; public final IShaderPipeline<P> pipeline;
public WorldContext(Backend backend, IShaderPipeline<P> factory) { public WorldContext(Backend backend, ResourceLocation name, Supplier<Stream<ResourceLocation>> specStream, IShaderPipeline<P> pipeline) {
super(backend); this.backend = backend;
this.pipeline = factory;
specStream = () -> backend.allMaterials()
.stream()
.map(MaterialSpec::getProgramName);
}
public WorldContext<P> withName(ResourceLocation name) {
this.name = name; this.name = name;
return this;
}
public WorldContext<P> withSpecStream(Supplier<Stream<ResourceLocation>> specStream) {
this.specStream = specStream; this.specStream = specStream;
return this; this.pipeline = pipeline;
} }
@Override @Override
@ -59,4 +52,45 @@ public class WorldContext<P extends WorldProgram> extends ShaderContext<P> {
backend.sources.notifyError(); backend.sources.notifyError();
} }
} }
@Override
public Supplier<P> getProgramSupplier(ResourceLocation spec) {
return programs.get(spec);
}
@Override
public void delete() {
programs.values()
.forEach(IMultiProgram::delete);
programs.clear();
}
public static Builder builder(Backend backend, ResourceLocation name) {
return new Builder(backend, name);
}
public static class Builder {
private final Backend backend;
private final ResourceLocation name;
private Supplier<Stream<ResourceLocation>> specStream;
public Builder(Backend backend, ResourceLocation name) {
this.backend = backend;
this.name = name;
}
public Builder setSpecStream(Supplier<Stream<ResourceLocation>> specStream) {
this.specStream = specStream;
return this;
}
public <P extends WorldProgram> WorldContext<P> build(IShaderPipeline<P> pipeline) {
if (specStream == null) {
specStream = () -> backend.allMaterials()
.stream()
.map(MaterialSpec::getProgramName);
}
return new WorldContext<>(backend, name, specStream, pipeline);
}
}
} }

View file

@ -1,56 +1,54 @@
package com.jozufozu.flywheel.core.shader.spec; package com.jozufozu.flywheel.core.shader.spec;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import com.jozufozu.flywheel.backend.source.SourceFile;
import com.mojang.serialization.Codec; import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder; import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
/**
* An object describing a shader program that can be loaded by flywheel.
*
* <p>
* These are defined through json. All ProgramSpecs in <code>assets/modid/flywheel/programs</code> are parsed and
* processed. One ProgramSpec typically specifies one "material" that can be used in game to render things.
* </p>
* <p>
* All shader source files in <code>assets/modid/flywheel/shaders</code> are completely loaded and parsed into
* {@link SourceFile SourceFiles}, but not compiled until one of them is
* referenced by a ProgramSpec.
* </p>
*/
public class ProgramSpec { public class ProgramSpec {
// TODO: Block model style inheritance? // TODO: Block model style inheritance?
public static final Codec<ProgramSpec> CODEC = RecordCodecBuilder.create(instance -> instance.group( public static final Codec<ProgramSpec> CODEC = RecordCodecBuilder.create(instance -> instance.group(
ResourceLocation.CODEC.fieldOf("vert") ResourceLocation.CODEC.fieldOf("source")
.forGetter(ProgramSpec::getVert), .forGetter(ProgramSpec::getSource),
ResourceLocation.CODEC.fieldOf("frag")
.forGetter(ProgramSpec::getFrag),
ProgramState.CODEC.listOf() ProgramState.CODEC.listOf()
.optionalFieldOf("states", Collections.emptyList()) .optionalFieldOf("states", Collections.emptyList())
.forGetter(ProgramSpec::getStates)) .forGetter(ProgramSpec::getStates))
.apply(instance, ProgramSpec::new)); .apply(instance, ProgramSpec::new));
public ResourceLocation name; public ResourceLocation name;
public final ResourceLocation vert; public final ResourceLocation source;
public final ResourceLocation frag;
public final List<ProgramState> states; public final List<ProgramState> states;
public ProgramSpec(ResourceLocation vert, ResourceLocation frag, List<ProgramState> states) { public ProgramSpec(ResourceLocation source, List<ProgramState> states) {
this.vert = vert; this.source = source;
this.frag = frag;
this.states = states; this.states = states;
} }
public ProgramSpec(ResourceLocation name, ResourceLocation vert, ResourceLocation frag) {
this.name = name;
this.vert = vert;
this.frag = frag;
this.states = new ArrayList<>();
}
public void setName(ResourceLocation name) { public void setName(ResourceLocation name) {
this.name = name; this.name = name;
} }
public ResourceLocation getVert() { public ResourceLocation getSource() {
return vert; return source;
}
public ResourceLocation getFrag() {
return frag;
} }
public List<ProgramState> getStates() { public List<ProgramState> getStates() {

View file

@ -95,6 +95,9 @@ public class RenderHooksMixin {
// Instancing // Instancing
/**
* This gets called when a block is marked for rerender by vanilla.
*/
@Inject(at = @At("TAIL"), method = "setBlockDirty(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/BlockState;)V") @Inject(at = @At("TAIL"), method = "setBlockDirty(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/BlockState;)V")
private void checkUpdate(BlockPos pos, BlockState lastState, BlockState newState, CallbackInfo ci) { private void checkUpdate(BlockPos pos, BlockState lastState, BlockState newState, CallbackInfo ci) {
InstancedRenderDispatcher.getTiles(level) InstancedRenderDispatcher.getTiles(level)

View file

@ -1,6 +1,5 @@
{ {
"vert": "flywheel:model.vert", "source": "flywheel:model.vert",
"frag": "flywheel:block.frag",
"states": [ "states": [
{ {
"when": { "when": {

View file

@ -1,6 +1,5 @@
{ {
"vert": "flywheel:oriented.vert", "source": "flywheel:oriented.vert",
"frag": "flywheel:block.frag",
"states": [ "states": [
{ {
"when": { "when": {