160 lines
6.5 KiB
Java
160 lines
6.5 KiB
Java
package net.minecraft.client.renderer.texture;
|
|
|
|
import com.mojang.logging.LogUtils;
|
|
import java.util.Collection;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.concurrent.Executor;
|
|
import java.util.function.Function;
|
|
import java.util.function.Supplier;
|
|
import java.util.stream.Collectors;
|
|
import net.fabricmc.api.EnvType;
|
|
import net.fabricmc.api.Environment;
|
|
import net.minecraft.CrashReport;
|
|
import net.minecraft.CrashReportCategory;
|
|
import net.minecraft.ReportedException;
|
|
import net.minecraft.Util;
|
|
import net.minecraft.client.renderer.texture.atlas.SpriteResourceLoader;
|
|
import net.minecraft.client.renderer.texture.atlas.SpriteSourceList;
|
|
import net.minecraft.client.resources.metadata.animation.AnimationMetadataSection;
|
|
import net.minecraft.resources.ResourceLocation;
|
|
import net.minecraft.server.packs.metadata.MetadataSectionType;
|
|
import net.minecraft.server.packs.resources.ResourceManager;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.util.profiling.Profiler;
|
|
import net.minecraft.util.profiling.Zone;
|
|
import org.slf4j.Logger;
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
public class SpriteLoader {
|
|
public static final Set<MetadataSectionType<?>> DEFAULT_METADATA_SECTIONS = Set.of(AnimationMetadataSection.TYPE);
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private final ResourceLocation location;
|
|
private final int maxSupportedTextureSize;
|
|
private final int minWidth;
|
|
private final int minHeight;
|
|
|
|
public SpriteLoader(ResourceLocation location, int maxSupportedTextureSize, int minWidth, int minHeight) {
|
|
this.location = location;
|
|
this.maxSupportedTextureSize = maxSupportedTextureSize;
|
|
this.minWidth = minWidth;
|
|
this.minHeight = minHeight;
|
|
}
|
|
|
|
public static SpriteLoader create(TextureAtlas atlas) {
|
|
return new SpriteLoader(atlas.location(), atlas.maxSupportedTextureSize(), atlas.getWidth(), atlas.getHeight());
|
|
}
|
|
|
|
public SpriteLoader.Preparations stitch(List<SpriteContents> contents, int mipLevel, Executor executor) {
|
|
SpriteLoader.Preparations var17;
|
|
try (Zone zone = Profiler.get().zone((Supplier<String>)(() -> "stitch " + this.location))) {
|
|
int i = this.maxSupportedTextureSize;
|
|
Stitcher<SpriteContents> stitcher = new Stitcher<>(i, i, mipLevel);
|
|
int j = Integer.MAX_VALUE;
|
|
int k = 1 << mipLevel;
|
|
|
|
for (SpriteContents spriteContents : contents) {
|
|
j = Math.min(j, Math.min(spriteContents.width(), spriteContents.height()));
|
|
int l = Math.min(Integer.lowestOneBit(spriteContents.width()), Integer.lowestOneBit(spriteContents.height()));
|
|
if (l < k) {
|
|
LOGGER.warn(
|
|
"Texture {} with size {}x{} limits mip level from {} to {}",
|
|
spriteContents.name(),
|
|
spriteContents.width(),
|
|
spriteContents.height(),
|
|
Mth.log2(k),
|
|
Mth.log2(l)
|
|
);
|
|
k = l;
|
|
}
|
|
|
|
stitcher.registerSprite(spriteContents);
|
|
}
|
|
|
|
int m = Math.min(j, k);
|
|
int n = Mth.log2(m);
|
|
int l;
|
|
if (n < mipLevel) {
|
|
LOGGER.warn("{}: dropping miplevel from {} to {}, because of minimum power of two: {}", this.location, mipLevel, n, m);
|
|
l = n;
|
|
} else {
|
|
l = mipLevel;
|
|
}
|
|
|
|
try {
|
|
stitcher.stitch();
|
|
} catch (StitcherException var19) {
|
|
CrashReport crashReport = CrashReport.forThrowable(var19, "Stitching");
|
|
CrashReportCategory crashReportCategory = crashReport.addCategory("Stitcher");
|
|
crashReportCategory.setDetail(
|
|
"Sprites",
|
|
var19.getAllSprites()
|
|
.stream()
|
|
.map(entry -> String.format(Locale.ROOT, "%s[%dx%d]", entry.name(), entry.width(), entry.height()))
|
|
.collect(Collectors.joining(","))
|
|
);
|
|
crashReportCategory.setDetail("Max Texture Size", i);
|
|
throw new ReportedException(crashReport);
|
|
}
|
|
|
|
int o = Math.max(stitcher.getWidth(), this.minWidth);
|
|
int p = Math.max(stitcher.getHeight(), this.minHeight);
|
|
Map<ResourceLocation, TextureAtlasSprite> map = this.getStitchedSprites(stitcher, o, p);
|
|
TextureAtlasSprite textureAtlasSprite = (TextureAtlasSprite)map.get(MissingTextureAtlasSprite.getLocation());
|
|
CompletableFuture<Void> completableFuture;
|
|
if (l > 0) {
|
|
completableFuture = CompletableFuture.runAsync(
|
|
() -> map.values().forEach(textureAtlasSpritex -> textureAtlasSpritex.contents().increaseMipLevel(l)), executor
|
|
);
|
|
} else {
|
|
completableFuture = CompletableFuture.completedFuture(null);
|
|
}
|
|
|
|
var17 = new SpriteLoader.Preparations(o, p, l, textureAtlasSprite, map, completableFuture);
|
|
}
|
|
|
|
return var17;
|
|
}
|
|
|
|
public static CompletableFuture<List<SpriteContents>> runSpriteSuppliers(
|
|
SpriteResourceLoader spriteResourceLoader, List<Function<SpriteResourceLoader, SpriteContents>> factories, Executor executor
|
|
) {
|
|
List<CompletableFuture<SpriteContents>> list = factories.stream()
|
|
.map(function -> CompletableFuture.supplyAsync(() -> (SpriteContents)function.apply(spriteResourceLoader), executor))
|
|
.toList();
|
|
return Util.sequence(list).thenApply(listx -> listx.stream().filter(Objects::nonNull).toList());
|
|
}
|
|
|
|
public CompletableFuture<SpriteLoader.Preparations> loadAndStitch(ResourceManager resouceManager, ResourceLocation location, int mipLevel, Executor executor) {
|
|
return this.loadAndStitch(resouceManager, location, mipLevel, executor, DEFAULT_METADATA_SECTIONS);
|
|
}
|
|
|
|
public CompletableFuture<SpriteLoader.Preparations> loadAndStitch(
|
|
ResourceManager resourceManager, ResourceLocation location, int mipLevel, Executor executor, Collection<MetadataSectionType<?>> sectionSerializers
|
|
) {
|
|
SpriteResourceLoader spriteResourceLoader = SpriteResourceLoader.create(sectionSerializers);
|
|
return CompletableFuture.supplyAsync(() -> SpriteSourceList.load(resourceManager, location).list(resourceManager), executor)
|
|
.thenCompose(list -> runSpriteSuppliers(spriteResourceLoader, list, executor))
|
|
.thenApply(list -> this.stitch(list, mipLevel, executor));
|
|
}
|
|
|
|
private Map<ResourceLocation, TextureAtlasSprite> getStitchedSprites(Stitcher<SpriteContents> stitcher, int x, int y) {
|
|
Map<ResourceLocation, TextureAtlasSprite> map = new HashMap();
|
|
stitcher.gatherSprites((spriteContents, k, l) -> map.put(spriteContents.name(), new TextureAtlasSprite(this.location, spriteContents, x, y, k, l)));
|
|
return map;
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
public record Preparations(
|
|
int width, int height, int mipLevel, TextureAtlasSprite missing, Map<ResourceLocation, TextureAtlasSprite> regions, CompletableFuture<Void> readyForUpload
|
|
) {
|
|
public CompletableFuture<SpriteLoader.Preparations> waitForUpload() {
|
|
return this.readyForUpload.thenApply(void_ -> this);
|
|
}
|
|
}
|
|
}
|