diff --git a/src/main/java/com/jozufozu/flywheel/api/shader/FlexibleShader.java b/src/main/java/com/jozufozu/flywheel/api/shader/FlexibleShader.java new file mode 100644 index 000000000..a46802373 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/api/shader/FlexibleShader.java @@ -0,0 +1,15 @@ +package com.jozufozu.flywheel.api.shader; + +import com.jozufozu.flywheel.api.vertex.VertexType; +import com.jozufozu.flywheel.backend.gl.shader.GlProgram; + +/** + * Represents a vertex format agnostic shader. + */ +public interface FlexibleShader

{ + + /** + * Get a version of this shader that accepts the given VertexType as input. + */ + P get(VertexType type); +} diff --git a/src/main/java/com/jozufozu/flywheel/api/vertex/VertexType.java b/src/main/java/com/jozufozu/flywheel/api/vertex/VertexType.java index f785f4ac3..165512540 100644 --- a/src/main/java/com/jozufozu/flywheel/api/vertex/VertexType.java +++ b/src/main/java/com/jozufozu/flywheel/api/vertex/VertexType.java @@ -39,4 +39,6 @@ public interface VertexType { default int byteOffset(int vertexIndex) { return getStride() * vertexIndex; } + + String writeShaderHeader(); } diff --git a/src/main/java/com/jozufozu/flywheel/backend/Backend.java b/src/main/java/com/jozufozu/flywheel/backend/Backend.java index b8ec0ff04..1d5f931eb 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/Backend.java +++ b/src/main/java/com/jozufozu/flywheel/backend/Backend.java @@ -115,7 +115,7 @@ public class Backend { enabled = switch (engine) { case OFF -> false; - case BATCHING -> !usingShaders; + case BATCHING -> true; case INSTANCING -> !usingShaders && compat.instancedArraysSupported(); }; } diff --git a/src/main/java/com/jozufozu/flywheel/backend/ShaderContext.java b/src/main/java/com/jozufozu/flywheel/backend/ShaderContext.java index 66585908e..96c5e563d 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/ShaderContext.java +++ b/src/main/java/com/jozufozu/flywheel/backend/ShaderContext.java @@ -1,19 +1,19 @@ package com.jozufozu.flywheel.backend; -import java.util.function.Supplier; - +import com.jozufozu.flywheel.api.shader.FlexibleShader; +import com.jozufozu.flywheel.api.vertex.VertexType; import com.jozufozu.flywheel.backend.gl.shader.GlProgram; import net.minecraft.resources.ResourceLocation; public interface ShaderContext

