Unverified Commit 6e963769 authored by Nikita Lipsky's avatar Nikita Lipsky Committed by GitHub

Provide a way to configure Skia's DirectContext GPU Resource Cache size (#1095)

Fixes:
[CMP-8916](https://youtrack.jetbrains.com/issue/CMP-8916/Provide-a-way-to-configure-Skias-DirectContext-GPU-Resource-Cache-size)

The cache can be limited on Desktop platforms by specifying
`-Dskiko.gpu.resourceCacheLimit` VM property (accepts numbers optionally
suffixed with "K", "M" or "G").
parent efcc0529
...@@ -28,7 +28,7 @@ import kotlin.math.floor ...@@ -28,7 +28,7 @@ import kotlin.math.floor
actual open class SkiaLayer internal constructor( actual open class SkiaLayer internal constructor(
externalAccessibleFactory: ((Component) -> Accessible)? = null, externalAccessibleFactory: ((Component) -> Accessible)? = null,
private val properties: SkiaLayerProperties, val properties: SkiaLayerProperties,
private val renderFactory: RenderFactory = RenderFactory.Default, private val renderFactory: RenderFactory = RenderFactory.Default,
private val analytics: SkiaLayerAnalytics = SkiaLayerAnalytics.Empty, private val analytics: SkiaLayerAnalytics = SkiaLayerAnalytics.Empty,
actual val pixelGeometry: PixelGeometry = PixelGeometry.UNKNOWN, actual val pixelGeometry: PixelGeometry = PixelGeometry.UNKNOWN,
......
...@@ -2,29 +2,15 @@ package org.jetbrains.skiko.context ...@@ -2,29 +2,15 @@ package org.jetbrains.skiko.context
import org.jetbrains.skia.* import org.jetbrains.skia.*
import org.jetbrains.skiko.AngleApi import org.jetbrains.skiko.AngleApi
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.AngleRedrawer import org.jetbrains.skiko.redrawer.AngleRedrawer
internal class AngleContextHandler(layer: SkiaLayer) : JvmContextHandler(layer) { internal class AngleContextHandler(layer: SkiaLayer) : ContextBasedContextHandler(layer, "ANGLE") {
private val angleRedrawer: AngleRedrawer private val angleRedrawer: AngleRedrawer
get() = layer.redrawer!! as AngleRedrawer get() = layer.redrawer!! as AngleRedrawer
override fun initContext(): Boolean { override fun makeContext() = angleRedrawer.makeContext()
try {
if (context == null) {
context = angleRedrawer.makeContext()
if (System.getProperty("skiko.hardwareInfo.enabled") == "true") {
Logger.info { "Renderer info:\n ${rendererInfo()}" }
}
}
} catch (e: Exception) {
Logger.warn(e) { "Failed to create Skia ANGLE context!" }
return false
}
return true
}
private var currentWidth = 0 private var currentWidth = 0
private var currentHeight = 0 private var currentHeight = 0
......
package org.jetbrains.skiko.context
import org.jetbrains.skia.DirectContext
import org.jetbrains.skiko.Logger
import org.jetbrains.skiko.SkiaLayer
internal abstract class ContextBasedContextHandler(layer: SkiaLayer, val name: String) : JvmContextHandler(layer) {
abstract protected fun makeContext(): DirectContext
override fun initContext(): Boolean {
try {
if (context == null) {
context = makeContext()
onContextInitialized()
}
} catch (e: Exception) {
Logger.warn(e) { "Failed to create Skia $name context!" }
return false
}
return true
}
}
\ No newline at end of file
package org.jetbrains.skiko.context
import org.jetbrains.skiko.SkiaLayer
internal abstract class ContextFreeContextHandler(layer: SkiaLayer) : JvmContextHandler(layer) {
private var isInited = false
override fun initContext(): Boolean {
if (!isInited) {
isInited = true
onContextInitialized()
}
return isInited
}
}
\ No newline at end of file
package org.jetbrains.skiko.context package org.jetbrains.skiko.context
import org.jetbrains.skia.DirectContext
import org.jetbrains.skia.Surface import org.jetbrains.skia.Surface
import org.jetbrains.skia.SurfaceProps import org.jetbrains.skia.SurfaceProps
import org.jetbrains.skia.impl.getPtr import org.jetbrains.skia.impl.getPtr
...@@ -8,7 +9,7 @@ import org.jetbrains.skiko.SkiaLayer ...@@ -8,7 +9,7 @@ import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.redrawer.Direct3DRedrawer import org.jetbrains.skiko.redrawer.Direct3DRedrawer
import java.lang.ref.Reference import java.lang.ref.Reference
internal class Direct3DContextHandler(layer: SkiaLayer) : JvmContextHandler(layer) { internal class Direct3DContextHandler(layer: SkiaLayer) : ContextBasedContextHandler(layer, "Direct3D") {
private val bufferCount = 2 private val bufferCount = 2
private var surfaces: Array<Surface?> = arrayOfNulls(bufferCount) private var surfaces: Array<Surface?> = arrayOfNulls(bufferCount)
private fun isSurfacesNull() = surfaces.all { it == null } private fun isSurfacesNull() = surfaces.all { it == null }
...@@ -16,20 +17,7 @@ internal class Direct3DContextHandler(layer: SkiaLayer) : JvmContextHandler(laye ...@@ -16,20 +17,7 @@ internal class Direct3DContextHandler(layer: SkiaLayer) : JvmContextHandler(laye
private val directXRedrawer: Direct3DRedrawer private val directXRedrawer: Direct3DRedrawer
get() = layer.redrawer!! as Direct3DRedrawer get() = layer.redrawer!! as Direct3DRedrawer
override fun initContext(): Boolean { override fun makeContext(): DirectContext = directXRedrawer.makeContext()
try {
if (context == null) {
context = directXRedrawer.makeContext()
if (System.getProperty("skiko.hardwareInfo.enabled") == "true") {
Logger.info { "Renderer info:\n ${rendererInfo()}" }
}
}
} catch (e: Exception) {
Logger.warn(e) { "Failed to create Skia Direct3D context!" }
return false
}
return true
}
private var currentWidth = 0 private var currentWidth = 0
private var currentHeight = 0 private var currentHeight = 0
......
package org.jetbrains.skiko.context package org.jetbrains.skiko.context
import org.jetbrains.skia.impl.getPtr import org.jetbrains.skia.impl.getPtr
import org.jetbrains.skiko.Logger
import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.redrawer.AbstractDirectSoftwareRedrawer import org.jetbrains.skiko.redrawer.AbstractDirectSoftwareRedrawer
import java.lang.ref.Reference import java.lang.ref.Reference
internal class DirectSoftwareContextHandler(layer: SkiaLayer) : JvmContextHandler(layer) { internal class DirectSoftwareContextHandler(layer: SkiaLayer) : ContextFreeContextHandler(layer) {
var isInited = false
private val softwareRedrawer: AbstractDirectSoftwareRedrawer private val softwareRedrawer: AbstractDirectSoftwareRedrawer
get() = layer.redrawer!! as AbstractDirectSoftwareRedrawer get() = layer.redrawer!! as AbstractDirectSoftwareRedrawer
...@@ -23,16 +20,6 @@ internal class DirectSoftwareContextHandler(layer: SkiaLayer) : JvmContextHandle ...@@ -23,16 +20,6 @@ internal class DirectSoftwareContextHandler(layer: SkiaLayer) : JvmContextHandle
return false return false
} }
override fun initContext(): Boolean {
if (!isInited) {
if (System.getProperty("skiko.hardwareInfo.enabled") == "true") {
Logger.info { "Renderer info:\n ${rendererInfo()}" }
}
isInited = true
}
return isInited
}
override fun initCanvas() { override fun initCanvas() {
val scale = layer.contentScale val scale = layer.contentScale
val w = (layer.width * scale).toInt().coerceAtLeast(0) val w = (layer.width * scale).toInt().coerceAtLeast(0)
......
package org.jetbrains.skiko.context package org.jetbrains.skiko.context
import org.jetbrains.skiko.Logger
import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.SkiaLayer
internal abstract class JvmContextHandler(layer: SkiaLayer) : ContextHandler(layer, layer::draw) internal abstract class JvmContextHandler(layer: SkiaLayer) : ContextHandler(layer, layer::draw) {
\ No newline at end of file protected fun onContextInitialized() {
if (System.getProperty("skiko.hardwareInfo.enabled") == "true") {
Logger.info { "Renderer info:\n ${rendererInfo()}" }
}
context?.run {
val gpuResourceCacheLimit = layer.properties.gpuResourceCacheLimit
if (gpuResourceCacheLimit >= 0) {
resourceCacheLimit = gpuResourceCacheLimit
}
}
}
}
...@@ -19,22 +19,7 @@ internal class MetalContextHandler( ...@@ -19,22 +19,7 @@ internal class MetalContextHandler(
layer: SkiaLayer, layer: SkiaLayer,
private val device: MetalDevice, private val device: MetalDevice,
private val adapter: MetalAdapter private val adapter: MetalAdapter
) : JvmContextHandler(layer) { ) : ContextBasedContextHandler(layer, "Metal") {
override fun initContext(): Boolean {
try {
if (context == null) {
context = makeContext()
if (System.getProperty("skiko.hardwareInfo.enabled") == "true") {
Logger.info { "Renderer info:\n ${rendererInfo()}" }
}
}
} catch (e: Exception) {
Logger.warn(e) { "Failed to create Skia Metal context!" }
return false
}
return true
}
override fun initCanvas() { override fun initCanvas() {
disposeCanvas() disposeCanvas()
...@@ -79,7 +64,7 @@ internal class MetalContextHandler( ...@@ -79,7 +64,7 @@ internal class MetalContextHandler(
makeMetalRenderTarget(device.ptr, width, height) makeMetalRenderTarget(device.ptr, width, height)
) )
private fun makeContext() = DirectContext( override fun makeContext() = DirectContext(
makeMetalContext(device.ptr) makeMetalContext(device.ptr)
) )
......
...@@ -3,21 +3,9 @@ package org.jetbrains.skiko.context ...@@ -3,21 +3,9 @@ package org.jetbrains.skiko.context
import org.jetbrains.skia.* import org.jetbrains.skia.*
import org.jetbrains.skiko.* import org.jetbrains.skiko.*
internal class OpenGLContextHandler(layer: SkiaLayer) : JvmContextHandler(layer) { internal class OpenGLContextHandler(layer: SkiaLayer) : ContextBasedContextHandler(layer, "OpenGL") {
override fun initContext(): Boolean {
try { override fun makeContext() = makeGLContext()
if (context == null) {
context = makeGLContext()
if (System.getProperty("skiko.hardwareInfo.enabled") == "true") {
Logger.info { "Renderer info:\n ${rendererInfo()}" }
}
}
} catch (e: Exception) {
Logger.warn(e) { "Failed to create Skia OpenGL context!" }
return false
}
return true
}
private var currentWidth = 0 private var currentWidth = 0
private var currentHeight = 0 private var currentHeight = 0
......
package org.jetbrains.skiko.context package org.jetbrains.skiko.context
import org.jetbrains.skia.* import org.jetbrains.skia.*
import org.jetbrains.skiko.Logger
import org.jetbrains.skiko.OS import org.jetbrains.skiko.OS
import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.hostOs import org.jetbrains.skiko.hostOs
...@@ -10,7 +9,7 @@ import java.awt.Transparency ...@@ -10,7 +9,7 @@ import java.awt.Transparency
import java.awt.color.ColorSpace import java.awt.color.ColorSpace
import java.awt.image.* import java.awt.image.*
internal class SoftwareContextHandler(layer: SkiaLayer) : JvmContextHandler(layer) { internal class SoftwareContextHandler(layer: SkiaLayer) : ContextFreeContextHandler(layer) {
override fun isTransparentBackground(): Boolean { override fun isTransparentBackground(): Boolean {
// TODO: why Software rendering has another transparency logic from the begginning // TODO: why Software rendering has another transparency logic from the begginning
return hostOs == OS.MacOS && layer.transparency return hostOs == OS.MacOS && layer.transparency
...@@ -27,18 +26,6 @@ internal class SoftwareContextHandler(layer: SkiaLayer) : JvmContextHandler(laye ...@@ -27,18 +26,6 @@ internal class SoftwareContextHandler(layer: SkiaLayer) : JvmContextHandler(laye
var image: BufferedImage? = null var image: BufferedImage? = null
var imageData: ByteArray? = null var imageData: ByteArray? = null
var raster: WritableRaster? = null var raster: WritableRaster? = null
var isInited = false
override fun initContext(): Boolean {
// Raster does not need context
if (!isInited) {
if (System.getProperty("skiko.hardwareInfo.enabled") == "true") {
Logger.info { "Renderer info:\n ${rendererInfo()}" }
}
isInited = true
}
return isInited
}
override fun initCanvas() { override fun initCanvas() {
disposeCanvas() disposeCanvas()
......
...@@ -45,7 +45,7 @@ internal class Direct3DSwingRedrawer( ...@@ -45,7 +45,7 @@ internal class Direct3DSwingRedrawer(
private var bytesToDraw = ByteArray(0) private var bytesToDraw = ByteArray(0)
init { init {
onContextInit() onContextInit(context)
} }
override fun dispose() { override fun dispose() {
......
...@@ -29,7 +29,7 @@ internal class LinuxOpenGLSwingRedrawer( ...@@ -29,7 +29,7 @@ internal class LinuxOpenGLSwingRedrawer(
private var bytesToDraw = ByteArray(0) private var bytesToDraw = ByteArray(0)
init { init {
onContextInit() onContextInit(null)
} }
override fun dispose() { override fun dispose() {
...@@ -65,7 +65,7 @@ internal class LinuxOpenGLSwingRedrawer( ...@@ -65,7 +65,7 @@ internal class LinuxOpenGLSwingRedrawer(
).autoClose() ).autoClose()
// TODO: may be it is possible to reuse [makeGLContext] // TODO: may be it is possible to reuse [makeGLContext]
val directContext = makeGLContext().autoClose() val directContext = makeGLContext().configureContext().autoClose()
val surface = Surface.makeFromBackendRenderTarget( val surface = Surface.makeFromBackendRenderTarget(
directContext, directContext,
renderTarget, renderTarget,
......
...@@ -42,7 +42,7 @@ internal class MetalSwingRedrawer( ...@@ -42,7 +42,7 @@ internal class MetalSwingRedrawer(
private var texturePtr: Long = 0 private var texturePtr: Long = 0
init { init {
onContextInit() onContextInit(context)
} }
private val painter: SwingPainter = createSwingPainter(swingLayerProperties) private val painter: SwingPainter = createSwingPainter(swingLayerProperties)
......
...@@ -63,6 +63,8 @@ open class SkiaSwingLayer( ...@@ -63,6 +63,8 @@ open class SkiaSwingLayer(
get() = this@SkiaSwingLayer.graphicsConfiguration get() = this@SkiaSwingLayer.graphicsConfiguration
override val adapterPriority: GpuPriority override val adapterPriority: GpuPriority
get() = this@SkiaSwingLayer.properties.adapterPriority get() = this@SkiaSwingLayer.properties.adapterPriority
override val gpuResourceCacheLimit: Long
get() = this@SkiaSwingLayer.properties.gpuResourceCacheLimit
} }
private val redrawerManager = RedrawerManager<SwingRedrawer>( private val redrawerManager = RedrawerManager<SwingRedrawer>(
......
...@@ -33,7 +33,7 @@ internal class SoftwareSwingRedrawer( ...@@ -33,7 +33,7 @@ internal class SoftwareSwingRedrawer(
private val storage = Bitmap() private val storage = Bitmap()
init { init {
onContextInit() onContextInit(null)
} }
override fun dispose() { override fun dispose() {
......
...@@ -11,6 +11,8 @@ internal interface SwingLayerProperties { ...@@ -11,6 +11,8 @@ internal interface SwingLayerProperties {
val graphicsConfiguration: GraphicsConfiguration val graphicsConfiguration: GraphicsConfiguration
val adapterPriority: GpuPriority val adapterPriority: GpuPriority
val gpuResourceCacheLimit: Long
} }
internal val SwingLayerProperties.scale: Float get() = graphicsConfiguration.defaultTransform.scaleX.toFloat() internal val SwingLayerProperties.scale: Float get() = graphicsConfiguration.defaultTransform.scaleX.toFloat()
\ No newline at end of file
...@@ -68,15 +68,24 @@ internal abstract class SwingRedrawerBase( ...@@ -68,15 +68,24 @@ internal abstract class SwingRedrawerBase(
"OS: ${hostOs.id} ${hostArch.id}\n" "OS: ${hostOs.id} ${hostArch.id}\n"
} }
protected fun onContextInit() { protected fun onContextInit(context: DirectContext?) {
require(!isDisposed) { "$javaClass is disposed" } require(!isDisposed) { "$javaClass is disposed" }
requireNotNull(deviceAnalytics) { "deviceAnalytics is not null. Call onDeviceChosen after choosing the drawing device" } requireNotNull(deviceAnalytics) { "deviceAnalytics is not null. Call onDeviceChosen after choosing the drawing device" }
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()}" }
} }
context?.configureContext()
deviceAnalytics?.contextInit() deviceAnalytics?.contextInit()
} }
protected fun DirectContext.configureContext(
gpuResourceCacheLimit: Long = swingLayerProperties.gpuResourceCacheLimit
): DirectContext = apply {
if (gpuResourceCacheLimit >= 0) {
resourceCacheLimit = gpuResourceCacheLimit
}
}
private inline fun inDrawScope(body: () -> Unit) { private inline fun inDrawScope(body: () -> Unit) {
check(SwingUtilities.isEventDispatchThread()) { "Method should be called from AWT event dispatch thread" } check(SwingUtilities.isEventDispatchThread()) { "Method should be called from AWT event dispatch thread" }
requireNotNull(deviceAnalytics) { "deviceAnalytics is not null. Call onDeviceChosen after choosing the drawing device" } requireNotNull(deviceAnalytics) { "deviceAnalytics is not null. Call onDeviceChosen after choosing the drawing device" }
......
...@@ -127,6 +127,28 @@ class DirectContext internal constructor(ptr: NativePointer) : RefCnt(ptr) { ...@@ -127,6 +127,28 @@ class DirectContext internal constructor(ptr: NativePointer) : RefCnt(ptr) {
reachabilityBarrier(this) reachabilityBarrier(this)
} }
} }
/**
* GPU resource cache limit. If the cache currently exceeds this limit,
* it will be purged (LRU) to keep the cache within the limit.
*/
var resourceCacheLimit: Long
get() {
try {
Stats.onNativeCall()
return DirectContext_nGetResourceCacheLimit(_ptr)
} finally {
reachabilityBarrier(this)
}
}
set(value) {
try {
Stats.onNativeCall()
DirectContext_nSetResourceCacheLimit(_ptr, value)
} finally {
reachabilityBarrier(this)
}
}
} }
fun <R> DirectContext.useContext(block: (ctx: DirectContext) -> R): R = use { fun <R> DirectContext.useContext(block: (ctx: DirectContext) -> R): R = use {
...@@ -139,6 +161,12 @@ private external fun DirectContext_nFlush(ptr: NativePointer, surfacePtr: Native ...@@ -139,6 +161,12 @@ private external fun DirectContext_nFlush(ptr: NativePointer, surfacePtr: Native
@ExternalSymbolName("org_jetbrains_skia_DirectContext__1nFlushDefault") @ExternalSymbolName("org_jetbrains_skia_DirectContext__1nFlushDefault")
private external fun DirectContext_nFlushDefault(ptr: NativePointer) private external fun DirectContext_nFlushDefault(ptr: NativePointer)
@ExternalSymbolName("org_jetbrains_skia_DirectContext__1nGetResourceCacheLimit")
private external fun DirectContext_nGetResourceCacheLimit(ptr: NativePointer): Long
@ExternalSymbolName("org_jetbrains_skia_DirectContext__1nSetResourceCacheLimit")
private external fun DirectContext_nSetResourceCacheLimit(ptr: NativePointer, maxResourceBytes: Long)
@ExternalSymbolName("org_jetbrains_skia_DirectContext__1nMakeGL") @ExternalSymbolName("org_jetbrains_skia_DirectContext__1nMakeGL")
private external fun _nMakeGL(): NativePointer private external fun _nMakeGL(): NativePointer
......
package org.jetbrains.skia
import org.jetbrains.skiko.tests.TestGlContext
import kotlin.test.Test
import kotlin.test.assertEquals
class DirectContextNativeTest {
@Test
fun resourceCacheLimitTest() {
if (!TestGlContext.isAvailabale()) return
TestGlContext.run {
DirectContext.makeGL().useContext { context ->
context.resourceCacheLimit = 1024
assertEquals(1024, context.resourceCacheLimit)
}
}
}
}
\ No newline at end of file
...@@ -2,12 +2,6 @@ package org.jetbrains.skia ...@@ -2,12 +2,6 @@ package org.jetbrains.skia
import org.jetbrains.skia.impl.interopScope import org.jetbrains.skia.impl.interopScope
import org.jetbrains.skia.impl.use import org.jetbrains.skia.impl.use
import org.jetbrains.skiko.Arch
import org.jetbrains.skiko.KotlinBackend
import org.jetbrains.skiko.OS
import org.jetbrains.skiko.hostArch
import org.jetbrains.skiko.hostOs
import org.jetbrains.skiko.kotlinBackend
import org.jetbrains.skiko.tests.TestGlContext import org.jetbrains.skiko.tests.TestGlContext
import org.jetbrains.skiko.tests.allocateBytesForPixels import org.jetbrains.skiko.tests.allocateBytesForPixels
import org.jetbrains.skiko.tests.runTest import org.jetbrains.skiko.tests.runTest
...@@ -111,14 +105,7 @@ class SurfaceTest { ...@@ -111,14 +105,7 @@ class SurfaceTest {
@Test @Test
fun canMakeRenderTarget() { fun canMakeRenderTarget() {
if (hostOs != OS.Linux || kotlinBackend != KotlinBackend.Native) { if (!TestGlContext.isAvailabale()) return
// TODO implement for other platforms and render targets
return
}
if (hostArch == Arch.Arm64) {
// TODO implement and test EGL on arm64
return
}
val pixels = TestGlContext.run { val pixels = TestGlContext.run {
DirectContext.makeGL().useContext { ctx -> DirectContext.makeGL().useContext { ctx ->
......
...@@ -2,6 +2,12 @@ package org.jetbrains.skiko.tests ...@@ -2,6 +2,12 @@ package org.jetbrains.skiko.tests
import org.jetbrains.skia.* import org.jetbrains.skia.*
import org.jetbrains.skia.impl.* import org.jetbrains.skia.impl.*
import org.jetbrains.skiko.Arch
import org.jetbrains.skiko.KotlinBackend
import org.jetbrains.skiko.OS
import org.jetbrains.skiko.hostArch
import org.jetbrains.skiko.hostOs
import org.jetbrains.skiko.kotlinBackend
internal class TestGlContext : Managed(TestGlContext_nCreate(), FinalizerHolder.PTR) { internal class TestGlContext : Managed(TestGlContext_nCreate(), FinalizerHolder.PTR) {
private object FinalizerHolder { private object FinalizerHolder {
...@@ -17,7 +23,21 @@ internal class TestGlContext : Managed(TestGlContext_nCreate(), FinalizerHolder. ...@@ -17,7 +23,21 @@ internal class TestGlContext : Managed(TestGlContext_nCreate(), FinalizerHolder.
} }
companion object { companion object {
fun isAvailabale(): Boolean {
if (hostOs != OS.Linux || kotlinBackend != KotlinBackend.Native) {
// TODO implement for other platforms and render targets
return false
}
if (hostArch == Arch.Arm64) {
// TODO implement and test EGL on arm64
return false
}
return true
}
inline fun <T> run(block: TestGlContext.() -> T): T { inline fun <T> run(block: TestGlContext.() -> T): T {
check(isAvailabale()) { "TestGlContext is not available" }
return TestGlContext().use { return TestGlContext().use {
it.makeCurrent() it.makeCurrent()
val result = it.block() val result = it.block()
......
...@@ -62,6 +62,18 @@ extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_DirectContextKt_Direct ...@@ -62,6 +62,18 @@ extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_DirectContextKt_Direct
context->flush(skSurface); context->flush(skSurface);
} }
extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_DirectContextKt_DirectContext_1nGetResourceCacheLimit
(JNIEnv* env, jclass jclass, jlong ptr) {
GrDirectContext* context = reinterpret_cast<GrDirectContext*>(static_cast<uintptr_t>(ptr));
return (jlong) context->getResourceCacheLimit();
}
extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_DirectContextKt_DirectContext_1nSetResourceCacheLimit
(JNIEnv* env, jclass jclass, jlong ptr, jlong maxResourceBytes) {
GrDirectContext* context = reinterpret_cast<GrDirectContext*>(static_cast<uintptr_t>(ptr));
context->setResourceCacheLimit((size_t) maxResourceBytes);
}
GrSyncCpu grSyncCpuFromBool(bool syncCpu) { GrSyncCpu grSyncCpuFromBool(bool syncCpu) {
if (syncCpu) return GrSyncCpu::kYes; if (syncCpu) return GrSyncCpu::kYes;
return GrSyncCpu::kNo; return GrSyncCpu::kNo;
......
...@@ -21,6 +21,7 @@ class SkiaLayerProperties( ...@@ -21,6 +21,7 @@ class SkiaLayerProperties(
val frameBuffering: FrameBuffering = SkikoProperties.frameBuffering, val frameBuffering: FrameBuffering = SkikoProperties.frameBuffering,
val renderApi: GraphicsApi = SkikoProperties.renderApi, val renderApi: GraphicsApi = SkikoProperties.renderApi,
val adapterPriority: GpuPriority = SkikoProperties.gpuPriority, val adapterPriority: GpuPriority = SkikoProperties.gpuPriority,
val gpuResourceCacheLimit: Long = SkikoProperties.gpuResourceCacheLimit
) { ) {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
......
...@@ -87,6 +87,22 @@ object SkikoProperties { ...@@ -87,6 +87,22 @@ object SkikoProperties {
val macOsOpenGLEnabled: Boolean get() = getProperty("skiko.macos.opengl.enabled")?.toBoolean() ?: false val macOsOpenGLEnabled: Boolean get() = getProperty("skiko.macos.opengl.enabled")?.toBoolean() ?: false
val gpuResourceCacheLimit: Long get() = parseSize(getProperty("skiko.gpu.resourceCacheLimit"))
private fun parseSize(size: String?): Long {
if (size == null) return -1L
val size = size.uppercase()
val multiplier = when {
size.endsWith("K") -> 1024L
size.endsWith("M") -> 1024L * 1024L
size.endsWith("G") -> 1024L * 1024L * 1024L
else -> 1L
}
val numericPart = if (multiplier != 1L) size.substring(0, size.length - 1) else size
return numericPart.toLongOrNull()?.times(multiplier)
?: throw IllegalArgumentException("Invalid size format: $size")
}
private val properties = run { private val properties = run {
val resourcePropertiesEnabled = System.getProperty("skiko.resource.properties.enabled")?.toBoolean() ?: false val resourcePropertiesEnabled = System.getProperty("skiko.resource.properties.enabled")?.toBoolean() ?: false
val resources = if (resourcePropertiesEnabled) { val resources = if (resourcePropertiesEnabled) {
...@@ -101,33 +117,37 @@ object SkikoProperties { ...@@ -101,33 +117,37 @@ object SkikoProperties {
private fun getProperty(key: String): String? = properties.getProperty(key) private fun getProperty(key: String): String? = properties.getProperty(key)
internal fun parseRenderApi(text: String?): GraphicsApi { internal fun parseRenderApi(text: String?): GraphicsApi {
when(text) { when (text) {
"SOFTWARE_COMPAT" -> return GraphicsApi.SOFTWARE_COMPAT "SOFTWARE_COMPAT" -> return GraphicsApi.SOFTWARE_COMPAT
"SOFTWARE_FAST", "DIRECT_SOFTWARE" -> return GraphicsApi.SOFTWARE_FAST "SOFTWARE_FAST", "DIRECT_SOFTWARE" -> return GraphicsApi.SOFTWARE_FAST
"SOFTWARE" -> return if (hostOs == OS.MacOS) GraphicsApi.SOFTWARE_COMPAT else GraphicsApi.SOFTWARE_FAST "SOFTWARE" -> return if (hostOs == OS.MacOS) GraphicsApi.SOFTWARE_COMPAT else GraphicsApi.SOFTWARE_FAST
"OPENGL" -> { "OPENGL" -> {
// Skia isn't properly tested on OpenGL and Windows ARM (https://groups.google.com/g/skia-discuss/c/McoclAhLpvg?pli=1) // Skia isn't properly tested on OpenGL and Windows ARM (https://groups.google.com/g/skia-discuss/c/McoclAhLpvg?pli=1)
return if (hostOs != OS.Windows || hostArch != Arch.Arm64) GraphicsApi.OPENGL return if (hostOs != OS.Windows || hostArch != Arch.Arm64) GraphicsApi.OPENGL
else throw Exception("$hostOs-$hostArch does not support OpenGL rendering API.") else throw Exception("$hostOs-$hostArch does not support OpenGL rendering API.")
} }
"ANGLE" -> { "ANGLE" -> {
return if (hostOs == OS.Windows) GraphicsApi.ANGLE return if (hostOs == OS.Windows) GraphicsApi.ANGLE
else throw Exception("$hostOs does not support ANGLE rendering API.") else throw Exception("$hostOs does not support ANGLE rendering API.")
} }
"DIRECT3D" -> { "DIRECT3D" -> {
return if (hostOs == OS.Windows) GraphicsApi.DIRECT3D return if (hostOs == OS.Windows) GraphicsApi.DIRECT3D
else throw Exception("$hostOs does not support DirectX rendering API.") else throw Exception("$hostOs does not support DirectX rendering API.")
} }
"METAL" -> { "METAL" -> {
return if (hostOs == OS.MacOS) GraphicsApi.METAL return if (hostOs == OS.MacOS) GraphicsApi.METAL
else throw Exception("$hostOs does not support Metal rendering API.") else throw Exception("$hostOs does not support Metal rendering API.")
} }
else -> return bestRenderApiForCurrentOS() else -> return bestRenderApiForCurrentOS()
} }
} }
private fun bestRenderApiForCurrentOS(): GraphicsApi { private fun bestRenderApiForCurrentOS(): GraphicsApi {
return when(hostOs) { return when (hostOs) {
OS.MacOS -> GraphicsApi.METAL OS.MacOS -> GraphicsApi.METAL
OS.Linux -> GraphicsApi.OPENGL OS.Linux -> GraphicsApi.OPENGL
OS.Windows -> if (renderingAngleEnabled) GraphicsApi.ANGLE else GraphicsApi.DIRECT3D OS.Windows -> if (renderingAngleEnabled) GraphicsApi.ANGLE else GraphicsApi.DIRECT3D
...@@ -136,15 +156,28 @@ object SkikoProperties { ...@@ -136,15 +156,28 @@ object SkikoProperties {
} }
} }
internal fun fallbackRenderApiQueue(initialApi: GraphicsApi) : List<GraphicsApi> { internal fun fallbackRenderApiQueue(initialApi: GraphicsApi): List<GraphicsApi> {
var fallbackApis = when (hostOs) { var fallbackApis = when (hostOs) {
OS.Linux -> listOf(GraphicsApi.OPENGL, GraphicsApi.SOFTWARE_FAST, GraphicsApi.SOFTWARE_COMPAT) OS.Linux -> listOf(GraphicsApi.OPENGL, GraphicsApi.SOFTWARE_FAST, GraphicsApi.SOFTWARE_COMPAT)
OS.MacOS -> listOf(GraphicsApi.METAL, GraphicsApi.SOFTWARE_COMPAT) OS.MacOS -> listOf(GraphicsApi.METAL, GraphicsApi.SOFTWARE_COMPAT)
OS.Windows -> when (hostArch) { OS.Windows -> when (hostArch) {
// Skia isn't properly tested on OpenGL and Windows ARM (https://groups.google.com/g/skia-discuss/c/McoclAhLpvg?pli=1) // Skia isn't properly tested on OpenGL and Windows ARM (https://groups.google.com/g/skia-discuss/c/McoclAhLpvg?pli=1)
Arch.Arm64 -> listOf(GraphicsApi.ANGLE, GraphicsApi.DIRECT3D, GraphicsApi.SOFTWARE_FAST, GraphicsApi.SOFTWARE_COMPAT) Arch.Arm64 -> listOf(
else -> listOf(GraphicsApi.ANGLE, GraphicsApi.DIRECT3D, GraphicsApi.OPENGL, GraphicsApi.SOFTWARE_FAST, GraphicsApi.SOFTWARE_COMPAT) GraphicsApi.ANGLE,
GraphicsApi.DIRECT3D,
GraphicsApi.SOFTWARE_FAST,
GraphicsApi.SOFTWARE_COMPAT
)
else -> listOf(
GraphicsApi.ANGLE,
GraphicsApi.DIRECT3D,
GraphicsApi.OPENGL,
GraphicsApi.SOFTWARE_FAST,
GraphicsApi.SOFTWARE_COMPAT
)
} }
OS.Android -> return listOf(GraphicsApi.OPENGL) OS.Android -> return listOf(GraphicsApi.OPENGL)
else -> return listOf(GraphicsApi.UNKNOWN) else -> return listOf(GraphicsApi.UNKNOWN)
} }
......
...@@ -3,7 +3,9 @@ package org.jetbrains.skia ...@@ -3,7 +3,9 @@ package org.jetbrains.skia
import org.jetbrains.skia.impl.Native.Companion.NullPointer import org.jetbrains.skia.impl.Native.Companion.NullPointer
import org.jetbrains.skia.impl.NativePointer import org.jetbrains.skia.impl.NativePointer
import org.jetbrains.skia.impl.interopScope import org.jetbrains.skia.impl.interopScope
import org.jetbrains.skiko.Library
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals
fun getMakeGLAssembledInterfaceFunc(): NativePointer { fun getMakeGLAssembledInterfaceFunc(): NativePointer {
return interopScope { return interopScope {
...@@ -12,11 +14,29 @@ fun getMakeGLAssembledInterfaceFunc(): NativePointer { ...@@ -12,11 +14,29 @@ fun getMakeGLAssembledInterfaceFunc(): NativePointer {
} }
class DirectContextTest { class DirectContextTest {
@Test
fun makeFromInterfaceTest() { private companion object {
init {
Library.load()
}
}
private fun makeFromInterface(): DirectContext {
val fPtr = getMakeGLAssembledInterfaceFunc() val fPtr = getMakeGLAssembledInterfaceFunc()
val i = GLAssembledInterface.createFromNativePointers(NullPointer, fPtr) val i = GLAssembledInterface.createFromNativePointers(NullPointer, fPtr)
DirectContext.makeGLWithInterface(i) return DirectContext.makeGLWithInterface(i)
}
@Test
fun makeFromInterfaceTest() {
makeFromInterface()
}
@Test
fun resourceCacheLimitTest() {
val context = makeFromInterface()
context.resourceCacheLimit = 1024
assertEquals(1024, context.resourceCacheLimit)
} }
} }
......
...@@ -68,6 +68,18 @@ SKIKO_EXPORT void org_jetbrains_skia_DirectContext__1nFlush ...@@ -68,6 +68,18 @@ SKIKO_EXPORT void org_jetbrains_skia_DirectContext__1nFlush
context->flush(skSurface); context->flush(skSurface);
} }
SKIKO_EXPORT KLong org_jetbrains_skia_DirectContext__1nGetResourceCacheLimit
(KNativePointer ptr) {
GrDirectContext* context = reinterpret_cast<GrDirectContext*>(ptr);
return (KLong) context->getResourceCacheLimit();
}
SKIKO_EXPORT void org_jetbrains_skia_DirectContext__1nSetResourceCacheLimit
(KNativePointer ptr, KLong maxResourceBytes) {
GrDirectContext* context = reinterpret_cast<GrDirectContext*>(ptr);
context->setResourceCacheLimit((size_t) maxResourceBytes);
}
GrSyncCpu grSyncCpuFromBool(bool syncCpu) { GrSyncCpu grSyncCpuFromBool(bool syncCpu) {
if (syncCpu) return GrSyncCpu::kYes; if (syncCpu) return GrSyncCpu::kYes;
return GrSyncCpu::kNo; return GrSyncCpu::kNo;
......
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