Skip to content

Commit

Permalink
Temporarily replace the buffered entity rendering code to avoid memor…
Browse files Browse the repository at this point in the history
…y issues

This new strategy works by adding a few small tweaks to make the vanilla entity
batching code work better, instead of trying to go all-out with entity buffering.

We do two main things:
* Sort entities by type before rendering them
    * This allows many common entities to be batched into a single draw call by vanilla. It was just held back by having to constantly switch between rendering types since without sorting, the order is essentially random.
* Add things like enderman eyes / spider eyes / sheep wool to the list of render layers buffered by vanilla.
    * Vanilla does have special code to buffer up entity geometry, but it only activates for certain render layers. By adding a few more render layers to the list of buffered layers, we allow vanilla's batching code to not get interrupted by constantly switching between rendering enderman bodies and enderman eyes.

I have some code in the pipeline to make entity buffering work way better, but it's
not ready yet.
  • Loading branch information
coderbot16 committed Jul 24, 2021
1 parent d4dd969 commit d0a56ee
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 39 deletions.
28 changes: 28 additions & 0 deletions src/main/java/net/coderbot/iris/mixin/MixinWorldRenderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
import net.minecraft.client.options.GameOptions;
import net.minecraft.client.render.*;
import net.minecraft.client.util.math.Vector3f;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.profiler.Profiler;
import org.spongepowered.asm.mixin.Mixin;
Expand All @@ -29,6 +32,14 @@
import net.fabricmc.api.Environment;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Mixin(WorldRenderer.class)
@Environment(EnvType.CLIENT)
public class MixinWorldRenderer {
Expand Down Expand Up @@ -224,4 +235,21 @@ public class MixinWorldRenderer {
profiler.swap("iris_pre_translucent");
pipeline.beginTranslucents();
}

@Redirect(method = RENDER, at = @At(value = "INVOKE", target = "net/minecraft/client/world/ClientWorld.getEntities ()Ljava/lang/Iterable;"))
private Iterable<Entity> iris$sortEntityList(ClientWorld world) {
// Sort the entity list first in order to allow vanilla's entity batching code to work better.
Iterable<Entity> entityIterable = world.getEntities();

Map<EntityType<?>, List<Entity>> sortedEntities = new HashMap<>();

List<Entity> entities = new ArrayList<>();
entityIterable.forEach(entity -> {
sortedEntities.computeIfAbsent(entity.getType(), entityType -> new ArrayList<>(32)).add(entity);
});

sortedEntities.values().forEach(entities::addAll);

return entities;
}
}
Original file line number Diff line number Diff line change
@@ -1,61 +1,47 @@
package net.coderbot.iris.mixin.fantastic;

import net.coderbot.iris.fantastic.ExtendedBufferStorage;
import net.coderbot.iris.fantastic.FullyBufferedVertexConsumerProvider;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.BufferBuilderStorage;
import net.minecraft.client.render.OutlineVertexConsumerProvider;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

import java.util.SortedMap;

@Mixin(BufferBuilderStorage.class)
public class MixinBufferBuilderStorage implements ExtendedBufferStorage {
@Unique
private final VertexConsumerProvider.Immediate buffered = new FullyBufferedVertexConsumerProvider();

@Unique
private int begins = 0;
@Shadow
@Final
private SortedMap<RenderLayer, BufferBuilder> entityBuilders;

@Unique
private final OutlineVertexConsumerProvider outlineVertexConsumers = new OutlineVertexConsumerProvider(buffered);

@Inject(method = "getEntityVertexConsumers", at = @At("HEAD"), cancellable = true)
private void iris$replaceEntityVertexConsumers(CallbackInfoReturnable<VertexConsumerProvider.Immediate> provider) {
if (begins == 0) {
return;
}

provider.setReturnValue(buffered);
private static void iris$assignBufferBuilder(SortedMap<RenderLayer, BufferBuilder> builderStorage, RenderLayer layer) {
builderStorage.put(layer, new BufferBuilder(layer.getExpectedBufferSize()));
}

@Inject(method = "getEffectVertexConsumers", at = @At("HEAD"), cancellable = true)
private void iris$replaceEffectVertexConsumers(CallbackInfoReturnable<VertexConsumerProvider.Immediate> provider) {
if (begins == 0) {
return;
}

// NB: We can return the same VertexConsumerProvider here as long as the block entity and its breaking animation
// use different render layers. This seems like a sound assumption to make. This only works with our fully
// buffered vertex consumer provider - vanilla's Immediate cannot be used here since it would try to return the
// same buffer for the block entity and its breaking animation in many cases.
//
// If anything goes wrong here, Vanilla *will* catch the "duplicate delegates" error, so
// this shouldn't cause silent bugs.
provider.setReturnValue(buffered);
}
@Inject(method = "<init>()V", at = @At("RETURN"))
private void iris$onInit(CallbackInfo ci) {
// Add a few render layers to the list of specially-buffered layers in order to improve batching in some
// common survival scenes.

@Inject(method = "getOutlineVertexConsumers", at = @At("HEAD"), cancellable = true)
private void iris$replaceOutlineVertexConsumers(CallbackInfoReturnable<OutlineVertexConsumerProvider> provider) {
if (begins == 0) {
return;
}
// Special-case for enderman eyes and spider eyes since they're so common.
iris$assignBufferBuilder(entityBuilders, RenderLayer.getEyes(new Identifier("textures/entity/enderman/enderman_eyes.png")));
iris$assignBufferBuilder(entityBuilders, RenderLayer.getEyes(new Identifier("textures/entity/enderman/spider_eyes.png")));

provider.setReturnValue(outlineVertexConsumers);
// Similar deal with wool on sheeps.
iris$assignBufferBuilder(entityBuilders, RenderLayer.getEntityCutoutNoCull(new Identifier("textures/entity/sheep/sheep_fur.png")));
}

@Unique
private int begins = 0;

@Override
public void beginWorldRendering() {
begins += 1;
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/net/coderbot/iris/pipeline/ShadowRenderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Matrix4f;
import net.minecraft.util.math.Vec3d;
Expand All @@ -43,7 +44,10 @@
import org.lwjgl.opengl.GL30C;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;

Expand Down Expand Up @@ -377,6 +381,11 @@ public void renderShadows(WorldRendererAccessor worldRenderer, Camera playerCame
renderedEntities.add(entity);
}

worldRenderer.getWorld().getProfiler().swap("sort");

// Sort the entities by type first in order to allow vanilla's entity batching system to work better.
renderedEntities.sort(Comparator.comparingInt(entity -> entity.getType().hashCode()));

worldRenderer.getWorld().getProfiler().swap("build geometry");

for (Entity entity : renderedEntities) {
Expand Down

0 comments on commit d0a56ee

Please sign in to comment.