Rewrite faster_baking optimization, now defers baking to first use
This commit is contained in:
parent
ebafc9a0cc
commit
327813fa91
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
128
src/main/java/org/embeddedt/modernfix/models/LazyBakedModel.java
Normal file
128
src/main/java/org/embeddedt/modernfix/models/LazyBakedModel.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user