Skip to content

Commit

Permalink
Reuse cached MTLCommandQueue across multiple MetalRedrawer's (#1127)
Browse files Browse the repository at this point in the history
## Proposed Changes

`MTLCommandQueue` is not a transient object and internal pools of them
can be starved. It leads to Metal being unable to allocate a new one
when GC hasn't collected the previously created queues.
Cache it for reuse purposes.

## Testing

Test: N/A

## Issues Fixed

Fixes:
[COMPOSE-1022](https://youtrack.jetbrains.com/issue/COMPOSE-1022/)
  • Loading branch information
elijah-semyonov authored and igordmn committed Feb 21, 2024
1 parent a3e3eea commit 6573c28
Showing 1 changed file with 45 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import org.jetbrains.skia.Rect
import platform.Foundation.NSLock
import platform.Foundation.NSRunLoopCommonModes
import platform.Foundation.NSTimeInterval
import platform.Metal.MTLCommandQueueProtocol
import platform.Metal.MTLDeviceProtocol
import platform.UIKit.UIApplication
import platform.UIKit.UIApplicationState

Expand Down Expand Up @@ -197,8 +199,7 @@ internal class MetalRedrawer(
@Suppress("USELESS_CAST")
private val device = metalLayer.device as platform.Metal.MTLDeviceProtocol?
?: throw IllegalStateException("CAMetalLayer.device can not be null")
private val queue = device.newCommandQueue()
?: throw IllegalStateException("Couldn't create Metal command queue")
private val queue = getCachedCommandQueue(device)
private val context = DirectContext.makeMetal(device.objcPtr(), queue.objcPtr())
private var lastRenderTimestamp: NSTimeInterval = CACurrentMediaTime()
private val pictureRecorder = PictureRecorder()
Expand Down Expand Up @@ -300,6 +301,8 @@ internal class MetalRedrawer(
fun dispose() {
check(caDisplayLink != null) { "MetalRedrawer.dispose() was called more than once" }

releaseCachedCommandQueue(queue)

applicationStateListener.dispose()

caDisplayLink?.invalidate()
Expand Down Expand Up @@ -461,6 +464,46 @@ internal class MetalRedrawer(
companion object {
private val renderingDispatchQueue =
dispatch_queue_create("RenderingDispatchQueue", null)

private class CachedCommandQueue(
val queue: MTLCommandQueueProtocol,
var refCount: Int = 1
)

/**
* Cached command queue record. Assumed to be associated with default MTLDevice.
*/
private var cachedCommandQueue: CachedCommandQueue? = null

/**
* Get an existing command queue associated with the device or create a new one and cache it.
* Assumed to be run on the main thread.
*/
private fun getCachedCommandQueue(device: MTLDeviceProtocol): MTLCommandQueueProtocol {
val cached = cachedCommandQueue
if (cached != null) {
cached.refCount++
return cached.queue
} else {
val queue = device.newCommandQueue() ?: throw IllegalStateException("MTLDevice.newCommandQueue() returned null")
cachedCommandQueue = CachedCommandQueue(queue)
return queue
}
}

/**
* Release the cached command queue. Release the cache if refCount reaches 0.
* Assumed to be run on the main thread.
*/
private fun releaseCachedCommandQueue(queue: MTLCommandQueueProtocol) {
val cached = cachedCommandQueue ?: return
if (cached.queue == queue) {
cached.refCount--
if (cached.refCount == 0) {
cachedCommandQueue = null
}
}
}
}
}

Expand Down

0 comments on commit 6573c28

Please sign in to comment.