From 0ad6de76cb0302d1a11149b8409c0d908f707520 Mon Sep 17 00:00:00 2001
From: Barnaby <22575741+barnabwhy@users.noreply.github.com>
Date: Mon, 11 Nov 2024 17:04:22 +0000
Subject: [PATCH] Optimize custom map rendering (#11000)

---
 ...istance-check-in-MapPalette-by-remov.patch | 37 +++++++++
 ...-in-CraftMapCanvas.drawImage-by-limi.patch | 75 +++++++++++++++++++
 2 files changed, 112 insertions(+)
 create mode 100644 patches/api/Optimise-color-distance-check-in-MapPalette-by-remov.patch
 create mode 100644 patches/server/Reduce-work-done-in-CraftMapCanvas.drawImage-by-limi.patch

diff --git a/patches/api/Optimise-color-distance-check-in-MapPalette-by-remov.patch b/patches/api/Optimise-color-distance-check-in-MapPalette-by-remov.patch
new file mode 100644
index 0000000000..bffba7cf99
--- /dev/null
+++ b/patches/api/Optimise-color-distance-check-in-MapPalette-by-remov.patch
@@ -0,0 +1,37 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Barnaby <22575741+barnabwhy@users.noreply.github.com>
+Date: Sat, 29 Jun 2024 12:04:48 +0100
+Subject: [PATCH] Optimise color distance check in MapPalette by removing
+ floating point math
+
+
+diff --git a/src/main/java/org/bukkit/map/MapPalette.java b/src/main/java/org/bukkit/map/MapPalette.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/map/MapPalette.java
++++ b/src/main/java/org/bukkit/map/MapPalette.java
+@@ -0,0 +0,0 @@ public final class MapPalette {
+     }
+ 
+     private static double getDistance(@NotNull Color c1, @NotNull Color c2) {
+-        double rmean = (c1.getRed() + c2.getRed()) / 2.0;
+-        double r = c1.getRed() - c2.getRed();
+-        double g = c1.getGreen() - c2.getGreen();
++        // Paper start - Optimize color distance calculation by removing floating point math
++        int rsum = c1.getRed() + c2.getRed(); // Use sum instead of mean for no division
++        int r = c1.getRed() - c2.getRed();
++        int g = c1.getGreen() - c2.getGreen();
+         int b = c1.getBlue() - c2.getBlue();
+-        double weightR = 2 + rmean / 256.0;
+-        double weightG = 4.0;
+-        double weightB = 2 + (255 - rmean) / 256.0;
++        // All weights are 512x their original to avoid floating point division
++        int weightR = 1024 + rsum;
++        int weightG = 2048;
++        int weightB = 1024 + (255*2 - rsum);
++
++        // Division by 256 here is unnecessary as this won't change the result of the sort
+         return weightR * r * r + weightG * g * g + weightB * b * b;
++        // Paper end
+     }
+ 
+     @NotNull
diff --git a/patches/server/Reduce-work-done-in-CraftMapCanvas.drawImage-by-limi.patch b/patches/server/Reduce-work-done-in-CraftMapCanvas.drawImage-by-limi.patch
new file mode 100644
index 0000000000..b60eae83ae
--- /dev/null
+++ b/patches/server/Reduce-work-done-in-CraftMapCanvas.drawImage-by-limi.patch
@@ -0,0 +1,75 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Barnaby <22575741+barnabwhy@users.noreply.github.com>
+Date: Sat, 29 Jun 2024 12:06:51 +0100
+Subject: [PATCH] Reduce work done in CraftMapCanvas.drawImage by limiting size
+ of image and using System.arraycopy instead of for loops and use bitwise
+ operations to do bounds checks.
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/map/CraftMapCanvas.java b/src/main/java/org/bukkit/craftbukkit/map/CraftMapCanvas.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/craftbukkit/map/CraftMapCanvas.java
++++ b/src/main/java/org/bukkit/craftbukkit/map/CraftMapCanvas.java
+@@ -0,0 +0,0 @@ public class CraftMapCanvas implements MapCanvas {
+ 
+     @Override
+     public void drawImage(int x, int y, Image image) {
+-        byte[] bytes = MapPalette.imageToBytes(image);
+-        for (int x2 = 0; x2 < image.getWidth(null); ++x2) {
+-            for (int y2 = 0; y2 < image.getHeight(null); ++y2) {
+-                this.setPixel(x + x2, y + y2, bytes[y2 * image.getWidth(null) + x2]);
++        // Paper start - Reduce work done by limiting size of image and using System.arraycopy
++        int width = 128 - x;
++        int height = 128 - y;
++        if (image.getHeight(null) < height)
++            height = image.getHeight(null);
++
++        // Create a subimage if the image is larger than the max allowed size
++        java.awt.image.BufferedImage temp;
++        if (image.getWidth(null) >= width && image instanceof java.awt.image.BufferedImage bImage) {
++            // If the image is larger than the max allowed size, get a subimage, otherwise use the image as is
++            if (image.getWidth(null) > width || image.getHeight(null) > height) {
++                temp = bImage.getSubimage(0, 0, width, height);
++            } else {
++                temp = bImage;
+             }
++        } else {
++            temp = new java.awt.image.BufferedImage(width, height, java.awt.image.BufferedImage.TYPE_INT_ARGB);
++            java.awt.Graphics2D graphics = temp.createGraphics();
++            graphics.drawImage(image, 0, 0, null);
++            graphics.dispose();
+         }
++
++        byte[] bytes = MapPalette.imageToBytes(temp);
++        
++        // Since we now control the size of the image, we can safely use System.arraycopy
++        // If x is 0, we can just copy the entire image as width is 128 and height is <=(128-y)
++        if (x == 0) {
++            System.arraycopy(bytes, 0, this.buffer, y * 128, width * height);
++            return;
++        }
++
++        for (int y2 = 0; y2 < height; ++y2) {
++            System.arraycopy(bytes, 0, this.buffer, (y + y2) * 128 + x, width);
++        }
++        // Paper end
+     }
+ 
+     @Override
+diff --git a/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java b/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java
++++ b/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java
+@@ -0,0 +0,0 @@ public class CraftMapRenderer extends MapRenderer {
+     @Override
+     public void render(MapView map, MapCanvas canvas, Player player) {
+         // Map
+-        for (int x = 0; x < 128; ++x) {
+-            for (int y = 0; y < 128; ++y) {
++        // Paper start - Swap inner and outer loops here to (theoretically) improve cache locality
++        for (int y = 0; y < 128; ++y) {
++            for (int x = 0; x < 128; ++x) {
++        // Paper end
+                 canvas.setPixel(x, y, this.worldMap.colors[y * 128 + x]);
+             }
+         }