diff --git a/common/src/api/java/dev/engine_room/flywheel/api/instance/Instance.java b/common/src/api/java/dev/engine_room/flywheel/api/instance/Instance.java index 9d86d018c..bd81a3471 100644 --- a/common/src/api/java/dev/engine_room/flywheel/api/instance/Instance.java +++ b/common/src/api/java/dev/engine_room/flywheel/api/instance/Instance.java @@ -12,4 +12,8 @@ public interface Instance { default void delete() { handle().setDeleted(); } + + default void setVisible(boolean visible) { + handle().setVisible(visible); + } } 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..11e375668 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,55 +39,20 @@ public final class FlwPrograms { var vertexComponentsHeader = loader.find(COMPONENTS_HEADER_VERT); var fragmentComponentsHeader = loader.find(COMPONENTS_HEADER_FRAG); - var vertexMaterialComponent = createVertexMaterialComponent(loader); - var fragmentMaterialComponent = createFragmentMaterialComponent(loader); var fogComponent = createFogComponent(loader); - var cutoutComponent = createCutoutComponent(loader); - if (stats.errored() || vertexComponentsHeader == null || fragmentComponentsHeader == null || vertexMaterialComponent == null || fragmentMaterialComponent == null || fogComponent == null || cutoutComponent == null) { + // TODO: separate compilation for cutout OFF, but keep the rest uber'd? + if (stats.errored() || vertexComponentsHeader == null || fragmentComponentsHeader == null || fogComponent == null) { // Probably means the shader sources are missing. stats.emitErrorLog(); return; } - List vertexComponents = List.of(vertexComponentsHeader, vertexMaterialComponent); - List fragmentComponents = List.of(fragmentComponentsHeader, fragmentMaterialComponent, fogComponent, cutoutComponent); + List vertexComponents = List.of(vertexComponentsHeader); + List fragmentComponents = List.of(fragmentComponentsHeader, fogComponent); - 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(); - } - - @Nullable - private static UberShaderComponent createVertexMaterialComponent(SourceLoader loader) { - return UberShaderComponent.builder(Flywheel.rl("material_vertex")) - .materialSources(MaterialShaderIndices.vertexSources() - .all()) - .adapt(FnSignature.ofVoid("flw_materialVertex")) - .switchOn(GlslExpr.variable("_flw_uberMaterialVertexIndex")) - .build(loader); - } - - @Nullable - private static UberShaderComponent createFragmentMaterialComponent(SourceLoader loader) { - return UberShaderComponent.builder(Flywheel.rl("material_fragment")) - .materialSources(MaterialShaderIndices.fragmentSources() - .all()) - .adapt(FnSignature.ofVoid("flw_materialFragment")) - .switchOn(GlslExpr.variable("_flw_uberMaterialFragmentIndex")) - .build(loader); + 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..524a1b583 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; @@ -9,7 +8,9 @@ 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.CutoutShader; import dev.engine_room.flywheel.api.material.LightShader; +import dev.engine_room.flywheel.api.material.MaterialShaders; 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; @@ -43,11 +44,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 +82,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 +127,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,8 +146,12 @@ public class IndirectPrograms extends AtomicReferenceCounted { return instance != null; } - public GlProgram getIndirectProgram(InstanceType instanceType, ContextShader contextShader, LightShader light) { - return pipeline.get(new PipelineProgramKey(instanceType, contextShader, light)); + public static void kill() { + setInstance(null); + } + + public GlProgram getIndirectProgram(InstanceType instanceType, ContextShader contextShader, LightShader light, CutoutShader cutout, MaterialShaders shaders) { + return pipeline.get(new PipelineProgramKey(instanceType, contextShader, light, cutout, shaders)); } public GlProgram getCullingProgram(InstanceType instanceType) { @@ -195,11 +176,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..ac9de915d 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,14 +1,16 @@ package dev.engine_room.flywheel.backend.compile; import java.util.List; -import java.util.Map; 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.CutoutShader; import dev.engine_room.flywheel.api.material.LightShader; +import dev.engine_room.flywheel.api.material.MaterialShaders; +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 +24,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 +38,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 +69,16 @@ public class InstancingPrograms extends AtomicReferenceCounted { return instance != null; } - public GlProgram get(InstanceType instanceType, ContextShader contextShader, LightShader light) { - return pipeline.get(new PipelineProgramKey(instanceType, contextShader, light)); + public static void kill() { + setInstance(null); + } + + public GlProgram get(InstanceType instanceType, ContextShader contextShader, LightShader light, CutoutShader cutout, MaterialShaders materialShaders) { + return pipeline.get(new PipelineProgramKey(instanceType, contextShader, light, cutout, materialShaders)); } @Override protected void _delete() { - pipeline.values() - .forEach(GlProgram::delete); + pipeline.delete(); } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/PipelineCompiler.java b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/PipelineCompiler.java index 405ca7ffe..10e443f16 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/PipelineCompiler.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/PipelineCompiler.java @@ -35,9 +35,11 @@ public final class PipelineCompiler { var instance = ResourceUtil.toDebugFileNameNoExtension(key.instanceType() .vertexShader()); + var material = ResourceUtil.toDebugFileNameNoExtension(key.materialShaders() + .vertexSource()); var context = key.contextShader() .nameLowerCase(); - return "pipeline/" + pipeline.compilerMarker() + "/" + instance + "_" + context; + return "pipeline/" + pipeline.compilerMarker() + "/" + instance + "/" + material + "_" + context; }) .requireExtensions(extensions) .onCompile((key, comp) -> key.contextShader() @@ -47,6 +49,8 @@ public final class PipelineCompiler { .withComponent(key -> new InstanceStructComponent(key.instanceType())) .withResource(key -> key.instanceType() .vertexShader()) + .withResource(key -> key.materialShaders() + .vertexSource()) .withComponents(vertexComponents) .withResource(InternalVertex.LAYOUT_SHADER) .withComponent(key -> pipeline.assembler() @@ -56,8 +60,16 @@ public final class PipelineCompiler { .nameMapper(key -> { var context = key.contextShader() .nameLowerCase(); - return "pipeline/" + pipeline.compilerMarker() + "/" + ResourceUtil.toDebugFileNameNoExtension(key.light() - .source()) + "_" + context; + + var material = ResourceUtil.toDebugFileNameNoExtension(key.materialShaders() + .fragmentSource()); + + var cutout = ResourceUtil.toDebugFileNameNoExtension(key.cutout() + .source()); + + var light = ResourceUtil.toDebugFileNameNoExtension(key.light() + .source()); + return "pipeline/" + pipeline.compilerMarker() + "/frag/" + material + "/" + light + "_" + cutout + "_" + context; }) .requireExtensions(extensions) .enableExtension("GL_ARB_conservative_depth") @@ -65,9 +77,13 @@ public final class PipelineCompiler { .onCompile(comp)) .onCompile((key, comp) -> lightSmoothness.onCompile(comp)) .withResource(API_IMPL_FRAG) + .withResource(key -> key.materialShaders() + .fragmentSource()) .withComponents(fragmentComponents) .withResource(key -> key.light() .source()) + .withResource(key -> key.cutout() + .source()) .withResource(pipeline.fragmentMain())) .preLink((key, program) -> { program.bindAttribLocation("_flw_aPos", 0); diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/PipelineProgramKey.java b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/PipelineProgramKey.java index 3fe6a1033..bd8d0a1a2 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/PipelineProgramKey.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/PipelineProgramKey.java @@ -1,7 +1,9 @@ package dev.engine_room.flywheel.backend.compile; import dev.engine_room.flywheel.api.instance.InstanceType; +import dev.engine_room.flywheel.api.material.CutoutShader; import dev.engine_room.flywheel.api.material.LightShader; +import dev.engine_room.flywheel.api.material.MaterialShaders; /** * Represents the entire context of a program's usage. @@ -10,5 +12,6 @@ import dev.engine_room.flywheel.api.material.LightShader; * @param contextShader The context shader to use. * @param light The light shader to use. */ -public record PipelineProgramKey(InstanceType instanceType, ContextShader contextShader, LightShader light) { +public record PipelineProgramKey(InstanceType instanceType, ContextShader contextShader, LightShader light, + CutoutShader cutout, MaterialShaders materialShaders) { } 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/Compile.java b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/core/Compile.java index 0ba79b486..3375ecf2d 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/core/Compile.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/core/Compile.java @@ -12,12 +12,14 @@ import java.util.function.Function; import org.jetbrains.annotations.Nullable; +import dev.engine_room.flywheel.backend.compile.FlwPrograms; import dev.engine_room.flywheel.backend.gl.shader.GlProgram; import dev.engine_room.flywheel.backend.gl.shader.GlShader; import dev.engine_room.flywheel.backend.gl.shader.ShaderType; import dev.engine_room.flywheel.backend.glsl.GlslVersion; import dev.engine_room.flywheel.backend.glsl.ShaderSources; import dev.engine_room.flywheel.backend.glsl.SourceComponent; +import dev.engine_room.flywheel.lib.util.StringUtil; import net.minecraft.resources.ResourceLocation; /** @@ -122,6 +124,8 @@ public class Compile { @Nullable private GlShader compile(K key, ShaderCache compiler, SourceLoader loader) { + long start = System.nanoTime(); + var components = new ArrayList(); boolean ok = true; for (var fetcher : fetchers) { @@ -137,7 +141,14 @@ public class Compile { } Consumer cb = ctx -> compilationCallbacks.accept(key, ctx); - return compiler.compile(glslVersion, shaderType, nameMapper.apply(key), cb, components); + var name = nameMapper.apply(key); + var out = compiler.compile(glslVersion, shaderType, name, cb, components); + + long end = System.nanoTime(); + + FlwPrograms.LOGGER.debug("Compiled {} in {}", name, StringUtil.formatTime(end - start)); + + return out; } } @@ -177,6 +188,8 @@ public class Compile { throw new IllegalStateException("No shader compilers were added!"); } + long start = System.nanoTime(); + List shaders = new ArrayList<>(); boolean ok = true; @@ -198,6 +211,10 @@ public class Compile { postLink.accept(key, out); } + long end = System.nanoTime(); + + FlwPrograms.LOGGER.debug("Linked {} in {}", key, StringUtil.formatTime(end - start)); + return out; } } 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/AbstractInstancer.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractInstancer.java index 8938fe080..1911ca1c2 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractInstancer.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractInstancer.java @@ -1,7 +1,6 @@ package dev.engine_room.flywheel.backend.engine; import java.util.ArrayList; -import java.util.function.Supplier; import org.jetbrains.annotations.Nullable; @@ -11,10 +10,10 @@ import dev.engine_room.flywheel.api.instance.Instancer; import dev.engine_room.flywheel.backend.engine.embed.Environment; import dev.engine_room.flywheel.backend.util.AtomicBitSet; -public abstract class AbstractInstancer implements Instancer { +public abstract class AbstractInstancer implements Instancer, InstanceHandleImpl.State { public final InstanceType type; public final Environment environment; - private final Supplier> recreate; + private final Recreate recreate; // Lock for all instances, only needs to be used in methods that may run on the TaskExecutor. protected final Object lock = new Object(); @@ -24,28 +23,60 @@ public abstract class AbstractInstancer implements Instancer protected final AtomicBitSet changed = new AtomicBitSet(); protected final AtomicBitSet deleted = new AtomicBitSet(); - protected AbstractInstancer(InstancerKey key, Supplier> recreate) { + protected AbstractInstancer(InstancerKey key, Recreate recreate) { this.type = key.type(); this.environment = key.environment(); this.recreate = recreate; } @Override - public I createInstance() { - synchronized (lock) { - var i = instances.size(); - var handle = new InstanceHandleImpl(); - handle.instancer = this; - handle.recreate = recreate; - handle.index = i; - I instance = type.create(handle); - handle.instance = instance; + public InstanceHandleImpl.State setChanged(int index) { + notifyDirty(index); + return this; + } + @Override + public InstanceHandleImpl.State setDeleted(int index) { + notifyRemoval(index); + return InstanceHandleImpl.Deleted.instance(); + } + + @Override + public InstanceHandleImpl.State setVisible(InstanceHandleImpl handle, int index, boolean visible) { + if (visible) { + return this; + } + + notifyRemoval(index); + + I instance; + synchronized (lock) { + // I think we need to lock to prevent wacky stuff from happening if the array gets resized. + instance = instances.get(index); + } + + return new InstanceHandleImpl.Hidden<>(recreate, instance); + } + + @Override + public I createInstance() { + var handle = new InstanceHandleImpl<>(this); + I instance = type.create(handle); + + synchronized (lock) { + handle.index = instances.size(); addLocked(instance, handle); return instance; } } + public void revealInstance(InstanceHandleImpl handle, I instance) { + synchronized (lock) { + handle.index = instances.size(); + addLocked(instance, handle); + } + } + @Override public void stealInstance(@Nullable I instance) { if (instance == null) { @@ -60,9 +91,19 @@ public abstract class AbstractInstancer implements Instancer } // Should InstanceType have an isInstance method? + @SuppressWarnings("unchecked") var handle = (InstanceHandleImpl) instanceHandle; - if (handle.instancer == this && handle.visible) { + // No need to steal if this instance is already owned by this instancer. + if (handle.state == this) { + return; + } + // Not allowed to steal deleted instances. + if (handle.state instanceof InstanceHandleImpl.Deleted) { + return; + } + // No need to steal if the instance will recreate to us. + if (handle.state instanceof InstanceHandleImpl.Hidden hidden && recreate.equals(hidden.recreate())) { return; } @@ -70,21 +111,21 @@ public abstract class AbstractInstancer implements Instancer // is somehow being stolen by 2 different instancers between threads. // That seems kinda impossible so I'm fine leaving it as is for now. - // Remove the instance from its old instancer. - // This won't have any unwanted effect when the old instancer - // is filtering deleted instances later, so is safe. - handle.setDeleted(); - // Add the instance to this instancer. - handle.instancer = this; - handle.recreate = recreate; + if (handle.state instanceof AbstractInstancer other) { + // Remove the instance from its old instancer. + // This won't have any unwanted effect when the old instancer + // is filtering deleted instances later, so is safe. + other.notifyRemoval(handle.index); - if (handle.visible) { + handle.state = this; // Only lock now that we'll be mutating our state. synchronized (lock) { handle.index = instances.size(); addLocked(instance, handle); } + } else if (handle.state instanceof InstanceHandleImpl.Hidden) { + handle.state = new InstanceHandleImpl.Hidden<>(recreate, instance); } } @@ -94,7 +135,7 @@ public abstract class AbstractInstancer implements Instancer private void addLocked(I instance, InstanceHandleImpl handle) { instances.add(instance); handles.add(handle); - changed.set(handle.index); + setIndexChanged(handle.index); } public int instanceCount() { @@ -105,6 +146,10 @@ public abstract class AbstractInstancer implements Instancer if (index < 0 || index >= instanceCount()) { return; } + setIndexChanged(index); + } + + protected void setIndexChanged(int index) { changed.set(index); } @@ -183,8 +228,9 @@ public abstract class AbstractInstancer implements Instancer // clearing it here would cause significant visual artifacts and instance leaks. // At the same time, we need to clear handles we own to prevent // instances from changing/deleting positions in this instancer that no longer exist. - if (handle.instancer == this) { + if (handle.state == this) { handle.clear(); + handle.state = InstanceHandleImpl.Deleted.instance(); } } instances.clear(); @@ -199,4 +245,10 @@ public abstract class AbstractInstancer implements Instancer public String toString() { return "AbstractInstancer[" + instanceCount() + ']'; } + + public record Recreate(InstancerKey key, DrawManager drawManager) { + public AbstractInstancer recreate() { + return drawManager.getInstancer(key); + } + } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/CommonCrumbling.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/CommonCrumbling.java index de3661703..893cbdeab 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/CommonCrumbling.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/CommonCrumbling.java @@ -5,6 +5,7 @@ import dev.engine_room.flywheel.api.material.Transparency; import dev.engine_room.flywheel.api.material.WriteMask; import dev.engine_room.flywheel.lib.material.CutoutShaders; import dev.engine_room.flywheel.lib.material.FogShaders; +import dev.engine_room.flywheel.lib.material.LightShaders; import dev.engine_room.flywheel.lib.material.SimpleMaterial; public class CommonCrumbling { @@ -12,6 +13,7 @@ public class CommonCrumbling { crumblingMaterial.copyFrom(baseMaterial) .fog(FogShaders.NONE) .cutout(CutoutShaders.ONE_TENTH) + .light(LightShaders.SMOOTH_WHEN_EMBEDDED) .polygonOffset(true) .transparency(Transparency.CRUMBLING) .writeMask(WriteMask.COLOR) 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..20e27976c 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 @@ -115,8 +115,11 @@ public abstract class DrawManager> { continue; } - AbstractInstancer abstractInstancer = impl.instancer; + InstanceHandleImpl.State abstractInstancer = impl.state; + // AbstractInstancer directly implement HandleState, so this check is valid. if (!clazz.isInstance(abstractInstancer)) { + // This rejects instances that were created by a different engine, + // and also instances that are hidden or deleted. continue; } @@ -135,6 +138,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/InstanceHandleImpl.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/InstanceHandleImpl.java index 5641cb96c..d5932f45e 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/InstanceHandleImpl.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/InstanceHandleImpl.java @@ -1,56 +1,93 @@ package dev.engine_room.flywheel.backend.engine; -import java.util.function.Supplier; - -import org.jetbrains.annotations.UnknownNullability; - import dev.engine_room.flywheel.api.instance.Instance; import dev.engine_room.flywheel.api.instance.InstanceHandle; public class InstanceHandleImpl implements InstanceHandle { - @UnknownNullability - public AbstractInstancer instancer; - @UnknownNullability - public I instance; - @UnknownNullability - public Supplier> recreate; - public boolean visible = true; + public State state; public int index; + public InstanceHandleImpl(State state) { + this.state = state; + } + @Override public void setChanged() { - instancer.notifyDirty(index); + state = state.setChanged(index); } @Override public void setDeleted() { - instancer.notifyRemoval(index); + state = state.setDeleted(index); // invalidate ourselves clear(); } @Override public void setVisible(boolean visible) { - if (this.visible == visible) { - return; - } - - this.visible = visible; - - if (visible) { - recreate.get().stealInstance(instance); - } else { - instancer.notifyRemoval(index); - clear(); - } + state = state.setVisible(this, index, visible); } @Override public boolean isVisible() { - return visible; + return state instanceof AbstractInstancer; } public void clear() { index = -1; } + + public interface State { + State setChanged(int index); + + State setDeleted(int index); + + State setVisible(InstanceHandleImpl handle, int index, boolean visible); + } + + public record Hidden(AbstractInstancer.Recreate recreate, I instance) implements State { + @Override + public State setChanged(int index) { + return this; + } + + @Override + public State setDeleted(int index) { + return this; + } + + @Override + public State setVisible(InstanceHandleImpl handle, int index, boolean visible) { + if (!visible) { + return this; + } + var instancer = recreate.recreate(); + instancer.revealInstance(handle, instance); + return instancer; + } + } + + public record Deleted() implements State { + private static final Deleted INSTANCE = new Deleted<>(); + + @SuppressWarnings("unchecked") + public static Deleted instance() { + return (Deleted) INSTANCE; + } + + @Override + public State setChanged(int index) { + return this; + } + + @Override + public State setDeleted(int index) { + return this; + } + + @Override + public State setVisible(InstanceHandleImpl handle, int index, boolean visible) { + return this; + } + } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/MaterialRenderState.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/MaterialRenderState.java index 0ddf8e1e7..43206153f 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/MaterialRenderState.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/MaterialRenderState.java @@ -18,6 +18,12 @@ import net.minecraft.client.renderer.texture.AbstractTexture; public final class MaterialRenderState { public static final Comparator COMPARATOR = Comparator.comparing((Material m) -> m.light() .source()) + .thenComparing(material -> material.cutout() + .source()) + .thenComparing(material -> material.shaders() + .vertexSource()) + .thenComparing(material -> material.shaders() + .fragmentSource()) .thenComparing(Material::texture) .thenComparing(Material::blur) .thenComparing(Material::mipmap) @@ -185,12 +191,21 @@ public final class MaterialRenderState { return true; } - // Not here because ubershader: useLight, useOverlay, diffuse, shaders, fog shader, and cutout shader + // Not here because ubershader: useLight, useOverlay, diffuse, fog 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(); + // @formatter:off + return lhs.blur() == rhs.blur() + && lhs.mipmap() == rhs.mipmap() + && lhs.backfaceCulling() == rhs.backfaceCulling() + && lhs.polygonOffset() == rhs.polygonOffset() + && lhs.depthTest() == rhs.depthTest() + && lhs.transparency() == rhs.transparency() + && lhs.writeMask() == rhs.writeMask() + && lhs.light().source().equals(rhs.light().source()) + && lhs.texture().equals(rhs.texture()) + && lhs.cutout().source().equals(rhs.cutout().source()) + && lhs.shaders().fragmentSource().equals(rhs.shaders().fragmentSource()) + && lhs.shaders().vertexSource().equals(rhs.shaders().vertexSource()); + // @formatter:on } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectBuffers.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectBuffers.java index f8597184b..dc845ea8b 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectBuffers.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectBuffers.java @@ -113,7 +113,7 @@ public class IndirectBuffers { * Bind all buffers except the draw command buffer. */ public void bindForCrumbling() { - multiBind(3, 3); + multiBind(1, 4); } private void multiBind(int base, int count) { diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectCullingGroup.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectCullingGroup.java index c12277860..42511d84a 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectCullingGroup.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectCullingGroup.java @@ -26,7 +26,6 @@ import dev.engine_room.flywheel.backend.engine.MeshPool; 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 { @@ -204,7 +203,7 @@ public class IndirectCullingGroup { int baseDrawUniformLoc = -1; for (var multiDraw : multiDraws.get(visualType)) { - var drawProgram = programs.getIndirectProgram(instanceType, multiDraw.embedded ? ContextShader.EMBEDDED : ContextShader.DEFAULT, multiDraw.material.light()); + var drawProgram = programs.getIndirectProgram(instanceType, multiDraw.embedded ? ContextShader.EMBEDDED : ContextShader.DEFAULT, multiDraw.material.light(), multiDraw.material.cutout(), multiDraw.material.shaders()); if (drawProgram != lastProgram) { lastProgram = drawProgram; @@ -221,8 +220,8 @@ public class IndirectCullingGroup { } } - public void bindWithContextShader(ContextShader override) { - var program = programs.getIndirectProgram(instanceType, override, LightShaders.SMOOTH_WHEN_EMBEDDED); + public void bindWithContextShader(ContextShader override, Material material) { + var program = programs.getIndirectProgram(instanceType, override, material.light(), material.cutout(), material.shaders()); program.bind(); diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDraw.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDraw.java index 48517d1a2..d34087f56 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDraw.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDraw.java @@ -89,7 +89,7 @@ public class IndirectDraw { MemoryUtil.memPutInt(ptr + 4, 1); // instanceCount - only drawing one instance MemoryUtil.memPutInt(ptr + 8, mesh.firstIndex()); // firstIndex MemoryUtil.memPutInt(ptr + 12, mesh.baseVertex()); // baseVertex - MemoryUtil.memPutInt(ptr + 16, instancer.baseInstance() + instanceIndex); // baseInstance + MemoryUtil.memPutInt(ptr + 16, instancer.local2GlobalInstanceIndex(instanceIndex)); // baseInstance MemoryUtil.memPutInt(ptr + 20, instancer.modelIndex()); // modelIndex 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 e19999b5c..23ae02b9d 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 @@ -19,8 +19,10 @@ import dev.engine_room.flywheel.api.visualization.VisualType; import dev.engine_room.flywheel.backend.Samplers; import dev.engine_room.flywheel.backend.compile.ContextShader; import dev.engine_room.flywheel.backend.compile.IndirectPrograms; +import dev.engine_room.flywheel.backend.engine.AbstractInstancer; import dev.engine_room.flywheel.backend.engine.CommonCrumbling; import dev.engine_room.flywheel.backend.engine.DrawManager; +import dev.engine_room.flywheel.backend.engine.GroupKey; import dev.engine_room.flywheel.backend.engine.InstancerKey; import dev.engine_room.flywheel.backend.engine.LightStorage; import dev.engine_room.flywheel.backend.engine.MaterialRenderState; @@ -28,12 +30,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> { @@ -66,7 +68,7 @@ public class IndirectDrawManager extends DrawManager> { @Override protected IndirectInstancer create(InstancerKey key) { - return new IndirectInstancer<>(key, () -> getInstancer(key)); + return new IndirectInstancer<>(key, new AbstractInstancer.Recreate<>(key, this)); } @SuppressWarnings("unchecked") @@ -90,26 +92,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 @@ -193,57 +193,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); - 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(); - // Set up the crumbling program buffers. Nothing changes here between draws. - cullingGroups.get(groupEntry.getKey()) - .bindWithContextShader(ContextShader.CRUMBLING); + GroupKey groupKey = groupEntry.getKey(); + IndirectCullingGroup cullingGroup = cullingGroups.get(groupKey.instanceType()); - 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(); + for (var progressEntry : byProgress.int2ObjectEntrySet()) { + Samplers.CRUMBLING.makeActive(); + TextureBinder.bind(ModelBakery.BREAKING_LOCATIONS.get(progressEntry.getIntKey())); - block.free(); + 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()); + + cullingGroup.bindWithContextShader(ContextShader.CRUMBLING, crumblingMaterial); + + 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/indirect/IndirectInstancer.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectInstancer.java index 26ada98bb..d3ae8999e 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectInstancer.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectInstancer.java @@ -2,7 +2,6 @@ package dev.engine_room.flywheel.backend.engine.indirect; import java.util.ArrayList; import java.util.List; -import java.util.function.Supplier; import org.jetbrains.annotations.UnknownNullability; import org.joml.Vector4fc; @@ -28,7 +27,7 @@ public class IndirectInstancer extends AbstractInstancer private int modelIndex = -1; private int baseInstance = -1; - public IndirectInstancer(InstancerKey key, Supplier> recreate) { + public IndirectInstancer(InstancerKey key, Recreate recreate) { super(key, recreate); instanceStride = MoreMath.align4(type.layout() .byteSize()); @@ -37,10 +36,7 @@ public class IndirectInstancer extends AbstractInstancer } @Override - public void notifyDirty(int index) { - if (index < 0 || index >= instanceCount()) { - return; - } + public void setIndexChanged(int index) { changedPages.set(ObjectStorage.objectIndex2PageIndex(index)); } @@ -147,4 +143,8 @@ public class IndirectInstancer extends AbstractInstancer public int baseInstance() { return baseInstance; } + + public int local2GlobalInstanceIndex(int instanceIndex) { + return mapping.objectIndex2GlobalIndex(instanceIndex); + } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/ObjectStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/ObjectStorage.java index 027af4c6c..56d39613e 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/ObjectStorage.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/ObjectStorage.java @@ -216,5 +216,9 @@ public class ObjectStorage extends AbstractArena { pages = Arrays.copyOf(pages, neededPages); } + + public int objectIndex2GlobalIndex(int objectIndex) { + return (pages[objectIndex2PageIndex(objectIndex)] << LOG_2_PAGE_SIZE) + (objectIndex & PAGE_MASK); + } } } 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..eab6c811c 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 @@ -14,6 +14,7 @@ import dev.engine_room.flywheel.backend.MaterialShaderIndices; import dev.engine_room.flywheel.backend.Samplers; import dev.engine_room.flywheel.backend.compile.ContextShader; import dev.engine_room.flywheel.backend.compile.InstancingPrograms; +import dev.engine_room.flywheel.backend.engine.AbstractInstancer; import dev.engine_room.flywheel.backend.engine.CommonCrumbling; import dev.engine_room.flywheel.backend.engine.DrawManager; import dev.engine_room.flywheel.backend.engine.GroupKey; @@ -25,12 +26,11 @@ 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 @@ -128,7 +126,7 @@ public class InstancedDrawManager extends DrawManager> { @Override protected InstancedInstancer create(InstancerKey key) { - return new InstancedInstancer<>(key, () -> getInstancer(key)); + return new InstancedInstancer<>(key, new AbstractInstancer.Recreate<>(key, this)); } @Override @@ -162,46 +160,48 @@ 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(); + 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; + for (InstancedDraw draw : instancer.draws()) { + CommonCrumbling.applyCrumblingProperties(crumblingMaterial, draw.material()); + var program = programs.get(shader.instanceType(), ContextShader.CRUMBLING, crumblingMaterial.light(), crumblingMaterial.cutout(), crumblingMaterial.shaders()); + program.bind(); program.setInt("_flw_baseInstance", index); + 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/engine/instancing/InstancedInstancer.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedInstancer.java index d72117e7d..1f1de8f9d 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedInstancer.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedInstancer.java @@ -2,7 +2,6 @@ package dev.engine_room.flywheel.backend.engine.instancing; import java.util.ArrayList; import java.util.List; -import java.util.function.Supplier; import org.jetbrains.annotations.Nullable; @@ -25,7 +24,7 @@ public class InstancedInstancer extends AbstractInstancer private final List draws = new ArrayList<>(); - public InstancedInstancer(InstancerKey key, Supplier> recreate) { + public InstancedInstancer(InstancerKey key, Recreate recreate) { super(key, recreate); var layout = type.layout(); // Align to one texel in the texture buffer diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedRenderStage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedRenderStage.java index 73b1fdddf..572d9c573 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedRenderStage.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedRenderStage.java @@ -55,14 +55,13 @@ public class InstancedRenderStage { var environment = shader.environment(); for (var drawCall : drawCalls.draws) { - var program = programs.get(shader.instanceType(), environment.contextShader(), drawCall.material() - .light()); + var material = drawCall.material(); + + var program = programs.get(shader.instanceType(), environment.contextShader(), material.light(), material.cutout(), material.shaders()); program.bind(); environment.setupDraw(program); - var material = drawCall.material(); - uploadMaterialUniform(program, material); MaterialRenderState.setup(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/backend/resources/assets/flywheel/flywheel/internal/indirect/cull.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/cull.glsl index 38724599f..395979d5c 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/cull.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/cull.glsl @@ -100,6 +100,10 @@ bool _flw_isVisible(uint instanceIndex, uint modelIndex) { ivec4 bounds = ivec4(aabb * vec4(levelSizePair)); + // Clamp to the texture bounds. + // Since we're not going through a sampler out of bounds texel fetches will return 0. + bounds = clamp(bounds, ivec4(0), levelSizePair); + float depth01 = texelFetch(_flw_depthPyramid, bounds.xw, level).r; float depth11 = texelFetch(_flw_depthPyramid, bounds.zw, level).r; float depth10 = texelFetch(_flw_depthPyramid, bounds.zy, level).r; diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.vert b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.vert index d94725fcb..15583adee 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.vert +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.vert @@ -23,12 +23,16 @@ uniform uint _flw_baseDraw; flat out uvec3 _flw_packedMaterial; -void main() { #if __VERSION__ < 460 - uint drawIndex = gl_DrawIDARB + _flw_baseDraw; +#define flw_baseInstance gl_BaseInstanceARB +#define flw_drawId gl_DrawIDARB #else - uint drawIndex = gl_DrawID + _flw_baseDraw; +#define flw_baseInstance gl_BaseInstance +#define flw_drawId gl_DrawID #endif + +void main() { + uint drawIndex = flw_drawId + _flw_baseDraw; MeshDrawCommand draw = _flw_drawCommands[drawIndex]; _flw_uberMaterialVertexIndex = draw.materialVertexIndex; @@ -42,11 +46,12 @@ void main() { // _flw_normalMatrix = mat3(1.); #endif - #if __VERSION__ < 460 - uint instanceIndex = _flw_instanceIndices[gl_BaseInstanceARB + gl_InstanceID]; -#else - uint instanceIndex = _flw_instanceIndices[gl_BaseInstance + gl_InstanceID]; -#endif + #ifdef _FLW_CRUMBLING + uint instanceIndex = flw_baseInstance; + #else + uint instanceIndex = _flw_instanceIndices[flw_baseInstance + gl_InstanceID]; + #endif + FlwInstance instance = _flw_unpackInstance(instanceIndex); _flw_main(instance, instanceIndex); diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/AbstractInstance.java b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/AbstractInstance.java index b28dbffdf..80f5442d3 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/AbstractInstance.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/AbstractInstance.java @@ -34,4 +34,10 @@ public abstract class AbstractInstance implements Instance { // Override to mark final. handle.setDeleted(); } + + @Override + public final void setVisible(boolean visible) { + // Override to mark final. + handle.setVisible(visible); + } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java index 74700e480..8bab28975 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java @@ -41,6 +41,19 @@ public class TransformedInstance extends ColoredLitInstance implements Affine extends ComponentEntityV private final Matrix4fStack stack = new Matrix4fStack(2); private BlockState blockState; - private boolean active; public MinecartVisual(VisualizationContext ctx, T entity, float partialTick, ModelLayerLocation layerLocation) { super(ctx, entity, partialTick); @@ -65,11 +64,9 @@ public class MinecartVisual extends ComponentEntityV RenderShape shape = blockState.getRenderShape(); if (shape == RenderShape.ENTITYBLOCK_ANIMATED) { - instances.traverse(instance -> instance.setZeroTransform().setChanged()); - active = false; + instances.visible(false); return null; } - active = true; if (shape == RenderShape.INVISIBLE) { return null; @@ -100,8 +97,7 @@ public class MinecartVisual extends ComponentEntityV return; } - // TODO: add proper way to temporarily disable rendering a specific instance - if (!active) { + if (!instances.visible()) { return; } 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());