From 86a44581a5a38da3c7d5418a7fb54b6d9e5eda34 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Wed, 4 Oct 2023 22:50:23 +0700 Subject: [PATCH] Help GC in light calculation --- .../kstarbound/client/StarboundClient.kt | 2 +- .../kstarbound/world/LightCalculator.kt | 261 +++++++++--------- 2 files changed, 125 insertions(+), 138 deletions(-) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt index ac21dad0..92a620ce 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt @@ -119,7 +119,7 @@ class StarboundClient : Closeable { var viewportTopRight = Vector2d() private set - var fullbright = true + var fullbright = false var clientTerminated = false private set diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/LightCalculator.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/LightCalculator.kt index 5228934f..3686ad6c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/LightCalculator.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/LightCalculator.kt @@ -8,7 +8,6 @@ import ru.dbotthepony.kvector.arrays.Object2DArray import ru.dbotthepony.kvector.util.linearInterpolation import ru.dbotthepony.kvector.vector.RGBAColor import java.nio.ByteBuffer -import java.util.concurrent.Callable import java.util.concurrent.CompletableFuture import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ForkJoinPool @@ -16,7 +15,6 @@ import java.util.concurrent.Future import java.util.concurrent.locks.LockSupport import java.util.function.Supplier import kotlin.math.roundToInt -import kotlin.random.Random // this implementation quite closely resembles original code, mostly // because i found no other solution for light spreading @@ -54,58 +52,113 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int) } } - private fun interface Getter { - operator fun get(x: Int, y: Int): Grid.LightCell + companion object { + private const val STRIDE = 4 * 3 /* red + green + blue */ + + 1 /* isEmpty */ + 4 /* actualDropoff */ + } + + private val memoryPool = ConcurrentLinkedQueue() + + private fun acquire(): ByteBuffer { + // using direct buffer because memset is very likely to be more efficient than Arrays.fill + val first = memoryPool.poll() ?: return ByteBuffer.allocateDirect(STRIDE * width * height) + first.position(0) + BufferUtils.zeroBuffer(first) + return first + } + + private fun release(mem: ByteBuffer) { + memoryPool.add(mem) } private inner class Grid { - inner class LightCell(val x: Int, val y: Int) : ICell { - val actualDropoff by lazy(LazyThreadSafetyMode.NONE) { - val parent = this@LightCalculator.parent.getCell(x, y) ?: return@lazy 0f - val lightBlockStrength: Float + inner class LightCell(var x: Int, var y: Int) : ICell { + private var actualDropoff = 0f - if (parent.foreground.material.renderParameters.lightTransparent) { - lightBlockStrength = 0f - } else { - lightBlockStrength = 1f - } + private var index = 0 + private var isNotEmpty = true - linearInterpolation(lightBlockStrength, invMaxAirSpread, invMaxObstacleSpread) + override var red: Float = 0f + private set + override var green: Float = 0f + private set + override var blue: Float = 0f + private set + + fun set(red: Float, green: Float, blue: Float) { + mem.position(index) + mem.putFloat(red) + mem.putFloat(green) + mem.putFloat(blue) } - private var empty = true - override var red: Float = 0f - override var green: Float = 0f - override var blue: Float = 0f + init { + get(x, y) + } - fun spreadInto(target: LightCell, drop: Float, alreadyHadChanges: Boolean): Boolean { + operator fun get(x: Int, y: Int): LightCell { + index = (x + y * width) * STRIDE + + this.x = x + this.y = y + + mem.position(index) + + red = mem.getFloat() + green = mem.getFloat() + blue = mem.getFloat() + isNotEmpty = mem.get() > 0 + + actualDropoff = mem.getFloat() + + if (actualDropoff == 0f) { + actualDropoff = run { + val parent = this@LightCalculator.parent.getCell(x, y) ?: return@run 1f + val lightBlockStrength: Float + + if (parent.foreground.material.renderParameters.lightTransparent) { + lightBlockStrength = 0f + } else { + lightBlockStrength = 1f + } + + 1f + linearInterpolation(lightBlockStrength, invMaxAirSpread, invMaxObstacleSpread) + } + + mem.position(mem.position() - 4) + mem.putFloat(actualDropoff) + } + + return this + } + + fun spreadInto(target: LightCell, drop: Float, changes: Boolean): Boolean { val max = red.coerceAtLeast(green).coerceAtLeast(blue) - if (max <= 0f) return alreadyHadChanges - val newDrop = 1f - actualDropoff / max * drop + if (max <= 0f) return changes + val newDrop = 1f - (actualDropoff - 1f) / max * drop - @Suppress("name_shadowing") - var alreadyHadChanges = alreadyHadChanges val nred = target.red.coerceAtLeast(red * newDrop) val ngreen = target.green.coerceAtLeast(green * newDrop) val nblue = target.blue.coerceAtLeast(blue * newDrop) - if (!alreadyHadChanges) - alreadyHadChanges = nred != target.red || ngreen != target.green || nblue != target.blue + mem.position(target.index) - target.red = nred - target.green = ngreen - target.blue = nblue + mem.putFloat(nred) + mem.putFloat(ngreen) + mem.putFloat(nblue) - if (target.empty && (target.red >= epsilon || target.blue >= epsilon || target.green >= epsilon)) { + if (!target.isNotEmpty && (nred >= epsilon || ngreen >= epsilon || nblue >= epsilon)) { minX = minX.coerceAtMost(target.x - 1) minY = minY.coerceAtMost(target.y - 1) maxX = maxX.coerceAtLeast(target.x + 1) maxY = maxY.coerceAtLeast(target.y + 1) - target.empty = false + mem.put(1) clampRect() } - return alreadyHadChanges + return changes || nred != target.red || + ngreen != target.green || + nblue != target.blue } } @@ -125,23 +178,16 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int) clampRect() } - private val mem by lazy(LazyThreadSafetyMode.NONE) { - Object2DArray.nulls(width, height) + private val mem = acquire() + + fun release() { + release(mem) } operator fun get(x: Int, y: Int): LightCell { - var get = mem[x, y] - - if (get == null) { - get = LightCell(x, y) - mem[x, y] = get - } - - return get + return LightCell(x, y) } - fun get0(x: Int, y: Int) = mem[x, y] - fun safeGet(x: Int, y: Int): LightCell? { if (x in 0 until width && y in 0 until height) { return this[x, y] @@ -150,92 +196,45 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int) } } - /* - private val mem = Int2ObjectOpenHashMap(2048) - - operator fun get(x: Int, y: Int): LightCell { - var get = mem[x shl 16 or y] - - if (get == null) { - get = LightCell(x, y) - mem[x shl 16 or y] = get - } - - return get - } - - fun get0(x: Int, y: Int) = mem[x shl 16 or y] - - fun safeGet(x: Int, y: Int): LightCell? { - if (x in 0 until width && y in 0 until height) { - return this[x, y] - } else { - return null - } - }*/ - - private inner class Copy : Getter { - private val minX = this@Grid.minX - private val maxX = this@Grid.maxX - private val minY = this@Grid.minY - private val maxY = this@Grid.maxY - - private val mem = Object2DArray(maxX - minX + 3, maxY - minY + 3) { a, b -> this@Grid[a + minX - 1, b + minY - 1] } - - override fun get(x: Int, y: Int): LightCell { - return mem[x - minX + 1, y - minY + 1] - } - } - - private val passthrough = Getter { x, y -> this@Grid[x, y] } - fun calculateSpread() { if (minX > maxX || minY > maxY) return - // spread light in several passes - var repeats = passes + val current = LightCell(0, 0) + val target = LightCell(0, 0) - var copy: Getter? = null + var passes = passes - while (repeats-- >= 0) { + while (passes-- >= 0) { val minX = minX val maxX = maxX val minY = minY val maxY = maxY var changes = false - if (copy == null) { - copy = if (maxX - minX >= width / 2 || maxY - minY >= height / 2) { - passthrough - } else { - Copy() - } - } - // bottom to top for (y in minY .. maxY) { // left to right for (x in minX .. maxX) { - val current = copy[x, y] + current[x, y] - changes = current.spreadInto(copy[x, y + 1], 1f, changes) - changes = current.spreadInto(copy[x + 1, y], 1f, changes) - changes = current.spreadInto(copy[x + 1, y + 1], 1.4142135f, changes) + changes = current.spreadInto(target[x, y + 1], 1f, changes) + changes = current.spreadInto(target[x + 1, y], 1f, changes) + changes = current.spreadInto(target[x + 1, y + 1], 1.4142135f, changes) // original code performs this spread to camouflage prism shape of light spreading // we instead gonna do light pass on different diagonal if (quality.extraCell) - changes = current.spreadInto(copy[x + 1, y - 1], 1.4142135f, changes) + changes = current.spreadInto(target[x + 1, y - 1], 1.4142135f, changes) } // right to left if (quality.secondDiagonal) { for (x in maxX downTo minX) { - val current = copy[x, y] + current[x, y] - changes = current.spreadInto(copy[x, y + 1], 1f, changes) - changes = current.spreadInto(copy[x - 1, y], 1f, changes) - changes = current.spreadInto(copy[x - 1, y + 1], 1.4142135f, changes) + changes = current.spreadInto(target[x, y + 1], 1f, changes) + changes = current.spreadInto(target[x - 1, y], 1f, changes) + changes = current.spreadInto(target[x - 1, y + 1], 1.4142135f, changes) } } } @@ -244,46 +243,36 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int) for (y in maxY downTo minY) { // right to left for (x in maxX downTo minX) { - val current = copy[x, y] + current[x, y] - changes = current.spreadInto(copy[x, y - 1], 1f, changes) - changes = current.spreadInto(copy[x - 1, y], 1f, changes) - changes = current.spreadInto(copy[x - 1, y - 1], 1.4142135f, changes) + changes = current.spreadInto(target[x, y - 1], 1f, changes) + changes = current.spreadInto(target[x - 1, y], 1f, changes) + changes = current.spreadInto(target[x - 1, y - 1], 1.4142135f, changes) // original code performs this spread to camouflage prism shape of light spreading // we instead gonna do light pass on different diagonal if (quality.extraCell) - current.spreadInto(copy[x - 1, y + 1], 1.4142135f, changes) + changes = current.spreadInto(target[x - 1, y + 1], 1.4142135f, changes) } // left to right if (quality.secondDiagonal) { for (x in minX .. maxX) { - val current = this[x, y] + current[x, y] - changes = current.spreadInto(copy[x, y - 1], 1f, changes) - changes = current.spreadInto(copy[x + 1, y], 1f, changes) - changes = current.spreadInto(copy[x + 1, y - 1], 1.4142135f, changes) + changes = current.spreadInto(target[x, y - 1], 1f, changes) + changes = current.spreadInto(target[x + 1, y], 1f, changes) + changes = current.spreadInto(target[x + 1, y - 1], 1.4142135f, changes) } } } - // if our boundaries have updated, re-spread light - if ( - minX != this.minX || - maxX != this.maxX || - minY != this.minY || - maxY != this.maxY - ) { - repeats++ - - copy = if (this.maxX - this.minX >= width / 2 || this.maxY - this.minY >= height / 2) { - passthrough - } else { - Copy() - } - } else if (!changes) + // if our boundaries have updated, re-spread light, otherwise stop + if (minX != this.minX || maxX != this.maxX || minY != this.minY || maxY != this.maxY) { + passes++ + } else if (!changes) { break + } } } } @@ -475,7 +464,7 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int) for (x in grid.minX - 1 .. grid.maxX) { for (y in grid.minY - 1 .. grid.maxY) { if (x !in 0 until targetWidth || y !in 0 until targetHeight) continue - val a = grid.get0(x, y) ?: continue + val a = grid[x, y] val pos = initialPos + (targetWidth * y + x) * 3 targetMem.position(pos) @@ -499,7 +488,7 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int) for (x in grid.minX - 1 .. grid.maxX) { for (y in grid.minY - 1 .. grid.maxY) { - val a = grid.get0(x, y) ?: continue + val a = grid[x, y] var b = mainGrid[x, y] if (b == null) { @@ -513,6 +502,8 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int) } } } + + grid.release() } it.isDone @@ -527,9 +518,7 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int) val cell = grid.safeGet(light.x, light.y) ?: continue val speculatedSpread = (maxAirSpread * light.red.coerceAtLeast(light.green).coerceAtLeast(light.blue)).roundToInt() - cell.red = light.red - cell.green = light.green - cell.blue = light.blue + cell.set(light.red, light.green, light.blue) grid.minX = grid.minX.coerceAtMost(light.x - speculatedSpread) grid.minY = grid.minY.coerceAtMost(light.y - speculatedSpread) @@ -546,7 +535,7 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int) for (x in grid.minX - 1 .. grid.maxX) { for (y in grid.minY - 1 .. grid.maxY) { if (x !in 0 until targetWidth || y !in 0 until targetHeight) continue - val a = grid.get0(x, y) ?: continue + val a = grid[x, y] val (bRed, bGreen, bBlue) = a targetMem.position(initialPos + (targetWidth * y + x) * 3) @@ -560,7 +549,7 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int) for (x in grid.minX - 1 .. grid.maxX) { for (y in grid.minY - 1 .. grid.maxY) { - val a = grid.get0(x, y) ?: continue + val a = grid[x, y] mainGrid[x, y] = Cell( a.red, @@ -607,9 +596,7 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int) val speculatedSpread = (parent.maxAirSpread * light.red.coerceAtLeast(light.green).coerceAtLeast(light.blue)).roundToInt() val cell = grid.safeGet(light.x, light.y) ?: continue - cell.red = light.red - cell.green = light.green - cell.blue = light.blue + cell.set(light.red, light.green, light.blue) grid.minX = grid.minX.coerceAtMost(light.x - speculatedSpread) grid.minY = grid.minY.coerceAtMost(light.y - speculatedSpread)