Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Substantially improve IPC performance #2246

Merged
merged 5 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading