- De-uberify the light shader
- Remove lightSources index
- Include LightShader in PipelineProgramKey and parameterize the
  pipeline fragment shader by it
- Profiling suggests that specializing the shaders uses significantly
  less GPU time, and we may want to do this for actual user-authored
  material shaders (and cutout?) as well
- Sort LightShader highest in the material comparator
- Implement a materialEquals method so IndirectCullingGroup can bucket
  draws on more that just material reference equality
- Do not store any particular draw program in IndirectCullingGroup
This commit is contained in:
Jozufozu 2024-08-12 17:35:31 -07:00
parent 2d37c3894d
commit b7d2b2ac7c
15 changed files with 73 additions and 72 deletions

View file

@ -8,7 +8,6 @@ import org.jetbrains.annotations.Unmodifiable;
import dev.engine_room.flywheel.api.material.CutoutShader;
import dev.engine_room.flywheel.api.material.FogShader;
import dev.engine_room.flywheel.api.material.LightShader;
import dev.engine_room.flywheel.api.material.MaterialShaders;
import dev.engine_room.flywheel.api.registry.Registry;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
@ -26,8 +25,6 @@ public final class MaterialShaderIndices {
private static Index fogSources;
@Nullable
private static Index cutoutSources;
@Nullable
private static Index lightSources;
private MaterialShaderIndices() {
}
@ -60,13 +57,6 @@ public final class MaterialShaderIndices {
return cutoutSources;
}
public static Index lightSources() {
if (lightSources == null) {
lightSources = indexFromRegistry(LightShader.REGISTRY, LightShader::source);
}
return lightSources;
}
public static int vertexIndex(MaterialShaders shaders) {
return vertexSources().index(shaders.vertexSource());
}
@ -83,10 +73,6 @@ public final class MaterialShaderIndices {
return cutoutSources().index(cutoutShader.source());
}
public static int lightIndex(LightShader lightShader) {
return lightSources().index(lightShader.source());
}
private static <T> Index indexFromRegistry(Registry<T> registry, Function<T, ResourceLocation> sourceFunc) {
if (!registry.isFrozen()) {
throw new IllegalStateException("Cannot create index from registry that is not frozen!");

View file

@ -10,6 +10,7 @@ import com.google.common.collect.ImmutableList;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.material.LightShader;
import dev.engine_room.flywheel.backend.MaterialShaderIndices;
import dev.engine_room.flywheel.backend.compile.component.UberShaderComponent;
import dev.engine_room.flywheel.backend.compile.core.CompilerStats;
@ -46,16 +47,15 @@ public final class FlwPrograms {
var fragmentMaterialComponent = createFragmentMaterialComponent(loader);
var fogComponent = createFogComponent(loader);
var cutoutComponent = createCutoutComponent(loader);
var lightComponent = createLightComponent(loader);
if (stats.errored() || vertexComponentsHeader == null || fragmentComponentsHeader == null || vertexMaterialComponent == null || fragmentMaterialComponent == null || fogComponent == null || cutoutComponent == null || lightComponent == null) {
if (stats.errored() || vertexComponentsHeader == null || fragmentComponentsHeader == null || vertexMaterialComponent == null || fragmentMaterialComponent == null || fogComponent == null || cutoutComponent == null) {
// Probably means the shader sources are missing.
stats.emitErrorLog();
return;
}
List<SourceComponent> vertexComponents = List.of(vertexComponentsHeader, vertexMaterialComponent);
List<SourceComponent> fragmentComponents = List.of(fragmentComponentsHeader, fragmentMaterialComponent, fogComponent, cutoutComponent, lightComponent);
List<SourceComponent> fragmentComponents = List.of(fragmentComponentsHeader, fragmentMaterialComponent, fogComponent, cutoutComponent);
var pipelineKeys = createPipelineKeys();
InstancingPrograms.reload(sources, pipelineKeys, vertexComponents, fragmentComponents);
@ -66,7 +66,9 @@ public final class FlwPrograms {
ImmutableList.Builder<PipelineProgramKey> builder = ImmutableList.builder();
for (ContextShader contextShader : ContextShader.values()) {
for (InstanceType<?> instanceType : InstanceType.REGISTRY) {
builder.add(new PipelineProgramKey(instanceType, contextShader));
for (LightShader light : LightShader.REGISTRY.getAll()) {
builder.add(new PipelineProgramKey(instanceType, contextShader, light));
}
}
}
return builder.build();
@ -119,18 +121,4 @@ public final class FlwPrograms {
.switchOn(GlslExpr.variable("_flw_uberCutoutIndex"))
.build(loader);
}
// TODO: Do not uber this component. Shader compile times are very high now
@Nullable
private static UberShaderComponent createLightComponent(SourceLoader loader) {
return UberShaderComponent.builder(Flywheel.rl("light"))
.materialSources(MaterialShaderIndices.lightSources()
.all())
.adapt(FnSignature.create()
.returnType("void")
.name("flw_shaderLight")
.build())
.switchOn(GlslExpr.variable("_flw_uberLightIndex"))
.build(loader);
}
}

View file

@ -9,6 +9,7 @@ import com.google.common.collect.ImmutableList;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.material.LightShader;
import dev.engine_room.flywheel.backend.compile.component.InstanceStructComponent;
import dev.engine_room.flywheel.backend.compile.component.SsboInstanceComponent;
import dev.engine_room.flywheel.backend.compile.core.CompilationHarness;
@ -167,8 +168,8 @@ public class IndirectPrograms extends AtomicReferenceCounted {
return instance != null;
}
public GlProgram getIndirectProgram(InstanceType<?> instanceType, ContextShader contextShader) {
return pipeline.get(new PipelineProgramKey(instanceType, contextShader));
public GlProgram getIndirectProgram(InstanceType<?> instanceType, ContextShader contextShader, LightShader light) {
return pipeline.get(new PipelineProgramKey(instanceType, contextShader, light));
}
public GlProgram getCullingProgram(InstanceType<?> instanceType) {

View file

@ -8,6 +8,7 @@ import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.material.LightShader;
import dev.engine_room.flywheel.backend.gl.GlCompat;
import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
import dev.engine_room.flywheel.backend.glsl.GlslVersion;
@ -78,8 +79,8 @@ public class InstancingPrograms extends AtomicReferenceCounted {
return instance != null;
}
public GlProgram get(InstanceType<?> instanceType, ContextShader contextShader) {
return pipeline.get(new PipelineProgramKey(instanceType, contextShader));
public GlProgram get(InstanceType<?> instanceType, ContextShader contextShader, LightShader light) {
return pipeline.get(new PipelineProgramKey(instanceType, contextShader, light));
}
@Override

View file

@ -56,7 +56,8 @@ public final class PipelineCompiler {
.nameMapper(key -> {
var context = key.contextShader()
.nameLowerCase();
return "pipeline/" + pipeline.compilerMarker() + "/" + context;
return "pipeline/" + pipeline.compilerMarker() + "/" + ResourceUtil.toDebugFileNameNoExtension(key.light()
.source()) + "_" + context;
})
.requireExtensions(extensions)
.enableExtension("GL_ARB_conservative_depth")
@ -65,6 +66,8 @@ public final class PipelineCompiler {
.onCompile((key, comp) -> lightSmoothness.onCompile(comp))
.withResource(API_IMPL_FRAG)
.withComponents(fragmentComponents)
.withResource(key -> key.light()
.source())
.withResource(pipeline.fragmentMain()))
.preLink((key, program) -> {
program.bindAttribLocation("_flw_aPos", 0);

View file

@ -1,12 +1,14 @@
package dev.engine_room.flywheel.backend.compile;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.material.LightShader;
/**
* Represents the entire context of a program's usage.
*
* @param instanceType The instance shader to use.
* @param contextShader The context shader to use.
* @param light The light shader to use.
*/
public record PipelineProgramKey(InstanceType<?> instanceType, ContextShader contextShader) {
public record PipelineProgramKey(InstanceType<?> instanceType, ContextShader contextShader, LightShader light) {
}

View file

@ -55,8 +55,7 @@ public final class MaterialEncoder {
public static int packUberShader(Material material) {
var fog = MaterialShaderIndices.fogIndex(material.fog());
var cutout = MaterialShaderIndices.cutoutIndex(material.cutout());
var light = MaterialShaderIndices.lightIndex(material.light());
return (light & 0x3FF) | (cutout & 0x3FF) << 10 | (fog & 0x3FF) << 20;
return (cutout & 0xFFFF) | (fog & 0xFFFF) << 16;
}
// Packed format:

View file

@ -16,7 +16,9 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.AbstractTexture;
public final class MaterialRenderState {
public static final Comparator<Material> COMPARATOR = Comparator.comparing(Material::texture)
public static final Comparator<Material> COMPARATOR = Comparator.comparing((Material m) -> m.light()
.source())
.thenComparing(Material::texture)
.thenComparing(Material::blur)
.thenComparing(Material::mipmap)
.thenComparing(Material::backfaceCulling)
@ -177,4 +179,18 @@ public final class MaterialRenderState {
RenderSystem.depthMask(true);
RenderSystem.colorMask(true, true, true, true);
}
public static boolean materialEquals(Material lhs, Material rhs) {
if (lhs == rhs) {
return true;
}
// Not here because ubershader: useLight, useOverlay, diffuse, shaders, fog shader, and cutout shader
// Everything in the comparator should be here.
return lhs.blur() == rhs.blur() && lhs.mipmap() == rhs.mipmap() && lhs.backfaceCulling() == rhs.backfaceCulling() && lhs.polygonOffset() == rhs.polygonOffset() && lhs.light()
.source()
.equals(rhs.light()
.source()) && lhs.texture()
.equals(rhs.texture()) && lhs.depthTest() == rhs.depthTest() && lhs.transparency() == rhs.transparency() && lhs.writeMask() == rhs.writeMask();
}
}

View file

@ -28,6 +28,7 @@ import dev.engine_room.flywheel.backend.engine.embed.Environment;
import dev.engine_room.flywheel.backend.engine.uniform.Uniforms;
import dev.engine_room.flywheel.backend.gl.GlCompat;
import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
import dev.engine_room.flywheel.lib.material.LightShaders;
import dev.engine_room.flywheel.lib.math.MoreMath;
public class IndirectCullingGroup<I extends Instance> {
@ -49,7 +50,6 @@ public class IndirectCullingGroup<I extends Instance> {
private final IndirectPrograms programs;
private final GlProgram cullProgram;
private final GlProgram applyProgram;
private final GlProgram drawProgram;
private boolean needsDrawBarrier;
private boolean needsDrawSort;
@ -65,7 +65,6 @@ public class IndirectCullingGroup<I extends Instance> {
this.programs = programs;
cullProgram = programs.getCullingProgram(instanceType);
applyProgram = programs.getApplyProgram();
drawProgram = programs.getIndirectProgram(instanceType, environment.contextShader());
}
public void flushInstancers() {
@ -158,20 +157,23 @@ public class IndirectCullingGroup<I extends Instance> {
for (int start = 0, i = 0; i < indirectDraws.size(); i++) {
var draw1 = indirectDraws.get(i);
var material1 = draw1.material();
var visualType1 = draw1.visualType();
// if the next draw call has a different VisualType or Material, start a new MultiDraw
if (i == indirectDraws.size() - 1 || visualType1 != indirectDraws.get(i + 1)
.visualType() || !material1.equals(indirectDraws.get(i + 1)
.material())) {
multiDraws.computeIfAbsent(visualType1, s -> new ArrayList<>())
.add(new MultiDraw(material1, start, i + 1));
if (i == indirectDraws.size() - 1 || incompatibleDraws(draw1, indirectDraws.get(i + 1))) {
multiDraws.computeIfAbsent(draw1.visualType(), s -> new ArrayList<>())
.add(new MultiDraw(draw1.material(), start, i + 1));
start = i + 1;
}
}
}
private boolean incompatibleDraws(IndirectDraw draw1, IndirectDraw draw2) {
if (draw1.visualType() != draw2.visualType()) {
return true;
}
return !MaterialRenderState.materialEquals(draw1.material(), draw2.material());
}
public boolean hasVisualType(VisualType visualType) {
return multiDraws.containsKey(visualType);
}
@ -199,17 +201,25 @@ public class IndirectCullingGroup<I extends Instance> {
return;
}
drawProgram.bind();
buffers.bindForDraw();
environment.setupDraw(drawProgram);
drawBarrier();
var flwBaseDraw = drawProgram.getUniformLocation("_flw_baseDraw");
GlProgram lastProgram = null;
int baseDrawUniformLoc = -1;
for (var multiDraw : multiDraws.get(visualType)) {
glUniform1ui(flwBaseDraw, multiDraw.start);
var drawProgram = programs.getIndirectProgram(instanceType, environment.contextShader(), multiDraw.material.light());
if (drawProgram != lastProgram) {
lastProgram = drawProgram;
// Don't need to do this unless the program changes.
drawProgram.bind();
environment.setupDraw(drawProgram);
baseDrawUniformLoc = drawProgram.getUniformLocation("_flw_baseDraw");
}
glUniform1ui(baseDrawUniformLoc, multiDraw.start);
MaterialRenderState.setup(multiDraw.material);
@ -218,7 +228,7 @@ public class IndirectCullingGroup<I extends Instance> {
}
public void bindWithContextShader(ContextShader override) {
var program = programs.getIndirectProgram(instanceType, override);
var program = programs.getIndirectProgram(instanceType, override, LightShaders.SMOOTH_WHEN_EMBEDDED);
program.bind();
@ -226,7 +236,7 @@ public class IndirectCullingGroup<I extends Instance> {
drawBarrier();
var flwBaseDraw = drawProgram.getUniformLocation("_flw_baseDraw");
var flwBaseDraw = program.getUniformLocation("_flw_baseDraw");
glUniform1ui(flwBaseDraw, 0);
}

View file

@ -28,6 +28,7 @@ import dev.engine_room.flywheel.backend.gl.GlStateTracker;
import dev.engine_room.flywheel.backend.gl.TextureBuffer;
import dev.engine_room.flywheel.backend.gl.array.GlVertexArray;
import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
import dev.engine_room.flywheel.lib.material.LightShaders;
import dev.engine_room.flywheel.lib.material.SimpleMaterial;
import net.minecraft.client.resources.model.ModelBakery;
@ -170,7 +171,7 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
GroupKey<?> shader = groupEntry.getKey();
var program = programs.get(shader.instanceType(), ContextShader.CRUMBLING);
var program = programs.get(shader.instanceType(), ContextShader.CRUMBLING, LightShaders.SMOOTH_WHEN_EMBEDDED);
program.bind();
for (var progressEntry : byProgress.int2ObjectEntrySet()) {

View file

@ -54,12 +54,13 @@ public class InstancedRenderStage {
var environment = shader.environment();
var program = programs.get(shader.instanceType(), environment.contextShader());
program.bind();
environment.setupDraw(program);
for (var drawCall : drawCalls.draws) {
var program = programs.get(shader.instanceType(), environment.contextShader(), drawCall.material()
.light());
program.bind();
environment.setupDraw(program);
var material = drawCall.material();
uploadMaterialUniform(program, material);

View file

@ -1,4 +1,3 @@
uint _flw_uberMaterialFragmentIndex;
uint _flw_uberFogIndex;
uint _flw_uberCutoutIndex;
uint _flw_uberLightIndex;

View file

@ -6,7 +6,7 @@ flat in uvec3 _flw_packedMaterial;
void main() {
_flw_uberMaterialFragmentIndex = _flw_packedMaterial.x;
_flw_unpackUint3x10(_flw_packedMaterial.y, _flw_uberFogIndex, _flw_uberCutoutIndex, _flw_uberLightIndex);
_flw_unpackUint2x16(_flw_packedMaterial.y, _flw_uberFogIndex, _flw_uberCutoutIndex);
_flw_unpackMaterialProperties(_flw_packedMaterial.z, flw_material);
_flw_main();

View file

@ -5,7 +5,7 @@ uniform uvec4 _flw_packedMaterial;
void main() {
_flw_uberMaterialFragmentIndex = _flw_packedMaterial.y;
_flw_unpackUint3x10(_flw_packedMaterial.z, _flw_uberFogIndex, _flw_uberCutoutIndex, _flw_uberLightIndex);
_flw_unpackUint2x16(_flw_packedMaterial.z, _flw_uberFogIndex, _flw_uberCutoutIndex);
_flw_unpackMaterialProperties(_flw_packedMaterial.w, flw_material);
_flw_main();

View file

@ -53,9 +53,3 @@ void _flw_unpackUint2x16(uint s, out uint hi, out uint lo) {
hi = (s >> 16) & 0xFFFFu;
lo = s & 0xFFFFu;
}
void _flw_unpackUint3x10(uint s, out uint hi, out uint mi, out uint lo) {
hi = (s >> 20) & 0x3FFu;
mi = (s >> 10) & 0x3FFu;
lo = s & 0x3FFu;
}