Unverified Commit a17f4ed0 authored by Nikolai Rykunov's avatar Nikolai Rykunov Committed by GitHub

Refactor awt metal code and provide some basic docs (#699)

* [awt.metal] Extract MetalDevice interface into .h file

* [awt.metal] Move context and renderTarget creation to ContextHandler

ContextHandler shouldn't depend on redrawer, since redrawer depends on ContextHandler

* [awt.metal] Use value class to hold MetalDevice pointer

* [awt.metal] Add docs for MetalDevice

* [awt.metal] Add simple docs for MetalRedrawer and MetalContextHandler

* [awt.metal] Use null instead of artificial pointer to 0

---------
Co-authored-by: 's avatarIvan Matkov <ivan.matkov@jetbrains.com>
parent 778bfb49
......@@ -4,16 +4,25 @@ import org.jetbrains.skia.*
import org.jetbrains.skiko.Logger
import org.jetbrains.skiko.RenderException
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.redrawer.MetalRedrawer
internal class MetalContextHandler(layer: SkiaLayer) : JvmContextHandler(layer) {
val metalRedrawer: MetalRedrawer
get() = layer.redrawer!! as MetalRedrawer
import org.jetbrains.skiko.redrawer.MetalDevice
/**
* Provides a way to draw on Skia canvas created in [layer] bounds using Metal GPU acceleration.
*
* For each [ContextHandler.draw] request it initializes Skia Canvas with Metal context and
* draws [SkiaLayer.draw] content in this canvas.
*
* @see "src/awtMain/objectiveC/macos/MetalContextHandler.mm" -- native implementation
*/
internal class MetalContextHandler(
layer: SkiaLayer,
private val device: MetalDevice,
private val adapterInfo: AdapterInfo? = null
) : JvmContextHandler(layer) {
override fun initContext(): Boolean {
try {
if (context == null) {
context = metalRedrawer.makeContext()
context = makeContext()
if (System.getProperty("skiko.hardwareInfo.enabled") == "true") {
Logger.info { "Renderer info:\n ${rendererInfo()}" }
}
......@@ -33,7 +42,7 @@ internal class MetalContextHandler(layer: SkiaLayer) : JvmContextHandler(layer)
val height = (layer.backedLayer.height * scale).toInt().coerceAtLeast(0)
if (width > 0 && height > 0) {
renderTarget = metalRedrawer.makeRenderTarget(width, height)
renderTarget = makeRenderTarget(width, height)
surface = Surface.makeFromBackendRenderTarget(
context!!,
......@@ -55,12 +64,31 @@ internal class MetalContextHandler(layer: SkiaLayer) : JvmContextHandler(layer)
override fun flush() {
super.flush()
surface?.flushAndSubmit()
metalRedrawer.finishFrame()
finishFrame()
}
override fun rendererInfo(): String {
return super.rendererInfo() +
"Video card: ${metalRedrawer.adapterName}\n" +
"Total VRAM: ${metalRedrawer.adapterMemorySize / 1024 / 1024} MB\n"
if (adapterInfo != null) {
"Video card: ${adapterInfo.adapterName}\n" + "Total VRAM: ${adapterInfo.adapterMemorySize / 1024 / 1024} MB\n"
} else {
""
}
}
private fun makeRenderTarget(width: Int, height: Int) = BackendRenderTarget(
makeMetalRenderTarget(device.ptr, width, height)
)
private fun makeContext() = DirectContext(
makeMetalContext(device.ptr)
)
private fun finishFrame() = finishFrame(device.ptr)
private external fun makeMetalContext(device: Long): Long
private external fun makeMetalRenderTarget(device: Long, width: Int, height: Int): Long
private external fun finishFrame(device: Long)
data class AdapterInfo(val adapterName: String, val adapterMemorySize: Long)
}
......@@ -4,50 +4,76 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import org.jetbrains.skia.BackendRenderTarget
import org.jetbrains.skia.DirectContext
import org.jetbrains.skiko.*
import org.jetbrains.skiko.context.MetalContextHandler
import javax.swing.SwingUtilities.*
/**
* Holder for pointer on MetalDevice described in "MetalDevice.h"
*
* Naturally [MetalDevice] is just a holder for native objects required for drawing such as:
* * [CAMetalLayer](https://developer.apple.com/documentation/quartzcore/cametallayer)
* * [MTLDevice](https://developer.apple.com/documentation/metal/mtldevice)
* * etc.
*
* @see "src/awtMain/objectiveC/macos/MetalDevice.h"
*/
@JvmInline
internal value class MetalDevice(val ptr: Long)
/**
* Provides a way to request draws on Skia canvas created in [layer] bounds using Metal GPU acceleration.
*
* Content to draw is provided by [SkiaLayer.draw].
*
* @see MetalContextHandler
* @see FrameDispatcher
*/
internal class MetalRedrawer(
private val layer: SkiaLayer,
analytics: SkiaLayerAnalytics,
private val properties: SkiaLayerProperties
) : AWTRedrawer(layer, analytics, GraphicsApi.METAL) {
private val contextHandler = MetalContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo()
private val contextHandler: MetalContextHandler
companion object {
init {
Library.load()
}
}
private var drawLock = Any()
private var device: Long
/**
* [MetalDevice] initialized for given [layer] or null if [MetalRedrawer] is disposed,
* so future calls of [device] will throw exception
*/
private var _device: MetalDevice?
private val device: MetalDevice
get() {
check(field != 0L) { "Device is not initialized" }
return field
val currentDevice = _device
require(currentDevice != null) { "Device is disposed" }
return currentDevice
}
val adapterName: String
val adapterMemorySize: Long
init {
val adapter = chooseAdapter(properties.adapterPriority.ordinal)
adapterName = getAdapterName(adapter)
adapterMemorySize = getAdapterMemorySize(adapter)
val adapterName = getAdapterName(adapter)
val adapterMemorySize = getAdapterMemorySize(adapter)
onDeviceChosen(adapterName)
device = layer.backedLayer.useDrawingSurfacePlatformInfo {
createMetalDevice(layer.windowHandle, layer.transparency, adapter, it)
val initDevice = layer.backedLayer.useDrawingSurfacePlatformInfo {
MetalDevice(createMetalDevice(layer.windowHandle, layer.transparency, adapter, it))
}
_device = initDevice
contextHandler =
MetalContextHandler(layer, initDevice, MetalContextHandler.AdapterInfo(adapterName, adapterMemorySize))
setVSyncEnabled(initDevice.ptr, properties.isVsyncEnabled)
}
private val windowHandle = layer.windowHandle
override val renderInfo: String get() = contextHandler.rendererInfo()
init {
setVSyncEnabled(device, properties.isVsyncEnabled)
}
private val windowHandle = layer.windowHandle
private val frameDispatcher = FrameDispatcher(MainUIDispatcher) {
if (layer.isShowing) {
......@@ -63,8 +89,8 @@ internal class MetalRedrawer(
override fun dispose() = synchronized(drawLock) {
frameDispatcher.cancel()
contextHandler.dispose()
disposeDevice(device)
device = 0
disposeDevice(device.ptr)
_device = null
super.dispose()
}
......@@ -76,11 +102,11 @@ internal class MetalRedrawer(
override fun redrawImmediately() {
check(!isDisposed) { "MetalRedrawer is disposed" }
inDrawScope {
setVSyncEnabled(device, enabled = false)
setVSyncEnabled(device.ptr, enabled = false)
update(System.nanoTime())
if (!isDisposed) { // Redrawer may be disposed in user code, during `update`
performDraw()
setVSyncEnabled(device, properties.isVsyncEnabled)
setVSyncEnabled(device.ptr, properties.isVsyncEnabled)
}
}
}
......@@ -126,36 +152,23 @@ internal class MetalRedrawer(
check(isEventDispatchThread()) { "Method should be called from AWT event dispatch thread" }
val rootPane = getRootPane(layer)
val globalPosition = convertPoint(layer.backedLayer, 0, 0, rootPane)
setContentScale(device, layer.contentScale)
setContentScale(device.ptr, layer.contentScale)
val x = globalPosition.x
val y = rootPane.height - globalPosition.y - layer.height
val width = layer.backedLayer.width.coerceAtLeast(0)
val height = layer.backedLayer.height.coerceAtLeast(0)
Logger.debug { "MetalRedrawer#resizeLayers $this {x: $x y: $y width: $width height: $height} rootPane: ${rootPane.size}" }
resizeLayers(device, x, y, width, height)
resizeLayers(device.ptr, x, y, width, height)
}
override fun setVisible(isVisible: Boolean) {
Logger.debug { "MetalRedrawer#setVisible $this $isVisible" }
setLayerVisible(device, isVisible)
setLayerVisible(device.ptr, isVisible)
}
fun makeContext() = DirectContext(
makeMetalContext(device)
)
fun makeRenderTarget(width: Int, height: Int) = BackendRenderTarget(
makeMetalRenderTarget(device, width, height)
)
fun finishFrame() = finishFrame(device)
private external fun chooseAdapter(adapterPriority: Int): Long
private external fun createMetalDevice(window:Long, transparency: Boolean, adapter: Long, platformInfo: Long): Long
private external fun makeMetalContext(device: Long): Long
private external fun makeMetalRenderTarget(device: Long, width: Int, height: Int): Long
private external fun disposeDevice(device: Long)
private external fun finishFrame(device: Long)
private external fun resizeLayers(device: Long, x: Int, y: Int, width: Int, height: Int)
private external fun setLayerVisible(device: Long, isVisible: Boolean)
private external fun setContentScale(device: Long, contentScale: Float)
......
#ifdef SK_METAL
#import <jawt.h>
#import <jawt_md.h>
#import <QuartzCore/CAMetalLayer.h>
#import <Metal/Metal.h>
#import <GrDirectContext.h>
#import <mtl/GrMtlBackendContext.h>
#import <mtl/GrMtlTypes.h>
#import "MetalDevice.h"
extern "C"
{
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_context_MetalContextHandler_makeMetalContext(
JNIEnv* env, jobject contextHandler, jlong devicePtr)
{
@autoreleasepool {
MetalDevice *device = (__bridge MetalDevice *) (void*) devicePtr;
GrMtlBackendContext backendContext = {};
backendContext.fDevice.retain((__bridge GrMTLHandle) device.adapter);
backendContext.fQueue.retain((__bridge GrMTLHandle) device.queue);
return (jlong) GrDirectContext::MakeMetal(backendContext).release();
}
}
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_context_MetalContextHandler_makeMetalRenderTarget(
JNIEnv* env, jobject contextHandler, jlong devicePtr, jint width, jint height)
{
@autoreleasepool {
MetalDevice *device = (__bridge MetalDevice *) (void *) devicePtr;
GrBackendRenderTarget* renderTarget = NULL;
id<CAMetalDrawable> currentDrawable = [device.layer nextDrawable];
if (!currentDrawable) {
return NULL;
}
device.drawableHandle = currentDrawable;
GrMtlTextureInfo info;
info.fTexture.retain((__bridge GrMTLHandle) currentDrawable.texture);
renderTarget = new GrBackendRenderTarget(width, height, 0, info);
return (jlong) renderTarget;
}
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_context_MetalContextHandler_finishFrame(
JNIEnv *env, jobject contextHandler, jlong devicePtr)
{
@autoreleasepool {
MetalDevice *device = (__bridge MetalDevice *) (void *) devicePtr;
id<CAMetalDrawable> currentDrawable = device.drawableHandle;
if (currentDrawable) {
id<MTLCommandBuffer> commandBuffer = [device.queue commandBuffer];
commandBuffer.label = @"Present";
[commandBuffer presentDrawable:currentDrawable];
[commandBuffer commit];
device.drawableHandle = nil;
}
}
}
} // extern C
#endif
\ No newline at end of file
#ifdef SK_METAL
@interface AWTMetalLayer : CAMetalLayer
@property jobject javaRef;
@end
@interface MetalDevice : NSObject
@property (weak) CALayer *container;
@property (retain, strong) AWTMetalLayer *layer;
@property (retain, strong) id<MTLDevice> adapter;
@property (retain, strong) id<MTLCommandQueue> queue;
@property (retain, strong) id<CAMetalDrawable> drawableHandle;
@end
#endif
\ No newline at end of file
......@@ -13,6 +13,8 @@
#import <mtl/GrMtlBackendContext.h>
#import <mtl/GrMtlTypes.h>
#import "MetalDevice.h"
#define MuxGraphicsCard 7
#define kOpen 0
#define kGetMuxState 3
......@@ -21,22 +23,6 @@
#define AdpapterPriorityIntegrated 1
#define AdpapterPriorityDiscrete 2
@interface AWTMetalLayer : CAMetalLayer
@property jobject javaRef;
@end
@interface MetalDevice : NSObject
@property (weak) CALayer *container;
@property (retain, strong) AWTMetalLayer *layer;
@property (retain, strong) id<MTLDevice> adapter;
@property (retain, strong) id<MTLCommandQueue> queue;
@property (retain, strong) id<CAMetalDrawable> drawableHandle;
@end
@implementation AWTMetalLayer
- (id)init
......@@ -76,35 +62,6 @@
extern "C"
{
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_makeMetalContext(
JNIEnv* env, jobject redrawer, jlong devicePtr)
{
@autoreleasepool {
MetalDevice *device = (__bridge MetalDevice *) (void*) devicePtr;
GrMtlBackendContext backendContext = {};
backendContext.fDevice.retain((__bridge GrMTLHandle) device.adapter);
backendContext.fQueue.retain((__bridge GrMTLHandle) device.queue);
return (jlong) GrDirectContext::MakeMetal(backendContext).release();
}
}
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_makeMetalRenderTarget(
JNIEnv * env, jobject redrawer, jlong devicePtr, jint width, jint height)
{
@autoreleasepool {
MetalDevice *device = (__bridge MetalDevice *) (void *) devicePtr;
GrBackendRenderTarget* renderTarget = NULL;
id<CAMetalDrawable> currentDrawable = [device.layer nextDrawable];
if (!currentDrawable) return 0;
device.drawableHandle = currentDrawable;
GrMtlTextureInfo info;
info.fTexture.retain((__bridge GrMTLHandle) currentDrawable.texture);
renderTarget = new GrBackendRenderTarget(width, height, 0, info);
return (jlong) renderTarget;
}
}
extern "C" void* objc_autoreleasePoolPush(void);
extern "C" void objc_autoreleasePoolPop(void*);
......@@ -293,24 +250,6 @@ JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_setVSyncE
}
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_finishFrame(
JNIEnv *env, jobject redrawer, jlong devicePtr)
{
@autoreleasepool {
MetalDevice *device = (__bridge MetalDevice *) (void *) devicePtr;
id<CAMetalDrawable> currentDrawable = device.drawableHandle;
if (currentDrawable) {
id<MTLCommandBuffer> commandBuffer = [device.queue commandBuffer];
commandBuffer.label = @"Present";
[commandBuffer presentDrawable:currentDrawable];
[commandBuffer commit];
device.drawableHandle = nil;
}
}
}
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_disposeDevice(
JNIEnv *env, jobject redrawer, jlong devicePtr)
{
......
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