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.* ...@@ -4,16 +4,25 @@ import org.jetbrains.skia.*
import org.jetbrains.skiko.Logger import org.jetbrains.skiko.Logger
import org.jetbrains.skiko.RenderException import org.jetbrains.skiko.RenderException
import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.redrawer.MetalRedrawer import org.jetbrains.skiko.redrawer.MetalDevice
internal class MetalContextHandler(layer: SkiaLayer) : JvmContextHandler(layer) {
val metalRedrawer: MetalRedrawer
get() = layer.redrawer!! as MetalRedrawer
/**
* 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 { override fun initContext(): Boolean {
try { try {
if (context == null) { if (context == null) {
context = metalRedrawer.makeContext() context = makeContext()
if (System.getProperty("skiko.hardwareInfo.enabled") == "true") { if (System.getProperty("skiko.hardwareInfo.enabled") == "true") {
Logger.info { "Renderer info:\n ${rendererInfo()}" } Logger.info { "Renderer info:\n ${rendererInfo()}" }
} }
...@@ -33,7 +42,7 @@ internal class MetalContextHandler(layer: SkiaLayer) : JvmContextHandler(layer) ...@@ -33,7 +42,7 @@ internal class MetalContextHandler(layer: SkiaLayer) : JvmContextHandler(layer)
val height = (layer.backedLayer.height * scale).toInt().coerceAtLeast(0) val height = (layer.backedLayer.height * scale).toInt().coerceAtLeast(0)
if (width > 0 && height > 0) { if (width > 0 && height > 0) {
renderTarget = metalRedrawer.makeRenderTarget(width, height) renderTarget = makeRenderTarget(width, height)
surface = Surface.makeFromBackendRenderTarget( surface = Surface.makeFromBackendRenderTarget(
context!!, context!!,
...@@ -55,12 +64,31 @@ internal class MetalContextHandler(layer: SkiaLayer) : JvmContextHandler(layer) ...@@ -55,12 +64,31 @@ internal class MetalContextHandler(layer: SkiaLayer) : JvmContextHandler(layer)
override fun flush() { override fun flush() {
super.flush() super.flush()
surface?.flushAndSubmit() surface?.flushAndSubmit()
metalRedrawer.finishFrame() finishFrame()
} }
override fun rendererInfo(): String { override fun rendererInfo(): String {
return super.rendererInfo() + return super.rendererInfo() +
"Video card: ${metalRedrawer.adapterName}\n" + if (adapterInfo != null) {
"Total VRAM: ${metalRedrawer.adapterMemorySize / 1024 / 1024} MB\n" "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 ...@@ -4,50 +4,76 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jetbrains.skia.BackendRenderTarget
import org.jetbrains.skia.DirectContext
import org.jetbrains.skiko.* import org.jetbrains.skiko.*
import org.jetbrains.skiko.context.MetalContextHandler import org.jetbrains.skiko.context.MetalContextHandler
import javax.swing.SwingUtilities.* 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( internal class MetalRedrawer(
private val layer: SkiaLayer, private val layer: SkiaLayer,
analytics: SkiaLayerAnalytics, analytics: SkiaLayerAnalytics,
private val properties: SkiaLayerProperties private val properties: SkiaLayerProperties
) : AWTRedrawer(layer, analytics, GraphicsApi.METAL) { ) : AWTRedrawer(layer, analytics, GraphicsApi.METAL) {
private val contextHandler = MetalContextHandler(layer) private val contextHandler: MetalContextHandler
override val renderInfo: String get() = contextHandler.rendererInfo()
companion object { companion object {
init { init {
Library.load() Library.load()
} }
} }
private var drawLock = Any() 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() { get() {
check(field != 0L) { "Device is not initialized" } val currentDevice = _device
return field require(currentDevice != null) { "Device is disposed" }
return currentDevice
} }
val adapterName: String
val adapterMemorySize: Long
init { init {
val adapter = chooseAdapter(properties.adapterPriority.ordinal) val adapter = chooseAdapter(properties.adapterPriority.ordinal)
adapterName = getAdapterName(adapter) val adapterName = getAdapterName(adapter)
adapterMemorySize = getAdapterMemorySize(adapter) val adapterMemorySize = getAdapterMemorySize(adapter)
onDeviceChosen(adapterName) onDeviceChosen(adapterName)
device = layer.backedLayer.useDrawingSurfacePlatformInfo { val initDevice = layer.backedLayer.useDrawingSurfacePlatformInfo {
createMetalDevice(layer.windowHandle, layer.transparency, adapter, it) 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 { private val windowHandle = layer.windowHandle
setVSyncEnabled(device, properties.isVsyncEnabled)
}
private val frameDispatcher = FrameDispatcher(MainUIDispatcher) { private val frameDispatcher = FrameDispatcher(MainUIDispatcher) {
if (layer.isShowing) { if (layer.isShowing) {
...@@ -63,8 +89,8 @@ internal class MetalRedrawer( ...@@ -63,8 +89,8 @@ internal class MetalRedrawer(
override fun dispose() = synchronized(drawLock) { override fun dispose() = synchronized(drawLock) {
frameDispatcher.cancel() frameDispatcher.cancel()
contextHandler.dispose() contextHandler.dispose()
disposeDevice(device) disposeDevice(device.ptr)
device = 0 _device = null
super.dispose() super.dispose()
} }
...@@ -76,11 +102,11 @@ internal class MetalRedrawer( ...@@ -76,11 +102,11 @@ internal class MetalRedrawer(
override fun redrawImmediately() { override fun redrawImmediately() {
check(!isDisposed) { "MetalRedrawer is disposed" } check(!isDisposed) { "MetalRedrawer is disposed" }
inDrawScope { inDrawScope {
setVSyncEnabled(device, enabled = false) setVSyncEnabled(device.ptr, enabled = false)
update(System.nanoTime()) update(System.nanoTime())
if (!isDisposed) { // Redrawer may be disposed in user code, during `update` if (!isDisposed) { // Redrawer may be disposed in user code, during `update`
performDraw() performDraw()
setVSyncEnabled(device, properties.isVsyncEnabled) setVSyncEnabled(device.ptr, properties.isVsyncEnabled)
} }
} }
} }
...@@ -126,36 +152,23 @@ internal class MetalRedrawer( ...@@ -126,36 +152,23 @@ internal class MetalRedrawer(
check(isEventDispatchThread()) { "Method should be called from AWT event dispatch thread" } check(isEventDispatchThread()) { "Method should be called from AWT event dispatch thread" }
val rootPane = getRootPane(layer) val rootPane = getRootPane(layer)
val globalPosition = convertPoint(layer.backedLayer, 0, 0, rootPane) val globalPosition = convertPoint(layer.backedLayer, 0, 0, rootPane)
setContentScale(device, layer.contentScale) setContentScale(device.ptr, layer.contentScale)
val x = globalPosition.x val x = globalPosition.x
val y = rootPane.height - globalPosition.y - layer.height val y = rootPane.height - globalPosition.y - layer.height
val width = layer.backedLayer.width.coerceAtLeast(0) val width = layer.backedLayer.width.coerceAtLeast(0)
val height = layer.backedLayer.height.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}" } 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) { override fun setVisible(isVisible: Boolean) {
Logger.debug { "MetalRedrawer#setVisible $this $isVisible" } 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 chooseAdapter(adapterPriority: Int): Long
private external fun createMetalDevice(window:Long, transparency: Boolean, adapter: Long, platformInfo: Long): 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 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 resizeLayers(device: Long, x: Int, y: Int, width: Int, height: Int)
private external fun setLayerVisible(device: Long, isVisible: Boolean) private external fun setLayerVisible(device: Long, isVisible: Boolean)
private external fun setContentScale(device: Long, contentScale: Float) 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 @@ ...@@ -13,6 +13,8 @@
#import <mtl/GrMtlBackendContext.h> #import <mtl/GrMtlBackendContext.h>
#import <mtl/GrMtlTypes.h> #import <mtl/GrMtlTypes.h>
#import "MetalDevice.h"
#define MuxGraphicsCard 7 #define MuxGraphicsCard 7
#define kOpen 0 #define kOpen 0
#define kGetMuxState 3 #define kGetMuxState 3
...@@ -21,22 +23,6 @@ ...@@ -21,22 +23,6 @@
#define AdpapterPriorityIntegrated 1 #define AdpapterPriorityIntegrated 1
#define AdpapterPriorityDiscrete 2 #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 @implementation AWTMetalLayer
- (id)init - (id)init
...@@ -76,35 +62,6 @@ ...@@ -76,35 +62,6 @@
extern "C" 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_autoreleasePoolPush(void);
extern "C" void objc_autoreleasePoolPop(void*); extern "C" void objc_autoreleasePoolPop(void*);
...@@ -293,24 +250,6 @@ JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_setVSyncE ...@@ -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( JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_disposeDevice(
JNIEnv *env, jobject redrawer, jlong devicePtr) 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