SpriteContents.java

net.minecraft.client.renderer.texture.SpriteContents

信息

  • 全限定名:net.minecraft.client.renderer.texture.SpriteContents
  • 类型:public class
  • 包:net.minecraft.client.renderer.texture
  • 源码路径:src/main/java/net/minecraft/client/renderer/texture/SpriteContents.java
  • 起始行号:L47
  • 实现:AutoCloseable, Stitcher.Entry
  • 职责:

    TODO

字段/常量

  • LOGGER

    • 类型: Logger
    • 修饰符: private static final
    • 源码定位: L48
    • 说明:

      TODO

  • UBO_SIZE

    • 类型: int
    • 修饰符: public static final
    • 源码定位: L49
    • 说明:

      TODO

  • name

    • 类型: Identifier
    • 修饰符: private final
    • 源码定位: L50
    • 说明:

      TODO

  • width

    • 类型: int
    • 修饰符: private final
    • 源码定位: L51
    • 说明:

      TODO

  • height

    • 类型: int
    • 修饰符: private final
    • 源码定位: L52
    • 说明:

      TODO

  • originalImage

    • 类型: NativeImage
    • 修饰符: private final
    • 源码定位: L53
    • 说明:

      TODO

  • byMipLevel

    • 类型: NativeImage[]
    • 修饰符: private
    • 源码定位: L54
    • 说明:

      TODO

  • animatedTexture

    • 类型: SpriteContents.AnimatedTexture
    • 修饰符: private final
    • 源码定位: L55
    • 说明:

      TODO

  • additionalMetadata

    • 类型: List<MetadataSectionType.WithValue<?>>
    • 修饰符: private final
    • 源码定位: L56
    • 说明:

      TODO

  • mipmapStrategy

    • 类型: MipmapStrategy
    • 修饰符: private final
    • 源码定位: L57
    • 说明:

      TODO

  • alphaCutoffBias

    • 类型: float
    • 修饰符: private final
    • 源码定位: L58
    • 说明:

      TODO

  • transparency

    • 类型: Transparency
    • 修饰符: private final
    • 源码定位: L59
    • 说明:

      TODO

内部类/嵌套类型

  • net.minecraft.client.renderer.texture.SpriteContents.AnimatedTexture

    • 类型: class
    • 修饰符: private
    • 源码定位: L263
    • 说明:

      TODO

  • net.minecraft.client.renderer.texture.SpriteContents.AnimationState

    • 类型: class
    • 修饰符: public
    • 源码定位: L338
    • 说明:

      TODO

  • net.minecraft.client.renderer.texture.SpriteContents.FrameInfo

    • 类型: record
    • 修饰符: private
    • 源码定位: L410
    • 说明:

      TODO

构造器

public SpriteContents(Identifier name, FrameSize frameSize, NativeImage image) @ L61

  • 构造器名:SpriteContents
  • 源码定位:L61
  • 修饰符:public

参数:

  • name: Identifier
  • frameSize: FrameSize
  • image: NativeImage

说明:

TODO

public SpriteContents(Identifier name, FrameSize frameSize, NativeImage image, Optional<AnimationMetadataSection> animationInfo, List<MetadataSectionType.WithValue<?>> additionalMetadata, Optional<TextureMetadataSection> textureInfo) @ L65

  • 构造器名:SpriteContents
  • 源码定位:L65
  • 修饰符:public

参数:

  • name: Identifier
  • frameSize: FrameSize
  • image: NativeImage
  • animationInfo: Optional
  • additionalMetadata: List<MetadataSectionType.WithValue<?>>
  • textureInfo: Optional

说明:

TODO

方法

下面的方法块按源码顺序生成。

public void increaseMipLevel(int mipLevel) @ L88

  • 方法名:increaseMipLevel
  • 源码定位:L88
  • 返回类型:void
  • 修饰符:public

参数:

  • mipLevel: int

说明:

TODO

private int getFrameCount() @ L105

  • 方法名:getFrameCount
  • 源码定位:L105
  • 返回类型:int
  • 修饰符:private

参数:

说明:

TODO

public boolean isAnimated() @ L109

  • 方法名:isAnimated
  • 源码定位:L109
  • 返回类型:boolean
  • 修饰符:public

参数:

说明:

TODO

public Transparency transparency() @ L113

  • 方法名:transparency
  • 源码定位:L113
  • 返回类型:Transparency
  • 修饰符:public

参数:

说明:

TODO

