Animatica icon indicating copy to clipboard operation
Animatica copied to clipboard

Performance issue when using high resolution resource packs

Open FlashyReese opened this issue 2 years ago • 1 comments

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.

FlashyReese avatar Jul 24 '23 15:07 FlashyReese

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"
   ],

FlashyReese avatar Sep 19 '23 18:09 FlashyReese