Frame rate and tick rate limiting with distance.

- Significant performance improvement when dealing with massive amounts of dynamic instances, otherwise marginal.
This commit is contained in:
JozsefA 2021-03-26 16:47:37 -07:00
parent e91a15cf5f
commit 0b25f662dc
12 changed files with 141 additions and 43 deletions

View file

@ -51,6 +51,8 @@ public class DeployerInstance extends ShaftInstance implements IDynamicInstance,
relight(pos, pole.getInstance()); relight(pos, pole.getInstance());
updateRotation(pole, hand, yRot, zRot, zRotPole); updateRotation(pole, hand, yRot, zRot, zRotPole);
beginFrame();
} }
@Override @Override

View file

@ -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.OrientedModel;
import com.simibubi.create.foundation.render.backend.instancing.impl.TransformedModel; 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.util.math.BlockPos;
import net.minecraft.world.gen.feature.template.Template; import net.minecraft.world.gen.feature.template.Template;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
@ -44,14 +46,13 @@ public class ContraptionKineticRenderer extends InstancedTileRenderer<Contraptio
materials.put(KineticRenderMaterials.ACTORS, new RenderMaterial<>(this, AllProgramSpecs.C_ACTOR, ActorModel::new)); materials.put(KineticRenderMaterials.ACTORS, new RenderMaterial<>(this, AllProgramSpecs.C_ACTOR, ActorModel::new));
} }
@Override
public void tick() { public void tick() {
actors.forEach(ActorInstance::tick); actors.forEach(ActorInstance::tick);
} }
@Override @Override
public void beginFrame(double cameraX, double cameraY, double cameraZ) { public void beginFrame(ActiveRenderInfo info, double cameraX, double cameraY, double cameraZ) {
super.beginFrame(cameraX, cameraY, cameraZ); super.beginFrame(info, cameraX, cameraY, cameraZ);
actors.forEach(ActorInstance::beginFrame); actors.forEach(ActorInstance::beginFrame);
} }

View file

@ -28,20 +28,11 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraft.block.BlockRenderType; import net.minecraft.block.BlockRenderType;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BlockModelRenderer; import net.minecraft.client.renderer.*;
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.model.IBakedModel; import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.SectionPos;
import net.minecraft.world.ILightReader; import net.minecraft.world.ILightReader;
import net.minecraft.world.LightType; import net.minecraft.world.LightType;
import net.minecraft.world.World; import net.minecraft.world.World;
@ -94,9 +85,9 @@ public class ContraptionRenderDispatcher {
return contraption; 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()) { for (RenderedContraption renderer : renderers.values()) {
renderer.beginFrame(camX, camY, camZ); renderer.beginFrame(info, camX, camY, camZ);
} }
} }

View file

@ -20,12 +20,7 @@ import com.simibubi.create.foundation.utility.worldWrappers.PlacementSimulationW
import net.minecraft.block.BlockRenderType; import net.minecraft.block.BlockRenderType;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BlockModelRenderer; import net.minecraft.client.renderer.*;
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.model.IBakedModel; import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
@ -87,8 +82,8 @@ public class RenderedContraption {
} }
} }
public void beginFrame(double camX, double camY, double camZ) { public void beginFrame(ActiveRenderInfo info, double camX, double camY, double camZ) {
kinetics.beginFrame(camX, camY, camZ); kinetics.beginFrame(info, camX, camY, camZ);
AbstractContraptionEntity entity = contraption.entity; AbstractContraptionEntity entity = contraption.entity;
float pt = AnimationTickHolder.getPartialTicks(); float pt = AnimationTickHolder.getPartialTicks();

View file

@ -4,7 +4,6 @@ import com.simibubi.create.foundation.render.KineticRenderer;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.*; import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.texture.AtlasTexture;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import org.lwjgl.opengl.GL20; import org.lwjgl.opengl.GL20;
@ -57,8 +56,8 @@ public class RenderHooksMixin {
double camY = cameraPos.getY(); double camY = cameraPos.getY();
double camZ = cameraPos.getZ(); double camZ = cameraPos.getZ();
CreateClient.kineticRenderer.get(world).beginFrame(camX, camY, camZ); CreateClient.kineticRenderer.get(world).beginFrame(info, camX, camY, camZ);
ContraptionRenderDispatcher.beginFrame(camX, camY, camZ); ContraptionRenderDispatcher.beginFrame(info, camX, camY, camZ);
} }
@Inject(at = @At("TAIL"), method = "checkBlockRerender") @Inject(at = @At("TAIL"), method = "checkBlockRerender")

View file

@ -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.OrientedModel;
import com.simibubi.create.foundation.render.backend.instancing.impl.TransformedModel; 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.Matrix4f;
import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.RenderType;
import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntity;
@ -42,7 +43,7 @@ public class KineticRenderer extends InstancedTileRenderer<BasicProgram> {
} }
@Override @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 cX = MathHelper.floor(cameraX);
int cY = MathHelper.floor(cameraY); int cY = MathHelper.floor(cameraY);
int cZ = MathHelper.floor(cameraZ); int cZ = MathHelper.floor(cameraZ);
@ -62,7 +63,7 @@ public class KineticRenderer extends InstancedTileRenderer<BasicProgram> {
instancedTiles.forEach(this::add); instancedTiles.forEach(this::add);
} }
super.beginFrame(cameraX, cameraY, cameraZ); super.beginFrame(info, cameraX, cameraY, cameraZ);
} }
@Override @Override

View file

