diff --git a/build.gradle b/build.gradle index 205bdbf19..29a5b2d51 100644 --- a/build.gradle +++ b/build.gradle @@ -80,7 +80,8 @@ def jvm_arguments = [ def compiler_jvm_arguments = [ '--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED', - '--add-exports=java.base/jdk.internal.reflect=ALL-UNNAMED' + '--add-exports=java.base/jdk.internal.reflect=ALL-UNNAMED', + '--add-modules=jdk.incubator.vector' // CatRoom - SIMD support ] // Projects diff --git a/patches/minecraft/net/minecraft/server/dedicated/DedicatedServer.java.patch b/patches/minecraft/net/minecraft/server/dedicated/DedicatedServer.java.patch index 2141076f5..a119c40c7 100644 --- a/patches/minecraft/net/minecraft/server/dedicated/DedicatedServer.java.patch +++ b/patches/minecraft/net/minecraft/server/dedicated/DedicatedServer.java.patch @@ -147,7 +147,19 @@ this.field_154332_n = new ServerEula(new File("eula.txt")); if (!this.field_154332_n.func_154346_a()) -@@ -163,17 +202,17 @@ +@@ -152,6 +191,11 @@ + this.func_71189_e(this.field_71340_o.func_73671_a("server-ip", "")); + } + ++ // CatRoom start - SIMD support ++ try { ++ gg.pufferfish.pufferfish.simd.SIMDDetection.initialize(); ++ } catch (Throwable ignored) {} ++ // CatRoom end - SIMD support + this.func_71251_e(this.field_71340_o.func_73670_a("spawn-animals", true)); + this.func_71257_f(this.field_71340_o.func_73670_a("spawn-npcs", true)); + this.func_71188_g(this.field_71340_o.func_73670_a("pvp", true)); +@@ -163,17 +207,17 @@ if (this.field_71340_o.func_73669_a("difficulty", 1) < 0) { @@ -168,7 +180,7 @@ InetAddress inetaddress = null; if (!this.func_71211_k().isEmpty()) -@@ -186,29 +225,42 @@ +@@ -186,29 +230,42 @@ this.func_71208_b(this.field_71340_o.func_73669_a("server-port", 25565)); } @@ -227,7 +239,7 @@ field_155771_h.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file."); } -@@ -223,7 +275,9 @@ +@@ -223,7 +280,9 @@ } else { @@ -238,7 +250,7 @@ long j = System.nanoTime(); if (this.func_71270_I() == null) -@@ -234,7 +288,7 @@ +@@ -234,7 +293,7 @@ String s = this.field_71340_o.func_73671_a("level-seed", ""); String s1 = this.field_71340_o.func_73671_a("level-type", "DEFAULT"); String s2 = this.field_71340_o.func_73671_a("generator-settings", ""); @@ -247,7 +259,7 @@ if (!s.isEmpty()) { -@@ -247,7 +301,7 @@ +@@ -247,7 +306,7 @@ k = l; } } @@ -256,7 +268,7 @@ { k = (long)s.hashCode(); } -@@ -267,21 +321,21 @@ +@@ -267,21 +326,21 @@ this.func_71191_d(this.field_71340_o.func_73669_a("max-build-height", 256)); this.func_71191_d((this.func_71207_Z() + 8) / 16 * 16); this.func_71191_d(MathHelper.func_76125_a(this.func_71207_Z(), 64, 256)); @@ -285,7 +297,7 @@ this.field_71340_o.func_187238_b("announce-player-achievements"); this.field_71340_o.func_73668_b(); } -@@ -293,14 +347,33 @@ +@@ -293,14 +352,33 @@ this.field_71342_m.func_72602_a(); } @@ -324,7 +336,7 @@ { Thread thread1 = new Thread(new ServerHangWatchdog(this)); thread1.setName("Server Watchdog"); -@@ -309,7 +382,8 @@ +@@ -309,7 +387,8 @@ } Items.field_190931_a.func_150895_a(CreativeTabs.field_78027_g, NonNullList.func_191196_a()); @@ -334,7 +346,7 @@ } } } -@@ -339,46 +413,38 @@ +@@ -339,46 +418,38 @@ if (!this.field_71340_o.func_73671_a("resource-pack", "").isEmpty() && s.isEmpty()) { @@ -382,7 +394,7 @@ public CrashReport func_71230_b(CrashReport p_71230_1_) { p_71230_1_ = super.func_71230_b(p_71230_1_); -@@ -400,40 +466,34 @@ +@@ -400,40 +471,34 @@ return p_71230_1_; } @@ -425,7 +437,7 @@ public boolean func_70002_Q() { return this.field_71340_o.func_73670_a("snooper-enabled", true); -@@ -446,20 +506,39 @@ +@@ -446,20 +511,39 @@ public void func_71333_ah() { @@ -469,7 +481,7 @@ public boolean func_181035_ah() { return this.field_71340_o.func_73670_a("use-native-transport", true); -@@ -470,13 +549,11 @@ +@@ -470,13 +554,11 @@ return (DedicatedPlayerList)super.func_184103_al(); } @@ -483,7 +495,7 @@ public String func_71330_a(String p_71330_1_, String p_71330_2_) { return this.field_71340_o.func_73671_a(p_71330_1_, p_71330_2_); -@@ -487,38 +564,32 @@ +@@ -487,38 +569,32 @@ return this.field_71340_o.func_73670_a(p_71332_1_, p_71332_2_); } @@ -522,7 +534,7 @@ public String func_71274_v() { return this.func_71273_Y(); -@@ -530,34 +601,29 @@ +@@ -530,34 +606,29 @@ this.field_71335_s = true; } @@ -558,7 +570,7 @@ { return false; } -@@ -583,33 +649,28 @@ +@@ -583,33 +654,28 @@ } } @@ -593,7 +605,7 @@ public int func_175580_aG() { int i = this.field_71340_o.func_73669_a("max-world-size", super.func_175580_aG()); -@@ -626,12 +687,14 @@ +@@ -626,12 +692,14 @@ return i; } @@ -609,7 +621,7 @@ protected boolean func_152368_aE() throws IOException { boolean flag = false; -@@ -708,8 +771,9 @@ +@@ -708,8 +776,9 @@ { Thread.sleep(5000L); } @@ -620,7 +632,7 @@ } } -@@ -718,17 +782,68 @@ +@@ -718,17 +787,68 @@ return this.field_71340_o.func_179885_a("max-tick-time", TimeUnit.MINUTES.toMillis(1L)); } diff --git a/src/main/java/gg/pufferfish/pufferfish/simd/SIMDChecker.java b/src/main/java/gg/pufferfish/pufferfish/simd/SIMDChecker.java new file mode 100644 index 000000000..ba62b8847 --- /dev/null +++ b/src/main/java/gg/pufferfish/pufferfish/simd/SIMDChecker.java @@ -0,0 +1,45 @@ +package gg.pufferfish.pufferfish.simd; + +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.IntVector; +import jdk.incubator.vector.VectorSpecies; + +/** + * Basically, java is annoying and we have to push this out to its own class. + */ +public class SIMDChecker { + + public static void initialize() { + if (SIMDDetection.isInitialized()) { + return; + } + SIMDDetection.setInitialized(); + try { + int javaVersion = SIMDDetection.getJavaVersion(); + if (javaVersion < 17) { + return; + } + SIMDDetection.supportingJavaVersion = true; + SIMDDetection.testRunStarted = true; + + VectorSpecies ISPEC = IntVector.SPECIES_PREFERRED; + VectorSpecies FSPEC = FloatVector.SPECIES_PREFERRED; + + SIMDDetection.intVectorBitSize = ISPEC.vectorBitSize(); + SIMDDetection.floatVectorBitSize = FSPEC.vectorBitSize(); + + SIMDDetection.intElementSize = ISPEC.elementSize(); + SIMDDetection.floatElementSize = FSPEC.elementSize(); + + SIMDDetection.testRunCompleted = true; + + if (ISPEC.elementSize() < 2 || FSPEC.elementSize() < 2) { + SIMDDetection.unsupportingLaneSize = true; + return; + } + + SIMDDetection.isEnabled = true; + } catch (Throwable ignored) {} // Basically, we don't do anything. This lets us detect if it's not functional and disable it. + } + +} diff --git a/src/main/java/gg/pufferfish/pufferfish/simd/SIMDDetection.java b/src/main/java/gg/pufferfish/pufferfish/simd/SIMDDetection.java new file mode 100644 index 000000000..edb6014b1 --- /dev/null +++ b/src/main/java/gg/pufferfish/pufferfish/simd/SIMDDetection.java @@ -0,0 +1,75 @@ +package gg.pufferfish.pufferfish.simd; + +public class SIMDDetection { + + private static boolean isInitialized = false; + static int intVectorBitSize; + static int floatVectorBitSize; + static int intElementSize; + static int floatElementSize; + static boolean supportingJavaVersion; + static boolean testRunStarted; + static boolean testRunCompleted; + static boolean unsupportingLaneSize; + static boolean isEnabled; + + public static void initialize() { + try { + SIMDChecker.initialize(); + } catch (Throwable ignored) {} + } + + static void setInitialized() { + isInitialized = true; + } + + public static boolean isInitialized() { + return isInitialized; + } + + public static int intVectorBitSize() { + return intVectorBitSize; + } + + public static int floatVectorBitSize() { + return floatVectorBitSize; + } + + public static int intElementSize() { + return intElementSize; + } + + public static int floatElementSize() { + return floatElementSize; + } + + public static boolean supportingJavaVersion() { + return supportingJavaVersion; + } + + public static boolean testRunCompleted() { + return testRunCompleted; + } + + public static boolean unsupportingLaneSize() { + return unsupportingLaneSize; + } + + public static boolean isEnabled() { + return isEnabled; + } + + public static int getJavaVersion() { + // https://stackoverflow.com/a/2591122 + String version = System.getProperty("java.version"); + if(version.startsWith("1.")) { + version = version.substring(2, 3); + } else { + int dot = version.indexOf("."); + if(dot != -1) { version = version.substring(0, dot); } + } + version = version.split("-")[0]; // Azul is stupid + return Integer.parseInt(version); + } + +} diff --git a/src/main/java/gg/pufferfish/pufferfish/simd/VectorMapPalette.java b/src/main/java/gg/pufferfish/pufferfish/simd/VectorMapPalette.java new file mode 100644 index 000000000..9dd5abb42 --- /dev/null +++ b/src/main/java/gg/pufferfish/pufferfish/simd/VectorMapPalette.java @@ -0,0 +1,82 @@ +package gg.pufferfish.pufferfish.simd; + +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.IntVector; +import jdk.incubator.vector.VectorMask; +import jdk.incubator.vector.VectorSpecies; +import org.bukkit.map.MapPalette; + +import java.awt.*; + +public class VectorMapPalette { + + private static final VectorSpecies I_SPEC = IntVector.SPECIES_PREFERRED; + private static final VectorSpecies F_SPEC = FloatVector.SPECIES_PREFERRED; + + public static void matchColorVectorized(int[] in, byte[] out) { + int speciesLength = I_SPEC.length(); + int i; + for (i = 0; i < in.length - speciesLength; i += speciesLength) { + float[] redsArr = new float[speciesLength]; + float[] bluesArr = new float[speciesLength]; + float[] greensArr = new float[speciesLength]; + int[] alphasArr = new int[speciesLength]; + + for (int j = 0; j < speciesLength; j++) { + alphasArr[j] = (in[i + j] >> 24) & 0xFF; + redsArr[j] = (in[i + j] >> 16) & 0xFF; + greensArr[j] = (in[i + j] >> 8) & 0xFF; + bluesArr[j] = (in[i + j] >> 0) & 0xFF; + } + + IntVector alphas = IntVector.fromArray(I_SPEC, alphasArr, 0); + FloatVector reds = FloatVector.fromArray(F_SPEC, redsArr, 0); + FloatVector greens = FloatVector.fromArray(F_SPEC, greensArr, 0); + FloatVector blues = FloatVector.fromArray(F_SPEC, bluesArr, 0); + IntVector resultIndex = IntVector.zero(I_SPEC); + VectorMask modificationMask = VectorMask.fromLong(I_SPEC, 0xffffffff); + + modificationMask = modificationMask.and(alphas.lt(128).not()); + FloatVector bestDistances = FloatVector.broadcast(F_SPEC, Float.MAX_VALUE); + + for (int c = 4; c < MapPalette.colors.length; c++) { + // We're using 32-bit floats here because it's 2x faster and nobody will know the difference. + // For correctness, the original algorithm uses 64-bit floats instead. Completely unnecessary. + FloatVector compReds = FloatVector.broadcast(F_SPEC, MapPalette.colors[c].getRed()); + FloatVector compGreens = FloatVector.broadcast(F_SPEC, MapPalette.colors[c].getGreen()); + FloatVector compBlues = FloatVector.broadcast(F_SPEC, MapPalette.colors[c].getBlue()); + + FloatVector rMean = reds.add(compReds).div(2.0f); + FloatVector rDiff = reds.sub(compReds); + FloatVector gDiff = greens.sub(compGreens); + FloatVector bDiff = blues.sub(compBlues); + + FloatVector weightR = rMean.div(256.0f).add(2); + FloatVector weightG = FloatVector.broadcast(F_SPEC, 4.0f); + FloatVector weightB = FloatVector.broadcast(F_SPEC, 255.0f).sub(rMean).div(256.0f).add(2.0f); + + FloatVector distance = weightR.mul(rDiff).mul(rDiff).add(weightG.mul(gDiff).mul(gDiff)).add(weightB.mul(bDiff).mul(bDiff)); + + // Now we compare to the best distance we've found. + // This mask contains a "1" if better, and a "0" otherwise. + VectorMask bestDistanceMask = distance.lt(bestDistances); + bestDistances = bestDistances.blend(distance, bestDistanceMask); // Update the best distances + + // Update the result array + // We also AND with the modification mask because we don't want to interfere if the alpha value isn't large enough. + resultIndex = resultIndex.blend(c, bestDistanceMask.cast(I_SPEC).and(modificationMask)); // Update the results + } + + for (int j = 0; j < speciesLength; j++) { + int index = resultIndex.lane(j); + out[i + j] = (byte) (index < 128 ? index : -129 + (index - 127)); + } + } + + // For the final ones, fall back to the regular method + for (; i < in.length; i++) { + out[i] = MapPalette.matchColor(new Color(in[i], true)); + } + } + +} diff --git a/src/main/java/org/bukkit/map/MapPalette.java b/src/main/java/org/bukkit/map/MapPalette.java index ee68337ea..7cb18bf93 100644 --- a/src/main/java/org/bukkit/map/MapPalette.java +++ b/src/main/java/org/bukkit/map/MapPalette.java @@ -28,7 +28,7 @@ private static double getDistance(Color c1, Color c2) { return weightR * r * r + weightG * g * g + weightB * b * b; } - static final Color[] colors = { + public static final Color[] colors = { // CatRoom - Vectorized map palette - package-private -> public c(0, 0, 0), c(0, 0, 0), c(0, 0, 0), c(0, 0, 0), c(89, 125, 39), c(109, 153, 48), c(127, 178, 56), c(67, 94, 29), c(174, 164, 115), c(213, 201, 140), c(247, 233, 163), c(130, 123, 86), @@ -187,9 +187,14 @@ public static byte[] imageToBytes(Image image) { temp.getRGB(0, 0, temp.getWidth(), temp.getHeight(), pixels, 0, temp.getWidth()); byte[] result = new byte[temp.getWidth() * temp.getHeight()]; - for (int i = 0; i < pixels.length; i++) { - result[i] = matchColor(new Color(pixels[i], true)); + if (!gg.pufferfish.pufferfish.simd.SIMDDetection.isEnabled()) { // CatRoom start - Vectorized map color conversion + for (int i = 0; i < pixels.length; i++) { + result[i] = matchColor(new Color(pixels[i], true)); + } + } else { + gg.pufferfish.pufferfish.simd.VectorMapPalette.matchColorVectorized(pixels, result); } + // CatRoom end - Vectorized map color conversion return result; }