mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-27 06:20:02 +00:00
Share cave density lattice
- Adaptive plane lattice samples cached per carver across neighbour chunks - Grid cleared with carvers on hotload
This commit is contained in:
1 parent
631c486a5a
commit
2ce36eb6f1
4 files changed
+162
-12
No files matched your search
@@ -0,0 +1,84 @@
|
|||||||
|
package art.arcane.iris.generation.mantle;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.cache.ConcurrentClockCache;
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.VarHandle;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicReferenceArray;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shares the adaptive cave planes' 8-block density lattice between neighbouring chunks. Every lattice column on a
|
||||||
|
* chunk edge is sampled by two chunks and every corner by four, with identical coordinates and carver noise, so a
|
||||||
|
* value computed once is reused bit for bit. Racing writers store the same value.
|
||||||
|
*/
|
||||||
|
final class CaveDensityGrid {
|
||||||
|
static final int LATTICE_SHIFT = 3;
|
||||||
|
private static final int SEGMENT_SHIFT = 5;
|
||||||
|
private static final int SEGMENT_SIZE = 1 << SEGMENT_SHIFT;
|
||||||
|
private static final long COORDINATE_MASK = 0xFFFFFFL;
|
||||||
|
private static final VarHandle VALUES = MethodHandles.arrayElementVarHandle(double[].class);
|
||||||
|
private static final AtomicInteger OWNERS = new AtomicInteger();
|
||||||
|
|
||||||
|
private final ConcurrentClockCache<Column> columns;
|
||||||
|
|
||||||
|
CaveDensityGrid(int capacity) {
|
||||||
|
columns = new ConcurrentClockCache<>(capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int nextOwner() {
|
||||||
|
return OWNERS.getAndIncrement();
|
||||||
|
}
|
||||||
|
|
||||||
|
Column column(Object owner, int ownerId, int height, int latticeX, int latticeZ) {
|
||||||
|
long key = (long) (ownerId & 0xFFFF) << 48 | (latticeX & COORDINATE_MASK) << 24 | (latticeZ & COORDINATE_MASK);
|
||||||
|
Column cached = columns.get(key);
|
||||||
|
if (cached == null) {
|
||||||
|
cached = columns.putIfAbsent(key, new Column(owner, latticeX, latticeZ, height));
|
||||||
|
}
|
||||||
|
return cached.owner == owner && cached.latticeX == latticeX && cached.latticeZ == latticeZ ? cached : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
columns.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class Column {
|
||||||
|
private final Object owner;
|
||||||
|
private final int latticeX;
|
||||||
|
private final int latticeZ;
|
||||||
|
private final AtomicReferenceArray<double[]> segments;
|
||||||
|
|
||||||
|
private Column(Object owner, int latticeX, int latticeZ, int height) {
|
||||||
|
this.owner = owner;
|
||||||
|
this.latticeX = latticeX;
|
||||||
|
this.latticeZ = latticeZ;
|
||||||
|
segments = new AtomicReferenceArray<>(Math.ceilDiv(Math.max(1, height), SEGMENT_SIZE));
|
||||||
|
}
|
||||||
|
|
||||||
|
double get(int y) {
|
||||||
|
int segment = y >> SEGMENT_SHIFT;
|
||||||
|
if (segment >= segments.length()) {
|
||||||
|
return Double.NaN;
|
||||||
|
}
|
||||||
|
double[] values = segments.getAcquire(segment);
|
||||||
|
return values == null ? Double.NaN : (double) VALUES.getOpaque(values, y & (SEGMENT_SIZE - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(int y, double value) {
|
||||||
|
int segment = y >> SEGMENT_SHIFT;
|
||||||
|
if (segment >= segments.length()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
double[] values = segments.getAcquire(segment);
|
||||||
|
if (values == null) {
|
||||||
|
double[] created = new double[SEGMENT_SIZE];
|
||||||
|
Arrays.fill(created, Double.NaN);
|
||||||
|
double[] published = segments.compareAndExchange(segment, null, created);
|
||||||
|
values = published == null ? created : published;
|
||||||
|
}
|
||||||
|
VALUES.setOpaque(values, y & (SEGMENT_SIZE - 1), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,6 +61,7 @@ public class IrisCaveCarver3D {
|
|||||||
private static final int MINIMUM_PARALLEL_DENSITY_CELLS = 16_384;
|
private static final int MINIMUM_PARALLEL_DENSITY_CELLS = 16_384;
|
||||||
private static final int MAXIMUM_DENSITY_WORKERS = 4;
|
private static final int MAXIMUM_DENSITY_WORKERS = 4;
|
||||||
private static final int[] LATTICE_TILE_OFFSETS = {0, 1, 16, 17};
|
private static final int[] LATTICE_TILE_OFFSETS = {0, 1, 16, 17};
|
||||||
|
private static final int STANDALONE_DENSITY_GRID_COLUMNS = 1024;
|
||||||
|
|
||||||
private final Engine engine;
|
private final Engine engine;
|
||||||
private final IrisData data;
|
private final IrisData data;
|
||||||
@@ -96,9 +97,16 @@ public class IrisCaveCarver3D {
|
|||||||
private final int densityMaximumY;
|
private final int densityMaximumY;
|
||||||
private final ThreadLocal<Boolean> carving = ThreadLocal.withInitial(() -> false);
|
private final ThreadLocal<Boolean> carving = ThreadLocal.withInitial(() -> false);
|
||||||
private final ThreadLocal<CaveCarveScratch> scratchCache = ThreadLocal.withInitial(CaveCarveScratch::new);
|
private final ThreadLocal<CaveCarveScratch> scratchCache = ThreadLocal.withInitial(CaveCarveScratch::new);
|
||||||
|
private final CaveDensityGrid densityGrid;
|
||||||
|
private final int densityGridOwner = CaveDensityGrid.nextOwner();
|
||||||
|
|
||||||
public IrisCaveCarver3D(Engine engine, IrisCaveProfile profile) {
|
public IrisCaveCarver3D(Engine engine, IrisCaveProfile profile) {
|
||||||
|
this(engine, profile, new CaveDensityGrid(STANDALONE_DENSITY_GRID_COLUMNS));
|
||||||
|
}
|
||||||
|
|
||||||
|
IrisCaveCarver3D(Engine engine, IrisCaveProfile profile, CaveDensityGrid densityGrid) {
|
||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
|
this.densityGrid = densityGrid;
|
||||||
this.data = engine.getData();
|
this.data = engine.getData();
|
||||||
this.profile = profile;
|
this.profile = profile;
|
||||||
this.carveAir = new MatterCavern(true, "", LIQUID_AIR);
|
this.carveAir = new MatterCavern(true, "", LIQUID_AIR);
|
||||||
@@ -1542,7 +1550,7 @@ public class IrisCaveCarver3D {
|
|||||||
int rowOffset = sampleXIndex * axisSamples;
|
int rowOffset = sampleXIndex * axisSamples;
|
||||||
for (int sampleZIndex = adaptivePlaneSampleBounds[2]; sampleZIndex <= adaptivePlaneSampleBounds[3]; sampleZIndex++) {
|
for (int sampleZIndex = adaptivePlaneSampleBounds[2]; sampleZIndex <= adaptivePlaneSampleBounds[3]; sampleZIndex++) {
|
||||||
int sampleLocalZ = Math.min(sampleZIndex * adaptiveSampleStep, 16);
|
int sampleLocalZ = Math.min(sampleZIndex * adaptiveSampleStep, 16);
|
||||||
adaptivePlaneDensity[rowOffset + sampleZIndex] = sampleDensityNoWarpNoModules(x, y, z0 + sampleLocalZ);
|
adaptivePlaneDensity[rowOffset + sampleZIndex] = latticeDensity(scratch, x, y, z0 + sampleLocalZ);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1595,7 +1603,7 @@ public class IrisCaveCarver3D {
|
|||||||
int rowOffset = sampleXIndex * axisSamples;
|
int rowOffset = sampleXIndex * axisSamples;
|
||||||
for (int sampleZIndex = adaptivePlaneSampleBounds[2]; sampleZIndex <= adaptivePlaneSampleBounds[3]; sampleZIndex++) {
|
for (int sampleZIndex = adaptivePlaneSampleBounds[2]; sampleZIndex <= adaptivePlaneSampleBounds[3]; sampleZIndex++) {
|
||||||
int sampleLocalZ = Math.min(sampleZIndex * adaptiveSampleStep, 16);
|
int sampleLocalZ = Math.min(sampleZIndex * adaptiveSampleStep, 16);
|
||||||
adaptivePlaneDensity[rowOffset + sampleZIndex] = sampleDensityNoWarpNoModules(x, y, z0 + sampleLocalZ);
|
adaptivePlaneDensity[rowOffset + sampleZIndex] = latticeDensity(scratch, x, y, z0 + sampleLocalZ);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1643,7 +1651,7 @@ public class IrisCaveCarver3D {
|
|||||||
int rowOffset = sampleXIndex * axisSamples;
|
int rowOffset = sampleXIndex * axisSamples;
|
||||||
for (int sampleZIndex = adaptivePlaneSampleBounds[2]; sampleZIndex <= adaptivePlaneSampleBounds[3]; sampleZIndex++) {
|
for (int sampleZIndex = adaptivePlaneSampleBounds[2]; sampleZIndex <= adaptivePlaneSampleBounds[3]; sampleZIndex++) {
|
||||||
int sampleLocalZ = Math.min(sampleZIndex * adaptiveSampleStep, 16);
|
int sampleLocalZ = Math.min(sampleZIndex * adaptiveSampleStep, 16);
|
||||||
adaptivePlaneDensity[rowOffset + sampleZIndex] = sampleDensityWarpOnly(scratch, x, y, z0 + sampleLocalZ);
|
adaptivePlaneDensity[rowOffset + sampleZIndex] = latticeDensity(scratch, x, y, z0 + sampleLocalZ);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1696,14 +1704,7 @@ public class IrisCaveCarver3D {
|
|||||||
int rowOffset = sampleXIndex * axisSamples;
|
int rowOffset = sampleXIndex * axisSamples;
|
||||||
for (int sampleZIndex = adaptivePlaneSampleBounds[2]; sampleZIndex <= adaptivePlaneSampleBounds[3]; sampleZIndex++) {
|
for (int sampleZIndex = adaptivePlaneSampleBounds[2]; sampleZIndex <= adaptivePlaneSampleBounds[3]; sampleZIndex++) {
|
||||||
int sampleLocalZ = Math.min(sampleZIndex * adaptiveSampleStep, 16);
|
int sampleLocalZ = Math.min(sampleZIndex * adaptiveSampleStep, 16);
|
||||||
adaptivePlaneDensity[rowOffset + sampleZIndex] = sampleDensityWarpModules(
|
adaptivePlaneDensity[rowOffset + sampleZIndex] = latticeDensity(scratch, x, y, z0 + sampleLocalZ);
|
||||||
scratch,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
z0 + sampleLocalZ,
|
|
||||||
localModules,
|
|
||||||
activeModuleCount
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2463,6 +2464,33 @@ public class IrisCaveCarver3D {
|
|||||||
scratch.adaptiveGeometryAxisCells = axisCells;
|
scratch.adaptiveGeometryAxisCells = axisCells;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private double latticeDensity(CaveCarveScratch scratch, int x, int y, int z) {
|
||||||
|
int latticeMask = (1 << CaveDensityGrid.LATTICE_SHIFT) - 1;
|
||||||
|
CaveDensityGrid.Column column = ((x | z) & latticeMask) != 0 ? null : densityGrid.column(this, densityGridOwner,
|
||||||
|
densityMaximumY + 1, x >> CaveDensityGrid.LATTICE_SHIFT, z >> CaveDensityGrid.LATTICE_SHIFT);
|
||||||
|
if (column == null) {
|
||||||
|
return samplePlaneDensity(scratch, x, y, z);
|
||||||
|
}
|
||||||
|
double cached = column.get(y);
|
||||||
|
if (!Double.isNaN(cached)) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
double density = samplePlaneDensity(scratch, x, y, z);
|
||||||
|
column.set(y, density);
|
||||||
|
return density;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double samplePlaneDensity(CaveCarveScratch scratch, int x, int y, int z) {
|
||||||
|
if (!hasWarp) {
|
||||||
|
return sampleDensityNoWarpNoModules(x, y, z);
|
||||||
|
}
|
||||||
|
int activeModuleCount = hasModules ? prepareActiveModules(scratch, y) : 0;
|
||||||
|
if (activeModuleCount == 0) {
|
||||||
|
return sampleDensityWarpOnly(scratch, x, y, z);
|
||||||
|
}
|
||||||
|
return sampleDensityWarpModules(scratch, x, y, z, scratch.activeModules, activeModuleCount);
|
||||||
|
}
|
||||||
|
|
||||||
private double sampleDensityNoWarpNoModules(int x, int y, int z) {
|
private double sampleDensityNoWarpNoModules(int x, int y, int z) {
|
||||||
double density = baseDensity.noiseFastSigned3D(x, y, z) * baseWeight;
|
double density = baseDensity.noiseFastSigned3D(x, y, z) * baseWeight;
|
||||||
density += detailDensity.noiseFastSigned3D(x, y, z) * detailWeight;
|
density += detailDensity.noiseFastSigned3D(x, y, z) * detailWeight;
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
|||||||
private static final double THRESHOLD_PENALTY = 0.24D;
|
private static final double THRESHOLD_PENALTY = 0.24D;
|
||||||
private static final int MAX_BLENDED_PROFILE_PASSES = 2;
|
private static final int MAX_BLENDED_PROFILE_PASSES = 2;
|
||||||
private static final int MAX_POOLED_WEIGHT_BUFFERS = 64;
|
private static final int MAX_POOLED_WEIGHT_BUFFERS = 64;
|
||||||
|
/** Lattice columns kept for neighbouring chunks: about 3 KB each on land, roughly 50 MB when full. */
|
||||||
|
private static final int DENSITY_GRID_COLUMNS = 16_384;
|
||||||
private static final int KERNEL_WIDTH = (BLEND_RADIUS * 2) + 1;
|
private static final int KERNEL_WIDTH = (BLEND_RADIUS * 2) + 1;
|
||||||
private static final int KERNEL_SIZE = KERNEL_WIDTH * KERNEL_WIDTH;
|
private static final int KERNEL_SIZE = KERNEL_WIDTH * KERNEL_WIDTH;
|
||||||
private static final int[] KERNEL_DX = new int[KERNEL_SIZE];
|
private static final int[] KERNEL_DX = new int[KERNEL_SIZE];
|
||||||
@@ -62,6 +64,7 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
|||||||
private static final ThreadLocal<BlendScratch> BLEND_SCRATCH = ThreadLocal.withInitial(BlendScratch::new);
|
private static final ThreadLocal<BlendScratch> BLEND_SCRATCH = ThreadLocal.withInitial(BlendScratch::new);
|
||||||
|
|
||||||
private final Object profileCarverLock = new Object();
|
private final Object profileCarverLock = new Object();
|
||||||
|
private final CaveDensityGrid densityGrid = new CaveDensityGrid(DENSITY_GRID_COLUMNS);
|
||||||
private volatile Map<IrisCaveProfile, IrisCaveCarver3D> profileCarvers = new IdentityHashMap<>();
|
private volatile Map<IrisCaveProfile, IrisCaveCarver3D> profileCarvers = new IdentityHashMap<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@@ -94,6 +97,7 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
|||||||
// Carvers rebuild deterministically from the carve seed, so output is unchanged.
|
// Carvers rebuild deterministically from the carve seed, so output is unchanged.
|
||||||
synchronized (profileCarverLock) {
|
synchronized (profileCarverLock) {
|
||||||
profileCarvers = new IdentityHashMap<>();
|
profileCarvers = new IdentityHashMap<>();
|
||||||
|
densityGrid.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -524,7 +528,7 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
|||||||
return carver;
|
return carver;
|
||||||
}
|
}
|
||||||
|
|
||||||
IrisCaveCarver3D createdCarver = new IrisCaveCarver3D(getEngineMantle().getEngine(), profile);
|
IrisCaveCarver3D createdCarver = new IrisCaveCarver3D(getEngineMantle().getEngine(), profile, densityGrid);
|
||||||
synchronized (profileCarverLock) {
|
synchronized (profileCarverLock) {
|
||||||
IrisCaveCarver3D published = profileCarvers.get(profile);
|
IrisCaveCarver3D published = profileCarvers.get(profile);
|
||||||
if (published != null) {
|
if (published != null) {
|
||||||
|
|||||||
+34
@@ -1038,6 +1038,40 @@ public class IrisCaveCarver3DNearParityTest {
|
|||||||
largestPlane < 192);
|
largestPlane < 192);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sharedDensityLatticeReproducesIsolatedCarvesAcrossNeighbourChunks() throws Exception {
|
||||||
|
Engine engine = createEngine(96, 90);
|
||||||
|
for (boolean warp : new boolean[]{false, true}) {
|
||||||
|
for (boolean modules : new boolean[]{false, true}) {
|
||||||
|
AtomicLong sharedSamples = new AtomicLong();
|
||||||
|
AtomicLong isolatedSamples = new AtomicLong();
|
||||||
|
IrisCaveCarver3D shared = new IrisCaveCarver3D(engine,
|
||||||
|
createProfile(warp, modules).setAdaptiveSampling(true), new CaveDensityGrid(4096));
|
||||||
|
baseDensityField.set(shared, new CountingCNG((CNG) baseDensityField.get(shared), sharedSamples));
|
||||||
|
for (int chunkX = -1; chunkX <= 1; chunkX++) {
|
||||||
|
for (int chunkZ = -1; chunkZ <= 1; chunkZ++) {
|
||||||
|
WriterCapture sharedCapture = createWriterCapture(96);
|
||||||
|
int sharedCarved = shared.carve(sharedCapture.writer, chunkX, chunkZ, fullWeights(), 0D, 0D,
|
||||||
|
null, filledHeights(90));
|
||||||
|
IrisCaveCarver3D isolated = new IrisCaveCarver3D(engine,
|
||||||
|
createProfile(warp, modules).setAdaptiveSampling(true));
|
||||||
|
baseDensityField.set(isolated, new CountingCNG((CNG) baseDensityField.get(isolated), isolatedSamples));
|
||||||
|
WriterCapture isolatedCapture = createWriterCapture(96);
|
||||||
|
int isolatedCarved = isolated.carve(isolatedCapture.writer, chunkX, chunkZ, fullWeights(), 0D, 0D,
|
||||||
|
null, filledHeights(90));
|
||||||
|
assertTrue(isolatedCarved > 0);
|
||||||
|
assertEquals(isolatedCarved, sharedCarved);
|
||||||
|
assertEquals(isolatedCapture.carvedCells, sharedCapture.carvedCells);
|
||||||
|
assertEquals(isolatedCapture.carvedLiquids, sharedCapture.carvedLiquids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue("warp=" + warp + " modules=" + modules + " shared=" + sharedSamples.get()
|
||||||
|
+ " isolated=" + isolatedSamples.get(),
|
||||||
|
sharedSamples.get() < isolatedSamples.get() * 3 / 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void assertExactParity(boolean warp, boolean modules, boolean adaptiveSampling) throws Exception {
|
private void assertExactParity(boolean warp, boolean modules, boolean adaptiveSampling) throws Exception {
|
||||||
Engine engine = createEngine(96, 90);
|
Engine engine = createEngine(96, 90);
|
||||||
double[] columnWeights = fullWeights();
|
double[] columnWeights = fullWeights();
|
||||||
|
|||||||
Reference in new issue
Block a user