Unverified Commit 053fde05 authored by Nikolai Rykunov's avatar Nikolai Rykunov Committed by GitHub

Optimizations for Metal offscreen rendering for Swing interop (#765)

* Reuse Metal texture if size is not changed

* Reuse bitmap storage and recreate it only when size is changed

* Reuse ByteArrays if size is not changed
parent 7c12a857
......@@ -33,6 +33,12 @@ internal class MetalSwingRedrawer(
}
private val context: DirectContext = makeMetalContext()
private var texturePtr: Long = 0
private val storage = Bitmap()
private var bytesToDraw = ByteArray(0)
init {
onContextInit()
}
......@@ -40,6 +46,9 @@ internal class MetalSwingRedrawer(
private val swingOffscreenDrawer = SwingOffscreenDrawer(swingLayerProperties)
override fun dispose() {
bytesToDraw = ByteArray(0)
storage.close()
disposeMetalTexture(texturePtr)
adapter.dispose()
super.dispose()
}
......@@ -47,7 +56,8 @@ internal class MetalSwingRedrawer(
override fun onRender(g: Graphics2D, width: Int, height: Int, nanoTime: Long) {
autoreleasepool {
autoCloseScope {
val renderTarget = makeRenderTarget(width, height).autoClose()
texturePtr = makeMetalTexture(adapter.ptr, texturePtr, width, height)
val renderTarget = makeRenderTarget().autoClose()
val surface = Surface.makeFromBackendRenderTarget(
context,
renderTarget,
......@@ -71,15 +81,17 @@ internal class MetalSwingRedrawer(
val width = surface.width
val height = surface.height
val storage = Bitmap()
storage.setImageInfo(ImageInfo.makeN32Premul(width, height))
storage.allocPixels()
val dstRowBytes = width * 4
if (storage.width != width || storage.height != height) {
storage.allocPixelsFlags(ImageInfo.makeS32(width, height, ColorAlphaType.PREMUL), false)
bytesToDraw = ByteArray(storage.getReadPixelsArraySize(dstRowBytes = dstRowBytes))
}
// TODO: it copies pixels from GPU to CPU, so it is really slow
surface.readPixels(storage, 0, 0)
val bytes = storage.readPixels(storage.imageInfo, (width * 4), 0, 0)
if (bytes != null) {
swingOffscreenDrawer.draw(g, bytes, width, height)
val successfulRead = storage.readPixels(bytesToDraw, dstRowBytes = dstRowBytes)
if (successfulRead) {
swingOffscreenDrawer.draw(g, bytesToDraw, width, height)
}
}
......@@ -89,8 +101,8 @@ internal class MetalSwingRedrawer(
"Total VRAM: ${adapter.memorySize / 1024 / 1024} MB\n"
}
private fun makeRenderTarget(width: Int, height: Int) = BackendRenderTarget(
makeMetalRenderTargetOffScreen(adapter.ptr, width, height)
private fun makeRenderTarget() = BackendRenderTarget(
makeMetalRenderTargetOffScreen(texturePtr)
)
private fun makeMetalContext(): DirectContext = DirectContext(
......@@ -99,5 +111,13 @@ internal class MetalSwingRedrawer(
private external fun makeMetalContext(adapter: Long): Long
private external fun makeMetalRenderTargetOffScreen(adapter: Long, width: Int, height: Int): Long
private external fun makeMetalRenderTargetOffScreen(texture: Long): Long
/**
* Provides Metal texture taking given [oldTexture] into account
* since it can be reused if width and height are not changed,
* or the new one will be created.
*/
private external fun makeMetalTexture(adapter: Long, oldTexture: Long, width: Int, height: Int): Long
private external fun disposeMetalTexture(texture: Long): Long
}
\ No newline at end of file
......@@ -26,17 +26,38 @@ JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_swing_MetalSwingRedrawer_makeMe
}
}
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_swing_MetalSwingRedrawer_makeMetalTexture(
JNIEnv *env, jobject contextHandler, jlong adapterPtr, jlong oldTexturePtr, jint width, jint height
) {
@autoreleasepool {
id <MTLTexture> oldTexture = (__bridge_transfer id <MTLTexture>) (void *) oldTexturePtr;
id <MTLTexture> metalTexture;
if (oldTexture == nil || oldTexture.width != width || oldTexture.height != height) {
id <MTLDevice> adapter = (__bridge id <MTLDevice>) (void *) adapterPtr;
MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm width:width height:height mipmapped:NO];
metalTexture = [adapter newTextureWithDescriptor:textureDescriptor];
} else {
metalTexture = oldTexture;
}
return (jlong) (__bridge_retained void *) metalTexture;
}
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_swing_MetalSwingRedrawer_disposeMetalTexture(JNIEnv *env, jobject contextHandler, jlong texturePtr) {
@autoreleasepool {
id <MTLTexture> oldTexture = (__bridge_transfer id <MTLTexture>) (void *) texturePtr;
}
}
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_swing_MetalSwingRedrawer_makeMetalRenderTargetOffScreen(
JNIEnv *env, jobject contextHandler, jlong adapterPtr, jint width, jint height) {
JNIEnv *env, jobject contextHandler, jlong texturePtr) {
@autoreleasepool {
id <MTLDevice> adapter = (__bridge id <MTLDevice>) (void *) adapterPtr;
MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm width:width height:height mipmapped:NO];
// TODO: use double buffer
id <MTLTexture> metalTexture = [adapter newTextureWithDescriptor:textureDescriptor];
id <MTLTexture> texture = (__bridge id <MTLTexture>) (void *) texturePtr;
GrMtlTextureInfo info;
info.fTexture.retain((__bridge GrMTLHandle) metalTexture);
info.fTexture.retain((__bridge GrMTLHandle) texture);
GrBackendRenderTarget *renderTarget = NULL;
renderTarget = new GrBackendRenderTarget(width, height, 0, info);
renderTarget = new GrBackendRenderTarget(texture.width, texture.height, 0, info);
return (jlong) renderTarget;
}
}
......
......@@ -918,12 +918,32 @@ class Bitmap internal constructor(ptr: NativePointer) : Managed(ptr, _FinalizerH
srcX: Int = 0,
srcY: Int = 0
): ByteArray? {
return try {
val size = min(dstInfo.height, height - srcY) * dstRowBytes
val size = getReadPixelsArraySize(dstInfo, dstRowBytes, srcY)
val bitmapPixels = ByteArray(size)
val successfulRead = readPixels(bitmapPixels, dstInfo, dstRowBytes, srcX, srcY)
return bitmapPixels.takeIf { successfulRead }
}
/**
* See documentation for [readPixels]
*
* @param byteArray array where pixels will be read.
*/
internal fun readPixels(
byteArray: ByteArray,
dstInfo: ImageInfo = imageInfo,
dstRowBytes: Int = rowBytes,
srcX: Int = 0,
srcY: Int = 0
): Boolean {
check(byteArray.size == getReadPixelsArraySize(dstInfo, dstRowBytes, srcY)) {
"byteArray is not properly allocated. Use readPixelsArraySize"
}
try {
Stats.onNativeCall()
withNullableResult(ByteArray(size)) {
_nReadPixels(
interopScope {
val byteArrayHandle = toInteropForResult(byteArray)
val successfulRead = _nReadPixels(
_ptr,
dstInfo.width,
dstInfo.height,
......@@ -933,8 +953,12 @@ class Bitmap internal constructor(ptr: NativePointer) : Managed(ptr, _FinalizerH
dstRowBytes,
srcX,
srcY,
it
byteArrayHandle
)
if (successfulRead) {
byteArrayHandle.fromInterop(byteArray)
}
return successfulRead
}
} finally {
reachabilityBarrier(this)
......@@ -942,6 +966,12 @@ class Bitmap internal constructor(ptr: NativePointer) : Managed(ptr, _FinalizerH
}
}
internal fun getReadPixelsArraySize(
dstInfo: ImageInfo = imageInfo,
dstRowBytes: Int = rowBytes,
srcY: Int = 0
): Int = min(dstInfo.height, height - srcY) * dstRowBytes
/**
*
* Sets dst to alpha described by pixels. Returns false if dst cannot
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment