From 0b25f662dc0f62049188f706543a56bb0f75d5c2 Mon Sep 17 00:00:00 2001 From: JozsefA Date: Fri, 26 Mar 2021 16:47:37 -0700 Subject: [PATCH] Frame rate and tick rate limiting with distance. - Significant performance improvement when dealing with massive amounts of dynamic instances, otherwise marginal. --- .../components/deployer/DeployerInstance.java | 2 + .../render/ContraptionKineticRenderer.java | 7 +- .../render/ContraptionRenderDispatcher.java | 15 +--- .../render/RenderedContraption.java | 11 +-- .../foundation/mixin/RenderHooksMixin.java | 5 +- .../foundation/render/KineticRenderer.java | 5 +- .../render/backend/FastRenderDispatcher.java | 8 +- .../backend/instancing/IDynamicInstance.java | 15 +++- .../render/backend/instancing/IInstance.java | 13 ++++ .../backend/instancing/ITickableInstance.java | 15 +++- .../instancing/InstancedTileRenderer.java | 77 ++++++++++++++++--- .../instancing/TileEntityInstance.java | 11 ++- 12 files changed, 141 insertions(+), 43 deletions(-) create mode 100644 src/main/java/com/simibubi/create/foundation/render/backend/instancing/IInstance.java diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/deployer/DeployerInstance.java b/src/main/java/com/simibubi/create/content/contraptions/components/deployer/DeployerInstance.java index f05361148..bb6b8f6bb 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/deployer/DeployerInstance.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/deployer/DeployerInstance.java @@ -51,6 +51,8 @@ public class DeployerInstance extends ShaftInstance implements IDynamicInstance, relight(pos, pole.getInstance()); updateRotation(pole, hand, yRot, zRot, zRotPole); + + beginFrame(); } @Override diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionKineticRenderer.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionKineticRenderer.java index 20d0bc6a5..0fcb9e109 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionKineticRenderer.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionKineticRenderer.java @@ -15,6 +15,8 @@ import com.simibubi.create.foundation.render.backend.instancing.*; import com.simibubi.create.foundation.render.backend.instancing.impl.OrientedModel; import com.simibubi.create.foundation.render.backend.instancing.impl.TransformedModel; + +import net.minecraft.client.renderer.ActiveRenderInfo; import net.minecraft.util.math.BlockPos; import net.minecraft.world.gen.feature.template.Template; import org.apache.commons.lang3.tuple.Pair; @@ -44,14 +46,13 @@ public class ContraptionKineticRenderer extends InstancedTileRenderer(this, AllProgramSpecs.C_ACTOR, ActorModel::new)); } - @Override public void tick() { actors.forEach(ActorInstance::tick); } @Override - public void beginFrame(double cameraX, double cameraY, double cameraZ) { - super.beginFrame(cameraX, cameraY, cameraZ); + public void beginFrame(ActiveRenderInfo info, double cameraX, double cameraY, double cameraZ) { + super.beginFrame(info, cameraX, cameraY, cameraZ); actors.forEach(ActorInstance::beginFrame); } diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionRenderDispatcher.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionRenderDispatcher.java index bed4c8913..64497761d 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionRenderDispatcher.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionRenderDispatcher.java @@ -28,20 +28,11 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import net.minecraft.block.BlockRenderType; import net.minecraft.block.BlockState; import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.BlockModelRenderer; -import net.minecraft.client.renderer.BlockRendererDispatcher; -import net.minecraft.client.renderer.BufferBuilder; -import net.minecraft.client.renderer.IRenderTypeBuffer; -import net.minecraft.client.renderer.LightTexture; -import net.minecraft.client.renderer.Matrix4f; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.client.renderer.RenderTypeLookup; -import net.minecraft.client.renderer.WorldRenderer; +import net.minecraft.client.renderer.*; import net.minecraft.client.renderer.model.IBakedModel; import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.SectionPos; import net.minecraft.world.ILightReader; import net.minecraft.world.LightType; import net.minecraft.world.World; @@ -94,9 +85,9 @@ public class ContraptionRenderDispatcher { return contraption; } - public static void beginFrame(double camX, double camY, double camZ) { + public static void beginFrame(ActiveRenderInfo info, double camX, double camY, double camZ) { for (RenderedContraption renderer : renderers.values()) { - renderer.beginFrame(camX, camY, camZ); + renderer.beginFrame(info, camX, camY, camZ); } } diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/RenderedContraption.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/RenderedContraption.java index 66e49a9bc..6f23f4a53 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/RenderedContraption.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/RenderedContraption.java @@ -20,12 +20,7 @@ import com.simibubi.create.foundation.utility.worldWrappers.PlacementSimulationW import net.minecraft.block.BlockRenderType; import net.minecraft.block.BlockState; import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.BlockModelRenderer; -import net.minecraft.client.renderer.BlockRendererDispatcher; -import net.minecraft.client.renderer.BufferBuilder; -import net.minecraft.client.renderer.Matrix4f; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.client.renderer.RenderTypeLookup; +import net.minecraft.client.renderer.*; import net.minecraft.client.renderer.model.IBakedModel; import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; @@ -87,8 +82,8 @@ public class RenderedContraption { } } - public void beginFrame(double camX, double camY, double camZ) { - kinetics.beginFrame(camX, camY, camZ); + public void beginFrame(ActiveRenderInfo info, double camX, double camY, double camZ) { + kinetics.beginFrame(info, camX, camY, camZ); AbstractContraptionEntity entity = contraption.entity; float pt = AnimationTickHolder.getPartialTicks(); diff --git a/src/main/java/com/simibubi/create/foundation/mixin/RenderHooksMixin.java b/src/main/java/com/simibubi/create/foundation/mixin/RenderHooksMixin.java index 0d6e73641..e6a6f30ce 100644 --- a/src/main/java/com/simibubi/create/foundation/mixin/RenderHooksMixin.java +++ b/src/main/java/com/simibubi/create/foundation/mixin/RenderHooksMixin.java @@ -4,7 +4,6 @@ import com.simibubi.create.foundation.render.KineticRenderer; import net.minecraft.block.BlockState; import net.minecraft.client.renderer.*; -import net.minecraft.client.renderer.texture.AtlasTexture; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import org.lwjgl.opengl.GL20; @@ -57,8 +56,8 @@ public class RenderHooksMixin { double camY = cameraPos.getY(); double camZ = cameraPos.getZ(); - CreateClient.kineticRenderer.get(world).beginFrame(camX, camY, camZ); - ContraptionRenderDispatcher.beginFrame(camX, camY, camZ); + CreateClient.kineticRenderer.get(world).beginFrame(info, camX, camY, camZ); + ContraptionRenderDispatcher.beginFrame(info, camX, camY, camZ); } @Inject(at = @At("TAIL"), method = "checkBlockRerender") diff --git a/src/main/java/com/simibubi/create/foundation/render/KineticRenderer.java b/src/main/java/com/simibubi/create/foundation/render/KineticRenderer.java index e02db4d54..250bc904b 100644 --- a/src/main/java/com/simibubi/create/foundation/render/KineticRenderer.java +++ b/src/main/java/com/simibubi/create/foundation/render/KineticRenderer.java @@ -15,6 +15,7 @@ import com.simibubi.create.foundation.render.backend.instancing.RenderMaterial; import com.simibubi.create.foundation.render.backend.instancing.impl.OrientedModel; import com.simibubi.create.foundation.render.backend.instancing.impl.TransformedModel; +import net.minecraft.client.renderer.ActiveRenderInfo; import net.minecraft.client.renderer.Matrix4f; import net.minecraft.client.renderer.RenderType; import net.minecraft.tileentity.TileEntity; @@ -42,7 +43,7 @@ public class KineticRenderer extends InstancedTileRenderer { } @Override - public void beginFrame(double cameraX, double cameraY, double cameraZ) { + public void beginFrame(ActiveRenderInfo info, double cameraX, double cameraY, double cameraZ) { int cX = MathHelper.floor(cameraX); int cY = MathHelper.floor(cameraY); int cZ = MathHelper.floor(cameraZ); @@ -62,7 +63,7 @@ public class KineticRenderer extends InstancedTileRenderer { instancedTiles.forEach(this::add); } - super.beginFrame(cameraX, cameraY, cameraZ); + super.beginFrame(info, cameraX, cameraY, cameraZ); } @Override diff --git a/src/main/java/com/simibubi/create/foundation/render/backend/FastRenderDispatcher.java b/src/main/java/com/simibubi/create/foundation/render/backend/FastRenderDispatcher.java index 08e31dcc1..f118eeeef 100644 --- a/src/main/java/com/simibubi/create/foundation/render/backend/FastRenderDispatcher.java +++ b/src/main/java/com/simibubi/create/foundation/render/backend/FastRenderDispatcher.java @@ -17,6 +17,7 @@ import net.minecraft.client.renderer.Matrix4f; import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.Vector3f; import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.Entity; import net.minecraft.potion.Effects; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.math.MathHelper; @@ -37,10 +38,13 @@ public class FastRenderDispatcher { } public static void tick() { - ClientWorld world = Minecraft.getInstance().world; + Minecraft mc = Minecraft.getInstance(); + ClientWorld world = mc.world; KineticRenderer kineticRenderer = CreateClient.kineticRenderer.get(world); - kineticRenderer.tick(); + + Entity renderViewEntity = mc.renderViewEntity; + kineticRenderer.tick(renderViewEntity.getX(), renderViewEntity.getY(), renderViewEntity.getZ()); ConcurrentHashMap.KeySetView map = queuedUpdates.get(world); map diff --git a/src/main/java/com/simibubi/create/foundation/render/backend/instancing/IDynamicInstance.java b/src/main/java/com/simibubi/create/foundation/render/backend/instancing/IDynamicInstance.java index 90c26e0a8..8ee257a3c 100644 --- a/src/main/java/com/simibubi/create/foundation/render/backend/instancing/IDynamicInstance.java +++ b/src/main/java/com/simibubi/create/foundation/render/backend/instancing/IDynamicInstance.java @@ -9,9 +9,22 @@ package com.simibubi.create.foundation.render.backend.instancing; *

If your goal is offloading work to shaders, but you're unsure exactly how you need * to parameterize the instances, you're encouraged to implement this for prototyping. */ -public interface IDynamicInstance { +public interface IDynamicInstance extends IInstance { /** * Called every frame. */ void beginFrame(); + + /** + * As a further optimization, dynamic instances that are far away are ticked less often. + * This behavior can be disabled by returning false. + * + *
You might want to opt out of this if you want your animations to remain smooth + * even when far away from the camera. It is recommended to keep this as is, however. + * + * @return true if your instance should be slow ticked. + */ + default boolean decreaseFramerateWithDistance() { + return true; + } } diff --git a/src/main/java/com/simibubi/create/foundation/render/backend/instancing/IInstance.java b/src/main/java/com/simibubi/create/foundation/render/backend/instancing/IInstance.java new file mode 100644 index 000000000..88ec0eb22 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/render/backend/instancing/IInstance.java @@ -0,0 +1,13 @@ +package com.simibubi.create.foundation.render.backend.instancing; + +import net.minecraft.util.math.BlockPos; + +/** + * A general interface providing information about any type of thing that could use + * Flywheel's instanced rendering. Right now, that's only {@link InstancedTileRenderer}, + * but there could be an entity equivalent in the future. + */ +public interface IInstance { + + BlockPos getWorldPosition(); +} diff --git a/src/main/java/com/simibubi/create/foundation/render/backend/instancing/ITickableInstance.java b/src/main/java/com/simibubi/create/foundation/render/backend/instancing/ITickableInstance.java index 196ef9360..c9280973b 100644 --- a/src/main/java/com/simibubi/create/foundation/render/backend/instancing/ITickableInstance.java +++ b/src/main/java/com/simibubi/create/foundation/render/backend/instancing/ITickableInstance.java @@ -16,10 +16,23 @@ package com.simibubi.create.foundation.render.backend.instancing; * * */ -public interface ITickableInstance { +public interface ITickableInstance extends IInstance { /** * Called every tick. */ void tick(); + + /** + * As a further optimization, tickable instances that are far away are ticked less often. + * This behavior can be disabled by returning false. + * + *
You might want to opt out of this if you want your animations to remain smooth + * even when far away from the camera. It is recommended to keep this as is, however. + * + * @return true if your instance should be slow ticked. + */ + default boolean decreaseTickRateWithDistance() { + return true; + } } diff --git a/src/main/java/com/simibubi/create/foundation/render/backend/instancing/InstancedTileRenderer.java b/src/main/java/com/simibubi/create/foundation/render/backend/instancing/InstancedTileRenderer.java index ba4592852..80574a142 100644 --- a/src/main/java/com/simibubi/create/foundation/render/backend/instancing/InstancedTileRenderer.java +++ b/src/main/java/com/simibubi/create/foundation/render/backend/instancing/InstancedTileRenderer.java @@ -10,11 +10,9 @@ import com.simibubi.create.foundation.render.backend.gl.BasicProgram; import com.simibubi.create.foundation.render.backend.gl.shader.ShaderCallback; import com.simibubi.create.foundation.render.backend.instancing.impl.ModelData; import com.simibubi.create.foundation.render.backend.instancing.impl.OrientedData; -import com.simibubi.create.foundation.utility.AnimationTickHolder; import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.Matrix4f; -import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.*; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.math.BlockPos; import net.minecraft.world.IBlockReader; @@ -30,6 +28,8 @@ public abstract class InstancedTileRenderer

{ protected Map, RenderMaterial> materials = new HashMap<>(); + protected int frame; + protected InstancedTileRenderer() { registerMaterials(); } @@ -38,15 +38,74 @@ public abstract class InstancedTileRenderer

{ public abstract void registerMaterials(); - public void tick() { - if (tickableInstances.size() > 0) - tickableInstances.values().forEach(ITickableInstance::tick); + public void tick(double cameraX, double cameraY, double cameraZ) { + // integer camera pos + int cX = (int) cameraX; + int cY = (int) cameraY; + int cZ = (int) cameraZ; + + if (tickableInstances.size() > 0) { + for (ITickableInstance instance : tickableInstances.values()) { + if (!instance.decreaseTickRateWithDistance()) { + instance.tick(); + continue; + } + + BlockPos pos = instance.getWorldPosition(); + + int dX = pos.getX() - cX; + int dY = pos.getY() - cY; + int dZ = pos.getZ() - cZ; + + int dSq = dX * dX + dY * dY + dZ * dZ; + + int divisor = (dSq / 1024) + 1; + + if (frame % divisor == 0) + instance.tick(); + } + } } - public void beginFrame(double cameraX, double cameraY, double cameraZ) { + public void beginFrame(ActiveRenderInfo info, double cameraX, double cameraY, double cameraZ) { + frame++; processQueuedAdditions(); - if (dynamicInstances.size() > 0) - dynamicInstances.values().forEach(IDynamicInstance::beginFrame); + + Vector3f look = info.getHorizontalPlane(); + float lookX = look.getX(); + float lookY = look.getY(); + float lookZ = look.getZ(); + + // integer camera pos + int cX = (int) cameraX; + int cY = (int) cameraY; + int cZ = (int) cameraZ; + + if (dynamicInstances.size() > 0) { + for (IDynamicInstance dyn : dynamicInstances.values()) { + if (!dyn.decreaseFramerateWithDistance()) { + dyn.beginFrame(); + continue; + } + + BlockPos pos = dyn.getWorldPosition(); + + int dX = pos.getX() - cX; + int dY = pos.getY() - cY; + int dZ = pos.getZ() - cZ; + + float dot = dX * lookX + dY * lookY + dZ * lookZ; + + if (dot < 0) continue; // is it behind the camera? + + int dSq = dX * dX + dY * dY + dZ * dZ; + + int divisor = (dSq / 1024) + 1; // https://www.desmos.com/calculator/aaycpludsy + + if (frame % divisor == 0) + dyn.beginFrame(); + } + } } public void render(RenderType layer, Matrix4f viewProjection, double camX, double camY, double camZ) { diff --git a/src/main/java/com/simibubi/create/foundation/render/backend/instancing/TileEntityInstance.java b/src/main/java/com/simibubi/create/foundation/render/backend/instancing/TileEntityInstance.java index 97da4b18b..768171aee 100644 --- a/src/main/java/com/simibubi/create/foundation/render/backend/instancing/TileEntityInstance.java +++ b/src/main/java/com/simibubi/create/foundation/render/backend/instancing/TileEntityInstance.java @@ -30,12 +30,13 @@ import java.util.stream.Stream; * * @param The type of {@link TileEntity} your class is an instance of. */ -public abstract class TileEntityInstance { +public abstract class TileEntityInstance implements IInstance { protected final InstancedTileRenderer renderer; protected final T tile; protected final World world; protected final BlockPos pos; + protected final BlockPos instancePos; protected final BlockState blockState; public TileEntityInstance(InstancedTileRenderer renderer, T tile) { @@ -44,6 +45,7 @@ public abstract class TileEntityInstance { this.world = tile.getWorld(); this.pos = tile.getPos(); this.blockState = tile.getBlockState(); + this.instancePos = pos.subtract(renderer.getOriginCoordinate()); } /** @@ -89,7 +91,12 @@ public abstract class TileEntityInstance { * represents should be rendered at to appear in the correct location. */ public BlockPos getInstancePosition() { - return pos.subtract(renderer.getOriginCoordinate()); + return instancePos; + } + + @Override + public BlockPos getWorldPosition() { + return pos; } protected void relight(BlockPos pos, IFlatLight... models) {