From 6cb383952c9aa3b2ed7e8d31fe9fda310dc353c1 Mon Sep 17 00:00:00 2001 From: Sollace Date: Mon, 23 May 2022 23:19:53 +0200 Subject: [PATCH] Optimise villager rendering by first combining the textures so we can render the model once. --- .../render/entity/npc/NpcClothingFeature.java | 105 +++++++----------- .../client/util/render/TextureFlattener.java | 70 ++++++++++++ 2 files changed, 111 insertions(+), 64 deletions(-) create mode 100644 src/main/java/com/minelittlepony/client/util/render/TextureFlattener.java diff --git a/src/main/java/com/minelittlepony/client/render/entity/npc/NpcClothingFeature.java b/src/main/java/com/minelittlepony/client/render/entity/npc/NpcClothingFeature.java index ef9526c6..3a9a9fed 100644 --- a/src/main/java/com/minelittlepony/client/render/entity/npc/NpcClothingFeature.java +++ b/src/main/java/com/minelittlepony/client/render/entity/npc/NpcClothingFeature.java @@ -2,23 +2,16 @@ package com.minelittlepony.client.render.entity.npc; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.entity.feature.FeatureRendererContext; -import net.minecraft.client.render.entity.feature.VillagerResourceMetadata; -import net.minecraft.client.render.entity.feature.VillagerResourceMetadata.HatType; import net.minecraft.client.render.entity.model.EntityModel; import net.minecraft.client.render.entity.model.ModelWithHat; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.entity.LivingEntity; -import net.minecraft.resource.ResourceManager; -import net.minecraft.resource.Resource; import net.minecraft.util.Identifier; import net.minecraft.util.Util; import net.minecraft.util.math.MathHelper; -import net.minecraft.util.registry.DefaultedRegistry; import net.minecraft.util.registry.Registry; import net.minecraft.village.VillagerData; import net.minecraft.village.VillagerDataContainer; @@ -28,9 +21,9 @@ import net.minecraft.village.VillagerType; import com.minelittlepony.client.model.IPonyModel; import com.minelittlepony.client.render.IPonyRenderContext; import com.minelittlepony.client.render.entity.feature.AbstractPonyFeature; +import com.minelittlepony.client.util.render.TextureFlattener; -import java.io.IOException; -import java.util.Map; +import java.util.*; class NpcClothingFeature< T extends LivingEntity & VillagerDataContainer, @@ -45,13 +38,8 @@ class NpcClothingFeature< a.put(5, new Identifier("diamond")); }); - private final Object2ObjectMap typeHatCache = new Object2ObjectOpenHashMap<>(); - private final Object2ObjectMap profHatCache = new Object2ObjectOpenHashMap<>(); - private final String entityType; - private final ResourceManager resourceManager = MinecraftClient.getInstance().getResourceManager(); - public NpcClothingFeature(C context, String type) { super(context); entityType = type; @@ -60,72 +48,61 @@ class NpcClothingFeature< public static Identifier getClothingTexture(VillagerDataContainer entity, String entityType) { VillagerProfession profession = entity.getVillagerData().getProfession(); - return createTexture("minelittlepony", "profession", entityType, Registry.VILLAGER_PROFESSION.getId(profession)); + return createTexture("minelittlepony", entityType, "profession", Registry.VILLAGER_PROFESSION.getId(profession)); } - public static Identifier createTexture(String namespace, String type, String entityType, Identifier profession) { - return new Identifier(namespace, String.format("textures/entity/%s/%s/%s.png", entityType, type, profession.getPath())); + public static Identifier createTexture(String namespace, String entityType, String category, Identifier identifier) { + return new Identifier(namespace, String.format("textures/entity/%s/%s/%s.png", entityType, category, identifier.getPath())); + } + + public Identifier createTexture(String category, Identifier identifier) { + return createTexture("minelittlepony", entityType, category, identifier); } @Override public void render(MatrixStack matrixStack, VertexConsumerProvider provider, int i, T entity, float f, float g, float h, float j, float k, float l) { - if (!entity.isInvisible()) { + if (entity.isInvisible()) { + return; + } - VillagerData data = entity.getVillagerData(); - VillagerType type = data.getType(); - VillagerProfession profession = data.getProfession(); + VillagerData data = entity.getVillagerData(); + M entityModel = getContextModel(); - HatType typeHatLayer = getHatType(typeHatCache, "type", Registry.VILLAGER_TYPE, type); - HatType profHatLayer = getHatType(profHatCache, "profession", Registry.VILLAGER_PROFESSION, profession); - M entityModel = getContextModel(); - - entityModel.setHatVisible( - profHatLayer == VillagerResourceMetadata.HatType.NONE - || (profHatLayer == VillagerResourceMetadata.HatType.PARTIAL && typeHatLayer != VillagerResourceMetadata.HatType.FULL) - ); - - Identifier typeSkin = findTexture("type", Registry.VILLAGER_TYPE.getId(type)); + if (entity.isBaby() || data.getProfession() == VillagerProfession.NONE) { + Identifier typeSkin = createTexture("type", Registry.VILLAGER_TYPE.getId(data.getType())); renderModel(entityModel, typeSkin, matrixStack, provider, i, entity, 1, 1, 1); - - entityModel.setHatVisible(true); - - if (profession != VillagerProfession.NONE && !entity.isBaby()) { - Identifier professionSkin = findTexture("profession", Registry.VILLAGER_PROFESSION.getId(profession)); - - renderModel(entityModel, professionSkin, matrixStack, provider, i, entity, 1, 1, 1); - - if (profession != VillagerProfession.NITWIT) { - Identifier levelSkin = findTexture("profession_level", LEVEL_TO_ID.get(MathHelper.clamp(data.getLevel(), 1, LEVEL_TO_ID.size()))); - - renderModel(entityModel, levelSkin, matrixStack, provider, i, entity, 1, 1, 1); - } - } + } else { + renderModel(entityModel, getMergedTexture(data), matrixStack, provider, i, entity, 1, 1, 1); } } - public VillagerResourceMetadata.HatType getHatType(Object2ObjectMap cache, String type, DefaultedRegistry registry, K key) { - if (cache.containsKey(key)) { - return cache.get(key); // People often complain that villagers cause lag, - // so let's do better than Mojang and rather NOT go - // through all the lambda generations if we can avoid it. + public Identifier getMergedTexture(VillagerData data) { + VillagerType type = data.getType(); + VillagerProfession profession = data.getProfession(); + int level = MathHelper.clamp(data.getLevel(), 1, LEVEL_TO_ID.size()); + + Identifier typeId = Registry.VILLAGER_TYPE.getId(type); + Identifier profId = Registry.VILLAGER_PROFESSION.getId(profession); + + Identifier key = new Identifier((typeId + "/" + profId + "/" + level).replace(':', '_')); + + if (MinecraftClient.getInstance().getTextureManager().getOrDefault(key, null) == null) { + TextureFlattener.flatten(computeTextures(type, profession, typeId, profId, level), (Identifier)key); } - return loadHatType(cache, type, registry, key); + + return key; } - private VillagerResourceMetadata.HatType loadHatType(Map cache, String type, DefaultedRegistry registry, K key) { - return cache.computeIfAbsent(key, k -> { - try (Resource res = resourceManager.getResource(findTexture(type, registry.getId(k)))) { - VillagerResourceMetadata meta = res.getMetadata(VillagerResourceMetadata.READER); - if (meta != null) { - return meta.getHatType(); - } - } catch (IOException e) { } - return HatType.NONE; - }); - } + private List computeTextures(VillagerType type, VillagerProfession profession, Identifier typeId, Identifier profId, int level) { + List skins = new ArrayList<>(); - public Identifier findTexture(String category, Identifier identifier) { - return new Identifier("minelittlepony", "textures/entity/" + entityType + "/" + category + "/" + identifier.getPath() + ".png"); + skins.add(createTexture("type", typeId)); + skins.add(createTexture("profession", Registry.VILLAGER_PROFESSION.getId(profession))); + if (profession != VillagerProfession.NITWIT) { + skins.add(createTexture("profession_level", LEVEL_TO_ID.get(level))); + } + + return skins; } } diff --git a/src/main/java/com/minelittlepony/client/util/render/TextureFlattener.java b/src/main/java/com/minelittlepony/client/util/render/TextureFlattener.java new file mode 100644 index 00000000..e68ecbea --- /dev/null +++ b/src/main/java/com/minelittlepony/client/util/render/TextureFlattener.java @@ -0,0 +1,70 @@ +package com.minelittlepony.client.util.render; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.texture.*; +import net.minecraft.resource.ResourceManager; +import net.minecraft.util.Identifier; + +import com.google.common.base.Preconditions; +import com.mojang.blaze3d.platform.TextureUtil; +import com.mojang.blaze3d.systems.RenderSystem; + +import java.io.IOException; +import java.util.List; + +public class TextureFlattener { + + public static void flatten(List textures, Identifier output) { + Preconditions.checkArgument(textures.size() > 0, "Must have at least one image to flatten"); + MinecraftClient.getInstance().getTextureManager().registerTexture(output, new ResourceTexture(output) { + @Override + public void load(ResourceManager resManager) throws IOException { + NativeImage image = null; + + for (int i = 0; i < textures.size(); i++) { + TextureData data = TextureData.load(resManager, textures.get(0)); + data.checkException(); + if (image == null) { + image = data.getImage(); + } else { + copyOver(data.getImage(), image); + } + } + + if (!RenderSystem.isOnRenderThreadOrInit()) { + final NativeImage i = image; + RenderSystem.recordRenderCall(() -> upload(i)); + } else { + upload(image); + } + } + + private void upload(NativeImage image) { + TextureUtil.prepareImage(getGlId(), 0, image.getWidth(), image.getHeight()); + image.upload(0, 0, 0, 0, 0, image.getWidth(), image.getHeight(), false, false, false, true); + } + }); + } + + public static void copyOver(NativeImage from, NativeImage to) { + copyOver(from, to, 0, 0, + Math.min(from.getWidth(), to.getWidth()), + Math.min(from.getHeight(), to.getHeight()) + ); + } + + public static void copyOver(NativeImage from, NativeImage to, int x, int y, int w, int h) { + for (; x < w; x++) { + for (; y < h; y++) { + copy(from, to, x, y); + } + } + } + + public static void copy(NativeImage from, NativeImage to, int x, int y) { + int color = from.getColor(x, y); + if (NativeImage.getAlpha(color) > 0) { + to.setColor(x, y, color); + } + } +}