Pre-processing post-pre-refactor-refactor

- Separate pipeline compiler from compute compiler
- Remove Pipeline field from PipelineProgramKey
- Share one UniformComponent across compilers
- Use AbstractCompiler to deduplicate some shared code
- Separate indirect programs from instancing programs
- Move compilation tracking information to CompilerStats
- Improve compilation result/link result reporting
This commit is contained in:
Jozufozu 2023-04-18 20:27:25 -07:00
parent b30d686785
commit 4c8e174712
18 changed files with 523 additions and 377 deletions

View file

@ -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<K> {
protected final ShaderSources sources;
protected final ShaderCompiler shaderCompiler;
protected final ProgramLinker programLinker;
private final ImmutableList<K> keys;
private final CompilerStats stats = new CompilerStats();
public AbstractCompiler(ShaderSources sources, ImmutableList<K> 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<K, GlProgram> compile() {
stats.start();
Map<K, GlProgram> 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();
}
}

View file

@ -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) {

View file

@ -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);
}
}

View file

@ -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");
}
}
}

View file

@ -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<FailedCompilation> shaderErrors = new ArrayList<>();
private final List<String> 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++;
}
}

View file

@ -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<InstanceType<?>> {
private final UniformComponent uniformComponent;
private final SourceFile pipelineCompute;
public CullingCompiler(ShaderSources sources, ImmutableList<InstanceType<?>> 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<SourceComponent> 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");
}
}

View file

@ -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<PipelineProgramKey> pipelineKeys;
private final ImmutableList<InstanceType<?>> cullingKeys;
private final ShaderCompiler shaderCompiler;
private final List<FailedCompilation> errors = new ArrayList<>();
private final MaterialAdapterComponent vertexMaterialComponent;
private final MaterialAdapterComponent fragmentMaterialComponent;
private final UniformComponent uniformComponent;
private final Map<PipelineProgramKey, GlProgram> pipelinePrograms = new HashMap<>();
private final Map<InstanceType<?>, GlProgram> cullingPrograms = new HashMap<>();
public FlwCompiler(ShaderSources sources, ImmutableList<PipelineProgramKey> pipelineKeys, ImmutableList<InstanceType<?>> 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<SourceComponent> 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<SourceComponent> 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<SourceComponent> 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");
}
}

View file

@ -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<PipelineProgramKey, GlProgram> pipelinePrograms;
private final Map<InstanceType<?>, GlProgram> cullingPrograms;
public FlwPrograms(Map<PipelineProgramKey, GlProgram> pipelinePrograms, Map<InstanceType<?>, 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<PipelineProgramKey> createPipelineKeys() {
ImmutableList.Builder<PipelineProgramKey> 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<InstanceType<?>> createCullingKeys() {
ImmutableList.Builder<InstanceType<?>> 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) {
}
}

View file

@ -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<PipelineProgramKey, GlProgram> pipeline;
private final Map<InstanceType<?>, GlProgram> culling;
public IndirectPrograms(Map<PipelineProgramKey, GlProgram> pipeline, Map<InstanceType<?>, GlProgram> culling) {
this.pipeline = pipeline;
this.culling = culling;
}
public static void reload(ShaderSources sources, ImmutableList<PipelineProgramKey> 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<InstanceType<?>> createCullingKeys() {
ImmutableList.Builder<InstanceType<?>> 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);
}
}

View file

@ -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<PipelineProgramKey, GlProgram> pipeline;
public InstancingPrograms(Map<PipelineProgramKey, GlProgram> pipeline) {
this.pipeline = pipeline;
}
public static void reload(ShaderSources sources, ImmutableList<PipelineProgramKey> 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);
}
}

View file

@ -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);
}
}

View file

@ -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<PipelineProgramKey> {
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<PipelineProgramKey> 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<SourceComponent> 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<SourceComponent> getFragmentComponents(PipelineProgramKey key) {
var context = sources.find(key.contextShader()
.fragmentShader());
return ImmutableList.of(uniformComponent, fragmentMaterialComponent, context, pipelineFragment);
}
}

View file

@ -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) {
}

View file

@ -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;
}
}

View file

@ -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<ShaderKey, CompilationResult> shaderCache = new HashMap<>();
private final Consumer<FailedCompilation> errorConsumer;
private final Map<ShaderKey, ShaderResult> shaderCache = new HashMap<>();
private final CompilerStats stats;
private final CompilationFactory factory;
private final Includer includer;
public ShaderCompiler(Consumer<FailedCompilation> 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<SourceComponent> sourceComponents) {
private ShaderResult compileUncached(Compilation ctx, ImmutableList<SourceComponent> 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<FailedCompilation> errorConsumer = error -> {};
private CompilationFactory factory = Compilation::new;
private Includer includer = RecursiveIncluder.INSTANCE;
public Builder errorConsumer(Consumer<FailedCompilation> 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);
}
}
}

View file

@ -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);
}
}

View file

@ -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<I extends Instance> {
.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() {

View file

@ -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");