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 f0381eecc..3f2a4cdb3 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 @@ -15,7 +15,6 @@ import dev.engine_room.flywheel.api.task.Plan; import dev.engine_room.flywheel.api.visualization.VisualEmbedding; import dev.engine_room.flywheel.api.visualization.VisualizationContext; import dev.engine_room.flywheel.backend.FlwBackend; -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; @@ -96,7 +95,7 @@ public class EngineImpl implements Engine { Uniforms.update(context); environmentStorage.flush(); drawManager.render(lightStorage, environmentStorage); - } catch (ShaderException e) { + } catch (Exception e) { FlwBackend.LOGGER.error("Falling back", e); triggerFallback(); } @@ -106,7 +105,7 @@ public class EngineImpl implements Engine { public void renderCrumbling(RenderContext context, List crumblingBlocks) { try (var state = GlStateTracker.getRestoreState()) { drawManager.renderCrumbling(crumblingBlocks); - } catch (ShaderException e) { + } catch (Exception e) { FlwBackend.LOGGER.error("Falling back", e); triggerFallback(); } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/VisualManagerImpl.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/VisualManagerImpl.java index f65e081c1..ac777dc18 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/VisualManagerImpl.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/VisualManagerImpl.java @@ -7,6 +7,7 @@ import dev.engine_room.flywheel.api.task.Plan; import dev.engine_room.flywheel.api.visual.DynamicVisual; import dev.engine_room.flywheel.api.visual.TickableVisual; import dev.engine_room.flywheel.api.visualization.VisualManager; +import dev.engine_room.flywheel.api.visualization.VisualizationContext; import dev.engine_room.flywheel.impl.visualization.storage.Storage; import dev.engine_room.flywheel.impl.visualization.storage.Transaction; import dev.engine_room.flywheel.lib.task.SimplePlan; @@ -54,21 +55,25 @@ public class VisualManagerImpl> implements VisualManager queue.add(Transaction.update(obj)); } - public void processQueue(float partialTick) { + public void processQueue(VisualizationContext visualizationContext, float partialTick) { var storage = getStorage(); Transaction transaction; while ((transaction = queue.poll()) != null) { - transaction.apply(storage, partialTick); + switch (transaction.action()) { + case ADD -> storage.add(visualizationContext, transaction.obj(), partialTick); + case REMOVE -> storage.remove(transaction.obj()); + case UPDATE -> storage.update(transaction.obj(), partialTick); + } } } - public Plan framePlan() { - return SimplePlan.of(context -> processQueue(context.partialTick())) + public Plan framePlan(VisualizationContext visualizationContext) { + return SimplePlan.of(context -> processQueue(visualizationContext, context.partialTick())) .then(storage.framePlan()); } - public Plan tickPlan() { - return SimplePlan.of(context -> processQueue(1)) + public Plan tickPlan(VisualizationContext visualizationContext) { + return SimplePlan.of(context -> processQueue(visualizationContext, 1)) .then(storage.tickPlan()); } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/VisualizationManagerImpl.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/VisualizationManagerImpl.java index e5b39b1d6..4f88ef870 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/VisualizationManagerImpl.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/VisualizationManagerImpl.java @@ -59,9 +59,15 @@ public class VisualizationManagerImpl implements VisualizationManager { private static final LevelAttached MANAGERS = new LevelAttached<>(VisualizationManagerImpl::new, VisualizationManagerImpl::delete); private final TaskExecutorImpl taskExecutor; - private final Engine engine; private final DistanceUpdateLimiterImpl frameLimiter; private final RenderDispatcherImpl renderDispatcher = new RenderDispatcherImpl(); + private final LevelAccessor level; + + // VisualizationManagerImpl can (and should!) be constructed off of the main thread, but it may be + // difficult for engines to avoid OpenGL calls which would not be safe. Shove all the init logic + // that depends on engine construction into here, and defer until we get invoked on the main thread. + @Nullable + private LateInit lateInit; private final VisualManagerImpl blockEntities; private final VisualManagerImpl entities; @@ -70,49 +76,14 @@ public class VisualizationManagerImpl implements VisualizationManager { private final Flag frameFlag = new Flag("frame"); private final Flag tickFlag = new Flag("tick"); - private final Plan framePlan; - private final Plan tickPlan; - private VisualizationManagerImpl(LevelAccessor level) { + this.level = level; taskExecutor = FlwTaskExecutor.get(); - engine = BackendManager.currentBackend() - .createEngine(level); frameLimiter = createUpdateLimiter(); - var visualizationContext = engine.createVisualizationContext(); - var blockEntitiesStorage = new BlockEntityStorage(visualizationContext); - var entitiesStorage = new EntityStorage(visualizationContext); - var effectsStorage = new EffectStorage(visualizationContext); - - blockEntities = new VisualManagerImpl<>(blockEntitiesStorage); - entities = new VisualManagerImpl<>(entitiesStorage); - effects = new VisualManagerImpl<>(effectsStorage); - - var recreate = SimplePlan.of(context -> blockEntitiesStorage.recreateAll(context.partialTick()), - context -> entitiesStorage.recreateAll(context.partialTick()), - context -> effectsStorage.recreateAll(context.partialTick())); - - var update = MapContextPlan.map(this::createVisualFrameContext) - .to(NestedPlan.of(blockEntities.framePlan(), entities.framePlan(), effects.framePlan())); - - framePlan = IfElsePlan.on((RenderContext ctx) -> engine.updateRenderOrigin(ctx.camera())) - .ifTrue(recreate) - .ifFalse(update) - .plan() - .then(SimplePlan.of(() -> { - if (blockEntities.areGpuLightSectionsDirty() || entities.areGpuLightSectionsDirty() || effects.areGpuLightSectionsDirty()) { - var out = new LongOpenHashSet(); - out.addAll(blockEntities.gpuLightSections()); - out.addAll(entities.gpuLightSections()); - out.addAll(effects.gpuLightSections()); - engine.lightSections(out); - } - })) - .then(engine.createFramePlan()) - .then(RaisePlan.raise(frameFlag)); - - tickPlan = NestedPlan.of(blockEntities.tickPlan(), entities.tickPlan(), effects.tickPlan()) - .then(RaisePlan.raise(tickFlag)); + blockEntities = new VisualManagerImpl<>(new BlockEntityStorage()); + entities = new VisualManagerImpl<>(new EntityStorage()); + effects = new VisualManagerImpl<>(new EffectStorage()); if (level instanceof Level l) { LevelExtension.getAllLoadedEntities(l) @@ -120,16 +91,65 @@ public class VisualizationManagerImpl implements VisualizationManager { } } - private DynamicVisual.Context createVisualFrameContext(RenderContext ctx) { - Vec3i renderOrigin = engine.renderOrigin(); - var cameraPos = ctx.camera() - .getPosition(); + private class LateInit { + private final Engine engine; - Matrix4f viewProjection = new Matrix4f(ctx.viewProjection()); - viewProjection.translate((float) (renderOrigin.getX() - cameraPos.x), (float) (renderOrigin.getY() - cameraPos.y), (float) (renderOrigin.getZ() - cameraPos.z)); - FrustumIntersection frustum = new FrustumIntersection(viewProjection); + private final Plan framePlan; + private final Plan tickPlan; - return new DynamicVisualContextImpl(ctx.camera(), frustum, ctx.partialTick(), frameLimiter); + private LateInit(LevelAccessor level) { + engine = BackendManager.currentBackend() + .createEngine(level); + + var visualizationContext = engine.createVisualizationContext(); + + var recreate = SimplePlan.of(context -> blockEntities.getStorage() + .recreateAll(visualizationContext, context.partialTick()), context -> entities.getStorage() + .recreateAll(visualizationContext, context.partialTick()), context -> effects.getStorage() + .recreateAll(visualizationContext, context.partialTick())); + + var update = MapContextPlan.map(this::createVisualFrameContext) + .to(NestedPlan.of(blockEntities.framePlan(visualizationContext), entities.framePlan(visualizationContext), effects.framePlan(visualizationContext))); + + framePlan = IfElsePlan.on((RenderContext ctx) -> engine.updateRenderOrigin(ctx.camera())) + .ifTrue(recreate) + .ifFalse(update) + .plan() + .then(SimplePlan.of(() -> { + if (blockEntities.areGpuLightSectionsDirty() || entities.areGpuLightSectionsDirty() || effects.areGpuLightSectionsDirty()) { + var out = new LongOpenHashSet(); + out.addAll(blockEntities.gpuLightSections()); + out.addAll(entities.gpuLightSections()); + out.addAll(effects.gpuLightSections()); + engine.lightSections(out); + } + })) + .then(engine.createFramePlan()) + .then(RaisePlan.raise(frameFlag)); + + tickPlan = NestedPlan.of(blockEntities.tickPlan(visualizationContext), entities.tickPlan(visualizationContext), effects.tickPlan(visualizationContext)) + .then(RaisePlan.raise(tickFlag)); + } + + private DynamicVisual.Context createVisualFrameContext(RenderContext ctx) { + Vec3i renderOrigin = engine.renderOrigin(); + var cameraPos = ctx.camera() + .getPosition(); + + Matrix4f viewProjection = new Matrix4f(ctx.viewProjection()); + viewProjection.translate((float) (renderOrigin.getX() - cameraPos.x), (float) (renderOrigin.getY() - cameraPos.y), (float) (renderOrigin.getZ() - cameraPos.z)); + FrustumIntersection frustum = new FrustumIntersection(viewProjection); + + return new DynamicVisualContextImpl(ctx.camera(), frustum, ctx.partialTick(), frameLimiter); + } + } + + private LateInit lateInit() { + if (lateInit == null) { + lateInit = new LateInit(level); + } + + return lateInit; } private DistanceUpdateLimiterImpl createUpdateLimiter() { @@ -191,7 +211,11 @@ public class VisualizationManagerImpl implements VisualizationManager { @Override public Vec3i renderOrigin() { - return engine.renderOrigin(); + if (lateInit == null) { + return Vec3i.ZERO; + } else { + return lateInit.engine.renderOrigin(); + } } @Override @@ -225,7 +249,7 @@ public class VisualizationManagerImpl implements VisualizationManager { taskExecutor.syncUntil(tickFlag::isRaised); tickFlag.lower(); - tickPlan.execute(taskExecutor, TickableVisualContextImpl.INSTANCE); + lateInit().tickPlan.execute(taskExecutor, TickableVisualContextImpl.INSTANCE); } /** @@ -240,7 +264,7 @@ public class VisualizationManagerImpl implements VisualizationManager { frameLimiter.tick(); - framePlan.execute(taskExecutor, context); + lateInit().framePlan.execute(taskExecutor, context); } /** @@ -248,7 +272,7 @@ public class VisualizationManagerImpl implements VisualizationManager { */ private void render(RenderContext context) { taskExecutor.syncUntil(frameFlag::isRaised); - engine.render(context); + lateInit().engine.render(context); } private void renderCrumbling(RenderContext context, Long2ObjectMap> destructionProgress) { @@ -292,12 +316,12 @@ public class VisualizationManagerImpl implements VisualizationManager { } if (!crumblingBlocks.isEmpty()) { - engine.renderCrumbling(context, crumblingBlocks); + lateInit().engine.renderCrumbling(context, crumblingBlocks); } } public void onLightUpdate(SectionPos sectionPos, LightLayer layer) { - engine.onLightUpdate(sectionPos, layer); + lateInit().engine.onLightUpdate(sectionPos, layer); long longPos = sectionPos.asLong(); blockEntities.onLightUpdate(longPos); entities.onLightUpdate(longPos); @@ -315,7 +339,9 @@ public class VisualizationManagerImpl implements VisualizationManager { blockEntities.invalidate(); entities.invalidate(); effects.invalidate(); - engine.delete(); + if (lateInit != null) { + lateInit.engine.delete(); + } } private class RenderDispatcherImpl implements RenderDispatcher { @@ -335,6 +361,7 @@ public class VisualizationManagerImpl implements VisualizationManager { } } - private record CrumblingBlockImpl(BlockPos pos, int progress, List instances) implements Engine.CrumblingBlock { + private record CrumblingBlockImpl(BlockPos pos, int progress, + List instances) implements dev.engine_room.flywheel.api.backend.Engine.CrumblingBlock { } } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/BlockEntityStorage.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/BlockEntityStorage.java index fd8d4980f..86af9070b 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/BlockEntityStorage.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/BlockEntityStorage.java @@ -15,10 +15,6 @@ import net.minecraft.world.level.block.entity.BlockEntity; public class BlockEntityStorage extends Storage { private final Long2ObjectMap> posLookup = new Long2ObjectOpenHashMap<>(); - public BlockEntityStorage(VisualizationContext visualizationContext) { - super(visualizationContext); - } - @Nullable public BlockEntityVisual visualAtPos(long pos) { return posLookup.get(pos); @@ -50,7 +46,7 @@ public class BlockEntityStorage extends Storage { @Override @Nullable - protected BlockEntityVisual createRaw(BlockEntity obj, float partialTick) { + protected BlockEntityVisual createRaw(VisualizationContext visualizationContext, BlockEntity obj, float partialTick) { var visualizer = VisualizationHelper.getVisualizer(obj); if (visualizer == null) { return null; @@ -72,9 +68,9 @@ public class BlockEntityStorage extends Storage { } @Override - public void recreateAll(float partialTick) { + public void recreateAll(VisualizationContext visualizationContext, float partialTick) { posLookup.clear(); - super.recreateAll(partialTick); + super.recreateAll(visualizationContext, partialTick); } @Override diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/EffectStorage.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/EffectStorage.java index 6a8a3c1fd..87e78c2ed 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/EffectStorage.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/EffectStorage.java @@ -5,12 +5,8 @@ import dev.engine_room.flywheel.api.visual.EffectVisual; import dev.engine_room.flywheel.api.visualization.VisualizationContext; public class EffectStorage extends Storage { - public EffectStorage(VisualizationContext visualizationContext) { - super(visualizationContext); - } - @Override - protected EffectVisual createRaw(Effect obj, float partialTick) { + protected EffectVisual createRaw(VisualizationContext visualizationContext, Effect obj, float partialTick) { return obj.visualize(visualizationContext, partialTick); } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/EntityStorage.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/EntityStorage.java index 56d9446bb..0fe0197fd 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/EntityStorage.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/EntityStorage.java @@ -7,18 +7,14 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.level.Level; public class EntityStorage extends Storage { - public EntityStorage(VisualizationContext visualizationContext) { - super(visualizationContext); - } - @Override - protected EntityVisual createRaw(Entity obj, float partialTick) { + protected EntityVisual createRaw(VisualizationContext context, Entity obj, float partialTick) { var visualizer = VisualizationHelper.getVisualizer(obj); if (visualizer == null) { return null; } - return visualizer.createVisual(visualizationContext, obj, partialTick); + return visualizer.createVisual(context, obj, partialTick); } @Override diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java index 0bef0fd10..8bc84204b 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java @@ -25,8 +25,6 @@ import dev.engine_room.flywheel.lib.visual.SimpleTickableVisual; import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; public abstract class Storage { - protected final VisualizationContext visualizationContext; - private final Map visuals = new Reference2ObjectOpenHashMap<>(); protected final PlanMap dynamicVisuals = new PlanMap<>(); protected final PlanMap tickableVisuals = new PlanMap<>(); @@ -35,10 +33,6 @@ public abstract class Storage { protected final LightUpdatedVisualStorage lightUpdatedVisuals = new LightUpdatedVisualStorage(); protected final ShaderLightVisualStorage shaderLightVisuals = new ShaderLightVisualStorage(); - public Storage(VisualizationContext visualizationContext) { - this.visualizationContext = visualizationContext; - } - public Collection getAllVisuals() { return visuals.values(); } @@ -71,11 +65,16 @@ public abstract class Storage { */ public abstract boolean willAccept(T obj); - public void add(T obj, float partialTick) { + public void add(VisualizationContext visualizationContext, T obj, float partialTick) { Visual visual = visuals.get(obj); if (visual == null) { - create(obj, partialTick); + visual = createRaw(visualizationContext, obj, partialTick); + + if (visual != null) { + setup(visual, partialTick); + visuals.put(obj, visual); + } } } @@ -120,7 +119,7 @@ public abstract class Storage { visual.update(partialTick); } - public void recreateAll(float partialTick) { + public void recreateAll(VisualizationContext visualizationContext, float partialTick) { dynamicVisuals.clear(); tickableVisuals.clear(); simpleDynamicVisuals.clear(); @@ -131,7 +130,7 @@ public abstract class Storage { visuals.replaceAll((obj, visual) -> { visual.delete(); - var out = createRaw(obj, partialTick); + var out = createRaw(visualizationContext, obj, partialTick); if (out != null) { setup(out, partialTick); @@ -141,17 +140,8 @@ public abstract class Storage { }); } - private void create(T obj, float partialTick) { - var visual = createRaw(obj, partialTick); - - if (visual != null) { - setup(visual, partialTick); - visuals.put(obj, visual); - } - } - @Nullable - protected abstract Visual createRaw(T obj, float partialTick); + protected abstract Visual createRaw(VisualizationContext visualizationContext, T obj, float partialTick); private void setup(Visual visual, float partialTick) { if (visual instanceof DynamicVisual dynamic) { diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Transaction.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Transaction.java index 26dbd6c86..a2a5fde7c 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Transaction.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Transaction.java @@ -12,12 +12,4 @@ public record Transaction(T obj, Action action) { public static Transaction update(T obj) { return new Transaction<>(obj, Action.UPDATE); } - - public void apply(Storage storage, float partialTick) { - switch (action) { - case ADD -> storage.add(obj, partialTick); - case REMOVE -> storage.remove(obj); - case UPDATE -> storage.update(obj, partialTick); - } - } }