Revert "Help GC in light calculation"

This reverts commit 86a44581a5.
This commit is contained in:
DBotThePony 2023-10-04 23:32:09 +07:00
parent 86a44581a5
commit 7fcad1352e
Signed by: DBot
GPG Key ID: DCC23B5715498507
2 changed files with 139 additions and 126 deletions

View File

@ -119,7 +119,7 @@ class StarboundClient : Closeable {
var viewportTopRight = Vector2d() var viewportTopRight = Vector2d()
private set private set
var fullbright = false var fullbright = true
var clientTerminated = false var clientTerminated = false
private set private set

View File

@ -8,6 +8,7 @@ import ru.dbotthepony.kvector.arrays.Object2DArray
import ru.dbotthepony.kvector.util.linearInterpolation import ru.dbotthepony.kvector.util.linearInterpolation
import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.RGBAColor
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.util.concurrent.Callable
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.ForkJoinPool import java.util.concurrent.ForkJoinPool
@ -15,6 +16,7 @@ import java.util.concurrent.Future
import java.util.concurrent.locks.LockSupport import java.util.concurrent.locks.LockSupport
import java.util.function.Supplier import java.util.function.Supplier
import kotlin.math.roundToInt import kotlin.math.roundToInt
import kotlin.random.Random
// this implementation quite closely resembles original code, mostly // this implementation quite closely resembles original code, mostly
// because i found no other solution for light spreading // because i found no other solution for light spreading
@ -52,113 +54,58 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
} }
} }
companion object { private fun interface Getter {
private const val STRIDE = 4 * 3 /* red + green + blue */ + operator fun get(x: Int, y: Int): Grid.LightCell
1 /* isEmpty */ + 4 /* actualDropoff */
}
private val memoryPool = ConcurrentLinkedQueue<ByteBuffer>()
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 { private inner class Grid {
inner class LightCell(var x: Int, var y: Int) : ICell { inner class LightCell(val x: Int, val y: Int) : ICell {
private var actualDropoff = 0f val actualDropoff by lazy(LazyThreadSafetyMode.NONE) {
val parent = this@LightCalculator.parent.getCell(x, y) ?: return@lazy 0f
val lightBlockStrength: Float
private var index = 0 if (parent.foreground.material.renderParameters.lightTransparent) {
private var isNotEmpty = true lightBlockStrength = 0f
} else {
override var red: Float = 0f lightBlockStrength = 1f
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)
}
init {
get(x, y)
}
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 linearInterpolation(lightBlockStrength, invMaxAirSpread, invMaxObstacleSpread)
} }
fun spreadInto(target: LightCell, drop: Float, changes: Boolean): Boolean { private var empty = true
val max = red.coerceAtLeast(green).coerceAtLeast(blue) override var red: Float = 0f
if (max <= 0f) return changes override var green: Float = 0f
val newDrop = 1f - (actualDropoff - 1f) / max * drop override var blue: Float = 0f
fun spreadInto(target: LightCell, drop: Float, alreadyHadChanges: Boolean): Boolean {
val max = red.coerceAtLeast(green).coerceAtLeast(blue)
if (max <= 0f) return alreadyHadChanges
val newDrop = 1f - actualDropoff / max * drop
@Suppress("name_shadowing")
var alreadyHadChanges = alreadyHadChanges
val nred = target.red.coerceAtLeast(red * newDrop) val nred = target.red.coerceAtLeast(red * newDrop)
val ngreen = target.green.coerceAtLeast(green * newDrop) val ngreen = target.green.coerceAtLeast(green * newDrop)
val nblue = target.blue.coerceAtLeast(blue * newDrop) val nblue = target.blue.coerceAtLeast(blue * newDrop)
mem.position(target.index) if (!alreadyHadChanges)
alreadyHadChanges = nred != target.red || ngreen != target.green || nblue != target.blue
mem.putFloat(nred) target.red = nred
mem.putFloat(ngreen) target.green = ngreen
mem.putFloat(nblue) target.blue = nblue
if (!target.isNotEmpty && (nred >= epsilon || ngreen >= epsilon || nblue >= epsilon)) { if (target.empty && (target.red >= epsilon || target.blue >= epsilon || target.green >= epsilon)) {
minX = minX.coerceAtMost(target.x - 1) minX = minX.coerceAtMost(target.x - 1)
minY = minY.coerceAtMost(target.y - 1) minY = minY.coerceAtMost(target.y - 1)
maxX = maxX.coerceAtLeast(target.x + 1) maxX = maxX.coerceAtLeast(target.x + 1)
maxY = maxY.coerceAtLeast(target.y + 1) maxY = maxY.coerceAtLeast(target.y + 1)
mem.put(1) target.empty = false
clampRect() clampRect()
} }
return changes || nred != target.red || return alreadyHadChanges
ngreen != target.green ||
nblue != target.blue
} }
} }
@ -178,16 +125,23 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
clampRect() clampRect()
} }
private val mem = acquire() private val mem by lazy(LazyThreadSafetyMode.NONE) {
Object2DArray.nulls<LightCell>(width, height)
fun release() {
release(mem)
} }
operator fun get(x: Int, y: Int): LightCell { operator fun get(x: Int, y: Int): LightCell {
return LightCell(x, y) var get = mem[x, y]
if (get == null) {
get = LightCell(x, y)
mem[x, y] = get
}
return get
} }
fun get0(x: Int, y: Int) = mem[x, y]
fun safeGet(x: Int, y: Int): LightCell? { fun safeGet(x: Int, y: Int): LightCell? {
if (x in 0 until width && y in 0 until height) { if (x in 0 until width && y in 0 until height) {
return this[x, y] return this[x, y]
@ -196,45 +150,92 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
} }
} }
/*
private val mem = Int2ObjectOpenHashMap<LightCell>(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() { fun calculateSpread() {
if (minX > maxX || minY > maxY) return if (minX > maxX || minY > maxY) return
val current = LightCell(0, 0) // spread light in several passes
val target = LightCell(0, 0) var repeats = passes
var passes = passes var copy: Getter? = null
while (passes-- >= 0) { while (repeats-- >= 0) {
val minX = minX val minX = minX
val maxX = maxX val maxX = maxX
val minY = minY val minY = minY
val maxY = maxY val maxY = maxY
var changes = false var changes = false
if (copy == null) {
copy = if (maxX - minX >= width / 2 || maxY - minY >= height / 2) {
passthrough
} else {
Copy()
}
}
// bottom to top // bottom to top
for (y in minY .. maxY) { for (y in minY .. maxY) {
// left to right // left to right
for (x in minX .. maxX) { for (x in minX .. maxX) {
current[x, y] val current = copy[x, y]
changes = current.spreadInto(target[x, y + 1], 1f, changes) changes = current.spreadInto(copy[x, y + 1], 1f, changes)
changes = current.spreadInto(target[x + 1, y], 1f, changes) changes = current.spreadInto(copy[x + 1, y], 1f, changes)
changes = current.spreadInto(target[x + 1, y + 1], 1.4142135f, changes) changes = current.spreadInto(copy[x + 1, y + 1], 1.4142135f, changes)
// original code performs this spread to camouflage prism shape of light spreading // original code performs this spread to camouflage prism shape of light spreading
// we instead gonna do light pass on different diagonal // we instead gonna do light pass on different diagonal
if (quality.extraCell) if (quality.extraCell)
changes = current.spreadInto(target[x + 1, y - 1], 1.4142135f, changes) changes = current.spreadInto(copy[x + 1, y - 1], 1.4142135f, changes)
} }
// right to left // right to left
if (quality.secondDiagonal) { if (quality.secondDiagonal) {
for (x in maxX downTo minX) { for (x in maxX downTo minX) {
current[x, y] val current = copy[x, y]
changes = current.spreadInto(target[x, y + 1], 1f, changes) changes = current.spreadInto(copy[x, y + 1], 1f, changes)
changes = current.spreadInto(target[x - 1, y], 1f, changes) changes = current.spreadInto(copy[x - 1, y], 1f, changes)
changes = current.spreadInto(target[x - 1, y + 1], 1.4142135f, changes) changes = current.spreadInto(copy[x - 1, y + 1], 1.4142135f, changes)
} }
} }
} }
@ -243,36 +244,46 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
for (y in maxY downTo minY) { for (y in maxY downTo minY) {
// right to left // right to left
for (x in maxX downTo minX) { for (x in maxX downTo minX) {
current[x, y] val current = copy[x, y]
changes = current.spreadInto(target[x, y - 1], 1f, changes) changes = current.spreadInto(copy[x, y - 1], 1f, changes)
changes = current.spreadInto(target[x - 1, y], 1f, changes) changes = current.spreadInto(copy[x - 1, y], 1f, changes)
changes = current.spreadInto(target[x - 1, y - 1], 1.4142135f, changes) changes = current.spreadInto(copy[x - 1, y - 1], 1.4142135f, changes)
// original code performs this spread to camouflage prism shape of light spreading // original code performs this spread to camouflage prism shape of light spreading
// we instead gonna do light pass on different diagonal // we instead gonna do light pass on different diagonal
if (quality.extraCell) if (quality.extraCell)
changes = current.spreadInto(target[x - 1, y + 1], 1.4142135f, changes) current.spreadInto(copy[x - 1, y + 1], 1.4142135f, changes)
} }
// left to right // left to right
if (quality.secondDiagonal) { if (quality.secondDiagonal) {
for (x in minX .. maxX) { for (x in minX .. maxX) {
current[x, y] val current = this[x, y]
changes = current.spreadInto(target[x, y - 1], 1f, changes) changes = current.spreadInto(copy[x, y - 1], 1f, changes)
changes = current.spreadInto(target[x + 1, y], 1f, changes) changes = current.spreadInto(copy[x + 1, y], 1f, changes)
changes = current.spreadInto(target[x + 1, y - 1], 1.4142135f, changes) changes = current.spreadInto(copy[x + 1, y - 1], 1.4142135f, changes)
} }
} }
} }
// if our boundaries have updated, re-spread light, otherwise stop // if our boundaries have updated, re-spread light
if (minX != this.minX || maxX != this.maxX || minY != this.minY || maxY != this.maxY) { if (
passes++ minX != this.minX ||
} else if (!changes) { 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)
break break
}
} }
} }
} }
@ -464,7 +475,7 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
for (x in grid.minX - 1 .. grid.maxX) { for (x in grid.minX - 1 .. grid.maxX) {
for (y in grid.minY - 1 .. grid.maxY) { for (y in grid.minY - 1 .. grid.maxY) {
if (x !in 0 until targetWidth || y !in 0 until targetHeight) continue if (x !in 0 until targetWidth || y !in 0 until targetHeight) continue
val a = grid[x, y] val a = grid.get0(x, y) ?: continue
val pos = initialPos + (targetWidth * y + x) * 3 val pos = initialPos + (targetWidth * y + x) * 3
targetMem.position(pos) targetMem.position(pos)
@ -488,7 +499,7 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
for (x in grid.minX - 1 .. grid.maxX) { for (x in grid.minX - 1 .. grid.maxX) {
for (y in grid.minY - 1 .. grid.maxY) { for (y in grid.minY - 1 .. grid.maxY) {
val a = grid[x, y] val a = grid.get0(x, y) ?: continue
var b = mainGrid[x, y] var b = mainGrid[x, y]
if (b == null) { if (b == null) {
@ -502,8 +513,6 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
} }
} }
} }
grid.release()
} }
it.isDone it.isDone
@ -518,7 +527,9 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
val cell = grid.safeGet(light.x, light.y) ?: continue val cell = grid.safeGet(light.x, light.y) ?: continue
val speculatedSpread = (maxAirSpread * light.red.coerceAtLeast(light.green).coerceAtLeast(light.blue)).roundToInt() val speculatedSpread = (maxAirSpread * light.red.coerceAtLeast(light.green).coerceAtLeast(light.blue)).roundToInt()
cell.set(light.red, light.green, light.blue) cell.red = light.red
cell.green = light.green
cell.blue = light.blue
grid.minX = grid.minX.coerceAtMost(light.x - speculatedSpread) grid.minX = grid.minX.coerceAtMost(light.x - speculatedSpread)
grid.minY = grid.minY.coerceAtMost(light.y - speculatedSpread) grid.minY = grid.minY.coerceAtMost(light.y - speculatedSpread)
@ -535,7 +546,7 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
for (x in grid.minX - 1 .. grid.maxX) { for (x in grid.minX - 1 .. grid.maxX) {
for (y in grid.minY - 1 .. grid.maxY) { for (y in grid.minY - 1 .. grid.maxY) {
if (x !in 0 until targetWidth || y !in 0 until targetHeight) continue if (x !in 0 until targetWidth || y !in 0 until targetHeight) continue
val a = grid[x, y] val a = grid.get0(x, y) ?: continue
val (bRed, bGreen, bBlue) = a val (bRed, bGreen, bBlue) = a
targetMem.position(initialPos + (targetWidth * y + x) * 3) targetMem.position(initialPos + (targetWidth * y + x) * 3)
@ -549,7 +560,7 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
for (x in grid.minX - 1 .. grid.maxX) { for (x in grid.minX - 1 .. grid.maxX) {
for (y in grid.minY - 1 .. grid.maxY) { for (y in grid.minY - 1 .. grid.maxY) {
val a = grid[x, y] val a = grid.get0(x, y) ?: continue
mainGrid[x, y] = Cell( mainGrid[x, y] = Cell(
a.red, a.red,
@ -596,7 +607,9 @@ 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 speculatedSpread = (parent.maxAirSpread * light.red.coerceAtLeast(light.green).coerceAtLeast(light.blue)).roundToInt()
val cell = grid.safeGet(light.x, light.y) ?: continue val cell = grid.safeGet(light.x, light.y) ?: continue
cell.set(light.red, light.green, light.blue) cell.red = light.red
cell.green = light.green
cell.blue = light.blue
grid.minX = grid.minX.coerceAtMost(light.x - speculatedSpread) grid.minX = grid.minX.coerceAtMost(light.x - speculatedSpread)
grid.minY = grid.minY.coerceAtMost(light.y - speculatedSpread) grid.minY = grid.minY.coerceAtMost(light.y - speculatedSpread)