From 27e5b609af492268bf291eee7236e979318cd5cc Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sat, 21 Sep 2024 13:41:55 -0700 Subject: [PATCH] Reject eagerness return to lazy - Eagerly load ALL shaders in ShaderSources, resolving imports there - Compile and cache programs on-demand - Move gl state try blocks to EngineImpl - EngineImpl catches shader exceptions and triggers a fallback --- .../flywheel/backend/compile/FlwPrograms.java | 23 +-- .../backend/compile/IndirectPrograms.java | 50 ++----- .../backend/compile/InstancingPrograms.java | 29 ++-- .../compile/core/CompilationHarness.java | 32 ++--- .../backend/compile/core/ShaderException.java | 4 + .../flywheel/backend/engine/DrawManager.java | 2 + .../flywheel/backend/engine/EngineImpl.java | 19 ++- .../engine/indirect/IndirectDrawManager.java | 132 +++++++++--------- .../instancing/InstancedDrawManager.java | 74 +++++----- .../flywheel/backend/glsl/ShaderSources.java | 123 ++++++++++------ .../flywheel/backend/glsl/SourceFile.java | 5 +- .../backend/glsl/MockShaderSources.java | 52 +++++-- .../backend/glsl/TestShaderSourceLoading.java | 8 +- 13 files changed, 293 insertions(+), 260 deletions(-) create mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/compile/core/ShaderException.java diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/FlwPrograms.java b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/FlwPrograms.java index 4bb7a5066..8da5010e3 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/FlwPrograms.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/FlwPrograms.java @@ -6,11 +6,7 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -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; @@ -43,9 +39,11 @@ public final class FlwPrograms { var vertexComponentsHeader = loader.find(COMPONENTS_HEADER_VERT); var fragmentComponentsHeader = loader.find(COMPONENTS_HEADER_FRAG); + // TODO: de-uber material shaders var vertexMaterialComponent = createVertexMaterialComponent(loader); var fragmentMaterialComponent = createFragmentMaterialComponent(loader); var fogComponent = createFogComponent(loader); + // TODO: separate compilation for cutout OFF, but keep the rest uber'd var cutoutComponent = createCutoutComponent(loader); if (stats.errored() || vertexComponentsHeader == null || fragmentComponentsHeader == null || vertexMaterialComponent == null || fragmentMaterialComponent == null || fogComponent == null || cutoutComponent == null) { @@ -57,21 +55,8 @@ public final class FlwPrograms { List vertexComponents = List.of(vertexComponentsHeader, vertexMaterialComponent); List fragmentComponents = List.of(fragmentComponentsHeader, fragmentMaterialComponent, fogComponent, cutoutComponent); - var pipelineKeys = createPipelineKeys(); - InstancingPrograms.reload(sources, pipelineKeys, vertexComponents, fragmentComponents); - IndirectPrograms.reload(sources, pipelineKeys, vertexComponents, fragmentComponents); - } - - private static ImmutableList createPipelineKeys() { - ImmutableList.Builder builder = ImmutableList.builder(); - for (ContextShader contextShader : ContextShader.values()) { - for (InstanceType instanceType : InstanceType.REGISTRY) { - for (LightShader light : LightShader.REGISTRY.getAll()) { - builder.add(new PipelineProgramKey(instanceType, contextShader, light)); - } - } - } - return builder.build(); + InstancingPrograms.reload(sources, vertexComponents, fragmentComponents); + IndirectPrograms.reload(sources, vertexComponents, fragmentComponents); } @Nullable diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/IndirectPrograms.java b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/IndirectPrograms.java index 645f00451..ba226541e 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/IndirectPrograms.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/IndirectPrograms.java @@ -1,7 +1,6 @@ package dev.engine_room.flywheel.backend.compile; import java.util.List; -import java.util.Map; import org.jetbrains.annotations.Nullable; @@ -43,11 +42,11 @@ public class IndirectPrograms extends AtomicReferenceCounted { @Nullable private static IndirectPrograms instance; - private final Map pipeline; - private final Map, GlProgram> culling; - private final Map utils; + private final CompilationHarness pipeline; + private final CompilationHarness> culling; + private final CompilationHarness utils; - private IndirectPrograms(Map pipeline, Map, GlProgram> culling, Map utils) { + private IndirectPrograms(CompilationHarness pipeline, CompilationHarness> culling, CompilationHarness utils) { this.pipeline = pipeline; this.culling = culling; this.utils = utils; @@ -81,32 +80,16 @@ public class IndirectPrograms extends AtomicReferenceCounted { return extensions.build(); } - static void reload(ShaderSources sources, ImmutableList pipelineKeys, List vertexComponents, List fragmentComponents) { + static void reload(ShaderSources sources, List vertexComponents, List fragmentComponents) { if (!GlCompat.SUPPORTS_INDIRECT) { return; } - IndirectPrograms newInstance = null; - var pipelineCompiler = PipelineCompiler.create(sources, Pipelines.INDIRECT, vertexComponents, fragmentComponents, EXTENSIONS); var cullingCompiler = createCullingCompiler(sources); var utilCompiler = createUtilCompiler(sources); - try { - var pipelineResult = pipelineCompiler.compileAndReportErrors(pipelineKeys); - var cullingResult = cullingCompiler.compileAndReportErrors(createCullingKeys()); - var utils = utilCompiler.compileAndReportErrors(UTIL_SHADERS); - - if (pipelineResult != null && cullingResult != null && utils != null) { - newInstance = new IndirectPrograms(pipelineResult, cullingResult, utils); - } - } catch (Throwable t) { - FlwPrograms.LOGGER.error("Failed to compile indirect programs", t); - } - - pipelineCompiler.delete(); - cullingCompiler.delete(); - utilCompiler.delete(); + IndirectPrograms newInstance = new IndirectPrograms(pipelineCompiler, cullingCompiler, utilCompiler); setInstance(newInstance); } @@ -142,14 +125,6 @@ public class IndirectPrograms extends AtomicReferenceCounted { .harness("utilities", sources); } - private static ImmutableList> createCullingKeys() { - ImmutableList.Builder> builder = ImmutableList.builder(); - for (InstanceType instanceType : InstanceType.REGISTRY) { - builder.add(instanceType); - } - return builder.build(); - } - static void setInstance(@Nullable IndirectPrograms newInstance) { if (instance != null) { instance.release(); @@ -169,6 +144,10 @@ public class IndirectPrograms extends AtomicReferenceCounted { return instance != null; } + public static void kill() { + setInstance(null); + } + public GlProgram getIndirectProgram(InstanceType instanceType, ContextShader contextShader, LightShader light) { return pipeline.get(new PipelineProgramKey(instanceType, contextShader, light)); } @@ -195,11 +174,8 @@ public class IndirectPrograms extends AtomicReferenceCounted { @Override protected void _delete() { - pipeline.values() - .forEach(GlProgram::delete); - culling.values() - .forEach(GlProgram::delete); - utils.values() - .forEach(GlProgram::delete); + pipeline.delete(); + culling.delete(); + utils.delete(); } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/InstancingPrograms.java b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/InstancingPrograms.java index 6b9fcfad9..e63c80955 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/InstancingPrograms.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/InstancingPrograms.java @@ -1,7 +1,6 @@ package dev.engine_room.flywheel.backend.compile; import java.util.List; -import java.util.Map; import org.jetbrains.annotations.Nullable; @@ -9,6 +8,7 @@ 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.compile.core.CompilationHarness; 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; @@ -22,9 +22,9 @@ public class InstancingPrograms extends AtomicReferenceCounted { @Nullable private static InstancingPrograms instance; - private final Map pipeline; + private final CompilationHarness pipeline; - private InstancingPrograms(Map pipeline) { + private InstancingPrograms(CompilationHarness pipeline) { this.pipeline = pipeline; } @@ -36,26 +36,14 @@ public class InstancingPrograms extends AtomicReferenceCounted { return extensions.build(); } - static void reload(ShaderSources sources, ImmutableList pipelineKeys, List vertexComponents, List fragmentComponents) { + static void reload(ShaderSources sources, List vertexComponents, List fragmentComponents) { if (!GlCompat.SUPPORTS_INSTANCING) { return; } - InstancingPrograms newInstance = null; var pipelineCompiler = PipelineCompiler.create(sources, Pipelines.INSTANCING, vertexComponents, fragmentComponents, EXTENSIONS); - - try { - var pipelineResult = pipelineCompiler.compileAndReportErrors(pipelineKeys); - - if (pipelineResult != null) { - newInstance = new InstancingPrograms(pipelineResult); - } - } catch (Throwable t) { - FlwPrograms.LOGGER.error("Failed to compile instancing programs", t); - } - - pipelineCompiler.delete(); + InstancingPrograms newInstance = new InstancingPrograms(pipelineCompiler); setInstance(newInstance); } @@ -79,13 +67,16 @@ public class InstancingPrograms extends AtomicReferenceCounted { return instance != null; } + public static void kill() { + setInstance(null); + } + public GlProgram get(InstanceType instanceType, ContextShader contextShader, LightShader light) { return pipeline.get(new PipelineProgramKey(instanceType, contextShader, light)); } @Override protected void _delete() { - pipeline.values() - .forEach(GlProgram::delete); + pipeline.delete(); } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/core/CompilationHarness.java b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/core/CompilationHarness.java index 3bd497ace..afa7fa81e 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/core/CompilationHarness.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/core/CompilationHarness.java @@ -1,6 +1,5 @@ package dev.engine_room.flywheel.backend.compile.core; -import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -16,6 +15,8 @@ public class CompilationHarness { private final ProgramLinker programLinker; private final CompilerStats stats; + private final Map programs = new HashMap<>(); + public CompilationHarness(String marker, ShaderSources sources, KeyCompiler compiler) { this.compiler = compiler; stats = new CompilerStats(marker); @@ -24,23 +25,16 @@ public class CompilationHarness { programLinker = new ProgramLinker(stats); } - @Nullable - public Map compileAndReportErrors(Collection keys) { - stats.start(); - Map out = new HashMap<>(); - for (var key : keys) { - GlProgram glProgram = compiler.compile(key, sourceLoader, shaderCache, programLinker); - if (out != null && glProgram != null) { - out.put(key, glProgram); - } else { - out = null; // Return null when a preloading error occurs. - } - } - stats.finish(); + public GlProgram get(K key) { + return programs.computeIfAbsent(key, this::compile); + } - if (stats.errored()) { - stats.emitErrorLog(); - return null; + private GlProgram compile(K key) { + var out = compiler.compile(key, sourceLoader, shaderCache, programLinker); + + if (out == null) { + // TODO: populate exception with error details + throw new ShaderException(); } return out; @@ -48,6 +42,10 @@ public class CompilationHarness { public void delete() { shaderCache.delete(); + + for (var program : programs.values()) { + program.delete(); + } } public interface KeyCompiler { diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/core/ShaderException.java b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/core/ShaderException.java new file mode 100644 index 000000000..1a3be8dab --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/core/ShaderException.java @@ -0,0 +1,4 @@ +package dev.engine_room.flywheel.backend.compile.core; + +public class ShaderException extends RuntimeException { +} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/DrawManager.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/DrawManager.java index 19bdfeae1..06886d0fe 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/DrawManager.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/DrawManager.java @@ -135,6 +135,8 @@ public abstract class DrawManager> { initializationQueue.clear(); } + public abstract void triggerFallback(); + protected record UninitializedInstancer(InstancerKey key, N instancer) { } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java index 394085a70..3a96c4c59 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java @@ -13,6 +13,7 @@ import dev.engine_room.flywheel.api.task.Plan; import dev.engine_room.flywheel.api.visualization.VisualEmbedding; import dev.engine_room.flywheel.api.visualization.VisualType; import dev.engine_room.flywheel.api.visualization.VisualizationContext; +import dev.engine_room.flywheel.backend.compile.core.ShaderException; import dev.engine_room.flywheel.backend.engine.embed.EmbeddedEnvironment; import dev.engine_room.flywheel.backend.engine.embed.Environment; import dev.engine_room.flywheel.backend.engine.embed.EnvironmentStorage; @@ -90,17 +91,27 @@ public class EngineImpl implements Engine { Uniforms.update(context); environmentStorage.flush(); drawManager.flush(lightStorage, environmentStorage); + } catch (ShaderException e) { + triggerFallback(); } } @Override public void render(RenderContext context, VisualType visualType) { - drawManager.render(visualType); + try (var state = GlStateTracker.getRestoreState()) { + drawManager.render(visualType); + } catch (ShaderException e) { + triggerFallback(); + } } @Override public void renderCrumbling(RenderContext context, List crumblingBlocks) { - drawManager.renderCrumbling(crumblingBlocks); + try (var state = GlStateTracker.getRestoreState()) { + drawManager.renderCrumbling(crumblingBlocks); + } catch (ShaderException e) { + triggerFallback(); + } } @Override @@ -110,6 +121,10 @@ public class EngineImpl implements Engine { environmentStorage.delete(); } + private void triggerFallback() { + drawManager.triggerFallback(); + } + public Instancer instancer(Environment environment, InstanceType type, Model model, VisualType visualType, int bias) { return drawManager.getInstancer(environment, type, model, visualType, bias); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java index d3b7c140b..ac6093e47 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java @@ -29,12 +29,12 @@ import dev.engine_room.flywheel.backend.engine.MeshPool; import dev.engine_room.flywheel.backend.engine.TextureBinder; import dev.engine_room.flywheel.backend.engine.embed.EnvironmentStorage; import dev.engine_room.flywheel.backend.engine.uniform.Uniforms; -import dev.engine_room.flywheel.backend.gl.GlStateTracker; import dev.engine_room.flywheel.backend.gl.array.GlVertexArray; import dev.engine_room.flywheel.backend.gl.buffer.GlBuffer; import dev.engine_room.flywheel.backend.gl.buffer.GlBufferType; import dev.engine_room.flywheel.lib.material.SimpleMaterial; import dev.engine_room.flywheel.lib.memory.MemoryBlock; +import net.minecraft.client.Minecraft; import net.minecraft.client.resources.model.ModelBakery; public class IndirectDrawManager extends DrawManager> { @@ -91,26 +91,24 @@ public class IndirectDrawManager extends DrawManager> { return; } - try (var state = GlStateTracker.getRestoreState()) { - TextureBinder.bindLightAndOverlay(); + TextureBinder.bindLightAndOverlay(); - vertexArray.bindForDraw(); - lightBuffers.bind(); - matrixBuffer.bind(); - Uniforms.bindAll(); + vertexArray.bindForDraw(); + lightBuffers.bind(); + matrixBuffer.bind(); + Uniforms.bindAll(); - if (needsBarrier) { - glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); - needsBarrier = false; - } - - for (var group : cullingGroups.values()) { - group.submit(visualType); - } - - MaterialRenderState.reset(); - TextureBinder.resetLightAndOverlay(); + if (needsBarrier) { + glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); + needsBarrier = false; } + + for (var group : cullingGroups.values()) { + group.submit(visualType); + } + + MaterialRenderState.reset(); + TextureBinder.resetLightAndOverlay(); } @Override @@ -194,63 +192,67 @@ public class IndirectDrawManager extends DrawManager> { return; } - try (var state = GlStateTracker.getRestoreState()) { - TextureBinder.bindLightAndOverlay(); + TextureBinder.bindLightAndOverlay(); - vertexArray.bindForDraw(); - Uniforms.bindAll(); + vertexArray.bindForDraw(); + Uniforms.bindAll(); - var crumblingMaterial = SimpleMaterial.builder(); + var crumblingMaterial = SimpleMaterial.builder(); - // Scratch memory for writing draw commands. - var block = MemoryBlock.malloc(IndirectBuffers.DRAW_COMMAND_STRIDE); + // Scratch memory for writing draw commands. + var block = MemoryBlock.malloc(IndirectBuffers.DRAW_COMMAND_STRIDE); - // Set up the crumbling program buffers. Nothing changes here between draws. - GlBufferType.DRAW_INDIRECT_BUFFER.bind(crumblingDrawBuffer.handle()); - glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.DRAW, crumblingDrawBuffer.handle(), 0, IndirectBuffers.DRAW_COMMAND_STRIDE); + // Set up the crumbling program buffers. Nothing changes here between draws. + GlBufferType.DRAW_INDIRECT_BUFFER.bind(crumblingDrawBuffer.handle()); + glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.DRAW, crumblingDrawBuffer.handle(), 0, IndirectBuffers.DRAW_COMMAND_STRIDE); - for (var groupEntry : byType.entrySet()) { - var byProgress = groupEntry.getValue(); + for (var groupEntry : byType.entrySet()) { + var byProgress = groupEntry.getValue(); - GroupKey groupKey = groupEntry.getKey(); - IndirectCullingGroup cullingGroup = cullingGroups.get(groupKey.instanceType()); + GroupKey groupKey = groupEntry.getKey(); + IndirectCullingGroup cullingGroup = cullingGroups.get(groupKey.instanceType()); - if (cullingGroup == null) { - continue; - } - - cullingGroup.bindWithContextShader(ContextShader.CRUMBLING); - - for (var progressEntry : byProgress.int2ObjectEntrySet()) { - Samplers.CRUMBLING.makeActive(); - TextureBinder.bind(ModelBakery.BREAKING_LOCATIONS.get(progressEntry.getIntKey())); - - for (var instanceHandlePair : progressEntry.getValue()) { - IndirectInstancer instancer = instanceHandlePair.getFirst(); - int instanceIndex = instanceHandlePair.getSecond().index; - - for (IndirectDraw draw : instancer.draws()) { - // Transform the material to be suited for crumbling. - CommonCrumbling.applyCrumblingProperties(crumblingMaterial, draw.material()); - - MaterialRenderState.setup(crumblingMaterial); - - // Upload the draw command. - draw.writeWithOverrides(block.ptr(), instanceIndex, crumblingMaterial); - crumblingDrawBuffer.upload(block); - - // Submit! Everything is already bound by here. - glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, 0); - } - } - - } + if (cullingGroup == null) { + continue; } - MaterialRenderState.reset(); - TextureBinder.resetLightAndOverlay(); + cullingGroup.bindWithContextShader(ContextShader.CRUMBLING); - block.free(); + for (var progressEntry : byProgress.int2ObjectEntrySet()) { + Samplers.CRUMBLING.makeActive(); + TextureBinder.bind(ModelBakery.BREAKING_LOCATIONS.get(progressEntry.getIntKey())); + + for (var instanceHandlePair : progressEntry.getValue()) { + IndirectInstancer instancer = instanceHandlePair.getFirst(); + int instanceIndex = instanceHandlePair.getSecond().index; + + for (IndirectDraw draw : instancer.draws()) { + // Transform the material to be suited for crumbling. + CommonCrumbling.applyCrumblingProperties(crumblingMaterial, draw.material()); + + MaterialRenderState.setup(crumblingMaterial); + + // Upload the draw command. + draw.writeWithOverrides(block.ptr(), instanceIndex, crumblingMaterial); + crumblingDrawBuffer.upload(block); + + // Submit! Everything is already bound by here. + glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, 0); + } + } + + } } + + MaterialRenderState.reset(); + TextureBinder.resetLightAndOverlay(); + + block.free(); + } + + @Override + public void triggerFallback() { + IndirectPrograms.kill(); + Minecraft.getInstance().levelRenderer.allChanged(); } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedDrawManager.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedDrawManager.java index 400dae79c..2e5c07e28 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedDrawManager.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedDrawManager.java @@ -25,12 +25,12 @@ import dev.engine_room.flywheel.backend.engine.MeshPool; import dev.engine_room.flywheel.backend.engine.TextureBinder; import dev.engine_room.flywheel.backend.engine.embed.EnvironmentStorage; import dev.engine_room.flywheel.backend.engine.uniform.Uniforms; -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.Minecraft; import net.minecraft.client.resources.model.ModelBakery; public class InstancedDrawManager extends DrawManager> { @@ -94,17 +94,15 @@ public class InstancedDrawManager extends DrawManager> { return; } - try (var state = GlStateTracker.getRestoreState()) { - Uniforms.bindAll(); - vao.bindForDraw(); - TextureBinder.bindLightAndOverlay(); - light.bind(); + Uniforms.bindAll(); + vao.bindForDraw(); + TextureBinder.bindLightAndOverlay(); + light.bind(); - stage.draw(instanceTexture, programs); + stage.draw(instanceTexture, programs); - MaterialRenderState.reset(); - TextureBinder.resetLightAndOverlay(); - } + MaterialRenderState.reset(); + TextureBinder.resetLightAndOverlay(); } @Override @@ -162,46 +160,50 @@ public class InstancedDrawManager extends DrawManager> { var crumblingMaterial = SimpleMaterial.builder(); - try (var state = GlStateTracker.getRestoreState()) { - Uniforms.bindAll(); - vao.bindForDraw(); - TextureBinder.bindLightAndOverlay(); + Uniforms.bindAll(); + vao.bindForDraw(); + TextureBinder.bindLightAndOverlay(); - for (var groupEntry : byType.entrySet()) { - var byProgress = groupEntry.getValue(); + for (var groupEntry : byType.entrySet()) { + var byProgress = groupEntry.getValue(); - GroupKey shader = groupEntry.getKey(); + GroupKey shader = groupEntry.getKey(); - var program = programs.get(shader.instanceType(), ContextShader.CRUMBLING, LightShaders.SMOOTH_WHEN_EMBEDDED); - program.bind(); + var program = programs.get(shader.instanceType(), ContextShader.CRUMBLING, LightShaders.SMOOTH_WHEN_EMBEDDED); + program.bind(); - for (var progressEntry : byProgress.int2ObjectEntrySet()) { - Samplers.CRUMBLING.makeActive(); - TextureBinder.bind(ModelBakery.BREAKING_LOCATIONS.get(progressEntry.getIntKey())); + for (var progressEntry : byProgress.int2ObjectEntrySet()) { + Samplers.CRUMBLING.makeActive(); + TextureBinder.bind(ModelBakery.BREAKING_LOCATIONS.get(progressEntry.getIntKey())); - for (var instanceHandlePair : progressEntry.getValue()) { - InstancedInstancer instancer = instanceHandlePair.getFirst(); - var index = instanceHandlePair.getSecond().index; + for (var instanceHandlePair : progressEntry.getValue()) { + InstancedInstancer instancer = instanceHandlePair.getFirst(); + var index = instanceHandlePair.getSecond().index; - program.setInt("_flw_baseInstance", index); + program.setInt("_flw_baseInstance", index); - for (InstancedDraw draw : instancer.draws()) { - CommonCrumbling.applyCrumblingProperties(crumblingMaterial, draw.material()); - uploadMaterialUniform(program, crumblingMaterial); + for (InstancedDraw draw : instancer.draws()) { + CommonCrumbling.applyCrumblingProperties(crumblingMaterial, draw.material()); + uploadMaterialUniform(program, crumblingMaterial); - MaterialRenderState.setup(crumblingMaterial); + MaterialRenderState.setup(crumblingMaterial); - Samplers.INSTANCE_BUFFER.makeActive(); + Samplers.INSTANCE_BUFFER.makeActive(); - draw.renderOne(instanceTexture); - } + draw.renderOne(instanceTexture); } } } - - MaterialRenderState.reset(); - TextureBinder.resetLightAndOverlay(); } + + MaterialRenderState.reset(); + TextureBinder.resetLightAndOverlay(); + } + + @Override + public void triggerFallback() { + InstancingPrograms.kill(); + Minecraft.getInstance().levelRenderer.allChanged(); } public static void uploadMaterialUniform(GlProgram program, Material material) { diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/ShaderSources.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/ShaderSources.java index 0c1c08fed..18af74e6d 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/ShaderSources.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/ShaderSources.java @@ -11,7 +11,10 @@ import java.util.Map; import org.jetbrains.annotations.VisibleForTesting; +import dev.engine_room.flywheel.backend.compile.FlwPrograms; +import dev.engine_room.flywheel.lib.util.StringUtil; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.Resource; import net.minecraft.server.packs.resources.ResourceManager; /** @@ -20,62 +23,96 @@ import net.minecraft.server.packs.resources.ResourceManager; public class ShaderSources { public static final String SHADER_DIR = "flywheel/"; - private final ResourceManager manager; - @VisibleForTesting - protected final Map cache = new HashMap<>(); - - /** - * Tracks where we are in the mutual recursion to detect circular imports. - */ - private final Deque findStack = new ArrayDeque<>(); + protected final Map cache; public ShaderSources(ResourceManager manager) { - this.manager = manager; + var sourceFinder = new SourceFinder(manager); + + long loadStart = System.nanoTime(); + manager.listResources("flywheel", ShaderSources::isShader) + .forEach(sourceFinder::rootLoad); + + long loadEnd = System.nanoTime(); + + FlwPrograms.LOGGER.info("Loaded {} shader sources in {}", sourceFinder.results.size(), StringUtil.formatTime(loadEnd - loadStart)); + + this.cache = sourceFinder.results; } - public ShaderSources(ResourceManager manager, Map preloadCache) { - this.manager = manager; - cache.putAll(preloadCache); + private static ResourceLocation locationWithoutFlywheelPrefix(ResourceLocation loc) { + return new ResourceLocation(loc.getNamespace(), loc.getPath() + .substring(SHADER_DIR.length())); } public LoadResult find(ResourceLocation location) { - if (findStack.contains(location)) { - // Make a copy of the find stack with the offending location added on top to show the full path. + return cache.computeIfAbsent(location, loc -> new LoadResult.Failure(new LoadError.ResourceError(loc))); + } + + private static boolean isShader(ResourceLocation loc) { + var path = loc.getPath(); + return path.endsWith(".glsl") || path.endsWith(".vert") || path.endsWith(".frag") || path.endsWith(".comp"); + } + + private static class SourceFinder { + private final Deque findStack = new ArrayDeque<>(); + private final Map results = new HashMap<>(); + private final ResourceManager manager; + + public SourceFinder(ResourceManager manager) { + this.manager = manager; + } + + public void rootLoad(ResourceLocation loc, Resource resource) { + var strippedLoc = locationWithoutFlywheelPrefix(loc); + + if (results.containsKey(strippedLoc)) { + // Some other source already #included this one. + return; + } + + this.results.put(strippedLoc, readResource(strippedLoc, resource)); + } + + public LoadResult recursiveLoad(ResourceLocation location) { + if (findStack.contains(location)) { + // Make a copy of the find stack with the offending location added on top to show the full path. + findStack.addLast(location); + var copy = List.copyOf(findStack); + findStack.removeLast(); + return new LoadResult.Failure(new LoadError.CircularDependency(location, copy)); + } findStack.addLast(location); - var copy = List.copyOf(findStack); + + LoadResult out = _find(location); + findStack.removeLast(); - return new LoadResult.Failure(new LoadError.CircularDependency(location, copy)); + return out; } - findStack.addLast(location); - LoadResult out = _find(location); - - findStack.removeLast(); - return out; - } - - private LoadResult _find(ResourceLocation location) { - // Can't use computeIfAbsent because mutual recursion causes ConcurrentModificationExceptions - var out = cache.get(location); - if (out == null) { - out = load(location); - cache.put(location, out); + private LoadResult _find(ResourceLocation location) { + // Can't use computeIfAbsent because mutual recursion causes ConcurrentModificationExceptions + var out = results.get(location); + if (out == null) { + out = load(location); + results.put(location, out); + } + return out; } - return out; - } - @VisibleForTesting - protected LoadResult load(ResourceLocation loc) { - return manager.getResource(loc.withPrefix(SHADER_DIR)) - .map(resource -> { - try (InputStream stream = resource.open()) { - String sourceString = new String(stream.readAllBytes(), StandardCharsets.UTF_8); - return SourceFile.parse(this, loc, sourceString); - } catch (IOException e) { - return new LoadResult.Failure(new LoadError.IOError(loc, e)); - } - }) - .orElseGet(() -> new LoadResult.Failure(new LoadError.ResourceError(loc))); + private LoadResult load(ResourceLocation loc) { + return manager.getResource(loc.withPrefix(SHADER_DIR)) + .map(resource -> readResource(loc, resource)) + .orElseGet(() -> new LoadResult.Failure(new LoadError.ResourceError(loc))); + } + + private LoadResult readResource(ResourceLocation loc, Resource resource) { + try (InputStream stream = resource.open()) { + String sourceString = new String(stream.readAllBytes(), StandardCharsets.UTF_8); + return SourceFile.parse(this::recursiveLoad, loc, sourceString); + } catch (IOException e) { + return new LoadResult.Failure(new LoadError.IOError(loc, e)); + } + } } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/SourceFile.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/SourceFile.java index 1f9646c56..31f24b0f1 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/SourceFile.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/SourceFile.java @@ -5,6 +5,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Function; import org.jetbrains.annotations.Nullable; @@ -69,7 +70,7 @@ public class SourceFile implements SourceComponent { return new LoadResult.Success(new SourceFile(name, new SourceLines(name, ""), ImmutableMap.of(), ImmutableMap.of(), ImmutableList.of(), ImmutableMap.of(), ImmutableList.of(), "")); } - public static LoadResult parse(ShaderSources sourceFinder, ResourceLocation name, String stringSource) { + public static LoadResult parse(Function sourceFinder, ResourceLocation name, String stringSource) { var source = new SourceLines(name, stringSource); var imports = Import.parseImports(source); @@ -93,7 +94,7 @@ public class SourceFile implements SourceComponent { continue; } - var result = sourceFinder.find(location); + var result = sourceFinder.apply(location); if (result instanceof LoadResult.Success s) { included.add(s.unwrap()); diff --git a/common/src/test/java/dev/engine_room/flywheel/backend/glsl/MockShaderSources.java b/common/src/test/java/dev/engine_room/flywheel/backend/glsl/MockShaderSources.java index df1339621..4403088a5 100644 --- a/common/src/test/java/dev/engine_room/flywheel/backend/glsl/MockShaderSources.java +++ b/common/src/test/java/dev/engine_room/flywheel/backend/glsl/MockShaderSources.java @@ -1,36 +1,62 @@ package dev.engine_room.flywheel.backend.glsl; import java.io.FileNotFoundException; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.HashMap; +import java.util.List; import java.util.Map; -import org.junit.jupiter.api.Assertions; - import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.packs.resources.ResourceManager; -public class MockShaderSources extends ShaderSources { +public class MockShaderSources { private final Map sources = new HashMap<>(); + private final Map cache = new HashMap<>(); + private final Deque findStack = new ArrayDeque<>(); + public MockShaderSources() { - super(ResourceManager.Empty.INSTANCE); + } public void add(ResourceLocation loc, String source) { sources.put(loc, source); } - @Override - protected LoadResult load(ResourceLocation loc) { + public LoadResult find(ResourceLocation location) { + if (findStack.contains(location)) { + // Make a copy of the find stack with the offending location added on top to show the full path. + findStack.addLast(location); + var copy = List.copyOf(findStack); + findStack.removeLast(); + return new LoadResult.Failure(new LoadError.CircularDependency(location, copy)); + } + findStack.addLast(location); + + LoadResult out = load(location); + + findStack.removeLast(); + return out; + } + + private LoadResult load(ResourceLocation loc) { + var out = cache.get(loc); + if (out != null) { + return out; + } + + var loadResult = _load(loc); + + cache.put(loc, loadResult); + + return loadResult; + } + + private LoadResult _load(ResourceLocation loc) { var maybeFound = sources.get(loc); if (maybeFound == null) { return new LoadResult.Failure(new LoadError.IOError(loc, new FileNotFoundException(loc.toString()))); } - return SourceFile.parse(this, loc, maybeFound); - } - - public LoadResult assertLoaded(ResourceLocation loc) { - Assertions.assertTrue(cache.containsKey(loc), "Expected " + loc + " to be cached"); - return cache.get(loc); + return SourceFile.parse(this::find, loc, maybeFound); } } diff --git a/common/src/test/java/dev/engine_room/flywheel/backend/glsl/TestShaderSourceLoading.java b/common/src/test/java/dev/engine_room/flywheel/backend/glsl/TestShaderSourceLoading.java index 3acf1ba2d..b19c481b7 100644 --- a/common/src/test/java/dev/engine_room/flywheel/backend/glsl/TestShaderSourceLoading.java +++ b/common/src/test/java/dev/engine_room/flywheel/backend/glsl/TestShaderSourceLoading.java @@ -38,7 +38,6 @@ public class TestShaderSourceLoading extends TestBase { sources.add(FLW_B, ""); findAndAssertSuccess(sources, FLW_A); - sources.assertLoaded(FLW_B); } @Test @@ -78,7 +77,6 @@ public class TestShaderSourceLoading extends TestBase { sources.add(FLW_B, ""); SourceFile a = findAndAssertSuccess(sources, FLW_A); - sources.assertLoaded(FLW_B); var includeB = assertSingletonList(a.imports); assertEquals(FLW_B.toString(), includeB.file() @@ -99,7 +97,6 @@ public class TestShaderSourceLoading extends TestBase { sources.add(FLW_B, ""); SourceFile a = findAndAssertSuccess(sources, FLW_A); - sources.assertLoaded(FLW_B); assertEquals(2, a.imports.size()); for (Import include : a.imports) { @@ -112,7 +109,7 @@ public class TestShaderSourceLoading extends TestBase { """, a.finalSource, "Both include statements should be elided."); - LoadResult bResult = sources.assertLoaded(FLW_B); + LoadResult bResult = sources.find(FLW_B); SourceFile b = assertSuccessAndUnwrap(FLW_B, bResult); assertEquals(ImmutableList.of(b), a.included); @@ -148,7 +145,6 @@ public class TestShaderSourceLoading extends TestBase { """); var aErr = findAndAssertError(LoadError.IncludeError.class, sources, FLW_A); - sources.assertLoaded(FLW_B); var recursiveInclude = assertSimpleNestedErrorsToDepth(LoadError.CircularDependency.class, aErr, 2); assertEquals(ImmutableList.of(FLW_A, FLW_B, FLW_A), recursiveInclude.stack()); @@ -168,8 +164,6 @@ public class TestShaderSourceLoading extends TestBase { """); var aErr = findAndAssertError(LoadError.IncludeError.class, sources, FLW_A); - sources.assertLoaded(FLW_B); - sources.assertLoaded(FLW_C); var recursiveInclude = assertSimpleNestedErrorsToDepth(LoadError.CircularDependency.class, aErr, 3); assertEquals(ImmutableList.of(FLW_A, FLW_B, FLW_C, FLW_A), recursiveInclude.stack());