This commit is contained in:
Brian Neumann-Fopiano
2026-06-10 23:42:12 -04:00
parent 58d24ca55e
commit c5a2a57822
14 changed files with 385 additions and 14 deletions
+5
View File
@@ -214,10 +214,15 @@ tasks.named('compileJava', JavaCompile).configure {
* We need parameter meta for the decree command system
*/
options.compilerArgs.add('-parameters')
options.compilerArgs.addAll(['--add-modules', 'jdk.incubator.vector'])
options.encoding = 'UTF-8'
options.debugOptions.debugLevel = 'none'
}
tasks.named('test', Test).configure {
jvmArgs('--add-modules', 'jdk.incubator.vector')
}
tasks.named('processResources').configure {
/**
* Expand properties into plugin yml
+1 -1
View File
@@ -1 +1 @@
748295809
983895216
@@ -70,6 +70,7 @@ import art.arcane.iris.util.common.plugin.VolmitSender;
import art.arcane.iris.util.common.plugin.chunk.ChunkTickets;
import art.arcane.iris.util.common.scheduling.J;
import art.arcane.iris.util.common.misc.ServerProperties;
import art.arcane.iris.util.simd.SimdSupport;
import art.arcane.volmlib.util.scheduling.Queue;
import art.arcane.volmlib.util.scheduling.ShurikenQueue;
import io.papermc.lib.PaperLib;
@@ -547,6 +548,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
private void enable() {
PaperLibBootstrap.install();
SimdSupport.install();
services = new KMap<>();
setupAudience();
Bindings.setupSentry();
@@ -193,6 +193,7 @@ public class IrisSettings {
public int objectLoaderCacheSize = 4_096;
public int tectonicPlateSize = -1;
public int mantleCleanupDelay = 200;
public boolean simdKernels = true;
public int getTectonicPlateSize() {
if (tectonicPlateSize > 0)
@@ -215,6 +215,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<BlockData>
int fluidHeight = dimension.getFluidHeight();
int clampedFluidHeight = Math.min(chunkHeight, fluidHeight);
boolean bedrockEnabled = dimension.isBedrock();
boolean hideOres = dimension.isHideOresForHiddenOre();
ChunkedDataCache<IrisBiome> biomeCache = context.getBiome();
ChunkedDataCache<IrisRegion> regionCache = context.getRegion();
ChunkedDataCache<BlockData> fluidCache = context.getFluid();
@@ -235,8 +236,8 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<BlockData>
int topY = Math.min(hf, chunkHeight - 1);
BlockData fluid = fluidCache.get(xf, zf);
BlockData rock = rockCache.get(xf, zf);
boolean hasSurfaceOres = biome.hasSurfaceOres() || region.hasSurfaceOres() || dimension.hasSurfaceOres();
boolean hasUndergroundOres = biome.hasUndergroundOres() || region.hasUndergroundOres() || dimension.hasUndergroundOres();
boolean hasSurfaceOres = !hideOres && (biome.hasSurfaceOres() || region.hasSurfaceOres() || dimension.hasSurfaceOres());
boolean hasUndergroundOres = !hideOres && (biome.hasUndergroundOres() || region.hasUndergroundOres() || dimension.hasUndergroundOres());
KList<BlockData> blocks = null;
KList<BlockData> fblocks = null;
@@ -34,6 +34,8 @@ import art.arcane.iris.engine.object.IrisRange;
import art.arcane.iris.util.project.context.ChunkContext;
import art.arcane.iris.util.project.stream.ProceduralStream;
import art.arcane.iris.util.project.stream.utility.ChunkFillableDoubleStream2D;
import art.arcane.iris.util.simd.SimdKernels;
import art.arcane.iris.util.simd.SimdSupport;
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
import art.arcane.volmlib.util.mantle.flag.ReservedFlag;
import art.arcane.volmlib.util.math.PowerOfTwoCoordinates;
@@ -230,14 +232,9 @@ public class MantleCarvingComponent extends IrisMantleComponent {
continue;
}
double totalWeight = 0D;
double maxWeight = 0D;
for (double weight : weights) {
totalWeight += weight;
if (weight > maxWeight) {
maxWeight = weight;
}
}
SimdKernels kernels = SimdSupport.kernels();
double totalWeight = kernels.sum(weights, weights.length);
double maxWeight = kernels.max(weights, weights.length);
if (maxWeight < MIN_WEIGHT) {
continue;
@@ -25,25 +25,61 @@ import art.arcane.iris.util.common.data.B;
import art.arcane.iris.util.project.hunk.Hunk;
import art.arcane.iris.util.common.parallel.BurstExecutor;
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
import org.bukkit.Material;
import org.bukkit.block.data.Bisected;
import org.bukkit.block.data.BlockData;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class IrisPerfectionModifier extends EngineAssignedModifier<BlockData> {
private static final BlockData AIR = B.get("AIR");
private static final BlockData WATER = B.get("WATER");
private static final Map<Material, BlockData> ORE_BASES = buildOreBases();
public IrisPerfectionModifier(Engine engine) {
super(engine, "Perfection");
}
private static Map<Material, BlockData> buildOreBases() {
Map<Material, BlockData> map = new EnumMap<>(Material.class);
BlockData stone = B.get("STONE");
BlockData deepslate = B.get("DEEPSLATE");
BlockData netherrack = B.get("NETHERRACK");
BlockData blackstone = B.get("BLACKSTONE");
map.put(Material.COAL_ORE, stone);
map.put(Material.COPPER_ORE, stone);
map.put(Material.IRON_ORE, stone);
map.put(Material.GOLD_ORE, stone);
map.put(Material.REDSTONE_ORE, stone);
map.put(Material.LAPIS_ORE, stone);
map.put(Material.DIAMOND_ORE, stone);
map.put(Material.EMERALD_ORE, stone);
map.put(Material.DEEPSLATE_COAL_ORE, deepslate);
map.put(Material.DEEPSLATE_COPPER_ORE, deepslate);
map.put(Material.DEEPSLATE_IRON_ORE, deepslate);
map.put(Material.DEEPSLATE_GOLD_ORE, deepslate);
map.put(Material.DEEPSLATE_REDSTONE_ORE, deepslate);
map.put(Material.DEEPSLATE_LAPIS_ORE, deepslate);
map.put(Material.DEEPSLATE_DIAMOND_ORE, deepslate);
map.put(Material.DEEPSLATE_EMERALD_ORE, deepslate);
map.put(Material.NETHER_GOLD_ORE, netherrack);
map.put(Material.NETHER_QUARTZ_ORE, netherrack);
map.put(Material.ANCIENT_DEBRIS, netherrack);
map.put(Material.GILDED_BLACKSTONE, blackstone);
return map;
}
@Override
public void onModify(int x, int z, Hunk<BlockData> output, boolean multicore, ChunkContext context) {
PrecisionStopwatch p = PrecisionStopwatch.start();
if (getDimension().isHideOresForHiddenOre()) {
hideOres(output, multicore);
}
AtomicBoolean changed = new AtomicBoolean(true);
int passes = 0;
AtomicInteger changes = new AtomicInteger();
@@ -123,6 +159,29 @@ public class IrisPerfectionModifier extends EngineAssignedModifier<BlockData> {
getEngine().getMetrics().getPerfection().put(p.getMilliseconds());
}
private void hideOres(Hunk<BlockData> output, boolean multicore) {
BurstExecutor burst = burst().burst(multicore);
int height = output.getHeight();
for (int i = 0; i < 16; i++) {
int finalI = i;
burst.queue(() -> {
for (int j = 0; j < 16; j++) {
for (int k = height - 1; k >= 0; k--) {
BlockData block = output.get(finalI, k, j);
if (block == null) {
continue;
}
BlockData base = ORE_BASES.get(block.getMaterial());
if (base != null) {
output.set(finalI, k, j, base);
}
}
}
});
}
burst.complete();
}
private int getHeight(Hunk<BlockData> output, int x, int z) {
for (int i = output.getHeight() - 1; i >= 0; i--) {
BlockData b = output.get(x, i, z);
@@ -140,6 +140,8 @@ public class IrisDimension extends IrisRegistrant {
private boolean debugSmartBore = false;
@Desc("Generate decorations or not")
private boolean decorate = true;
@Desc("When true, every ore block the generator would place (terrain ores, deposits, ores inside objects) is replaced with its base material such as stone, deepslate or netherrack, so drop-control plugins like HiddenOre can manage ore rewards themselves")
private boolean hideOresForHiddenOre = false;
@Desc("Use post processing or not")
private boolean postProcessing = true;
@Desc("Add slabs in post processing")
@@ -2,6 +2,7 @@ package art.arcane.iris.util.project.context;
import art.arcane.iris.util.project.stream.ProceduralStream;
import art.arcane.iris.util.project.stream.utility.ChunkFillableDoubleStream2D;
import art.arcane.iris.util.simd.SimdSupport;
import art.arcane.volmlib.util.documentation.BlockCoordinates;
import java.util.Arrays;
@@ -56,9 +57,7 @@ public class ChunkedDoubleDataCache {
if (stream instanceof ChunkFillableDoubleStream2D cachedStream) {
cachedStream.fillChunkDoubles(x, z, data);
if (roundedTarget != null) {
for (int index = 0; index < 256; index++) {
roundedTarget[index] = (int) Math.round(data[index]);
}
SimdSupport.kernels().roundToInt(data, roundedTarget, 256);
}
return;
}
@@ -0,0 +1,37 @@
package art.arcane.iris.util.simd;
public final class ScalarSimdKernels implements SimdKernels {
@Override
public String describe() {
return "scalar";
}
@Override
public void roundToInt(double[] source, int[] target, int length) {
for (int index = 0; index < length; index++) {
target[index] = (int) Math.round(source[index]);
}
}
@Override
public double sum(double[] values, int length) {
double total = 0D;
for (int index = 0; index < length; index++) {
total += values[index];
}
return total;
}
@Override
public double max(double[] values, int length) {
double best = Double.NEGATIVE_INFINITY;
for (int index = 0; index < length; index++) {
if (values[index] > best) {
best = values[index];
}
}
return best;
}
}
@@ -0,0 +1,11 @@
package art.arcane.iris.util.simd;
public interface SimdKernels {
String describe();
void roundToInt(double[] source, int[] target, int length);
double sum(double[] values, int length);
double max(double[] values, int length);
}
@@ -0,0 +1,64 @@
package art.arcane.iris.util.simd;
import art.arcane.iris.Iris;
import art.arcane.iris.core.IrisSettings;
public final class SimdSupport {
private static final String VECTOR_MODULE = "jdk.incubator.vector";
private static final boolean MODULE_PRESENT = ModuleLayer.boot().findModule(VECTOR_MODULE).isPresent();
private static final SimdKernels KERNELS = selectKernels();
private SimdSupport() {
}
public static SimdKernels kernels() {
return KERNELS;
}
public static boolean isVectorModulePresent() {
return MODULE_PRESENT;
}
public static boolean isVectorized() {
return !(KERNELS instanceof ScalarSimdKernels);
}
public static void install() {
if (isVectorized()) {
Iris.info("SIMD: vector kernels enabled (" + KERNELS.describe() + ")");
} else if (!MODULE_PRESENT) {
Iris.info("SIMD: scalar kernels active; add --add-modules " + VECTOR_MODULE + " to JVM flags to enable vectorized generation kernels");
} else {
Iris.info("SIMD: vector kernels disabled (performance.simdKernels=false)");
}
}
public static SimdKernels createVectorKernels() {
if (!MODULE_PRESENT) {
return null;
}
try {
return (SimdKernels) Class.forName("art.arcane.iris.util.simd.VectorSimdKernels").getDeclaredConstructor().newInstance();
} catch (Throwable e) {
return null;
}
}
private static SimdKernels selectKernels() {
if (!simdEnabledInSettings()) {
return new ScalarSimdKernels();
}
SimdKernels vector = createVectorKernels();
return vector == null ? new ScalarSimdKernels() : vector;
}
private static boolean simdEnabledInSettings() {
try {
return IrisSettings.get().getPerformance().isSimdKernels();
} catch (Throwable e) {
return true;
}
}
}
@@ -0,0 +1,74 @@
package art.arcane.iris.util.simd;
import jdk.incubator.vector.DoubleVector;
import jdk.incubator.vector.IntVector;
import jdk.incubator.vector.VectorMask;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorShape;
import jdk.incubator.vector.VectorSpecies;
public final class VectorSimdKernels implements SimdKernels {
private static final VectorSpecies<Double> DOUBLE_SPECIES = DoubleVector.SPECIES_PREFERRED;
private static final VectorSpecies<Integer> INT_SPECIES = VectorSpecies.of(int.class, VectorShape.forBitSize(DOUBLE_SPECIES.length() * Integer.SIZE));
@Override
public String describe() {
return DOUBLE_SPECIES.length() + "x64-bit lanes, " + DOUBLE_SPECIES.vectorShape();
}
@Override
public void roundToInt(double[] source, int[] target, int length) {
int lanes = DOUBLE_SPECIES.length();
int bound = DOUBLE_SPECIES.loopBound(length);
int index = 0;
for (; index < bound; index += lanes) {
DoubleVector shifted = DoubleVector.fromArray(DOUBLE_SPECIES, source, index).add(0.5D);
IntVector truncated = (IntVector) shifted.convertShape(VectorOperators.D2I, INT_SPECIES, 0);
DoubleVector truncatedBack = (DoubleVector) truncated.convertShape(VectorOperators.I2D, DOUBLE_SPECIES, 0);
VectorMask<Integer> needsDecrement = shifted.lt(truncatedBack).cast(INT_SPECIES);
truncated.sub(1, needsDecrement).intoArray(target, index);
}
for (; index < length; index++) {
target[index] = (int) Math.round(source[index]);
}
}
@Override
public double sum(double[] values, int length) {
int lanes = DOUBLE_SPECIES.length();
int bound = DOUBLE_SPECIES.loopBound(length);
DoubleVector accumulator = DoubleVector.zero(DOUBLE_SPECIES);
int index = 0;
for (; index < bound; index += lanes) {
accumulator = accumulator.add(DoubleVector.fromArray(DOUBLE_SPECIES, values, index));
}
double total = accumulator.reduceLanes(VectorOperators.ADD);
for (; index < length; index++) {
total += values[index];
}
return total;
}
@Override
public double max(double[] values, int length) {
int lanes = DOUBLE_SPECIES.length();
int bound = DOUBLE_SPECIES.loopBound(length);
DoubleVector accumulator = DoubleVector.broadcast(DOUBLE_SPECIES, Double.NEGATIVE_INFINITY);
int index = 0;
for (; index < bound; index += lanes) {
accumulator = accumulator.max(DoubleVector.fromArray(DOUBLE_SPECIES, values, index));
}
double best = accumulator.reduceLanes(VectorOperators.MAX);
for (; index < length; index++) {
if (values[index] > best) {
best = values[index];
}
}
return best;
}
}
@@ -0,0 +1,119 @@
package art.arcane.iris.util.simd;
import org.junit.Assume;
import org.junit.Test;
import java.util.Random;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class SimdKernelsTest {
private static final double[] BOUNDARY_VALUES = {
0D, 0.5D, -0.5D, 1.5D, -1.5D, 2.5D, -2.5D, 63.5D, -63.5D, 64.49999D, -64.49999D,
0.4999999D, -0.4999999D, 255.5D, -255.5D, 319.999D, -64D, 2031.5D, -2031.5D, 0.0001D, -0.0001D
};
private static double[] terrainLikeValues(int length) {
Random random = new Random(42L);
double[] values = new double[length];
for (int index = 0; index < length; index++) {
values[index] = (random.nextDouble() - 0.5D) * 8192D;
}
return values;
}
@Test
public void scalarRoundToIntMatchesMathRound() {
ScalarSimdKernels kernels = new ScalarSimdKernels();
int[] rounded = new int[BOUNDARY_VALUES.length];
kernels.roundToInt(BOUNDARY_VALUES, rounded, BOUNDARY_VALUES.length);
for (int index = 0; index < BOUNDARY_VALUES.length; index++) {
assertEquals("index " + index + " value " + BOUNDARY_VALUES[index], (int) Math.round(BOUNDARY_VALUES[index]), rounded[index]);
}
}
@Test
public void scalarSumAndMaxMatchManualLoop() {
ScalarSimdKernels kernels = new ScalarSimdKernels();
double[] values = terrainLikeValues(256);
double expectedSum = 0D;
double expectedMax = Double.NEGATIVE_INFINITY;
for (double value : values) {
expectedSum += value;
if (value > expectedMax) {
expectedMax = value;
}
}
assertEquals(expectedSum, kernels.sum(values, values.length), 0D);
assertEquals(expectedMax, kernels.max(values, values.length), 0D);
}
@Test
public void vectorRoundToIntMatchesScalarOnBoundaries() {
SimdKernels vector = requireVectorKernels();
ScalarSimdKernels scalar = new ScalarSimdKernels();
int[] vectorRounded = new int[BOUNDARY_VALUES.length];
int[] scalarRounded = new int[BOUNDARY_VALUES.length];
vector.roundToInt(BOUNDARY_VALUES, vectorRounded, BOUNDARY_VALUES.length);
scalar.roundToInt(BOUNDARY_VALUES, scalarRounded, BOUNDARY_VALUES.length);
for (int index = 0; index < BOUNDARY_VALUES.length; index++) {
assertEquals("index " + index + " value " + BOUNDARY_VALUES[index], scalarRounded[index], vectorRounded[index]);
}
}
@Test
public void vectorRoundToIntMatchesScalarOnTerrainSweep() {
SimdKernels vector = requireVectorKernels();
ScalarSimdKernels scalar = new ScalarSimdKernels();
for (int length : new int[]{256, 251, 1, 7}) {
double[] values = terrainLikeValues(length);
int[] vectorRounded = new int[length];
int[] scalarRounded = new int[length];
vector.roundToInt(values, vectorRounded, length);
scalar.roundToInt(values, scalarRounded, length);
for (int index = 0; index < length; index++) {
assertEquals("length " + length + " index " + index + " value " + values[index], scalarRounded[index], vectorRounded[index]);
}
}
}
@Test
public void vectorSumMatchesScalarWithinTolerance() {
SimdKernels vector = requireVectorKernels();
ScalarSimdKernels scalar = new ScalarSimdKernels();
for (int length : new int[]{256, 251, 1, 7}) {
double[] values = terrainLikeValues(length);
double expected = scalar.sum(values, length);
double actual = vector.sum(values, length);
assertEquals("length " + length, expected, actual, Math.max(1e-9D, Math.abs(expected) * 1e-12D));
}
}
@Test
public void vectorMaxMatchesScalarExactly() {
SimdKernels vector = requireVectorKernels();
ScalarSimdKernels scalar = new ScalarSimdKernels();
for (int length : new int[]{256, 251, 1, 7}) {
double[] values = terrainLikeValues(length);
assertEquals("length " + length, scalar.max(values, length), vector.max(values, length), 0D);
}
}
@Test
public void vectorMaxOfAllZeroWeightsIsZero() {
SimdKernels vector = requireVectorKernels();
double[] weights = new double[256];
assertEquals(0D, vector.max(weights, weights.length), 0D);
assertEquals(0D, vector.sum(weights, weights.length), 0D);
}
private static SimdKernels requireVectorKernels() {
Assume.assumeTrue("jdk.incubator.vector module not present in test JVM", SimdSupport.isVectorModulePresent());
SimdKernels vector = SimdSupport.createVectorKernels();
assertNotNull("vector kernels failed to instantiate despite module being present", vector);
return vector;
}
}