{ - default P getProgram(ResourceLocation loc) { + default P getProgram(ResourceLocation loc, VertexType inputType) { return this.getProgramSupplier(loc) - .get(); + .get(inputType); } - Supplier

getProgramSupplier(ResourceLocation loc); + FlexibleShader

getProgramSupplier(ResourceLocation loc); /** * Load all programs associated with this context. This might be just one, if the context is very specialized. diff --git a/src/main/java/com/jozufozu/flywheel/backend/gl/GlNumericType.java b/src/main/java/com/jozufozu/flywheel/backend/gl/GlNumericType.java index 29baffd7f..aaf69e61d 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/gl/GlNumericType.java +++ b/src/main/java/com/jozufozu/flywheel/backend/gl/GlNumericType.java @@ -26,15 +26,15 @@ public enum GlNumericType { private static final GlNumericType[] VALUES = values(); private static final Map NAME_LOOKUP = Arrays.stream(VALUES) - .collect(Collectors.toMap(GlNumericType::getDisplayName, type -> type)); + .collect(Collectors.toMap(GlNumericType::getTypeName, type -> type)); private final int byteWidth; - private final String displayName; + private final String typeName; private final int glEnum; GlNumericType(int bytes, String name, int glEnum) { this.byteWidth = bytes; - this.displayName = name; + this.typeName = name; this.glEnum = glEnum; } @@ -42,8 +42,8 @@ public enum GlNumericType { return this.byteWidth; } - public String getDisplayName() { - return this.displayName; + public String getTypeName() { + return this.typeName; } public int getGlEnum() { @@ -64,4 +64,9 @@ public enum GlNumericType { public static GlNumericType byName(String name) { return name == null ? null : NAME_LOOKUP.get(name.toLowerCase(Locale.ROOT)); } + + @Override + public String toString() { + return typeName; + } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/gl/GlVertexArray.java b/src/main/java/com/jozufozu/flywheel/backend/gl/GlVertexArray.java index 1ec315fa9..c50681c7f 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/gl/GlVertexArray.java +++ b/src/main/java/com/jozufozu/flywheel/backend/gl/GlVertexArray.java @@ -39,8 +39,8 @@ public class GlVertexArray extends GlObject { int offset = 0; for (LayoutItem spec : type.getLayoutItems()) { spec.vertexAttribPointer(type.getStride(), startIndex, offset); - startIndex += spec.getAttributeCount(); - offset += spec.getSize(); + startIndex += spec.attributeCount(); + offset += spec.size(); } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/gl/shader/GlShader.java b/src/main/java/com/jozufozu/flywheel/backend/gl/shader/GlShader.java index c386294ba..f6464778c 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/gl/shader/GlShader.java +++ b/src/main/java/com/jozufozu/flywheel/backend/gl/shader/GlShader.java @@ -1,10 +1,16 @@ package com.jozufozu.flywheel.backend.gl.shader; +import java.util.List; + import org.lwjgl.opengl.GL20; import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.gl.GlObject; import com.jozufozu.flywheel.backend.gl.versioned.GlCompat; +import com.jozufozu.flywheel.backend.pipeline.ShaderCompiler; +import com.jozufozu.flywheel.backend.source.ShaderLoadingException; +import com.jozufozu.flywheel.backend.source.error.ErrorBuilder; +import com.jozufozu.flywheel.backend.source.error.ErrorReporter; import net.minecraft.resources.ResourceLocation; @@ -13,8 +19,8 @@ public class GlShader extends GlObject { public final ResourceLocation name; public final ShaderType type; - public GlShader(ResourceLocation name, ShaderType type, CharSequence source) { - this.name = name; + public GlShader(ShaderCompiler env, ShaderType type, String source) { + name = env.name; this.type = type; int handle = GL20.glCreateShader(type.glEnum); @@ -24,13 +30,31 @@ public class GlShader extends GlObject { String log = GL20.glGetShaderInfoLog(handle); if (!log.isEmpty()) { - Backend.log.error("Shader compilation log for " + name + ": " + log); - Backend.log.error(source); + List lines = log.lines() + .toList(); + + boolean needsSourceDump = false; + + StringBuilder errors = new StringBuilder(); + for (String line : lines) { + ErrorBuilder builder = env.parseCompilerError(line); + + if (builder != null) { + errors.append(builder.build()); + } else { + errors.append(line).append('\n'); + needsSourceDump = true; + } + } + Backend.log.error("Errors compiling '" + name + "': \n" + errors); + if (needsSourceDump) { + // TODO: generated code gets its own "file" + ErrorReporter.printLines(source); + } } - //Backend.log.debug(shader.printSource()); if (GL20.glGetShaderi(handle, GL20.GL_COMPILE_STATUS) != GL20.GL_TRUE) { - throw new RuntimeException("Could not compile " + name + ". See log for details."); + throw new ShaderLoadingException("Could not compile " + name + ". See log for details."); } setHandle(handle); diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancedMaterial.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancedMaterial.java index a72dcf415..becb71114 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancedMaterial.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancedMaterial.java @@ -1,20 +1,17 @@ package com.jozufozu.flywheel.backend.instancing.instancing; -import java.util.concurrent.ExecutionException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.function.Supplier; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; import com.jozufozu.flywheel.api.InstanceData; import com.jozufozu.flywheel.api.Instancer; import com.jozufozu.flywheel.api.Material; import com.jozufozu.flywheel.api.struct.Instanced; -import com.jozufozu.flywheel.backend.Backend; -import com.jozufozu.flywheel.backend.RenderWork; -import com.jozufozu.flywheel.backend.model.ImmediateAllocator; import com.jozufozu.flywheel.backend.model.ModelAllocator; -import com.jozufozu.flywheel.backend.model.ModelPool; -import com.jozufozu.flywheel.core.Formats; import com.jozufozu.flywheel.core.model.Model; /** @@ -23,24 +20,14 @@ import com.jozufozu.flywheel.core.model.Model; */ public class InstancedMaterial implements Material { - final ModelAllocator allocator; - protected final Cache> models; + protected final ModelAllocator allocator; + protected final Map> models = new HashMap<>(); protected final Instanced type; + protected final List> uninitialized = new ArrayList<>(); - public InstancedMaterial(Instanced type) { + public InstancedMaterial(Instanced type, ModelAllocator allocator) { this.type = type; - - if (Backend.getInstance().compat.onAMDWindows()) { - allocator = ImmediateAllocator.INSTANCE; - } else { - allocator = new ModelPool(Formats.POS_TEX_NORMAL, 64); - } - this.models = CacheBuilder.newBuilder() - .removalListener(notification -> { - GPUInstancer instancer = (GPUInstancer) notification.getValue(); - RenderWork.enqueue(instancer::delete); - }) - .build(); + this.allocator = allocator; } /** @@ -52,32 +39,33 @@ public class InstancedMaterial implements Material { */ @Override public Instancer model(Object key, Supplier modelSupplier) { - try { - return models.get(key, () -> new GPUInstancer<>(type, modelSupplier.get(), allocator)); - } catch (ExecutionException e) { - throw new RuntimeException("error creating instancer", e); - } + return models.computeIfAbsent(key, $ -> { + GPUInstancer instancer = new GPUInstancer<>(type, modelSupplier.get(), allocator); + uninitialized.add(instancer); + return instancer; + }); } public boolean nothingToRender() { - return models.size() > 0 && models.asMap() - .values() + return models.size() > 0 && models.values() .stream() .allMatch(GPUInstancer::isEmpty); } public void delete() { - models.invalidateAll(); - if (allocator instanceof ModelPool pool) pool.delete(); + models.values().forEach(GPUInstancer::delete); + models.clear(); } /** * Clear all instance data without freeing resources. */ public void clear() { - models.asMap() - .values() + models.values() .forEach(GPUInstancer::clear); } + public Collection> getAllInstancers() { + return models.values(); + } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancedMaterialGroup.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancedMaterialGroup.java index 8938686f1..c558213cd 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancedMaterialGroup.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancedMaterialGroup.java @@ -1,6 +1,5 @@ package com.jozufozu.flywheel.backend.instancing.instancing; -import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -8,7 +7,11 @@ import com.jozufozu.flywheel.api.InstanceData; import com.jozufozu.flywheel.api.MaterialGroup; import com.jozufozu.flywheel.api.struct.Instanced; import com.jozufozu.flywheel.api.struct.StructType; +import com.jozufozu.flywheel.backend.Backend; +import com.jozufozu.flywheel.backend.model.ImmediateAllocator; +import com.jozufozu.flywheel.backend.model.ModelAllocator; import com.jozufozu.flywheel.backend.model.ModelPool; +import com.jozufozu.flywheel.core.Formats; import com.jozufozu.flywheel.core.shader.WorldProgram; import com.jozufozu.flywheel.util.Textures; import com.mojang.math.Matrix4f; @@ -27,17 +30,23 @@ public class InstancedMaterialGroup

implements MaterialG protected final RenderType type; private final Map, InstancedMaterial> materials = new HashMap<>(); + private final ModelAllocator allocator; public InstancedMaterialGroup(InstancingEngine

owner, RenderType type) { this.owner = owner; this.type = type; + if (Backend.getInstance().compat.onAMDWindows()) { + this.allocator = ImmediateAllocator.INSTANCE; + } else { + this.allocator = new ModelPool(Formats.POS_TEX_NORMAL, 2048); + } } @SuppressWarnings("unchecked") @Override public InstancedMaterial material(StructType type) { if (type instanceof Instanced instanced) { - return (InstancedMaterial) materials.computeIfAbsent(instanced, InstancedMaterial::new); + return (InstancedMaterial) materials.computeIfAbsent(instanced, t -> new InstancedMaterial<>(t, allocator)); } else { throw new ClassCastException("Cannot use type '" + type + "' with GPU instancing."); } @@ -51,25 +60,25 @@ public class InstancedMaterialGroup

implements MaterialG } protected void renderAll(Matrix4f viewProjection, double camX, double camY, double camZ) { + // initialize all uninitialized instancers... + for (InstancedMaterial material : materials.values()) { + for (GPUInstancer instancer : material.uninitialized) { + instancer.init(); + } + material.uninitialized.clear(); + } + + if (allocator instanceof ModelPool pool) { + // ...and then flush the model arena in case anything was marked for upload + pool.flush(); + } + for (Map.Entry, InstancedMaterial> entry : materials.entrySet()) { InstancedMaterial material = entry.getValue(); if (material.nothingToRender()) continue; - Collection> instancers = material.models.asMap() - .values(); - - // initialize all uninitialized instancers... - for (GPUInstancer gpuInstancer : instancers) { - gpuInstancer.init(); - } - - if (material.allocator instanceof ModelPool pool) { - // ...and then flush the model arena in case anything was marked for upload - pool.flush(); - } - - P program = owner.getProgram(entry.getKey() - .getProgramSpec()).get(); + P program = owner.context.getProgram(entry.getKey() + .getProgramSpec(), Formats.POS_TEX_NORMAL); program.bind(); program.uploadViewProjection(viewProjection); @@ -77,7 +86,7 @@ public class InstancedMaterialGroup

implements MaterialG setup(program); - for (GPUInstancer instancer : instancers) { + for (GPUInstancer instancer : material.getAllInstancers()) { instancer.render(); } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancingEngine.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancingEngine.java index 7f30b541e..4366b5091 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancingEngine.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancingEngine.java @@ -3,17 +3,17 @@ 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.instancing.TaskEngine; +import com.jozufozu.flywheel.api.shader.FlexibleShader; 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.WorldContext; import com.jozufozu.flywheel.core.shader.WorldProgram; import com.jozufozu.flywheel.event.RenderLayerEvent; @@ -126,7 +126,7 @@ public class InstancingEngine

implements Engine { } } - public Supplier

getProgram(ResourceLocation name) { + public FlexibleShader

getProgram(ResourceLocation name) { return context.getProgramSupplier(name); } diff --git a/src/main/java/com/jozufozu/flywheel/backend/pipeline/InstancingTemplate.java b/src/main/java/com/jozufozu/flywheel/backend/pipeline/InstancingTemplate.java deleted file mode 100644 index fdb4e4381..000000000 --- a/src/main/java/com/jozufozu/flywheel/backend/pipeline/InstancingTemplate.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.jozufozu.flywheel.backend.pipeline; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import com.jozufozu.flywheel.backend.gl.shader.ShaderType; -import com.jozufozu.flywheel.backend.source.SourceFile; - -public class InstancingTemplate extends Template { - - public static final InstancingTemplate INSTANCE = new InstancingTemplate(); - - public InstancingTemplate() { - super(InstancingProgramMetaData::new); - } - - @Override - public void generateTemplateSource(StringBuilder builder, ShaderType type, SourceFile file) { - if (type == ShaderType.VERTEX) { - vertexFooter(builder, file); - } else if (type == ShaderType.FRAGMENT) { - fragmentFooter(builder, file); - } - } - - @Override - public Collection getShaderInputs(SourceFile file) { - InstancingProgramMetaData data = getMetadata(file); - - List inputs = new ArrayList<>(ShaderInput.fromStruct(data.vertex, "a_v_")); - inputs.addAll(ShaderInput.fromStruct(data.instance, "a_i_")); - - return inputs; - } - - public void vertexFooter(StringBuilder template, SourceFile file) { - InstancingProgramMetaData data = getMetadata(file); - - Template.prefixFields(template, data.vertex, "in", "a_v_"); - Template.prefixFields(template, data.instance, "in", "a_i_"); - Template.prefixFields(template, data.interpolant, "out", "v2f_"); - - template.append("void main() {\n"); - template.append(data.vertexName) - .append(" v;\n"); - Template.assignFields(template, data.vertex, "v.", "a_v_"); - - template.append(data.instanceName) - .append(" i;\n"); - Template.assignFields(template, data.instance, "i.", "a_i_"); - - template.append(data.interpolantName) - .append(" o = ") - .append(data.vertexMain.call("v", "i")) - .append(";\n"); - - Template.assignFields(template, data.interpolant, "v2f_", "o."); - - template.append('}'); - } - - public void fragmentFooter(StringBuilder template, SourceFile file) { - InstancingProgramMetaData data = getMetadata(file); - - Template.prefixFields(template, data.interpolant, "in", "v2f_"); - - template.append("void main() {\n"); - template.append(data.interpolantName) - .append(" o;\n"); - Template.assignFields(template, data.interpolant, "o.", "v2f_"); - - template.append(data.fragmentMain.call("o")) - .append(";\n"); - - template.append('}'); - } -} diff --git a/src/main/java/com/jozufozu/flywheel/backend/pipeline/InstancingProgramMetaData.java b/src/main/java/com/jozufozu/flywheel/backend/pipeline/InstancingTemplateData.java similarity index 50% rename from src/main/java/com/jozufozu/flywheel/backend/pipeline/InstancingProgramMetaData.java rename to src/main/java/com/jozufozu/flywheel/backend/pipeline/InstancingTemplateData.java index c033b7b8c..2eb6f74be 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/pipeline/InstancingProgramMetaData.java +++ b/src/main/java/com/jozufozu/flywheel/backend/pipeline/InstancingTemplateData.java @@ -3,15 +3,17 @@ package com.jozufozu.flywheel.backend.pipeline; 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.StructField; import com.jozufozu.flywheel.backend.source.parse.Variable; import com.jozufozu.flywheel.backend.source.span.Span; -public class InstancingProgramMetaData { +public class InstancingTemplateData implements TemplateData { public final SourceFile file; public final ShaderFunction vertexMain; @@ -20,10 +22,9 @@ public class InstancingProgramMetaData { public final Span vertexName; public final Span instanceName; public final ShaderStruct interpolant; - public final ShaderStruct vertex; public final ShaderStruct instance; - public InstancingProgramMetaData(SourceFile file) { + public InstancingTemplateData(SourceFile file) { this.file = file; Optional vertexFunc = file.findFunction("vertex"); @@ -54,20 +55,25 @@ public class InstancingProgramMetaData { throw new ShaderLoadingException(); } - interpolantName = vertexMain.getType(); - vertexName = vertexParams.get(0) - .typeName(); - instanceName = vertexParams.get(1) - .typeName(); + Variable vertexParam = vertexParams.get(0); + vertexName = vertexParam.type; + + boolean namedVertex = vertexParam.type + .toString() + .equals("Vertex"); + + + if (!(namedVertex && vertexParam.qualifier == Variable.Qualifier.INOUT)) { + ErrorReporter.generateSpanError(vertexParam.qualifierSpan, "first parameter must be inout Vertex"); + throw new ShaderLoadingException(); + } + + interpolantName = parameters.get(0).type; + instanceName = vertexParams.get(1).type; Optional maybeInterpolant = file.findStruct(interpolantName); - Optional maybeVertex = file.findStruct(vertexName); Optional maybeInstance = file.findStruct(instanceName); - if (maybeVertex.isEmpty()) { - ErrorReporter.generateMissingStruct(file, vertexName, "struct not defined"); - } - if (maybeInterpolant.isEmpty()) { ErrorReporter.generateMissingStruct(file, interpolantName, "struct not defined"); } @@ -76,12 +82,74 @@ public class InstancingProgramMetaData { ErrorReporter.generateMissingStruct(file, instanceName, "struct not defined"); } - if (maybeVertex.isEmpty() || maybeInterpolant.isEmpty() || maybeInstance.isEmpty()) { + if (maybeInterpolant.isEmpty() || maybeInstance.isEmpty()) { throw new ShaderLoadingException(); } interpolant = maybeInterpolant.get(); - vertex = maybeVertex.get(); instance = maybeInstance.get(); } + + public void vertexFooter(StringBuilder template, ShaderCompiler shader) { + ImmutableList fields = instance.getFields(); + VertexType vertexType = shader.vertexType; + + int attributeBinding = vertexType.getLayout() + .getAttributeCount(); + + for (StructField field : fields) { + template.append("layout(location = ") + .append(attributeBinding) + .append(") in") + .append(' ') + .append(field.type) + .append(' ') + .append("a_i_") + .append(field.name) + .append(";\n"); + attributeBinding += ShaderInput.from(field).attribCount; + } + Template.prefixFields(template, interpolant, "out", "v2f_"); + + template.append(String.format(""" + void main() { + Vertex v = FLWCreateVertex(); + %s i; + %s + vertex(v, i); + 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 + } + """, + instanceName, + Template.assignFields(instance, "i.", "a_i_") + )); + } + + public void fragmentFooter(StringBuilder template, ShaderCompiler shader) { + 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; + + vec4 color = %s; + FLWFinalizeColor(color); + } + """, + fragmentMain.call("o") + )); + } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/pipeline/LazyCompiler.java b/src/main/java/com/jozufozu/flywheel/backend/pipeline/LazyCompiler.java new file mode 100644 index 000000000..a8b57c4ba --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/pipeline/LazyCompiler.java @@ -0,0 +1,33 @@ +package com.jozufozu.flywheel.backend.pipeline; + +import java.util.HashMap; +import java.util.Map; + +import com.jozufozu.flywheel.api.shader.FlexibleShader; +import com.jozufozu.flywheel.api.vertex.VertexType; +import com.jozufozu.flywheel.core.shader.ContextAwareProgram; +import com.jozufozu.flywheel.core.shader.WorldProgram; +import com.jozufozu.flywheel.core.shader.spec.ProgramSpec; + +public class LazyCompiler

implements FlexibleShader

{ + + private final ShaderPipeline

pipeline; + private final ProgramSpec spec; + + private final Map> cache = new HashMap<>(); + + public LazyCompiler(ShaderPipeline

pipeline, ProgramSpec spec) { + + this.pipeline = pipeline; + this.spec = spec; + } + + public void delete() { + cache.values().forEach(ContextAwareProgram::delete); + } + + @Override + public P get(VertexType type) { + return cache.computeIfAbsent(type, t -> pipeline.compile(spec, t)).get(); + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/pipeline/OneShotTemplate.java b/src/main/java/com/jozufozu/flywheel/backend/pipeline/OneShotTemplate.java deleted file mode 100644 index f457ebfc4..000000000 --- a/src/main/java/com/jozufozu/flywheel/backend/pipeline/OneShotTemplate.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.jozufozu.flywheel.backend.pipeline; - -import java.util.Collection; - -import com.jozufozu.flywheel.backend.gl.shader.ShaderType; -import com.jozufozu.flywheel.backend.source.SourceFile; - -public class OneShotTemplate extends Template { - - public static final OneShotTemplate INSTANCE = new OneShotTemplate(); - - public OneShotTemplate() { - super(OneShotProgramMetaData::new); - } - - @Override - public void generateTemplateSource(StringBuilder builder, ShaderType type, SourceFile file) { - if (type == ShaderType.VERTEX) { - vertexFooter(builder, file); - } else if (type == ShaderType.FRAGMENT) { - fragmentFooter(builder, file); - } - } - - @Override - public Collection getShaderInputs(SourceFile file) { - OneShotProgramMetaData data = getMetadata(file); - - return ShaderInput.fromStruct(data.vertex, "a_v_"); - } - - public void vertexFooter(StringBuilder template, SourceFile file) { - OneShotProgramMetaData data = getMetadata(file); - - Template.prefixFields(template, data.vertex, "in", "a_v_"); - Template.prefixFields(template, data.interpolant, "out", "v2f_"); - - template.append("void main() {\n"); - template.append(data.vertexName) - .append(" v;\n"); - Template.assignFields(template, data.vertex, "v.", "a_v_"); - - template.append(data.interpolantName) - .append(" o = ") - .append(data.vertexMain.call("v")) - .append(";\n"); - - Template.assignFields(template, data.interpolant, "v2f_", "o."); - - template.append('}'); - } - - public void fragmentFooter(StringBuilder template, SourceFile file) { - OneShotProgramMetaData data = getMetadata(file); - - Template.prefixFields(template, data.interpolant, "in", "v2f_"); - - template.append("void main() {\n"); - template.append(data.interpolant.name) - .append(" o;\n"); - Template.assignFields(template, data.interpolant, "o.", "v2f_"); - - template.append(data.fragmentMain.call("o")) - .append(";\n"); - - template.append('}'); - } -} diff --git a/src/main/java/com/jozufozu/flywheel/backend/pipeline/OneShotProgramMetaData.java b/src/main/java/com/jozufozu/flywheel/backend/pipeline/OneShotTemplateData.java similarity index 56% rename from src/main/java/com/jozufozu/flywheel/backend/pipeline/OneShotProgramMetaData.java rename to src/main/java/com/jozufozu/flywheel/backend/pipeline/OneShotTemplateData.java index 67562a68b..b46945cfa 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/pipeline/OneShotProgramMetaData.java +++ b/src/main/java/com/jozufozu/flywheel/backend/pipeline/OneShotTemplateData.java @@ -3,6 +3,7 @@ package com.jozufozu.flywheel.backend.pipeline; import java.util.Optional; import com.google.common.collect.ImmutableList; +import com.jozufozu.flywheel.backend.source.ShaderLoadingException; import com.jozufozu.flywheel.backend.source.SourceFile; import com.jozufozu.flywheel.backend.source.error.ErrorReporter; import com.jozufozu.flywheel.backend.source.parse.ShaderFunction; @@ -10,17 +11,15 @@ 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 OneShotProgramMetaData { +public class OneShotTemplateData implements TemplateData { public final SourceFile file; public final ShaderFunction vertexMain; public final Span interpolantName; - public final Span vertexName; public final ShaderStruct interpolant; - public final ShaderStruct vertex; public final ShaderFunction fragmentMain; - public OneShotProgramMetaData(SourceFile file) { + public OneShotTemplateData(SourceFile file) { this.file = file; Optional maybeVertexMain = file.findFunction("vertex"); @@ -55,24 +54,67 @@ public class OneShotProgramMetaData { throw new RuntimeException(); } - interpolantName = vertexMain.getType(); - vertexName = vertexParameters.get(0) - .typeName(); + Variable vertexParam = vertexMain.getParameters().get(0); + + boolean namedVertex = vertexParam.type + .toString() + .equals("Vertex"); + + if (!(namedVertex && vertexParam.qualifier == Variable.Qualifier.INOUT)) { + ErrorReporter.generateSpanError(vertexParam.qualifierSpan, "first parameter must be inout Vertex"); + throw new ShaderLoadingException(); + } + + interpolantName = fragmentMain.getParameters().get(0).type; Optional maybeInterpolant = file.findStruct(interpolantName); - Optional maybeVertex = file.findStruct(vertexName); - if (maybeVertex.isEmpty()) - ErrorReporter.generateMissingStruct(file, vertexName, "struct not defined"); - - if (maybeInterpolant.isEmpty()) + if (maybeInterpolant.isEmpty()) { ErrorReporter.generateMissingStruct(file, interpolantName, "struct not defined"); - if (maybeVertex.isEmpty() || maybeInterpolant.isEmpty()) { throw new RuntimeException(); } interpolant = maybeInterpolant.get(); - vertex = maybeVertex.get(); + } + + public void vertexFooter(StringBuilder template, ShaderCompiler file) { + Template.prefixFields(template, interpolant, "out", "v2f_"); + + 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, ShaderCompiler 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; + + vec4 color = %s; + FLWFinalizeColor(color); + } + """, + fragmentMain.call("o") + )); } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/pipeline/ProtoProgram.java b/src/main/java/com/jozufozu/flywheel/backend/pipeline/ProgramAssembler.java similarity index 56% rename from src/main/java/com/jozufozu/flywheel/backend/pipeline/ProtoProgram.java rename to src/main/java/com/jozufozu/flywheel/backend/pipeline/ProgramAssembler.java index caa331d79..c3abd0e40 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/pipeline/ProtoProgram.java +++ b/src/main/java/com/jozufozu/flywheel/backend/pipeline/ProgramAssembler.java @@ -3,7 +3,6 @@ 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.glAttachShader; -import static org.lwjgl.opengl.GL20.glBindAttribLocation; import static org.lwjgl.opengl.GL20.glCreateProgram; import static org.lwjgl.opengl.GL20.glGetProgramInfoLog; import static org.lwjgl.opengl.GL20.glGetProgrami; @@ -13,44 +12,33 @@ import java.util.List; import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.gl.shader.GlShader; -import com.jozufozu.flywheel.backend.gl.shader.ShaderType; +import com.jozufozu.flywheel.core.shader.ExtensibleGlProgram; +import com.jozufozu.flywheel.core.shader.WorldProgram; import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.resources.ResourceLocation; -public class ProtoProgram { +public class ProgramAssembler { public final int program; - public final WorldShader parent; + private final ResourceLocation name; - private int attributeIndex; + private final List shaders = new ObjectArrayList<>(); - private final List shaders; - - public ProtoProgram(WorldShader parent) { - this.parent = parent; + public ProgramAssembler(ResourceLocation name) { + this.name = name; this.program = glCreateProgram(); - shaders = new ObjectArrayList<>(); - } - - public ProtoProgram compilePart(ShaderType type) { - GlShader shader = parent.compile(type); - attachShader(shader); - return this; } /** * Links the attached shaders to this program. */ - public ProtoProgram link() { - - parent.template.getShaderInputs(parent.mainFile) - .forEach(this::addAttribute); - + public ProgramAssembler link() { glLinkProgram(this.program); String log = glGetProgramInfoLog(this.program); if (!log.isEmpty()) { - Backend.log.debug("Program link log for " + parent.name + ": " + log); + Backend.log.debug("Program link log for " + name + ": " + log); } int result = glGetProgrami(this.program, GL_LINK_STATUS); @@ -62,19 +50,18 @@ public class ProtoProgram { return this; } - public ProtoProgram deleteLinkedShaders() { + public ProgramAssembler deleteLinkedShaders() { shaders.forEach(GlShader::delete); return this; } - private void attachShader(GlShader glShader) { + public ProgramAssembler attachShader(GlShader glShader) { shaders.add(glShader); glAttachShader(this.program, glShader.handle()); + return this; } - private void addAttribute(ShaderInput shaderInput) { - glBindAttribLocation(this.program, attributeIndex, shaderInput.name); - attributeIndex += shaderInput.attribCount; + public

P build(ExtensibleGlProgram.Factory

factory) { + return factory.create(name, program); } - } diff --git a/src/main/java/com/jozufozu/flywheel/backend/pipeline/ShaderCompiler.java b/src/main/java/com/jozufozu/flywheel/backend/pipeline/ShaderCompiler.java new file mode 100644 index 000000000..cd1ce9608 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/pipeline/ShaderCompiler.java @@ -0,0 +1,145 @@ +package com.jozufozu.flywheel.backend.pipeline; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.Nullable; + +import com.jozufozu.flywheel.api.vertex.VertexType; +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.span.Span; +import com.jozufozu.flywheel.core.shader.ExtensibleGlProgram; +import com.jozufozu.flywheel.core.shader.WorldProgram; +import com.jozufozu.flywheel.core.shader.spec.ProgramState; + +import net.minecraft.resources.ResourceLocation; + +public class ShaderCompiler { + + public final ResourceLocation name; + public final Template template; + private final FileResolution header; + + @Nullable + private ProgramState variant; + + public final VertexType vertexType; + + public SourceFile mainFile; + + public ShaderCompiler(ResourceLocation name, SourceFile mainSource, Template template, FileResolution header, VertexType vertexType) { + this.name = name; + this.template = template; + this.header = header; + this.mainFile = mainSource; + this.vertexType = vertexType; + } + + public ShaderCompiler setMainSource(SourceFile file) { + if (mainFile == file) return this; + + mainFile = file; + + return this; + } + + public GlShader compile(ShaderType type) { + + 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("#define ") + .append(type.define) // special case shader type declaration + .append('\n'); + + ProgramState variant = getVariant(); + if (variant != null) { + for (String def : variant.defines()) { + 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()); + } + + files.clear(); + if (header.getFile() != null) { + header.getFile().generateFinalSource(this, finalSource); + } + mainFile.generateFinalSource(this, finalSource); + + template.getMetadata(mainFile).generateFooter(finalSource, type, this); + + return new GlShader(this, type, finalSource.toString()); + } + + @Nullable + public ProgramState getVariant() { + return variant; + } + + public void setVariant(@Nullable ProgramState variant) { + this.variant = variant; + } + + private final List files = new ArrayList<>(); + + public int allocateFile(SourceFile sourceFile) { + int i = files.indexOf(sourceFile); + if (i != -1) { + return i; + } + + 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); + } + + @Nullable + public ErrorBuilder parseCompilerError(String line) { + try { + ErrorBuilder error = ErrorBuilder.fromLogLine(this, line); + if (error != null) { + return error; + } + } catch (Exception ignored) { + } + + return null; + } + + public

P compile(ExtensibleGlProgram.Factory

worldShaderPipeline) { + return new ProgramAssembler(this.name) + .attachShader(compile(ShaderType.VERTEX)) + .attachShader(compile(ShaderType.FRAGMENT)) + .link() + .deleteLinkedShaders() + .build(worldShaderPipeline); + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/pipeline/ShaderPipeline.java b/src/main/java/com/jozufozu/flywheel/backend/pipeline/ShaderPipeline.java index 0caa59d57..a6f94413f 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/pipeline/ShaderPipeline.java +++ b/src/main/java/com/jozufozu/flywheel/backend/pipeline/ShaderPipeline.java @@ -1,5 +1,6 @@ package com.jozufozu.flywheel.backend.pipeline; +import com.jozufozu.flywheel.api.vertex.VertexType; import com.jozufozu.flywheel.core.shader.ContextAwareProgram; import com.jozufozu.flywheel.core.shader.WorldProgram; import com.jozufozu.flywheel.core.shader.spec.ProgramSpec; @@ -10,6 +11,6 @@ import com.jozufozu.flywheel.core.shader.spec.ProgramSpec; */ public interface ShaderPipeline

{ - ContextAwareProgram

compile(ProgramSpec spec); + ContextAwareProgram

compile(ProgramSpec spec, VertexType vertexType); } diff --git a/src/main/java/com/jozufozu/flywheel/backend/pipeline/Template.java b/src/main/java/com/jozufozu/flywheel/backend/pipeline/Template.java index 8b5b621e8..3906cbecc 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/pipeline/Template.java +++ b/src/main/java/com/jozufozu/flywheel/backend/pipeline/Template.java @@ -1,13 +1,11 @@ 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; @@ -21,37 +19,25 @@ import com.jozufozu.flywheel.backend.source.parse.StructField; *

* @param Holds metadata, generates errors. */ -public abstract class Template { +public class Template { private final Map metadata = new HashMap<>(); private final Function reader; + private final GLSLVersion glslVersion; - protected Template(Function reader) { + public Template(GLSLVersion glslVersion, Function reader) { this.reader = reader; + this.glslVersion = glslVersion; } - /** - * Generate the necessary glue code here. - * - *

- * See {@link InstancingTemplate} and {@link OneShotTemplate} for examples. - *

- * @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 getShaderInputs(SourceFile file); - public D getMetadata(SourceFile file) { // lazily read files, cache results return metadata.computeIfAbsent(file, reader); } public GLSLVersion getVersion() { - return GLSLVersion.V150; + return glslVersion; } public static void prefixFields(StringBuilder builder, ShaderStruct struct, String qualifier, String prefix) { @@ -68,9 +54,11 @@ public abstract class Template { } } - public static void assignFields(StringBuilder builder, ShaderStruct struct, String prefix1, String prefix2) { + public static StringBuilder assignFields(ShaderStruct struct, String prefix1, String prefix2) { ImmutableList fields = struct.getFields(); + StringBuilder builder = new StringBuilder(); + for (StructField field : fields) { builder.append(prefix1) .append(field.name) @@ -79,5 +67,7 @@ public abstract class Template { .append(field.name) .append(";\n"); } + + return builder; } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/pipeline/TemplateData.java b/src/main/java/com/jozufozu/flywheel/backend/pipeline/TemplateData.java new file mode 100644 index 000000000..50603fdf0 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/pipeline/TemplateData.java @@ -0,0 +1,23 @@ +package com.jozufozu.flywheel.backend.pipeline; + +import com.jozufozu.flywheel.backend.gl.shader.ShaderType; + +public interface TemplateData { + void vertexFooter(StringBuilder builder, ShaderCompiler file); + void fragmentFooter(StringBuilder builder, ShaderCompiler 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); + } + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/pipeline/WorldShader.java b/src/main/java/com/jozufozu/flywheel/backend/pipeline/WorldShader.java deleted file mode 100644 index cffa9142b..000000000 --- a/src/main/java/com/jozufozu/flywheel/backend/pipeline/WorldShader.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.jozufozu.flywheel.backend.pipeline; - -import java.util.List; -import java.util.Optional; - -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 net.minecraft.resources.ResourceLocation; - -public class WorldShader { - - public final ResourceLocation name; - public final Template template; - public final CharSequence header; - - public SourceFile mainFile; - - private CharSequence source; - private StringBuilder defines; - - public WorldShader(ResourceLocation name, Template template, FileResolution header) { - this.name = name; - this.template = template; - this.header = Optional.ofNullable(header.getFile()) - .map(SourceFile::generateFinalSource) - .orElse(""); - } - - public WorldShader setDefines(List defs) { - defines = new StringBuilder(); - - for (String def : defs) { - defines.append("#define ") - .append(def) - .append('\n'); - } - return this; - } - - public WorldShader setMainSource(SourceFile file) { - if (mainFile == file) return this; - - mainFile = file; - source = file.generateFinalSource(); - - return this; - } - - public GlShader compile(ShaderType type) { - - StringBuilder finalSource = new StringBuilder(); - - finalSource.append("#version ") - .append(template.getVersion()) - .append('\n') - .append("#extension GL_ARB_conservative_depth : enable\n") - .append("#define ") - .append(type.define) // special case shader type declaration - .append('\n') - .append(defines != null ? defines : "") - .append(header) - .append('\n') - .append(source) - .append('\n'); - - template.generateTemplateSource(finalSource, type, mainFile); - - return new GlShader(name, type, finalSource); - } - - public ProtoProgram createProgram() { - return new ProtoProgram(this); - } -} diff --git a/src/main/java/com/jozufozu/flywheel/backend/pipeline/WorldShaderPipeline.java b/src/main/java/com/jozufozu/flywheel/backend/pipeline/WorldShaderPipeline.java index 9129cb514..c46dcbe28 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/pipeline/WorldShaderPipeline.java +++ b/src/main/java/com/jozufozu/flywheel/backend/pipeline/WorldShaderPipeline.java @@ -1,10 +1,6 @@ package com.jozufozu.flywheel.backend.pipeline; -import java.util.List; - -import javax.annotation.Nullable; - -import com.jozufozu.flywheel.backend.gl.shader.ShaderType; +import com.jozufozu.flywheel.api.vertex.VertexType; import com.jozufozu.flywheel.backend.source.FileResolution; import com.jozufozu.flywheel.backend.source.SourceFile; import com.jozufozu.flywheel.core.shader.ContextAwareProgram; @@ -14,8 +10,6 @@ import com.jozufozu.flywheel.core.shader.WorldProgram; import com.jozufozu.flywheel.core.shader.spec.ProgramSpec; import com.jozufozu.flywheel.core.shader.spec.ProgramState; -import net.minecraft.resources.ResourceLocation; - public class WorldShaderPipeline

implements ShaderPipeline

{ private final ExtensibleGlProgram.Factory

factory; @@ -29,38 +23,18 @@ public class WorldShaderPipeline

implements ShaderPipeli this.header = header; } - public ContextAwareProgram

compile(ProgramSpec spec) { + public ContextAwareProgram

compile(ProgramSpec spec, VertexType vertexType) { SourceFile file = spec.getSource().getFile(); - return compile(spec.name, file, spec.getStates()); - } + ShaderCompiler shader = new ShaderCompiler(spec.name, file, template, header, vertexType); - public ContextAwareProgram

compile(ResourceLocation name, SourceFile file, List variants) { - WorldShader shader = new WorldShader(name, template, header) - .setMainSource(file); - - GameStateProgram.Builder

builder = GameStateProgram.builder(compile(shader, null)); - - for (ProgramState variant : variants) { - builder.withVariant(variant.context(), compile(shader, variant)); + GameStateProgram.Builder

builder = GameStateProgram.builder(shader.compile(this.factory)); + for (ProgramState variant : spec.getStates()) { + shader.setVariant(variant); + builder.withVariant(variant.context(), shader.compile(this.factory)); } return builder.build(); } - - private P compile(WorldShader shader, @Nullable ProgramState variant) { - - if (variant != null) { - shader.setDefines(variant.defines()); - } - - ProtoProgram program = shader.createProgram() - .compilePart(ShaderType.VERTEX) - .compilePart(ShaderType.FRAGMENT) - .link() - .deleteLinkedShaders(); - - return factory.create(shader.name, program.program); - } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/source/FileResolution.java b/src/main/java/com/jozufozu/flywheel/backend/source/FileResolution.java index ba4e5f343..00f1593a6 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/source/FileResolution.java +++ b/src/main/java/com/jozufozu/flywheel/backend/source/FileResolution.java @@ -62,16 +62,15 @@ public class FileResolution { * Called after all files are loaded. If we can't find the file here, it doesn't exist. *

*/ - void resolve(ISourceHolder sources) { + void resolve(SourceFinder sources) { + file = sources.findSource(fileLoc); - try { - file = sources.findSource(fileLoc); - } catch (RuntimeException error) { + if (file == null) { ErrorBuilder builder = ErrorBuilder.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.pointAtFile(span.getSourceFile()) - .pointAt(span, 2); + .pointAt(span, 1); } Backend.log.error(builder.build()); } diff --git a/src/main/java/com/jozufozu/flywheel/backend/source/Resolver.java b/src/main/java/com/jozufozu/flywheel/backend/source/Resolver.java index 185fcd4af..202b7a94d 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/source/Resolver.java +++ b/src/main/java/com/jozufozu/flywheel/backend/source/Resolver.java @@ -28,7 +28,7 @@ public class Resolver { /** * Try and resolve all referenced source files, printing errors if any aren't found. */ - public void resolve(ISourceHolder sources) { + public void resolve(SourceFinder sources) { for (FileResolution resolution : resolutions.values()) { resolution.resolve(sources); } diff --git a/src/main/java/com/jozufozu/flywheel/backend/source/ShaderLoadingException.java b/src/main/java/com/jozufozu/flywheel/backend/source/ShaderLoadingException.java index 02e013091..19024e686 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/source/ShaderLoadingException.java +++ b/src/main/java/com/jozufozu/flywheel/backend/source/ShaderLoadingException.java @@ -1,22 +1,11 @@ package com.jozufozu.flywheel.backend.source; public class ShaderLoadingException extends RuntimeException { + public ShaderLoadingException() { } public ShaderLoadingException(String message) { super(message); } - - public ShaderLoadingException(String message, Throwable cause) { - super(message, cause); - } - - public ShaderLoadingException(Throwable cause) { - super(cause); - } - - public ShaderLoadingException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/source/ShaderSources.java b/src/main/java/com/jozufozu/flywheel/backend/source/ShaderSources.java index 00898a238..97f4acead 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/source/ShaderSources.java +++ b/src/main/java/com/jozufozu/flywheel/backend/source/ShaderSources.java @@ -6,6 +6,8 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import javax.annotation.Nullable; + import com.google.common.collect.Lists; import com.jozufozu.flywheel.util.ResourceUtil; import com.jozufozu.flywheel.util.StringUtil; @@ -17,7 +19,7 @@ import net.minecraft.server.packs.resources.ResourceManager; /** * The main object for loading and parsing source files. */ -public class ShaderSources implements ISourceHolder { +public class ShaderSources implements SourceFinder { public static final String SHADER_DIR = "flywheel/shaders/"; public static final ArrayList EXTENSIONS = Lists.newArrayList(".vert", ".vsh", ".frag", ".fsh", ".glsl"); @@ -51,13 +53,9 @@ public class ShaderSources implements ISourceHolder { } @Override + @Nullable public SourceFile findSource(ResourceLocation name) { - SourceFile source = shaderSources.get(name); - if (source == null) { - throw new ShaderLoadingException(String.format("shader '%s' does not exist", name)); - } - - return source; + return shaderSources.get(name); } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/source/SourceFile.java b/src/main/java/com/jozufozu/flywheel/backend/source/SourceFile.java index ab055553d..2b93d7168 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/source/SourceFile.java +++ b/src/main/java/com/jozufozu/flywheel/backend/source/SourceFile.java @@ -10,6 +10,7 @@ import java.util.regex.Pattern; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.jozufozu.flywheel.backend.pipeline.ShaderCompiler; import com.jozufozu.flywheel.backend.source.parse.Import; import com.jozufozu.flywheel.backend.source.parse.ShaderFunction; import com.jozufozu.flywheel.backend.source.parse.ShaderStruct; @@ -74,6 +75,23 @@ public class SourceFile { this.elided = elideSource(source, elisions).toString(); } + public Span getLineSpan(int line) { + int begin = lines.getLineStart(line); + int end = begin + lines.getLine(line).length(); + return new StringSpan(this, lines.getCharPos(begin), lines.getCharPos(end)); + } + + public Span getLineSpanNoWhitespace(int line) { + int begin = lines.getLineStart(line); + int end = begin + lines.getLine(line).length(); + + while (begin < end && Character.isWhitespace(source.charAt(begin))) { + begin++; + } + + return new StringSpan(this, lines.getCharPos(begin), lines.getCharPos(end)); + } + /** * Search this file and recursively search all imports to find a struct definition matching the given name. * @@ -120,18 +138,25 @@ public class SourceFile { return "#use " + '"' + name + '"'; } - public CharSequence generateFinalSource() { + public CharSequence generateFinalSource(ShaderCompiler env) { StringBuilder builder = new StringBuilder(); - generateFinalSource(builder); + generateFinalSource(env, builder); return builder; } - public void generateFinalSource(StringBuilder source) { + public void generateFinalSource(ShaderCompiler env, StringBuilder source) { for (Import include : imports) { SourceFile file = include.getFile(); - if (file != null) file.generateFinalSource(source); + if (file != null) file.generateFinalSource(env, source); } + + int i = env.allocateFile(this); + source.append("#line ") + .append(0) + .append(' ') + .append(i) + .append('\n'); source.append(elided); } diff --git a/src/main/java/com/jozufozu/flywheel/backend/source/ISourceHolder.java b/src/main/java/com/jozufozu/flywheel/backend/source/SourceFinder.java similarity index 73% rename from src/main/java/com/jozufozu/flywheel/backend/source/ISourceHolder.java rename to src/main/java/com/jozufozu/flywheel/backend/source/SourceFinder.java index df209dcf7..09d5f8d90 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/source/ISourceHolder.java +++ b/src/main/java/com/jozufozu/flywheel/backend/source/SourceFinder.java @@ -1,12 +1,15 @@ package com.jozufozu.flywheel.backend.source; +import javax.annotation.Nullable; + import net.minecraft.resources.ResourceLocation; /** * A minimal source file lookup function. */ @FunctionalInterface -public interface ISourceHolder { +public interface SourceFinder { + @Nullable SourceFile findSource(ResourceLocation name); } diff --git a/src/main/java/com/jozufozu/flywheel/backend/source/SourceLines.java b/src/main/java/com/jozufozu/flywheel/backend/source/SourceLines.java index 1c04bdee7..6fd722744 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/source/SourceLines.java +++ b/src/main/java/com/jozufozu/flywheel/backend/source/SourceLines.java @@ -29,6 +29,48 @@ public class SourceLines { this.lines = getLines(source, lineStarts); } + public int getLineCount() { + return lines.size(); + } + + public String getLine(int lineNo) { + return lines.get(lineNo); + } + + public int getLineStart(int lineNo) { + + return lineStarts.getInt(lineNo); + } + + public CharPos getCharPos(int charPos) { + int lineNo = 0; + for (; lineNo < lineStarts.size(); lineNo++) { + int ls = lineStarts.getInt(lineNo); + + if (charPos < ls) { + break; + } + } + + lineNo -= 1; + + int lineStart = lineStarts.getInt(lineNo); + + return new CharPos(charPos, lineNo, charPos - lineStart); + } + + public String printLinesWithNumbers() { + StringBuilder builder = new StringBuilder(); + + for (int i = 0, linesSize = lines.size(); i < linesSize; i++) { + builder.append(String.format("%1$4s: ", i + 1)) + .append(lines.get(i)) + .append('\n'); + } + + return builder.toString(); + } + /** * Scan the source for line breaks, recording the position of the first character of each line. * @param source @@ -57,41 +99,4 @@ public class SourceLines { return builder.build(); } - - public CharPos getCharPos(int charPos) { - int lineNo = 0; - for (; lineNo < lineStarts.size(); lineNo++) { - int ls = lineStarts.getInt(lineNo); - - if (charPos < ls) { - break; - } - } - - lineNo -= 1; - - int lineStart = lineStarts.getInt(lineNo); - - return new CharPos(charPos, lineNo, charPos - lineStart); - } - - public int getLineCount() { - return lines.size(); - } - - public String getLine(int lineNo) { - return lines.get(lineNo); - } - - public String printLinesWithNumbers() { - StringBuilder builder = new StringBuilder(); - - for (int i = 0, linesSize = lines.size(); i < linesSize; i++) { - builder.append(String.format("%1$4s: ", i + 1)) - .append(lines.get(i)) - .append('\n'); - } - - return builder.toString(); - } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/source/error/ErrorBuilder.java b/src/main/java/com/jozufozu/flywheel/backend/source/error/ErrorBuilder.java index 8a908f6dc..b1ef7b2b5 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/source/error/ErrorBuilder.java +++ b/src/main/java/com/jozufozu/flywheel/backend/source/error/ErrorBuilder.java @@ -2,9 +2,12 @@ package com.jozufozu.flywheel.backend.source.error; import java.util.ArrayList; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.annotation.Nullable; +import com.jozufozu.flywheel.backend.pipeline.ShaderCompiler; import com.jozufozu.flywheel.backend.source.SourceFile; import com.jozufozu.flywheel.backend.source.SourceLines; import com.jozufozu.flywheel.backend.source.error.lines.ErrorLine; @@ -12,27 +15,60 @@ import com.jozufozu.flywheel.backend.source.error.lines.FileLine; import com.jozufozu.flywheel.backend.source.error.lines.HeaderLine; 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.util.FlwUtil; public class ErrorBuilder { + private static final Pattern ERROR_LINE = Pattern.compile("(\\d+)\\((\\d+)\\) : (.*)"); + private final List lines = new ArrayList<>(); - private final Level level; + public ErrorBuilder() { - public ErrorBuilder(Level level, CharSequence msg) { - this.level = level; - - lines.add(new HeaderLine(level.toString(), msg)); } public static ErrorBuilder error(CharSequence msg) { - return new ErrorBuilder(Level.ERROR, msg); + return new ErrorBuilder() + .header(ErrorLevel.ERROR, msg); + } + + public static ErrorBuilder compError(CharSequence msg) { + return new ErrorBuilder() + .extra(msg); } public static ErrorBuilder warn(CharSequence msg) { - return new ErrorBuilder(Level.WARN, msg); + return new ErrorBuilder() + .header(ErrorLevel.WARN, msg); + } + + @Nullable + public static ErrorBuilder fromLogLine(ShaderCompiler env, String s) { + Matcher matcher = ERROR_LINE.matcher(s); + + if (matcher.find()) { + String fileId = matcher.group(1); + String lineNo = matcher.group(2); + String msg = matcher.group(3); + Span span = env.getLineSpan(Integer.parseInt(fileId), Integer.parseInt(lineNo)); + return ErrorBuilder.compError(msg) + .pointAtFile(span.getSourceFile()) + .pointAt(span, 1); + } else { + return null; + } + } + + public ErrorBuilder header(ErrorLevel level, CharSequence msg) { + lines.add(new HeaderLine(level.toString(), msg)); + return this; + } + + public ErrorBuilder extra(CharSequence msg) { + lines.add(new TextLine(msg.toString())); + return this; } public ErrorBuilder pointAtFile(SourceFile file) { @@ -42,14 +78,14 @@ public class ErrorBuilder { public ErrorBuilder hintIncludeFor(@Nullable Span span, CharSequence msg) { if (span == null) return this; + SourceFile sourceFile = span.getSourceFile(); - String builder = "add " + span.getSourceFile() - .importStatement() + ' ' + msg; + String builder = "add " + sourceFile.importStatement() + ' ' + msg + "\n defined here:"; - lines.add(new HeaderLine("hint", builder)); + header(ErrorLevel.HINT, builder); - return this.pointAtFile(span.getSourceFile()) - .pointAt(span, 1); + return this.pointAtFile(sourceFile) + .pointAt(span, 0); } public ErrorBuilder pointAt(Span span, int ctxLines) { diff --git a/src/main/java/com/jozufozu/flywheel/backend/source/error/Level.java b/src/main/java/com/jozufozu/flywheel/backend/source/error/ErrorLevel.java similarity index 75% rename from src/main/java/com/jozufozu/flywheel/backend/source/error/Level.java rename to src/main/java/com/jozufozu/flywheel/backend/source/error/ErrorLevel.java index ecf151fdd..cbd161969 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/source/error/Level.java +++ b/src/main/java/com/jozufozu/flywheel/backend/source/error/ErrorLevel.java @@ -1,13 +1,14 @@ package com.jozufozu.flywheel.backend.source.error; -public enum Level { +public enum ErrorLevel { WARN("warn"), ERROR("error"), + HINT("hint"), ; private final String error; - Level(String error) { + ErrorLevel(String error) { this.error = error; } diff --git a/src/main/java/com/jozufozu/flywheel/backend/source/error/ErrorReporter.java b/src/main/java/com/jozufozu/flywheel/backend/source/error/ErrorReporter.java index 97232a6c4..5829a8279 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/source/error/ErrorReporter.java +++ b/src/main/java/com/jozufozu/flywheel/backend/source/error/ErrorReporter.java @@ -1,12 +1,15 @@ package com.jozufozu.flywheel.backend.source.error; +import java.util.List; import java.util.Optional; +import com.jozufozu.flywheel.Flywheel; 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; +import com.jozufozu.flywheel.util.FlwUtil; public class ErrorReporter { @@ -42,7 +45,7 @@ public class ErrorReporter { ErrorBuilder error = ErrorBuilder.error(msg) .pointAtFile(file) - .pointAt(vertexName, 2) + .pointAt(vertexName, 1) .hintIncludeFor(span.orElse(null), hint); Backend.log.error(error.build()); @@ -64,4 +67,29 @@ public class ErrorReporter { Backend.log.error(error.build()); } + + public static void printLines(CharSequence source) { + String string = source.toString(); + + List lines = string.lines() + .toList(); + + int size = lines.size(); + + int maxWidth = FlwUtil.numDigits(size) + 1; + + StringBuilder builder = new StringBuilder().append('\n'); + + for (int i = 0; i < size; i++) { + + builder.append(i) + .append(FlwUtil.repeatChar(' ', maxWidth - FlwUtil.numDigits(i))) + .append("| ") + .append(lines.get(i)) + .append('\n'); + } + + Flywheel.LOGGER.error(builder.toString()); + } + } diff --git a/src/main/java/com/jozufozu/flywheel/backend/source/error/lines/ErrorLine.java b/src/main/java/com/jozufozu/flywheel/backend/source/error/lines/ErrorLine.java index fe3f6ec57..cac9900cb 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/source/error/lines/ErrorLine.java +++ b/src/main/java/com/jozufozu/flywheel/backend/source/error/lines/ErrorLine.java @@ -14,6 +14,10 @@ public interface ErrorLine { return left() + divider() + right(); } - String left(); - String right(); + default String left() { + return ""; + } + default String right() { + return ""; + } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/source/error/lines/HeaderLine.java b/src/main/java/com/jozufozu/flywheel/backend/source/error/lines/HeaderLine.java index 2488095e8..5ed78ec06 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/source/error/lines/HeaderLine.java +++ b/src/main/java/com/jozufozu/flywheel/backend/source/error/lines/HeaderLine.java @@ -11,14 +11,4 @@ public record HeaderLine(String level, CharSequence message) implements ErrorLin public String build() { return level + ": " + message; } - - @Override - public String left() { - return null; - } - - @Override - public String right() { - return null; - } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/source/error/lines/TextLine.java b/src/main/java/com/jozufozu/flywheel/backend/source/error/lines/TextLine.java new file mode 100644 index 000000000..beb66c60c --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/source/error/lines/TextLine.java @@ -0,0 +1,9 @@ +package com.jozufozu.flywheel.backend.source.error.lines; + +public record TextLine(String msg) implements ErrorLine { + + @Override + public String build() { + return msg; + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/source/parse/ShaderFunction.java b/src/main/java/com/jozufozu/flywheel/backend/source/parse/ShaderFunction.java index 1621debf7..92feb12a8 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/source/parse/ShaderFunction.java +++ b/src/main/java/com/jozufozu/flywheel/backend/source/parse/ShaderFunction.java @@ -9,7 +9,7 @@ import com.jozufozu.flywheel.backend.source.span.Span; public class ShaderFunction extends AbstractShaderElement { - public static final Pattern argument = Pattern.compile("(\\w+)\\s+(\\w+)"); + public static final Pattern argument = Pattern.compile("(?:(inout|in|out) )?(\\w+)\\s+(\\w+)"); public static final Pattern assignment = Pattern.compile("(\\w+)\\s*="); private final Span type; @@ -66,10 +66,11 @@ public class ShaderFunction extends AbstractShaderElement { while (arguments.find()) { Span self = Span.fromMatcher(args, arguments); - Span type = Span.fromMatcher(args, arguments, 1); - Span name = Span.fromMatcher(args, arguments, 2); + Span qualifier = Span.fromMatcher(args, arguments, 1); + Span type = Span.fromMatcher(args, arguments, 2); + Span name = Span.fromMatcher(args, arguments, 3); - builder.add(new Variable(self, type, name)); + builder.add(new Variable(self, qualifier, type, name)); } return builder.build(); @@ -79,7 +80,7 @@ public class ShaderFunction extends AbstractShaderElement { public String toString() { String p = parameters.stream() - .map(Variable::typeName) + .map(variable -> variable.type) .map(Span::get) .collect(Collectors.joining(",")); diff --git a/src/main/java/com/jozufozu/flywheel/backend/source/parse/Variable.java b/src/main/java/com/jozufozu/flywheel/backend/source/parse/Variable.java index 270acee61..342684a73 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/source/parse/Variable.java +++ b/src/main/java/com/jozufozu/flywheel/backend/source/parse/Variable.java @@ -4,25 +4,41 @@ import com.jozufozu.flywheel.backend.source.span.Span; public class Variable extends AbstractShaderElement { - private final Span type; - private final Span name; + public final Span qualifierSpan; + public final Span type; + public final Span name; + public final Qualifier qualifier; - public Variable(Span self, Span type, Span name) { + public Variable(Span self, Span qualifier, Span type, Span name) { super(self); + this.qualifierSpan = qualifier; this.type = type; this.name = name; - } - - public Span typeName() { - return type; - } - - public Span getName() { - return name; + this.qualifier = Qualifier.fromSpan(qualifierSpan); } @Override public String toString() { return type + " " + name; } + + public enum Qualifier { + NONE, + IN, + OUT, + INOUT, + ERROR; + + public static Qualifier fromSpan(Span s) { + String span = s.toString(); + + return switch (span) { + case "" -> NONE; + case "in" -> IN; + case "inout" -> INOUT; + case "out" -> OUT; + default -> ERROR; + }; + } + } } diff --git a/src/main/java/com/jozufozu/flywheel/core/Contexts.java b/src/main/java/com/jozufozu/flywheel/core/Contexts.java index 744ef5ae6..3261cd19d 100644 --- a/src/main/java/com/jozufozu/flywheel/core/Contexts.java +++ b/src/main/java/com/jozufozu/flywheel/core/Contexts.java @@ -3,7 +3,6 @@ package com.jozufozu.flywheel.core; import com.jozufozu.flywheel.Flywheel; import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.GameStateRegistry; -import com.jozufozu.flywheel.backend.pipeline.InstancingTemplate; import com.jozufozu.flywheel.backend.pipeline.ShaderPipeline; import com.jozufozu.flywheel.backend.pipeline.WorldShaderPipeline; import com.jozufozu.flywheel.backend.source.FileResolution; @@ -32,8 +31,8 @@ public class Contexts { FileResolution crumblingBuiltins = Resolver.INSTANCE.findShader(ResourceUtil.subPath(Names.CRUMBLING, ".glsl")); FileResolution worldBuiltins = Resolver.INSTANCE.findShader(ResourceUtil.subPath(Names.WORLD, ".glsl")); - ShaderPipeline crumblingPipeline = new WorldShaderPipeline<>(CrumblingProgram::new, InstancingTemplate.INSTANCE, crumblingBuiltins); - ShaderPipeline worldPipeline = new WorldShaderPipeline<>(WorldProgram::new, InstancingTemplate.INSTANCE, worldBuiltins); + ShaderPipeline crumblingPipeline = new WorldShaderPipeline<>(CrumblingProgram::new, Templates.INSTANCING, crumblingBuiltins); + ShaderPipeline worldPipeline = new WorldShaderPipeline<>(WorldProgram::new, Templates.INSTANCING, worldBuiltins); CRUMBLING = backend.register(WorldContext.builder(backend, Names.CRUMBLING).build(crumblingPipeline)); WORLD = backend.register(WorldContext.builder(backend, Names.WORLD).build(worldPipeline)); diff --git a/src/main/java/com/jozufozu/flywheel/core/Templates.java b/src/main/java/com/jozufozu/flywheel/core/Templates.java new file mode 100644 index 000000000..7bd3f718f --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/core/Templates.java @@ -0,0 +1,12 @@ +package com.jozufozu.flywheel.core; + +import com.jozufozu.flywheel.backend.gl.GLSLVersion; +import com.jozufozu.flywheel.backend.pipeline.InstancingTemplateData; +import com.jozufozu.flywheel.backend.pipeline.OneShotTemplateData; +import com.jozufozu.flywheel.backend.pipeline.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); +} diff --git a/src/main/java/com/jozufozu/flywheel/core/WorldContext.java b/src/main/java/com/jozufozu/flywheel/core/WorldContext.java index 5306d605c..d9b608608 100644 --- a/src/main/java/com/jozufozu/flywheel/core/WorldContext.java +++ b/src/main/java/com/jozufozu/flywheel/core/WorldContext.java @@ -6,11 +6,13 @@ import java.util.Objects; import java.util.function.Supplier; import java.util.stream.Stream; +import com.jozufozu.flywheel.api.shader.FlexibleShader; import com.jozufozu.flywheel.api.struct.Instanced; import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.ShaderContext; +import com.jozufozu.flywheel.backend.pipeline.LazyCompiler; import com.jozufozu.flywheel.backend.pipeline.ShaderPipeline; -import com.jozufozu.flywheel.core.shader.ContextAwareProgram; +import com.jozufozu.flywheel.backend.source.ShaderLoadingException; import com.jozufozu.flywheel.core.shader.WorldProgram; import com.jozufozu.flywheel.core.shader.spec.ProgramSpec; @@ -18,7 +20,7 @@ import net.minecraft.resources.ResourceLocation; public class WorldContext

implements ShaderContext

{ public final Backend backend; - protected final Map> programs = new HashMap<>(); + protected final Map> programs = new HashMap<>(); protected final ResourceLocation name; protected final Supplier> specStream; @@ -44,25 +46,27 @@ public class WorldContext

implements ShaderContext

{ private void loadSpec(ProgramSpec spec) { try { - programs.put(spec.name, pipeline.compile(spec)); + programs.put(spec.name, new LazyCompiler<>(pipeline, spec)); Backend.log.debug("Loaded program {}", spec.name); } catch (Exception e) { Backend.log.error("Error loading program {}", spec.name); - Backend.log.error("", e); + if (!(e instanceof ShaderLoadingException)) { + Backend.log.error("", e); + } backend.loader.notifyError(); } } @Override - public Supplier

getProgramSupplier(ResourceLocation spec) { + public FlexibleShader

getProgramSupplier(ResourceLocation spec) { return programs.get(spec); } @Override public void delete() { programs.values() - .forEach(ContextAwareProgram::delete); + .forEach(LazyCompiler::delete); programs.clear(); } diff --git a/src/main/java/com/jozufozu/flywheel/core/layout/BufferLayout.java b/src/main/java/com/jozufozu/flywheel/core/layout/BufferLayout.java index d7111f706..ecdde9ea3 100644 --- a/src/main/java/com/jozufozu/flywheel/core/layout/BufferLayout.java +++ b/src/main/java/com/jozufozu/flywheel/core/layout/BufferLayout.java @@ -27,8 +27,8 @@ public class BufferLayout { int numAttributes = 0, stride = 0; for (LayoutItem spec : allAttributes) { - numAttributes += spec.getAttributeCount(); - stride += spec.getSize(); + numAttributes += spec.attributeCount(); + stride += spec.size(); } this.numAttributes = numAttributes; this.stride = stride; diff --git a/src/main/java/com/jozufozu/flywheel/core/layout/CommonItems.java b/src/main/java/com/jozufozu/flywheel/core/layout/CommonItems.java index 1021296ac..232879964 100644 --- a/src/main/java/com/jozufozu/flywheel/core/layout/CommonItems.java +++ b/src/main/java/com/jozufozu/flywheel/core/layout/CommonItems.java @@ -19,6 +19,6 @@ public class CommonItems { public static final PrimitiveItem LIGHT_SHORT = new PrimitiveItem(GlNumericType.USHORT, 2, true); public static final PrimitiveItem NORMALIZED_BYTE = new PrimitiveItem(GlNumericType.BYTE, 1, true); - public static final LayoutItem PADDING_BYTE = new PaddingItem(1); + public static final LayoutItem PADDING_BYTE = new Padding(1); } diff --git a/src/main/java/com/jozufozu/flywheel/core/layout/LayoutItem.java b/src/main/java/com/jozufozu/flywheel/core/layout/LayoutItem.java index ae910dc51..cc0f6fabe 100644 --- a/src/main/java/com/jozufozu/flywheel/core/layout/LayoutItem.java +++ b/src/main/java/com/jozufozu/flywheel/core/layout/LayoutItem.java @@ -4,7 +4,7 @@ public interface LayoutItem { void vertexAttribPointer(int stride, int index, int offset); - int getSize(); + int size(); - int getAttributeCount(); + int attributeCount(); } diff --git a/src/main/java/com/jozufozu/flywheel/core/layout/MatrixItems.java b/src/main/java/com/jozufozu/flywheel/core/layout/MatrixItems.java index f9d8ffe2e..1ca6f2fec 100644 --- a/src/main/java/com/jozufozu/flywheel/core/layout/MatrixItems.java +++ b/src/main/java/com/jozufozu/flywheel/core/layout/MatrixItems.java @@ -26,12 +26,13 @@ public enum MatrixItems implements LayoutItem { } @Override - public int getSize() { + public int size() { return GlNumericType.FLOAT.getByteWidth() * rows * cols; } @Override - public int getAttributeCount() { + public int attributeCount() { return rows; } + } diff --git a/src/main/java/com/jozufozu/flywheel/core/layout/PaddingItem.java b/src/main/java/com/jozufozu/flywheel/core/layout/Padding.java similarity index 62% rename from src/main/java/com/jozufozu/flywheel/core/layout/PaddingItem.java rename to src/main/java/com/jozufozu/flywheel/core/layout/Padding.java index 788aa9b3d..7b3abbed3 100644 --- a/src/main/java/com/jozufozu/flywheel/core/layout/PaddingItem.java +++ b/src/main/java/com/jozufozu/flywheel/core/layout/Padding.java @@ -1,6 +1,6 @@ package com.jozufozu.flywheel.core.layout; -record PaddingItem(int bytes) implements LayoutItem { +record Padding(int bytes) implements LayoutItem { @Override public void vertexAttribPointer(int stride, int index, int offset) { @@ -8,12 +8,13 @@ record PaddingItem(int bytes) implements LayoutItem { } @Override - public int getSize() { + public int size() { return bytes; } @Override - public int getAttributeCount() { + public int attributeCount() { return 0; } + } diff --git a/src/main/java/com/jozufozu/flywheel/core/layout/PrimitiveItem.java b/src/main/java/com/jozufozu/flywheel/core/layout/PrimitiveItem.java index be1373867..c7c1bd6c0 100644 --- a/src/main/java/com/jozufozu/flywheel/core/layout/PrimitiveItem.java +++ b/src/main/java/com/jozufozu/flywheel/core/layout/PrimitiveItem.java @@ -30,12 +30,13 @@ public class PrimitiveItem implements LayoutItem { } @Override - public int getSize() { + public int size() { return size; } @Override - public int getAttributeCount() { + public int attributeCount() { return attributeCount; } + } diff --git a/src/main/java/com/jozufozu/flywheel/core/vertex/BlockVertex.java b/src/main/java/com/jozufozu/flywheel/core/vertex/BlockVertex.java index eb77286fc..4a4cb1021 100644 --- a/src/main/java/com/jozufozu/flywheel/core/vertex/BlockVertex.java +++ b/src/main/java/com/jozufozu/flywheel/core/vertex/BlockVertex.java @@ -4,8 +4,8 @@ import java.nio.ByteBuffer; import com.jozufozu.flywheel.api.vertex.VertexList; import com.jozufozu.flywheel.api.vertex.VertexType; -import com.jozufozu.flywheel.core.layout.CommonItems; import com.jozufozu.flywheel.core.layout.BufferLayout; +import com.jozufozu.flywheel.core.layout.CommonItems; import com.mojang.blaze3d.vertex.BufferBuilder; import com.mojang.blaze3d.vertex.DefaultVertexFormat; import com.mojang.datafixers.util.Pair; @@ -36,6 +36,27 @@ public class BlockVertex implements VertexType { return new BlockVertexListUnsafe(buffer, vertexCount); } + @Override + public String writeShaderHeader() { + return """ +layout (location = 0) in vec3 _flw_v_pos; +layout (location = 1) in vec4 _flw_v_color; +layout (location = 2) in vec2 _flw_v_texCoords; +layout (location = 3) in vec2 _flw_v_light; +layout (location = 4) in vec3 _flw_v_normal; + +Vertex FLWCreateVertex() { + Vertex v; + v.pos = _flw_v_pos; + v.color = _flw_v_color; + v.texCoords = _flw_v_texCoords; + v.light = _flw_v_light; + v.normal = _flw_v_normal; + return v; +} + """; + } + public VertexList createReader(BufferBuilder bufferBuilder) { // TODO: try to avoid virtual model rendering Pair pair = bufferBuilder.popNextBuffer(); diff --git a/src/main/java/com/jozufozu/flywheel/core/vertex/PosTexNormalVertex.java b/src/main/java/com/jozufozu/flywheel/core/vertex/PosTexNormalVertex.java index feb7ff348..63610ee87 100644 --- a/src/main/java/com/jozufozu/flywheel/core/vertex/PosTexNormalVertex.java +++ b/src/main/java/com/jozufozu/flywheel/core/vertex/PosTexNormalVertex.java @@ -3,8 +3,8 @@ package com.jozufozu.flywheel.core.vertex; import java.nio.ByteBuffer; import com.jozufozu.flywheel.api.vertex.VertexType; -import com.jozufozu.flywheel.core.layout.CommonItems; import com.jozufozu.flywheel.core.layout.BufferLayout; +import com.jozufozu.flywheel.core.layout.CommonItems; public class PosTexNormalVertex implements VertexType { @@ -26,4 +26,23 @@ public class PosTexNormalVertex implements VertexType { public PosTexNormalVertexListUnsafe createReader(ByteBuffer buffer, int vertexCount) { return new PosTexNormalVertexListUnsafe(buffer, vertexCount); } + + @Override + public String writeShaderHeader() { + return """ +layout (location = 0) in vec3 _flw_v_pos; +layout (location = 1) in vec2 _flw_v_texCoords; +layout (location = 2) in vec3 _flw_v_normal; + +Vertex FLWCreateVertex() { + Vertex v; + v.pos = _flw_v_pos; + v.color = vec4(1.); + v.texCoords = _flw_v_texCoords; + v.light = vec2(0.); + v.normal = _flw_v_normal; + return v; +} + """; + } } diff --git a/src/main/resources/assets/flywheel/flywheel/shaders/block.frag b/src/main/resources/assets/flywheel/flywheel/shaders/block.frag index d8e5169fe..70bbe95f4 100644 --- a/src/main/resources/assets/flywheel/flywheel/shaders/block.frag +++ b/src/main/resources/assets/flywheel/flywheel/shaders/block.frag @@ -1,5 +1,5 @@ -struct BlockFrag { +struct Fragment { vec2 texCoords; vec4 color; float diffuse; @@ -7,11 +7,9 @@ struct BlockFrag { }; #if defined(FRAGMENT_SHADER) -void fragment(BlockFrag r) { +vec4 fragment(Fragment r) { vec4 tex = FLWBlockTexture(r.texCoords); - vec4 color = vec4(tex.rgb * FLWLight(r.light).rgb * r.diffuse, tex.a) * r.color; - - FLWFinalizeColor(color); + return vec4(tex.rgb * FLWLight(r.light).rgb * r.diffuse, tex.a) * r.color; } #endif diff --git a/src/main/resources/assets/flywheel/flywheel/shaders/context/crumbling.glsl b/src/main/resources/assets/flywheel/flywheel/shaders/context/crumbling.glsl index c9ab41cbe..958941126 100644 --- a/src/main/resources/assets/flywheel/flywheel/shaders/context/crumbling.glsl +++ b/src/main/resources/assets/flywheel/flywheel/shaders/context/crumbling.glsl @@ -1,4 +1,5 @@ #use "flywheel:context/fog.glsl" +#use "flywheel:core/diffuse.glsl" uniform float uTime; uniform mat4 uViewProjection; @@ -11,17 +12,11 @@ uniform sampler2D uCrumbling; uniform vec2 uWindowSize; -void FLWFinalizeNormal(inout vec3 normal) { - // noop -} - #if defined(VERTEX_SHADER) -void FLWFinalizeWorldPos(inout vec4 worldPos) { - #if defined(USE_FOG) - FragDistance = cylindrical_distance(worldPos.xyz, uCameraPos); - #endif +vec4 FLWVertex(inout Vertex v) { + FragDistance = cylindrical_distance(v.pos, uCameraPos); - gl_Position = uViewProjection * worldPos; + return uViewProjection * vec4(v.pos, 1.); } #elif defined(FRAGMENT_SHADER) diff --git a/src/main/resources/assets/flywheel/flywheel/shaders/context/world.glsl b/src/main/resources/assets/flywheel/flywheel/shaders/context/world.glsl index 1503ed993..58ad850b6 100644 --- a/src/main/resources/assets/flywheel/flywheel/shaders/context/world.glsl +++ b/src/main/resources/assets/flywheel/flywheel/shaders/context/world.glsl @@ -1,4 +1,5 @@ #use "flywheel:context/fog.glsl" +#use "flywheel:core/diffuse.glsl" uniform float uTime; uniform mat4 uViewProjection; @@ -10,15 +11,12 @@ uniform sampler2D uLightMap; uniform vec2 uWindowSize; -void FLWFinalizeNormal(inout vec3 normal) { - // noop -} - #if defined(VERTEX_SHADER) -void FLWFinalizeWorldPos(inout vec4 worldPos) { - FragDistance = cylindrical_distance(worldPos.xyz, uCameraPos); - gl_Position = uViewProjection * worldPos; +vec4 FLWVertex(inout Vertex v) { + FragDistance = cylindrical_distance(v.pos, uCameraPos); + + return uViewProjection * vec4(v.pos, 1.); } #elif defined(FRAGMENT_SHADER) diff --git a/src/main/resources/assets/flywheel/flywheel/shaders/data/modelvertex.glsl b/src/main/resources/assets/flywheel/flywheel/shaders/data/modelvertex.glsl deleted file mode 100644 index 48e713b43..000000000 --- a/src/main/resources/assets/flywheel/flywheel/shaders/data/modelvertex.glsl +++ /dev/null @@ -1,5 +0,0 @@ -struct Vertex { - vec3 pos; - vec2 texCoords; - vec3 normal; -}; diff --git a/src/main/resources/assets/flywheel/flywheel/shaders/model.vert b/src/main/resources/assets/flywheel/flywheel/shaders/model.vert index 603d17e54..075269708 100644 --- a/src/main/resources/assets/flywheel/flywheel/shaders/model.vert +++ b/src/main/resources/assets/flywheel/flywheel/shaders/model.vert @@ -1,7 +1,7 @@ -#use "flywheel:core/diffuse.glsl" -#use "flywheel:data/modelvertex.glsl" #use "flywheel:block.frag" +#if defined(VERTEX_SHADER) + struct Instance { vec2 light; vec4 color; @@ -9,26 +9,10 @@ struct Instance { mat3 normalMat; }; -#if defined(VERTEX_SHADER) -BlockFrag vertex(Vertex v, Instance i) { - vec4 worldPos = i.transform * vec4(v.pos, 1.); - - vec3 norm = i.normalMat * v.normal; - - FLWFinalizeWorldPos(worldPos); - FLWFinalizeNormal(norm); - - norm = normalize(norm); - - BlockFrag b; - b.diffuse = diffuse(norm); - b.texCoords = v.texCoords; - b.light = i.light; - #if defined(DEBUG_NORMAL) - b.color = vec4(norm, 1.); - #else - b.color = i.color; - #endif - return b; +void vertex(inout Vertex v, Instance i) { + v.pos = (i.transform * vec4(v.pos, 1.)).xyz; + v.normal = i.normalMat * v.normal; + v.color = i.color; + v.light = i.light; } #endif diff --git a/src/main/resources/assets/flywheel/flywheel/shaders/oriented.vert b/src/main/resources/assets/flywheel/flywheel/shaders/oriented.vert index bac28858b..b89479a8b 100644 --- a/src/main/resources/assets/flywheel/flywheel/shaders/oriented.vert +++ b/src/main/resources/assets/flywheel/flywheel/shaders/oriented.vert @@ -1,9 +1,8 @@ -#use "flywheel:core/quaternion.glsl" -#use "flywheel:core/diffuse.glsl" - -#use "flywheel:data/modelvertex.glsl" #use "flywheel:block.frag" +#if defined(VERTEX_SHADER) +#use "flywheel:core/quaternion.glsl" + struct Oriented { vec2 light; vec4 color; @@ -12,24 +11,10 @@ struct Oriented { vec4 rotation; }; -#if defined(VERTEX_SHADER) -BlockFrag vertex(Vertex v, Oriented o) { - vec4 worldPos = vec4(rotateVertexByQuat(v.pos - o.pivot, o.rotation) + o.pivot + o.pos, 1.); - - vec3 norm = rotateVertexByQuat(v.normal, o.rotation); - - FLWFinalizeWorldPos(worldPos); - FLWFinalizeNormal(norm); - - BlockFrag b; - b.diffuse = diffuse(norm); - b.texCoords = v.texCoords; - b.light = o.light; - #if defined(DEBUG_NORMAL) - b.color = vec4(norm, 1.); - #else - b.color = o.color; - #endif - return b; +void vertex(inout Vertex v, Oriented o) { + v.pos = rotateVertexByQuat(v.pos - o.pivot, o.rotation) + o.pivot + o.pos; + v.normal = rotateVertexByQuat(v.normal, o.rotation); + v.color = o.color; + v.light = o.light; } #endif diff --git a/src/main/resources/assets/flywheel/flywheel/shaders/smooth_oriented.glsl b/src/main/resources/assets/flywheel/flywheel/shaders/smooth_oriented.glsl deleted file mode 100644 index c657678aa..000000000 --- a/src/main/resources/assets/flywheel/flywheel/shaders/smooth_oriented.glsl +++ /dev/null @@ -1,53 +0,0 @@ -#use "flywheel:core/matutils.glsl" -#use "flywheel:core/quaternion.glsl" -#use "flywheel:core/diffuse.glsl" - -struct Oriented { -// each vec 4 is 2 light coords packed -// x z - vec4 lightA;// lo, lo - vec4 lightB;// hi, lo - vec4 lightC;// hi, hi - vec4 lightD;// lo, hi - - vec3 loCorner; - vec3 size; - - vec4 color; - vec3 pos; - vec3 pivot; - vec4 rotation; -}; - -#use "flywheel:data/modelvertex.glsl" -#use "flywheel:block.frag" - -BlockFrag vertex(Vertex v, Oriented o) { - vec4 worldPos = vec4(rotateVertexByQuat(v.pos - o.pivot, o.rotation) + o.pivot + o.pos, 1.); - - vec3 norm = rotateVertexByQuat(v.normal, o.rotation); - - FLWFinalizeWorldPos(worldPos); - FLWFinalizeNormal(norm); - - // manual trilinear interpolation - vec3 lightPos = (worldPos.xyz - o.loCorner) / o.size; - - vec4 lightLoZ = mix(lightA, lightB, lightPos.x);// lo z - vec4 lightHiZ = mix(lightD, lightC, lightPos.x);// hi z - - vec4 lightE = mix(lightLoZ, lightHiZ, lightPos.z);// - - vec2 lightCoord = mix(lightE.xy, lightE.zw, lightPos.y); - - BlockFrag b; - b.diffuse = diffuse(norm); - b.texCoords = v.texCoords; - b.light = lightCoord; - #if defined(DEBUG_NORMAL) - b.color = vec4(norm, 1.); - #else - b.color = o.color; - #endif - return b; -}