2025-11-24 22:52:51 +03:00

329 lines
15 KiB
Java

/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* com.mojang.logging.LogUtils
* it.unimi.dsi.fastutil.ints.Int2ObjectMap
* it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
* it.unimi.dsi.fastutil.ints.IntOpenHashSet
* it.unimi.dsi.fastutil.ints.IntSet
* org.jspecify.annotations.Nullable
* org.slf4j.Logger
*/
package net.minecraft.client.renderer.texture;
import com.mojang.blaze3d.buffers.GpuBufferSlice;
import com.mojang.blaze3d.buffers.Std140SizeCalculator;
import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.systems.GpuDevice;
import com.mojang.blaze3d.systems.RenderPass;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.textures.FilterMode;
import com.mojang.blaze3d.textures.GpuSampler;
import com.mojang.blaze3d.textures.GpuTexture;
import com.mojang.blaze3d.textures.GpuTextureView;
import com.mojang.blaze3d.textures.TextureFormat;
import com.mojang.logging.LogUtils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.stream.IntStream;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.client.renderer.texture.MipmapGenerator;
import net.minecraft.client.renderer.texture.MipmapStrategy;
import net.minecraft.client.renderer.texture.Stitcher;
import net.minecraft.client.resources.metadata.animation.AnimationFrame;
import net.minecraft.client.resources.metadata.animation.AnimationMetadataSection;
import net.minecraft.client.resources.metadata.animation.FrameSize;
import net.minecraft.resources.Identifier;
import net.minecraft.server.packs.metadata.MetadataSectionType;
import net.minecraft.util.ARGB;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
public class SpriteContents
implements AutoCloseable,
Stitcher.Entry {
private static final Logger LOGGER = LogUtils.getLogger();
public static final int UBO_SIZE = new Std140SizeCalculator().putMat4f().putMat4f().putFloat().putFloat().putInt().get();
private final Identifier name;
private final int width;
private final int height;
private final NativeImage originalImage;
private NativeImage[] byMipLevel;
private final @Nullable AnimatedTexture animatedTexture;
private final List<MetadataSectionType.WithValue<?>> additionalMetadata;
private final MipmapStrategy mipmapStrategy;
public SpriteContents(Identifier name, FrameSize frameSize, NativeImage image) {
this(name, frameSize, image, Optional.empty(), List.of(), MipmapStrategy.AUTO);
}
public SpriteContents(Identifier name, FrameSize frameSize, NativeImage image, Optional<AnimationMetadataSection> animationInfo, List<MetadataSectionType.WithValue<?>> additionalMetadata, MipmapStrategy mipmapStrategy) {
this.name = name;
this.width = frameSize.width();
this.height = frameSize.height();
this.additionalMetadata = additionalMetadata;
this.animatedTexture = animationInfo.map(animation -> this.createAnimatedTexture(frameSize, image.getWidth(), image.getHeight(), (AnimationMetadataSection)animation)).orElse(null);
this.originalImage = image;
this.byMipLevel = new NativeImage[]{this.originalImage};
this.mipmapStrategy = mipmapStrategy;
}
public void increaseMipLevel(int mipLevel) {
try {
this.byMipLevel = MipmapGenerator.generateMipLevels(this.name, this.byMipLevel, mipLevel, this.mipmapStrategy);
}
catch (Throwable t) {
CrashReport report = CrashReport.forThrowable(t, "Generating mipmaps for frame");
CrashReportCategory frameCategory = report.addCategory("Frame being iterated");
frameCategory.setDetail("Sprite name", this.name);
frameCategory.setDetail("Sprite size", () -> this.width + " x " + this.height);
frameCategory.setDetail("Sprite frames", () -> this.getFrameCount() + " frames");
frameCategory.setDetail("Mipmap levels", mipLevel);
frameCategory.setDetail("Original image size", () -> this.originalImage.getWidth() + "x" + this.originalImage.getHeight());
throw new ReportedException(report);
}
}
private int getFrameCount() {
return this.animatedTexture != null ? this.animatedTexture.frames.size() : 1;
}
public boolean isAnimated() {
return this.getFrameCount() > 1;
}
private @Nullable AnimatedTexture createAnimatedTexture(FrameSize frameSize, int fullWidth, int fullHeight, AnimationMetadataSection metadata) {
ArrayList<FrameInfo> frames;
int frameRowSize = fullWidth / frameSize.width();
int frameColumnSize = fullHeight / frameSize.height();
int totalFrameCount = frameRowSize * frameColumnSize;
int defaultFrameTime = metadata.defaultFrameTime();
if (metadata.frames().isEmpty()) {
frames = new ArrayList<FrameInfo>(totalFrameCount);
for (int i = 0; i < totalFrameCount; ++i) {
frames.add(new FrameInfo(i, defaultFrameTime));
}
} else {
List<AnimationFrame> metadataFrames = metadata.frames().get();
frames = new ArrayList(metadataFrames.size());
for (AnimationFrame frame : metadataFrames) {
frames.add(new FrameInfo(frame.index(), frame.timeOr(defaultFrameTime)));
}
int index = 0;
IntOpenHashSet usedFrameIndices = new IntOpenHashSet();
Iterator iterator = frames.iterator();
while (iterator.hasNext()) {
FrameInfo frame = (FrameInfo)iterator.next();
boolean isValid = true;
if (frame.time <= 0) {
LOGGER.warn("Invalid frame duration on sprite {} frame {}: {}", new Object[]{this.name, index, frame.time});
isValid = false;
}
if (frame.index < 0 || frame.index >= totalFrameCount) {
LOGGER.warn("Invalid frame index on sprite {} frame {}: {}", new Object[]{this.name, index, frame.index});
isValid = false;
}
if (isValid) {
usedFrameIndices.add(frame.index);
} else {
iterator.remove();
}
++index;
}
int[] unusedFrameIndices = IntStream.range(0, totalFrameCount).filter(arg_0 -> SpriteContents.lambda$createAnimatedTexture$4((IntSet)usedFrameIndices, arg_0)).toArray();
if (unusedFrameIndices.length > 0) {
LOGGER.warn("Unused frames in sprite {}: {}", (Object)this.name, (Object)Arrays.toString(unusedFrameIndices));
}
}
if (frames.size() <= 1) {
return null;
}
return new AnimatedTexture(List.copyOf(frames), frameRowSize, metadata.interpolatedFrames());
}
@Override
public int width() {
return this.width;
}
@Override
public int height() {
return this.height;
}
@Override
public Identifier name() {
return this.name;
}
public IntStream getUniqueFrames() {
return this.animatedTexture != null ? this.animatedTexture.getUniqueFrames() : IntStream.of(1);
}
public @Nullable AnimationState createAnimationState(GpuBufferSlice uboSlice, int spriteUboSize) {
return this.animatedTexture != null ? this.animatedTexture.createAnimationState(uboSlice, spriteUboSize) : null;
}
public <T> Optional<T> getAdditionalMetadata(MetadataSectionType<T> type) {
for (MetadataSectionType.WithValue<?> metadata : this.additionalMetadata) {
Optional<T> result = metadata.unwrapToType(type);
if (!result.isPresent()) continue;
return result;
}
return Optional.empty();
}
@Override
public void close() {
for (NativeImage image : this.byMipLevel) {
image.close();
}
}
public String toString() {
return "SpriteContents{name=" + String.valueOf(this.name) + ", frameCount=" + this.getFrameCount() + ", height=" + this.height + ", width=" + this.width + "}";
}
public boolean isTransparent(int frame, int x, int y) {
int actualX = x;
int actualY = y;
if (this.animatedTexture != null) {
actualX += this.animatedTexture.getFrameX(frame) * this.width;
actualY += this.animatedTexture.getFrameY(frame) * this.height;
}
return ARGB.alpha(this.originalImage.getPixel(actualX, actualY)) == 0;
}
public void uploadFirstFrame(GpuTexture destination, int level) {
RenderSystem.getDevice().createCommandEncoder().writeToTexture(destination, this.byMipLevel[level], level, 0, 0, 0, this.width >> level, this.height >> level, 0, 0);
}
private static /* synthetic */ boolean lambda$createAnimatedTexture$4(IntSet usedFrameIndices, int i) {
return !usedFrameIndices.contains(i);
}
private class AnimatedTexture {
private final List<FrameInfo> frames;
private final int frameRowSize;
private final boolean interpolateFrames;
private AnimatedTexture(List<FrameInfo> frames, int frameRowSize, boolean interpolateFrames) {
this.frames = frames;
this.frameRowSize = frameRowSize;
this.interpolateFrames = interpolateFrames;
}
private int getFrameX(int index) {
return index % this.frameRowSize;
}
private int getFrameY(int index) {
return index / this.frameRowSize;
}
public AnimationState createAnimationState(GpuBufferSlice uboSlice, int spriteUboSize) {
GpuDevice device = RenderSystem.getDevice();
Int2ObjectOpenHashMap frameTexturesByIndex = new Int2ObjectOpenHashMap();
GpuBufferSlice[] spriteUbosByMip = new GpuBufferSlice[SpriteContents.this.byMipLevel.length];
for (int frame : this.getUniqueFrames().toArray()) {
GpuTexture texture = device.createTexture(() -> String.valueOf(SpriteContents.this.name) + " animation frame " + frame, 5, TextureFormat.RGBA8, SpriteContents.this.width, SpriteContents.this.height, 1, SpriteContents.this.byMipLevel.length + 1);
int offsetX = this.getFrameX(frame) * SpriteContents.this.width;
int offsetY = this.getFrameY(frame) * SpriteContents.this.height;
for (int level = 0; level < SpriteContents.this.byMipLevel.length; ++level) {
RenderSystem.getDevice().createCommandEncoder().writeToTexture(texture, SpriteContents.this.byMipLevel[level], level, 0, 0, 0, SpriteContents.this.width >> level, SpriteContents.this.height >> level, offsetX >> level, offsetY >> level);
}
frameTexturesByIndex.put(frame, (Object)RenderSystem.getDevice().createTextureView(texture));
}
for (int level = 0; level < SpriteContents.this.byMipLevel.length; ++level) {
spriteUbosByMip[level] = uboSlice.slice(level * spriteUboSize, spriteUboSize);
}
return new AnimationState(SpriteContents.this, this, (Int2ObjectMap<GpuTextureView>)frameTexturesByIndex, spriteUbosByMip);
}
public IntStream getUniqueFrames() {
return this.frames.stream().mapToInt(f -> f.index).distinct();
}
}
private record FrameInfo(int index, int time) {
}
public class AnimationState
implements AutoCloseable {
private int frame;
private int subFrame;
private final AnimatedTexture animationInfo;
private final Int2ObjectMap<GpuTextureView> frameTexturesByIndex;
private final GpuBufferSlice[] spriteUbosByMip;
private boolean isDirty;
private AnimationState(SpriteContents this$0, AnimatedTexture animationInfo, Int2ObjectMap<GpuTextureView> frameTexturesByIndex, GpuBufferSlice[] spriteUbosByMip) {
this.animationInfo = animationInfo;
this.frameTexturesByIndex = frameTexturesByIndex;
this.spriteUbosByMip = spriteUbosByMip;
}
public void tick() {
++this.subFrame;
this.isDirty = false;
FrameInfo currentFrame = this.animationInfo.frames.get(this.frame);
if (this.subFrame >= currentFrame.time) {
int oldFrame = currentFrame.index;
this.frame = (this.frame + 1) % this.animationInfo.frames.size();
this.subFrame = 0;
int newFrame = this.animationInfo.frames.get((int)this.frame).index;
if (oldFrame != newFrame) {
this.isDirty = true;
}
}
}
public GpuBufferSlice getDrawUbo(int level) {
return this.spriteUbosByMip[level];
}
public boolean needsToDraw() {
return this.animationInfo.interpolateFrames || this.isDirty;
}
public void drawToAtlas(RenderPass renderPass, GpuBufferSlice ubo) {
GpuSampler sampler = RenderSystem.getSamplerCache().getClampToEdge(FilterMode.NEAREST, true);
List<FrameInfo> frames = this.animationInfo.frames;
int oldFrame = frames.get((int)this.frame).index;
float frameProgress = (float)this.subFrame / (float)this.animationInfo.frames.get((int)this.frame).time;
int frameProgressAsInt = (int)(frameProgress * 1000.0f);
if (this.animationInfo.interpolateFrames) {
int newFrame = frames.get((int)((this.frame + 1) % frames.size())).index;
renderPass.setPipeline(RenderPipelines.ANIMATE_SPRITE_INTERPOLATE);
renderPass.bindTexture("CurrentSprite", (GpuTextureView)this.frameTexturesByIndex.get(oldFrame), sampler);
renderPass.bindTexture("NextSprite", (GpuTextureView)this.frameTexturesByIndex.get(newFrame), sampler);
} else if (this.isDirty) {
renderPass.setPipeline(RenderPipelines.ANIMATE_SPRITE_BLIT);
renderPass.bindTexture("Sprite", (GpuTextureView)this.frameTexturesByIndex.get(oldFrame), sampler);
}
renderPass.setUniform("SpriteAnimationInfo", ubo);
renderPass.draw(frameProgressAsInt << 3, 6);
}
@Override
public void close() {
for (GpuTextureView view : this.frameTexturesByIndex.values()) {
view.texture().close();
view.close();
}
}
}
}