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);
String getShaderHeader();
default int getStride() {
return getLayout().getStride();
}
@ -39,6 +41,4 @@ public interface VertexType {
default int byteOffset(int 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.versioned.GlCompat;
import com.jozufozu.flywheel.backend.source.ShaderLoadingException;
import com.jozufozu.flywheel.core.compile.ShaderCompiler;
import net.minecraft.resources.ResourceLocation;
@ -14,8 +13,8 @@ public class GlShader extends GlObject {
public final ResourceLocation name;
public final ShaderType type;
public GlShader(ShaderCompiler env, ShaderType type, String source) {
name = env.name;
public GlShader(ResourceLocation name, ShaderType type, String source) {
this.name = name;
this.type = type;
int handle = GL20.glCreateShader(type.glEnum);
@ -24,9 +23,9 @@ public class GlShader extends GlObject {
String log = GL20.glGetShaderInfoLog(handle);
if (!log.isEmpty()) {
env.printShaderInfoLog(source, log, this.name);
}
// if (!log.isEmpty()) {
// env.printShaderInfoLog(source, log, this.name);
// }
if (GL20.glGetShaderi(handle, GL20.GL_COMPILE_STATUS) != GL20.GL_TRUE) {
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.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.api.MaterialGroup;
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.TaskEngine;
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.core.BlockPos;
import net.minecraft.core.Vec3i;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
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.TextLine;
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;
public class ErrorBuilder {
@ -45,7 +45,7 @@ public class ErrorBuilder {
}
@Nullable
public static ErrorBuilder fromLogLine(ShaderCompiler env, String s) {
public static ErrorBuilder fromLogLine(FileIndex env, String s) {
Matcher matcher = ERROR_LINE.matcher(s);
if (matcher.find()) {

View file

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

View file

@ -1,12 +1,14 @@
package com.jozufozu.flywheel.core;
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.OneShotTemplateData;
import com.jozufozu.flywheel.core.compile.Template;
public class Templates {
public static final Template INSTANCING = new Template(GLSLVersion.V330, InstancingTemplateData::new);
public static final Template ONE_SHOT = new Template(GLSLVersion.V150, OneShotTemplateData::new);
public static final Template<InstancingTemplateData> INSTANCING = new Template<>(GLSLVersion.V330, InstancingTemplateData::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;
import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.backend.source.span.Span;
public interface FileIndex {
/**
@ -10,4 +11,10 @@ public interface FileIndex {
* @return A file ID unique to the given 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.span.Span;
public class InstancingTemplateData implements TemplateData {
public class InstancingTemplateData implements VertexData {
public final SourceFile file;
public final ShaderFunction vertexMain;
public final ShaderFunction fragmentMain;
public final Span interpolantName;
public final Span vertexName;
public final Span instanceName;
public final ShaderStruct interpolant;
public final ShaderStruct instance;
public InstancingTemplateData(SourceFile file) {
this.file = file;
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()) {
ErrorReporter.generateFileError(file, "could not find \"vertex\" function");
}
if (fragmentFunc.isEmpty() || vertexFunc.isEmpty()) {
throw new ShaderLoadingException();
}
fragmentMain = fragmentFunc.get();
vertexMain = vertexFunc.get();
ImmutableList<Variable> parameters = fragmentMain.getParameters();
ImmutableList<Variable> vertexParams = vertexMain.getParameters();
if (parameters.size() != 1) {
ErrorReporter.generateSpanError(fragmentMain.getArgs(), "instancing requires fragment function to have 1 argument");
}
if (vertexParams.size() != 2) {
ErrorReporter.generateSpanError(vertexMain.getArgs(), "instancing requires vertex function to have 2 arguments");
throw new ShaderLoadingException();
@ -68,35 +52,27 @@ public class InstancingTemplateData implements TemplateData {
throw new ShaderLoadingException();
}
interpolantName = parameters.get(0).type;
instanceName = vertexParams.get(1).type;
Optional<ShaderStruct> maybeInterpolant = file.findStruct(interpolantName);
Optional<ShaderStruct> maybeInstance = file.findStruct(instanceName);
if (maybeInterpolant.isEmpty()) {
ErrorReporter.generateMissingStruct(file, interpolantName, "struct not defined");
}
if (maybeInstance.isEmpty()) {
ErrorReporter.generateMissingStruct(file, instanceName, "struct not defined");
}
if (maybeInterpolant.isEmpty() || maybeInstance.isEmpty()) {
throw new ShaderLoadingException();
}
interpolant = maybeInterpolant.get();
instance = maybeInstance.get();
}
public void vertexFooter(StringBuilder template, ShaderCompiler shader) {
@Override
public String generateFooter(FileIndex shader, VertexType vertexType) {
ImmutableList<StructField> fields = instance.getFields();
VertexType vertexType = shader.vertexType;
int attributeBinding = vertexType.getLayout()
.getAttributeCount();
StringBuilder template = new StringBuilder();
for (StructField field : fields) {
template.append("layout(location = ")
.append(attributeBinding)
@ -109,9 +85,12 @@ public class InstancingTemplateData implements TemplateData {
.append(";\n");
attributeBinding += TypeHelper.getAttributeCount(field.type);
}
Template.prefixFields(template, interpolant, "out", "v2f_");
template.append(String.format("""
out vec4 v2f_color;
out vec2 v2f_texCoords;
out vec2 v2f_light;
out float v2f_diffuse;
void main() {
Vertex v = FLWCreateVertex();
%s i;
@ -130,26 +109,26 @@ public class InstancingTemplateData implements TemplateData {
}
""",
instanceName,
Template.assignFields(instance, "i.", "a_i_")
assignFields(instance, "i.", "a_i_")
));
return template.toString();
}
public void fragmentFooter(StringBuilder template, FileIndex shader) {
Template.prefixFields(template, interpolant, "in", "v2f_");
public static StringBuilder assignFields(ShaderStruct struct, String prefix1, String prefix2) {
ImmutableList<StructField> fields = struct.getFields();
template.append(String.format("""
void main() {
Fragment o;
o.color = v2f_color;
o.texCoords = v2f_texCoords;
o.light = v2f_light;
o.diffuse = v2f_diffuse;
StringBuilder builder = new StringBuilder();
vec4 color = %s;
FLWFinalizeColor(color);
}
""",
fragmentMain.call("o")
));
for (StructField field : fields) {
builder.append(prefix1)
.append(field.name)
.append(" = ")
.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 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.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.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 ShaderFunction vertexMain;
public final Span interpolantName;
public final ShaderStruct interpolant;
public final ShaderFunction fragmentMain;
public OneShotTemplateData(SourceFile file) {
this.file = file;
Optional<ShaderFunction> maybeVertexMain = file.findFunction("vertex");
Optional<ShaderFunction> maybeFragmentMain = file.findFunction("fragment");
if (maybeVertexMain.isEmpty()) {
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();
}
vertexMain = maybeVertexMain.get();
fragmentMain = maybeFragmentMain.get();
ImmutableList<Variable> fragmentParameters = fragmentMain.getParameters();
ImmutableList<Variable> vertexParameters = vertexMain.getParameters();
if (vertexParameters.size() != 1) {
ErrorReporter.generateSpanError(vertexMain.getArgs(), "a basic model requires vertex function to have one argument");
}
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();
}
@ -64,57 +43,30 @@ public class OneShotTemplateData implements TemplateData {
ErrorReporter.generateSpanError(vertexParam.qualifierSpan, "first parameter must be inout Vertex");
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) {
Template.prefixFields(template, interpolant, "out", "v2f_");
@Override
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() {
Fragment o;
o.color = v2f_color;
o.texCoords = v2f_texCoords;
o.light = v2f_light;
o.diffuse = v2f_diffuse;
Vertex v = FLWCreateVertex();
vertex(v);
gl_Position = FLWVertex(v);
v.normal = normalize(v.normal);
vec4 color = %s;
FLWFinalizeColor(color);
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
}
""",
fragmentMain.call("o")
));
}
""";
}
}

View file

@ -2,10 +2,12 @@ package com.jozufozu.flywheel.core.compile;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
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.core.Templates;
/**
* A caching compiler.
@ -18,15 +20,27 @@ import com.jozufozu.flywheel.backend.source.FileResolution;
public class ProgramCompiler<P extends GlProgram> {
protected final Map<ProgramContext, P> cache = new HashMap<>();
private final GlProgram.Factory<P> factory;
private final Function<ProgramContext, GlShader> vertexCompiler;
private final Function<ProgramContext, GlShader> fragmentCompiler;
private final Template template;
private final FileResolution header;
public ProgramCompiler(GlProgram.Factory<P> factory, Template template, FileResolution header) {
public ProgramCompiler(GlProgram.Factory<P> factory, Function<ProgramContext, GlShader> vertexCompiler, Function<ProgramContext, GlShader> fragmentCompiler) {
this.factory = factory;
this.template = template;
this.header = header;
this.vertexCompiler = vertexCompiler;
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) {
ShaderCompiler compiler = new ShaderCompiler(ctx, template, header);
return new ProgramAssembler(compiler.name)
.attachShader(compiler.compile(ShaderType.VERTEX))
.attachShader(compiler.compile(ShaderType.FRAGMENT))
return new ProgramAssembler(ctx.spec().name)
.attachShader(vertexCompiler.apply(ctx))
.attachShader(fragmentCompiler.apply(ctx))
.link()
.deleteLinkedShaders()
.build(this.factory);

View file

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

View file

@ -1,183 +1,88 @@
package com.jozufozu.flywheel.core.compile;
import java.util.ArrayList;
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.GLSLVersion;
import com.jozufozu.flywheel.backend.gl.shader.GlShader;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.backend.source.FileResolution;
import com.jozufozu.flywheel.backend.source.SourceFile;
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;
import com.jozufozu.flywheel.core.shader.ProgramSpec;
/**
* Compiles a shader program.
*/
public class ShaderCompiler implements FileIndex {
/**
* 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 class ShaderCompiler {
public static <T extends VertexData> GlShader compileVertex(ProgramContext context, Template<T> template, FileResolution header) {
StringBuilder finalSource = new StringBuilder();
finalSource.append("#version ")
.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
finalSource.append(generateHeader(template.getVersion(), ShaderType.VERTEX));
if (alphaDiscard > 0) {
finalSource.append("#define ALPHA_DISCARD 0.1\n");
}
for (String def : defines) {
for (String def : context.createDefines()) {
finalSource.append("#define ")
.append(def)
.append('\n');
}
if (type == ShaderType.VERTEX) {
finalSource.append("""
struct Vertex {
vec3 pos;
vec4 color;
vec2 texCoords;
vec2 light;
vec3 normal;
};
""");
finalSource.append(vertexType.writeShaderHeader());
}
finalSource.append("""
struct Vertex {
vec3 pos;
vec4 color;
vec2 texCoords;
vec2 light;
vec3 normal;
};
""");
finalSource.append(context.vertexType()
.getShaderHeader());
files.clear();
header.getFile().generateFinalSource(this, finalSource);
mainFile.generateFinalSource(this, finalSource);
FileIndexImpl index = new FileIndexImpl();
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) {
return new ProgramAssembler(this.name)
.attachShader(compile(ShaderType.VERTEX))
.attachShader(compile(ShaderType.FRAGMENT))
.link()
.deleteLinkedShaders()
.build(worldShaderPipeline);
}
public static <T extends FragmentData> GlShader compileFragment(ProgramContext context, Template<T> template, FileResolution header) {
/**
* 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;
StringBuilder finalSource = new StringBuilder();
finalSource.append(generateHeader(template.getVersion(), ShaderType.FRAGMENT));
for (String def : context.createDefines()) {
finalSource.append("#define ")
.append(def)
.append('\n');
}
int size = files.size();
files.add(sourceFile);
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) {
if (context.alphaDiscard() > 0) {
finalSource.append("#define ALPHA_DISCARD 0.1\n");
}
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.function.Function;
import com.google.common.collect.ImmutableList;
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.parse.ShaderStruct;
import com.jozufozu.flywheel.backend.source.parse.StructField;
/**
* 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.
* </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;
public Template(GLSLVersion glslVersion, Function<SourceFile, TemplateData> reader) {
public Template(GLSLVersion glslVersion, Function<SourceFile, T> reader) {
this.reader = reader;
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
return metadata.computeIfAbsent(file, reader);
}
/**
* Creates a program compiler using this template.
* @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.
* @return The GLSL version this template requires.
*/
public <P extends GlProgram> ProgramCompiler<P> programCompiler(GlProgram.Factory<P> factory, FileResolution header) {
return new ProgramCompiler<>(factory, this, header);
}
public GLSLVersion getVersion() {
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?
public static final Codec<ProgramSpec> CODEC = RecordCodecBuilder.create(instance -> instance.group(
ResourceLocation.CODEC.fieldOf("source")
ResourceLocation.CODEC.fieldOf("vertex")
.forGetter(ProgramSpec::getSourceLoc),
ResourceLocation.CODEC.fieldOf("fragment")
.forGetter(ProgramSpec::getFragmentLoc),
ProgramState.CODEC.listOf()
.optionalFieldOf("states", Collections.emptyList())
.forGetter(ProgramSpec::getStates))
.apply(instance, ProgramSpec::new));
public ResourceLocation name;
public final FileResolution source;
public final FileResolution vertex;
public final FileResolution fragment;
public final ImmutableList<ProgramState> states;
public ProgramSpec(ResourceLocation source, List<ProgramState> states) {
this.source = Resolver.INSTANCE.get(source);
public ProgramSpec(ResourceLocation vertex, ResourceLocation fragment, List<ProgramState> states) {
this.vertex = Resolver.INSTANCE.get(vertex);
this.fragment = Resolver.INSTANCE.get(fragment);
this.states = ImmutableList.copyOf(states);
}
public void setName(ResourceLocation name) {
this.name = name;
this.source.addSpec(name);
this.vertex.addSpec(name);
this.fragment.addSpec(name);
}
public ResourceLocation getSourceLoc() {
return source.getFileLoc();
return vertex.getFileLoc();
}
public SourceFile getSource() {
return source.getFile();
public ResourceLocation getFragmentLoc() {
return fragment.getFileLoc();
}
public SourceFile getVertexFile() {
return vertex.getFile();
}
public SourceFile getFragmentFile() {
return fragment.getFile();
}
public ImmutableList<ProgramState> getStates() {

View file

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

View file

@ -28,7 +28,7 @@ public class PosTexNormalVertex implements VertexType {
}
@Override
public String writeShaderHeader() {
public String getShaderHeader() {
return """
layout (location = 0) in vec3 _flw_v_pos;
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": [
{
"when": "flywheel:normal_debug",

View file

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

View file

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

View file

@ -6,10 +6,8 @@ struct Fragment {
vec2 light;
};
#if defined(FRAGMENT_SHADER)
vec4 fragment(Fragment r) {
vec4 tex = FLWBlockTexture(r.texCoords);
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 {
vec2 light;
@ -15,4 +12,3 @@ void vertex(inout Vertex v, Instance i) {
v.color = i.color;
v.light = i.light;
}
#endif

View file

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

View file

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