private SpriteContents.AnimatedTexture createAnimatedTexture(FrameSize frameSize, int fullWidth, int fullHeight, AnimationMetadataSection metadata) @ L117

  • 方法名:createAnimatedTexture
  • 源码定位:L117
  • 返回类型:SpriteContents.AnimatedTexture
  • 修饰符:private

参数:

  • frameSize: FrameSize
  • fullWidth: int
  • fullHeight: int
  • metadata: AnimationMetadataSection

说明:

TODO

public int width() @ L171

  • 方法名:width
  • 源码定位:L171
  • 返回类型:int
  • 修饰符:public

参数:

说明:

TODO

public int height() @ L176

  • 方法名:height
  • 源码定位:L176
  • 返回类型:int
  • 修饰符:public

参数:

说明:

TODO

public Identifier name() @ L181

  • 方法名:name
  • 源码定位:L181
  • 返回类型:Identifier
  • 修饰符:public

参数:

说明:

TODO

public IntList getUniqueFrames() @ L186

  • 方法名:getUniqueFrames
  • 源码定位:L186
  • 返回类型:IntList
  • 修饰符:public

参数:

说明:

TODO

public SpriteContents.AnimationState createAnimationState(GpuBufferSlice uboSlice, int spriteUboSize) @ L190

  • 方法名:createAnimationState
  • 源码定位:L190
  • 返回类型:SpriteContents.AnimationState
  • 修饰符:public

参数:

  • uboSlice: GpuBufferSlice
  • spriteUboSize: int

说明:

TODO

public <T> Optional<T> getAdditionalMetadata(MetadataSectionType<T> type) @ L194

  • 方法名:getAdditionalMetadata
  • 源码定位:L194
  • 返回类型: Optional
  • 修饰符:public

参数:

  • type: MetadataSectionType

说明:

TODO

public void close() @ L205

  • 方法名:close
  • 源码定位:L205
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

public String toString() @ L212

  • 方法名:toString
  • 源码定位:L212
  • 返回类型:String
  • 修饰符:public

参数:

说明:

TODO

public boolean isTransparent(int frame, int x, int y) @ L217

  • 方法名:isTransparent
  • 源码定位:L217
  • 返回类型:boolean
  • 修饰符:public

参数:

  • frame: int
  • x: int
  • y: int

说明:

TODO

public Transparency computeTransparency(float u0, float v0, float u1, float v1) @ L228

  • 方法名:computeTransparency
  • 源码定位:L228
  • 返回类型:Transparency
  • 修饰符:public

参数:

  • u0: float
  • v0: float
  • u1: float
  • v1: float

说明:

TODO

public void uploadFirstFrame(GpuTexture destination, int level) @ L256

  • 方法名:uploadFirstFrame
  • 源码定位:L256
  • 返回类型:void
  • 修饰符:public

参数:

  • destination: GpuTexture
  • level: int

说明:

TODO

代码

