Unverified Commit bd79451a authored by Igor Demin's avatar Igor Demin Committed by GitHub

SkiaLayerAnalytics - send events for different steps of rendering (#568)

* SkiaLayerAnalytics - send events for different steps of rendering

It will help us to detect GPU's, on which we have crashes. After we compare two events, we can investigate GPU, and if the issue only in it, we will add it to not-supported-adapter.list

The plan to integrate this analytics to Toolbox.

* Fix build

* Disable performance tests on CI

CI can have different performance for different runs. Currently we have failing vsync tests for Windows

* Fix analytics test on github actions

* Fix CI
parent 257b0404
......@@ -28,8 +28,8 @@ jobs:
java-version: '11'
- shell: bash
run: |
./gradlew --stacktrace --info -Pskiko.native.enabled=true :skiko:awtTest
./gradlew --stacktrace --info -Pskiko.native.enabled=true :skiko:macosX64Test
./gradlew --stacktrace --info -Pskiko.native.enabled=true -Pskiko.test.onci=true :skiko:awtTest
./gradlew --stacktrace --info -Pskiko.native.enabled=true -Pskiko.test.onci=true :skiko:macosX64Test
./gradlew --stacktrace --info -Pskiko.native.enabled=true :skiko:publishToMavenLocal
./gradlew --stacktrace --info :SkiaAwtSample:installDist # check AWT sample works
- uses: actions/upload-artifact@v2
......@@ -51,7 +51,7 @@ jobs:
java-version: '11'
- shell: bash
run: |
./gradlew --stacktrace --info -Pskiko.native.enabled=true :skiko:iosX64Test
./gradlew --stacktrace --info -Pskiko.native.enabled=true -Pskiko.test.onci=true :skiko:iosX64Test
./gradlew --stacktrace --info -Pskiko.native.enabled=true :skiko:publishToMavenLocal
# TODO run iOS specific tests on iPhone simulator
- uses: actions/upload-artifact@v2
......@@ -93,7 +93,7 @@ jobs:
sudo apt-get install ninja-build fontconfig libfontconfig1-dev libglu1-mesa-dev libxrandr-dev libdbus-1-dev zip xvfb -y
sudo Xvfb :0 -screen 0 1280x720x24 &
export DISPLAY=:0
./gradlew --stacktrace --info -Pskiko.native.enabled=true -Pkotlin.native.cacheKind.linuxX64=none :skiko:linuxX64Test :skiko:awtTest
./gradlew --stacktrace --info -Pskiko.native.enabled=true -Pkotlin.native.cacheKind.linuxX64=none -Pskiko.test.onci=true :skiko:linuxX64Test :skiko:awtTest
./gradlew --stacktrace --info -Pkotlin.native.cacheKind.linuxX64=none :skiko:publishToMavenLocal
./gradlew --stacktrace --info :SkiaAwtSample:installDist # check jvm sample works
./gradlew -Pskiko.android.enabled=true :skiko:publishSkikoJvmRuntimeAndroidX64PublicationToMavenLocal :skiko:publishSkikoJvmRuntimeAndroidArm64PublicationToMavenLocal :skiko:publishAndroidPublicationToMavenLocal
......@@ -132,7 +132,7 @@ jobs:
./emsdk activate 2.0.29
source ./emsdk_env.sh
cd ..
./gradlew --stacktrace --info -Pskiko.wasm.enabled=true jsTest
./gradlew --stacktrace --info -Pskiko.wasm.enabled=true -Pskiko.test.onci=true jsTest
./gradlew --stacktrace --info -Pskiko.wasm.enabled=true publishSkikoWasmRuntimePublicationToMavenLocal
- uses: actions/upload-artifact@v2
with:
......@@ -152,7 +152,7 @@ jobs:
java-version: '11'
- shell: bash
run: |
./gradlew --stacktrace --info :skiko:awtTest
./gradlew --stacktrace --info -Pskiko.test.onci=true :skiko:awtTest
./gradlew --stacktrace --info :skiko:publishToMavenLocal
./gradlew --stacktrace --info :SkiaAwtSample:installDist # check jvm sample works
- uses: actions/upload-artifact@v2
......
......@@ -1082,7 +1082,10 @@ tasks.withType<Test>().configureEach {
systemProperty("skiko.test.screenshots.dir", File(project.projectDir, "src/jvmTest/screenshots").absolutePath)
systemProperty("skiko.test.font.dir", File(project.projectDir, "src/commonTest/resources/fonts").absolutePath)
val canRunUiTests = System.getProperty("os.name") != "Mac OS X"
val testingOnCI = System.getProperty("skiko.test.onci", "false").toBoolean()
val canRunPerformanceTests = testingOnCI
val canRunUiTests = testingOnCI || System.getProperty("os.name") != "Mac OS X"
systemProperty("skiko.test.performance.enabled", System.getProperty("skiko.test.performance.enabled", canRunPerformanceTests.toString()))
systemProperty("skiko.test.ui.enabled", System.getProperty("skiko.test.ui.enabled", canRunUiTests.toString()))
systemProperty("skiko.test.ui.renderApi", System.getProperty("skiko.test.ui.renderApi", "all"))
......
......@@ -26,6 +26,7 @@ internal actual fun makeDefaultRenderFactory(): RenderFactory {
override fun createRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi,
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties
): Redrawer = when (hostOs) {
OS.Android -> AndroidOpenGLRedrawer(layer, properties)
......
......@@ -17,27 +17,25 @@ internal actual fun makeDefaultRenderFactory(): RenderFactory {
override fun createRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi,
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties
): Redrawer = when (hostOs) {
OS.MacOS -> when (renderApi) {
GraphicsApi.SOFTWARE_COMPAT, GraphicsApi.SOFTWARE_FAST -> SoftwareRedrawer(layer, properties)
else -> MetalRedrawer(layer, properties)
GraphicsApi.SOFTWARE_COMPAT, GraphicsApi.SOFTWARE_FAST -> SoftwareRedrawer(layer, analytics, properties)
else -> MetalRedrawer(layer, analytics, properties)
}
OS.Windows -> when (renderApi) {
GraphicsApi.SOFTWARE_COMPAT -> SoftwareRedrawer(layer, properties)
GraphicsApi.SOFTWARE_FAST -> WindowsSoftwareRedrawer(layer, properties)
GraphicsApi.OPENGL -> WindowsOpenGLRedrawer(layer, properties)
else -> Direct3DRedrawer(layer, properties)
GraphicsApi.SOFTWARE_COMPAT -> SoftwareRedrawer(layer, analytics, properties)
GraphicsApi.SOFTWARE_FAST -> WindowsSoftwareRedrawer(layer, analytics, properties)
GraphicsApi.OPENGL -> WindowsOpenGLRedrawer(layer, analytics, properties)
else -> Direct3DRedrawer(layer, analytics, properties)
}
OS.Linux -> when (renderApi) {
GraphicsApi.SOFTWARE_COMPAT -> SoftwareRedrawer(layer, properties)
GraphicsApi.SOFTWARE_FAST -> LinuxSoftwareRedrawer(layer, properties)
else -> LinuxOpenGLRedrawer(layer, properties)
}
OS.Android -> TODO()
OS.JS, OS.Ios -> {
TODO("Commonize me")
GraphicsApi.SOFTWARE_COMPAT -> SoftwareRedrawer(layer, analytics, properties)
GraphicsApi.SOFTWARE_FAST -> LinuxSoftwareRedrawer(layer, analytics, properties)
else -> LinuxOpenGLRedrawer(layer, analytics, properties)
}
OS.Android, OS.JS, OS.Ios -> throw UnsupportedOperationException("The awt target doesn't support $hostOs")
}
}
}
......
......@@ -19,7 +19,8 @@ import javax.swing.UIManager
actual open class SkiaLayer internal constructor(
externalAccessibleFactory: ((Component) -> Accessible)? = null,
private val properties: SkiaLayerProperties,
private val renderFactory: RenderFactory = RenderFactory.Default
private val renderFactory: RenderFactory = RenderFactory.Default,
private val analytics: SkiaLayerAnalytics = SkiaLayerAnalytics.Empty,
) : JPanel() {
internal companion object {
......@@ -52,6 +53,7 @@ actual open class SkiaLayer internal constructor(
isVsyncEnabled: Boolean = SkikoProperties.vsyncEnabled,
isVsyncFramelimitFallbackEnabled: Boolean = SkikoProperties.vsyncFramelimitFallbackEnabled,
renderApi: GraphicsApi = SkikoProperties.renderApi,
analytics: SkiaLayerAnalytics = SkiaLayerAnalytics.Empty
) : this(
externalAccessibleFactory,
SkiaLayerProperties(
......@@ -59,7 +61,8 @@ actual open class SkiaLayer internal constructor(
isVsyncFramelimitFallbackEnabled,
renderApi
),
RenderFactory.Default
RenderFactory.Default,
analytics,
)
val canvas: java.awt.Canvas
......@@ -279,7 +282,7 @@ actual open class SkiaLayer internal constructor(
try {
renderApi = fallbackRenderApiQueue.removeAt(0)
redrawer?.dispose()
redrawer = renderFactory.createRedrawer(this, renderApi, properties)
redrawer = renderFactory.createRedrawer(this, renderApi, analytics, properties)
redrawer?.syncSize()
} catch (e: RenderException) {
println(e.message)
......
......@@ -61,7 +61,7 @@ internal class MetalContextHandler(layer: SkiaLayer) : JvmContextHandler(layer)
override fun rendererInfo(): String {
return super.rendererInfo() +
"Video card: ${metalRedrawer.getAdapterName()}\n" +
"Total VRAM: ${metalRedrawer.getAdapterMemorySize() / 1024 / 1024} MB\n"
"Video card: ${metalRedrawer.adapterName}\n" +
"Total VRAM: ${metalRedrawer.adapterMemorySize / 1024 / 1024} MB\n"
}
}
package org.jetbrains.skiko.redrawer
import org.jetbrains.skiko.*
import org.jetbrains.skiko.SkiaLayerAnalytics.DeviceAnalytics
/**
* Common class for all AWT redrawers.
* Don't forget to call [onDeviceChosen] and [onContextInit] to send necessary analytics.
*/
@OptIn(ExperimentalSkikoApi::class)
internal abstract class AWTRedrawer(
private val layer: SkiaLayer,
private val analytics: SkiaLayerAnalytics,
private val graphicsApi: GraphicsApi,
) : Redrawer {
private var isFirstFrameRendered = false
private val rendererAnalytics = analytics.renderer(Version.skiko, hostOs, graphicsApi)
private var deviceAnalytics: DeviceAnalytics? = null
protected var isDisposed = false
private set
init {
rendererAnalytics.init()
}
override fun dispose() {
require(!isDisposed) { "$javaClass is disposed" }
isDisposed = true
}
/**
* Should be called when the device name is known as early, as possible.
*/
protected fun onDeviceChosen(deviceName: String?) {
require(!isDisposed) { "$javaClass is disposed" }
require(deviceAnalytics == null) { "deviceAnalytics is not null" }
rendererAnalytics.deviceChosen()
deviceAnalytics = analytics.device(Version.skiko, hostOs, graphicsApi, deviceName)
deviceAnalytics?.init()
}
/**
* Should be called when initialization of graphic context is ended. Only call it after [onDeviceChosen]
*/
protected fun onContextInit() {
require(!isDisposed) { "$javaClass is disposed" }
requireNotNull(deviceAnalytics) { "deviceAnalytics is not null. Call onDeviceChosen after choosing the drawing device" }
deviceAnalytics?.contextInit()
}
protected fun update(nanoTime: Long) {
require(!isDisposed) { "$javaClass is disposed" }
layer.update(nanoTime)
}
protected inline fun inDrawScope(body: () -> Unit) {
requireNotNull(deviceAnalytics) { "deviceAnalytics is not null. Call onDeviceChosen after choosing the drawing device" }
if (!isDisposed) {
if (!isFirstFrameRendered) {
deviceAnalytics?.beforeFirstFrameRender()
}
layer.inDrawScope(body)
if (!isFirstFrameRendered && !isDisposed) {
deviceAnalytics?.afterFirstFrameRender()
}
isFirstFrameRendered = true
}
}
}
\ No newline at end of file
......@@ -2,18 +2,15 @@ package org.jetbrains.skiko.redrawer
import org.jetbrains.skia.Surface
import kotlinx.coroutines.*
import org.jetbrains.skiko.FrameDispatcher
import org.jetbrains.skiko.*
import org.jetbrains.skiko.FrameLimiter
import org.jetbrains.skiko.RenderException
import org.jetbrains.skiko.MainUIDispatcher
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkiaLayerProperties
import org.jetbrains.skiko.context.DirectSoftwareContextHandler
internal abstract class AbstractDirectSoftwareRedrawer(
private val layer: SkiaLayer,
analytics: SkiaLayerAnalytics,
private val properties: SkiaLayerProperties
) : Redrawer {
) : AWTRedrawer(layer, analytics, GraphicsApi.SOFTWARE_FAST) {
private val contextHandler = DirectSoftwareContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo()
......@@ -25,7 +22,7 @@ internal abstract class AbstractDirectSoftwareRedrawer(
}
if (layer.isShowing) {
layer.update(System.nanoTime())
update(System.nanoTime())
draw()
}
}
......@@ -36,11 +33,11 @@ internal abstract class AbstractDirectSoftwareRedrawer(
frameDispatcher.scheduleFrame()
}
protected open fun draw() = layer.inDrawScope(contextHandler::draw)
protected open fun draw() = inDrawScope(contextHandler::draw)
override fun redrawImmediately() {
layer.update(System.nanoTime())
layer.inDrawScope(contextHandler::draw)
update(System.nanoTime())
draw()
}
open fun resize(width: Int, height: Int) = resize(device, width, height)
......@@ -58,7 +55,8 @@ internal abstract class AbstractDirectSoftwareRedrawer(
disposeDevice(device)
runBlocking {
frameJob.cancelAndJoin()
}
}
super.dispose()
}
private external fun resize(devicePtr: Long, width: Int, height: Int)
......
......@@ -9,18 +9,25 @@ import org.jetbrains.skiko.context.Direct3DContextHandler
internal class Direct3DRedrawer(
private val layer: SkiaLayer,
analytics: SkiaLayerAnalytics,
private val properties: SkiaLayerProperties
) : Redrawer {
) : AWTRedrawer(layer, analytics, GraphicsApi.DIRECT3D) {
private val contextHandler = Direct3DContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo()
private var isDisposed = false
private var drawLock = Any()
private val device = createDirectXDevice(
properties.adapterPriority.ordinal, layer.contentHandle, layer.transparency
).also {
if (it == 0L || !isVideoCardSupported(layer.renderApi)) {
private val device: Long
val adapterName: String
val adapterMemorySize: Long
init {
val adapter = chooseAdapter(properties.adapterPriority.ordinal)
adapterName = getAdapterName(adapter)
adapterMemorySize = getAdapterMemorySize(adapter)
onDeviceChosen(getAdapterName(adapter))
device = createDirectXDevice(adapter, layer.contentHandle, layer.transparency)
if (device == 0L) {
throw RenderException("Failed to create DirectX12 device.")
}
}
......@@ -32,11 +39,15 @@ internal class Direct3DRedrawer(
}
}
init {
onContextInit()
}
override fun dispose() = synchronized(drawLock) {
frameDispatcher.cancel()
contextHandler.dispose()
disposeDevice(device)
isDisposed = true
super.dispose()
}
override fun needRedraw() {
......@@ -46,18 +57,14 @@ internal class Direct3DRedrawer(
override fun redrawImmediately() {
check(!isDisposed) { "Direct3DRedrawer is disposed" }
layer.inDrawScope {
layer.update(System.nanoTime())
inDrawScope {
update(System.nanoTime())
drawAndSwap(withVsync = false)
}
}
private fun update(nanoTime: Long) {
layer.update(nanoTime)
}
private suspend fun draw() {
layer.inDrawScope {
inDrawScope {
withContext(Dispatchers.IO) {
drawAndSwap(withVsync = properties.isVsyncEnabled)
}
......@@ -84,10 +91,12 @@ internal class Direct3DRedrawer(
fun getBufferIndex() = getBufferIndex(device)
fun initSwapChain() = initSwapChain(device)
fun initFence() = initFence(device)
val adapterName get() = getAdapterName(device)
val adapterMemorySize get() = getAdapterMemorySize(device)
private external fun createDirectXDevice(adapterPriority: Int, contentHandle: Long, transparency: Boolean): Long
// Called from native code
private fun isAdapterSupported(name: String) = isVideoCardSupported(GraphicsApi.DIRECT3D, name)
private external fun chooseAdapter(adapterPriority: Int): Long
private external fun createDirectXDevice(adapter: Long, contentHandle: Long, transparency: Boolean): Long
private external fun makeDirectXContext(device: Long): Long
private external fun makeDirectXSurface(device: Long, context: Long, width: Int, height: Int, index: Int): Long
private external fun resizeBuffers(device: Long, width: Int, height: Int)
......@@ -96,6 +105,6 @@ internal class Direct3DRedrawer(
private external fun getBufferIndex(device: Long): Int
private external fun initSwapChain(device: Long)
private external fun initFence(device: Long)
private external fun getAdapterName(device: Long): String
private external fun getAdapterMemorySize(device: Long): Long
private external fun getAdapterName(adapter: Long): String
private external fun getAdapterMemorySize(adapter: Long): Long
}
......@@ -6,12 +6,12 @@ import org.jetbrains.skiko.context.OpenGLContextHandler
internal class LinuxOpenGLRedrawer(
private val layer: SkiaLayer,
analytics: SkiaLayerAnalytics,
private val properties: SkiaLayerProperties
) : Redrawer {
) : AWTRedrawer(layer, analytics, GraphicsApi.OPENGL) {
private val contextHandler = OpenGLContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo()
private var isDisposed = false
private var context = 0L
private val swapInterval = if (properties.isVsyncEnabled) 1 else 0
......@@ -22,12 +22,17 @@ internal class LinuxOpenGLRedrawer(
throw RenderException("Cannot create Linux GL context")
}
it.makeCurrent(context)
if (!isVideoCardSupported(layer.renderApi)) {
if (!isVideoCardSupported(GraphicsApi.OPENGL, adapterName)) {
throw RenderException("Cannot create Linux GL context")
}
onDeviceChosen(adapterName)
it.setSwapInterval(swapInterval)
}
onContextInit()
}
private val adapterName get() = OpenGLApi.instance.glGetString(OpenGLApi.instance.GL_RENDERER)
private val frameJob = Job()
@Volatile
private var frameLimit = 0.0
......@@ -60,7 +65,7 @@ internal class LinuxOpenGLRedrawer(
runBlocking {
frameJob.cancelAndJoin()
}
isDisposed = true
super.dispose()
}
override fun needRedraw() {
......@@ -71,8 +76,8 @@ internal class LinuxOpenGLRedrawer(
override fun redrawImmediately() = layer.backedLayer.lockLinuxDrawingSurface {
check(!isDisposed) { "LinuxOpenGLRedrawer is disposed" }
layer.inDrawScope {
update(System.nanoTime())
update(System.nanoTime())
inDrawScope {
it.makeCurrent(context)
contextHandler.draw()
it.setSwapInterval(0)
......@@ -82,12 +87,8 @@ internal class LinuxOpenGLRedrawer(
}
}
private fun update(nanoTime: Long) {
layer.update(nanoTime)
}
private fun draw() {
layer.inDrawScope(contextHandler::draw)
inDrawScope(contextHandler::draw)
}
companion object {
......
......@@ -4,10 +4,12 @@ import org.jetbrains.skiko.*
internal class LinuxSoftwareRedrawer(
private val layer: SkiaLayer,
private val properties: SkiaLayerProperties
) : AbstractDirectSoftwareRedrawer(layer, properties) {
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties
) : AbstractDirectSoftwareRedrawer(layer, analytics, properties) {
init {
onDeviceChosen("Software")
val scale = layer.contentScale
val w = (layer.width * scale).toInt().coerceAtLeast(0)
val h = (layer.height * scale).toInt().coerceAtLeast(0)
......@@ -18,6 +20,7 @@ internal class LinuxSoftwareRedrawer(
}
}
}
onContextInit()
}
override fun dispose() = layer.backedLayer.lockLinuxDrawingSurface {
......
......@@ -12,8 +12,9 @@ import javax.swing.SwingUtilities.*
internal class MetalRedrawer(
private val layer: SkiaLayer,
analytics: SkiaLayerAnalytics,
private val properties: SkiaLayerProperties
) : Redrawer {
) : AWTRedrawer(layer, analytics, GraphicsApi.METAL) {
private val contextHandler = MetalContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo()
......@@ -22,11 +23,22 @@ internal class MetalRedrawer(
Library.load()
}
}
private var isDisposed = false
private var drawLock = Any()
private val device = layer.backedLayer.useDrawingSurfacePlatformInfo {
createMetalDevice(layer.windowHandle, layer.transparency, properties.adapterPriority.ordinal, it)
private val device: Long
val adapterName: String
val adapterMemorySize: Long
init {
val adapter = chooseAdapter(properties.adapterPriority.ordinal)
adapterName = getAdapterName(adapter)
adapterMemorySize = getAdapterMemorySize(adapter)
onDeviceChosen(adapterName)
device = layer.backedLayer.useDrawingSurfacePlatformInfo {
createMetalDevice(layer.windowHandle, layer.transparency, adapter, it)
}
}
private val windowHandle = layer.windowHandle
init {
......@@ -40,11 +52,15 @@ internal class MetalRedrawer(
}
}
init {
onContextInit()
}
override fun dispose() = synchronized(drawLock) {
frameDispatcher.cancel()
contextHandler.dispose()
disposeDevice(device)
isDisposed = true
super.dispose()
}
override fun needRedraw() {
......@@ -54,7 +70,7 @@ internal class MetalRedrawer(
override fun redrawImmediately() {
check(!isDisposed) { "MetalRedrawer is disposed" }
layer.inDrawScope {
inDrawScope {
setVSyncEnabled(device, enabled = false)
update(System.nanoTime())
performDraw()
......@@ -62,10 +78,6 @@ internal class MetalRedrawer(
}
}
private fun update(nanoTime: Long) {
layer.update(nanoTime)
}
private suspend fun draw() {
// 2,3 GHz 8-Core Intel Core i9
//
......@@ -78,7 +90,7 @@ internal class MetalRedrawer(
//
// Executors.newSingleThreadExecutor().asCoroutineDispatcher(): 50 FPS, 150% CPU
// Dispatchers.IO: 50 FPS, 200% CPU
layer.inDrawScope {
inDrawScope {
withContext(Dispatchers.IO) {
performDraw()
}
......@@ -127,10 +139,8 @@ internal class MetalRedrawer(
fun finishFrame() = finishFrame(device)
fun getAdapterName(): String = getAdapterName(device)
fun getAdapterMemorySize(): Long = getAdapterMemorySize(device)
private external fun createMetalDevice(window:Long, transparency: Boolean, adapterPriority: Int, platformInfo: Long): Long
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)
......@@ -139,8 +149,8 @@ internal class MetalRedrawer(
private external fun setContentScale(device: Long, contentScale: Float)
private external fun setVSyncEnabled(device: Long, enabled: Boolean)
private external fun isOccluded(window: Long): Boolean
private external fun getAdapterName(device: Long): String
private external fun getAdapterMemorySize(device: Long): Long
private external fun getAdapterName(adapter: Long): String
private external fun getAdapterMemorySize(adapter: Long): Long
private external fun startRendering(): Long
private external fun endRendering(handle: Long)
}
package org.jetbrains.skiko.redrawer
import kotlinx.coroutines.*
import org.jetbrains.skiko.FrameDispatcher
import org.jetbrains.skiko.*
import org.jetbrains.skiko.FrameLimiter
import org.jetbrains.skiko.MainUIDispatcher
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkiaLayerProperties
import org.jetbrains.skiko.context.SoftwareContextHandler
internal class SoftwareRedrawer(
private val layer: SkiaLayer,
analytics: SkiaLayerAnalytics,
private val properties: SkiaLayerProperties
) : Redrawer {
) : AWTRedrawer(layer, analytics, GraphicsApi.SOFTWARE_FAST) {
init {
onDeviceChosen("Software")
}
private val contextHandler = SoftwareContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo()
......@@ -24,17 +26,22 @@ internal class SoftwareRedrawer(
}
if (layer.isShowing) {
layer.update(System.nanoTime())
layer.inDrawScope(contextHandler::draw)
update(System.nanoTime())
inDrawScope(contextHandler::draw)
}
}
init {
onContextInit()
}
override fun dispose() {
frameDispatcher.cancel()
contextHandler.dispose()
runBlocking {
frameJob.cancelAndJoin()
}
super.dispose()
}
override fun needRedraw() {
......@@ -42,7 +49,7 @@ internal class SoftwareRedrawer(
}
override fun redrawImmediately() {
layer.update(System.nanoTime())
layer.inDrawScope(contextHandler::draw)
update(System.nanoTime())
inDrawScope(contextHandler::draw)
}
}
\ No newline at end of file
......@@ -8,8 +8,9 @@ import org.jetbrains.skiko.context.OpenGLContextHandler
internal class WindowsOpenGLRedrawer(
private val layer: SkiaLayer,
analytics: SkiaLayerAnalytics,
private val properties: SkiaLayerProperties
) : Redrawer {
) : AWTRedrawer(layer, analytics, GraphicsApi.OPENGL) {
private val contextHandler = OpenGLContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo()
......@@ -19,11 +20,13 @@ internal class WindowsOpenGLRedrawer(
throw RenderException("Cannot create Windows GL context")
}
makeCurrent(device, it)
if (!isVideoCardSupported(layer.renderApi)) {
if (!isVideoCardSupported(GraphicsApi.OPENGL, adapterName)) {
throw RenderException("Cannot create Windows GL context")
}
onDeviceChosen(adapterName)
}
private var isDisposed = false
private val adapterName get() = OpenGLApi.instance.glGetString(OpenGLApi.instance.GL_RENDERER)
init {
makeCurrent()
......@@ -32,6 +35,7 @@ internal class WindowsOpenGLRedrawer(
// With dwmFlush it is stable (16.6-16.8 ms)
// GLFW also uses dwmFlush (https://www.glfw.org/docs/3.0/window.html#window_swap)
setSwapInterval(0)
onContextInit()
}
override fun dispose() {
......@@ -39,7 +43,7 @@ internal class WindowsOpenGLRedrawer(
makeCurrent()
contextHandler.dispose()
deleteContext(context)
isDisposed = true
super.dispose()
}
override fun needRedraw() {
......@@ -50,7 +54,7 @@ internal class WindowsOpenGLRedrawer(
override fun redrawImmediately() {
check(!isDisposed) { "WindowsOpenGLRedrawer is disposed" }
layer.inDrawScope {
inDrawScope {
update(System.nanoTime())
makeCurrent()
contextHandler.draw()
......@@ -59,12 +63,8 @@ internal class WindowsOpenGLRedrawer(
}
}
private fun update(nanoTime: Long) {
layer.update(nanoTime)
}
private fun draw() {
layer.inDrawScope(contextHandler::draw)
inDrawScope(contextHandler::draw)
}
private fun makeCurrent() = makeCurrent(device, context)
......
......@@ -3,18 +3,22 @@ package org.jetbrains.skiko.redrawer
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkiaLayerProperties
import org.jetbrains.skiko.RenderException
import org.jetbrains.skiko.SkiaLayerAnalytics
internal class WindowsSoftwareRedrawer(
layer: SkiaLayer,
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties
) : AbstractDirectSoftwareRedrawer(layer, properties) {
) : AbstractDirectSoftwareRedrawer(layer, analytics, properties) {
init {
onDeviceChosen("Software")
device = createDevice(layer.contentHandle, layer.transparency).also {
if (it == 0L) {
throw RenderException("Failed to create Software device")
}
}
onContextInit()
}
private external fun createDevice(contentHandle: Long, transparency: Boolean): Long
......
......@@ -14,4 +14,16 @@ package org.jetbrains.skiko
message = "This is a delicate API and its use requires care." +
" Make sure you fully read and understand documentation of the declaration that is marked as a delicate API."
)
annotation class DelicateSkikoApi
\ No newline at end of file
annotation class DelicateSkikoApi
/**
* Marks declarations that are experimental and don't have stable API yet.
*/
@MustBeDocumented
@Retention(value = AnnotationRetention.BINARY)
@RequiresOptIn(
level = RequiresOptIn.Level.WARNING,
message = "This is an experimental API and can be changed in the near future. The behaviour isn't properly tested" +
"and can have bugs."
)
annotation class ExperimentalSkikoApi
package org.jetbrains.skiko
/**
* Analytics that helps to know more about SkiaLayer behaviour.
* Implementation usually uses third-party solution to send info to some centralized analytics gatherer.
*/
interface SkiaLayerAnalytics {
/**
* Create analytics for a renderer with specific API. Can be called multiple times because of API fallbacks.
*/
@ExperimentalSkikoApi
fun renderer(
skikoVersion: String,
os: OS,
api: GraphicsApi
): RendererAnalytics = RendererAnalytics.Empty
/**
* Create analytics for a device with specific API and name. Can be called multiple times because of API fallbacks.
*/
@ExperimentalSkikoApi
fun device(
skikoVersion: String,
os: OS,
api: GraphicsApi,
deviceName: String?
): DeviceAnalytics = DeviceAnalytics.Empty
@ExperimentalSkikoApi
interface RendererAnalytics {
fun init() = Unit
fun deviceChosen() = Unit
companion object {
val Empty = object : RendererAnalytics {}
}
}
@ExperimentalSkikoApi
interface DeviceAnalytics {
fun init() = Unit
fun contextInit() = Unit
fun beforeFirstFrameRender() = Unit
fun afterFirstFrameRender() = Unit
companion object {
val Empty = object : DeviceAnalytics {}
}
}
companion object {
val Empty = object : SkiaLayerAnalytics {}
}
}
\ No newline at end of file
......@@ -19,18 +19,6 @@
const int BuffersCount = 2;
// This is a list of not supported graphics cards that have rendering issues (black screen, flickering)
// with the current Swing/Skia integration.
// If PC has other graphics cards suitable for DirectX12, one of them will be used. Otherwise,
// rendering will falls back to OpenGL.
const std::vector<std::wstring> notSupportedAdapters{
L"Intel(R) HD Graphics 520",
L"Intel(R) HD Graphics 530",
L"Intel(R) HD Graphics 4400",
L"NVIDIA GeForce GTX 750 Ti",
L"NVIDIA GeForce GTX 960M",
L"NVIDIA Quadro M2000M"};
class DirectXDevice
{
public:
......@@ -214,94 +202,63 @@ extern "C"
return impl(pSrcData, SrcDataSize, pSourceName, pDefines, pInclude, pEntrypoint, pTarget, Flags1, Flags2, ppCode, ppErrorMsgs);
}
bool isNotSupported(IDXGIAdapter1 *hardwareAdapter)
{
bool isAdapterSupported(JNIEnv *env, jobject redrawer, IDXGIAdapter1 *hardwareAdapter) {
DXGI_ADAPTER_DESC1 desc;
hardwareAdapter->GetDesc1(&desc);
if ((desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) != 0)
{
return true;
if ((desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) != 0) {
return false;
}
std::wstring currentAdapterName(desc.Description);
for (std::wstring name : notSupportedAdapters)
{
if (currentAdapterName == name)
{
fwprintf(stderr, L"Graphics card %s is not supported.\n", name.c_str());
return true;
}
}
return false;
std::wstring tmp(desc.Description);
std::string name(tmp.begin(), tmp.end());
jstring jname = env->NewStringUTF(name.c_str());
static jclass cls = (jclass) env->NewGlobalRef(env->FindClass("org/jetbrains/skiko/redrawer/Direct3DRedrawer"));
static jmethodID method = env->GetMethodID(cls, "isAdapterSupported", "(Ljava/lang/String;)Z");
return env->CallBooleanMethod(redrawer, method, jname);
}
bool defineHardwareAdapter(DXGI_GPU_PREFERENCE adapterPriority, IDXGIFactory4 *pFactory, IDXGIAdapter1 **ppAdapter)
{
*ppAdapter = nullptr;
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_Direct3DRedrawer_chooseAdapter(
JNIEnv *env, jobject redrawer, jint adapterPriority) {
gr_cp<IDXGIFactory4> deviceFactory;
if (!SUCCEEDED(CreateDXGIFactory1(IID_PPV_ARGS(&deviceFactory)))) {
return 0;
}
#if defined(__dxgi1_6_h__) && defined(NTDDI_WIN10_RS4)
gr_cp<IDXGIFactory6> factory6;
if (SUCCEEDED(pFactory->QueryInterface(IID_PPV_ARGS(&factory6))))
{
for (UINT adapterIndex = 0;; ++adapterIndex)
{
IDXGIAdapter1 *pAdapter = nullptr;
if (!SUCCEEDED(factory6->EnumAdapterByGpuPreference(adapterIndex, adapterPriority, IID_PPV_ARGS(&pAdapter))))
{
break;
}
if (SUCCEEDED(D3D12CreateDevice(pAdapter, D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr)))
{
if (isNotSupported(pAdapter))
{
pAdapter->Release();
continue;
}
*ppAdapter = pAdapter;
return true;
}
pAdapter->Release();
}
if (!SUCCEEDED(deviceFactory->QueryInterface(IID_PPV_ARGS(&factory6)))) {
return 0;
}
#endif
if (*ppAdapter == nullptr)
{
for (UINT adapterIndex = 0;; ++adapterIndex)
{
IDXGIAdapter1 *pAdapter = nullptr;
if (DXGI_ERROR_NOT_FOUND == pFactory->EnumAdapters1(adapterIndex, &pAdapter))
{
break;
}
if (SUCCEEDED(D3D12CreateDevice(pAdapter, D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr)))
{
if (isNotSupported(pAdapter))
{
pAdapter->Release();
continue;
}
*ppAdapter = pAdapter;
return true;
}
pAdapter->Release();
for (UINT adapterIndex = 0;; ++adapterIndex) {
IDXGIAdapter1 *adapter = nullptr;
if (!SUCCEEDED(factory6->EnumAdapterByGpuPreference(adapterIndex, (DXGI_GPU_PREFERENCE) adapterPriority, IID_PPV_ARGS(&adapter)))) {
break;
}
if (
SUCCEEDED(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr)) &&
isAdapterSupported(env, redrawer, adapter)
) {
return toJavaPointer(adapter);
} else {
adapter->Release();
}
}
return false;
return 0;
}
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_Direct3DRedrawer_createDirectXDevice(
JNIEnv *env, jobject redrawer, jint adapterPriority, jlong contentHandle, jboolean transparency)
{
JNIEnv *env, jobject redrawer, jlong adapterPtr, jlong contentHandle, jboolean transparency) {
gr_cp<IDXGIFactory4> deviceFactory;
if (!SUCCEEDED(CreateDXGIFactory1(IID_PPV_ARGS(&deviceFactory))))
{
if (!SUCCEEDED(CreateDXGIFactory1(IID_PPV_ARGS(&deviceFactory)))) {
return 0;
}
gr_cp<IDXGIAdapter1> hardwareAdapter;
if (!defineHardwareAdapter((DXGI_GPU_PREFERENCE) adapterPriority, deviceFactory.get(), &hardwareAdapter))
{
if (adapterPtr == 0) {
return 0;
}
gr_cp<IDXGIAdapter1> adapter((IDXGIAdapter1 *) adapterPtr);
D3D_FEATURE_LEVEL maxSupportedFeatureLevel = D3D_FEATURE_LEVEL_11_0;
D3D_FEATURE_LEVEL featureLevels[] = {
......@@ -311,21 +268,17 @@ extern "C"
D3D_FEATURE_LEVEL_11_0
};
for (int i = 0; i < _countof(featureLevels); i++)
{
if (SUCCEEDED(D3D12CreateDevice(hardwareAdapter.get(), featureLevels[i], _uuidof(ID3D12Device), nullptr)))
{
for (int i = 0; i < _countof(featureLevels); i++) {
if (SUCCEEDED(D3D12CreateDevice(adapter.get(), featureLevels[i], _uuidof(ID3D12Device), nullptr))) {
maxSupportedFeatureLevel = featureLevels[i];
break;
}
}
gr_cp<ID3D12Device> device;
if (!SUCCEEDED(D3D12CreateDevice(hardwareAdapter.get(), maxSupportedFeatureLevel, IID_PPV_ARGS(&device))))
{
if (!SUCCEEDED(D3D12CreateDevice(adapter.get(), maxSupportedFeatureLevel, IID_PPV_ARGS(&device)))) {
return 0;
}
deviceFactory.reset(nullptr);
// Create the command queue
gr_cp<ID3D12CommandQueue> queue;
......@@ -333,13 +286,12 @@ extern "C"
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
if (!SUCCEEDED(device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&queue))))
{
if (!SUCCEEDED(device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&queue)))) {
return 0;
}
DirectXDevice *d3dDevice = new DirectXDevice();
d3dDevice->backendContext.fAdapter = hardwareAdapter;
d3dDevice->backendContext.fAdapter = adapter;
d3dDevice->backendContext.fDevice = device;
d3dDevice->backendContext.fQueue = queue;
d3dDevice->backendContext.fProtectedContext = GrProtected::kNo;
......@@ -348,8 +300,7 @@ extern "C"
d3dDevice->queue = queue;
d3dDevice->window = (HWND)contentHandle;
if (transparency)
{
if (transparency) {
//TODO: current swapChain does not support transparency
return 0;
// HWND wnd = GetAncestor(d3dDevice->window, GA_PARENT);
......@@ -493,24 +444,23 @@ extern "C"
}
}
JNIEXPORT jstring JNICALL Java_org_jetbrains_skiko_redrawer_Direct3DRedrawer_getAdapterName(JNIEnv *env, jobject redrawer, jlong devicePtr)
JNIEXPORT jstring JNICALL Java_org_jetbrains_skiko_redrawer_Direct3DRedrawer_getAdapterName(JNIEnv *env, jobject redrawer, jlong adapterPtr)
{
DirectXDevice *d3dDevice = fromJavaPointer<DirectXDevice *>(devicePtr);
IDXGIAdapter1 *adapter = fromJavaPointer<IDXGIAdapter1 *>(adapterPtr);
DXGI_ADAPTER_DESC1 desc;
d3dDevice->backendContext.fAdapter->GetDesc1(&desc);
adapter->GetDesc1(&desc);
std::wstring w_tmp(desc.Description);
std::string currentAdapterName(w_tmp.begin(), w_tmp.end());
jstring result = env->NewStringUTF(currentAdapterName.c_str());
return result;
return env->NewStringUTF(currentAdapterName.c_str());
}
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_Direct3DRedrawer_getAdapterMemorySize(JNIEnv *env, jobject redrawer, jlong devicePtr)
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_Direct3DRedrawer_getAdapterMemorySize(JNIEnv *env, jobject redrawer, jlong adapterPtr)
{
DirectXDevice *d3dDevice = fromJavaPointer<DirectXDevice *>(devicePtr);
IDXGIAdapter1 *adapter = fromJavaPointer<IDXGIAdapter1 *>(adapterPtr);
DXGI_ADAPTER_DESC1 desc;
d3dDevice->backendContext.fAdapter->GetDesc1(&desc);
adapter->GetDesc1(&desc);
__int64 result = desc.DedicatedVideoMemory;
return (jlong)result;
}
......
......@@ -73,8 +73,8 @@ void logJavaException(JNIEnv *env, const char *function, DWORD sehCode)
char buffer[200];
int result = snprintf(
buffer, sizeof(buffer) - 1, "Native exception in [%s]:\nSEH description: %s\n", function, getDescription(sehCode));
jclass logClass = env->FindClass("org/jetbrains/skiko/RenderExceptionsHandler");
jmethodID logMethod = env->GetStaticMethodID(logClass, "logAndThrow", "(Ljava/lang/String;)V");
static jclass logClass = (jclass) env->NewGlobalRef(env->FindClass("org/jetbrains/skiko/RenderExceptionsHandler"));
static jmethodID logMethod = env->GetStaticMethodID(logClass, "throwException", "(Ljava/lang/String;)V");
env->CallStaticVoidMethod(logClass, logMethod, env->NewStringUTF(buffer));
}
}
......
package org.jetbrains.skiko
private val notSupportedAdapters by lazy {
val resource = SkiaLayer::class.java.getResource("/not-supported-adapter.list").readText()
resource.split(";").map { it.trim() }
}
internal fun isVideoCardSupported(renderApi: GraphicsApi): Boolean {
return when (renderApi) {
GraphicsApi.DIRECT3D -> {
true
private val notSupportedAdapters: Set<NotSupportedAdapter> by lazy {
val resource = SkiaLayer::class.java.getResource("/not-supported-adapter.list")?.readText().orEmpty()
resource.splitToSequence(";").map { it.trim() }.mapNotNullTo(mutableSetOf()) {
val splits = it.split(":")
val api = when (splits.getOrNull(0)) {
"directx" -> GraphicsApi.DIRECT3D
"opengl" -> GraphicsApi.OPENGL
else -> return@mapNotNullTo null
}
GraphicsApi.OPENGL -> {
val gl = OpenGLApi.instance
val adaptersList = notSupportedAdapters.filter { it.startsWith("opengl:") }.map {
it.replace("opengl:", "")
}
var adapter = gl.glGetString(gl.GL_RENDERER).also {
if (it == null) { return false }
}
adaptersList.forEach {
if (adapter?.startsWith(it) == true) {
return false
}
}
true
}
else -> true
val name = splits.getOrNull(1) ?: return@mapNotNullTo null
NotSupportedAdapter(api, name)
}
}
private external fun getNextDirectXAdapter(index: Int = 0): String?
internal fun isVideoCardSupported(
api: GraphicsApi,
name: String?
): Boolean = name == null || !notSupportedAdapters.contains(NotSupportedAdapter(api, name))
private data class NotSupportedAdapter(
val api: GraphicsApi,
val name: String
)
\ No newline at end of file
......@@ -3,7 +3,12 @@ package org.jetbrains.skiko
import org.jetbrains.skiko.redrawer.*
internal interface RenderFactory {
fun createRedrawer(layer: SkiaLayer, renderApi: GraphicsApi, properties: SkiaLayerProperties): Redrawer
fun createRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi,
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties
): Redrawer
companion object {
val Default = makeDefaultRenderFactory()
......
......@@ -31,7 +31,7 @@
@property (weak) CALayer *container;
@property (retain, strong) AWTMetalLayer *layer;
@property (retain, strong) id<MTLDevice> device;
@property (retain, strong) id<MTLDevice> adapter;
@property (retain, strong) id<MTLCommandQueue> queue;
@property (retain, strong) id<CAMetalDrawable> drawableHandle;
......@@ -63,7 +63,7 @@
if (self)
{
self.layer = nil;
self.device = nil;
self.adapter = nil;
self.queue = nil;
self.drawableHandle = nil;
}
......@@ -82,7 +82,7 @@ JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_makeMeta
@autoreleasepool {
MetalDevice *device = (__bridge MetalDevice *) (void*) devicePtr;
GrMtlBackendContext backendContext = {};
backendContext.fDevice.retain((__bridge GrMTLHandle) device.device);
backendContext.fDevice.retain((__bridge GrMTLHandle) device.adapter);
backendContext.fQueue.retain((__bridge GrMTLHandle) device.queue);
return (jlong) GrDirectContext::MakeMetal(backendContext).release();
}
......@@ -177,10 +177,21 @@ id<MTLDevice> MTLCreateIntegratedDevice(int adapterPriority) {
return gpu;
}
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_chooseAdapter(
JNIEnv *env, jobject redrawer, jlong adapterPriority)
{
@autoreleasepool {
id<MTLDevice> adapter = MTLCreateIntegratedDevice(adapterPriority);
return (jlong) (__bridge_retained void *) adapter;
}
}
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_createMetalDevice(
JNIEnv *env, jobject redrawer, jlong windowPtr, jboolean transparency, jint adapterPriority, jlong platformInfoPtr)
JNIEnv *env, jobject redrawer, jlong windowPtr, jboolean transparency, jlong adapterPtr, jlong platformInfoPtr)
{
@autoreleasepool {
id<MTLDevice> adapter = (__bridge_transfer id<MTLDevice>) (void *) adapterPtr;
MetalDevice *device = [MetalDevice new];
NSObject<JAWT_SurfaceLayers>* dsi_mac = (__bridge NSObject<JAWT_SurfaceLayers> *) (void*) platformInfoPtr;
......@@ -194,15 +205,14 @@ JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_createMe
[container addSublayer: layer];
layer.javaRef = env->NewGlobalRef(redrawer);
id<MTLDevice> fDevice = MTLCreateIntegratedDevice(adapterPriority);
id<MTLCommandQueue> fQueue = [fDevice newCommandQueue];
id<MTLCommandQueue> fQueue = [adapter newCommandQueue];
device.container = container;
device.layer = layer;
device.device = fDevice;
device.adapter = adapter;
device.queue = fQueue;
device.layer.device = device.device;
device.layer.device = device.adapter;
device.layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
device.layer.contentsGravity = kCAGravityTopLeft;
......@@ -296,22 +306,21 @@ JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_disposeDe
}
JNIEXPORT jstring JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_getAdapterName(
JNIEnv *env, jobject redrawer, jlong devicePtr)
JNIEnv *env, jobject redrawer, jlong adapterPtr)
{
@autoreleasepool {
MetalDevice *device = (__bridge MetalDevice *) (void *) devicePtr;
const char *currentAdapterName = [[device.device name] cStringUsingEncoding:NSASCIIStringEncoding];
jstring result = env->NewStringUTF(currentAdapterName);
return result;
id<MTLDevice> adapter = (__bridge id<MTLDevice>) (void *) adapterPtr;
const char *currentAdapterName = [[adapter name] cStringUsingEncoding:NSASCIIStringEncoding];
return env->NewStringUTF(currentAdapterName);
}
}
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_getAdapterMemorySize(
JNIEnv *env, jobject redrawer, jlong devicePtr)
JNIEnv *env, jobject redrawer, jlong adapterPtr)
{
@autoreleasepool {
MetalDevice *device = (__bridge MetalDevice *) (void *) devicePtr;
uint64_t totalMemory = [device.device recommendedMaxWorkingSetSize];
id<MTLDevice> adapter = (__bridge id<MTLDevice>) (void *) adapterPtr;
uint64_t totalMemory = [adapter recommendedMaxWorkingSetSize];
return (jlong)totalMemory;
}
}
......
......@@ -5,6 +5,7 @@ import org.jetbrains.skia.*
import org.jetbrains.skiko.util.UiTestScope
import org.jetbrains.skiko.util.UiTestWindow
import org.jetbrains.skiko.util.uiTest
import org.junit.Assume.assumeTrue
import org.junit.Test
import java.awt.Point
import javax.swing.WindowConstants
......@@ -128,6 +129,8 @@ class SkiaLayerPerformanceTest {
@Test
fun `FPS is near display refresh rate (multiple windows)`() = uiTest {
assumeTrue(System.getProperty("skiko.test.performance.enabled", "true") == "true")
val helpers = (1..3).map { index ->
performanceHelper(width = 40, height = 20, frameCount = 300, deviatedTerminalCount = 20).apply {
window.toFront()
......
......@@ -28,6 +28,7 @@ import javax.swing.JFrame
import javax.swing.JLayeredPane
import javax.swing.WindowConstants
import kotlin.random.Random
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
@Suppress("BlockingMethodInNonBlockingContext", "SameParameterValue")
......@@ -374,6 +375,7 @@ class SkiaLayerTest {
override fun createRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi,
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties
) = object : Redrawer {
private val contextHandler = object : JvmContextHandler(layer) {
......@@ -397,6 +399,7 @@ class SkiaLayerTest {
override fun createRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi,
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties
) = throw RenderException()
}
......@@ -410,6 +413,7 @@ class SkiaLayerTest {
override fun createRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi,
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties
) = object : Redrawer {
override fun dispose() = Unit
......@@ -456,12 +460,13 @@ class SkiaLayerTest {
override fun createRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi,
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties
): Redrawer {
return if (renderApi == GraphicsApi.SOFTWARE_COMPAT) {
RenderFactory.Default.createRedrawer(layer, renderApi, properties)
RenderFactory.Default.createRedrawer(layer, renderApi, analytics, properties)
} else {
nonSoftwareRenderFactory.createRedrawer(layer, renderApi, properties)
nonSoftwareRenderFactory.createRedrawer(layer, renderApi, analytics, properties)
}
}
}
......@@ -518,6 +523,119 @@ class SkiaLayerTest {
testRenderText(OS.MacOS)
}
@Test
fun analytics() = uiTest {
val analytics = object : SkiaLayerAnalytics {
val rendererInfo = object {
var skikoVersion: String? = null
var os: OS? = null
var api: GraphicsApi? = null
var init = 0
var deviceChosen = 0
}
val deviceInfo = object {
var skikoVersion: String? = null
var os: OS? = null
var api: GraphicsApi? = null
var deviceName: String? = null
var init = 0
var contextInit = 0
var beforeFirstFrameRender = 0
var afterFirstFrameRender = 0
}
@ExperimentalSkikoApi
override fun renderer(
skikoVersion: String,
os: OS,
api: GraphicsApi
) = object : SkiaLayerAnalytics.RendererAnalytics {
init {
rendererInfo.skikoVersion = skikoVersion
rendererInfo.os = os
rendererInfo.api = api
rendererInfo.init = 0
rendererInfo.deviceChosen = 0
}
override fun init() {
rendererInfo.init++
}
override fun deviceChosen() {
rendererInfo.deviceChosen++
}
}
@ExperimentalSkikoApi
override fun device(
skikoVersion: String,
os: OS,
api: GraphicsApi,
deviceName: String?
) = object : SkiaLayerAnalytics.DeviceAnalytics {
init {
deviceInfo.skikoVersion = skikoVersion
deviceInfo.os = os
deviceInfo.api = api
deviceInfo.deviceName = deviceName
deviceInfo.init = 0
deviceInfo.contextInit = 0
deviceInfo.beforeFirstFrameRender = 0
deviceInfo.afterFirstFrameRender = 0
}
override fun init() {
deviceInfo.init++
}
override fun contextInit() {
deviceInfo.contextInit++
}
override fun beforeFirstFrameRender() {
deviceInfo.beforeFirstFrameRender++
}
override fun afterFirstFrameRender() {
deviceInfo.afterFirstFrameRender++
}
}
}
val window = UiTestWindow(analytics = analytics)
try {
window.setLocation(200, 200)
window.setSize(400, 200)
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
val app = RectRenderer(window.layer, 200, 100, Color.RED)
window.layer.skikoView = app
window.isUndecorated = true
window.isVisible = true
delay(1000)
assertEquals(Version.skiko, analytics.rendererInfo.skikoVersion)
assertEquals(hostOs, analytics.rendererInfo.os)
assertNotNull(analytics.rendererInfo.api)
assertEquals(1, analytics.rendererInfo.init)
assertEquals(1, analytics.rendererInfo.deviceChosen)
assertEquals(Version.skiko, analytics.deviceInfo.skikoVersion)
assertNotNull(analytics.deviceInfo.api)
assertNotNull(analytics.deviceInfo.deviceName)
assertEquals(1, analytics.deviceInfo.init)
assertEquals(1, analytics.deviceInfo.contextInit)
assertEquals(1, analytics.deviceInfo.beforeFirstFrameRender)
assertEquals(1, analytics.deviceInfo.afterFirstFrameRender)
} finally {
window.close()
}
}
private fun testRenderText(os: OS) = uiTest {
assumeTrue(hostOs == os)
......
......@@ -33,10 +33,12 @@ internal class UiTestScope(
) : CoroutineScope by scope {
fun UiTestWindow(
properties: SkiaLayerProperties = SkiaLayerProperties(),
analytics: SkiaLayerAnalytics = SkiaLayerAnalytics.Empty,
renderFactory: RenderFactory = RenderFactory.Default
) = object : UiTestWindow() {
override val layer: SkiaLayer = SkiaLayer(
properties = properties.copy(renderApi = renderApi),
analytics = analytics,
renderFactory = renderFactory
)
......
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