Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,12 @@ import androidx.compose.ui.viewinterop.UIKitInteropTransaction
import kotlin.math.roundToInt
import kotlinx.cinterop.*
import org.jetbrains.skia.*
import org.jetbrains.skia.Rect
import platform.Foundation.NSRunLoop
import platform.Foundation.NSSelectorFromString
import platform.Foundation.NSThread
import platform.Metal.MTLCommandBufferProtocol
import platform.QuartzCore.*
import platform.darwin.*
import org.jetbrains.skia.Rect
import platform.Foundation.NSLock
import platform.Foundation.NSRunLoopCommonModes
import platform.Foundation.NSTimeInterval
import platform.Metal.MTLCommandQueueProtocol
Expand Down Expand Up @@ -105,27 +103,6 @@ private class DisplayLinkConditions(
}
}

internal class InflightCommandBuffers(
private val maxInflightCount: Int
) {
private val lock = NSLock()
private val list = mutableListOf<MTLCommandBufferProtocol>()

fun waitUntilAllAreScheduled() = lock.doLocked {
list.fastForEach {
it.waitUntilScheduled()
}
}

fun add(commandBuffer: MTLCommandBufferProtocol) = lock.doLocked {
if (list.size == maxInflightCount) {
list.removeAt(0)
}

list.add(commandBuffer)
}
}

internal class MetalRedrawer(
private val metalLayer: CAMetalLayer,
private var retrieveInteropTransaction: () -> UIKitInteropTransaction,
Expand All @@ -149,12 +126,8 @@ internal class MetalRedrawer(
private var lastRenderTimestamp: NSTimeInterval = CACurrentMediaTime()
private val pictureRecorder = PictureRecorder()

// Semaphore for preventing command buffers count more than swapchain size to be scheduled/executed at the same time
private val inflightSemaphore =
dispatch_semaphore_create(metalLayer.maximumDrawableCount.toLong())
private val inflightCommandBuffersGroup = dispatch_group_create()
private val drawCanvasSemaphore = dispatch_semaphore_create(1)
private val inflightCommandBuffers =
InflightCommandBuffers(metalLayer.maximumDrawableCount.toInt())

var isForcedToPresentWithTransactionEveryFrame = false

Expand Down Expand Up @@ -256,7 +229,10 @@ internal class MetalRedrawer(

displayLinkConditions.isActive = newValue
if (!newValue) {
inflightCommandBuffers.waitUntilAllAreScheduled()
// If an application goes background, synchronously schedule all inflightCommandBuffers, as per
// https://developer.apple.com/documentation/metal/gpu_devices_and_work_submission/preparing_your_metal_app_to_run_in_the_background?language=objc
// Set the expiration time to 1 second to ensure that the main thread does not get stuck when the app is suspended.
dispatch_group_wait(inflightCommandBuffersGroup, dispatch_time(DISPATCH_TIME_NOW, 1L * NSEC_PER_SEC.toLong()))
}
}
}
Expand Down Expand Up @@ -305,7 +281,6 @@ internal class MetalRedrawer(
if (caDisplayLink == null) {
return
}

draw(waitUntilCompletion, CACurrentMediaTime())
}

Expand Down Expand Up @@ -374,10 +349,6 @@ internal class MetalRedrawer(
currentFrameRate = Float.NaN
}

trace("MetalRedrawer:draw:waitInflightSemaphore") {
dispatch_semaphore_wait(inflightSemaphore, DISPATCH_TIME_FOREVER)
}

val metalDrawable = trace("MetalRedrawer:draw:nextDrawable") {
metalDrawablesHandler.nextDrawable()
}
Expand All @@ -386,7 +357,6 @@ internal class MetalRedrawer(
// TODO: anomaly, log
// Logger.warn { "'metalLayer.nextDrawable()' returned null. 'metalLayer.allowsNextDrawableTimeout' should be set to false. Skipping the frame." }
picture.close()
dispatch_semaphore_signal(inflightSemaphore)
return@autoreleasepool
}

Expand All @@ -411,7 +381,6 @@ internal class MetalRedrawer(
picture.close()
renderTarget.close()
metalDrawablesHandler.releaseDrawable(metalDrawable)
dispatch_semaphore_signal(inflightSemaphore)
return@autoreleasepool
}

Expand Down Expand Up @@ -439,6 +408,9 @@ internal class MetalRedrawer(
picture.close()
surface.flushAndSubmit()

surface.close()
renderTarget.close()

if (useSeparateRenderThreadWhenPossible) {
dispatch_semaphore_signal(drawCanvasSemaphore)
}
Expand All @@ -452,9 +424,9 @@ internal class MetalRedrawer(
metalDrawablesHandler.scheduleDrawablePresentation(metalDrawable, commandBuffer)
}

dispatch_group_enter(inflightCommandBuffersGroup)
commandBuffer.addCompletedHandler {
// Signal work finish, allow a new command buffer to be scheduled
dispatch_semaphore_signal(inflightSemaphore)
dispatch_group_leave(inflightCommandBuffersGroup)
}
commandBuffer.commit()

Expand All @@ -478,12 +450,7 @@ internal class MetalRedrawer(
}
}

surface.close()
renderTarget.close()

// Track current inflight command buffers to synchronously wait for their schedule in case app goes background
inflightCommandBuffers.add(commandBuffer)

if (waitUntilCompletion) {
trace("MetalRedrawer:draw:waitUntilCompleted") {
commandBuffer.waitUntilCompleted()
Expand Down Expand Up @@ -560,13 +527,3 @@ private class DisplayLinkProxy(
callback()
}
}

private inline fun <T> NSLock.doLocked(block: () -> T): T {
lock()

try {
return block()
} finally {
unlock()
}
}