Rewrite faster_baking optimization, now defers baking to first use

This commit is contained in:
embeddedt 2023-02-11 10:30:19 -05:00
parent ebafc9a0cc
commit 327813fa91
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
5 changed files with 149 additions and 157 deletions

View File

@ -26,7 +26,6 @@ public class ModernFixEarlyConfig {
this.addMixinRule("perf.skip_first_datapack_reload", true);
this.addMixinRule("perf.parallelize_model_loading", true);
this.addMixinRule("perf.parallelize_model_loading.multipart", false);
this.addMixinRule("perf.trim_model_caches", true);
this.addMixinRule("bugfix.concurrency", true);
this.addMixinRule("bugfix.edge_chunk_not_saved", true);
this.addMixinRule("bugfix.packet_leak", false);

View File

@ -7,6 +7,7 @@ import net.minecraft.client.renderer.model.multipart.Multipart;
import net.minecraft.client.renderer.model.multipart.Selector;
import net.minecraft.client.renderer.texture.AtlasTexture;
import net.minecraft.client.renderer.texture.SpriteMap;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.profiler.IProfiler;
import net.minecraft.util.ResourceLocation;
@ -17,6 +18,7 @@ import org.apache.commons.lang3.tuple.Triple;
import org.apache.logging.log4j.Logger;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.ModernFixClient;
import org.embeddedt.modernfix.models.LazyBakedModel;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@ -26,6 +28,7 @@ import javax.annotation.Nullable;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import static org.embeddedt.modernfix.ModernFix.LOGGER;
@ -42,74 +45,11 @@ public abstract class ModelBakeryMixin {
@Shadow @Nullable private SpriteMap atlasSet;
@Shadow @Final private Map<ResourceLocation, IUnbakedModel> unbakedCache;
@Shadow @Mutable
@Final private Map<Triple<ResourceLocation, TransformationMatrix, Boolean>, IBakedModel> bakedCache;
@Shadow @Nullable public abstract IBakedModel getBakedModel(ResourceLocation pLocation, IModelTransform pTransform, Function<RenderMaterial, TextureAtlasSprite> textureGetter);
@Shadow @Final private static ItemModelGenerator ITEM_MODEL_GENERATOR;
@Shadow @Final public static BlockModel GENERATION_MARKER;
@Shadow @Final public static ModelResourceLocation MISSING_MODEL_LOCATION;
@Shadow public abstract IUnbakedModel getModel(ResourceLocation p_209597_1_);
private ConcurrentHashMap<ResourceLocation, IdentityHashMap<IModelTransform, IBakedModel>> fasterBakedCache;
private Map<Boolean, List<Map.Entry<ResourceLocation, IUnbakedModel>>> modelsToBakeParallel;
private boolean canBakeParallel(IUnbakedModel unbakedModel) {
if(unbakedModel instanceof BlockModel) {
BlockModel model = (BlockModel)unbakedModel;
return !model.customData.hasCustomGeometry();
} else if((unbakedModel instanceof Multipart) || (unbakedModel instanceof VariantList))
return true;
else
return false;
}
private IBakedModel bakeModel(ResourceLocation pLocation, IModelTransform pTransform, java.util.function.Function<RenderMaterial, net.minecraft.client.renderer.texture.TextureAtlasSprite> textureGetter) {
IUnbakedModel iunbakedmodel = this.unbakedCache.get(pLocation);
if(iunbakedmodel == null)
throw new IllegalStateException("we should not be baking unloaded models");
if (iunbakedmodel instanceof BlockModel) {
BlockModel blockmodel = (BlockModel)iunbakedmodel;
if (blockmodel.getRootModel() == GENERATION_MARKER) {
return ITEM_MODEL_GENERATOR.generateBlockModel(textureGetter, blockmodel).bake((ModelBakery)(Object)this, blockmodel, this.atlasSet::getSprite, pTransform, pLocation, false);
}
}
/* Ensure only one thread builds at a time if needed */
if(canBakeParallel(iunbakedmodel)) {
return iunbakedmodel.bake((ModelBakery)(Object)this, textureGetter, pTransform, pLocation);
} else {
synchronized(this.bakedCache) {
return iunbakedmodel.bake((ModelBakery)(Object)this, textureGetter, pTransform, pLocation);
}
}
}
/**
* @author embeddedt
* @reason avoid rehashing model data when unneeded
*/
@Overwrite(remap = false)
public IBakedModel getBakedModel(ResourceLocation pLocation, IModelTransform pTransform, java.util.function.Function<RenderMaterial, net.minecraft.client.renderer.texture.TextureAtlasSprite> textureGetter) {
/* Try to retrieve the model */
IdentityHashMap<IModelTransform, IBakedModel> modelsForLocation = this.fasterBakedCache.computeIfAbsent(pLocation, loc -> new IdentityHashMap<>());
synchronized (modelsForLocation) {
IBakedModel result = modelsForLocation.get(pTransform);
if(result != null)
return result;
}
/* Otherwise, bake the model and then store it */
synchronized(this.unbakedCache) {
this.getModel(pLocation);
}
IBakedModel result = bakeModel(pLocation, pTransform, textureGetter);
synchronized (modelsForLocation) {
modelsForLocation.put(pTransform, result);
}
return result;
}
@Shadow @Final private Map<Triple<ResourceLocation, TransformationMatrix, Boolean>, IBakedModel> bakedCache;
@Inject(method = "processLoading", at = @At(value = "INVOKE", target = "Lnet/minecraft/profiler/IProfiler;pop()V"))
private void bakeModels(IProfiler pProfiler, int p_i226056_4_, CallbackInfo ci) {
@ -124,61 +64,24 @@ public abstract class ModelBakeryMixin {
pProfiler.popPush("baking");
StartupMessageManager.mcLoaderConsumer().ifPresent(c -> c.accept("Baking models"));
this.atlasSet = new SpriteMap(this.atlasPreparations.values().stream().map(Pair::getFirst).collect(Collectors.toList()));
this.bakedCache = Collections.emptyMap();
this.fasterBakedCache = new ConcurrentHashMap<>();
this.modelsToBakeParallel = this.unbakedCache.entrySet().stream()
.collect(Collectors.partitioningBy(entry -> {
IUnbakedModel unbakedModel = entry.getValue();
if(unbakedModel == null)
return false;
else
return this.canBakeParallel(unbakedModel);
}));
List<Map.Entry<ResourceLocation, IUnbakedModel>> serialModels = this.modelsToBakeParallel.get(false);
List<Map.Entry<ResourceLocation, IUnbakedModel>> parallelModels = this.modelsToBakeParallel.get(true);
LOGGER.debug("Collected "
+ serialModels.size()
+ " serial models, "
+ parallelModels.size()
+ " parallel models");
List<CompletableFuture<Pair<ResourceLocation, IBakedModel>>> futures = new ArrayList<>();
/* First submit the parallel models */
for(Map.Entry<ResourceLocation, IUnbakedModel> entry : parallelModels) {
ResourceLocation loc = entry.getKey();
futures.add(CompletableFuture.supplyAsync(() -> {
IBakedModel ibakedmodel = null;
IBakedModel missingModel = this.bake(MISSING_MODEL_LOCATION, ModelRotation.X0_Y0);
this.bakedTopLevelModels.put(MISSING_MODEL_LOCATION, missingModel);
this.topLevelModels.keySet().forEach((p_229350_1_) -> {
this.bakedTopLevelModels.put(p_229350_1_, new LazyBakedModel(() -> {
synchronized (this.bakedCache) {
IBakedModel ibakedmodel = null;
try {
ibakedmodel = this.bake(loc, ModelRotation.X0_Y0);
} catch (Exception exception) {
exception.printStackTrace();
LOGGER.warn("Unable to bake model: '{}': {}", loc, exception);
try {
ibakedmodel = this.bake(p_229350_1_, ModelRotation.X0_Y0);
} catch (Exception exception) {
exception.printStackTrace();
LOGGER.warn("Unable to bake model: '{}': {}", p_229350_1_, exception);
}
return ibakedmodel != null ? ibakedmodel : missingModel;
}
return ibakedmodel != null ? Pair.of(loc, ibakedmodel) : null;
}, Util.backgroundExecutor()));
}
futures.forEach(future -> {
Pair<ResourceLocation, IBakedModel> pair = future.join();
if(pair != null && this.topLevelModels.containsKey(pair.getFirst()))
this.bakedTopLevelModels.put(pair.getFirst(), pair.getSecond());
}));
});
/* Then process serial models */
serialModels.forEach((p_229350_1_) -> {
IBakedModel ibakedmodel = null;
ResourceLocation loc = p_229350_1_.getKey();
try {
ibakedmodel = this.bake(loc, ModelRotation.X0_Y0);
} catch (Exception exception) {
exception.printStackTrace();
LOGGER.warn("Unable to bake model: '{}': {}", loc, exception);
}
if (ibakedmodel != null) {
this.bakedTopLevelModels.put(loc, ibakedmodel);
}
});
this.modelsToBakeParallel = null;
this.atlasSet = null;
}

View File

@ -1,37 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.trim_model_caches;
import net.minecraft.client.renderer.model.ModelBakery;
import net.minecraft.client.renderer.model.ModelManager;
import net.minecraft.profiler.IProfiler;
import net.minecraft.resources.IResourceManager;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
import org.embeddedt.modernfix.ModernFix;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.HashMap;
import java.util.Map;
@Mixin(ModelManager.class)
public class ModelManagerMixin {
private void trimBakeryMap(ModelBakery bakery, String fieldName) {
Map map = ObfuscationReflectionHelper.getPrivateValue(ModelBakery.class, bakery, fieldName);
int size = map.size();
ModernFix.LOGGER.warn("Trimming " + fieldName + " with " + size + " entries");
if(map instanceof HashMap) {
ObfuscationReflectionHelper.setPrivateValue(ModelBakery.class, bakery, new HashMap<>(), fieldName);
} else
map.clear();
}
@Inject(method = "apply(Lnet/minecraft/client/renderer/model/ModelBakery;Lnet/minecraft/resources/IResourceManager;Lnet/minecraft/profiler/IProfiler;)V", at = @At("RETURN"))
private void trimModelCaches(ModelBakery bakery, IResourceManager p_212853_2_, IProfiler p_212853_3_, CallbackInfo ci) {
trimBakeryMap(bakery, "field_217849_F"); // unbakedCache
trimBakeryMap(bakery, "field_217850_G"); // bakedCache
trimBakeryMap(bakery, "field_217851_H"); // topLevelModels
// bakedTopLevelModels is used as the model registry
}
}

View File

@ -0,0 +1,128 @@
package org.embeddedt.modernfix.models;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.datafixers.util.Pair;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.model.BakedQuad;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.client.renderer.model.ItemCameraTransforms;
import net.minecraft.client.renderer.model.ItemOverrideList;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockDisplayReader;
import net.minecraftforge.client.model.data.EmptyModelData;
import net.minecraftforge.client.model.data.IModelData;
import org.embeddedt.modernfix.ModernFix;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Random;
import java.util.function.Supplier;
public class LazyBakedModel implements IBakedModel {
private IBakedModel delegate = null;
private Supplier<IBakedModel> delegateSupplier;
public LazyBakedModel(Supplier<IBakedModel> delegateSupplier) {
this.delegateSupplier = delegateSupplier;
}
public IBakedModel computeDelegate() {
if(this.delegate == null) {
this.delegate = this.delegateSupplier.get();
this.delegateSupplier = null;
}
return this.delegate;
}
@Override
public List<BakedQuad> getQuads(@Nullable BlockState pState, @Nullable Direction pSide, Random pRand) {
return computeDelegate().getQuads(pState, pSide, pRand);
}
@Override
public boolean useAmbientOcclusion() {
return computeDelegate().useAmbientOcclusion();
}
@Override
public boolean isGui3d() {
return computeDelegate().isGui3d();
}
@Override
public boolean usesBlockLight() {
return computeDelegate().usesBlockLight();
}
@Override
public boolean isCustomRenderer() {
return computeDelegate().isCustomRenderer();
}
@Override
public TextureAtlasSprite getParticleIcon() {
return computeDelegate().getParticleIcon();
}
@Override
public ItemCameraTransforms getTransforms() {
return computeDelegate().getTransforms();
}
@Override
public ItemOverrideList getOverrides() {
return computeDelegate().getOverrides();
}
@Override
public IBakedModel getBakedModel() {
return computeDelegate().getBakedModel();
}
@Nonnull
@Override
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, @Nonnull Random rand, @Nonnull IModelData extraData) {
return computeDelegate().getQuads(state, side, rand, extraData);
}
@Override
public boolean isAmbientOcclusion(BlockState state) {
return computeDelegate().isAmbientOcclusion(state);
}
@Override
public boolean doesHandlePerspectives() {
return computeDelegate().doesHandlePerspectives();
}
@Override
public IBakedModel handlePerspective(ItemCameraTransforms.TransformType cameraTransformType, MatrixStack mat) {
return computeDelegate().handlePerspective(cameraTransformType, mat);
}
@Nonnull
@Override
public IModelData getModelData(@Nonnull IBlockDisplayReader world, @Nonnull BlockPos pos, @Nonnull BlockState state, @Nonnull IModelData tileData) {
return computeDelegate().getModelData(world, pos, state, tileData);
}
@Override
public TextureAtlasSprite getParticleTexture(@Nonnull IModelData data) {
return computeDelegate().getParticleTexture(data);
}
@Override
public boolean isLayered() {
return computeDelegate().isLayered();
}
@Override
public List<Pair<IBakedModel, RenderType>> getLayerModels(ItemStack itemStack, boolean fabulous) {
return computeDelegate().getLayerModels(itemStack, fabulous);
}
}

View File

@ -40,7 +40,6 @@
"perf.parallelize_model_loading.SelectorMixin",
"perf.parallelize_model_loading.TransformationMatrixMixin",
"perf.parallelize_model_loading.BooleanPropertyMixin",
"perf.trim_model_caches.ModelManagerMixin",
"perf.async_jei.IngredientListElementFactoryMixin",
"perf.async_jei.ClientLifecycleHandlerMixin",
"perf.async_jei.JeiStarterMixin",