@ -17,6 +17,7 @@ import net.minecraft.client.renderer.Matrix4f;
import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.Vector3f; import net.minecraft.client.renderer.Vector3f;
import net.minecraft.client.world.ClientWorld; import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.Entity;
import net.minecraft.potion.Effects; import net.minecraft.potion.Effects;
import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
@ -37,10 +38,13 @@ public class FastRenderDispatcher {
} }
public static void tick() { public static void tick() {
ClientWorld world = Minecraft.getInstance().world; Minecraft mc = Minecraft.getInstance();
ClientWorld world = mc.world;
KineticRenderer kineticRenderer = CreateClient.kineticRenderer.get(world); KineticRenderer kineticRenderer = CreateClient.kineticRenderer.get(world);
kineticRenderer.tick();
Entity renderViewEntity = mc.renderViewEntity;
kineticRenderer.tick(renderViewEntity.getX(), renderViewEntity.getY(), renderViewEntity.getZ());
ConcurrentHashMap.KeySetView<TileEntity, Boolean> map = queuedUpdates.get(world); ConcurrentHashMap.KeySetView<TileEntity, Boolean> map = queuedUpdates.get(world);
map map

View file

@ -9,9 +9,22 @@ package com.simibubi.create.foundation.render.backend.instancing;
* <br><br> If your goal is offloading work to shaders, but you're unsure exactly how you need * <br><br> 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. * to parameterize the instances, you're encouraged to implement this for prototyping.
*/ */
public interface IDynamicInstance { public interface IDynamicInstance extends IInstance {
/** /**
* Called every frame. * Called every frame.
*/ */
void beginFrame(); void beginFrame();
/**
* As a further optimization, dynamic instances that are far away are ticked less often.
* This behavior can be disabled by returning false.
*
* <br> 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 <code>true</code> if your instance should be slow ticked.
*/
default boolean decreaseFramerateWithDistance() {
return true;
}
} }

View file

@ -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();
}

View file

@ -16,10 +16,23 @@ package com.simibubi.create.foundation.render.backend.instancing;
* </li> * </li>
* </ul> * </ul>
*/ */
public interface ITickableInstance { public interface ITickableInstance extends IInstance {
/** /**
* Called every tick. * Called every tick.
*/ */
void 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.
*
* <br> 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 <code>true</code> if your instance should be slow ticked.
*/
default boolean decreaseTickRateWithDistance() {
return true;
}
} }

View file

@ -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.gl.shader.ShaderCallback;
import com.simibubi.create.foundation.render.backend.instancing.impl.ModelData; 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.render.backend.instancing.impl.OrientedData;
import com.simibubi.create.foundation.utility.AnimationTickHolder;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.Matrix4f; import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockReader; import net.minecraft.world.IBlockReader;
@ -30,6 +28,8 @@ public abstract class InstancedTileRenderer<P extends BasicProgram> {
protected Map<MaterialType<?>, RenderMaterial<P, ?>> materials = new HashMap<>(); protected Map<MaterialType<?>, RenderMaterial<P, ?>> materials = new HashMap<>();
protected int frame;
protected InstancedTileRenderer() { protected InstancedTileRenderer() {
registerMaterials(); registerMaterials();
} }
@ -38,15 +38,74 @@ public abstract class InstancedTileRenderer<P extends BasicProgram> {
public abstract void registerMaterials(); public abstract void registerMaterials();
public void tick() { public void tick(double cameraX, double cameraY, double cameraZ) {
if (tickableInstances.size() > 0) // integer camera pos
tickableInstances.values().forEach(ITickableInstance::tick); 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;
} }
public void beginFrame(double cameraX, double cameraY, double cameraZ) { 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(ActiveRenderInfo info, double cameraX, double cameraY, double cameraZ) {
frame++;
processQueuedAdditions(); 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) { public void render(RenderType layer, Matrix4f viewProjection, double camX, double camY, double camZ) {

View file

@ -30,12 +30,13 @@ import java.util.stream.Stream;
* *
* @param <T> The type of {@link TileEntity} your class is an instance of. * @param <T> The type of {@link TileEntity} your class is an instance of.
*/ */
public abstract class TileEntityInstance<T extends TileEntity> { public abstract class TileEntityInstance<T extends TileEntity> implements IInstance {
protected final InstancedTileRenderer<?> renderer; protected final InstancedTileRenderer<?> renderer;
protected final T tile; protected final T tile;
protected final World world; protected final World world;
protected final BlockPos pos; protected final BlockPos pos;
protected final BlockPos instancePos;
protected final BlockState blockState; protected final BlockState blockState;
public TileEntityInstance(InstancedTileRenderer<?> renderer, T tile) { public TileEntityInstance(InstancedTileRenderer<?> renderer, T tile) {
@ -44,6 +45,7 @@ public abstract class TileEntityInstance<T extends TileEntity> {
this.world = tile.getWorld(); this.world = tile.getWorld();
this.pos = tile.getPos(); this.pos = tile.getPos();
this.blockState = tile.getBlockState(); this.blockState = tile.getBlockState();
this.instancePos = pos.subtract(renderer.getOriginCoordinate());
} }
/** /**
@ -89,7 +91,12 @@ public abstract class TileEntityInstance<T extends TileEntity> {
* represents should be rendered at to appear in the correct location. * represents should be rendered at to appear in the correct location.
*/ */
public BlockPos getInstancePosition() { public BlockPos getInstancePosition() {
return pos.subtract(renderer.getOriginCoordinate()); return instancePos;
}
@Override
public BlockPos getWorldPosition() {
return pos;
} }
protected void relight(BlockPos pos, IFlatLight<?>... models) { protected void relight(BlockPos pos, IFlatLight<?>... models) {