mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-27 22:40:13 +00:00
Added chunk terrain bake
- Compiled generator programs evaluate layer trees in batches through NoiseProgram - TerrainProgram accumulates interpolator group means over chunk columns - ChunkBakeCache holds compute-once per-chunk planes with provisional results never published - Height, biome, region, rock, fluid and cave streams read the bake as thin facades - Sparse hydrology and bounds reads stay scalar with a direct-mapped base height memo - ColumnBake assembles id planes, heights, fluid surface, columns and the apron on demand
This commit is contained in:
1 parent
2471066937
commit
037156ab7d
22 files changed
+2032
-331
No files matched your search
@@ -0,0 +1,140 @@
|
||||
package art.arcane.iris.generation.noise;
|
||||
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.SplittableRandom;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class GeneratorProgramParityTest {
|
||||
private static final NoiseStyle[] STYLES = {
|
||||
NoiseStyle.SIMPLEX, NoiseStyle.PERLIN, NoiseStyle.IRIS, NoiseStyle.IRIS_HALF, NoiseStyle.FRACTAL_SMOKE,
|
||||
NoiseStyle.FRACTAL_WATER, NoiseStyle.CLOVER, NoiseStyle.CELLULAR, NoiseStyle.NOWHERE,
|
||||
NoiseStyle.CELLULAR_IRIS_DOUBLE, NoiseStyle.SIMPLEX_VASCULAR, NoiseStyle.FLAT, NoiseStyle.STATIC,
|
||||
NoiseStyle.GLOB_IRIS_THICK, NoiseStyle.FRACTAL_BILLOW_PERLIN, NoiseStyle.PERLIN_IRIS
|
||||
};
|
||||
|
||||
@Test
|
||||
public void programHeightsMatchScalarHeightsBitForBit() {
|
||||
SplittableRandom random = new SplittableRandom(0x5EED1E55L);
|
||||
TerrainBatchScratch scratch = new TerrainBatchScratch();
|
||||
for (int generatorIndex = 0; generatorIndex < 160; generatorIndex++) {
|
||||
long shapeSeed = random.nextLong();
|
||||
IrisGenerator scalar = generator(new SplittableRandom(shapeSeed));
|
||||
IrisGenerator batch = generator(new SplittableRandom(shapeSeed));
|
||||
long superSeed = random.nextLong();
|
||||
GeneratorProgram program = batch.compileProgram(superSeed);
|
||||
int count = 1 + random.nextInt(600);
|
||||
double[] xs = new double[count];
|
||||
double[] zs = new double[count];
|
||||
double[] out = new double[count];
|
||||
fillCoordinates(random, xs, zs, count);
|
||||
program.heights(scratch, xs, zs, count, out);
|
||||
for (int index = 0; index < count; index++) {
|
||||
assertEquals("generator " + generatorIndex + " point " + xs[index] + "," + zs[index],
|
||||
Double.doubleToRawLongBits(scalar.getHeight(xs[index], zs[index], superSeed)),
|
||||
Double.doubleToRawLongBits(out[index]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chunkGridsMatchScalarHeightsBitForBit() {
|
||||
SplittableRandom random = new SplittableRandom(0xC0FFEEL);
|
||||
TerrainBatchScratch scratch = new TerrainBatchScratch();
|
||||
double[] xs = new double[256];
|
||||
double[] zs = new double[256];
|
||||
double[] out = new double[256];
|
||||
for (int generatorIndex = 0; generatorIndex < 60; generatorIndex++) {
|
||||
long shapeSeed = random.nextLong();
|
||||
IrisGenerator scalar = generator(new SplittableRandom(shapeSeed));
|
||||
IrisGenerator batch = generator(new SplittableRandom(shapeSeed));
|
||||
long superSeed = random.nextLong();
|
||||
GeneratorProgram program = batch.compileProgram(superSeed);
|
||||
for (int chunk = 0; chunk < 4; chunk++) {
|
||||
int originX = random.nextInt(-200_000, 200_000) << 4;
|
||||
int originZ = random.nextInt(-200_000, 200_000) << 4;
|
||||
for (int index = 0; index < 256; index++) {
|
||||
xs[index] = originX + (index & 15);
|
||||
zs[index] = originZ + (index >> 4);
|
||||
}
|
||||
program.heights(scratch, xs, zs, 256, out);
|
||||
for (int index = 0; index < 256; index++) {
|
||||
assertEquals(Double.doubleToRawLongBits(scalar.getHeight(xs[index], zs[index], superSeed)),
|
||||
Double.doubleToRawLongBits(out[index]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void fillCoordinates(SplittableRandom random, double[] xs, double[] zs, int count) {
|
||||
for (int index = 0; index < count; index++) {
|
||||
xs[index] = coordinate(random);
|
||||
zs[index] = random.nextInt(5) == 0 ? xs[index] : coordinate(random);
|
||||
}
|
||||
}
|
||||
|
||||
private static double coordinate(SplittableRandom random) {
|
||||
return switch (random.nextInt(6)) {
|
||||
case 0 -> random.nextInt(-4096, 4096);
|
||||
case 1 -> random.nextInt(-30_000_000, 30_000_000);
|
||||
case 2 -> random.nextInt(-512, 512) + random.nextDouble();
|
||||
case 3 -> random.nextInt(-64, 64) * 6;
|
||||
case 4 -> random.nextDouble(-30_000_000D, 30_000_000D);
|
||||
default -> random.nextInt(-16, 16) * 0.25D;
|
||||
};
|
||||
}
|
||||
|
||||
private static IrisGenerator generator(SplittableRandom random) {
|
||||
int layers = 1 + random.nextInt(3);
|
||||
KList<IrisNoiseGenerator> composite = new KList<>();
|
||||
for (int layer = 0; layer < layers; layer++) {
|
||||
composite.add(layer(random, 0));
|
||||
}
|
||||
double[] details = {0D, 0.25D, 0.5D, 1D};
|
||||
IrisGenerator generator = new IrisGenerator().setComposite(composite)
|
||||
.setSurfaceDetail(details[random.nextInt(details.length)])
|
||||
.setMultiplicitive(random.nextInt(4) == 0)
|
||||
.setSeed(random.nextLong(-5000L, 5000L))
|
||||
.setZoom(0.2D + random.nextDouble() * 3D)
|
||||
.setOffsetX(random.nextInt(-40, 40) * 0.25D)
|
||||
.setOffsetZ(random.nextInt(-40, 40) * 0.25D)
|
||||
.setOpacity(0.1D + random.nextDouble());
|
||||
if (random.nextInt(3) == 0) {
|
||||
generator.setCliffHeightMin(random.nextDouble() * 4D).setCliffHeightMax(4D + random.nextDouble() * 12D)
|
||||
.setCliffHeightGenerator(layer(random, 2));
|
||||
}
|
||||
if (random.nextInt(5) == 0) {
|
||||
generator.setCellFractureHeight(random.nextDouble()).setCellFractureZoom(0.5D + random.nextDouble());
|
||||
}
|
||||
return generator;
|
||||
}
|
||||
|
||||
private static IrisNoiseGenerator layer(SplittableRandom random, int depth) {
|
||||
IrisNoiseGenerator layer = new IrisNoiseGenerator()
|
||||
.setStyle(STYLES[random.nextInt(STYLES.length)].style())
|
||||
.setSeed(random.nextLong(-100_000L, 100_000L))
|
||||
.setZoom(0.3D + random.nextDouble() * 4D)
|
||||
.setOpacity(0.05D + random.nextDouble())
|
||||
.setOffsetX(random.nextInt(-20, 20) * 0.5D)
|
||||
.setOffsetZ(random.nextInt(-20, 20) * 0.5D)
|
||||
.setOffsetY(random.nextInt(4) == 0 ? random.nextDouble() * 0.1D : 0D)
|
||||
.setNegative(random.nextInt(4) == 0)
|
||||
.setExponent(random.nextInt(4) == 0 ? 0.5D + random.nextDouble() : 1D)
|
||||
.setParametric(random.nextInt(6) == 0)
|
||||
.setBezier(random.nextInt(6) == 0)
|
||||
.setSinCentered(random.nextInt(8) == 0)
|
||||
.setOctaves(random.nextInt(5) == 0 ? 2 : 1)
|
||||
.setEnabled(depth == 0 || random.nextInt(8) != 0);
|
||||
if (depth < 4 && random.nextInt(3) != 0) {
|
||||
int children = random.nextInt(4) == 0 ? 2 + random.nextInt(2) : 1;
|
||||
KList<IrisNoiseGenerator> fracture = new KList<>();
|
||||
for (int child = 0; child < children; child++) {
|
||||
fracture.add(layer(random, depth + 1));
|
||||
}
|
||||
layer.setFracture(fracture);
|
||||
}
|
||||
return layer;
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,6 @@ package art.arcane.iris.generation.noise;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
@@ -34,7 +31,7 @@ public class IrisGeneratorBatchTest {
|
||||
assertTrue(batch.supportsBatch());
|
||||
double[] sampled = new double[XS.length];
|
||||
for (long seed : seeds) {
|
||||
batch.getHeights(XS, ZS, seed, sampled, sampled.length);
|
||||
batch.compileProgram(seed).heights(new TerrainBatchScratch(), XS, ZS, sampled.length, sampled);
|
||||
for (int index = 0; index < sampled.length; index++) {
|
||||
assertEquals(style + " detail=" + detail + " seed=" + seed + " index=" + index,
|
||||
Double.doubleToRawLongBits(scalar.getHeight(XS[index], ZS[index], seed)),
|
||||
@@ -54,7 +51,7 @@ public class IrisGeneratorBatchTest {
|
||||
IrisGenerator batch = generator(style, detail, false).setCliffHeightMax(0D).setCellFractureHeight(0D);
|
||||
double[] values = new double[XS.length];
|
||||
for (long seed : new long[]{-1337L, Long.MAX_VALUE - 37L}) {
|
||||
batch.getHeights(XS, ZS, seed, values, XS.length);
|
||||
batch.compileProgram(seed).heights(new TerrainBatchScratch(), XS, ZS, XS.length, values);
|
||||
for (int index = 0; index < XS.length; index++) {
|
||||
assertEquals(Double.doubleToRawLongBits(scalar.getHeight(XS[index], ZS[index], seed)),
|
||||
Double.doubleToRawLongBits(values[index]));
|
||||
@@ -65,13 +62,15 @@ public class IrisGeneratorBatchTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesLayerContextOncePerBatch() {
|
||||
public void resolvesLayerContextOncePerCompile() {
|
||||
IrisNoiseGenerator layer = spy(new IrisNoiseGenerator().setStyle(NoiseStyle.SIMPLEX.style()));
|
||||
IrisGenerator generator = new IrisGenerator().setComposite(new KList<>(layer));
|
||||
double[] sampled = new double[XS.length];
|
||||
generator.getHeights(XS, ZS, 99L, sampled, XS.length);
|
||||
GeneratorProgram program = generator.compileProgram(99L);
|
||||
verify(layer, times(1)).getGenerator(anyLong(), isNull(), isNull());
|
||||
clearInvocations(layer);
|
||||
program.heights(new TerrainBatchScratch(), XS, ZS, XS.length, sampled);
|
||||
verify(layer, times(0)).getGenerator(anyLong(), isNull(), isNull());
|
||||
for (int index = 0; index < XS.length; index++) {
|
||||
generator.getHeight(XS[index], ZS[index], 99L);
|
||||
}
|
||||
@@ -79,24 +78,15 @@ public class IrisGeneratorBatchTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customGeneratorFallbackKeepsCoordinateOrder() {
|
||||
List<Double> sampled = new ArrayList<>();
|
||||
public void customGeneratorsCannotCompile() {
|
||||
IrisGenerator generator = new IrisGenerator() {
|
||||
@Override
|
||||
public double getHeight(double x, double z, long seed) {
|
||||
sampled.add(x);
|
||||
return sampled.size();
|
||||
return x;
|
||||
}
|
||||
};
|
||||
assertFalse(generator.supportsBatch());
|
||||
double[] values = new double[XS.length];
|
||||
generator.getHeights(XS, ZS, 0L, values, values.length);
|
||||
for (int index = 0; index < values.length; index++) {
|
||||
assertEquals(Double.valueOf(XS[index]), sampled.get(index));
|
||||
assertEquals(index + 1D, values[index], 0D);
|
||||
}
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> generator.getHeights(new double[4097], new double[4097], 0L, new double[4097], 4097));
|
||||
assertThrows(IllegalStateException.class, () -> generator.compileProgram(0L));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+3
-8
@@ -44,6 +44,7 @@ import static org.mockito.ArgumentMatchers.anyDouble;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.atMost;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -89,16 +90,10 @@ public class IrisComplexHeightStreamTest {
|
||||
try (Fixture fixture = new Fixture(pack())) {
|
||||
IrisBiome mapped = new IrisBiome();
|
||||
when(fixture.images.sampleBiome(anyDouble(), anyDouble())).thenReturn(mapped);
|
||||
MeteredCache bridge = (MeteredCache) fixture.complex.getBridgeStream();
|
||||
MeteredCache regions = (MeteredCache) fixture.complex.getRegionStream();
|
||||
MeteredCache heights = (MeteredCache) fixture.complex.getNaturalHeightStream();
|
||||
assertSame(mapped, fixture.complex.getBaseBiomeStream().get(-16, 32));
|
||||
assertSame(mapped, fixture.complex.getBaseBiomeStream().get(-16.75, 10, 32.5));
|
||||
assertSame(mapped, fixture.complex.getNaturalTrueBiomeStream().get(-16, 32));
|
||||
assertSame(mapped, fixture.complex.getNaturalTrueBiomeStream().get(-16.75, 10, 32.5));
|
||||
assertEquals(0L, bridge.getSize());
|
||||
assertEquals(0L, regions.getSize());
|
||||
assertEquals(0L, heights.getSize());
|
||||
assertNull(fixture.complex.getSharedCornerBounds());
|
||||
|
||||
when(fixture.images.sampleBiome(anyDouble(), anyDouble())).thenReturn(null);
|
||||
@@ -116,7 +111,7 @@ public class IrisComplexHeightStreamTest {
|
||||
int[] rounded = new int[256];
|
||||
clearInvocations(fixture.hooks);
|
||||
cache.fillRounded(rounded);
|
||||
verify(fixture.hooks, times(1)).isMainThread();
|
||||
verify(fixture.hooks, atMost(2)).isMainThread();
|
||||
assertEquals(256, samples.get());
|
||||
for (int index = 0; index < 256; index++) {
|
||||
int x = index & 15;
|
||||
@@ -127,7 +122,7 @@ public class IrisComplexHeightStreamTest {
|
||||
}
|
||||
clearInvocations(fixture.hooks);
|
||||
cache.fill();
|
||||
verify(fixture.hooks, times(1)).isMainThread();
|
||||
verify(fixture.hooks, atMost(2)).isMainThread();
|
||||
assertEquals(256, samples.get());
|
||||
}
|
||||
}
|
||||
|
||||
+10
-9
@@ -2,6 +2,9 @@ package art.arcane.iris.generation.runtime;
|
||||
|
||||
import art.arcane.iris.configuration.IrisSettings;
|
||||
import art.arcane.iris.world.pregen.PregenPerformanceProfile;
|
||||
import art.arcane.iris.generation.bake.ChunkBakeCache;
|
||||
import art.arcane.iris.generation.bake.PlaneSource;
|
||||
import art.arcane.iris.generation.bake.SparseHeightMemo;
|
||||
import art.arcane.iris.generation.biome.IrisBiome;
|
||||
import art.arcane.iris.generation.hydrology.HydrologyPlanner;
|
||||
import art.arcane.iris.generation.hydrology.HydrologyPlannerSettings;
|
||||
@@ -48,17 +51,14 @@ public class IrisComplexHydrologyNoiseCacheTest {
|
||||
try (Fixture fixture = new Fixture(1_024)) {
|
||||
ProceduralStream<Double> wrapper = fixture.complex.getRawHeightStream();
|
||||
assertEquals(13D, fixture.height.getDouble(18, 5), 0D);
|
||||
assertEquals(13D, fixture.rawHeight.getDouble(18, 5), 0D);
|
||||
|
||||
PregenPerformanceProfile.apply(fixture.engine);
|
||||
|
||||
assertEquals(4_096L * 256L, fixture.height.getMaxSize());
|
||||
assertEquals(4_096L * 256L, fixture.rawHeight.getMaxSize());
|
||||
assertTrue(fixture.bakes.capacity() >= 4_096);
|
||||
assertEquals(1_024L * 256L, fixture.biomes.getMaxSize());
|
||||
assertSame(wrapper, fixture.complex.getRawHeightStream());
|
||||
assertEquals(47D, wrapper.getDouble(18, 5), 0D);
|
||||
assertEquals(13D, fixture.height.getDouble(18, 5), 0D);
|
||||
assertEquals(13D, fixture.rawHeight.getDouble(18, 5), 0D);
|
||||
} finally {
|
||||
PregenPerformanceProfile.restore();
|
||||
IrisSettings.settings = previousSettings;
|
||||
@@ -75,7 +75,7 @@ public class IrisComplexHydrologyNoiseCacheTest {
|
||||
fixture.complex.ensureTerrainNoiseCacheSize(4_096);
|
||||
|
||||
fixture.assertCapacities(32_768);
|
||||
assertEquals(4_096L * 256L, fixture.rawHeight.getMaxSize());
|
||||
assertTrue(fixture.bakes.capacity() >= 4_096);
|
||||
assertEquals(reservedBytes, budget.reservedBytes());
|
||||
fixture.complex.close();
|
||||
assertEquals(0L, budget.reservedBytes());
|
||||
@@ -88,7 +88,7 @@ public class IrisComplexHydrologyNoiseCacheTest {
|
||||
fixture.complex.ensureTerrainNoiseCacheSize(4_096);
|
||||
|
||||
fixture.assertCapacities(65_536);
|
||||
assertEquals(65_536L * 256L, fixture.rawHeight.getMaxSize());
|
||||
assertTrue(fixture.bakes.capacity() >= 65_536);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,7 +297,7 @@ public class IrisComplexHydrologyNoiseCacheTest {
|
||||
private final CachedStream2D<IrisRegion> regions;
|
||||
private final CachedStream2D<InferredType> bridge;
|
||||
private final CachedDoubleStream2D height;
|
||||
private final CachedDoubleStream2D rawHeight;
|
||||
private final ChunkBakeCache bakes;
|
||||
|
||||
private Fixture(int capacity) throws Exception {
|
||||
services.when(() -> IrisServices.get(PreservationRegistry.class)).thenReturn(mock(PreservationRegistry.class));
|
||||
@@ -305,10 +305,11 @@ public class IrisComplexHydrologyNoiseCacheTest {
|
||||
regions = objectCache(capacity);
|
||||
bridge = objectCache(capacity);
|
||||
height = heightCache(capacity);
|
||||
rawHeight = heightCache(capacity);
|
||||
bakes = new ChunkBakeCache(mock(PlaneSource.class), capacity);
|
||||
complex.setNaturalHeightStream(height);
|
||||
complex.setHeightStream(ProceduralStream.ofDouble((x, z) -> 47D));
|
||||
setField(complex, "cachedHeightStream", rawHeight);
|
||||
setField(complex, "bakeCache", bakes);
|
||||
setField(complex, "sparseBaseHeights", new SparseHeightMemo(16));
|
||||
when(engine.getComplex()).thenReturn(complex);
|
||||
complex.setBaseBiomeStream(biomes);
|
||||
complex.setRegionStream(regions);
|
||||
|
||||
Reference in new issue
Block a user