Separate vertex and fragment shaders and templates

This commit is contained in:
Jozufozu 2022-01-10 14:38:26 -08:00
parent 12cab85f69
commit 2d63d8c7db
28 changed files with 379 additions and 400 deletions

View file

@ -32,6 +32,8 @@ public interface VertexType {
*/ */
VertexList createReader(ByteBuffer buffer, int vertexCount); VertexList createReader(ByteBuffer buffer, int vertexCount);
String getShaderHeader();
default int getStride() { default int getStride() {
return getLayout().getStride(); return getLayout().getStride();
} }
@ -39,6 +41,4 @@ public interface VertexType {
default int byteOffset(int vertexIndex) { default int byteOffset(int vertexIndex) {
return getStride() * vertexIndex; return getStride() * vertexIndex;
} }
String writeShaderHeader();
} }

View file

@ -5,7 +5,6 @@ import org.lwjgl.opengl.GL20;
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.source.ShaderLoadingException; import com.jozufozu.flywheel.backend.source.ShaderLoadingException;
import com.jozufozu.flywheel.core.compile.ShaderCompiler;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
@ -14,8 +13,8 @@ public class GlShader extends GlObject {
public final ResourceLocation name; public final ResourceLocation name;
public final ShaderType type; public final ShaderType type;
public GlShader(ShaderCompiler env, ShaderType type, String source) { public GlShader(ResourceLocation name, ShaderType type, String source) {
name = env.name; this.name = name;
this.type = type; this.type = type;
int handle = GL20.glCreateShader(type.glEnum); int handle = GL20.glCreateShader(type.glEnum);
@ -24,9 +23,9 @@ public class GlShader extends GlObject {
String log = GL20.glGetShaderInfoLog(handle); String log = GL20.glGetShaderInfoLog(handle);
if (!log.isEmpty()) { // if (!log.isEmpty()) {
env.printShaderInfoLog(source, log, this.name); // env.printShaderInfoLog(source, log, this.name);
} // }
if (GL20.glGetShaderi(handle, GL20.GL_COMPILE_STATUS) != GL20.GL_TRUE) { if (GL20.glGetShaderi(handle, GL20.GL_COMPILE_STATUS) != GL20.GL_TRUE) {
throw new ShaderLoadingException("Could not compile " + name + ". See log for details."); throw new ShaderLoadingException("Could not compile " + name + ". See log for details.");

View file

@ -3,15 +3,12 @@ package com.jozufozu.flywheel.backend.instancing.instancing;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import com.jozufozu.flywheel.api.MaterialGroup; import com.jozufozu.flywheel.api.MaterialGroup;
import com.jozufozu.flywheel.backend.RenderLayer; import com.jozufozu.flywheel.backend.RenderLayer;
import com.jozufozu.flywheel.backend.gl.GlVertexArray;
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType;
import com.jozufozu.flywheel.backend.instancing.Engine; import com.jozufozu.flywheel.backend.instancing.Engine;
import com.jozufozu.flywheel.backend.instancing.TaskEngine; import com.jozufozu.flywheel.backend.instancing.TaskEngine;
import com.jozufozu.flywheel.core.compile.ProgramCompiler; import com.jozufozu.flywheel.core.compile.ProgramCompiler;
@ -24,7 +21,6 @@ import net.minecraft.client.Camera;
import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.RenderType;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i; import net.minecraft.core.Vec3i;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
public class InstancingEngine<P extends WorldProgram> implements Engine { public class InstancingEngine<P extends WorldProgram> implements Engine {

View file

@ -16,7 +16,7 @@ import com.jozufozu.flywheel.backend.source.error.lines.SourceLine;
import com.jozufozu.flywheel.backend.source.error.lines.SpanHighlightLine; import com.jozufozu.flywheel.backend.source.error.lines.SpanHighlightLine;
import com.jozufozu.flywheel.backend.source.error.lines.TextLine; import com.jozufozu.flywheel.backend.source.error.lines.TextLine;
import com.jozufozu.flywheel.backend.source.span.Span; import com.jozufozu.flywheel.backend.source.span.Span;
import com.jozufozu.flywheel.core.compile.ShaderCompiler; import com.jozufozu.flywheel.core.compile.FileIndex;
import com.jozufozu.flywheel.util.FlwUtil; import com.jozufozu.flywheel.util.FlwUtil;
public class ErrorBuilder { public class ErrorBuilder {
@ -45,7 +45,7 @@ public class ErrorBuilder {
} }
@Nullable @Nullable
public static ErrorBuilder fromLogLine(ShaderCompiler env, String s) { public static ErrorBuilder fromLogLine(FileIndex env, String s) {
Matcher matcher = ERROR_LINE.matcher(s); Matcher matcher = ERROR_LINE.matcher(s);
if (matcher.find()) { if (matcher.find()) {

View file

@ -27,8 +27,8 @@ public class Contexts {
FileResolution worldBuiltins = Resolver.INSTANCE.get(ResourceUtil.subPath(Names.WORLD, ".glsl")); FileResolution worldBuiltins = Resolver.INSTANCE.get(ResourceUtil.subPath(Names.WORLD, ".glsl"));
FileResolution crumblingBuiltins = Resolver.INSTANCE.get(ResourceUtil.subPath(Names.CRUMBLING, ".glsl")); FileResolution crumblingBuiltins = Resolver.INSTANCE.get(ResourceUtil.subPath(Names.CRUMBLING, ".glsl"));
WORLD = Templates.INSTANCING.programCompiler(WorldProgram::new, worldBuiltins); WORLD = ProgramCompiler.create(Templates.INSTANCING, WorldProgram::new, worldBuiltins);
CRUMBLING = Templates.INSTANCING.programCompiler(CrumblingProgram::new, crumblingBuiltins); CRUMBLING = ProgramCompiler.create(Templates.INSTANCING, CrumblingProgram::new, crumblingBuiltins);
} }
public static class Names { public static class Names {

View file

@ -1,12 +1,14 @@
package com.jozufozu.flywheel.core; package com.jozufozu.flywheel.core;
import com.jozufozu.flywheel.backend.gl.GLSLVersion; import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.core.compile.FragmentTemplateData;
import com.jozufozu.flywheel.core.compile.InstancingTemplateData; import com.jozufozu.flywheel.core.compile.InstancingTemplateData;
import com.jozufozu.flywheel.core.compile.OneShotTemplateData; import com.jozufozu.flywheel.core.compile.OneShotTemplateData;
import com.jozufozu.flywheel.core.compile.Template; import com.jozufozu.flywheel.core.compile.Template;
public class Templates { public class Templates {
public static final Template INSTANCING = new Template(GLSLVersion.V330, InstancingTemplateData::new); public static final Template<InstancingTemplateData> INSTANCING = new Template<>(GLSLVersion.V330, InstancingTemplateData::new);
public static final Template ONE_SHOT = new Template(GLSLVersion.V150, OneShotTemplateData::new); public static final Template<OneShotTemplateData> ONE_SHOT = new Template<>(GLSLVersion.V150, OneShotTemplateData::new);
public static final Template<FragmentTemplateData> FRAGMENT = new Template<>(GLSLVersion.V150, FragmentTemplateData::new);
} }

View file

@ -1,6 +1,7 @@
package com.jozufozu.flywheel.core.compile; package com.jozufozu.flywheel.core.compile;
import com.jozufozu.flywheel.backend.source.SourceFile; import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.backend.source.span.Span;
public interface FileIndex { public interface FileIndex {
/** /**
@ -10,4 +11,10 @@ public interface FileIndex {
* @return A file ID unique to the given sourceFile. * @return A file ID unique to the given sourceFile.
*/ */
int getFileID(SourceFile sourceFile); int getFileID(SourceFile sourceFile);
SourceFile getFile(int fileID);
default Span getLineSpan(int fileId, int lineNo) {
return getFile(fileId).getLineSpanNoWhitespace(lineNo);
}
} }

View file

@ -0,0 +1,77 @@
package com.jozufozu.flywheel.core.compile;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.backend.source.error.ErrorBuilder;
import com.jozufozu.flywheel.backend.source.error.ErrorReporter;
import net.minecraft.resources.ResourceLocation;
public class FileIndexImpl implements FileIndex {
public final List<SourceFile> files = new ArrayList<>();
/**
* Returns an arbitrary file ID for use this compilation context, or generates one if missing.
* @param sourceFile The file to retrieve the ID for.
* @return A file ID unique to the given sourceFile.
*/
@Override
public int getFileID(SourceFile sourceFile) {
int i = files.indexOf(sourceFile);
if (i != -1) {
return i;
}
int size = files.size();
files.add(sourceFile);
return size;
}
@Override
public SourceFile getFile(int fileId) {
return files.get(fileId);
}
public void printShaderInfoLog(String source, String log, ResourceLocation name) {
List<String> lines = log.lines()
.toList();
boolean needsSourceDump = false;
StringBuilder errors = new StringBuilder();
for (String line : lines) {
ErrorBuilder builder = parseCompilerError(line);
if (builder != null) {
errors.append(builder.build());
} else {
errors.append(line).append('\n');
needsSourceDump = true;
}
}
Backend.LOGGER.error("Errors compiling '" + name + "': \n" + errors);
if (needsSourceDump) {
// TODO: generated code gets its own "file"
ErrorReporter.printLines(source);
}
}
@Nullable
private ErrorBuilder parseCompilerError(String line) {
try {
ErrorBuilder error = ErrorBuilder.fromLogLine(this, line);
if (error != null) {
return error;
}
} catch (Exception ignored) {
}
return null;
}
}

View file

@ -0,0 +1,8 @@
package com.jozufozu.flywheel.core.compile;
public interface FragmentData {
/**
* Generate the necessary glue code here.
*/
String generateFooter();
}

View file

@ -0,0 +1,88 @@
package com.jozufozu.flywheel.core.compile;
import java.util.Optional;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.backend.source.error.ErrorReporter;
import com.jozufozu.flywheel.backend.source.parse.ShaderFunction;
import com.jozufozu.flywheel.backend.source.parse.ShaderStruct;
import com.jozufozu.flywheel.backend.source.parse.StructField;
import com.jozufozu.flywheel.backend.source.parse.Variable;
import com.jozufozu.flywheel.backend.source.span.Span;
public class FragmentTemplateData implements FragmentData {
public final SourceFile file;
public final Span interpolantName;
public final ShaderStruct interpolant;
public final ShaderFunction fragmentMain;
public FragmentTemplateData(SourceFile file) {
this.file = file;
Optional<ShaderFunction> maybeFragmentMain = file.findFunction("fragment");
if (maybeFragmentMain.isEmpty()) {
ErrorReporter.generateMissingFunction(file, "fragment", "\"fragment\" function not defined");
throw new RuntimeException();
}
fragmentMain = maybeFragmentMain.get();
ImmutableList<Variable> fragmentParameters = fragmentMain.getParameters();
if (fragmentParameters.size() != 1) {
ErrorReporter.generateSpanError(fragmentMain.getArgs(), "fragment function must have exactly one argument");
throw new RuntimeException();
}
interpolantName = fragmentMain.getParameters().get(0).type;
Optional<ShaderStruct> maybeInterpolant = file.findStruct(interpolantName);
if (maybeInterpolant.isEmpty()) {
ErrorReporter.generateMissingStruct(file, interpolantName, "struct not defined");
throw new RuntimeException();
}
interpolant = maybeInterpolant.get();
}
@Override
public String generateFooter() {
StringBuilder builder = new StringBuilder();
prefixFields(builder, interpolant, "in", "v2f_");
builder.append(String.format("""
void main() {
Fragment o;
o.color = v2f_color;
o.texCoords = v2f_texCoords;
o.light = v2f_light;
o.diffuse = v2f_diffuse;
vec4 color = %s;
FLWFinalizeColor(color);
}
""",
fragmentMain.call("o")
));
return builder.toString();
}
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");
}
}
}

View file

@ -13,43 +13,27 @@ import com.jozufozu.flywheel.backend.source.parse.StructField;
import com.jozufozu.flywheel.backend.source.parse.Variable; import com.jozufozu.flywheel.backend.source.parse.Variable;
import com.jozufozu.flywheel.backend.source.span.Span; import com.jozufozu.flywheel.backend.source.span.Span;
public class InstancingTemplateData implements TemplateData { public class InstancingTemplateData implements VertexData {
public final SourceFile file; public final SourceFile file;
public final ShaderFunction vertexMain; public final ShaderFunction vertexMain;
public final ShaderFunction fragmentMain;
public final Span interpolantName;
public final Span vertexName; public final Span vertexName;
public final Span instanceName; public final Span instanceName;
public final ShaderStruct interpolant;
public final ShaderStruct instance; public final ShaderStruct instance;
public InstancingTemplateData(SourceFile file) { public InstancingTemplateData(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");
if (fragmentFunc.isEmpty()) {
ErrorReporter.generateMissingFunction(file, "fragment", "\"fragment\" function not defined");
}
if (vertexFunc.isEmpty()) { if (vertexFunc.isEmpty()) {
ErrorReporter.generateFileError(file, "could not find \"vertex\" function"); ErrorReporter.generateFileError(file, "could not find \"vertex\" function");
}
if (fragmentFunc.isEmpty() || vertexFunc.isEmpty()) {
throw new ShaderLoadingException(); throw new ShaderLoadingException();
} }
fragmentMain = fragmentFunc.get();
vertexMain = vertexFunc.get(); vertexMain = vertexFunc.get();
ImmutableList<Variable> parameters = fragmentMain.getParameters();
ImmutableList<Variable> vertexParams = vertexMain.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) { if (vertexParams.size() != 2) {
ErrorReporter.generateSpanError(vertexMain.getArgs(), "instancing requires vertex function to have 2 arguments"); ErrorReporter.generateSpanError(vertexMain.getArgs(), "instancing requires vertex function to have 2 arguments");
throw new ShaderLoadingException(); throw new ShaderLoadingException();
@ -68,35 +52,27 @@ public class InstancingTemplateData implements TemplateData {
throw new ShaderLoadingException(); throw new ShaderLoadingException();
} }
interpolantName = parameters.get(0).type;
instanceName = vertexParams.get(1).type; instanceName = vertexParams.get(1).type;
Optional<ShaderStruct> maybeInterpolant = file.findStruct(interpolantName);
Optional<ShaderStruct> maybeInstance = file.findStruct(instanceName); Optional<ShaderStruct> maybeInstance = file.findStruct(instanceName);
if (maybeInterpolant.isEmpty()) {
ErrorReporter.generateMissingStruct(file, interpolantName, "struct not defined");
}
if (maybeInstance.isEmpty()) { if (maybeInstance.isEmpty()) {
ErrorReporter.generateMissingStruct(file, instanceName, "struct not defined"); ErrorReporter.generateMissingStruct(file, instanceName, "struct not defined");
}
if (maybeInterpolant.isEmpty() || maybeInstance.isEmpty()) {
throw new ShaderLoadingException(); throw new ShaderLoadingException();
} }
interpolant = maybeInterpolant.get();
instance = maybeInstance.get(); instance = maybeInstance.get();
} }
public void vertexFooter(StringBuilder template, ShaderCompiler shader) { @Override
public String generateFooter(FileIndex shader, VertexType vertexType) {
ImmutableList<StructField> fields = instance.getFields(); ImmutableList<StructField> fields = instance.getFields();
VertexType vertexType = shader.vertexType;
int attributeBinding = vertexType.getLayout() int attributeBinding = vertexType.getLayout()
.getAttributeCount(); .getAttributeCount();
StringBuilder template = new StringBuilder();
for (StructField field : fields) { for (StructField field : fields) {
template.append("layout(location = ") template.append("layout(location = ")
.append(attributeBinding) .append(attributeBinding)
@ -109,9 +85,12 @@ public class InstancingTemplateData implements TemplateData {
.append(";\n"); .append(";\n");
attributeBinding += TypeHelper.getAttributeCount(field.type); attributeBinding += TypeHelper.getAttributeCount(field.type);
} }
Template.prefixFields(template, interpolant, "out", "v2f_");
template.append(String.format(""" template.append(String.format("""
out vec4 v2f_color;
out vec2 v2f_texCoords;
out vec2 v2f_light;
out float v2f_diffuse;
void main() { void main() {
Vertex v = FLWCreateVertex(); Vertex v = FLWCreateVertex();
%s i; %s i;
@ -130,26 +109,26 @@ public class InstancingTemplateData implements TemplateData {
} }
""", """,
instanceName, instanceName,
Template.assignFields(instance, "i.", "a_i_") assignFields(instance, "i.", "a_i_")
)); ));
return template.toString();
} }
public void fragmentFooter(StringBuilder template, FileIndex shader) { public static StringBuilder assignFields(ShaderStruct struct, String prefix1, String prefix2) {
Template.prefixFields(template, interpolant, "in", "v2f_"); ImmutableList<StructField> fields = struct.getFields();
template.append(String.format(""" StringBuilder builder = new StringBuilder();
void main() {
Fragment o;
o.color = v2f_color;
o.texCoords = v2f_texCoords;
o.light = v2f_light;
o.diffuse = v2f_diffuse;
vec4 color = %s; for (StructField field : fields) {
FLWFinalizeColor(color); builder.append(prefix1)
} .append(field.name)
""", .append(" = ")
fragmentMain.call("o") .append(prefix2)
)); .append(field.name)
.append(";\n");
}
return builder;
} }
} }

View file

@ -3,54 +3,33 @@ package com.jozufozu.flywheel.core.compile;
import java.util.Optional; import java.util.Optional;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.source.ShaderLoadingException; import com.jozufozu.flywheel.backend.source.ShaderLoadingException;
import com.jozufozu.flywheel.backend.source.SourceFile; import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.backend.source.error.ErrorReporter; import com.jozufozu.flywheel.backend.source.error.ErrorReporter;
import com.jozufozu.flywheel.backend.source.parse.ShaderFunction; import com.jozufozu.flywheel.backend.source.parse.ShaderFunction;
import com.jozufozu.flywheel.backend.source.parse.ShaderStruct;
import com.jozufozu.flywheel.backend.source.parse.Variable; import com.jozufozu.flywheel.backend.source.parse.Variable;
import com.jozufozu.flywheel.backend.source.span.Span;
public class OneShotTemplateData implements TemplateData { public class OneShotTemplateData implements VertexData {
public final SourceFile file; public final SourceFile file;
public final ShaderFunction vertexMain; public final ShaderFunction vertexMain;
public final Span interpolantName;
public final ShaderStruct interpolant;
public final ShaderFunction fragmentMain;
public OneShotTemplateData(SourceFile file) { public OneShotTemplateData(SourceFile file) {
this.file = file; this.file = file;
Optional<ShaderFunction> maybeVertexMain = file.findFunction("vertex"); Optional<ShaderFunction> maybeVertexMain = file.findFunction("vertex");
Optional<ShaderFunction> maybeFragmentMain = file.findFunction("fragment");
if (maybeVertexMain.isEmpty()) { if (maybeVertexMain.isEmpty()) {
ErrorReporter.generateFileError(file, "could not find \"vertex\" function"); ErrorReporter.generateFileError(file, "could not find \"vertex\" function");
}
if (maybeFragmentMain.isEmpty()) {
ErrorReporter.generateMissingFunction(file, "fragment", "\"fragment\" function not defined");
}
if (maybeVertexMain.isEmpty() || maybeFragmentMain.isEmpty()) {
throw new RuntimeException(); throw new RuntimeException();
} }
vertexMain = maybeVertexMain.get(); vertexMain = maybeVertexMain.get();
fragmentMain = maybeFragmentMain.get();
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");
}
if (fragmentParameters.size() != 1) {
ErrorReporter.generateSpanError(fragmentMain.getArgs(), "fragment function must have exactly one argument");
}
if (vertexParameters.size() != 1 || fragmentParameters.size() != 1) {
throw new RuntimeException(); throw new RuntimeException();
} }
@ -64,57 +43,30 @@ public class OneShotTemplateData implements TemplateData {
ErrorReporter.generateSpanError(vertexParam.qualifierSpan, "first parameter must be inout Vertex"); ErrorReporter.generateSpanError(vertexParam.qualifierSpan, "first parameter must be inout Vertex");
throw new ShaderLoadingException(); throw new ShaderLoadingException();
} }
interpolantName = fragmentMain.getParameters().get(0).type;
Optional<ShaderStruct> maybeInterpolant = file.findStruct(interpolantName);
if (maybeInterpolant.isEmpty()) {
ErrorReporter.generateMissingStruct(file, interpolantName, "struct not defined");
throw new RuntimeException();
}
interpolant = maybeInterpolant.get();
} }
public void vertexFooter(StringBuilder template, ShaderCompiler file) { @Override
Template.prefixFields(template, interpolant, "out", "v2f_"); public String generateFooter(FileIndex file, VertexType vertexType) {
return """
out vec4 v2f_color;
out vec2 v2f_texCoords;
out vec2 v2f_light;
out float v2f_diffuse;
template.append("""
void main() {
Vertex v = FLWCreateVertex();
vertex(v);
gl_Position = FLWVertex(v);
v.normal = normalize(v.normal);
v2f_color = v.color;
v2f_texCoords = v.texCoords;
v2f_light = v.light;
v2f_diffuse = diffuse(v.normal);
#if defined(DEBUG_NORMAL)
v2f_color = vec4(v.normal, 1.);
#endif
}
""");
}
public void fragmentFooter(StringBuilder template, FileIndex file) {
Template.prefixFields(template, interpolant, "in", "v2f_");
template.append(String.format("""
void main() { void main() {
Fragment o; Vertex v = FLWCreateVertex();
o.color = v2f_color; vertex(v);
o.texCoords = v2f_texCoords; gl_Position = FLWVertex(v);
o.light = v2f_light; v.normal = normalize(v.normal);
o.diffuse = v2f_diffuse;
vec4 color = %s; v2f_color = v.color;
FLWFinalizeColor(color); v2f_texCoords = v.texCoords;
v2f_light = v.light;
v2f_diffuse = diffuse(v.normal);
#if defined(DEBUG_NORMAL)
v2f_color = vec4(v.normal, 1.);
#endif
} }
""", """;
fragmentMain.call("o") }
));
}
} }

View file

@ -2,10 +2,12 @@ package com.jozufozu.flywheel.core.compile;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Function;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram; import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType; import com.jozufozu.flywheel.backend.gl.shader.GlShader;
import com.jozufozu.flywheel.backend.source.FileResolution; import com.jozufozu.flywheel.backend.source.FileResolution;
import com.jozufozu.flywheel.core.Templates;
/** /**
* A caching compiler. * A caching compiler.
@ -18,15 +20,27 @@ import com.jozufozu.flywheel.backend.source.FileResolution;
public class ProgramCompiler<P extends GlProgram> { public class ProgramCompiler<P extends GlProgram> {
protected final Map<ProgramContext, P> cache = new HashMap<>(); protected final Map<ProgramContext, P> cache = new HashMap<>();
private final GlProgram.Factory<P> factory; private final GlProgram.Factory<P> factory;
private final Function<ProgramContext, GlShader> vertexCompiler;
private final Function<ProgramContext, GlShader> fragmentCompiler;
private final Template template; public ProgramCompiler(GlProgram.Factory<P> factory, Function<ProgramContext, GlShader> vertexCompiler, Function<ProgramContext, GlShader> fragmentCompiler) {
private final FileResolution header;
public ProgramCompiler(GlProgram.Factory<P> factory, Template template, FileResolution header) {
this.factory = factory; this.factory = factory;
this.template = template; this.vertexCompiler = vertexCompiler;
this.header = header; this.fragmentCompiler = fragmentCompiler;
}
/**
* Creates a program compiler using this template.
* @param template The vertex template to use.
* @param factory A factory to add meaning to compiled programs.
* @param header The header file to use for the program.
* @param <P> The type of program to compile.
* @return A program compiler.
*/
public static <T extends VertexData, P extends GlProgram> ProgramCompiler<P> create(Template<T> template, GlProgram.Factory<P> factory, FileResolution header) {
return new ProgramCompiler<>(factory, ctx -> ShaderCompiler.compileVertex(ctx, template, header), ctx -> ShaderCompiler.compileFragment(ctx, Templates.FRAGMENT, header));
} }
/** /**
@ -45,11 +59,10 @@ public class ProgramCompiler<P extends GlProgram> {
} }
private P compile(ProgramContext ctx) { private P compile(ProgramContext ctx) {
ShaderCompiler compiler = new ShaderCompiler(ctx, template, header);
return new ProgramAssembler(compiler.name) return new ProgramAssembler(ctx.spec().name)
.attachShader(compiler.compile(ShaderType.VERTEX)) .attachShader(vertexCompiler.apply(ctx))
.attachShader(compiler.compile(ShaderType.FRAGMENT)) .attachShader(fragmentCompiler.apply(ctx))
.link() .link()
.deleteLinkedShaders() .deleteLinkedShaders()
.build(this.factory); .build(this.factory);

View file

@ -1,5 +1,6 @@
package com.jozufozu.flywheel.core.compile; package com.jozufozu.flywheel.core.compile;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -7,7 +8,6 @@ import javax.annotation.Nullable;
import com.jozufozu.flywheel.api.vertex.VertexType; import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.RenderLayer; import com.jozufozu.flywheel.backend.RenderLayer;
import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.core.shader.ProgramSpec; import com.jozufozu.flywheel.core.shader.ProgramSpec;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
@ -51,8 +51,8 @@ public record ProgramContext(float alphaDiscard, VertexType vertexType, ProgramS
return layer == RenderLayer.CUTOUT ? 0.1f : 0f; return layer == RenderLayer.CUTOUT ? 0.1f : 0f;
} }
public SourceFile getFile() { public List<String> createDefines() {
return spec().getSource(); return spec().getDefines(ctx());
} }
@Override @Override

View file

@ -1,183 +1,88 @@
package com.jozufozu.flywheel.core.compile; package com.jozufozu.flywheel.core.compile;
import java.util.ArrayList; import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import java.util.List;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
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.FileResolution; import com.jozufozu.flywheel.backend.source.FileResolution;
import com.jozufozu.flywheel.backend.source.SourceFile; import com.jozufozu.flywheel.core.shader.ProgramSpec;
import com.jozufozu.flywheel.backend.source.error.ErrorBuilder;
import com.jozufozu.flywheel.backend.source.error.ErrorReporter;
import com.jozufozu.flywheel.backend.source.span.Span;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import net.minecraft.resources.ResourceLocation;
/** /**
* Compiles a shader program. * Compiles a shader program.
*/ */
public class ShaderCompiler implements FileIndex { public class ShaderCompiler {
/**
* The name of the file responsible for this compilation.
*/
public final ResourceLocation name;
/**
* The template we'll be using to generate the final source.
*/
public final Template template;
private final FileResolution header;
/**
* Extra {@code #define}s to be added to the shader.
*/
private final List<String> defines;
/**
* Alpha threshold below which pixels are discarded.
*/
private final float alphaDiscard;
/**
* The vertex type to use.
*/
public final VertexType vertexType;
/**
* The main file to compile.
*/
public final SourceFile mainFile;
private final List<SourceFile> files = new ArrayList<>();
public ShaderCompiler(ProgramContext context, Template template, FileResolution header) {
this.name = context.spec().name;
this.template = template;
this.header = header;
this.mainFile = context.getFile();
this.defines = context.spec()
.getDefines(context.ctx());
this.vertexType = context.vertexType();
this.alphaDiscard = context.alphaDiscard();
}
public GlShader compile(ShaderType type) {
public static <T extends VertexData> GlShader compileVertex(ProgramContext context, Template<T> template, FileResolution header) {
StringBuilder finalSource = new StringBuilder(); StringBuilder finalSource = new StringBuilder();
finalSource.append("#version ") finalSource.append(generateHeader(template.getVersion(), ShaderType.VERTEX));
.append(template.getVersion())
.append('\n')
.append("#extension GL_ARB_explicit_attrib_location : enable\n")
.append("#extension GL_ARB_conservative_depth : enable\n")
.append(type.getDefineStatement()); // special case shader type declaration
if (alphaDiscard > 0) { for (String def : context.createDefines()) {
finalSource.append("#define ALPHA_DISCARD 0.1\n");
}
for (String def : defines) {
finalSource.append("#define ") finalSource.append("#define ")
.append(def) .append(def)
.append('\n'); .append('\n');
} }
if (type == ShaderType.VERTEX) { finalSource.append("""
finalSource.append(""" struct Vertex {
struct Vertex { vec3 pos;
vec3 pos; vec4 color;
vec4 color; vec2 texCoords;
vec2 texCoords; vec2 light;
vec2 light; vec3 normal;
vec3 normal; };
}; """);
"""); finalSource.append(context.vertexType()
finalSource.append(vertexType.writeShaderHeader()); .getShaderHeader());
}
files.clear(); FileIndexImpl index = new FileIndexImpl();
header.getFile().generateFinalSource(this, finalSource);
mainFile.generateFinalSource(this, finalSource);
template.getMetadata(mainFile).generateFooter(finalSource, type, this); header.getFile()
.generateFinalSource(index, finalSource);
ProgramSpec spec = context.spec();
spec.getVertexFile()
.generateFinalSource(index, finalSource);
return new GlShader(this, type, finalSource.toString()); T appliedTemplate = template.apply(spec.getVertexFile());
finalSource.append(appliedTemplate.generateFooter(index, context.vertexType()));
return new GlShader(spec.name, ShaderType.VERTEX, finalSource.toString());
} }
public <P extends WorldProgram> P compile(GlProgram.Factory<P> worldShaderPipeline) { public static <T extends FragmentData> GlShader compileFragment(ProgramContext context, Template<T> template, FileResolution header) {
return new ProgramAssembler(this.name)
.attachShader(compile(ShaderType.VERTEX))
.attachShader(compile(ShaderType.FRAGMENT))
.link()
.deleteLinkedShaders()
.build(worldShaderPipeline);
}
/** StringBuilder finalSource = new StringBuilder();
* Returns an arbitrary file ID for use this compilation context, or generates one if missing.
* @param sourceFile The file to retrieve the ID for. finalSource.append(generateHeader(template.getVersion(), ShaderType.FRAGMENT));
* @return A file ID unique to the given sourceFile. for (String def : context.createDefines()) {
*/ finalSource.append("#define ")
@Override .append(def)
public int getFileID(SourceFile sourceFile) { .append('\n');
int i = files.indexOf(sourceFile);
if (i != -1) {
return i;
} }
int size = files.size(); if (context.alphaDiscard() > 0) {
files.add(sourceFile); finalSource.append("#define ALPHA_DISCARD 0.1\n");
return size;
}
public Span getLineSpan(int fileId, int lineNo) {
SourceFile file = files.get(fileId);
return file.getLineSpanNoWhitespace(lineNo);
}
public void printShaderInfoLog(String source, String log, ResourceLocation name) {
List<String> lines = log.lines()
.toList();
boolean needsSourceDump = false;
StringBuilder errors = new StringBuilder();
for (String line : lines) {
ErrorBuilder builder = parseCompilerError(line);
if (builder != null) {
errors.append(builder.build());
} else {
errors.append(line).append('\n');
needsSourceDump = true;
}
}
Backend.LOGGER.error("Errors compiling '" + name + "': \n" + errors);
if (needsSourceDump) {
// TODO: generated code gets its own "file"
ErrorReporter.printLines(source);
}
}
@Nullable
private ErrorBuilder parseCompilerError(String line) {
try {
ErrorBuilder error = ErrorBuilder.fromLogLine(this, line);
if (error != null) {
return error;
}
} catch (Exception ignored) {
} }
return null;
FileIndexImpl index = new FileIndexImpl();
ProgramSpec spec = context.spec();
header.getFile().generateFinalSource(index, finalSource);
spec.getFragmentFile()
.generateFinalSource(index, finalSource);
T appliedTemplate = template.apply(spec.getFragmentFile());
finalSource.append(appliedTemplate.generateFooter());
return new GlShader(spec.name, ShaderType.FRAGMENT, finalSource.toString());
}
protected static String generateHeader(GLSLVersion version, ShaderType type) {
return "#version "
+ version
+ '\n'
+ "#extension GL_ARB_explicit_attrib_location : enable\n"
+ "#extension GL_ARB_conservative_depth : enable\n"
+ type.getDefineStatement();
} }
} }

View file

@ -4,13 +4,8 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.gl.GLSLVersion; import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.backend.source.FileResolution;
import com.jozufozu.flywheel.backend.source.SourceFile; 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. * A class that generates glsl glue code given a SourceFile.
@ -20,66 +15,33 @@ import com.jozufozu.flywheel.backend.source.parse.StructField;
* metadata to generate shader code that OpenGL can use to call into our shader programs. * metadata to generate shader code that OpenGL can use to call into our shader programs.
* </p> * </p>
*/ */
public class Template { public class Template<T> {
private final Map<SourceFile, TemplateData> metadata = new HashMap<>(); private final Map<SourceFile, T> metadata = new HashMap<>();
private final Function<SourceFile, TemplateData> reader; private final Function<SourceFile, T> reader;
private final GLSLVersion glslVersion; private final GLSLVersion glslVersion;
public Template(GLSLVersion glslVersion, Function<SourceFile, TemplateData> reader) { public Template(GLSLVersion glslVersion, Function<SourceFile, T> reader) {
this.reader = reader; this.reader = reader;
this.glslVersion = glslVersion; this.glslVersion = glslVersion;
} }
public TemplateData getMetadata(SourceFile file) { /**
* Verify that the given SourceFile is valid for this Template and return the metadata.
* @param file The SourceFile to apply this Template to.
* @return The applied template metadata.
*/
public T apply(SourceFile file) {
// lazily read files, cache results // lazily read files, cache results
return metadata.computeIfAbsent(file, reader); return metadata.computeIfAbsent(file, reader);
} }
/** /**
* Creates a program compiler using this template. * @return The GLSL version this template requires.
* @param factory A factory to add meaning to compiled programs.
* @param header The header file to use for the program.
* @param <P> The type of program to compile.
* @return A program compiler.
*/ */
public <P extends GlProgram> ProgramCompiler<P> programCompiler(GlProgram.Factory<P> factory, FileResolution header) {
return new ProgramCompiler<>(factory, this, header);
}
public GLSLVersion getVersion() { public GLSLVersion getVersion() {
return glslVersion; return glslVersion;
} }
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 StringBuilder assignFields(ShaderStruct struct, String prefix1, String prefix2) {
ImmutableList<StructField> fields = struct.getFields();
StringBuilder builder = new StringBuilder();
for (StructField field : fields) {
builder.append(prefix1)
.append(field.name)
.append(" = ")
.append(prefix2)
.append(field.name)
.append(";\n");
}
return builder;
}
} }

View file

@ -1,23 +0,0 @@
package com.jozufozu.flywheel.core.compile;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
public interface TemplateData {
void vertexFooter(StringBuilder builder, ShaderCompiler file);
void fragmentFooter(StringBuilder builder, FileIndex file);
/**
* Generate the necessary glue code here.
*
* @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.
*/
default void generateFooter(StringBuilder builder, ShaderType type, ShaderCompiler file) {
if (type == ShaderType.VERTEX) {
vertexFooter(builder, file);
} else if (type == ShaderType.FRAGMENT) {
fragmentFooter(builder, file);
}
}
}

View file

@ -0,0 +1,11 @@
package com.jozufozu.flywheel.core.compile;
import com.jozufozu.flywheel.api.vertex.VertexType;
public interface VertexData {
/**
* Generate the necessary glue code here.
* @param file The SourceFile with user written code.
*/
String generateFooter(FileIndex file, VertexType vertexType);
}

View file

@ -30,34 +30,47 @@ 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("source") ResourceLocation.CODEC.fieldOf("vertex")
.forGetter(ProgramSpec::getSourceLoc), .forGetter(ProgramSpec::getSourceLoc),
ResourceLocation.CODEC.fieldOf("fragment")
.forGetter(ProgramSpec::getFragmentLoc),
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 FileResolution source; public final FileResolution vertex;
public final FileResolution fragment;
public final ImmutableList<ProgramState> states; public final ImmutableList<ProgramState> states;
public ProgramSpec(ResourceLocation source, List<ProgramState> states) { public ProgramSpec(ResourceLocation vertex, ResourceLocation fragment, List<ProgramState> states) {
this.source = Resolver.INSTANCE.get(source); this.vertex = Resolver.INSTANCE.get(vertex);
this.fragment = Resolver.INSTANCE.get(fragment);
this.states = ImmutableList.copyOf(states); this.states = ImmutableList.copyOf(states);
} }
public void setName(ResourceLocation name) { public void setName(ResourceLocation name) {
this.name = name; this.name = name;
this.source.addSpec(name); this.vertex.addSpec(name);
this.fragment.addSpec(name);
} }
public ResourceLocation getSourceLoc() { public ResourceLocation getSourceLoc() {
return source.getFileLoc(); return vertex.getFileLoc();
} }
public SourceFile getSource() { public ResourceLocation getFragmentLoc() {
return source.getFile(); return fragment.getFileLoc();
}
public SourceFile getVertexFile() {
return vertex.getFile();
}
public SourceFile getFragmentFile() {
return fragment.getFile();
} }
public ImmutableList<ProgramState> getStates() { public ImmutableList<ProgramState> getStates() {

View file

@ -37,7 +37,7 @@ public class BlockVertex implements VertexType {
} }
@Override @Override
public String writeShaderHeader() { public String getShaderHeader() {
return """ return """
layout (location = 0) in vec3 _flw_v_pos; layout (location = 0) in vec3 _flw_v_pos;
layout (location = 1) in vec4 _flw_v_color; layout (location = 1) in vec4 _flw_v_color;

View file

@ -28,7 +28,7 @@ public class PosTexNormalVertex implements VertexType {
} }
@Override @Override
public String writeShaderHeader() { public String getShaderHeader() {
return """ return """
layout (location = 0) in vec3 _flw_v_pos; layout (location = 0) in vec3 _flw_v_pos;
layout (location = 1) in vec2 _flw_v_texCoords; layout (location = 1) in vec2 _flw_v_texCoords;

View file

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

View file

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

View file

@ -1,5 +1,6 @@
{ {
"source": "flywheel:passthru.vert", "vertex": "flywheel:passthru.vert",
"fragment": "flywheel:block.frag",
"states": [ "states": [
{ {
"when": "flywheel:normal_debug", "when": "flywheel:normal_debug",

View file

@ -6,10 +6,8 @@ struct Fragment {
vec2 light; vec2 light;
}; };
#if defined(FRAGMENT_SHADER)
vec4 fragment(Fragment r) { vec4 fragment(Fragment r) {
vec4 tex = FLWBlockTexture(r.texCoords); vec4 tex = FLWBlockTexture(r.texCoords);
return vec4(tex.rgb * FLWLight(r.light).rgb * r.diffuse, tex.a) * r.color; return vec4(tex.rgb * FLWLight(r.light).rgb * r.diffuse, tex.a) * r.color;
} }
#endif

View file

@ -1,6 +1,3 @@
#use "flywheel:block.frag"
#if defined(VERTEX_SHADER)
struct Instance { struct Instance {
vec2 light; vec2 light;
@ -15,4 +12,3 @@ void vertex(inout Vertex v, Instance i) {
v.color = i.color; v.color = i.color;
v.light = i.light; v.light = i.light;
} }
#endif

View file

@ -1,6 +1,3 @@
#use "flywheel:block.frag"
#if defined(VERTEX_SHADER)
#use "flywheel:core/quaternion.glsl" #use "flywheel:core/quaternion.glsl"
struct Oriented { struct Oriented {
@ -17,4 +14,3 @@ void vertex(inout Vertex v, Oriented o) {
v.color = o.color; v.color = o.color;
v.light = o.light; v.light = o.light;
} }
#endif

View file

@ -1,7 +1,4 @@
#use "flywheel:block.frag"
#if defined(VERTEX_SHADER)
void vertex(inout Vertex v) { void vertex(inout Vertex v) {
} }
#endif