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( ...@@ -33,6 +33,12 @@ internal class MetalSwingRedrawer(
} }
private val context: DirectContext = makeMetalContext() private val context: DirectContext = makeMetalContext()
private var texturePtr: Long = 0
private val storage = Bitmap()
private var bytesToDraw = ByteArray(0)
init { init {
onContextInit() onContextInit()
} }
...@@ -40,6 +46,9 @@ internal class MetalSwingRedrawer( ...@@ -40,6 +46,9 @@ internal class MetalSwingRedrawer(
private val swingOffscreenDrawer = SwingOffscreenDrawer(swingLayerProperties) private val swingOffscreenDrawer = SwingOffscreenDrawer(swingLayerProperties)
override fun dispose() { override fun dispose() {
bytesToDraw = ByteArray(0)
storage.close()
disposeMetalTexture(texturePtr)
adapter.dispose() adapter.dispose()
super.dispose() super.dispose()
} }
...@@ -47,7 +56,8 @@ internal class MetalSwingRedrawer( ...@@ -47,7 +56,8 @@ internal class MetalSwingRedrawer(
override fun onRender(g: Graphics2D, width: Int, height: Int, nanoTime: Long) { override fun onRender(g: Graphics2D, width: Int, height: Int, nanoTime: Long) {
autoreleasepool { autoreleasepool {
autoCloseScope { autoCloseScope {
val renderTarget = makeRenderTarget(width, height).autoClose() texturePtr = makeMetalTexture(adapter.ptr, texturePtr, width, height)
val renderTarget = makeRenderTarget().autoClose()
val surface = Surface.makeFromBackendRenderTarget( val surface = Surface.makeFromBackendRenderTarget(
context, context,
renderTarget, renderTarget,
...@@ -71,15 +81,17 @@ internal class MetalSwingRedrawer( ...@@ -71,15 +81,17 @@ internal class MetalSwingRedrawer(
val width = surface.width val width = surface.width
val height = surface.height val height = surface.height
val storage = Bitmap() val dstRowBytes = width * 4
storage.setImageInfo(ImageInfo.makeN32Premul(width, height)) if (storage.width != width || storage.height != height) {
storage.allocPixels() 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 // TODO: it copies pixels from GPU to CPU, so it is really slow
surface.readPixels(storage, 0, 0) surface.readPixels(storage, 0, 0)
val bytes = storage.readPixels(storage.imageInfo, (width * 4), 0, 0) val successfulRead = storage.readPixels(bytesToDraw, dstRowBytes = dstRowBytes)
if (bytes != null) { if (successfulRead) {
swingOffscreenDrawer.draw(g, bytes, width, height) swingOffscreenDrawer.draw(g, bytesToDraw, width, height)
} }
} }
...@@ -89,8 +101,8 @@ internal class MetalSwingRedrawer( ...@@ -89,8 +101,8 @@ internal class MetalSwingRedrawer(
"Total VRAM: ${adapter.memorySize / 1024 / 1024} MB\n" "Total VRAM: ${adapter.memorySize / 1024 / 1024} MB\n"
} }
private fun makeRenderTarget(width: Int, height: Int) = BackendRenderTarget( private fun makeRenderTarget() = BackendRenderTarget(
makeMetalRenderTargetOffScreen(adapter.ptr, width, height) makeMetalRenderTargetOffScreen(texturePtr)
) )
private fun makeMetalContext(): DirectContext = DirectContext( private fun makeMetalContext(): DirectContext = DirectContext(
...@@ -99,5 +111,13 @@ internal class MetalSwingRedrawer( ...@@ -99,5 +111,13 @@ internal class MetalSwingRedrawer(
private external fun makeMetalContext(adapter: Long): Long 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 ...@@ -26,17 +26,38 @@ JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_swing_MetalSwingRedrawer_makeMe
} }
} }
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_swing_MetalSwingRedrawer_makeMetalRenderTargetOffScreen( JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_swing_MetalSwingRedrawer_makeMetalTexture(
JNIEnv *env, jobject contextHandler, jlong adapterPtr, jint width, jint height) { JNIEnv *env, jobject contextHandler, jlong adapterPtr, jlong oldTexturePtr, jint width, jint height
) {
@autoreleasepool { @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; id <MTLDevice> adapter = (__bridge id <MTLDevice>) (void *) adapterPtr;
MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm width:width height:height mipmapped:NO]; MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm width:width height:height mipmapped:NO];
// TODO: use double buffer metalTexture = [adapter newTextureWithDescriptor:textureDescriptor];
id <MTLTexture> 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 texturePtr) {
@autoreleasepool {
id <MTLTexture> texture = (__bridge id <MTLTexture>) (void *) texturePtr;
GrMtlTextureInfo info; GrMtlTextureInfo info;
info.fTexture.retain((__bridge GrMTLHandle) metalTexture); info.fTexture.retain((__bridge GrMTLHandle) texture);
GrBackendRenderTarget *renderTarget = NULL; GrBackendRenderTarget *renderTarget = NULL;
renderTarget = new GrBackendRenderTarget(width, height, 0, info); renderTarget = new GrBackendRenderTarget(texture.width, texture.height, 0, info);
return (jlong) renderTarget; return (jlong) renderTarget;
} }
} }
......
...@@ -918,12 +918,32 @@ class Bitmap internal constructor(ptr: NativePointer) : Managed(ptr, _FinalizerH ...@@ -918,12 +918,32 @@ class Bitmap internal constructor(ptr: NativePointer) : Managed(ptr, _FinalizerH
srcX: Int = 0, srcX: Int = 0,
srcY: Int = 0 srcY: Int = 0
): ByteArray? { ): ByteArray? {
return try { val size = getReadPixelsArraySize(dstInfo, dstRowBytes, srcY)
val size = min(dstInfo.height, height - srcY) * dstRowBytes 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() Stats.onNativeCall()
withNullableResult(ByteArray(size)) { interopScope {
_nReadPixels( val byteArrayHandle = toInteropForResult(byteArray)
val successfulRead = _nReadPixels(
_ptr, _ptr,
dstInfo.width, dstInfo.width,
dstInfo.height, dstInfo.height,
...@@ -933,8 +953,12 @@ class Bitmap internal constructor(ptr: NativePointer) : Managed(ptr, _FinalizerH ...@@ -933,8 +953,12 @@ class Bitmap internal constructor(ptr: NativePointer) : Managed(ptr, _FinalizerH
dstRowBytes, dstRowBytes,
srcX, srcX,
srcY, srcY,
it byteArrayHandle
) )
if (successfulRead) {
byteArrayHandle.fromInterop(byteArray)
}
return successfulRead
} }
} finally { } finally {
reachabilityBarrier(this) reachabilityBarrier(this)
...@@ -942,6 +966,12 @@ class Bitmap internal constructor(ptr: NativePointer) : Managed(ptr, _FinalizerH ...@@ -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 * 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