More tweaks to model loading

This commit is contained in:
embeddedt 2023-02-04 12:43:38 -05:00
parent aead8ef90f
commit e9a6a3d194
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
6 changed files with 148 additions and 27 deletions

View File

@ -18,7 +18,7 @@ plugins {
apply plugin: 'org.spongepowered.mixin'
group = 'org.embeddedt'
version = '1.5.2'
version = '1.6.0-beta1'
java {
archivesBaseName = 'modernfix-mc' + minecraft_version
@ -137,7 +137,6 @@ dependencies {
if(include_optimization_mods.toBoolean()) {
runtimeOnly fg.deobf("curse.maven:roadrunner-529754:3683120")
runtimeOnly fg.deobf("curse.maven:starlight-529754:3683120")
runtimeOnly fg.deobf("curse.maven:rubidium-574856:3949659")
runtimeOnly fg.deobf("curse.maven:noexperimental-407174:3188120")
runtimeOnly fg.deobf("curse.maven:spark-361579:3767277")

View File

@ -2,21 +2,29 @@ package org.embeddedt.modernfix.mixin.perf.parallelize_model_loading;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Sets;
import com.mojang.datafixers.util.Pair;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BlockModelShapes;
import net.minecraft.client.renderer.model.*;
import net.minecraft.client.renderer.texture.SpriteMap;
import net.minecraft.profiler.IProfiler;
import net.minecraft.resources.IResource;
import net.minecraft.resources.IResourceManager;
import net.minecraft.state.StateContainer;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.Util;
import net.minecraft.util.math.vector.TransformationMatrix;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import net.minecraft.util.registry.Registry;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.util.AsyncStopwatch;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@ -24,6 +32,9 @@ import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
@ -44,7 +55,9 @@ public abstract class ModelBakeryMixin {
@Shadow public abstract IUnbakedModel getModel(ResourceLocation modelLocation);
@Shadow @Final private Map<ResourceLocation, IUnbakedModel> topLevelModels;
@Shadow @Final protected IResourceManager resourceManager;
private Map<ResourceLocation, BlockModel> deserializedModelCache = null;
private Map<ResourceLocation, List<Pair<String, BlockModelDefinition>>> deserializedBlockstateCache = null;
private boolean useModelCache = false;
@Inject(method = "loadBlockModel", at = @At("HEAD"), cancellable = true)
@ -65,34 +78,74 @@ public abstract class ModelBakeryMixin {
}
}
@Inject(method = "processLoading", at = @At(value = "INVOKE", target = "Lnet/minecraft/profiler/IProfiler;popPush(Ljava/lang/String;)V", ordinal = 1))
@Inject(method = "processLoading", at = @At(value = "INVOKE", target = "Lnet/minecraft/profiler/IProfiler;popPush(Ljava/lang/String;)V", ordinal = 0))
private void preloadJsonModels(IProfiler profilerIn, int maxMipmapLevel, CallbackInfo ci) {
profilerIn.popPush("loadjsons");
ModernFix.LOGGER.warn("Preloading JSONs in parallel...");
Stopwatch stopwatch = Stopwatch.createStarted();
useModelCache = false;
deserializedModelCache = Minecraft.getInstance().getResourceManager().listResources("models", p -> {
if(!p.endsWith(".json"))
return false;
for(int i = 0; i < p.length(); i++) {
if(!ResourceLocation.validPathChar(p.charAt(i)))
return false;
}
return true;
})
.parallelStream()
.map(location -> new ResourceLocation(location.getNamespace(), location.getPath().substring(7, location.getPath().length() - 5)))
.map(location -> Pair.of(location, this.loadModelSafely(location)))
.filter(pair -> pair.getValue() != null)
.collect(Collectors.toConcurrentMap(Pair::getKey, Pair::getValue));
useModelCache = true;
ModernFix.LOGGER.warn("Preloading JSONs took " + stopwatch.elapsed(TimeUnit.MILLISECONDS)/1000f + " seconds");
stopwatch.stop();
AsyncStopwatch.measureAndLogSerialRunningTime("Parallel JSON loading", () -> {
useModelCache = false;
deserializedModelCache = Minecraft.getInstance().getResourceManager().listResources("models", p -> {
if(!p.endsWith(".json"))
return false;
for(int i = 0; i < p.length(); i++) {
if(!ResourceLocation.validPathChar(p.charAt(i)))
return false;
}
return true;
})
.parallelStream()
.map(location -> new ResourceLocation(location.getNamespace(), location.getPath().substring(7, location.getPath().length() - 5)))
.map(location -> Pair.of(location, this.loadModelSafely(location)))
.filter(pair -> pair.getSecond() != null)
.collect(Collectors.toConcurrentMap(Pair::getFirst, Pair::getSecond));
useModelCache = true;
});
AsyncStopwatch.measureAndLogSerialRunningTime("Parallel blockstate loading", () -> {
ThreadLocal<BlockModelDefinition.ContainerHolder> containerHolder = ThreadLocal.withInitial(BlockModelDefinition.ContainerHolder::new);
this.deserializedBlockstateCache = Registry.BLOCK.keySet().parallelStream()
.flatMap(block -> {
ResourceLocation blockStateJSON = new ResourceLocation(block.getNamespace(), "blockstates/" + block.getPath() + ".json");
List<IResource> blockStates;
try {
blockStates = this.resourceManager.getResources(blockStateJSON);
} catch(IOException e) {
ModernFix.LOGGER.warn("Exception loading blockstate definition: {}: {}", block, e);
blockStates = Collections.emptyList();
}
return blockStates.stream().map(resource -> Pair.of(block, resource));
})
.map((pair) -> {
ResourceLocation block = pair.getFirst();
IResource resource = pair.getSecond();
try (InputStream inputstream = resource.getInputStream()) {
BlockModelDefinition.ContainerHolder context = containerHolder.get();
context.setDefinition(Registry.BLOCK.get(block).getStateDefinition());
return Pair.of(block, Pair.of(resource.getSourceName(), BlockModelDefinition.fromStream(context, new InputStreamReader(inputstream, StandardCharsets.UTF_8))));
} catch (Exception exception1) {
ModernFix.LOGGER.warn(String.format("Exception loading blockstate definition: '%s' in resourcepack: '%s': %s", resource.getLocation(), resource.getSourceName(), exception1.getMessage()));
return Pair.of(block, Pair.of((String)null, (BlockModelDefinition)null));
}
})
.filter(pair -> pair.getSecond().getSecond() != null)
.collect(Collectors.groupingBy(Pair::getFirst, Collectors.mapping(Pair::getSecond, Collectors.toList())));
});
AsyncStopwatch.measureAndLogSerialRunningTime("Predicate generation", () -> {
/* Pregenerate predicates */
this.deserializedBlockstateCache.entrySet().parallelStream()
.flatMap(entry -> entry.getValue().stream()
.map(Pair::getSecond)
.map(def -> Pair.of(Registry.BLOCK.get(entry.getKey()).getStateDefinition(), def)))
.filter(pair -> pair.getSecond().isMultiPart())
.flatMap(pair -> pair.getSecond().getMultiPart().getSelectors().stream().map(selector -> Pair.of(pair.getFirst(), selector)))
.forEach(pair -> {
pair.getSecond().getPredicate(pair.getFirst());
});
});
}
@Inject(method = "processLoading", at = @At("RETURN"), remap = false)
private void clearModelCache(IProfiler profilerIn, int maxMipmapLevel, CallbackInfo ci) {
deserializedModelCache.clear();
deserializedBlockstateCache.clear();
useModelCache = false;
}
@ -111,7 +164,7 @@ public abstract class ModelBakeryMixin {
}
return candidate;
};
Set<com.mojang.datafixers.util.Pair<String, String>> set = Collections.synchronizedSet(Sets.newLinkedHashSet());
Set<Pair<String, String>> set = Collections.synchronizedSet(Sets.newLinkedHashSet());
String modelMissingString = MISSING_MODEL_LOCATION.toString();
Set<RenderMaterial> materials = this.topLevelModels.values().parallelStream().flatMap((unbaked) -> {
return unbaked.getMaterials(safeUnbakedGetter, set).stream();
@ -125,4 +178,29 @@ public abstract class ModelBakeryMixin {
stopwatch.stop();
return materials;
}
private List<?> replacementList = null;
@Redirect(method = "loadModel", at = @At(value = "INVOKE", target = "Lnet/minecraft/resources/IResourceManager;getResources(Lnet/minecraft/util/ResourceLocation;)Ljava/util/List;", ordinal = 0))
private List<?> getResourceList(IResourceManager instance, ResourceLocation jsonLocation, ResourceLocation originalBlockLocation) throws IOException {
replacementList = null;
if(this.deserializedBlockstateCache != null) {
if(!(originalBlockLocation instanceof ModelResourceLocation)) {
throw new AssertionError("Injector in unexpected spot?");
}
ModelResourceLocation mrl = (ModelResourceLocation)originalBlockLocation;
ResourceLocation location = new ResourceLocation(mrl.getNamespace(), mrl.getPath());
List<?> theList = this.deserializedBlockstateCache.get(location);
if(theList != null && theList.size() > 0) {
replacementList = theList;
return theList;
}
}
return instance.getResources(jsonLocation);
}
@Redirect(method = "loadModel", at = @At(value = "INVOKE", target = "Ljava/util/stream/Stream;map(Ljava/util/function/Function;)Ljava/util/stream/Stream;", ordinal = 0))
private Stream<?> fakeResourceList(Stream<?> instance, Function function) {
return replacementList != null ? instance : instance.map(function);
}
}

View File

@ -0,0 +1,30 @@
package org.embeddedt.modernfix.mixin.perf.parallelize_model_loading;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.model.multipart.Selector;
import net.minecraft.state.StateContainer;
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.CallbackInfoReturnable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
@Mixin(Selector.class)
public class SelectorMixin {
private ConcurrentHashMap<StateContainer<Block, BlockState>, Predicate<BlockState>> predicateCache = new ConcurrentHashMap<>();
@Inject(method = "getPredicate", at = @At("HEAD"), cancellable = true)
private void useCachedPredicate(StateContainer<Block, BlockState> pState, CallbackInfoReturnable<Predicate<BlockState>> cir) {
Predicate<BlockState> cached = this.predicateCache.get(pState);
if(cached != null)
cir.setReturnValue(cached);
}
@Inject(method = "getPredicate", at = @At("RETURN"))
private void storeCachedPredicate(StateContainer<Block, BlockState> pState, CallbackInfoReturnable<Predicate<BlockState>> cir) {
this.predicateCache.put(pState, cir.getReturnValue());
}
}

View File

@ -1,6 +1,7 @@
package org.embeddedt.modernfix.util;
import com.google.common.base.Stopwatch;
import org.embeddedt.modernfix.ModernFix;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@ -30,4 +31,15 @@ public class AsyncStopwatch {
public long getCpuTime() {
return cpuTimeMs.get();
}
public static void measureAndLogSerialRunningTime(String label, Runnable runnable) {
ModernFix.LOGGER.info(label + "...");
Stopwatch stopwatch = Stopwatch.createStarted();
try {
runnable.run();
ModernFix.LOGGER.info(label + " took " + stopwatch.elapsed(TimeUnit.MILLISECONDS)/1000f + " seconds");
} finally {
stopwatch.stop();
}
}
}

View File

@ -2,4 +2,5 @@ public net.minecraft.client.Minecraft$WorldSelectionType
public net.minecraft.client.renderer.RenderType$Type
public net.minecraft.client.renderer.RenderType$Type <init>(Ljava/lang/String;Lnet/minecraft/client/renderer/vertex/VertexFormat;IIZZLnet/minecraft/client/renderer/RenderType$State;)V
public net.minecraft.block.AbstractBlock$AbstractBlockState$Cache
public net.minecraft.util.math.shapes.VoxelShape <init>(Lnet/minecraft/util/math/shapes/VoxelShapePart;)V # <init>
public net.minecraft.util.math.shapes.VoxelShape <init>(Lnet/minecraft/util/math/shapes/VoxelShapePart;)V # <init>
public net.minecraft.client.renderer.model.ModelBakery$BlockStateDefinitionException

View File

@ -36,6 +36,7 @@
"perf.parallelize_model_loading.OBJLoaderMixin",
"perf.parallelize_model_loading.multipart.MultipartMixin",
"perf.parallelize_model_loading.multipart.VariantListMixin",
"perf.parallelize_model_loading.SelectorMixin",
"perf.trim_model_caches.ModelManagerMixin",
"perf.async_jei.IngredientListElementFactoryMixin",
"perf.async_jei.ClientLifecycleHandlerMixin",