Working towards 1.20.2-v3: Improve performance of map image loading

Signed-off-by: Irmo van den Berge <irmo.vandenberge@ziggo.nl>
This commit is contained in:
Irmo van den Berge committed 2023-11-23 19:48:14 +01:00
1 parent 366ad3c13e
commit 4ac9faf0bb
5 files changed
+70 -98

No files matched your search

+6 -1
View File
@@ -8,7 +8,7 @@ plugins {
val buildNumber = System.getenv("BUILD_NUMBER") ?: "NO-CI"
group = "com.bergerkiller.bukkit"
version = "1.20.2-v2"
version = "1.20.2-v3-SNAPSHOT"
repositories {
mavenLocal {
@@ -67,6 +67,8 @@ dependencies {
}
// GSON isn't available in spigot versions prior to 1.8.1, shade it in order to keep 1.8 compatibility
internal(libs.gson)
// Color conversion helper library, https://github.com/bergerhealer/BKCommonLib-ColorConversionHelper
api(libs.colorconversionhelper)
//
// Optional provided dependencies that BKCommonLib can talk with
@@ -152,6 +154,9 @@ tasks {
this.testLogging {
this.showStandardStreams = true
}
// Uncomment to enable SIMD optimizations, to test performance of color conversion
//jvmArgs("--add-modules", "jdk.incubator.vector");
}
javadoc {
+2
View File
@@ -5,6 +5,7 @@ netty = "4.1.42.Final"
log4j = "2.17.0"
mountiplex = "2.99"
regionchangetracker = "1.2"
colorconversionhelper = "1.0"
softdependencylib = "1.02"
timings = "1.0.4"
gson = "2.8.9"
@@ -28,6 +29,7 @@ log4j-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4
log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" }
mountiplex = { module = "com.bergerkiller.mountiplex:Mountiplex", version.ref = "mountiplex" }
regionchangetracker = { module = "com.bergerkiller.bukkit.regionchangetracker:BKCommonLib-RegionChangeTracker-Core", version.ref = "regionchangetracker" }
colorconversionhelper = { module = "com.bergerkiller.bukkit.colorconversionhelper:BKCommonLib-ColorConversionHelper", version.ref = "colorconversionhelper" }
softdependency = { module = "com.bergerkiller.bukkit.softdependency:SoftDependency", version.ref = "softdependencylib" }
timings = { module = "co.aikar:minecraft-timings", version.ref = "timings" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
@@ -36,6 +36,7 @@ import com.bergerkiller.bukkit.common.internal.network.ProtocolLibPacketHandler;
import com.bergerkiller.bukkit.common.internal.permissions.PermissionHandler;
import com.bergerkiller.bukkit.common.internal.permissions.PermissionHandlerSelector;
import com.bergerkiller.bukkit.common.map.MapColorPalette;
import com.bergerkiller.bukkit.common.map.util.RGBColorToIntConversion;
import com.bergerkiller.bukkit.common.offline.OfflineWorld;
import com.bergerkiller.bukkit.common.protocol.PlayerGameInfo;
import com.bergerkiller.bukkit.common.softdependency.SoftDependency;
@@ -716,6 +717,11 @@ public class CommonPlugin extends PluginBase {
// Do not do this if map displays are disabled.
if (this.isMapDisplaysEnabled) {
MapColorPalette.getColor(0, 0, 0);
// Log a message if java's experimental incubator vector api is used for loading images into map colors
if (RGBColorToIntConversion.ABGR.isUsingSIMD()) {
getLogger().log(Level.INFO, "JDK17+ incubator vector maths are enabled. Will use it for loading MapDisplay textures.");
}
}
// Initialize NBT early
@@ -18,6 +18,7 @@ import com.bergerkiller.bukkit.common.internal.CommonBootstrap;
import com.bergerkiller.bukkit.common.map.color.MCSDBubbleFormat;
import com.bergerkiller.bukkit.common.map.color.MCSDGenBukkit;
import com.bergerkiller.bukkit.common.map.color.MapColorSpaceData;
import com.bergerkiller.bukkit.common.map.util.RGBColorToIntConversion;
/**
* Additional functionality on top of Bukkit's MapPalette
@@ -284,19 +285,17 @@ public class MapColorPalette {
// Obtain the color conversion type that has to be applied (RGB or BGR?)
int type = imageBuf.getType();
int byteStep = 4;
ColorConverterType converterType = null;
RGBColorToIntConversion converterType = null;
if (type == BufferedImage.TYPE_INT_RGB) {
converterType = ColorConverterType.RGB;
converterType = RGBColorToIntConversion.RGB;
} else if (type == BufferedImage.TYPE_INT_ARGB) {
converterType = ColorConverterType.ARGB;
converterType = RGBColorToIntConversion.ARGB;
} else if (type == BufferedImage.TYPE_INT_BGR) {
converterType = ColorConverterType.BGR;
converterType = RGBColorToIntConversion.BGR;
} else if (type == BufferedImage.TYPE_3BYTE_BGR) {
converterType = ColorConverterType.BGR;
byteStep = 3;
converterType = RGBColorToIntConversion.BGR;
} else if (type == BufferedImage.TYPE_4BYTE_ABGR) {
converterType = ColorConverterType.ABGR;
converterType = RGBColorToIntConversion.ABGR;
}
// Incompatible backing buffer or color format; simply use int[] ARGB
@@ -304,21 +303,24 @@ public class MapColorPalette {
intPixels = new int[width * height];
bytePixels = null;
imageBuf.getRGB(0, 0, width, height, intPixels, 0, width);
converterType = ColorConverterType.ARGB;
converterType = RGBColorToIntConversion.ARGB;
}
// Conversion method
RGBColorToIntConversion.RGBToMapColorFunction converter;
if (converterType.hasTransparency()) {
converter = rgba -> (rgba & 0x80000000) != 0 ? COLOR_MAP_DATA.get(rgba & 0xFFFFFF)
: COLOR_TRANSPARENT;
} else {
converter = COLOR_MAP_DATA::get;
}
// Perform efficient conversion from RGB int to byte color codes
byte[] result = new byte[width * height];
if (bytePixels != null) {
int index = 0;
for (int i = 0; i < result.length; i++) {
result[i] = converterType.convertBytes(bytePixels, index);
index += byteStep;
}
converterType.byteBufferToMapColors(bytePixels, width * height, converter, result);
} else {
for (int i = 0; i < result.length; i++) {
result[i] = converterType.convert(intPixels[i]);
}
converterType.intBufferToMapColors(intPixels, width * height, converter, result);
}
return result;
}
@@ -430,12 +432,13 @@ public class MapColorPalette {
graphics2D.dispose();
}
// Conversion method
RGBColorToIntConversion.RGBToMapColorFunction converter = rgba -> (rgba & 0x80000000) != 0
? COLOR_MAP_DATA.get(rgba & 0xFFFFFF) : COLOR_TRANSPARENT;
// Convert back to map colors
int[] intPixels = ((java.awt.image.DataBufferInt) outputImage.getRaster().getDataBuffer()).getData();
ColorConverterType converterType = ColorConverterType.ARGB;
for (int i = 0; i < outputLength; i++) {
output[i] = converterType.convert(intPixels[i]);
}
RGBColorToIntConversion.ARGB.intBufferToMapColors(intPixels, outputLength, converter, output);
}
/**
@@ -496,78 +499,4 @@ public class MapColorPalette {
public static IndexColorModel getIndexColorModel() {
return COLOR_INDEX_MODEL;
}
/**
* This class is used to convert raw color data into the
* map color format without using an int[] buffer in between.
* It is used when reading in images.
*/
private static abstract class ColorConverterType {
public abstract byte convert(int color);
public abstract byte convertBytes(byte[] buffer, int index);
public static final ColorConverterType RGB = new ColorConverterType() {
@Override
public byte convert(int color) {
return COLOR_MAP_DATA.get(color >> 16, color >> 8, color);
}
@Override
public byte convertBytes(byte[] buffer, int index) {
return COLOR_MAP_DATA.get(buffer[index], buffer[index + 1], buffer[index + 2]);
}
};
public static final ColorConverterType ARGB = new ColorConverterType() {
@Override
public byte convert(int color) {
if ((color & 0x80000000) == 0) {
return COLOR_TRANSPARENT;
} else {
return COLOR_MAP_DATA.get(color >> 16, color >> 8, color);
}
}
@Override
public byte convertBytes(byte[] buffer, int index) {
if ((buffer[index] & 0x80) == 0) {
return COLOR_TRANSPARENT;
} else {
return COLOR_MAP_DATA.get(buffer[index + 1], buffer[index + 2], buffer[index + 3]);
}
}
};
public static final ColorConverterType BGR = new ColorConverterType() {
@Override
public byte convert(int color) {
return COLOR_MAP_DATA.get(color, color >> 8, color >> 16);
}
@Override
public byte convertBytes(byte[] buffer, int index) {
return COLOR_MAP_DATA.get(buffer[index + 2], buffer[index + 1], buffer[index]);
}
};
public static final ColorConverterType ABGR = new ColorConverterType() {
@Override
public byte convert(int color) {
if ((color & 0x80000000) == 0) {
return COLOR_TRANSPARENT;
} else {
return COLOR_MAP_DATA.get(color, color >> 8, color >> 16);
}
}
@Override
public byte convertBytes(byte[] buffer, int index) {
if ((buffer[index] & 0x80) == 0) {
return COLOR_TRANSPARENT;
} else {
return COLOR_MAP_DATA.get(buffer[index + 3], buffer[index + 2], buffer[index + 1]);
}
}
};
}
}
@@ -2,8 +2,7 @@ package com.bergerkiller.bukkit.common;
import static org.junit.Assert.*;
import java.awt.Color;
import java.awt.Image;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.BufferedWriter;
import java.io.File;
@@ -14,6 +13,7 @@ import java.io.IOException;
import javax.imageio.ImageIO;
import com.bergerkiller.bukkit.common.map.util.RGBColorToIntConversion;
import org.junit.Ignore;
import org.junit.Test;
@@ -30,6 +30,36 @@ import com.bergerkiller.mountiplex.reflection.SafeField;
public class MapColorPaletteTest {
@Ignore
@Test
public void benchmark() throws Throwable {
// Load image and initialize the palette by running it once
Image image = ImageIO.read(new File("misc/map_test_bg.jpg"));
{
BufferedImage temp = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB);
Graphics2D g = temp.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
image = temp;
}
System.out.println(RGBColorToIntConversion.SIMDLoader.getError());
MapColorPalette.convertImage(image);
MapDebugWindow.showMapForever(MapTexture.fromImage(image));
// Time further invocations
int count = 10000;
long start = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
MapColorPalette.convertImage(image);
}
long duration = System.currentTimeMillis() - start;
System.out.println("Took: " + (((double) duration) / count) + " ms/op");
}
@Ignore
@Test
public void generateColorMap() {