@OnlyIn(Dist.CLIENT)
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 SpriteContents.@Nullable AnimatedTexture animatedTexture;
    private final List<MetadataSectionType.WithValue<?>> additionalMetadata;
    private final MipmapStrategy mipmapStrategy;
    private final float alphaCutoffBias;
    private final Transparency transparency;
 
    public SpriteContents(Identifier name, FrameSize frameSize, NativeImage image) {
        this(name, frameSize, image, Optional.empty(), List.of(), Optional.empty());
    }
 
    public SpriteContents(
        Identifier name,
        FrameSize frameSize,
        NativeImage image,
        Optional<AnimationMetadataSection> animationInfo,
        List<MetadataSectionType.WithValue<?>> additionalMetadata,
        Optional<TextureMetadataSection> textureInfo
    ) {
        this.name = name;
        this.width = frameSize.width();
        this.height = frameSize.height();
        this.additionalMetadata = additionalMetadata;
        this.animatedTexture = animationInfo.<SpriteContents.AnimatedTexture>map(
                animation -> this.createAnimatedTexture(frameSize, image.getWidth(), image.getHeight(), animation)
            )
            .orElse(null);
        this.originalImage = image;
        this.byMipLevel = new NativeImage[]{this.originalImage};
        this.mipmapStrategy = textureInfo.map(TextureMetadataSection::mipmapStrategy).orElse(MipmapStrategy.AUTO);
        this.alphaCutoffBias = textureInfo.map(TextureMetadataSection::alphaCutoffBias).orElse(0.0F);
        this.transparency = image.computeTransparency();
    }
 
    public void increaseMipLevel(int mipLevel) {
        try {
            this.byMipLevel = MipmapGenerator.generateMipLevels(
                this.name, this.byMipLevel, mipLevel, this.mipmapStrategy, this.alphaCutoffBias, this.transparency
            );
        } catch (Throwable var5) {
            CrashReport report = CrashReport.forThrowable(var5, "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;
    }
 
    public Transparency transparency() {
        return this.transparency;
    }
 
    private SpriteContents.@Nullable AnimatedTexture createAnimatedTexture(
        FrameSize frameSize, int fullWidth, int fullHeight, AnimationMetadataSection metadata
    ) {
        int frameRowSize = fullWidth / frameSize.width();
        int frameColumnSize = fullHeight / frameSize.height();
        int totalFrameCount = frameRowSize * frameColumnSize;
        int defaultFrameTime = metadata.defaultFrameTime();
        List<SpriteContents.FrameInfo> frames;
        if (metadata.frames().isEmpty()) {
            frames = new ArrayList<>(totalFrameCount);
 
            for (int i = 0; i < totalFrameCount; i++) {
                frames.add(new SpriteContents.FrameInfo(i, defaultFrameTime));
            }
        } else {
            List<AnimationFrame> metadataFrames = metadata.frames().get();
            frames = new ArrayList<>(metadataFrames.size());
 
            for (AnimationFrame frame : metadataFrames) {
                frames.add(new SpriteContents.FrameInfo(frame.index(), frame.timeOr(defaultFrameTime)));
            }
 
            int index = 0;
            IntSet usedFrameIndices = new IntOpenHashSet();
 
            for (Iterator<SpriteContents.FrameInfo> iterator = frames.iterator(); iterator.hasNext(); index++) {
                SpriteContents.FrameInfo frame = iterator.next();
                boolean isValid = true;
                if (frame.time <= 0) {
                    LOGGER.warn("Invalid frame duration on sprite {} frame {}: {}", this.name, index, frame.time);
                    isValid = false;
                }
 
                if (frame.index < 0 || frame.index >= totalFrameCount) {
                    LOGGER.warn("Invalid frame index on sprite {} frame {}: {}", this.name, index, frame.index);
                    isValid = false;
                }
 
                if (isValid) {
                    usedFrameIndices.add(frame.index);
                } else {
                    iterator.remove();
                }
            }
 
            int[] unusedFrameIndices = IntStream.range(0, totalFrameCount).filter(i -> !usedFrameIndices.contains(i)).toArray();
            if (unusedFrameIndices.length > 0) {
                LOGGER.warn("Unused frames in sprite {}: {}", this.name, Arrays.toString(unusedFrameIndices));
            }
        }
 
        return frames.size() <= 1 ? null : new SpriteContents.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 IntList getUniqueFrames() {
        return this.animatedTexture != null ? this.animatedTexture.getUniqueFrames() : IntList.of(1);
    }
 
    public SpriteContents.@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()) {
                return result;
            }
        }
 
        return Optional.empty();
    }
 
    @Override
    public void close() {
        for (NativeImage image : this.byMipLevel) {
            image.close();
        }
    }
 
    @Override
    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 actualX = x;
        int actualY = y;
        if (this.animatedTexture != null) {
            actualX = x + this.animatedTexture.getFrameX(frame) * this.width;
            actualY = y + this.animatedTexture.getFrameY(frame) * this.height;
        }
 
        return ARGB.alpha(this.originalImage.getPixel(actualX, actualY)) == 0;
    }
 
    public Transparency computeTransparency(float u0, float v0, float u1, float v1) {
        if (this.transparency.isOpaque()) {
            return this.transparency;
        } else if (u0 == 0.0F && v0 == 0.0F && u1 == 1.0F && v1 == 1.0F) {
            return this.transparency;
        } else {
            int x0 = Mth.floor(u0 * this.width);
            int y0 = Mth.floor(v0 * this.height);
            int x1 = Mth.ceil(u1 * this.width);
            int y1 = Mth.ceil(v1 * this.height);
            if (this.animatedTexture == null) {
                return this.originalImage.computeTransparency(x0, y0, x1, y1);
            } else {
                IntList uniqueFrames = this.animatedTexture.uniqueFrames;
                Transparency transparency = Transparency.NONE;
 
                for (int i = 0; i < uniqueFrames.size(); i++) {
                    int frame = uniqueFrames.getInt(i);
                    int frameX = this.animatedTexture.getFrameX(frame) * this.width;
                    int frameY = this.animatedTexture.getFrameY(frame) * this.height;
                    transparency = transparency.or(this.originalImage.computeTransparency(frameX + x0, frameY + y0, frameX + x1, frameY + y1));
                }
 
                return transparency;
            }
        }
    }
 
    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);
    }
 
    @OnlyIn(Dist.CLIENT)
    private class AnimatedTexture {
        private final List<SpriteContents.FrameInfo> frames;
        private final IntList uniqueFrames;
        private final int frameRowSize;
        private final boolean interpolateFrames;
 
        private AnimatedTexture(List<SpriteContents.FrameInfo> frames, int frameRowSize, boolean interpolateFrames) {
            Objects.requireNonNull(SpriteContents.this);
            super();
            this.frames = frames;
            this.frameRowSize = frameRowSize;
            this.interpolateFrames = interpolateFrames;
            this.uniqueFrames = IntArrayList.toList(frames.stream().mapToInt(SpriteContents.FrameInfo::index).distinct());
        }
 
        private int getFrameX(int index) {
            return index % this.frameRowSize;
        }
 
        private int getFrameY(int index) {
            return index / this.frameRowSize;
        }
 
        public SpriteContents.AnimationState createAnimationState(GpuBufferSlice uboSlice, int spriteUboSize) {
            GpuDevice device = RenderSystem.getDevice();
            Int2ObjectMap<GpuTextureView> frameTexturesByIndex = new Int2ObjectOpenHashMap<>();
            GpuBufferSlice[] spriteUbosByMip = new GpuBufferSlice[SpriteContents.this.byMipLevel.length];
 
            for (int i = 0; i < this.uniqueFrames.size(); i++) {
                int frame = this.uniqueFrames.getInt(i);
                GpuTexture texture = device.createTexture(
                    () -> SpriteContents.this.name + " animation frame " + frame,
                    5,
                    TextureFormat.RGBA8,
                    SpriteContents.this.width,
                    SpriteContents.this.height,
                    1,
                    SpriteContents.this.byMipLevel.length
                );
                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, RenderSystem.getDevice().createTextureView(texture));
            }
 
            for (int level = 0; level < SpriteContents.this.byMipLevel.length; level++) {
                spriteUbosByMip[level] = uboSlice.slice(level * spriteUboSize, spriteUboSize);
            }
 
            return SpriteContents.this.new AnimationState(this, frameTexturesByIndex, spriteUbosByMip);
        }
 
        public IntList getUniqueFrames() {
            return this.uniqueFrames;
        }
    }
 
    @OnlyIn(Dist.CLIENT)
    public class AnimationState implements AutoCloseable {
        private int frame;
        private int subFrame;
        private final SpriteContents.AnimatedTexture animationInfo;
        private final Int2ObjectMap<GpuTextureView> frameTexturesByIndex;
        private final GpuBufferSlice[] spriteUbosByMip;
        private boolean isDirty;
 
        private AnimationState(
            SpriteContents.AnimatedTexture animationInfo, Int2ObjectMap<GpuTextureView> frameTexturesByIndex, GpuBufferSlice[] spriteUbosByMip
        ) {
            Objects.requireNonNull(SpriteContents.this);
            super();
            this.isDirty = true;
            this.animationInfo = animationInfo;
            this.frameTexturesByIndex = frameTexturesByIndex;
            this.spriteUbosByMip = spriteUbosByMip;
        }
 
        public void tick() {
            this.subFrame++;
            this.isDirty = false;
            SpriteContents.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(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<SpriteContents.FrameInfo> frames = this.animationInfo.frames;
            int oldFrame = frames.get(this.frame).index;
            float frameProgress = (float)this.subFrame / this.animationInfo.frames.get(this.frame).time;
            int frameProgressAsInt = (int)(frameProgress * 1000.0F);
            if (this.animationInfo.interpolateFrames) {
                int newFrame = frames.get((this.frame + 1) % frames.size()).index;
                renderPass.setPipeline(RenderPipelines.ANIMATE_SPRITE_INTERPOLATE);
                renderPass.bindTexture("CurrentSprite", this.frameTexturesByIndex.get(oldFrame), sampler);
                renderPass.bindTexture("NextSprite", this.frameTexturesByIndex.get(newFrame), sampler);
            } else if (this.isDirty) {
                renderPass.setPipeline(RenderPipelines.ANIMATE_SPRITE_BLIT);
                renderPass.bindTexture("Sprite", 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();
            }
        }
    }
 
    @OnlyIn(Dist.CLIENT)
    private record FrameInfo(int index, int time) {
    }
}

引用的其他类