mirror of
https://github.com/MineLittlePony/MineLittlePony.git
synced 2025-03-30 16:27:43 +02:00
Optimise villager rendering by first combining the textures so we can render the model once.
This commit is contained in:
parent
8911cbf337
commit
6cb383952c
2 changed files with 111 additions and 64 deletions
|
@ -2,23 +2,16 @@ package com.minelittlepony.client.render.entity.npc;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
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.MinecraftClient;
|
||||||
import net.minecraft.client.render.VertexConsumerProvider;
|
import net.minecraft.client.render.VertexConsumerProvider;
|
||||||
import net.minecraft.client.render.entity.feature.FeatureRendererContext;
|
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.EntityModel;
|
||||||
import net.minecraft.client.render.entity.model.ModelWithHat;
|
import net.minecraft.client.render.entity.model.ModelWithHat;
|
||||||
import net.minecraft.client.util.math.MatrixStack;
|
import net.minecraft.client.util.math.MatrixStack;
|
||||||
import net.minecraft.entity.LivingEntity;
|
import net.minecraft.entity.LivingEntity;
|
||||||
import net.minecraft.resource.ResourceManager;
|
|
||||||
import net.minecraft.resource.Resource;
|
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
import net.minecraft.util.Util;
|
import net.minecraft.util.Util;
|
||||||
import net.minecraft.util.math.MathHelper;
|
import net.minecraft.util.math.MathHelper;
|
||||||
import net.minecraft.util.registry.DefaultedRegistry;
|
|
||||||
import net.minecraft.util.registry.Registry;
|
import net.minecraft.util.registry.Registry;
|
||||||
import net.minecraft.village.VillagerData;
|
import net.minecraft.village.VillagerData;
|
||||||
import net.minecraft.village.VillagerDataContainer;
|
import net.minecraft.village.VillagerDataContainer;
|
||||||
|
@ -28,9 +21,9 @@ import net.minecraft.village.VillagerType;
|
||||||
import com.minelittlepony.client.model.IPonyModel;
|
import com.minelittlepony.client.model.IPonyModel;
|
||||||
import com.minelittlepony.client.render.IPonyRenderContext;
|
import com.minelittlepony.client.render.IPonyRenderContext;
|
||||||
import com.minelittlepony.client.render.entity.feature.AbstractPonyFeature;
|
import com.minelittlepony.client.render.entity.feature.AbstractPonyFeature;
|
||||||
|
import com.minelittlepony.client.util.render.TextureFlattener;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.util.*;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
class NpcClothingFeature<
|
class NpcClothingFeature<
|
||||||
T extends LivingEntity & VillagerDataContainer,
|
T extends LivingEntity & VillagerDataContainer,
|
||||||
|
@ -45,13 +38,8 @@ class NpcClothingFeature<
|
||||||
a.put(5, new Identifier("diamond"));
|
a.put(5, new Identifier("diamond"));
|
||||||
});
|
});
|
||||||
|
|
||||||
private final Object2ObjectMap<VillagerType, HatType> typeHatCache = new Object2ObjectOpenHashMap<>();
|
|
||||||
private final Object2ObjectMap<VillagerProfession, HatType> profHatCache = new Object2ObjectOpenHashMap<>();
|
|
||||||
|
|
||||||
private final String entityType;
|
private final String entityType;
|
||||||
|
|
||||||
private final ResourceManager resourceManager = MinecraftClient.getInstance().getResourceManager();
|
|
||||||
|
|
||||||
public NpcClothingFeature(C context, String type) {
|
public NpcClothingFeature(C context, String type) {
|
||||||
super(context);
|
super(context);
|
||||||
entityType = type;
|
entityType = type;
|
||||||
|
@ -60,72 +48,61 @@ class NpcClothingFeature<
|
||||||
public static Identifier getClothingTexture(VillagerDataContainer entity, String entityType) {
|
public static Identifier getClothingTexture(VillagerDataContainer entity, String entityType) {
|
||||||
VillagerProfession profession = entity.getVillagerData().getProfession();
|
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) {
|
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, type, profession.getPath()));
|
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
|
@Override
|
||||||
public void render(MatrixStack matrixStack, VertexConsumerProvider provider, int i, T entity, float f, float g, float h, float j, float k, float l) {
|
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();
|
VillagerData data = entity.getVillagerData();
|
||||||
VillagerType type = data.getType();
|
M entityModel = getContextModel();
|
||||||
VillagerProfession profession = data.getProfession();
|
|
||||||
|
|
||||||
HatType typeHatLayer = getHatType(typeHatCache, "type", Registry.VILLAGER_TYPE, type);
|
if (entity.isBaby() || data.getProfession() == VillagerProfession.NONE) {
|
||||||
HatType profHatLayer = getHatType(profHatCache, "profession", Registry.VILLAGER_PROFESSION, profession);
|
Identifier typeSkin = createTexture("type", Registry.VILLAGER_TYPE.getId(data.getType()));
|
||||||
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));
|
|
||||||
|
|
||||||
renderModel(entityModel, typeSkin, matrixStack, provider, i, entity, 1, 1, 1);
|
renderModel(entityModel, typeSkin, matrixStack, provider, i, entity, 1, 1, 1);
|
||||||
|
} else {
|
||||||
entityModel.setHatVisible(true);
|
renderModel(entityModel, getMergedTexture(data), matrixStack, provider, i, entity, 1, 1, 1);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public <K> VillagerResourceMetadata.HatType getHatType(Object2ObjectMap<K, HatType> cache, String type, DefaultedRegistry<K> registry, K key) {
|
public Identifier getMergedTexture(VillagerData data) {
|
||||||
if (cache.containsKey(key)) {
|
VillagerType type = data.getType();
|
||||||
return cache.get(key); // People often complain that villagers cause lag,
|
VillagerProfession profession = data.getProfession();
|
||||||
// so let's do better than Mojang and rather NOT go
|
int level = MathHelper.clamp(data.getLevel(), 1, LEVEL_TO_ID.size());
|
||||||
// through all the lambda generations if we can avoid it.
|
|
||||||
|
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 <K> VillagerResourceMetadata.HatType loadHatType(Map<K, HatType> cache, String type, DefaultedRegistry<K> registry, K key) {
|
private List<Identifier> computeTextures(VillagerType type, VillagerProfession profession, Identifier typeId, Identifier profId, int level) {
|
||||||
return cache.computeIfAbsent(key, k -> {
|
List<Identifier> skins = new ArrayList<>();
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public Identifier findTexture(String category, Identifier identifier) {
|
skins.add(createTexture("type", typeId));
|
||||||
return new Identifier("minelittlepony", "textures/entity/" + entityType + "/" + category + "/" + identifier.getPath() + ".png");
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<Identifier> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue