Animatica

Animatica

3M Downloads

Performance issue when using high resolution resource packs

FlashyReese opened this issue ยท 1 comments

commented

I have identified a performance issue while using Animatica in conjunction with the Hyper Realistic Sky resource pack, which contains high-resolution textures. Upon profiling, I noticed that a significant amount of time is being spent on blending and copying operations.

One possible solution I can think of is caching the interpolated frames(NativeImage) so they can be reused rather than recalculating the interpolation, considering that animations always utilize the same set of textures.

commented

I decided to dedicate some time to writing a caching patch. I've only tested this with Hyper Realistic Sky tornadoes, and it works fine. There are potential pitfalls in this approach. I believe the major one is the utilization of only 16 bits for the native image pointer when packing. Additionally, I'm concerned that I haven't packed enough information to differentiate other interpolation phases.

diff --git a/gradlew b/gradlew
old mode 100644
new mode 100755
diff --git a/src/main/java/io/github/foundationgames/animatica/animation/AnimatedTexture.java b/src/main/java/io/github/foundationgames/animatica/animation/AnimatedTexture.java
index 0d46e2e..ba9d13c 100644
--- a/src/main/java/io/github/foundationgames/animatica/animation/AnimatedTexture.java
+++ b/src/main/java/io/github/foundationgames/animatica/animation/AnimatedTexture.java
@@ -2,7 +2,9 @@ package io.github.foundationgames.animatica.animation;
 
 import com.google.common.collect.ImmutableList;
 import io.github.foundationgames.animatica.Animatica;
+import io.github.foundationgames.animatica.util.NativeImageExtended;
 import io.github.foundationgames.animatica.util.TextureUtil;
+import it.unimi.dsi.fastutil.longs.Long2ObjectArrayMap;
 import net.minecraft.client.texture.NativeImage;
 import net.minecraft.client.texture.NativeImageBackedTexture;
 import net.minecraft.resource.ResourceManager;
@@ -12,9 +14,12 @@ import net.minecraft.util.math.MathHelper;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 
 public class AnimatedTexture extends NativeImageBackedTexture {
+    private final Map<Long, NativeImage> imageCache = new Long2ObjectArrayMap<>();
+
     public final Animation[] anims;
     private final NativeImage original;
     private int frame = 0;
@@ -75,7 +80,20 @@ public class AnimatedTexture extends NativeImageBackedTexture {
             for (var anim : anims) {
                 phase = anim.getCurrentPhase();
                 if (phase instanceof InterpolatedPhase iPhase) {
-                    TextureUtil.blendCopy(anim.sourceTexture, 0, iPhase.prevV, 0, iPhase.v, anim.width, anim.height, image, anim.targetX, anim.targetY, iPhase.blend.getBlend(anim.getPhaseFrame()));
+                    // This is a bit of a hacky way to get the pointer of the source texture, but it works
+                    // we are only using the last 16 bits of the pointer, this might be a problem
+                    // iPhase.v shouldn't need more than 16 bits to be honest, but this is just to be safe if we can, we should allocate the rest of the bits to the pointer
+                    // PhaseFrame shouldn't need more than 16 bits as well, but again, just to be safe
+                    // The targetY is also unlikely to need more than 16 bits because of max texture size (typically 2^14 on modern gpus)
+                    long identifier = ((long) anim.getPhaseFrame()) << 48 | ((long) anim.targetY) << 32 | ((long) iPhase.v) << 16 | (((NativeImageExtended) (Object) anim.sourceTexture).getPointer());// this could be problematic but for testing purposes
+                    if (this.imageCache.containsKey(identifier)) {
+                        image.copyFrom(this.imageCache.get(identifier));
+                    } else {
+                        TextureUtil.blendCopy(anim.sourceTexture, 0, iPhase.prevV, 0, iPhase.v, anim.width, anim.height, image, anim.targetX, anim.targetY, iPhase.blend.getBlend(anim.getPhaseFrame()));
+                        NativeImage cache = new NativeImage(image.getFormat(), image.getWidth(), image.getHeight(), true);
+                        cache.copyFrom(image);
+                        this.imageCache.put(identifier, cache);
+                    }
                 } else {
                     TextureUtil.copy(anim.sourceTexture, 0, phase.v, anim.width, anim.height, image, anim.targetX, anim.targetY);
                 }
@@ -102,6 +120,9 @@ public class AnimatedTexture extends NativeImageBackedTexture {
             anim.close();
         }
 
+        this.imageCache.values().forEach(NativeImage::close);
+        this.imageCache.clear();
+
         this.original.close();
         super.close();
     }
diff --git a/src/main/java/io/github/foundationgames/animatica/mixin/NativeImageMixin.java b/src/main/java/io/github/foundationgames/animatica/mixin/NativeImageMixin.java
new file mode 100644
index 0000000..e5c525c
--- /dev/null
+++ b/src/main/java/io/github/foundationgames/animatica/mixin/NativeImageMixin.java
@@ -0,0 +1,17 @@
+package io.github.foundationgames.animatica.mixin;
+
+import io.github.foundationgames.animatica.util.NativeImageExtended;
+import net.minecraft.client.texture.NativeImage;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+
+@Mixin(NativeImage.class)
+public class NativeImageMixin implements NativeImageExtended {
+    @Shadow
+    private long pointer;
+
+    @Override
+    public long getPointer() {
+        return this.pointer;
+    }
+}
diff --git a/src/main/java/io/github/foundationgames/animatica/util/NativeImageExtended.java b/src/main/java/io/github/foundationgames/animatica/util/NativeImageExtended.java
new file mode 100644
index 0000000..0e70473
--- /dev/null
+++ b/src/main/java/io/github/foundationgames/animatica/util/NativeImageExtended.java
@@ -0,0 +1,5 @@
+package io.github.foundationgames.animatica.util;
+
+public interface NativeImageExtended {
+    long getPointer();
+}
diff --git a/src/main/resources/animatica.mixins.json b/src/main/resources/animatica.mixins.json
index 0d022d5..11725d9 100644
--- a/src/main/resources/animatica.mixins.json
+++ b/src/main/resources/animatica.mixins.json
@@ -5,6 +5,7 @@
   "compatibilityLevel": "JAVA_16",
   "client": [
     "RenderSystemMixin",
+    "NativeImageMixin",
     "IdentifierMixin",
     "VideoOptionsScreenMixin"
   ],