Little things

- Do not store a list of initialized instancers.
- Remove AbstractInstancer#delete. Only InstancedInstancer was using it
  so move instancer deletion to InstancedDrawManager.
- Improve CompilationHarness builder pattern and reusability.
  - Pass compilation keys directly to compileAndReportErrors.
  - Build the harness at the end of the builder chain rather than at the
    beginning.
  - Use same CompilationHarness for apply shader and scatter shader.
- Remove _ prefix from packed struct fields.
- Make element type's byte size 4-aligned.
- Remove byteSize method from Element.
This commit is contained in:
Jozufozu 2024-01-08 13:25:32 -08:00
parent 0deac32fde
commit 381288da52
17 changed files with 82 additions and 140 deletions

View file

@ -24,7 +24,5 @@ public interface Layout {
ElementType type();
int offset();
int byteSize();
}
}

View file

@ -1,11 +1,11 @@
package com.jozufozu.flywheel.backend.compile;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.compile.core.CompilerStats;
import com.jozufozu.flywheel.backend.compile.core.ProgramLinker;
@ -18,12 +18,9 @@ public class CompilationHarness<K> {
private final SourceLoader sourceLoader;
private final ShaderCompiler shaderCompiler;
private final ProgramLinker programLinker;
private final ImmutableList<K> keys;
private final CompilerStats stats = new CompilerStats();
public CompilationHarness(ShaderSources sources, ImmutableList<K> keys, KeyCompiler<K> compiler) {
this.keys = keys;
public CompilationHarness(ShaderSources sources, KeyCompiler<K> compiler) {
this.compiler = compiler;
sourceLoader = new SourceLoader(sources, stats);
shaderCompiler = new ShaderCompiler(stats);
@ -31,7 +28,7 @@ public class CompilationHarness<K> {
}
@Nullable
public Map<K, GlProgram> compileAndReportErrors() {
public Map<K, GlProgram> compileAndReportErrors(Collection<K> keys) {
stats.start();
Map<K, GlProgram> out = new HashMap<>();
for (var key : keys) {
@ -59,28 +56,4 @@ public class CompilationHarness<K> {
public interface KeyCompiler<K> {
@Nullable GlProgram compile(K key, SourceLoader loader, ShaderCompiler shaderCompiler, ProgramLinker programLinker);
}
public static class Builder<K> {
private final ShaderSources sources;
private ImmutableList<K> keys;
private KeyCompiler<K> compiler;
public Builder(ShaderSources sources) {
this.sources = sources;
}
public Builder<K> keys(ImmutableList<K> keys) {
this.keys = keys;
return this;
}
public Builder<K> compiler(KeyCompiler<K> compiler) {
this.compiler = compiler;
return this;
}
public CompilationHarness<K> build() {
return new CompilationHarness<>(sources, keys, compiler);
}
}
}

View file

@ -44,15 +44,15 @@ public class Compile<K> {
return new ProgramLinkBuilder<>();
}
public CompilationHarness.Builder<K> harness(ShaderSources sources) {
return new CompilationHarness.Builder<>(sources);
}
public static class ProgramLinkBuilder<K> implements CompilationHarness.KeyCompiler<K> {
private final Map<ShaderType, ShaderCompilerBuilder<K>> compilers = new EnumMap<>(ShaderType.class);
private BiConsumer<K, GlProgram> onLink = (k, p) -> {
};
public CompilationHarness<K> harness(ShaderSources sources) {
return new CompilationHarness<>(sources, this);
}
public ProgramLinkBuilder<K> link(ShaderCompilerBuilder<K> compilerBuilder) {
if (compilers.containsKey(compilerBuilder.shaderType)) {
throw new IllegalArgumentException("Duplicate shader type: " + compilerBuilder.shaderType);

View file

@ -17,7 +17,6 @@ import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.backend.glsl.GlslVersion;
import com.jozufozu.flywheel.backend.glsl.ShaderSources;
import com.jozufozu.flywheel.backend.glsl.SourceComponent;
import com.jozufozu.flywheel.lib.util.Unit;
import net.minecraft.resources.ResourceLocation;
@ -28,7 +27,7 @@ public class IndirectPrograms {
public static IndirectPrograms instance;
private static final Compile<InstanceType<?>> CULL = new Compile<>();
private static final Compile<Unit> UNIT = new Compile<>();
private static final Compile<ResourceLocation> UTIL = new Compile<>();
private final Map<PipelineProgramKey, GlProgram> pipeline;
private final Map<InstanceType<?>, GlProgram> culling;
private final GlProgram apply;
@ -43,19 +42,17 @@ public class IndirectPrograms {
static void reload(ShaderSources sources, ImmutableList<PipelineProgramKey> pipelineKeys, UniformComponent uniformComponent, List<SourceComponent> vertexComponents, List<SourceComponent> fragmentComponents) {
_delete();
var pipelineCompiler = PipelineCompiler.create(sources, Pipelines.INDIRECT, pipelineKeys, uniformComponent, vertexComponents, fragmentComponents);
var pipelineCompiler = PipelineCompiler.create(sources, Pipelines.INDIRECT, uniformComponent, vertexComponents, fragmentComponents);
var cullingCompiler = createCullingCompiler(uniformComponent, sources);
var applyCompiler = createApplyCompiler(sources);
var scatterCompiler = createScatterCompiler(sources);
var applyCompiler = createUtilCompiler(sources);
try {
var pipelineResult = pipelineCompiler.compileAndReportErrors();
var cullingResult = cullingCompiler.compileAndReportErrors();
var applyResult = applyCompiler.compileAndReportErrors();
var scatterResult = scatterCompiler.compileAndReportErrors();
var pipelineResult = pipelineCompiler.compileAndReportErrors(pipelineKeys);
var cullingResult = cullingCompiler.compileAndReportErrors(createCullingKeys());
var utils = applyCompiler.compileAndReportErrors(List.of(APPLY_SHADER_MAIN, SCATTER_SHADER_MAIN));
if (pipelineResult != null && cullingResult != null && applyResult != null && scatterResult != null) {
instance = new IndirectPrograms(pipelineResult, cullingResult, applyResult.get(Unit.INSTANCE), scatterResult.get(Unit.INSTANCE));
if (pipelineResult != null && cullingResult != null && utils != null) {
instance = new IndirectPrograms(pipelineResult, cullingResult, utils.get(APPLY_SHADER_MAIN), utils.get(SCATTER_SHADER_MAIN));
}
} catch (Throwable e) {
Flywheel.LOGGER.error("Failed to compile indirect programs", e);
@ -90,37 +87,23 @@ public class IndirectPrograms {
}
private static CompilationHarness<InstanceType<?>> createCullingCompiler(UniformComponent uniformComponent, ShaderSources sources) {
return CULL.harness(sources)
.keys(createCullingKeys())
.compiler(CULL.program()
.link(CULL.shader(GlslVersion.V460, ShaderType.COMPUTE)
.define("_FLW_SUBGROUP_SIZE", GlCompat.SUBGROUP_SIZE)
.withComponent(uniformComponent)
.withComponent(IndirectComponent::create)
.withResource(InstanceType::cullShader)
.withResource(CULL_SHADER_MAIN))
.then((key, program) -> program.setUniformBlockBinding("FlwUniforms", 0)))
.build();
return CULL.program()
.link(CULL.shader(GlslVersion.V460, ShaderType.COMPUTE)
.define("_FLW_SUBGROUP_SIZE", GlCompat.SUBGROUP_SIZE)
.withComponent(uniformComponent)
.withComponent(IndirectComponent::create)
.withResource(InstanceType::cullShader)
.withResource(CULL_SHADER_MAIN))
.then((key, program) -> program.setUniformBlockBinding("FlwUniforms", 0))
.harness(sources);
}
private static CompilationHarness<Unit> createApplyCompiler(ShaderSources sources) {
return UNIT.harness(sources)
.keys(ImmutableList.of(Unit.INSTANCE))
.compiler(UNIT.program()
.link(UNIT.shader(GlslVersion.V460, ShaderType.COMPUTE)
.define("_FLW_SUBGROUP_SIZE", GlCompat.SUBGROUP_SIZE)
.withResource(APPLY_SHADER_MAIN)))
.build();
}
private static CompilationHarness<Unit> createScatterCompiler(ShaderSources sources) {
return UNIT.harness(sources)
.keys(ImmutableList.of(Unit.INSTANCE))
.compiler(UNIT.program()
.link(UNIT.shader(GlslVersion.V460, ShaderType.COMPUTE)
.define("_FLW_SUBGROUP_SIZE", GlCompat.SUBGROUP_SIZE)
.withResource(SCATTER_SHADER_MAIN)))
.build();
private static CompilationHarness<ResourceLocation> createUtilCompiler(ShaderSources sources) {
return UTIL.program()
.link(UTIL.shader(GlslVersion.V460, ShaderType.COMPUTE)
.define("_FLW_SUBGROUP_SIZE", GlCompat.SUBGROUP_SIZE)
.withResource(s -> s))
.harness(sources);
}
public GlProgram getIndirectProgram(InstanceType<?> instanceType, Context contextShader) {

View file

@ -24,10 +24,10 @@ public class InstancingPrograms {
static void reload(ShaderSources sources, ImmutableList<PipelineProgramKey> pipelineKeys, UniformComponent uniformComponent, List<SourceComponent> vertexComponents, List<SourceComponent> fragmentComponents) {
_delete();
var instancingCompiler = PipelineCompiler.create(sources, Pipelines.INSTANCED_ARRAYS, pipelineKeys, uniformComponent, vertexComponents, fragmentComponents);
var instancingCompiler = PipelineCompiler.create(sources, Pipelines.INSTANCED_ARRAYS, uniformComponent, vertexComponents, fragmentComponents);
try {
var result = instancingCompiler.compileAndReportErrors();
var result = instancingCompiler.compileAndReportErrors(pipelineKeys);
if (result != null) {
instance = new InstancingPrograms(result);

View file

@ -2,7 +2,6 @@ package com.jozufozu.flywheel.backend.compile;
import java.util.List;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.InternalVertex;
import com.jozufozu.flywheel.backend.compile.component.UniformComponent;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
@ -12,35 +11,33 @@ import com.jozufozu.flywheel.backend.glsl.SourceComponent;
public class PipelineCompiler {
private static final Compile<PipelineProgramKey> PIPELINE = new Compile<>();
static CompilationHarness<PipelineProgramKey> create(ShaderSources sources, Pipeline pipeline, ImmutableList<PipelineProgramKey> pipelineKeys, UniformComponent uniformComponent, List<SourceComponent> vertexComponents, List<SourceComponent> fragmentComponents) {
return PIPELINE.harness(sources)
.keys(pipelineKeys)
.compiler(PIPELINE.program()
.link(PIPELINE.shader(pipeline.glslVersion(), ShaderType.VERTEX)
.withComponent(uniformComponent)
.withResource(pipeline.vertexApiImpl())
.withResource(InternalVertex.LAYOUT_SHADER)
.withComponent(key -> pipeline.assembler()
.assemble(new Pipeline.InstanceAssemblerContext(InternalVertex.ATTRIBUTE_COUNT, key.instanceType())))
.withComponents(vertexComponents)
.withResource(key -> key.instanceType()
.vertexShader())
.withResource(key -> key.contextShader()
.vertexShader())
.withResource(pipeline.vertexMain()))
.link(PIPELINE.shader(pipeline.glslVersion(), ShaderType.FRAGMENT)
.enableExtension("GL_ARB_conservative_depth")
.withComponent(uniformComponent)
.withResource(pipeline.fragmentApiImpl())
.withComponents(fragmentComponents)
.withResource(key -> key.contextShader()
.fragmentShader())
.withResource(pipeline.fragmentMain()))
.then((key, program) -> {
key.contextShader()
.onProgramLink(program);
program.setUniformBlockBinding("FlwUniforms", 0);
}))
.build();
static CompilationHarness<PipelineProgramKey> create(ShaderSources sources, Pipeline pipeline, UniformComponent uniformComponent, List<SourceComponent> vertexComponents, List<SourceComponent> fragmentComponents) {
return PIPELINE.program()
.link(PIPELINE.shader(pipeline.glslVersion(), ShaderType.VERTEX)
.withComponent(uniformComponent)
.withResource(pipeline.vertexApiImpl())
.withResource(InternalVertex.LAYOUT_SHADER)
.withComponent(key -> pipeline.assembler()
.assemble(new Pipeline.InstanceAssemblerContext(InternalVertex.ATTRIBUTE_COUNT, key.instanceType())))
.withComponents(vertexComponents)
.withResource(key -> key.instanceType()
.vertexShader())
.withResource(key -> key.contextShader()
.vertexShader())
.withResource(pipeline.vertexMain()))
.link(PIPELINE.shader(pipeline.glslVersion(), ShaderType.FRAGMENT)
.enableExtension("GL_ARB_conservative_depth")
.withComponent(uniformComponent)
.withResource(pipeline.fragmentApiImpl())
.withComponents(fragmentComponents)
.withResource(key -> key.contextShader()
.fragmentShader())
.withResource(pipeline.fragmentMain()))
.then((key, program) -> {
key.contextShader()
.onProgramLink(program);
program.setUniformBlockBinding("FlwUniforms", 0);
})
.harness(sources);
}
}

View file

@ -113,7 +113,7 @@ public class IndirectComponent implements SourceComponent {
// FIXME: I don't think we're unpacking signed byte/short values correctly
// FIXME: we definitely don't consider endianness. this all assumes little endian which works on my machine.
var type = element.type();
var name = "_" + element.name();
var name = element.name();
if (type instanceof ScalarElementType scalar) {
return unpackScalar(name, packed, scalar);

View file

@ -150,9 +150,6 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
deleted.clear();
}
public void delete() {
}
@Override
public String toString() {
return "AbstractInstancer[" + getInstanceCount() + ']';

View file

@ -20,25 +20,18 @@ public abstract class InstancerStorage<N extends AbstractInstancer<?>> {
* <br>
* See {@link #getInstancer} for insertion details.
*/
private final Map<InstancerKey<?>, N> instancers = new HashMap<>();
protected final Map<InstancerKey<?>, N> instancers = new HashMap<>();
/**
* A list of instancers that have not yet been initialized.
* <br>
* All new instancers land here before having resources allocated in {@link #flush}.
* Write access to this list must be synchronized on {@link #creationLock}.
*/
private final List<UninitializedInstancer<N, ?>> uninitializedInstancers = new ArrayList<>();
protected final List<UninitializedInstancer<N, ?>> uninitializedInstancers = new ArrayList<>();
/**
* Mutex for {@link #instancers} and {@link #uninitializedInstancers}.
*/
private final Object creationLock = new Object();
/**
* A list of initialized instancers.
* <br>
* These are instancers that may need to be cleared or deleted.
*/
private final List<N> initializedInstancers = new ArrayList<>();
protected final Object creationLock = new Object();
@SuppressWarnings("unchecked")
public <I extends Instance> Instancer<I> getInstancer(InstanceType<I> type, Model model, RenderStage stage) {
@ -69,21 +62,18 @@ public abstract class InstancerStorage<N extends AbstractInstancer<?>> {
public void delete() {
instancers.clear();
uninitializedInstancers.clear();
initializedInstancers.forEach(AbstractInstancer::delete);
initializedInstancers.clear();
}
public void flush() {
for (var instancer : uninitializedInstancers) {
add(instancer.key(), instancer.instancer(), instancer.model(), instancer.stage());
initializedInstancers.add(instancer.instancer());
}
uninitializedInstancers.clear();
}
public void onRenderOriginChanged() {
initializedInstancers.forEach(AbstractInstancer::clear);
instancers.values()
.forEach(AbstractInstancer::clear);
}
protected abstract <I extends Instance> N create(InstanceType<I> type);

View file

@ -40,6 +40,9 @@ public class InstancedDrawManager extends InstancerStorage<InstancedInstancer<?>
}
public void delete() {
instancers.values()
.forEach(InstancedInstancer::delete);
super.delete();
meshPool.delete();

View file

@ -119,7 +119,6 @@ public class InstancedInstancer<I extends Instance> extends AbstractInstancer<I>
vao.bindAttributes(1, startAttrib, instanceAttributes);
}
@Override
public void delete() {
vbo.delete();
vbo = null;

View file

@ -14,7 +14,6 @@ import com.jozufozu.flywheel.api.layout.Layout.Element;
import com.jozufozu.flywheel.api.layout.LayoutBuilder;
import com.jozufozu.flywheel.api.layout.ValueRepr;
import com.jozufozu.flywheel.impl.layout.LayoutImpl.ElementImpl;
import com.jozufozu.flywheel.lib.math.MoreMath;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
@ -126,9 +125,9 @@ public class LayoutBuilderImpl implements LayoutBuilder {
}
private LayoutBuilder element(String name, ElementType type) {
elements.add(new ElementImpl(name, type, offset, MoreMath.align4(type.byteSize())));
elements.add(new ElementImpl(name, type, offset));
// type.byteSize() is guaranteed to be 4-aligned.
offset += type.byteSize();
offset = MoreMath.align4(offset);
return this;
}

View file

@ -71,6 +71,6 @@ final class LayoutImpl implements Layout {
return elements.equals(other.elements);
}
record ElementImpl(String name, ElementType type, int offset, int byteSize) implements Element {
record ElementImpl(String name, ElementType type, int offset) implements Element {
}
}

View file

@ -4,6 +4,7 @@ import org.jetbrains.annotations.Range;
import com.jozufozu.flywheel.api.layout.FloatRepr;
import com.jozufozu.flywheel.api.layout.MatrixElementType;
import com.jozufozu.flywheel.lib.math.MoreMath;
record MatrixElementTypeImpl(FloatRepr repr, @Range(from = 2, to = 4) int rows, @Range(from = 2, to = 4) int columns,
int byteSize) implements MatrixElementType {
@ -14,7 +15,7 @@ record MatrixElementTypeImpl(FloatRepr repr, @Range(from = 2, to = 4) int rows,
if (columns < 2 || columns > 4) {
throw new IllegalArgumentException("Matrix element column count must be in range [2, 4]!");
}
int byteSize = repr.byteSize() * rows * columns;
int byteSize = MoreMath.align4(repr.byteSize() * rows * columns);
return new MatrixElementTypeImpl(repr, rows, columns, byteSize);
}
}

View file

@ -2,9 +2,10 @@ package com.jozufozu.flywheel.impl.layout;
import com.jozufozu.flywheel.api.layout.ScalarElementType;
import com.jozufozu.flywheel.api.layout.ValueRepr;
import com.jozufozu.flywheel.lib.math.MoreMath;
record ScalarElementTypeImpl(ValueRepr repr, int byteSize) implements ScalarElementType {
static ScalarElementTypeImpl create(ValueRepr repr) {
return new ScalarElementTypeImpl(repr, repr.byteSize());
return new ScalarElementTypeImpl(repr, MoreMath.align4(repr.byteSize()));
}
}

View file

@ -4,6 +4,7 @@ import org.jetbrains.annotations.Range;
import com.jozufozu.flywheel.api.layout.ValueRepr;
import com.jozufozu.flywheel.api.layout.VectorElementType;
import com.jozufozu.flywheel.lib.math.MoreMath;
record VectorElementTypeImpl(ValueRepr repr, @Range(from = 2, to = 4) int size,
int byteSize) implements VectorElementType {
@ -13,7 +14,7 @@ record VectorElementTypeImpl(ValueRepr repr, @Range(from = 2, to = 4) int size,
throw new IllegalArgumentException("Vector element size must be in range [2, 4]!");
}
int byteSize = repr.byteSize() * size;
int byteSize = MoreMath.align4(repr.byteSize() * size);
return new VectorElementTypeImpl(repr, size, byteSize);
}
}

View file

@ -9,12 +9,12 @@ public final class MoreMath {
*/
public static final float SQRT_3_OVER_2 = (float) (Math.sqrt(3.0) / 2.0);
public static int align16(int numToRound) {
return (numToRound + 15) & ~15;
public static int align16(int size) {
return (size + 15) & ~15;
}
public static int align4(int offset1) {
return (offset1 + 3) & ~3;
public static int align4(int size) {
return (size + 3) & ~3;
}
public static int ceilingDiv(int numerator, int denominator) {