diff --git a/core/src/main/java/art/arcane/iris/generation/mantle/CaveCarveScratch.java b/core/src/main/java/art/arcane/iris/generation/mantle/CaveCarveScratch.java index 061c88ac7..dd456abf5 100644 --- a/core/src/main/java/art/arcane/iris/generation/mantle/CaveCarveScratch.java +++ b/core/src/main/java/art/arcane/iris/generation/mantle/CaveCarveScratch.java @@ -40,6 +40,7 @@ final class CaveCarveScratch { final double[] planeThresholdLimit = new double[256]; final boolean[] planeCarve = new boolean[256]; final double[] adaptivePlaneDensity = new double[81]; + final CaveDensityGrid.Column[] densityColumns = new CaveDensityGrid.Column[16]; double[] verticalDensityLower = new double[9]; double[] verticalDensityUpper = new double[9]; int verticalDensityLowerY = Integer.MIN_VALUE; @@ -79,8 +80,9 @@ final class CaveCarveScratch { int adaptiveGeometryAxisCells = -1; boolean fullWeightsInitialized; - void releaseSections() { + void releaseReferences() { Arrays.fill(sectionMatter, null); Arrays.fill(sectionSlices, null); + Arrays.fill(densityColumns, null); } } diff --git a/core/src/main/java/art/arcane/iris/generation/mantle/CaveDensityGrid.java b/core/src/main/java/art/arcane/iris/generation/mantle/CaveDensityGrid.java index b0064d1a2..824523a64 100644 --- a/core/src/main/java/art/arcane/iris/generation/mantle/CaveDensityGrid.java +++ b/core/src/main/java/art/arcane/iris/generation/mantle/CaveDensityGrid.java @@ -37,7 +37,7 @@ final class CaveDensityGrid { 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; + return cached.matches(owner, latticeX, latticeZ) ? cached : null; } void clear() { @@ -57,6 +57,10 @@ final class CaveDensityGrid { segments = new AtomicReferenceArray<>(Math.ceilDiv(Math.max(1, height), SEGMENT_SIZE)); } + boolean matches(Object owner, int latticeX, int latticeZ) { + return this.owner == owner && this.latticeX == latticeX && this.latticeZ == latticeZ; + } + double get(int y) { int segment = y >> SEGMENT_SHIFT; if (segment >= segments.length()) { diff --git a/core/src/main/java/art/arcane/iris/generation/mantle/IrisCaveCarver3D.java b/core/src/main/java/art/arcane/iris/generation/mantle/IrisCaveCarver3D.java index 0f6858c6e..baa041b15 100644 --- a/core/src/main/java/art/arcane/iris/generation/mantle/IrisCaveCarver3D.java +++ b/core/src/main/java/art/arcane/iris/generation/mantle/IrisCaveCarver3D.java @@ -449,7 +449,7 @@ public class IrisCaveCarver3D { return carved; } finally { - scratchCache.get().releaseSections(); + scratchCache.get().releaseReferences(); carving.set(nested); if (nested) { scratchCache.set(previousScratch); @@ -2466,8 +2466,19 @@ public class IrisCaveCarver3D { 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 (((x | z) & latticeMask) != 0) { + return samplePlaneDensity(scratch, x, y, z); + } + int latticeX = x >> CaveDensityGrid.LATTICE_SHIFT; + int latticeZ = z >> CaveDensityGrid.LATTICE_SHIFT; + int slot = ((latticeX & 3) << 2) | (latticeZ & 3); + CaveDensityGrid.Column column = parallelDensity ? scratch.densityColumns[slot] : null; + if (column == null || !column.matches(this, latticeX, latticeZ)) { + column = densityGrid.column(this, densityGridOwner, densityMaximumY + 1, latticeX, latticeZ); + if (parallelDensity) { + scratch.densityColumns[slot] = column; + } + } if (column == null) { return samplePlaneDensity(scratch, x, y, z); } diff --git a/core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DNearParityTest.java b/core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DNearParityTest.java index e6963e27c..421f58386 100644 --- a/core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DNearParityTest.java +++ b/core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DNearParityTest.java @@ -53,6 +53,7 @@ import static org.mockito.ArgumentMatchers.anyDouble; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.withSettings; public class IrisCaveCarver3DNearParityTest { @@ -299,10 +300,10 @@ public class IrisCaveCarver3DNearParityTest { } @Test - public void carvingReleasesSectionReferencesAfterSuccessAndFailure() throws Exception { + public void carvingReleasesReferencesAfterSuccessAndFailure() throws Exception { for (boolean fail : new boolean[]{false, true}) { Engine engine = createEngine(128, 110); - IrisCaveProfile profile = createProfile(false, false).setAdaptiveSampling(false) + IrisCaveProfile profile = createProfile(false, false).setAdaptiveSampling(true) .setAllowFluid(false).setDetailWeight(0D) .setDensityThreshold(new IrisStyledRange(2D, 2D, new IrisGeneratorStyle(NoiseStyle.FLAT))); IrisCaveCarver3D carver = new IrisCaveCarver3D(engine, profile); @@ -334,6 +335,9 @@ public class IrisCaveCarver3DNearParityTest { for (MatterSlice slice : scratch.sectionSlices) { assertNull(slice); } + for (CaveDensityGrid.Column column : scratch.densityColumns) { + assertNull(column); + } } } @@ -1074,6 +1078,153 @@ public class IrisCaveCarver3DNearParityTest { } } + @Test + public void retainedLatticeColumnsPreserveRawSamplesThroughEvictionAndClear() throws Exception { + Method latticeDensity = IrisCaveCarver3D.class.getDeclaredMethod("latticeDensity", + CaveCarveScratch.class, int.class, int.class, int.class); + latticeDensity.setAccessible(true); + Method planeDensity = IrisCaveCarver3D.class.getDeclaredMethod("samplePlaneDensity", + CaveCarveScratch.class, int.class, int.class, int.class); + planeDensity.setAccessible(true); + Engine engine = createEngine(128, 110); + for (boolean warp : new boolean[]{false, true}) { + for (boolean modules : new boolean[]{false, true}) { + AtomicInteger lookups = new AtomicInteger(); + CaveDensityGrid grid = countingGrid(4, lookups); + IrisCaveCarver3D carver = new IrisCaveCarver3D(engine, createProfile(warp, modules), grid); + CaveCarveScratch retained = new CaveCarveScratch(); + CaveCarveScratch reference = new CaveCarveScratch(); + for (int y = 0; y < 128; y++) { + if (y == 64) { + grid.clear(); + } + for (int x = -16; x <= 0; x += 8) { + for (int z = 32; z <= 48; z += 8) { + double expected = (double) planeDensity.invoke(carver, reference, x, y, z); + double actual = (double) latticeDensity.invoke(carver, retained, x, y, z); + assertEquals(Double.doubleToRawLongBits(expected), Double.doubleToRawLongBits(actual)); + } + } + assertEquals(Double.doubleToRawLongBits((double) planeDensity.invoke(carver, reference, -15, y, 33)), + Double.doubleToRawLongBits((double) latticeDensity.invoke(carver, retained, -15, y, 33))); + } + assertEquals(9, lookups.get()); + retained.releaseReferences(); + latticeDensity.invoke(carver, retained, -16, 1, 32); + assertEquals(10, lookups.get()); + double expected = (double) planeDensity.invoke(carver, reference, 16, 1, 32); + double actual = (double) latticeDensity.invoke(carver, retained, 16, 1, 32); + assertEquals(Double.doubleToRawLongBits(expected), Double.doubleToRawLongBits(actual)); + assertEquals(11, lookups.get()); + IrisCaveCarver3D reloaded = new IrisCaveCarver3D(engine, + createProfile(warp, modules).setBaseWeight(0.73D), grid); + expected = (double) planeDensity.invoke(reloaded, new CaveCarveScratch(), 16, 1, 32); + actual = (double) latticeDensity.invoke(reloaded, retained, 16, 1, 32); + assertEquals(Double.doubleToRawLongBits(expected), Double.doubleToRawLongBits(actual)); + assertEquals(12, lookups.get()); + } + } + } + + @Test + public void retainedLatticeColumnsMatchOriginalCarvesAndReduceLookups() throws Exception { + Field fixedDensity = IrisCaveCarver3D.class.getDeclaredField("parallelDensity"); + fixedDensity.setAccessible(true); + Engine engine = createEngine(128, 110); + for (boolean warp : new boolean[]{false, true}) { + for (boolean modules : new boolean[]{false, true}) { + AtomicInteger originalLookups = new AtomicInteger(); + AtomicInteger retainedLookups = new AtomicInteger(); + IrisCaveCarver3D original = new IrisCaveCarver3D(engine, + createProfile(warp, modules), countingGrid(4096, originalLookups)); + fixedDensity.set(original, false); + IrisCaveCarver3D retained = new IrisCaveCarver3D(engine, + createProfile(warp, modules), countingGrid(4096, retainedLookups)); + for (int chunkX = -1; chunkX <= 0; chunkX++) { + WriterCapture expected = createWriterCapture(128); + WriterCapture actual = createWriterCapture(128); + assertEquals(original.carve(expected.writer, chunkX, -3, fullWeights(), 0D, 0D, + null, filledHeights(110)), + retained.carve(actual.writer, chunkX, -3, fullWeights(), 0D, 0D, + null, filledHeights(110))); + assertFalse(expected.carvedCells.isEmpty()); + assertEquals(expected.carvedCells, actual.carvedCells); + assertEquals(expected.carvedLiquids, actual.carvedLiquids); + } + assertEquals(18, retainedLookups.get()); + assertTrue(originalLookups.get() > retainedLookups.get() * 50); + } + } + } + + @Test + public void expressionDensityDoesNotRetainEvictedColumns() throws Exception { + Engine engine = createEngine(128, 110); + CNG density = mock(CNG.class, withSettings().stubOnly()); + AtomicInteger samples = new AtomicInteger(); + doAnswer(invocation -> samples.incrementAndGet() * 0.125D).when(density) + .noiseFastSigned3D(anyDouble(), anyDouble(), anyDouble()); + IrisGeneratorStyle style = mock(IrisGeneratorStyle.class, withSettings().stubOnly()); + doReturn("density_expression").when(style).getExpression(); + doReturn(density).when(style).create(any(RNG.class), any(IrisData.class)); + AtomicInteger lookups = new AtomicInteger(); + CaveDensityGrid grid = countingGrid(4, lookups); + IrisCaveCarver3D carver = new IrisCaveCarver3D(engine, + createProfile(false, false).setBaseDensityStyle(style).setDetailWeight(0D), grid); + CaveCarveScratch scratch = new CaveCarveScratch(); + Method latticeDensity = IrisCaveCarver3D.class.getDeclaredMethod("latticeDensity", + CaveCarveScratch.class, int.class, int.class, int.class); + latticeDensity.setAccessible(true); + double first = (double) latticeDensity.invoke(carver, scratch, -16, 20, 32); + grid.clear(); + double second = (double) latticeDensity.invoke(carver, scratch, -16, 20, 32); + assertNotEquals(Double.doubleToRawLongBits(first), Double.doubleToRawLongBits(second)); + assertEquals(2, samples.get()); + assertEquals(2, lookups.get()); + for (CaveDensityGrid.Column column : scratch.densityColumns) { + assertNull(column); + } + } + + @Test + public void nestedAdaptiveCarvingPreservesRetainedColumns() throws Exception { + Engine engine = createEngine(128, 110); + IrisCaveProfile profile = createProfile(true, true); + WriterCapture expected = createWriterCapture(128); + new IrisCaveCarver3D(engine, profile).carve(expected.writer, -1, 2, + fullWeights(), 0D, 0D, null, filledHeights(110)); + IrisCaveCarver3D carver = new IrisCaveCarver3D(engine, profile); + CNG original = (CNG) baseDensityField.get(carver); + CNG density = mock(CNG.class, withSettings().stubOnly()); + AtomicBoolean entered = new AtomicBoolean(); + WriterCapture nested = createWriterCapture(128); + doAnswer(invocation -> { + if (entered.compareAndSet(false, true)) { + carver.carve(nested.writer, -9, -11, fullWeights(), 0D, 0D, null, filledHeights(80)); + } + double x = invocation.getArgument(0); + double y = invocation.getArgument(1); + double z = invocation.getArgument(2); + return original.noiseFastSigned3D(x, y, z); + }).when(density).noiseFastSigned3D(anyDouble(), anyDouble(), anyDouble()); + baseDensityField.set(carver, density); + WriterCapture actual = createWriterCapture(128); + carver.carve(actual.writer, -1, 2, fullWeights(), 0D, 0D, null, filledHeights(110)); + assertTrue(entered.get()); + assertFalse(nested.carvedCells.isEmpty()); + assertEquals(expected.carvedCells, actual.carvedCells); + assertEquals(expected.carvedLiquids, actual.carvedLiquids); + } + + private CaveDensityGrid countingGrid(int capacity, AtomicInteger lookups) { + CaveDensityGrid grid = spy(new CaveDensityGrid(capacity)); + doAnswer(invocation -> { + lookups.incrementAndGet(); + return invocation.callRealMethod(); + }).when(grid).column(any(), anyInt(), anyInt(), anyInt(), anyInt()); + return grid; + } + private void assertExactParity(boolean warp, boolean modules, boolean adaptiveSampling) throws Exception { Engine engine = createEngine(96, 90); double[] columnWeights = fullWeights();