diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/AbstractCompiler.java b/src/main/java/com/jozufozu/flywheel/backend/compile/AbstractCompiler.java new file mode 100644 index 000000000..20711942b --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/AbstractCompiler.java @@ -0,0 +1,47 @@ +package com.jozufozu.flywheel.backend.compile; + +import java.util.HashMap; +import java.util.Map; + +import org.jetbrains.annotations.Nullable; + +import com.google.common.collect.ImmutableList; +import com.jozufozu.flywheel.gl.shader.GlProgram; +import com.jozufozu.flywheel.glsl.ShaderSources; + +public abstract class AbstractCompiler { + protected final ShaderSources sources; + protected final ShaderCompiler shaderCompiler; + protected final ProgramLinker programLinker; + private final ImmutableList keys; + private final CompilerStats stats = new CompilerStats(); + + public AbstractCompiler(ShaderSources sources, ImmutableList keys) { + this.sources = sources; + this.keys = keys; + + shaderCompiler = ShaderCompiler.builder() + .build(stats); + programLinker = new ProgramLinker(stats); + } + + @Nullable + protected abstract GlProgram compile(K key); + + public Map compile() { + stats.start(); + Map out = new HashMap<>(); + for (var key : keys) { + GlProgram glProgram = compile(key); + if (glProgram != null) { + out.put(key, glProgram); + } + } + stats.finish(); + return out; + } + + public void delete() { + shaderCompiler.delete(); + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/Compilation.java b/src/main/java/com/jozufozu/flywheel/backend/compile/Compilation.java index a4128cea5..d925fd3be 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/compile/Compilation.java +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/Compilation.java @@ -46,7 +46,7 @@ public class Compilation { } @NotNull - public CompilationResult compile() { + public ShaderResult compile() { int handle = GL20.glCreateShader(shaderType.glEnum); var source = fullSource.toString(); @@ -56,13 +56,14 @@ public class Compilation { var shaderName = buildShaderName(); dumpSource(source, shaderType.getFileName(shaderName)); + var infoLog = GL20.glGetShaderInfoLog(handle); + if (compiledSuccessfully(handle)) { - return CompilationResult.success(new GlShader(handle, shaderType, shaderName)); + return ShaderResult.success(new GlShader(handle, shaderType, shaderName), infoLog); } - var errorLog = GL20.glGetShaderInfoLog(handle); GL20.glDeleteShader(handle); - return CompilationResult.failure(new FailedCompilation(shaderName, files, generatedSource.toString(), errorLog)); + return ShaderResult.failure(new FailedCompilation(shaderName, files, generatedSource.toString(), infoLog)); } public void enableExtension(String ext) { diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/CompilationResult.java b/src/main/java/com/jozufozu/flywheel/backend/compile/CompilationResult.java deleted file mode 100644 index 21149cde4..000000000 --- a/src/main/java/com/jozufozu/flywheel/backend/compile/CompilationResult.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.jozufozu.flywheel.backend.compile; - -import org.jetbrains.annotations.Nullable; - -import com.jozufozu.flywheel.gl.shader.GlShader; - -public sealed interface CompilationResult { - @Nullable - default GlShader unwrap() { - if (this instanceof Success s) { - return s.shader(); - } - return null; - } - - record Success(GlShader shader) implements CompilationResult { - } - - record Failure(FailedCompilation failure) implements CompilationResult { - } - - static CompilationResult success(GlShader program) { - return new Success(program); - } - - static CompilationResult failure(FailedCompilation failure) { - return new Failure(failure); - } -} diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/CompileUtil.java b/src/main/java/com/jozufozu/flywheel/backend/compile/CompileUtil.java index 58b27c65f..053de2709 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/compile/CompileUtil.java +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/CompileUtil.java @@ -1,11 +1,5 @@ package com.jozufozu.flywheel.backend.compile; -import static org.lwjgl.opengl.GL11.GL_TRUE; -import static org.lwjgl.opengl.GL20.GL_LINK_STATUS; -import static org.lwjgl.opengl.GL20.glGetProgramInfoLog; -import static org.lwjgl.opengl.GL20.glGetProgrami; -import static org.lwjgl.opengl.GL20.glLinkProgram; - import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -13,7 +7,6 @@ import java.util.stream.Stream; import org.jetbrains.annotations.NotNull; -import com.jozufozu.flywheel.Flywheel; import com.jozufozu.flywheel.gl.GLSLVersion; import com.jozufozu.flywheel.gl.shader.ShaderType; import com.jozufozu.flywheel.glsl.SourceFile; @@ -64,25 +57,4 @@ public class CompileUtil { .map(SourceFile::toString) .collect(Collectors.joining(" -> ")); } - - /** - * Check the program info log for errors. - * - * @param handle The handle of the program to check. - */ - public static void checkLinkLog(int handle) { - glLinkProgram(handle); - - String log = glGetProgramInfoLog(handle); - - if (!log.isEmpty()) { - Flywheel.LOGGER.debug("Program link log: " + log); - } - - int result = glGetProgrami(handle, GL_LINK_STATUS); - - if (result != GL_TRUE) { - throw new RuntimeException("Shader program linking failed, see log for details"); - } - } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/CompilerStats.java b/src/main/java/com/jozufozu/flywheel/backend/compile/CompilerStats.java new file mode 100644 index 000000000..0f73fad3f --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/CompilerStats.java @@ -0,0 +1,57 @@ +package com.jozufozu.flywheel.backend.compile; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.jozufozu.flywheel.Flywheel; +import com.jozufozu.flywheel.util.StringUtil; + +public class CompilerStats { + private long compileStart; + + private final List shaderErrors = new ArrayList<>(); + private final List programErrors = new ArrayList<>(); + private boolean errored = false; + private int shaderCount = 0; + private int programCount = 0; + + public void start() { + compileStart = System.nanoTime(); + } + + public void finish() { + long compileEnd = System.nanoTime(); + var elapsed = StringUtil.formatTime(compileEnd - compileStart); + + Flywheel.LOGGER.info("Compiled " + shaderCount + " shaders (with " + shaderErrors.size() + " compile errors) " + "and " + programCount + " programs (with " + programErrors.size() + " link errors) in " + elapsed); + + } + + // TODO: use this to turn off backends + public boolean errored() { + return errored; + } + + private String generateLog() { + return String.join("\n", programErrors) + '\n' + shaderErrors.stream() + .map(FailedCompilation::getMessage) + .collect(Collectors.joining("\n")); + } + + public void shaderResult(ShaderResult result) { + if (result instanceof ShaderResult.Failure f) { + shaderErrors.add(f.failure()); + errored = true; + } + shaderCount++; + } + + public void linkResult(LinkResult linkResult) { + if (linkResult instanceof LinkResult.Failure f) { + programErrors.add(f.failure()); + errored = true; + } + programCount++; + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/CullingCompiler.java b/src/main/java/com/jozufozu/flywheel/backend/compile/CullingCompiler.java new file mode 100644 index 000000000..b0ba8cc2d --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/CullingCompiler.java @@ -0,0 +1,52 @@ +package com.jozufozu.flywheel.backend.compile; + +import org.jetbrains.annotations.Nullable; + +import com.google.common.collect.ImmutableList; +import com.jozufozu.flywheel.Flywheel; +import com.jozufozu.flywheel.api.instance.InstanceType; +import com.jozufozu.flywheel.backend.engine.indirect.IndirectComponent; +import com.jozufozu.flywheel.gl.GLSLVersion; +import com.jozufozu.flywheel.gl.shader.GlProgram; +import com.jozufozu.flywheel.gl.shader.ShaderType; +import com.jozufozu.flywheel.glsl.ShaderSources; +import com.jozufozu.flywheel.glsl.SourceComponent; +import com.jozufozu.flywheel.glsl.SourceFile; + +import net.minecraft.resources.ResourceLocation; + +public class CullingCompiler extends AbstractCompiler> { + private final UniformComponent uniformComponent; + private final SourceFile pipelineCompute; + + public CullingCompiler(ShaderSources sources, ImmutableList> keys, UniformComponent uniformComponent) { + super(sources, keys); + + this.uniformComponent = uniformComponent; + pipelineCompute = sources.find(Files.INDIRECT_CULL); + } + + @Nullable + @Override + protected GlProgram compile(InstanceType key) { + var computeComponents = getComputeComponents(key); + var compute = shaderCompiler.compile(GLSLVersion.V460, ShaderType.COMPUTE, computeComponents); + + if (compute == null) { + return null; + } + + return programLinker.link(compute); + } + + private ImmutableList getComputeComponents(InstanceType instanceType) { + var instanceAssembly = new IndirectComponent(sources, instanceType); + var instance = sources.find(instanceType.instanceShader()); + + return ImmutableList.of(uniformComponent, instanceAssembly, instance, pipelineCompute); + } + + private static final class Files { + public static final ResourceLocation INDIRECT_CULL = Flywheel.rl("internal/indirect_cull.glsl"); + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/FlwCompiler.java b/src/main/java/com/jozufozu/flywheel/backend/compile/FlwCompiler.java deleted file mode 100644 index da57af321..000000000 --- a/src/main/java/com/jozufozu/flywheel/backend/compile/FlwCompiler.java +++ /dev/null @@ -1,213 +0,0 @@ -package com.jozufozu.flywheel.backend.compile; - -import static org.lwjgl.opengl.GL20.glAttachShader; -import static org.lwjgl.opengl.GL20.glCreateProgram; -import static org.lwjgl.opengl.GL20.glLinkProgram; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import org.jetbrains.annotations.Nullable; - -import com.google.common.collect.ImmutableList; -import com.jozufozu.flywheel.Flywheel; -import com.jozufozu.flywheel.api.instance.InstanceType; -import com.jozufozu.flywheel.api.uniform.ShaderUniforms; -import com.jozufozu.flywheel.backend.compile.FlwPrograms.PipelineProgramKey; -import com.jozufozu.flywheel.backend.compile.pipeline.Pipeline; -import com.jozufozu.flywheel.backend.engine.indirect.IndirectComponent; -import com.jozufozu.flywheel.gl.GLSLVersion; -import com.jozufozu.flywheel.gl.shader.GlProgram; -import com.jozufozu.flywheel.gl.shader.ShaderType; -import com.jozufozu.flywheel.glsl.ShaderLoadingException; -import com.jozufozu.flywheel.glsl.ShaderSources; -import com.jozufozu.flywheel.glsl.SourceComponent; -import com.jozufozu.flywheel.glsl.generate.FnSignature; -import com.jozufozu.flywheel.glsl.generate.GlslExpr; -import com.jozufozu.flywheel.lib.material.MaterialIndices; -import com.jozufozu.flywheel.util.StringUtil; - -import net.minecraft.resources.ResourceLocation; - -public class FlwCompiler { - private final long compileStart = System.nanoTime(); - - private final ShaderSources sources; - - private final ImmutableList pipelineKeys; - private final ImmutableList> cullingKeys; - - private final ShaderCompiler shaderCompiler; - private final List errors = new ArrayList<>(); - - private final MaterialAdapterComponent vertexMaterialComponent; - private final MaterialAdapterComponent fragmentMaterialComponent; - private final UniformComponent uniformComponent; - - private final Map pipelinePrograms = new HashMap<>(); - private final Map, GlProgram> cullingPrograms = new HashMap<>(); - - public FlwCompiler(ShaderSources sources, ImmutableList pipelineKeys, ImmutableList> cullingKeys) { - this.sources = sources; - - this.pipelineKeys = pipelineKeys; - this.cullingKeys = cullingKeys; - - shaderCompiler = ShaderCompiler.builder() - .errorConsumer(errors::add) - .build(); - - vertexMaterialComponent = MaterialAdapterComponent.builder(Flywheel.rl("vertex_material_adapter")) - .materialSources(MaterialIndices.getAllVertexShaders()) - .adapt(FnSignature.ofVoid("flw_materialVertex")) - .switchOn(GlslExpr.variable("_flw_materialVertexID")) - .build(sources); - fragmentMaterialComponent = MaterialAdapterComponent.builder(Flywheel.rl("fragment_material_adapter")) - .materialSources(MaterialIndices.getAllFragmentShaders()) - .adapt(FnSignature.ofVoid("flw_materialFragment")) - .adapt(FnSignature.create() - .returnType("bool") - .name("flw_discardPredicate") - .arg("vec4", "color") - .build(), GlslExpr.literal(false)) - .adapt(FnSignature.create() - .returnType("vec4") - .name("flw_fogFilter") - .arg("vec4", "color") - .build(), GlslExpr.variable("color")) - .switchOn(GlslExpr.variable("_flw_materialFragmentID")) - .build(sources); - uniformComponent = UniformComponent.builder(Flywheel.rl("uniforms")) - .sources(ShaderUniforms.REGISTRY.getAll() - .stream() - .map(ShaderUniforms::uniformShader) - .toList()) - .build(sources); - } - - public FlwPrograms compile() { - doCompilation(); - - finish(); - - return new FlwPrograms(pipelinePrograms, cullingPrograms); - } - - private void doCompilation() { - for (var key : pipelineKeys) { - GlProgram glProgram = compilePipelineProgram(key); - if (glProgram != null) { - pipelinePrograms.put(key, glProgram); - } - } - - for (var key : cullingKeys) { - GlProgram glProgram = compileCullingProgram(key); - if (glProgram != null) { - cullingPrograms.put(key, glProgram); - } - } - } - - private static GlProgram link(int... shaders) { - var handle = glCreateProgram(); - for (var shader : shaders) { - glAttachShader(handle, shader); - } - glLinkProgram(handle); - CompileUtil.checkLinkLog(handle); - return new GlProgram(handle); - } - - @Nullable - private GlProgram compilePipelineProgram(PipelineProgramKey key) { - var glslVersion = key.pipelineShader() - .glslVersion(); - - var vertex = shaderCompiler.compile(glslVersion, ShaderType.VERTEX, getVertexComponents(key)); - var fragment = shaderCompiler.compile(glslVersion, ShaderType.FRAGMENT, getFragmentComponents(key)); - - if (vertex == null || fragment == null) { - return null; - } - - var glProgram = link(vertex.handle(), fragment.handle()); - key.contextShader() - .onProgramLink(glProgram); - return glProgram; - } - - private ImmutableList getVertexComponents(PipelineProgramKey key) { - var instanceAssembly = key.pipelineShader() - .assembler() - .assemble(new Pipeline.InstanceAssemblerContext(sources, key.vertexType(), key.instanceType())); - - var layout = sources.find(key.vertexType() - .layoutShader()); - var instance = sources.find(key.instanceType() - .instanceShader()); - var context = sources.find(key.contextShader() - .vertexShader()); - var pipeline = sources.find(key.pipelineShader() - .vertexShader()); - - return ImmutableList.of(uniformComponent, vertexMaterialComponent, instanceAssembly, layout, instance, context, pipeline); - } - - private ImmutableList getFragmentComponents(PipelineProgramKey key) { - var context = sources.find(key.contextShader() - .fragmentShader()); - var pipeline = sources.find(key.pipelineShader() - .fragmentShader()); - return ImmutableList.of(uniformComponent, fragmentMaterialComponent, context, pipeline); - } - - @Nullable - private GlProgram compileCullingProgram(InstanceType key) { - var computeComponents = getComputeComponents(key); - var result = shaderCompiler.compile(GLSLVersion.V460, ShaderType.COMPUTE, computeComponents); - - if (result == null) { - return null; - } - - return link(result.handle()); - } - - private ImmutableList getComputeComponents(InstanceType instanceType) { - var instanceAssembly = new IndirectComponent(sources, instanceType); - var instance = sources.find(instanceType.instanceShader()); - var pipeline = sources.find(Files.INDIRECT_CULL); - - return ImmutableList.of(uniformComponent, instanceAssembly, instance, pipeline); - } - - private void finish() { - long compileEnd = System.nanoTime(); - int programCount = pipelineKeys.size() + cullingKeys.size(); - int shaderCount = shaderCompiler.shaderCount(); - int errorCount = errors.size(); - var elapsed = StringUtil.formatTime(compileEnd - compileStart); - - Flywheel.LOGGER.info("Compiled " + programCount + " programs and " + shaderCount + " shaders in " + elapsed + " with " + errorCount + " errors."); - - if (errorCount > 0) { - var details = errors.stream() - .map(FailedCompilation::getMessage) - .collect(Collectors.joining("\n")); - // TODO: disable backend instead of crashing if compilation fails - throw new ShaderLoadingException("Compilation failed.\n" + details); - } - } - - public void delete() { - shaderCompiler.delete(); - } - - private static final class Files { - public static final ResourceLocation INDIRECT_CULL = Flywheel.rl("internal/indirect_cull.glsl"); - } -} diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/FlwPrograms.java b/src/main/java/com/jozufozu/flywheel/backend/compile/FlwPrograms.java index 1b8afb758..791976f88 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/compile/FlwPrograms.java +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/FlwPrograms.java @@ -1,16 +1,10 @@ package com.jozufozu.flywheel.backend.compile; -import java.util.Map; - -import org.jetbrains.annotations.Nullable; - import com.google.common.collect.ImmutableList; -import com.jozufozu.flywheel.api.context.Context; +import com.jozufozu.flywheel.Flywheel; import com.jozufozu.flywheel.api.instance.InstanceType; +import com.jozufozu.flywheel.api.uniform.ShaderUniforms; import com.jozufozu.flywheel.api.vertex.VertexType; -import com.jozufozu.flywheel.backend.compile.pipeline.Pipeline; -import com.jozufozu.flywheel.backend.compile.pipeline.Pipelines; -import com.jozufozu.flywheel.gl.shader.GlProgram; import com.jozufozu.flywheel.glsl.ShaderSources; import com.jozufozu.flywheel.glsl.error.ErrorReporter; import com.jozufozu.flywheel.lib.context.Contexts; @@ -18,77 +12,32 @@ import com.jozufozu.flywheel.lib.context.Contexts; import net.minecraft.server.packs.resources.ResourceManager; public class FlwPrograms { - private static FlwPrograms instance; - - private final Map pipelinePrograms; - private final Map, GlProgram> cullingPrograms; - - public FlwPrograms(Map pipelinePrograms, Map, GlProgram> cullingPrograms) { - this.pipelinePrograms = pipelinePrograms; - this.cullingPrograms = cullingPrograms; + private FlwPrograms() { } public static void reload(ResourceManager resourceManager) { - if (instance != null) { - instance.delete(); - } + var errorReporter = new ErrorReporter(); - ErrorReporter errorReporter = new ErrorReporter(); - ShaderSources sources = new ShaderSources(errorReporter, resourceManager); - FlwCompiler compiler = new FlwCompiler(sources, createPipelineKeys(), createCullingKeys()); - instance = compiler.compile(); - compiler.delete(); + var sources = new ShaderSources(errorReporter, resourceManager); + var pipelineKeys = createPipelineKeys(); + var uniformComponent = UniformComponent.builder(Flywheel.rl("uniforms")) + .sources(ShaderUniforms.REGISTRY.getAll() + .stream() + .map(ShaderUniforms::uniformShader) + .toList()) + .build(sources); + + InstancingPrograms.reload(sources, pipelineKeys, uniformComponent); + IndirectPrograms.reload(sources, pipelineKeys, uniformComponent); } private static ImmutableList createPipelineKeys() { ImmutableList.Builder builder = ImmutableList.builder(); - for (Pipeline pipelineShader : Pipelines.ALL) { - for (InstanceType instanceType : InstanceType.REGISTRY) { - for (VertexType vertexType : VertexType.REGISTRY) { - builder.add(new PipelineProgramKey(vertexType, instanceType, Contexts.WORLD, pipelineShader)); - } + for (InstanceType instanceType : InstanceType.REGISTRY) { + for (VertexType vertexType : VertexType.REGISTRY) { + builder.add(new PipelineProgramKey(vertexType, instanceType, Contexts.WORLD)); } } return builder.build(); } - - private static ImmutableList> createCullingKeys() { - ImmutableList.Builder> builder = ImmutableList.builder(); - for (InstanceType instanceType : InstanceType.REGISTRY) { - builder.add(instanceType); - } - return builder.build(); - } - - @Nullable - public static FlwPrograms get() { - return instance; - } - - public GlProgram getPipelineProgram(VertexType vertexType, InstanceType instanceType, Context contextShader, Pipeline pipelineShader) { - return pipelinePrograms.get(new PipelineProgramKey(vertexType, instanceType, contextShader, pipelineShader)); - } - - public GlProgram getCullingProgram(InstanceType instanceType) { - return cullingPrograms.get(instanceType); - } - - private void delete() { - pipelinePrograms.values() - .forEach(GlProgram::delete); - cullingPrograms.values() - .forEach(GlProgram::delete); - } - - /** - * Represents the entire context of a program's usage. - * - * @param vertexType The vertex type the program should be adapted for. - * @param instanceType The instance shader to use. - * @param contextShader The context shader to use. - * @param pipelineShader The pipeline shader to use. - */ - public record PipelineProgramKey(VertexType vertexType, InstanceType instanceType, Context contextShader, - Pipeline pipelineShader) { - } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/IndirectPrograms.java b/src/main/java/com/jozufozu/flywheel/backend/compile/IndirectPrograms.java new file mode 100644 index 000000000..5201be604 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/IndirectPrograms.java @@ -0,0 +1,63 @@ +package com.jozufozu.flywheel.backend.compile; + +import java.util.Map; + +import org.jetbrains.annotations.Nullable; + +import com.google.common.collect.ImmutableList; +import com.jozufozu.flywheel.api.context.Context; +import com.jozufozu.flywheel.api.instance.InstanceType; +import com.jozufozu.flywheel.api.vertex.VertexType; +import com.jozufozu.flywheel.backend.compile.pipeline.Pipelines; +import com.jozufozu.flywheel.gl.shader.GlProgram; +import com.jozufozu.flywheel.glsl.ShaderSources; + +public class IndirectPrograms { + private static IndirectPrograms instance; + private final Map pipeline; + private final Map, GlProgram> culling; + + public IndirectPrograms(Map pipeline, Map, GlProgram> culling) { + this.pipeline = pipeline; + this.culling = culling; + } + + public static void reload(ShaderSources sources, ImmutableList pipelineKeys, UniformComponent uniformComponent) { + if (instance != null) { + instance.delete(); + } + var indirectCompiler = new PipelineCompiler(sources, pipelineKeys, Pipelines.INDIRECT, uniformComponent); + var cullingCompiler = new CullingCompiler(sources, createCullingKeys(), uniformComponent); + instance = new IndirectPrograms(indirectCompiler.compile(), cullingCompiler.compile()); + indirectCompiler.delete(); + cullingCompiler.delete(); + } + + private static ImmutableList> createCullingKeys() { + ImmutableList.Builder> builder = ImmutableList.builder(); + for (InstanceType instanceType : InstanceType.REGISTRY) { + builder.add(instanceType); + } + return builder.build(); + } + + @Nullable + public static IndirectPrograms get() { + return instance; + } + + public GlProgram getIndirectProgram(VertexType vertexType, InstanceType instanceType, Context contextShader) { + return pipeline.get(new PipelineProgramKey(vertexType, instanceType, contextShader)); + } + + public GlProgram getCullingProgram(InstanceType instanceType) { + return culling.get(instanceType); + } + + public void delete() { + pipeline.values() + .forEach(GlProgram::delete); + culling.values() + .forEach(GlProgram::delete); + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/InstancingPrograms.java b/src/main/java/com/jozufozu/flywheel/backend/compile/InstancingPrograms.java new file mode 100644 index 000000000..e9fd36356 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/InstancingPrograms.java @@ -0,0 +1,45 @@ +package com.jozufozu.flywheel.backend.compile; + +import java.util.Map; + +import org.jetbrains.annotations.Nullable; + +import com.google.common.collect.ImmutableList; +import com.jozufozu.flywheel.api.context.Context; +import com.jozufozu.flywheel.api.instance.InstanceType; +import com.jozufozu.flywheel.api.vertex.VertexType; +import com.jozufozu.flywheel.backend.compile.pipeline.Pipelines; +import com.jozufozu.flywheel.gl.shader.GlProgram; +import com.jozufozu.flywheel.glsl.ShaderSources; + +public class InstancingPrograms { + private static InstancingPrograms instance; + private final Map pipeline; + + public InstancingPrograms(Map pipeline) { + this.pipeline = pipeline; + } + + public static void reload(ShaderSources sources, ImmutableList pipelineKeys, UniformComponent uniformComponent) { + if (instance != null) { + instance.delete(); + } + var instancingCompiler = new PipelineCompiler(sources, pipelineKeys, Pipelines.INSTANCED_ARRAYS, uniformComponent); + instance = new InstancingPrograms(instancingCompiler.compile()); + instancingCompiler.delete(); + } + + @Nullable + public static InstancingPrograms get() { + return instance; + } + + public GlProgram get(VertexType vertexType, InstanceType instanceType, Context contextShader) { + return pipeline.get(new PipelineProgramKey(vertexType, instanceType, contextShader)); + } + + public void delete() { + pipeline.values() + .forEach(GlProgram::delete); + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/LinkResult.java b/src/main/java/com/jozufozu/flywheel/backend/compile/LinkResult.java new file mode 100644 index 000000000..bc592db18 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/LinkResult.java @@ -0,0 +1,30 @@ +package com.jozufozu.flywheel.backend.compile; + +import org.jetbrains.annotations.Nullable; + +import com.jozufozu.flywheel.gl.shader.GlProgram; + +public sealed interface LinkResult { + + @Nullable + default GlProgram unwrap() { + if (this instanceof Success s) { + return s.program(); + } + return null; + } + + record Success(GlProgram program, String log) implements LinkResult { + } + + record Failure(String failure) implements LinkResult { + } + + static LinkResult success(GlProgram program, String log) { + return new Success(program, log); + } + + static LinkResult failure(String failure) { + return new Failure(failure); + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/PipelineCompiler.java b/src/main/java/com/jozufozu/flywheel/backend/compile/PipelineCompiler.java new file mode 100644 index 000000000..6958bd238 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/PipelineCompiler.java @@ -0,0 +1,92 @@ +package com.jozufozu.flywheel.backend.compile; + +import org.jetbrains.annotations.Nullable; + +import com.google.common.collect.ImmutableList; +import com.jozufozu.flywheel.Flywheel; +import com.jozufozu.flywheel.backend.compile.pipeline.Pipeline; +import com.jozufozu.flywheel.gl.shader.GlProgram; +import com.jozufozu.flywheel.gl.shader.ShaderType; +import com.jozufozu.flywheel.glsl.ShaderSources; +import com.jozufozu.flywheel.glsl.SourceComponent; +import com.jozufozu.flywheel.glsl.SourceFile; +import com.jozufozu.flywheel.glsl.generate.FnSignature; +import com.jozufozu.flywheel.glsl.generate.GlslExpr; +import com.jozufozu.flywheel.lib.material.MaterialIndices; + +public class PipelineCompiler extends AbstractCompiler { + private final Pipeline pipeline; + private final MaterialAdapterComponent vertexMaterialComponent; + private final MaterialAdapterComponent fragmentMaterialComponent; + private final UniformComponent uniformComponent; + private final SourceFile pipelineFragment; + private final SourceFile pipelineVertex; + + public PipelineCompiler(ShaderSources sources, ImmutableList keys, Pipeline pipeline, UniformComponent uniformComponent) { + super(sources, keys); + this.pipeline = pipeline; + this.uniformComponent = uniformComponent; + + vertexMaterialComponent = MaterialAdapterComponent.builder(Flywheel.rl("vertex_material_adapter")) + .materialSources(MaterialIndices.getAllVertexShaders()) + .adapt(FnSignature.ofVoid("flw_materialVertex")) + .switchOn(GlslExpr.variable("_flw_materialVertexID")) + .build(sources); + fragmentMaterialComponent = MaterialAdapterComponent.builder(Flywheel.rl("fragment_material_adapter")) + .materialSources(MaterialIndices.getAllFragmentShaders()) + .adapt(FnSignature.ofVoid("flw_materialFragment")) + .adapt(FnSignature.create() + .returnType("bool") + .name("flw_discardPredicate") + .arg("vec4", "color") + .build(), GlslExpr.literal(false)) + .adapt(FnSignature.create() + .returnType("vec4") + .name("flw_fogFilter") + .arg("vec4", "color") + .build(), GlslExpr.variable("color")) + .switchOn(GlslExpr.variable("_flw_materialFragmentID")) + .build(sources); + + pipelineFragment = sources.find(pipeline.fragmentShader()); + pipelineVertex = sources.find(pipeline.vertexShader()); + } + + @Nullable + @Override + protected GlProgram compile(PipelineProgramKey key) { + var glslVersion = pipeline.glslVersion(); + + var vertex = shaderCompiler.compile(glslVersion, ShaderType.VERTEX, getVertexComponents(key)); + var fragment = shaderCompiler.compile(glslVersion, ShaderType.FRAGMENT, getFragmentComponents(key)); + + if (vertex == null || fragment == null) { + return null; + } + + var glProgram = programLinker.link(vertex, fragment); + key.contextShader() + .onProgramLink(glProgram); + return glProgram; + } + + private ImmutableList getVertexComponents(PipelineProgramKey key) { + var instanceAssembly = pipeline.assembler() + .assemble(new Pipeline.InstanceAssemblerContext(sources, key.vertexType(), key.instanceType())); + + var layout = sources.find(key.vertexType() + .layoutShader()); + var instance = sources.find(key.instanceType() + .instanceShader()); + var context = sources.find(key.contextShader() + .vertexShader()); + + return ImmutableList.of(uniformComponent, vertexMaterialComponent, instanceAssembly, layout, instance, context, pipelineVertex); + } + + private ImmutableList getFragmentComponents(PipelineProgramKey key) { + var context = sources.find(key.contextShader() + .fragmentShader()); + return ImmutableList.of(uniformComponent, fragmentMaterialComponent, context, pipelineFragment); + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/PipelineProgramKey.java b/src/main/java/com/jozufozu/flywheel/backend/compile/PipelineProgramKey.java new file mode 100644 index 000000000..9cceab7da --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/PipelineProgramKey.java @@ -0,0 +1,15 @@ +package com.jozufozu.flywheel.backend.compile; + +import com.jozufozu.flywheel.api.context.Context; +import com.jozufozu.flywheel.api.instance.InstanceType; +import com.jozufozu.flywheel.api.vertex.VertexType; + +/** + * Represents the entire context of a program's usage. + * + * @param vertexType The vertex type the program should be adapted for. + * @param instanceType The instance shader to use. + * @param contextShader The context shader to use. + */ +public record PipelineProgramKey(VertexType vertexType, InstanceType instanceType, Context contextShader) { +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/ProgramLinker.java b/src/main/java/com/jozufozu/flywheel/backend/compile/ProgramLinker.java new file mode 100644 index 000000000..63394441a --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/ProgramLinker.java @@ -0,0 +1,52 @@ +package com.jozufozu.flywheel.backend.compile; + +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.glCreateProgram; +import static org.lwjgl.opengl.GL20.glGetProgramInfoLog; +import static org.lwjgl.opengl.GL20.glGetProgrami; +import static org.lwjgl.opengl.GL20.glLinkProgram; + +import org.jetbrains.annotations.Nullable; + +import com.jozufozu.flywheel.gl.shader.GlProgram; +import com.jozufozu.flywheel.gl.shader.GlShader; + +public class ProgramLinker { + private final CompilerStats stats; + + public ProgramLinker(CompilerStats stats) { + this.stats = stats; + } + + @Nullable + public GlProgram link(GlShader... shaders) { + // this probably doesn't need caching + var linkResult = linkInternal(shaders); + stats.linkResult(linkResult); + return linkResult.unwrap(); + } + + private LinkResult linkInternal(GlShader... shaders) { + int handle = glCreateProgram(); + + for (GlShader shader : shaders) { + glAttachShader(handle, shader.handle()); + } + + glLinkProgram(handle); + String log = glGetProgramInfoLog(handle); + + if (linkSuccessful(handle)) { + return LinkResult.success(new GlProgram(handle), log); + } else { + return LinkResult.failure(log); + } + } + + private static boolean linkSuccessful(int handle) { + return glGetProgrami(handle, GL_LINK_STATUS) == GL_TRUE; + } + +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/ShaderCompiler.java b/src/main/java/com/jozufozu/flywheel/backend/compile/ShaderCompiler.java index 0326dbfb5..ec4aa00e8 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/compile/ShaderCompiler.java +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/ShaderCompiler.java @@ -3,7 +3,6 @@ package com.jozufozu.flywheel.backend.compile; import java.util.HashMap; import java.util.Map; import java.util.Objects; -import java.util.function.Consumer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -15,13 +14,13 @@ import com.jozufozu.flywheel.gl.shader.ShaderType; import com.jozufozu.flywheel.glsl.SourceComponent; public class ShaderCompiler { - private final Map shaderCache = new HashMap<>(); - private final Consumer errorConsumer; + private final Map shaderCache = new HashMap<>(); + private final CompilerStats stats; private final CompilationFactory factory; private final Includer includer; - public ShaderCompiler(Consumer errorConsumer, CompilationFactory factory, Includer includer) { - this.errorConsumer = errorConsumer; + public ShaderCompiler(CompilerStats stats, CompilationFactory factory, Includer includer) { + this.stats = stats; this.factory = factory; this.includer = includer; } @@ -38,31 +37,22 @@ public class ShaderCompiler { return cached.unwrap(); } - CompilationResult out = compileUncached(factory.create(glslVersion, shaderType), sourceComponents); + ShaderResult out = compileUncached(factory.create(glslVersion, shaderType), sourceComponents); shaderCache.put(key, out); - return unwrapAndReportError(out); + stats.shaderResult(out); + return out.unwrap(); } public void delete() { shaderCache.values() .stream() - .map(CompilationResult::unwrap) + .map(ShaderResult::unwrap) .filter(Objects::nonNull) .forEach(GlShader::delete); } - @Nullable - private GlShader unwrapAndReportError(CompilationResult result) { - if (result instanceof CompilationResult.Success s) { - return s.shader(); - } else if (result instanceof CompilationResult.Failure f) { - errorConsumer.accept(f.failure()); - } - return null; - } - @NotNull - private CompilationResult compileUncached(Compilation ctx, ImmutableList sourceComponents) { + private ShaderResult compileUncached(Compilation ctx, ImmutableList sourceComponents) { ctx.enableExtension("GL_ARB_explicit_attrib_location"); ctx.enableExtension("GL_ARB_conservative_depth"); @@ -86,15 +76,9 @@ public class ShaderCompiler { } public static class Builder { - private Consumer errorConsumer = error -> {}; private CompilationFactory factory = Compilation::new; private Includer includer = RecursiveIncluder.INSTANCE; - public Builder errorConsumer(Consumer errorConsumer) { - this.errorConsumer = errorConsumer; - return this; - } - public Builder compilationFactory(CompilationFactory factory) { this.factory = factory; return this; @@ -105,8 +89,8 @@ public class ShaderCompiler { return this; } - public ShaderCompiler build() { - return new ShaderCompiler(errorConsumer, factory, includer); + public ShaderCompiler build(CompilerStats stats) { + return new ShaderCompiler(stats, factory, includer); } } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/ShaderResult.java b/src/main/java/com/jozufozu/flywheel/backend/compile/ShaderResult.java new file mode 100644 index 000000000..71f97829a --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/ShaderResult.java @@ -0,0 +1,29 @@ +package com.jozufozu.flywheel.backend.compile; + +import org.jetbrains.annotations.Nullable; + +import com.jozufozu.flywheel.gl.shader.GlShader; + +public sealed interface ShaderResult { + @Nullable + default GlShader unwrap() { + if (this instanceof Success s) { + return s.shader(); + } + return null; + } + + record Success(GlShader shader, String infoLog) implements ShaderResult { + } + + record Failure(FailedCompilation failure) implements ShaderResult { + } + + static ShaderResult success(GlShader program, String infoLog) { + return new Success(program, infoLog); + } + + static ShaderResult failure(FailedCompilation failure) { + return new Failure(failure); + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectCullingGroup.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectCullingGroup.java index 95f6071ba..e07726475 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectCullingGroup.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectCullingGroup.java @@ -15,8 +15,7 @@ import com.jozufozu.flywheel.api.event.RenderStage; import com.jozufozu.flywheel.api.instance.Instance; import com.jozufozu.flywheel.api.instance.InstanceType; import com.jozufozu.flywheel.api.vertex.VertexType; -import com.jozufozu.flywheel.backend.compile.FlwPrograms; -import com.jozufozu.flywheel.backend.compile.pipeline.Pipelines; +import com.jozufozu.flywheel.backend.compile.IndirectPrograms; import com.jozufozu.flywheel.backend.engine.UniformBuffer; import com.jozufozu.flywheel.gl.shader.GlProgram; import com.jozufozu.flywheel.lib.context.Contexts; @@ -62,8 +61,9 @@ public class IndirectCullingGroup { .quads2Tris(2048).glBuffer; setupVertexArray(); - compute = FlwPrograms.get().getCullingProgram(instanceType); - draw = FlwPrograms.get().getPipelineProgram(vertexType, instanceType, Contexts.WORLD, Pipelines.INDIRECT); + var indirectPrograms = IndirectPrograms.get(); + compute = indirectPrograms.getCullingProgram(instanceType); + draw = indirectPrograms.getIndirectProgram(vertexType, instanceType, Contexts.WORLD); } private void setupVertexArray() { diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancingEngine.java b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancingEngine.java index 1562b861f..e394c23c0 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancingEngine.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancingEngine.java @@ -13,8 +13,7 @@ import com.jozufozu.flywheel.api.instance.Instancer; import com.jozufozu.flywheel.api.model.Model; import com.jozufozu.flywheel.api.task.Plan; import com.jozufozu.flywheel.api.task.TaskExecutor; -import com.jozufozu.flywheel.backend.compile.FlwPrograms; -import com.jozufozu.flywheel.backend.compile.pipeline.Pipelines; +import com.jozufozu.flywheel.backend.compile.InstancingPrograms; import com.jozufozu.flywheel.backend.engine.AbstractEngine; import com.jozufozu.flywheel.backend.engine.UniformBuffer; import com.jozufozu.flywheel.gl.GlStateTracker; @@ -106,7 +105,8 @@ public class InstancingEngine extends AbstractEngine { var vertexType = desc.vertexType(); var instanceType = desc.instanceType(); - var program = FlwPrograms.get().getPipelineProgram(vertexType, instanceType, context, Pipelines.INSTANCED_ARRAYS); + var program = InstancingPrograms.get() + .get(vertexType, instanceType, context); UniformBuffer.syncAndBind(program); var uniformLocation = program.getUniformLocation("_flw_materialID_instancing");