Skip to content

Commit

Permalink
Substantially improve IPC performance (#2246)
Browse files Browse the repository at this point in the history
  • Loading branch information
soywiz committed Jun 20, 2024
1 parent 681ceec commit c947b02
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 45 deletions.
63 changes: 42 additions & 21 deletions korge-core/src/korlibs/graphics/gl/AGOpengl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,8 @@ class AGOpengl(val gl: KmlGl, var context: KmlGlContext? = null) : AG() {
}
}

private var tempReadMemory = Buffer.allocDirect(0)

override fun readToMemory(frameBuffer: AGFrameBufferBase, frameBufferInfo: AGFrameBufferInfo, x: Int, y: Int, width: Int, height: Int, data: Any, kind: AGReadKind) {
val gl: KmlGl = this.gl

Expand All @@ -789,35 +791,54 @@ class AGOpengl(val gl: KmlGl, var context: KmlGlContext? = null) : AG() {
is IntArray -> 4
is FloatArray -> 4
is ByteArray -> 1
else -> TODO()
else -> when (kind) {
AGReadKind.COLOR -> 4
AGReadKind.DEPTH -> 4
AGReadKind.STENCIL -> 1
else -> 1
}
}
val flipY = frameBuffer.isMain
val area = width * height
val stride = width * bytesPerPixel
BufferTemp(height * stride) { buffer ->
BufferTemp(stride) { temp ->
when (kind) {
AGReadKind.COLOR -> gl.readPixels(region.x, region.y, region.width, region.height, KmlGl.RGBA, KmlGl.UNSIGNED_BYTE, buffer)
AGReadKind.DEPTH -> gl.readPixels(region.x, region.y, region.width, region.height, KmlGl.DEPTH_COMPONENT, KmlGl.FLOAT, buffer)
AGReadKind.STENCIL -> gl.readPixels(region.x, region.y, region.width, region.height, KmlGl.STENCIL_INDEX, KmlGl.UNSIGNED_BYTE, buffer)
}
when (data) {
is IntArray -> buffer.getArrayInt32(0, data, size = area)
is FloatArray -> buffer.getArrayFloat32(0, data, size = area)
is ByteArray -> buffer.getArrayInt8(0, data, size = area)
else -> TODO()
}
val nbytes = height * stride
if (tempReadMemory.sizeInBytes < nbytes) {
tempReadMemory = Buffer.allocDirect(nbytes)
}
val buffer = tempReadMemory
when (kind) {
AGReadKind.COLOR -> gl.readPixels(region.x, region.y, region.width, region.height, KmlGl.RGBA, KmlGl.UNSIGNED_BYTE, buffer)
AGReadKind.DEPTH -> gl.readPixels(region.x, region.y, region.width, region.height, KmlGl.DEPTH_COMPONENT, KmlGl.FLOAT, buffer)
AGReadKind.STENCIL -> gl.readPixels(region.x, region.y, region.width, region.height, KmlGl.STENCIL_INDEX, KmlGl.UNSIGNED_BYTE, buffer)
}
when (data) {
is IntArray -> buffer.getS32Array(0 * Int.SIZE_BYTES, data, size = area)
is FloatArray -> buffer.getF32Array(0 * Float.SIZE_BYTES, data, size = area)
is ByteArray -> buffer.getS8Array(0 * Byte.SIZE_BYTES, data, size = area)
is Buffer -> {
if (flipY) {
when (data) {
is IntArray -> Bitmap32(width, height, RgbaArray(data))
is FloatArray -> FloatBitmap32(width, height, data)
is ByteArray -> Bitmap8(width, height, data)
else -> TODO()
}.flipY()
//arraycopy(buffer, 0, data, 0, nbytes)
for (n in 0 until height) {
arraycopy(buffer, (height - 1 - n) * stride, data, n * stride, stride)
}
} else {
arraycopy(buffer, 0, data, 0, nbytes)
}
}
//println("readColor.HASH:" + bitmap.computeHash())
else -> TODO()
}
if (flipY) {
when (data) {
is IntArray -> Bitmap32(width, height, RgbaArray(data))
is FloatArray -> FloatBitmap32(width, height, data)
is ByteArray -> Bitmap8(width, height, data)
else -> {
// Not flipping Buffer
null
}
}?.flipY()
}
//println("readColor.HASH:" + bitmap.computeHash())
}

fun readPixelsToTexture(tex: AGTexture, x: Int, y: Int, width: Int, height: Int, kind: AGReadKind) {
Expand Down
44 changes: 33 additions & 11 deletions korge-ipc/src/main/kotlin/korlibs/korge/ipc/KorgeIPC.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ class KorgeIPC(val path: String = System.getenv("KORGE_IPC") ?: DEFAULT_PATH) {
val events = KorgeEventsBuffer("$path.events")

val availableEvents get() = events.availableRead
fun resetEvents() {
events.reset()
}
fun writeEvent(e: IPCEvent) = events.writeEvent(e)
fun readEvent(e: IPCEvent = IPCEvent()): IPCEvent? = events.readEvent(e)
fun setFrame(f: IPCFrame) = frame.setFrame(f)
Expand All @@ -40,6 +43,8 @@ data class IPCEvent(

companion object {
val RESIZE = 1
val BRING_BACK = 2
val BRING_FRONT = 3

val MOUSE_MOVE = 10
val MOUSE_DOWN = 11
Expand All @@ -60,7 +65,7 @@ class KorgeEventsBuffer(val path: String) {
var buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0L, (HEAD_SIZE + EVENT_SIZE * MAX_EVENTS).toLong())

init {
File(path).deleteOnExit()
//File(path).deleteOnExit()
}

var readPos: Long by DelegateBufferLong(buffer, 0)
Expand Down Expand Up @@ -112,6 +117,11 @@ class KorgeEventsBuffer(val path: String) {
channel.close()
}

fun delete() {
close()
File(path).delete()
}

class DelegateBufferLong(val buffer: ByteBuffer, val index: Int) {
operator fun getValue(obj: Any, property: KProperty<*>): Long = buffer.getLong(index)
operator fun setValue(obj: Any, property: KProperty<*>, value: Long) { buffer.putLong(index, value) }
Expand All @@ -123,54 +133,66 @@ class KorgeEventsBuffer(val path: String) {
}
}

class IPCFrame(val id: Int, val width: Int, val height: Int, val pixels: IntArray = IntArray(width * height))
class IPCFrame(val id: Int, val width: Int, val height: Int, val pixels: IntArray = IntArray(0), val buffer: IntBuffer? = null, val pid: Int = -1, val version: Int = -1) {
}

class KorgeFrameBuffer(val path: String) {
val channel = FileChannel.open(Path.of(path), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE)
var width: Int = 0
var height: Int = 0
var buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0L, 16 + 0)
var buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0L, 32 + 0)
var ibuffer = buffer.asIntBuffer()

init {
File(path).deleteOnExit()
}

fun ensureSize(width: Int, height: Int) {
if (this.width < width || this.height < height) {
this.width = width
this.height = height
buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0L, (16 + (width * height * 4)).toLong())
buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0L, (32 + (width * height * 4)).toLong())
ibuffer = buffer.asIntBuffer()
}
}

val currentProcessId = ProcessHandle.current().pid().toInt()

fun setFrame(frame: IPCFrame) {
ensureSize(frame.width, frame.height)
ibuffer.clear()
ibuffer.put(0) // version
ibuffer.put(currentProcessId)
ibuffer.put(frame.id)
ibuffer.put(frame.width)
ibuffer.put(frame.height)
ibuffer.put(frame.pixels)
if (frame.buffer != null) {
ibuffer.put(frame.buffer)
} else {
ibuffer.put(frame.pixels)
}
}

fun getFrameId(): Int {
ibuffer.clear()
return ibuffer.get()
return ibuffer.get(2)
}

fun getFrame(): IPCFrame {
ibuffer.clear()
val version = ibuffer.get()
val pid = ibuffer.get()
val id = ibuffer.get()
val width = ibuffer.get()
val height = ibuffer.get()
ensureSize(width, height)
val pixels = IntArray(width * height)
ibuffer.get(pixels)
return IPCFrame(id, width, height, pixels)
return IPCFrame(id, width, height, pixels, pid = pid, version = version)
}

fun close() {
channel.close()
}

fun delete() {
close()
File(path).delete()
}
}
74 changes: 61 additions & 13 deletions korge/src@jvm/korlibs/korge/KorgeExtJvm.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@ import korlibs.event.*
import korlibs.graphics.*
import korlibs.image.bitmap.*
import korlibs.image.color.*
import korlibs.time.*
import korlibs.korge.awt.*
import korlibs.korge.awt.views
import korlibs.korge.ipc.*
import korlibs.korge.render.*
import korlibs.korge.time.*
import korlibs.korge.view.*
import korlibs.korge.view.Ellipse
import korlibs.korge.view.Image
import korlibs.math.geom.*
import korlibs.memory.*
import korlibs.render.awt.*
import korlibs.time.*
import kotlinx.coroutines.*
import java.awt.Container
import java.util.ServiceLoader
import java.util.*

interface ViewsCompleter {
fun completeViews(views: Views)
Expand Down Expand Up @@ -68,18 +70,56 @@ class IPCViewsCompleter : ViewsCompleter {
views.onBeforeRender {
while (ipc.availableEvents > 0) {
val e = ipc.readEvent() ?: break
if (e.timestamp < System.currentTimeMillis() - 100) continue
//if (e.timestamp < System.currentTimeMillis() - 100) continue
if (e.timestamp < System.currentTimeMillis() - 100 && e.type != IPCEvent.RESIZE && e.type != IPCEvent.BRING_BACK && e.type != IPCEvent.BRING_FRONT) continue // @TODO: BRING_BACK/BRING_FRONT

when (e.type) {
IPCEvent.KEY_DOWN, IPCEvent.KEY_UP -> {
views.dispatch(
KeyEvent(when (e.type) {
IPCEvent.KEY_DOWN -> KeyEvent.Type.DOWN
IPCEvent.KEY_UP -> KeyEvent.Type.UP
else -> KeyEvent.Type.DOWN
}, key = awtKeyCodeToKey(e.p0)
)
views.gameWindow.dispatchKeyEvent(
type = when (e.type) {
IPCEvent.KEY_DOWN -> KeyEvent.Type.DOWN
IPCEvent.KEY_UP -> KeyEvent.Type.UP
else -> KeyEvent.Type.DOWN
},
id = 0,
key = awtKeyCodeToKey(e.p0),
character = e.p1.toChar(),
keyCode = e.p0,
str = null,
)
}
IPCEvent.MOUSE_MOVE, IPCEvent.MOUSE_DOWN, IPCEvent.MOUSE_UP, IPCEvent.MOUSE_CLICK -> {
views.gameWindow.dispatchMouseEvent(
id = 0,
type = when (e.type) {
IPCEvent.MOUSE_CLICK -> MouseEvent.Type.CLICK
IPCEvent.MOUSE_MOVE -> MouseEvent.Type.MOVE
IPCEvent.MOUSE_DOWN -> MouseEvent.Type.UP
IPCEvent.MOUSE_UP -> MouseEvent.Type.UP
else -> MouseEvent.Type.DOWN
}, x = e.p0, y = e.p1,
button = MouseButton[e.p2]
)
//println(e)
}
IPCEvent.RESIZE -> {
val awtGameWindow = (views.gameWindow as? AwtGameWindow?)
if (awtGameWindow != null) {
awtGameWindow.frame.setSize(e.p0, e.p1)
} else {
views.resized(e.p0, e.p1)
}
//
}
IPCEvent.BRING_BACK, IPCEvent.BRING_FRONT -> {
val awtGameWindow = (views.gameWindow as? AwtGameWindow?)
if (awtGameWindow != null) {
if (e.type == IPCEvent.BRING_BACK) {
awtGameWindow.frame.toBack()
} else {
awtGameWindow.frame.toFront()
}
}
}
else -> {
println(e)
Expand All @@ -88,10 +128,18 @@ class IPCViewsCompleter : ViewsCompleter {
}
}

var fbMem = Buffer(0, direct = true)

views.onAfterRender {
val bmp = it.ag.readColor(it.currentFrameBuffer)
val fb = it.currentFrameBufferOrMain
val nbytes = fb.width * fb.height * 4
if (fbMem.size < nbytes) {
fbMem = Buffer(nbytes, direct = true)
}
it.ag.readToMemory(fb.base, fb.info, 0, 0, fb.width, fb.height, fbMem, AGReadKind.COLOR)
//val bmp = it.ag.readColor(it.currentFrameBuffer)
//channel.trySend(bmp)
ipc.setFrame(IPCFrame(System.currentTimeMillis().toInt(), bmp.width, bmp.height, bmp.ints))
ipc.setFrame(IPCFrame(System.currentTimeMillis().toInt(), fb.width, fb.height, IntArray(0), fbMem.sliceWithSize(0, nbytes).nioIntBuffer))
}
}
}
Expand Down

0 comments on commit c947b02

Please sign in to comment.