331 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			331 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| package net.minecraft.client.renderer.texture;
 | |
| 
 | |
| import com.mojang.blaze3d.platform.NativeImage;
 | |
| import com.mojang.blaze3d.systems.RenderSystem;
 | |
| import com.mojang.blaze3d.textures.GpuTexture;
 | |
| import com.mojang.logging.LogUtils;
 | |
| 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.stream.IntStream;
 | |
| 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.resources.metadata.animation.AnimationFrame;
 | |
| import net.minecraft.client.resources.metadata.animation.AnimationMetadataSection;
 | |
| import net.minecraft.client.resources.metadata.animation.FrameSize;
 | |
| import net.minecraft.resources.ResourceLocation;
 | |
| import net.minecraft.server.packs.resources.ResourceMetadata;
 | |
| import net.minecraft.util.ARGB;
 | |
| import org.jetbrains.annotations.Nullable;
 | |
| import org.slf4j.Logger;
 | |
| 
 | |
| @Environment(EnvType.CLIENT)
 | |
| public class SpriteContents implements Stitcher.Entry, AutoCloseable {
 | |
| 	private static final Logger LOGGER = LogUtils.getLogger();
 | |
| 	private final ResourceLocation name;
 | |
| 	final int width;
 | |
| 	final int height;
 | |
| 	private final NativeImage originalImage;
 | |
| 	NativeImage[] byMipLevel;
 | |
| 	@Nullable
 | |
| 	private final SpriteContents.AnimatedTexture animatedTexture;
 | |
| 	private final ResourceMetadata metadata;
 | |
| 
 | |
| 	public SpriteContents(ResourceLocation name, FrameSize frameSize, NativeImage originalImage, ResourceMetadata metadata) {
 | |
| 		this.name = name;
 | |
| 		this.width = frameSize.width();
 | |
| 		this.height = frameSize.height();
 | |
| 		this.metadata = metadata;
 | |
| 		this.animatedTexture = (SpriteContents.AnimatedTexture)metadata.getSection(AnimationMetadataSection.TYPE)
 | |
| 			.map(animationMetadataSection -> this.createAnimatedTexture(frameSize, originalImage.getWidth(), originalImage.getHeight(), animationMetadataSection))
 | |
| 			.orElse(null);
 | |
| 		this.originalImage = originalImage;
 | |
| 		this.byMipLevel = new NativeImage[]{this.originalImage};
 | |
| 	}
 | |
| 
 | |
| 	public void increaseMipLevel(int mipLevel) {
 | |
| 		try {
 | |
| 			this.byMipLevel = MipmapGenerator.generateMipLevels(this.byMipLevel, mipLevel);
 | |
| 		} catch (Throwable var6) {
 | |
| 			CrashReport crashReport = CrashReport.forThrowable(var6, "Generating mipmaps for frame");
 | |
| 			CrashReportCategory crashReportCategory = crashReport.addCategory("Sprite being mipmapped");
 | |
| 			crashReportCategory.setDetail("First frame", (CrashReportDetail<String>)(() -> {
 | |
| 				StringBuilder stringBuilder = new StringBuilder();
 | |
| 				if (stringBuilder.length() > 0) {
 | |
| 					stringBuilder.append(", ");
 | |
| 				}
 | |
| 
 | |
| 				stringBuilder.append(this.originalImage.getWidth()).append("x").append(this.originalImage.getHeight());
 | |
| 				return stringBuilder.toString();
 | |
| 			}));
 | |
| 			CrashReportCategory crashReportCategory2 = crashReport.addCategory("Frame being iterated");
 | |
| 			crashReportCategory2.setDetail("Sprite name", this.name);
 | |
| 			crashReportCategory2.setDetail("Sprite size", (CrashReportDetail<String>)(() -> this.width + " x " + this.height));
 | |
| 			crashReportCategory2.setDetail("Sprite frames", (CrashReportDetail<String>)(() -> this.getFrameCount() + " frames"));
 | |
| 			crashReportCategory2.setDetail("Mipmap levels", mipLevel);
 | |
| 			throw new ReportedException(crashReport);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private int getFrameCount() {
 | |
| 		return this.animatedTexture != null ? this.animatedTexture.frames.size() : 1;
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	private SpriteContents.AnimatedTexture createAnimatedTexture(FrameSize frameSize, int width, int height, AnimationMetadataSection metadata) {
 | |
| 		int i = width / frameSize.width();
 | |
| 		int j = height / frameSize.height();
 | |
| 		int k = i * j;
 | |
| 		int l = metadata.defaultFrameTime();
 | |
| 		List<SpriteContents.FrameInfo> list;
 | |
| 		if (metadata.frames().isEmpty()) {
 | |
| 			list = new ArrayList(k);
 | |
| 
 | |
| 			for (int m = 0; m < k; m++) {
 | |
| 				list.add(new SpriteContents.FrameInfo(m, l));
 | |
| 			}
 | |
| 		} else {
 | |
| 			List<AnimationFrame> list2 = (List<AnimationFrame>)metadata.frames().get();
 | |
| 			list = new ArrayList(list2.size());
 | |
| 
 | |
| 			for (AnimationFrame animationFrame : list2) {
 | |
| 				list.add(new SpriteContents.FrameInfo(animationFrame.index(), animationFrame.timeOr(l)));
 | |
| 			}
 | |
| 
 | |
| 			int n = 0;
 | |
| 			IntSet intSet = new IntOpenHashSet();
 | |
| 
 | |
| 			for (Iterator<SpriteContents.FrameInfo> iterator = list.iterator(); iterator.hasNext(); n++) {
 | |
| 				SpriteContents.FrameInfo frameInfo = (SpriteContents.FrameInfo)iterator.next();
 | |
| 				boolean bl = true;
 | |
| 				if (frameInfo.time <= 0) {
 | |
| 					LOGGER.warn("Invalid frame duration on sprite {} frame {}: {}", this.name, n, frameInfo.time);
 | |
| 					bl = false;
 | |
| 				}
 | |
| 
 | |
| 				if (frameInfo.index < 0 || frameInfo.index >= k) {
 | |
| 					LOGGER.warn("Invalid frame index on sprite {} frame {}: {}", this.name, n, frameInfo.index);
 | |
| 					bl = false;
 | |
| 				}
 | |
| 
 | |
| 				if (bl) {
 | |
| 					intSet.add(frameInfo.index);
 | |
| 				} else {
 | |
| 					iterator.remove();
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			int[] is = IntStream.range(0, k).filter(ix -> !intSet.contains(ix)).toArray();
 | |
| 			if (is.length > 0) {
 | |
| 				LOGGER.warn("Unused frames in sprite {}: {}", this.name, Arrays.toString(is));
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return list.size() <= 1 ? null : new SpriteContents.AnimatedTexture(List.copyOf(list), i, metadata.interpolatedFrames());
 | |
| 	}
 | |
| 
 | |
| 	void upload(int x, int y, int sourceX, int sourceY, NativeImage[] images, GpuTexture texture) {
 | |
| 		for (int i = 0; i < this.byMipLevel.length; i++) {
 | |
| 			RenderSystem.getDevice()
 | |
| 				.createCommandEncoder()
 | |
| 				.writeToTexture(texture, images[i], i, 0, x >> i, y >> i, this.width >> i, this.height >> i, sourceX >> i, sourceY >> i);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public int width() {
 | |
| 		return this.width;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public int height() {
 | |
| 		return this.height;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public ResourceLocation name() {
 | |
| 		return this.name;
 | |
| 	}
 | |
| 
 | |
| 	public IntStream getUniqueFrames() {
 | |
| 		return this.animatedTexture != null ? this.animatedTexture.getUniqueFrames() : IntStream.of(1);
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	public SpriteTicker createTicker() {
 | |
| 		return this.animatedTexture != null ? this.animatedTexture.createTicker() : null;
 | |
| 	}
 | |
| 
 | |
| 	public ResourceMetadata metadata() {
 | |
| 		return this.metadata;
 | |
| 	}
 | |
| 
 | |
| 	public void close() {
 | |
| 		for (NativeImage nativeImage : this.byMipLevel) {
 | |
| 			nativeImage.close();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public String toString() {
 | |
| 		return "SpriteContents{name=" + this.name + ", frameCount=" + this.getFrameCount() + ", height=" + this.height + ", width=" + this.width + "}";
 | |
| 	}
 | |
| 
 | |
| 	public boolean isTransparent(int frame, int x, int y) {
 | |
| 		int i = x;
 | |
| 		int j = y;
 | |
| 		if (this.animatedTexture != null) {
 | |
| 			i = x + this.animatedTexture.getFrameX(frame) * this.width;
 | |
| 			j = y + this.animatedTexture.getFrameY(frame) * this.height;
 | |
| 		}
 | |
| 
 | |
| 		return ARGB.alpha(this.originalImage.getPixel(i, j)) == 0;
 | |
| 	}
 | |
| 
 | |
| 	public void uploadFirstFrame(int x, int y, GpuTexture texture) {
 | |
| 		if (this.animatedTexture != null) {
 | |
| 			this.animatedTexture.uploadFirstFrame(x, y, texture);
 | |
| 		} else {
 | |
| 			this.upload(x, y, 0, 0, this.byMipLevel, texture);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Environment(EnvType.CLIENT)
 | |
| 	class AnimatedTexture {
 | |
| 		final List<SpriteContents.FrameInfo> frames;
 | |
| 		private final int frameRowSize;
 | |
| 		private final boolean interpolateFrames;
 | |
| 
 | |
| 		AnimatedTexture(final List<SpriteContents.FrameInfo> frames, final int frameRowSize, final boolean interpolateFrames) {
 | |
| 			this.frames = frames;
 | |
| 			this.frameRowSize = frameRowSize;
 | |
| 			this.interpolateFrames = interpolateFrames;
 | |
| 		}
 | |
| 
 | |
| 		int getFrameX(int frameIndex) {
 | |
| 			return frameIndex % this.frameRowSize;
 | |
| 		}
 | |
| 
 | |
| 		int getFrameY(int frameIndex) {
 | |
| 			return frameIndex / this.frameRowSize;
 | |
| 		}
 | |
| 
 | |
| 		void uploadFrame(int x, int y, int frameIndex, GpuTexture texture) {
 | |
| 			int i = this.getFrameX(frameIndex) * SpriteContents.this.width;
 | |
| 			int j = this.getFrameY(frameIndex) * SpriteContents.this.height;
 | |
| 			SpriteContents.this.upload(x, y, i, j, SpriteContents.this.byMipLevel, texture);
 | |
| 		}
 | |
| 
 | |
| 		public SpriteTicker createTicker() {
 | |
| 			return SpriteContents.this.new Ticker(this, this.interpolateFrames ? SpriteContents.this.new InterpolationData() : null);
 | |
| 		}
 | |
| 
 | |
| 		public void uploadFirstFrame(int x, int y, GpuTexture texture) {
 | |
| 			this.uploadFrame(x, y, ((SpriteContents.FrameInfo)this.frames.get(0)).index, texture);
 | |
| 		}
 | |
| 
 | |
| 		public IntStream getUniqueFrames() {
 | |
| 			return this.frames.stream().mapToInt(frameInfo -> frameInfo.index).distinct();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Environment(EnvType.CLIENT)
 | |
| 	record FrameInfo(int index, int time) {
 | |
| 	}
 | |
| 
 | |
| 	@Environment(EnvType.CLIENT)
 | |
| 	final class InterpolationData implements AutoCloseable {
 | |
| 		private final NativeImage[] activeFrame = new NativeImage[SpriteContents.this.byMipLevel.length];
 | |
| 
 | |
| 		InterpolationData() {
 | |
| 			for (int i = 0; i < this.activeFrame.length; i++) {
 | |
| 				int j = SpriteContents.this.width >> i;
 | |
| 				int k = SpriteContents.this.height >> i;
 | |
| 				this.activeFrame[i] = new NativeImage(j, k, false);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		void uploadInterpolatedFrame(int x, int y, SpriteContents.Ticker ticker, GpuTexture texture) {
 | |
| 			SpriteContents.AnimatedTexture animatedTexture = ticker.animationInfo;
 | |
| 			List<SpriteContents.FrameInfo> list = animatedTexture.frames;
 | |
| 			SpriteContents.FrameInfo frameInfo = (SpriteContents.FrameInfo)list.get(ticker.frame);
 | |
| 			float f = (float)ticker.subFrame / frameInfo.time;
 | |
| 			int i = frameInfo.index;
 | |
| 			int j = ((SpriteContents.FrameInfo)list.get((ticker.frame + 1) % list.size())).index;
 | |
| 			if (i != j) {
 | |
| 				for (int k = 0; k < this.activeFrame.length; k++) {
 | |
| 					int l = SpriteContents.this.width >> k;
 | |
| 					int m = SpriteContents.this.height >> k;
 | |
| 
 | |
| 					for (int n = 0; n < m; n++) {
 | |
| 						for (int o = 0; o < l; o++) {
 | |
| 							int p = this.getPixel(animatedTexture, i, k, o, n);
 | |
| 							int q = this.getPixel(animatedTexture, j, k, o, n);
 | |
| 							this.activeFrame[k].setPixel(o, n, ARGB.lerp(f, p, q));
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				SpriteContents.this.upload(x, y, 0, 0, this.activeFrame, texture);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		private int getPixel(SpriteContents.AnimatedTexture animatedTexture, int frameIndex, int mipLevel, int x, int y) {
 | |
| 			return SpriteContents.this.byMipLevel[mipLevel]
 | |
| 				.getPixel(
 | |
| 					x + (animatedTexture.getFrameX(frameIndex) * SpriteContents.this.width >> mipLevel),
 | |
| 					y + (animatedTexture.getFrameY(frameIndex) * SpriteContents.this.height >> mipLevel)
 | |
| 				);
 | |
| 		}
 | |
| 
 | |
| 		public void close() {
 | |
| 			for (NativeImage nativeImage : this.activeFrame) {
 | |
| 				nativeImage.close();
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Environment(EnvType.CLIENT)
 | |
| 	class Ticker implements SpriteTicker {
 | |
| 		int frame;
 | |
| 		int subFrame;
 | |
| 		final SpriteContents.AnimatedTexture animationInfo;
 | |
| 		@Nullable
 | |
| 		private final SpriteContents.InterpolationData interpolationData;
 | |
| 
 | |
| 		Ticker(final SpriteContents.AnimatedTexture animationInfo, @Nullable final SpriteContents.InterpolationData interpolationData) {
 | |
| 			this.animationInfo = animationInfo;
 | |
| 			this.interpolationData = interpolationData;
 | |
| 		}
 | |
| 
 | |
| 		@Override
 | |
| 		public void tickAndUpload(int x, int y, GpuTexture texture) {
 | |
| 			this.subFrame++;
 | |
| 			SpriteContents.FrameInfo frameInfo = (SpriteContents.FrameInfo)this.animationInfo.frames.get(this.frame);
 | |
| 			if (this.subFrame >= frameInfo.time) {
 | |
| 				int i = frameInfo.index;
 | |
| 				this.frame = (this.frame + 1) % this.animationInfo.frames.size();
 | |
| 				this.subFrame = 0;
 | |
| 				int j = ((SpriteContents.FrameInfo)this.animationInfo.frames.get(this.frame)).index;
 | |
| 				if (i != j) {
 | |
| 					this.animationInfo.uploadFrame(x, y, j, texture);
 | |
| 				}
 | |
| 			} else if (this.interpolationData != null) {
 | |
| 				this.interpolationData.uploadInterpolatedFrame(x, y, this, texture);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		@Override
 | |
| 		public void close() {
 | |
| 			if (this.interpolationData != null) {
 | |
| 				this.interpolationData.close();
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 |