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> 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 contents, int mipLevel, Executor executor) { SpriteLoader.Preparations var17; try (Zone zone = Profiler.get().zone((Supplier)(() -> "stitch " + this.location))) { int i = this.maxSupportedTextureSize; Stitcher 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 map = this.getStitchedSprites(stitcher, o, p); TextureAtlasSprite textureAtlasSprite = (TextureAtlasSprite)map.get(MissingTextureAtlasSprite.getLocation()); CompletableFuture 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> runSpriteSuppliers( SpriteResourceLoader spriteResourceLoader, List> factories, Executor executor ) { List> 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 loadAndStitch(ResourceManager resouceManager, ResourceLocation location, int mipLevel, Executor executor) { return this.loadAndStitch(resouceManager, location, mipLevel, executor, DEFAULT_METADATA_SECTIONS); } public CompletableFuture loadAndStitch( ResourceManager resourceManager, ResourceLocation location, int mipLevel, Executor executor, Collection> 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 getStitchedSprites(Stitcher stitcher, int x, int y) { Map 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 regions, CompletableFuture readyForUpload ) { public CompletableFuture waitForUpload() { return this.readyForUpload.thenApply(void_ -> this); } } }