minecraft-src/net/minecraft/client/renderer/texture/TextureManager.java
2025-07-04 01:41:11 +03:00

230 lines
7.9 KiB
Java

package net.minecraft.client.renderer.texture;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.logging.LogUtils;
import com.mojang.realmsclient.gui.screens.AddRealmPopupScreen;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.CrashReportDetail;
import net.minecraft.ReportedException;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.TitleScreen;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.PreparableReloadListener;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.util.profiling.ProfilerFiller;
import org.slf4j.Logger;
@Environment(EnvType.CLIENT)
public class TextureManager implements PreparableReloadListener, Tickable, AutoCloseable {
private static final Logger LOGGER = LogUtils.getLogger();
public static final ResourceLocation INTENTIONAL_MISSING_TEXTURE = ResourceLocation.withDefaultNamespace("");
private final Map<ResourceLocation, AbstractTexture> byPath = Maps.<ResourceLocation, AbstractTexture>newHashMap();
private final Set<Tickable> tickableTextures = Sets.<Tickable>newHashSet();
private final Map<String, Integer> prefixRegister = Maps.<String, Integer>newHashMap();
private final ResourceManager resourceManager;
public TextureManager(ResourceManager resourceManager) {
this.resourceManager = resourceManager;
}
public void bindForSetup(ResourceLocation path) {
if (!RenderSystem.isOnRenderThread()) {
RenderSystem.recordRenderCall(() -> this._bind(path));
} else {
this._bind(path);
}
}
private void _bind(ResourceLocation path) {
AbstractTexture abstractTexture = (AbstractTexture)this.byPath.get(path);
if (abstractTexture == null) {
abstractTexture = new SimpleTexture(path);
this.register(path, abstractTexture);
}
abstractTexture.bind();
}
public void register(ResourceLocation path, AbstractTexture texture) {
texture = this.loadTexture(path, texture);
AbstractTexture abstractTexture = (AbstractTexture)this.byPath.put(path, texture);
if (abstractTexture != texture) {
if (abstractTexture != null && abstractTexture != MissingTextureAtlasSprite.getTexture()) {
this.safeClose(path, abstractTexture);
}
if (texture instanceof Tickable) {
this.tickableTextures.add((Tickable)texture);
}
}
}
private void safeClose(ResourceLocation path, AbstractTexture texture) {
if (texture != MissingTextureAtlasSprite.getTexture()) {
this.tickableTextures.remove(texture);
try {
texture.close();
} catch (Exception var4) {
LOGGER.warn("Failed to close texture {}", path, var4);
}
}
texture.releaseId();
}
private AbstractTexture loadTexture(ResourceLocation path, AbstractTexture texture) {
try {
texture.load(this.resourceManager);
return texture;
} catch (IOException var6) {
if (path != INTENTIONAL_MISSING_TEXTURE) {
LOGGER.warn("Failed to load texture: {}", path, var6);
}
return MissingTextureAtlasSprite.getTexture();
} catch (Throwable var7) {
CrashReport crashReport = CrashReport.forThrowable(var7, "Registering texture");
CrashReportCategory crashReportCategory = crashReport.addCategory("Resource location being registered");
crashReportCategory.setDetail("Resource location", path);
crashReportCategory.setDetail("Texture object class", (CrashReportDetail<String>)(() -> texture.getClass().getName()));
throw new ReportedException(crashReport);
}
}
public AbstractTexture getTexture(ResourceLocation path) {
AbstractTexture abstractTexture = (AbstractTexture)this.byPath.get(path);
if (abstractTexture == null) {
abstractTexture = new SimpleTexture(path);
this.register(path, abstractTexture);
}
return abstractTexture;
}
public AbstractTexture getTexture(ResourceLocation path, AbstractTexture defaultTexture) {
return (AbstractTexture)this.byPath.getOrDefault(path, defaultTexture);
}
public ResourceLocation register(String name, DynamicTexture texture) {
Integer integer = (Integer)this.prefixRegister.get(name);
if (integer == null) {
integer = 1;
} else {
integer = integer + 1;
}
this.prefixRegister.put(name, integer);
ResourceLocation resourceLocation = ResourceLocation.withDefaultNamespace(String.format(Locale.ROOT, "dynamic/%s_%d", name, integer));
this.register(resourceLocation, texture);
return resourceLocation;
}
public CompletableFuture<Void> preload(ResourceLocation path, Executor backgroundExecutor) {
if (!this.byPath.containsKey(path)) {
PreloadedTexture preloadedTexture = new PreloadedTexture(this.resourceManager, path, backgroundExecutor);
this.byPath.put(path, preloadedTexture);
return preloadedTexture.getFuture().thenRunAsync(() -> this.register(path, preloadedTexture), TextureManager::execute);
} else {
return CompletableFuture.completedFuture(null);
}
}
private static void execute(Runnable runnable) {
Minecraft.getInstance().execute(() -> RenderSystem.recordRenderCall(runnable::run));
}
@Override
public void tick() {
for (Tickable tickable : this.tickableTextures) {
tickable.tick();
}
}
public void release(ResourceLocation path) {
AbstractTexture abstractTexture = (AbstractTexture)this.byPath.remove(path);
if (abstractTexture != null) {
this.safeClose(path, abstractTexture);
}
}
public void close() {
this.byPath.forEach(this::safeClose);
this.byPath.clear();
this.tickableTextures.clear();
this.prefixRegister.clear();
}
@Override
public CompletableFuture<Void> reload(
PreparableReloadListener.PreparationBarrier preparationBarrier,
ResourceManager resourceManager,
ProfilerFiller preparationsProfiler,
ProfilerFiller reloadProfiler,
Executor backgroundExecutor,
Executor gameExecutor
) {
CompletableFuture<Void> completableFuture = new CompletableFuture();
TitleScreen.preloadResources(this, backgroundExecutor).thenCompose(preparationBarrier::wait).thenAcceptAsync(void_ -> {
MissingTextureAtlasSprite.getTexture();
AddRealmPopupScreen.updateCarouselImages(this.resourceManager);
Iterator<Entry<ResourceLocation, AbstractTexture>> iterator = this.byPath.entrySet().iterator();
while (iterator.hasNext()) {
Entry<ResourceLocation, AbstractTexture> entry = (Entry<ResourceLocation, AbstractTexture>)iterator.next();
ResourceLocation resourceLocation = (ResourceLocation)entry.getKey();
AbstractTexture abstractTexture = (AbstractTexture)entry.getValue();
if (abstractTexture == MissingTextureAtlasSprite.getTexture() && !resourceLocation.equals(MissingTextureAtlasSprite.getLocation())) {
iterator.remove();
} else {
abstractTexture.reset(this, resourceManager, resourceLocation, gameExecutor);
}
}
Minecraft.getInstance().tell(() -> completableFuture.complete(null));
}, runnable -> RenderSystem.recordRenderCall(runnable::run));
return completableFuture;
}
public void dumpAllSheets(Path path) {
if (!RenderSystem.isOnRenderThread()) {
RenderSystem.recordRenderCall(() -> this._dumpAllSheets(path));
} else {
this._dumpAllSheets(path);
}
}
private void _dumpAllSheets(Path path) {
try {
Files.createDirectories(path);
} catch (IOException var3) {
LOGGER.error("Failed to create directory {}", path, var3);
return;
}
this.byPath.forEach((resourceLocation, abstractTexture) -> {
if (abstractTexture instanceof Dumpable dumpable) {
try {
dumpable.dumpContents(resourceLocation, path);
} catch (IOException var5) {
LOGGER.error("Failed to dump texture {}", resourceLocation, var5);
}
}
});
}
}