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: ...@@ -28,8 +28,8 @@ jobs:
java-version: '11' java-version: '11'
- shell: bash - shell: bash
run: | run: |
./gradlew --stacktrace --info -Pskiko.native.enabled=true :skiko:awtTest ./gradlew --stacktrace --info -Pskiko.native.enabled=true -Pskiko.test.onci=true :skiko:awtTest
./gradlew --stacktrace --info -Pskiko.native.enabled=true :skiko:macosX64Test ./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 -Pskiko.native.enabled=true :skiko:publishToMavenLocal
./gradlew --stacktrace --info :SkiaAwtSample:installDist # check AWT sample works ./gradlew --stacktrace --info :SkiaAwtSample:installDist # check AWT sample works
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
...@@ -51,7 +51,7 @@ jobs: ...@@ -51,7 +51,7 @@ jobs:
java-version: '11' java-version: '11'
- shell: bash - shell: bash
run: | 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 ./gradlew --stacktrace --info -Pskiko.native.enabled=true :skiko:publishToMavenLocal
# TODO run iOS specific tests on iPhone simulator # TODO run iOS specific tests on iPhone simulator
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
...@@ -93,7 +93,7 @@ jobs: ...@@ -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 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 & sudo Xvfb :0 -screen 0 1280x720x24 &
export DISPLAY=:0 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 -Pkotlin.native.cacheKind.linuxX64=none :skiko:publishToMavenLocal
./gradlew --stacktrace --info :SkiaAwtSample:installDist # check jvm sample works ./gradlew --stacktrace --info :SkiaAwtSample:installDist # check jvm sample works
./gradlew -Pskiko.android.enabled=true :skiko:publishSkikoJvmRuntimeAndroidX64PublicationToMavenLocal :skiko:publishSkikoJvmRuntimeAndroidArm64PublicationToMavenLocal :skiko:publishAndroidPublicationToMavenLocal ./gradlew -Pskiko.android.enabled=true :skiko:publishSkikoJvmRuntimeAndroidX64PublicationToMavenLocal :skiko:publishSkikoJvmRuntimeAndroidArm64PublicationToMavenLocal :skiko:publishAndroidPublicationToMavenLocal
...@@ -132,7 +132,7 @@ jobs: ...@@ -132,7 +132,7 @@ jobs:
./emsdk activate 2.0.29 ./emsdk activate 2.0.29
source ./emsdk_env.sh source ./emsdk_env.sh
cd .. 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 ./gradlew --stacktrace --info -Pskiko.wasm.enabled=true publishSkikoWasmRuntimePublicationToMavenLocal
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
with: with:
...@@ -152,7 +152,7 @@ jobs: ...@@ -152,7 +152,7 @@ jobs:
java-version: '11' java-version: '11'
- shell: bash - shell: bash
run: | run: |
./gradlew --stacktrace --info :skiko:awtTest ./gradlew --stacktrace --info -Pskiko.test.onci=true :skiko:awtTest
./gradlew --stacktrace --info :skiko:publishToMavenLocal ./gradlew --stacktrace --info :skiko:publishToMavenLocal
./gradlew --stacktrace --info :SkiaAwtSample:installDist # check jvm sample works ./gradlew --stacktrace --info :SkiaAwtSample:installDist # check jvm sample works
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
......
...@@ -1082,7 +1082,10 @@ tasks.withType<Test>().configureEach { ...@@ -1082,7 +1082,10 @@ tasks.withType<Test>().configureEach {
systemProperty("skiko.test.screenshots.dir", File(project.projectDir, "src/jvmTest/screenshots").absolutePath) 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) 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.enabled", System.getProperty("skiko.test.ui.enabled", canRunUiTests.toString()))
systemProperty("skiko.test.ui.renderApi", System.getProperty("skiko.test.ui.renderApi", "all")) systemProperty("skiko.test.ui.renderApi", System.getProperty("skiko.test.ui.renderApi", "all"))
......
...@@ -26,6 +26,7 @@ internal actual fun makeDefaultRenderFactory(): RenderFactory { ...@@ -26,6 +26,7 @@ internal actual fun makeDefaultRenderFactory(): RenderFactory {
override fun createRedrawer( override fun createRedrawer(
layer: SkiaLayer, layer: SkiaLayer,
renderApi: GraphicsApi, renderApi: GraphicsApi,
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties properties: SkiaLayerProperties
): Redrawer = when (hostOs) { ): Redrawer = when (hostOs) {
OS.Android -> AndroidOpenGLRedrawer(layer, properties) OS.Android -> AndroidOpenGLRedrawer(layer, properties)
......
...@@ -17,27 +17,25 @@ internal actual fun makeDefaultRenderFactory(): RenderFactory { ...@@ -17,27 +17,25 @@ internal actual fun makeDefaultRenderFactory(): RenderFactory {
override fun createRedrawer( override fun createRedrawer(
layer: SkiaLayer, layer: SkiaLayer,
renderApi: GraphicsApi, renderApi: GraphicsApi,
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties properties: SkiaLayerProperties
): Redrawer = when (hostOs) { ): Redrawer = when (hostOs) {
OS.MacOS -> when (renderApi) { OS.MacOS -> when (renderApi) {
GraphicsApi.SOFTWARE_COMPAT, GraphicsApi.SOFTWARE_FAST -> SoftwareRedrawer(layer, properties) GraphicsApi.SOFTWARE_COMPAT, GraphicsApi.SOFTWARE_FAST -> SoftwareRedrawer(layer, analytics, properties)
else -> MetalRedrawer(layer, properties) else -> MetalRedrawer(layer, analytics, properties)
} }
OS.Windows -> when (renderApi) { OS.Windows -> when (renderApi) {
GraphicsApi.SOFTWARE_COMPAT -> SoftwareRedrawer(layer, properties) GraphicsApi.SOFTWARE_COMPAT -> SoftwareRedrawer(layer, analytics, properties)
GraphicsApi.SOFTWARE_FAST -> WindowsSoftwareRedrawer(layer, properties) GraphicsApi.SOFTWARE_FAST -> WindowsSoftwareRedrawer(layer, analytics, properties)
GraphicsApi.OPENGL -> WindowsOpenGLRedrawer(layer, properties) GraphicsApi.OPENGL -> WindowsOpenGLRedrawer(layer, analytics, properties)
else -> Direct3DRedrawer(layer, properties) else -> Direct3DRedrawer(layer, analytics, properties)
} }
OS.Linux -> when (renderApi) { OS.Linux -> when (renderApi) {
GraphicsApi.SOFTWARE_COMPAT -> SoftwareRedrawer(layer, properties) GraphicsApi.SOFTWARE_COMPAT -> SoftwareRedrawer(layer, analytics, properties)
GraphicsApi.SOFTWARE_FAST -> LinuxSoftwareRedrawer(layer, properties) GraphicsApi.SOFTWARE_FAST -> LinuxSoftwareRedrawer(layer, analytics, properties)
else -> LinuxOpenGLRedrawer(layer, properties) else -> LinuxOpenGLRedrawer(layer, analytics, properties)
}
OS.Android -> TODO()
OS.JS, OS.Ios -> {
TODO("Commonize me")
} }
OS.Android, OS.JS, OS.Ios -> throw UnsupportedOperationException("The awt target doesn't support $hostOs")
} }
} }
} }
......
...@@ -19,7 +19,8 @@ import javax.swing.UIManager ...@@ -19,7 +19,8 @@ import javax.swing.UIManager
actual open class SkiaLayer internal constructor( actual open class SkiaLayer internal constructor(
externalAccessibleFactory: ((Component) -> Accessible)? = null, externalAccessibleFactory: ((Component) -> Accessible)? = null,
private val properties: SkiaLayerProperties, private val properties: SkiaLayerProperties,
private val renderFactory: RenderFactory = RenderFactory.Default private val renderFactory: RenderFactory = RenderFactory.Default,
private val analytics: SkiaLayerAnalytics = SkiaLayerAnalytics.Empty,
) : JPanel() { ) : JPanel() {
internal companion object { internal companion object {
...@@ -52,6 +53,7 @@ actual open class SkiaLayer internal constructor( ...@@ -52,6 +53,7 @@ actual open class SkiaLayer internal constructor(
isVsyncEnabled: Boolean = SkikoProperties.vsyncEnabled, isVsyncEnabled: Boolean = SkikoProperties.vsyncEnabled,
isVsyncFramelimitFallbackEnabled: Boolean = SkikoProperties.vsyncFramelimitFallbackEnabled, isVsyncFramelimitFallbackEnabled: Boolean = SkikoProperties.vsyncFramelimitFallbackEnabled,
renderApi: GraphicsApi = SkikoProperties.renderApi, renderApi: GraphicsApi = SkikoProperties.renderApi,
analytics: SkiaLayerAnalytics = SkiaLayerAnalytics.Empty
) : this( ) : this(
externalAccessibleFactory, externalAccessibleFactory,
SkiaLayerProperties( SkiaLayerProperties(
...@@ -59,7 +61,8 @@ actual open class SkiaLayer internal constructor( ...@@ -59,7 +61,8 @@ actual open class SkiaLayer internal constructor(
isVsyncFramelimitFallbackEnabled, isVsyncFramelimitFallbackEnabled,
renderApi renderApi
), ),
RenderFactory.Default RenderFactory.Default,
analytics,
) )
val canvas: java.awt.Canvas val canvas: java.awt.Canvas
...@@ -279,7 +282,7 @@ actual open class SkiaLayer internal constructor( ...@@ -279,7 +282,7 @@ actual open class SkiaLayer internal constructor(
try { try {
renderApi = fallbackRenderApiQueue.removeAt(0) renderApi = fallbackRenderApiQueue.removeAt(0)
redrawer?.dispose() redrawer?.dispose()
redrawer = renderFactory.createRedrawer(this, renderApi, properties) redrawer = renderFactory.createRedrawer(this, renderApi, analytics, properties)
redrawer?.syncSize() redrawer?.syncSize()
} catch (e: RenderException) { } catch (e: RenderException) {
println(e.message) println(e.message)
......
...@@ -61,7 +61,7 @@ internal class MetalContextHandler(layer: SkiaLayer) : JvmContextHandler(layer) ...@@ -61,7 +61,7 @@ internal class MetalContextHandler(layer: SkiaLayer) : JvmContextHandler(layer)
override fun rendererInfo(): String { override fun rendererInfo(): String {
return super.rendererInfo() + return super.rendererInfo() +
"Video card: ${metalRedrawer.getAdapterName()}\n" + "Video card: ${metalRedrawer.adapterName}\n" +
"Total VRAM: ${metalRedrawer.getAdapterMemorySize() / 1024 / 1024} MB\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 ...@@ -2,18 +2,15 @@ package org.jetbrains.skiko.redrawer
import org.jetbrains.skia.Surface import org.jetbrains.skia.Surface
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.jetbrains.skiko.FrameDispatcher import org.jetbrains.skiko.*
import org.jetbrains.skiko.FrameLimiter 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 import org.jetbrains.skiko.context.DirectSoftwareContextHandler
internal abstract class AbstractDirectSoftwareRedrawer( internal abstract class AbstractDirectSoftwareRedrawer(
private val layer: SkiaLayer, private val layer: SkiaLayer,
analytics: SkiaLayerAnalytics,
private val properties: SkiaLayerProperties private val properties: SkiaLayerProperties
) : Redrawer { ) : AWTRedrawer(layer, analytics, GraphicsApi.SOFTWARE_FAST) {
private val contextHandler = DirectSoftwareContextHandler(layer) private val contextHandler = DirectSoftwareContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo() override val renderInfo: String get() = contextHandler.rendererInfo()
...@@ -25,7 +22,7 @@ internal abstract class AbstractDirectSoftwareRedrawer( ...@@ -25,7 +22,7 @@ internal abstract class AbstractDirectSoftwareRedrawer(
} }
if (layer.isShowing) { if (layer.isShowing) {
layer.update(System.nanoTime()) update(System.nanoTime())
draw() draw()
} }
} }
...@@ -36,11 +33,11 @@ internal abstract class AbstractDirectSoftwareRedrawer( ...@@ -36,11 +33,11 @@ internal abstract class AbstractDirectSoftwareRedrawer(
frameDispatcher.scheduleFrame() frameDispatcher.scheduleFrame()
} }
protected open fun draw() = layer.inDrawScope(contextHandler::draw) protected open fun draw() = inDrawScope(contextHandler::draw)
override fun redrawImmediately() { override fun redrawImmediately() {
layer.update(System.nanoTime()) update(System.nanoTime())
layer.inDrawScope(contextHandler::draw) draw()
} }
open fun resize(width: Int, height: Int) = resize(device, width, height) open fun resize(width: Int, height: Int) = resize(device, width, height)
...@@ -59,6 +56,7 @@ internal abstract class AbstractDirectSoftwareRedrawer( ...@@ -59,6 +56,7 @@ internal abstract class AbstractDirectSoftwareRedrawer(
runBlocking { runBlocking {
frameJob.cancelAndJoin() frameJob.cancelAndJoin()
} }
super.dispose()
} }
private external fun resize(devicePtr: Long, width: Int, height: Int) private external fun resize(devicePtr: Long, width: Int, height: Int)
......
...@@ -9,18 +9,25 @@ import org.jetbrains.skiko.context.Direct3DContextHandler ...@@ -9,18 +9,25 @@ import org.jetbrains.skiko.context.Direct3DContextHandler
internal class Direct3DRedrawer( internal class Direct3DRedrawer(
private val layer: SkiaLayer, private val layer: SkiaLayer,
analytics: SkiaLayerAnalytics,
private val properties: SkiaLayerProperties private val properties: SkiaLayerProperties
) : Redrawer { ) : AWTRedrawer(layer, analytics, GraphicsApi.DIRECT3D) {
private val contextHandler = Direct3DContextHandler(layer) private val contextHandler = Direct3DContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo() override val renderInfo: String get() = contextHandler.rendererInfo()
private var isDisposed = false
private var drawLock = Any() private var drawLock = Any()
private val device = createDirectXDevice( private val device: Long
properties.adapterPriority.ordinal, layer.contentHandle, layer.transparency val adapterName: String
).also { val adapterMemorySize: Long
if (it == 0L || !isVideoCardSupported(layer.renderApi)) {
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.") throw RenderException("Failed to create DirectX12 device.")
} }
} }
...@@ -32,11 +39,15 @@ internal class Direct3DRedrawer( ...@@ -32,11 +39,15 @@ internal class Direct3DRedrawer(
} }
} }
init {
onContextInit()
}
override fun dispose() = synchronized(drawLock) { override fun dispose() = synchronized(drawLock) {
frameDispatcher.cancel() frameDispatcher.cancel()
contextHandler.dispose() contextHandler.dispose()
disposeDevice(device) disposeDevice(device)
isDisposed = true super.dispose()
} }
override fun needRedraw() { override fun needRedraw() {
...@@ -46,18 +57,14 @@ internal class Direct3DRedrawer( ...@@ -46,18 +57,14 @@ internal class Direct3DRedrawer(
override fun redrawImmediately() { override fun redrawImmediately() {
check(!isDisposed) { "Direct3DRedrawer is disposed" } check(!isDisposed) { "Direct3DRedrawer is disposed" }
layer.inDrawScope { inDrawScope {
layer.update(System.nanoTime()) update(System.nanoTime())
drawAndSwap(withVsync = false) drawAndSwap(withVsync = false)
} }
} }
private fun update(nanoTime: Long) {
layer.update(nanoTime)
}
private suspend fun draw() { private suspend fun draw() {
layer.inDrawScope { inDrawScope {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
drawAndSwap(withVsync = properties.isVsyncEnabled) drawAndSwap(withVsync = properties.isVsyncEnabled)
} }
...@@ -84,10 +91,12 @@ internal class Direct3DRedrawer( ...@@ -84,10 +91,12 @@ internal class Direct3DRedrawer(
fun getBufferIndex() = getBufferIndex(device) fun getBufferIndex() = getBufferIndex(device)
fun initSwapChain() = initSwapChain(device) fun initSwapChain() = initSwapChain(device)
fun initFence() = initFence(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 makeDirectXContext(device: Long): Long
private external fun makeDirectXSurface(device: Long, context: Long, width: Int, height: Int, index: Int): 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) private external fun resizeBuffers(device: Long, width: Int, height: Int)
...@@ -96,6 +105,6 @@ internal class Direct3DRedrawer( ...@@ -96,6 +105,6 @@ internal class Direct3DRedrawer(
private external fun getBufferIndex(device: Long): Int private external fun getBufferIndex(device: Long): Int
private external fun initSwapChain(device: Long) private external fun initSwapChain(device: Long)
private external fun initFence(device: Long) private external fun initFence(device: Long)
private external fun getAdapterName(device: Long): String private external fun getAdapterName(adapter: Long): String
private external fun getAdapterMemorySize(device: Long): Long private external fun getAdapterMemorySize(adapter: Long): Long
} }
...@@ -6,12 +6,12 @@ import org.jetbrains.skiko.context.OpenGLContextHandler ...@@ -6,12 +6,12 @@ import org.jetbrains.skiko.context.OpenGLContextHandler
internal class LinuxOpenGLRedrawer( internal class LinuxOpenGLRedrawer(
private val layer: SkiaLayer, private val layer: SkiaLayer,
analytics: SkiaLayerAnalytics,
private val properties: SkiaLayerProperties private val properties: SkiaLayerProperties
) : Redrawer { ) : AWTRedrawer(layer, analytics, GraphicsApi.OPENGL) {
private val contextHandler = OpenGLContextHandler(layer) private val contextHandler = OpenGLContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo() override val renderInfo: String get() = contextHandler.rendererInfo()
private var isDisposed = false
private var context = 0L private var context = 0L
private val swapInterval = if (properties.isVsyncEnabled) 1 else 0 private val swapInterval = if (properties.isVsyncEnabled) 1 else 0
...@@ -22,12 +22,17 @@ internal class LinuxOpenGLRedrawer( ...@@ -22,12 +22,17 @@ internal class LinuxOpenGLRedrawer(
throw RenderException("Cannot create Linux GL context") throw RenderException("Cannot create Linux GL context")
} }
it.makeCurrent(context) it.makeCurrent(context)
if (!isVideoCardSupported(layer.renderApi)) { if (!isVideoCardSupported(GraphicsApi.OPENGL, adapterName)) {
throw RenderException("Cannot create Linux GL context") throw RenderException("Cannot create Linux GL context")
} }
onDeviceChosen(adapterName)
it.setSwapInterval(swapInterval) it.setSwapInterval(swapInterval)
} }
onContextInit()
} }
private val adapterName get() = OpenGLApi.instance.glGetString(OpenGLApi.instance.GL_RENDERER)
private val frameJob = Job() private val frameJob = Job()
@Volatile @Volatile
private var frameLimit = 0.0 private var frameLimit = 0.0
...@@ -60,7 +65,7 @@ internal class LinuxOpenGLRedrawer( ...@@ -60,7 +65,7 @@ internal class LinuxOpenGLRedrawer(
runBlocking { runBlocking {
frameJob.cancelAndJoin() frameJob.cancelAndJoin()
} }
isDisposed = true super.dispose()
} }
override fun needRedraw() { override fun needRedraw() {
...@@ -71,8 +76,8 @@ internal class LinuxOpenGLRedrawer( ...@@ -71,8 +76,8 @@ internal class LinuxOpenGLRedrawer(
override fun redrawImmediately() = layer.backedLayer.lockLinuxDrawingSurface { override fun redrawImmediately() = layer.backedLayer.lockLinuxDrawingSurface {
check(!isDisposed) { "LinuxOpenGLRedrawer is disposed" } check(!isDisposed) { "LinuxOpenGLRedrawer is disposed" }
layer.inDrawScope {
update(System.nanoTime()) update(System.nanoTime())
inDrawScope {
it.makeCurrent(context) it.makeCurrent(context)
contextHandler.draw() contextHandler.draw()
it.setSwapInterval(0) it.setSwapInterval(0)
...@@ -82,12 +87,8 @@ internal class LinuxOpenGLRedrawer( ...@@ -82,12 +87,8 @@ internal class LinuxOpenGLRedrawer(
} }
} }
private fun update(nanoTime: Long) {
layer.update(nanoTime)
}
private fun draw() { private fun draw() {
layer.inDrawScope(contextHandler::draw) inDrawScope(contextHandler::draw)
} }
companion object { companion object {
......
...@@ -4,10 +4,12 @@ import org.jetbrains.skiko.* ...@@ -4,10 +4,12 @@ import org.jetbrains.skiko.*
internal class LinuxSoftwareRedrawer( internal class LinuxSoftwareRedrawer(
private val layer: SkiaLayer, private val layer: SkiaLayer,
private val properties: SkiaLayerProperties analytics: SkiaLayerAnalytics,
) : AbstractDirectSoftwareRedrawer(layer, properties) { properties: SkiaLayerProperties
) : AbstractDirectSoftwareRedrawer(layer, analytics, properties) {
init { init {
onDeviceChosen("Software")
val scale = layer.contentScale val scale = layer.contentScale
val w = (layer.width * scale).toInt().coerceAtLeast(0) val w = (layer.width * scale).toInt().coerceAtLeast(0)
val h = (layer.height * scale).toInt().coerceAtLeast(0) val h = (layer.height * scale).toInt().coerceAtLeast(0)
...@@ -18,6 +20,7 @@ internal class LinuxSoftwareRedrawer( ...@@ -18,6 +20,7 @@ internal class LinuxSoftwareRedrawer(
} }
} }
} }
onContextInit()
} }
override fun dispose() = layer.backedLayer.lockLinuxDrawingSurface { override fun dispose() = layer.backedLayer.lockLinuxDrawingSurface {
......
...@@ -12,8 +12,9 @@ import javax.swing.SwingUtilities.* ...@@ -12,8 +12,9 @@ import javax.swing.SwingUtilities.*
internal class MetalRedrawer( internal class MetalRedrawer(
private val layer: SkiaLayer, private val layer: SkiaLayer,
analytics: SkiaLayerAnalytics,
private val properties: SkiaLayerProperties private val properties: SkiaLayerProperties
) : Redrawer { ) : AWTRedrawer(layer, analytics, GraphicsApi.METAL) {
private val contextHandler = MetalContextHandler(layer) private val contextHandler = MetalContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo() override val renderInfo: String get() = contextHandler.rendererInfo()
...@@ -22,11 +23,22 @@ internal class MetalRedrawer( ...@@ -22,11 +23,22 @@ internal class MetalRedrawer(
Library.load() Library.load()
} }
} }
private var isDisposed = false
private var drawLock = Any() 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 private val windowHandle = layer.windowHandle
init { init {
...@@ -40,11 +52,15 @@ internal class MetalRedrawer( ...@@ -40,11 +52,15 @@ internal class MetalRedrawer(
} }
} }
init {
onContextInit()
}
override fun dispose() = synchronized(drawLock) { override fun dispose() = synchronized(drawLock) {
frameDispatcher.cancel() frameDispatcher.cancel()
contextHandler.dispose() contextHandler.dispose()
disposeDevice(device) disposeDevice(device)
isDisposed = true super.dispose()
} }
override fun needRedraw() { override fun needRedraw() {
...@@ -54,7 +70,7 @@ internal class MetalRedrawer( ...@@ -54,7 +70,7 @@ internal class MetalRedrawer(
override fun redrawImmediately() { override fun redrawImmediately() {
check(!isDisposed) { "MetalRedrawer is disposed" } check(!isDisposed) { "MetalRedrawer is disposed" }
layer.inDrawScope { inDrawScope {
setVSyncEnabled(device, enabled = false) setVSyncEnabled(device, enabled = false)
update(System.nanoTime()) update(System.nanoTime())
performDraw() performDraw()
...@@ -62,10 +78,6 @@ internal class MetalRedrawer( ...@@ -62,10 +78,6 @@ internal class MetalRedrawer(
} }
} }
private fun update(nanoTime: Long) {
layer.update(nanoTime)
}
private suspend fun draw() { private suspend fun draw() {
// 2,3 GHz 8-Core Intel Core i9 // 2,3 GHz 8-Core Intel Core i9
// //
...@@ -78,7 +90,7 @@ internal class MetalRedrawer( ...@@ -78,7 +90,7 @@ internal class MetalRedrawer(
// //
// Executors.newSingleThreadExecutor().asCoroutineDispatcher(): 50 FPS, 150% CPU // Executors.newSingleThreadExecutor().asCoroutineDispatcher(): 50 FPS, 150% CPU
// Dispatchers.IO: 50 FPS, 200% CPU // Dispatchers.IO: 50 FPS, 200% CPU
layer.inDrawScope { inDrawScope {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
performDraw() performDraw()
} }
...@@ -127,10 +139,8 @@ internal class MetalRedrawer( ...@@ -127,10 +139,8 @@ internal class MetalRedrawer(
fun finishFrame() = finishFrame(device) fun finishFrame() = finishFrame(device)
fun getAdapterName(): String = getAdapterName(device) private external fun chooseAdapter(adapterPriority: Int): Long
fun getAdapterMemorySize(): Long = getAdapterMemorySize(device) private external fun createMetalDevice(window:Long, transparency: Boolean, adapter: Long, platformInfo: Long): Long
private external fun createMetalDevice(window:Long, transparency: Boolean, adapterPriority: Int, platformInfo: Long): Long
private external fun makeMetalContext(device: Long): Long private external fun makeMetalContext(device: Long): Long
private external fun makeMetalRenderTarget(device: Long, width: Int, height: Int): Long private external fun makeMetalRenderTarget(device: Long, width: Int, height: Int): Long
private external fun disposeDevice(device: Long) private external fun disposeDevice(device: Long)
...@@ -139,8 +149,8 @@ internal class MetalRedrawer( ...@@ -139,8 +149,8 @@ internal class MetalRedrawer(
private external fun setContentScale(device: Long, contentScale: Float) private external fun setContentScale(device: Long, contentScale: Float)
private external fun setVSyncEnabled(device: Long, enabled: Boolean) private external fun setVSyncEnabled(device: Long, enabled: Boolean)
private external fun isOccluded(window: Long): Boolean private external fun isOccluded(window: Long): Boolean
private external fun getAdapterName(device: Long): String private external fun getAdapterName(adapter: Long): String
private external fun getAdapterMemorySize(device: Long): Long private external fun getAdapterMemorySize(adapter: Long): Long
private external fun startRendering(): Long private external fun startRendering(): Long
private external fun endRendering(handle: Long) private external fun endRendering(handle: Long)
} }
package org.jetbrains.skiko.redrawer package org.jetbrains.skiko.redrawer
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.jetbrains.skiko.FrameDispatcher import org.jetbrains.skiko.*
import org.jetbrains.skiko.FrameLimiter 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 import org.jetbrains.skiko.context.SoftwareContextHandler
internal class SoftwareRedrawer( internal class SoftwareRedrawer(
private val layer: SkiaLayer, private val layer: SkiaLayer,
analytics: SkiaLayerAnalytics,
private val properties: SkiaLayerProperties private val properties: SkiaLayerProperties
) : Redrawer { ) : AWTRedrawer(layer, analytics, GraphicsApi.SOFTWARE_FAST) {
init {
onDeviceChosen("Software")
}
private val contextHandler = SoftwareContextHandler(layer) private val contextHandler = SoftwareContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo() override val renderInfo: String get() = contextHandler.rendererInfo()
...@@ -24,17 +26,22 @@ internal class SoftwareRedrawer( ...@@ -24,17 +26,22 @@ internal class SoftwareRedrawer(
} }
if (layer.isShowing) { if (layer.isShowing) {
layer.update(System.nanoTime()) update(System.nanoTime())
layer.inDrawScope(contextHandler::draw) inDrawScope(contextHandler::draw)
} }
} }
init {
onContextInit()
}
override fun dispose() { override fun dispose() {
frameDispatcher.cancel() frameDispatcher.cancel()
contextHandler.dispose() contextHandler.dispose()
runBlocking { runBlocking {
frameJob.cancelAndJoin() frameJob.cancelAndJoin()
} }
super.dispose()
} }
override fun needRedraw() { override fun needRedraw() {
...@@ -42,7 +49,7 @@ internal class SoftwareRedrawer( ...@@ -42,7 +49,7 @@ internal class SoftwareRedrawer(
} }
override fun redrawImmediately() { override fun redrawImmediately() {
layer.update(System.nanoTime()) update(System.nanoTime())
layer.inDrawScope(contextHandler::draw) inDrawScope(contextHandler::draw)
} }
} }
\ No newline at end of file
...@@ -8,8 +8,9 @@ import org.jetbrains.skiko.context.OpenGLContextHandler ...@@ -8,8 +8,9 @@ import org.jetbrains.skiko.context.OpenGLContextHandler
internal class WindowsOpenGLRedrawer( internal class WindowsOpenGLRedrawer(
private val layer: SkiaLayer, private val layer: SkiaLayer,
analytics: SkiaLayerAnalytics,
private val properties: SkiaLayerProperties private val properties: SkiaLayerProperties
) : Redrawer { ) : AWTRedrawer(layer, analytics, GraphicsApi.OPENGL) {
private val contextHandler = OpenGLContextHandler(layer) private val contextHandler = OpenGLContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo() override val renderInfo: String get() = contextHandler.rendererInfo()
...@@ -19,11 +20,13 @@ internal class WindowsOpenGLRedrawer( ...@@ -19,11 +20,13 @@ internal class WindowsOpenGLRedrawer(
throw RenderException("Cannot create Windows GL context") throw RenderException("Cannot create Windows GL context")
} }
makeCurrent(device, it) makeCurrent(device, it)
if (!isVideoCardSupported(layer.renderApi)) { if (!isVideoCardSupported(GraphicsApi.OPENGL, adapterName)) {
throw RenderException("Cannot create Windows GL context") 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 { init {
makeCurrent() makeCurrent()
...@@ -32,6 +35,7 @@ internal class WindowsOpenGLRedrawer( ...@@ -32,6 +35,7 @@ internal class WindowsOpenGLRedrawer(
// With dwmFlush it is stable (16.6-16.8 ms) // 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) // GLFW also uses dwmFlush (https://www.glfw.org/docs/3.0/window.html#window_swap)
setSwapInterval(0) setSwapInterval(0)
onContextInit()
} }
override fun dispose() { override fun dispose() {
...@@ -39,7 +43,7 @@ internal class WindowsOpenGLRedrawer( ...@@ -39,7 +43,7 @@ internal class WindowsOpenGLRedrawer(
makeCurrent() makeCurrent()
contextHandler.dispose() contextHandler.dispose()
deleteContext(context) deleteContext(context)
isDisposed = true super.dispose()
} }
override fun needRedraw() { override fun needRedraw() {
...@@ -50,7 +54,7 @@ internal class WindowsOpenGLRedrawer( ...@@ -50,7 +54,7 @@ internal class WindowsOpenGLRedrawer(
override fun redrawImmediately() { override fun redrawImmediately() {
check(!isDisposed) { "WindowsOpenGLRedrawer is disposed" } check(!isDisposed) { "WindowsOpenGLRedrawer is disposed" }
layer.inDrawScope { inDrawScope {
update(System.nanoTime()) update(System.nanoTime())
makeCurrent() makeCurrent()
contextHandler.draw() contextHandler.draw()
...@@ -59,12 +63,8 @@ internal class WindowsOpenGLRedrawer( ...@@ -59,12 +63,8 @@ internal class WindowsOpenGLRedrawer(
} }
} }
private fun update(nanoTime: Long) {
layer.update(nanoTime)
}
private fun draw() { private fun draw() {
layer.inDrawScope(contextHandler::draw) inDrawScope(contextHandler::draw)
} }
private fun makeCurrent() = makeCurrent(device, context) private fun makeCurrent() = makeCurrent(device, context)
......
...@@ -3,18 +3,22 @@ package org.jetbrains.skiko.redrawer ...@@ -3,18 +3,22 @@ package org.jetbrains.skiko.redrawer
import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkiaLayerProperties import org.jetbrains.skiko.SkiaLayerProperties
import org.jetbrains.skiko.RenderException import org.jetbrains.skiko.RenderException
import org.jetbrains.skiko.SkiaLayerAnalytics
internal class WindowsSoftwareRedrawer( internal class WindowsSoftwareRedrawer(
layer: SkiaLayer, layer: SkiaLayer,
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties properties: SkiaLayerProperties
) : AbstractDirectSoftwareRedrawer(layer, properties) { ) : AbstractDirectSoftwareRedrawer(layer, analytics, properties) {
init { init {
onDeviceChosen("Software")
device = createDevice(layer.contentHandle, layer.transparency).also { device = createDevice(layer.contentHandle, layer.transparency).also {
if (it == 0L) { if (it == 0L) {
throw RenderException("Failed to create Software device") throw RenderException("Failed to create Software device")
} }
} }
onContextInit()
} }
private external fun createDevice(contentHandle: Long, transparency: Boolean): Long private external fun createDevice(contentHandle: Long, transparency: Boolean): Long
......
...@@ -15,3 +15,15 @@ package org.jetbrains.skiko ...@@ -15,3 +15,15 @@ package org.jetbrains.skiko
" Make sure you fully read and understand documentation of the declaration that is marked as a delicate API." " Make sure you fully read and understand documentation of the declaration that is marked as a delicate API."
) )
annotation class DelicateSkikoApi 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 @@ ...@@ -19,18 +19,6 @@
const int BuffersCount = 2; 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 class DirectXDevice
{ {
public: public:
...@@ -214,94 +202,63 @@ extern "C" ...@@ -214,94 +202,63 @@ extern "C"
return impl(pSrcData, SrcDataSize, pSourceName, pDefines, pInclude, pEntrypoint, pTarget, Flags1, Flags2, ppCode, ppErrorMsgs); 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; DXGI_ADAPTER_DESC1 desc;
hardwareAdapter->GetDesc1(&desc); hardwareAdapter->GetDesc1(&desc);
if ((desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) != 0) if ((desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) != 0) {
{
return true;
}
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; return false;
} }
bool defineHardwareAdapter(DXGI_GPU_PREFERENCE adapterPriority, IDXGIFactory4 *pFactory, IDXGIAdapter1 **ppAdapter) std::wstring tmp(desc.Description);
{ std::string name(tmp.begin(), tmp.end());
*ppAdapter = nullptr; jstring jname = env->NewStringUTF(name.c_str());
#if defined(__dxgi1_6_h__) && defined(NTDDI_WIN10_RS4) static jclass cls = (jclass) env->NewGlobalRef(env->FindClass("org/jetbrains/skiko/redrawer/Direct3DRedrawer"));
gr_cp<IDXGIFactory6> factory6; static jmethodID method = env->GetMethodID(cls, "isAdapterSupported", "(Ljava/lang/String;)Z");
if (SUCCEEDED(pFactory->QueryInterface(IID_PPV_ARGS(&factory6))))
{ return env->CallBooleanMethod(redrawer, method, jname);
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();
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;
} }
gr_cp<IDXGIFactory6> factory6;
if (!SUCCEEDED(deviceFactory->QueryInterface(IID_PPV_ARGS(&factory6)))) {
return 0;
} }
#endif
if (*ppAdapter == nullptr) for (UINT adapterIndex = 0;; ++adapterIndex) {
{ IDXGIAdapter1 *adapter = nullptr;
for (UINT adapterIndex = 0;; ++adapterIndex) if (!SUCCEEDED(factory6->EnumAdapterByGpuPreference(adapterIndex, (DXGI_GPU_PREFERENCE) adapterPriority, IID_PPV_ARGS(&adapter)))) {
{
IDXGIAdapter1 *pAdapter = nullptr;
if (DXGI_ERROR_NOT_FOUND == pFactory->EnumAdapters1(adapterIndex, &pAdapter))
{
break; break;
} }
if (SUCCEEDED(D3D12CreateDevice(pAdapter, D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr))) if (
{ SUCCEEDED(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr)) &&
if (isNotSupported(pAdapter)) isAdapterSupported(env, redrawer, adapter)
{ ) {
pAdapter->Release(); return toJavaPointer(adapter);
continue; } else {
} adapter->Release();
*ppAdapter = pAdapter;
return true;
} }
pAdapter->Release();
} }
}
return false; return 0;
} }
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_Direct3DRedrawer_createDirectXDevice( 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; gr_cp<IDXGIFactory4> deviceFactory;
if (!SUCCEEDED(CreateDXGIFactory1(IID_PPV_ARGS(&deviceFactory)))) if (!SUCCEEDED(CreateDXGIFactory1(IID_PPV_ARGS(&deviceFactory)))) {
{
return 0; return 0;
} }
gr_cp<IDXGIAdapter1> hardwareAdapter; if (adapterPtr == 0) {
if (!defineHardwareAdapter((DXGI_GPU_PREFERENCE) adapterPriority, deviceFactory.get(), &hardwareAdapter))
{
return 0; return 0;
} }
gr_cp<IDXGIAdapter1> adapter((IDXGIAdapter1 *) adapterPtr);
D3D_FEATURE_LEVEL maxSupportedFeatureLevel = D3D_FEATURE_LEVEL_11_0; D3D_FEATURE_LEVEL maxSupportedFeatureLevel = D3D_FEATURE_LEVEL_11_0;
D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL featureLevels[] = {
...@@ -311,21 +268,17 @@ extern "C" ...@@ -311,21 +268,17 @@ extern "C"
D3D_FEATURE_LEVEL_11_0 D3D_FEATURE_LEVEL_11_0
}; };
for (int i = 0; i < _countof(featureLevels); i++) for (int i = 0; i < _countof(featureLevels); i++) {
{ if (SUCCEEDED(D3D12CreateDevice(adapter.get(), featureLevels[i], _uuidof(ID3D12Device), nullptr))) {
if (SUCCEEDED(D3D12CreateDevice(hardwareAdapter.get(), featureLevels[i], _uuidof(ID3D12Device), nullptr)))
{
maxSupportedFeatureLevel = featureLevels[i]; maxSupportedFeatureLevel = featureLevels[i];
break; break;
} }
} }
gr_cp<ID3D12Device> device; 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; return 0;
} }
deviceFactory.reset(nullptr);
// Create the command queue // Create the command queue
gr_cp<ID3D12CommandQueue> queue; gr_cp<ID3D12CommandQueue> queue;
...@@ -333,13 +286,12 @@ extern "C" ...@@ -333,13 +286,12 @@ extern "C"
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; 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; return 0;
} }
DirectXDevice *d3dDevice = new DirectXDevice(); DirectXDevice *d3dDevice = new DirectXDevice();
d3dDevice->backendContext.fAdapter = hardwareAdapter; d3dDevice->backendContext.fAdapter = adapter;
d3dDevice->backendContext.fDevice = device; d3dDevice->backendContext.fDevice = device;
d3dDevice->backendContext.fQueue = queue; d3dDevice->backendContext.fQueue = queue;
d3dDevice->backendContext.fProtectedContext = GrProtected::kNo; d3dDevice->backendContext.fProtectedContext = GrProtected::kNo;
...@@ -348,8 +300,7 @@ extern "C" ...@@ -348,8 +300,7 @@ extern "C"
d3dDevice->queue = queue; d3dDevice->queue = queue;
d3dDevice->window = (HWND)contentHandle; d3dDevice->window = (HWND)contentHandle;
if (transparency) if (transparency) {
{
//TODO: current swapChain does not support transparency //TODO: current swapChain does not support transparency
return 0; return 0;
// HWND wnd = GetAncestor(d3dDevice->window, GA_PARENT); // HWND wnd = GetAncestor(d3dDevice->window, GA_PARENT);
...@@ -493,24 +444,23 @@ extern "C" ...@@ -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; DXGI_ADAPTER_DESC1 desc;
d3dDevice->backendContext.fAdapter->GetDesc1(&desc); adapter->GetDesc1(&desc);
std::wstring w_tmp(desc.Description); std::wstring w_tmp(desc.Description);
std::string currentAdapterName(w_tmp.begin(), w_tmp.end()); std::string currentAdapterName(w_tmp.begin(), w_tmp.end());
jstring result = env->NewStringUTF(currentAdapterName.c_str()); return env->NewStringUTF(currentAdapterName.c_str());
return result;
} }
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; DXGI_ADAPTER_DESC1 desc;
d3dDevice->backendContext.fAdapter->GetDesc1(&desc); adapter->GetDesc1(&desc);
__int64 result = desc.DedicatedVideoMemory; __int64 result = desc.DedicatedVideoMemory;
return (jlong)result; return (jlong)result;
} }
......
...@@ -73,8 +73,8 @@ void logJavaException(JNIEnv *env, const char *function, DWORD sehCode) ...@@ -73,8 +73,8 @@ void logJavaException(JNIEnv *env, const char *function, DWORD sehCode)
char buffer[200]; char buffer[200];
int result = snprintf( int result = snprintf(
buffer, sizeof(buffer) - 1, "Native exception in [%s]:\nSEH description: %s\n", function, getDescription(sehCode)); buffer, sizeof(buffer) - 1, "Native exception in [%s]:\nSEH description: %s\n", function, getDescription(sehCode));
jclass logClass = env->FindClass("org/jetbrains/skiko/RenderExceptionsHandler"); static jclass logClass = (jclass) env->NewGlobalRef(env->FindClass("org/jetbrains/skiko/RenderExceptionsHandler"));
jmethodID logMethod = env->GetStaticMethodID(logClass, "logAndThrow", "(Ljava/lang/String;)V"); static jmethodID logMethod = env->GetStaticMethodID(logClass, "throwException", "(Ljava/lang/String;)V");
env->CallStaticVoidMethod(logClass, logMethod, env->NewStringUTF(buffer)); env->CallStaticVoidMethod(logClass, logMethod, env->NewStringUTF(buffer));
} }
} }
......
package org.jetbrains.skiko package org.jetbrains.skiko
private val notSupportedAdapters by lazy { private val notSupportedAdapters: Set<NotSupportedAdapter> by lazy {
val resource = SkiaLayer::class.java.getResource("/not-supported-adapter.list").readText() val resource = SkiaLayer::class.java.getResource("/not-supported-adapter.list")?.readText().orEmpty()
resource.split(";").map { it.trim() } resource.splitToSequence(";").map { it.trim() }.mapNotNullTo(mutableSetOf()) {
} val splits = it.split(":")
val api = when (splits.getOrNull(0)) {
internal fun isVideoCardSupported(renderApi: GraphicsApi): Boolean { "directx" -> GraphicsApi.DIRECT3D
return when (renderApi) { "opengl" -> GraphicsApi.OPENGL
GraphicsApi.DIRECT3D -> { else -> return@mapNotNullTo null
true
}
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 { val name = splits.getOrNull(1) ?: return@mapNotNullTo null
if (adapter?.startsWith(it) == true) { NotSupportedAdapter(api, name)
return false
}
}
true
}
else -> true
} }
} }
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 ...@@ -3,7 +3,12 @@ package org.jetbrains.skiko
import org.jetbrains.skiko.redrawer.* import org.jetbrains.skiko.redrawer.*
internal interface RenderFactory { 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 { companion object {
val Default = makeDefaultRenderFactory() val Default = makeDefaultRenderFactory()
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
@property (weak) CALayer *container; @property (weak) CALayer *container;
@property (retain, strong) AWTMetalLayer *layer; @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<MTLCommandQueue> queue;
@property (retain, strong) id<CAMetalDrawable> drawableHandle; @property (retain, strong) id<CAMetalDrawable> drawableHandle;
...@@ -63,7 +63,7 @@ ...@@ -63,7 +63,7 @@
if (self) if (self)
{ {
self.layer = nil; self.layer = nil;
self.device = nil; self.adapter = nil;
self.queue = nil; self.queue = nil;
self.drawableHandle = nil; self.drawableHandle = nil;
} }
...@@ -82,7 +82,7 @@ JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_makeMeta ...@@ -82,7 +82,7 @@ JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_makeMeta
@autoreleasepool { @autoreleasepool {
MetalDevice *device = (__bridge MetalDevice *) (void*) devicePtr; MetalDevice *device = (__bridge MetalDevice *) (void*) devicePtr;
GrMtlBackendContext backendContext = {}; GrMtlBackendContext backendContext = {};
backendContext.fDevice.retain((__bridge GrMTLHandle) device.device); backendContext.fDevice.retain((__bridge GrMTLHandle) device.adapter);
backendContext.fQueue.retain((__bridge GrMTLHandle) device.queue); backendContext.fQueue.retain((__bridge GrMTLHandle) device.queue);
return (jlong) GrDirectContext::MakeMetal(backendContext).release(); return (jlong) GrDirectContext::MakeMetal(backendContext).release();
} }
...@@ -177,10 +177,21 @@ id<MTLDevice> MTLCreateIntegratedDevice(int adapterPriority) { ...@@ -177,10 +177,21 @@ id<MTLDevice> MTLCreateIntegratedDevice(int adapterPriority) {
return gpu; 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( 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 { @autoreleasepool {
id<MTLDevice> adapter = (__bridge_transfer id<MTLDevice>) (void *) adapterPtr;
MetalDevice *device = [MetalDevice new]; MetalDevice *device = [MetalDevice new];
NSObject<JAWT_SurfaceLayers>* dsi_mac = (__bridge NSObject<JAWT_SurfaceLayers> *) (void*) platformInfoPtr; 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 ...@@ -194,15 +205,14 @@ JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_createMe
[container addSublayer: layer]; [container addSublayer: layer];
layer.javaRef = env->NewGlobalRef(redrawer); layer.javaRef = env->NewGlobalRef(redrawer);
id<MTLDevice> fDevice = MTLCreateIntegratedDevice(adapterPriority); id<MTLCommandQueue> fQueue = [adapter newCommandQueue];
id<MTLCommandQueue> fQueue = [fDevice newCommandQueue];
device.container = container; device.container = container;
device.layer = layer; device.layer = layer;
device.device = fDevice; device.adapter = adapter;
device.queue = fQueue; device.queue = fQueue;
device.layer.device = device.device; device.layer.device = device.adapter;
device.layer.pixelFormat = MTLPixelFormatBGRA8Unorm; device.layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
device.layer.contentsGravity = kCAGravityTopLeft; device.layer.contentsGravity = kCAGravityTopLeft;
...@@ -296,22 +306,21 @@ JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_disposeDe ...@@ -296,22 +306,21 @@ JNIEXPORT void JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_disposeDe
} }
JNIEXPORT jstring JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_getAdapterName( JNIEXPORT jstring JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_getAdapterName(
JNIEnv *env, jobject redrawer, jlong devicePtr) JNIEnv *env, jobject redrawer, jlong adapterPtr)
{ {
@autoreleasepool { @autoreleasepool {
MetalDevice *device = (__bridge MetalDevice *) (void *) devicePtr; id<MTLDevice> adapter = (__bridge id<MTLDevice>) (void *) adapterPtr;
const char *currentAdapterName = [[device.device name] cStringUsingEncoding:NSASCIIStringEncoding]; const char *currentAdapterName = [[adapter name] cStringUsingEncoding:NSASCIIStringEncoding];
jstring result = env->NewStringUTF(currentAdapterName); return env->NewStringUTF(currentAdapterName);
return result;
} }
} }
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_getAdapterMemorySize( JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_getAdapterMemorySize(
JNIEnv *env, jobject redrawer, jlong devicePtr) JNIEnv *env, jobject redrawer, jlong adapterPtr)
{ {
@autoreleasepool { @autoreleasepool {
MetalDevice *device = (__bridge MetalDevice *) (void *) devicePtr; id<MTLDevice> adapter = (__bridge id<MTLDevice>) (void *) adapterPtr;
uint64_t totalMemory = [device.device recommendedMaxWorkingSetSize]; uint64_t totalMemory = [adapter recommendedMaxWorkingSetSize];
return (jlong)totalMemory; return (jlong)totalMemory;
} }
} }
......
...@@ -5,6 +5,7 @@ import org.jetbrains.skia.* ...@@ -5,6 +5,7 @@ import org.jetbrains.skia.*
import org.jetbrains.skiko.util.UiTestScope import org.jetbrains.skiko.util.UiTestScope
import org.jetbrains.skiko.util.UiTestWindow import org.jetbrains.skiko.util.UiTestWindow
import org.jetbrains.skiko.util.uiTest import org.jetbrains.skiko.util.uiTest
import org.junit.Assume.assumeTrue
import org.junit.Test import org.junit.Test
import java.awt.Point import java.awt.Point
import javax.swing.WindowConstants import javax.swing.WindowConstants
...@@ -128,6 +129,8 @@ class SkiaLayerPerformanceTest { ...@@ -128,6 +129,8 @@ class SkiaLayerPerformanceTest {
@Test @Test
fun `FPS is near display refresh rate (multiple windows)`() = uiTest { 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 -> val helpers = (1..3).map { index ->
performanceHelper(width = 40, height = 20, frameCount = 300, deviatedTerminalCount = 20).apply { performanceHelper(width = 40, height = 20, frameCount = 300, deviatedTerminalCount = 20).apply {
window.toFront() window.toFront()
......
...@@ -28,6 +28,7 @@ import javax.swing.JFrame ...@@ -28,6 +28,7 @@ import javax.swing.JFrame
import javax.swing.JLayeredPane import javax.swing.JLayeredPane
import javax.swing.WindowConstants import javax.swing.WindowConstants
import kotlin.random.Random import kotlin.random.Random
import kotlin.test.assertNotNull
import kotlin.test.assertTrue import kotlin.test.assertTrue
@Suppress("BlockingMethodInNonBlockingContext", "SameParameterValue") @Suppress("BlockingMethodInNonBlockingContext", "SameParameterValue")
...@@ -374,6 +375,7 @@ class SkiaLayerTest { ...@@ -374,6 +375,7 @@ class SkiaLayerTest {
override fun createRedrawer( override fun createRedrawer(
layer: SkiaLayer, layer: SkiaLayer,
renderApi: GraphicsApi, renderApi: GraphicsApi,
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties properties: SkiaLayerProperties
) = object : Redrawer { ) = object : Redrawer {
private val contextHandler = object : JvmContextHandler(layer) { private val contextHandler = object : JvmContextHandler(layer) {
...@@ -397,6 +399,7 @@ class SkiaLayerTest { ...@@ -397,6 +399,7 @@ class SkiaLayerTest {
override fun createRedrawer( override fun createRedrawer(
layer: SkiaLayer, layer: SkiaLayer,
renderApi: GraphicsApi, renderApi: GraphicsApi,
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties properties: SkiaLayerProperties
) = throw RenderException() ) = throw RenderException()
} }
...@@ -410,6 +413,7 @@ class SkiaLayerTest { ...@@ -410,6 +413,7 @@ class SkiaLayerTest {
override fun createRedrawer( override fun createRedrawer(
layer: SkiaLayer, layer: SkiaLayer,
renderApi: GraphicsApi, renderApi: GraphicsApi,
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties properties: SkiaLayerProperties
) = object : Redrawer { ) = object : Redrawer {
override fun dispose() = Unit override fun dispose() = Unit
...@@ -456,12 +460,13 @@ class SkiaLayerTest { ...@@ -456,12 +460,13 @@ class SkiaLayerTest {
override fun createRedrawer( override fun createRedrawer(
layer: SkiaLayer, layer: SkiaLayer,
renderApi: GraphicsApi, renderApi: GraphicsApi,
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties properties: SkiaLayerProperties
): Redrawer { ): Redrawer {
return if (renderApi == GraphicsApi.SOFTWARE_COMPAT) { return if (renderApi == GraphicsApi.SOFTWARE_COMPAT) {
RenderFactory.Default.createRedrawer(layer, renderApi, properties) RenderFactory.Default.createRedrawer(layer, renderApi, analytics, properties)
} else { } else {
nonSoftwareRenderFactory.createRedrawer(layer, renderApi, properties) nonSoftwareRenderFactory.createRedrawer(layer, renderApi, analytics, properties)
} }
} }
} }
...@@ -518,6 +523,119 @@ class SkiaLayerTest { ...@@ -518,6 +523,119 @@ class SkiaLayerTest {
testRenderText(OS.MacOS) 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 { private fun testRenderText(os: OS) = uiTest {
assumeTrue(hostOs == os) assumeTrue(hostOs == os)
......
...@@ -33,10 +33,12 @@ internal class UiTestScope( ...@@ -33,10 +33,12 @@ internal class UiTestScope(
) : CoroutineScope by scope { ) : CoroutineScope by scope {
fun UiTestWindow( fun UiTestWindow(
properties: SkiaLayerProperties = SkiaLayerProperties(), properties: SkiaLayerProperties = SkiaLayerProperties(),
analytics: SkiaLayerAnalytics = SkiaLayerAnalytics.Empty,
renderFactory: RenderFactory = RenderFactory.Default renderFactory: RenderFactory = RenderFactory.Default
) = object : UiTestWindow() { ) = object : UiTestWindow() {
override val layer: SkiaLayer = SkiaLayer( override val layer: SkiaLayer = SkiaLayer(
properties = properties.copy(renderApi = renderApi), properties = properties.copy(renderApi = renderApi),
analytics = analytics,
renderFactory = renderFactory 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