Unverified Commit 50223f6b authored by Igor Demin's avatar Igor Demin Committed by GitHub

Merge pull request #61 from JetBrains/fps-rewrite

Rewrite FPS counting
parents dc374a75 676bf9ef
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="SkijaInjectSample (show long frames) " type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$/samples/SkijaInjectSample" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="-Dskiko.fps.longFrames.show=true" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="run" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2">
<option name="Gradle.BeforeRunTask" enabled="false" tasks="publishToMavenLocal" externalProjectPath="$PROJECT_DIR$/skiko" vmOptions="" scriptParameters="" />
</method>
</configuration>
</component>
\ No newline at end of file
package org.jetbrains.skiko package org.jetbrains.skiko
import java.util.* import java.awt.Component
import kotlin.math.roundToInt import kotlin.math.roundToInt
internal class FPSCounter( internal class FPSCounter(
private val count: Int, private val periodSeconds: Double,
private val probability: Double private val showLongFrames: Boolean,
private val getLongFrameMillis: () -> Double
) { ) {
private var i = 0 private val times = mutableListOf<Long>()
private val times = LinkedList<Double>() private var lastLogTime = System.nanoTime()
private var t1 = System.nanoTime() private var lastTime = System.nanoTime()
/**
* [value] 0.0 - min, 1.0 - max, 0.5 - median
*/
private fun MutableList<Double>.quantile(value: Double) : Double {
val index = (value * (size - 1)).toInt()
return sorted()[index]
}
fun tick() { fun tick() {
val t2 = System.nanoTime() val time = System.nanoTime()
val frameTime = (t2 - t1) / 1E6 val timestamp = time.nanosToMillis().toLong()
t1 = t2 val frameTime = time - lastTime
lastTime = time
i++
times.add(frameTime) times.add(frameTime)
if (times.size > count) { if (showLongFrames && frameTime > getLongFrameMillis().millisToNanos()) {
times.removeFirst() println("[%d] Long frame %.2f ms".format(timestamp, frameTime.nanosToMillis()))
} }
if (i % count == 0) { if ((time - lastLogTime) > periodSeconds.secondsToNanos()) {
val quantile = (1 - probability) / 2.0 val average = (nanosPerSecond / times.average()).roundToInt()
val average = (1000.0 / times.average()).roundToInt() val min = (nanosPerSecond / times.max()!!).roundToInt()
val min = (1000.0 / times.quantile(1 - quantile)).roundToInt() val max = (nanosPerSecond / times.min()!!).roundToInt()
val max = (1000.0 / times.quantile(quantile)).roundToInt() println("[$timestamp] FPS $average ($min-$max)")
val probability = (100 * probability).roundToInt() times.clear()
println("FPS $average ($min-$max $probability%)") lastLogTime = time
}
} }
private val nanosPerMillis = 1_000_000.0
private val nanosPerSecond = 1_000_000_000.0
private fun Long.nanosToMillis(): Double = this / nanosPerMillis
private fun Double.millisToNanos(): Long = (this * nanosPerMillis).toLong()
private fun Double.secondsToNanos(): Long = (this * nanosPerSecond).toLong()
}
internal fun defaultFPSCounter(
component: Component
): FPSCounter? = with(SkikoProperties) {
if (!SkikoProperties.fpsEnabled) return@with null
// it is slow on Linux (100ms), so we cache it. Also refreshRate available only after window is visible
val refreshRate by lazy { component.graphicsConfiguration.device.displayMode.refreshRate }
FPSCounter(
periodSeconds = fpsPeriodSeconds,
showLongFrames = fpsLongFramesShow,
getLongFrameMillis = {
fpsLongFramesMillis ?: 1.5 * 1000 / refreshRate
} }
)
} }
\ No newline at end of file
package org.jetbrains.skiko package org.jetbrains.skiko
import org.jetbrains.skija.* import org.jetbrains.skija.Canvas
import org.jetbrains.skiko.redrawer.Redrawer import org.jetbrains.skija.ClipMode
import org.jetbrains.skiko.redrawer.RasterRedrawer import org.jetbrains.skija.Picture
import org.jetbrains.skiko.context.createContextHandler import org.jetbrains.skija.PictureRecorder
import org.jetbrains.skija.Rect
import org.jetbrains.skiko.context.SoftwareContextHandler import org.jetbrains.skiko.context.SoftwareContextHandler
import org.jetbrains.skiko.context.createContextHandler
import org.jetbrains.skiko.redrawer.RasterRedrawer
import org.jetbrains.skiko.redrawer.Redrawer
import java.awt.Graphics import java.awt.Graphics
import javax.swing.SwingUtilities.isEventDispatchThread import javax.swing.SwingUtilities.isEventDispatchThread
...@@ -64,18 +68,14 @@ open class SkiaLayer : HardwareLayer() { ...@@ -64,18 +68,14 @@ open class SkiaLayer : HardwareLayer() {
redrawer?.needRedraw() redrawer?.needRedraw()
} }
private val fpsCounter = FPSCounter( @Suppress("LeakingThis")
count = SkikoProperties.fpsCount, private val fpsCounter = defaultFPSCounter(this)
probability = SkikoProperties.fpsProbability
)
override fun update(nanoTime: Long) { override fun update(nanoTime: Long) {
check(!isDisposed) check(!isDisposed)
check(isEventDispatchThread()) check(isEventDispatchThread())
if (SkikoProperties.fpsEnabled) { fpsCounter?.tick()
fpsCounter.tick()
}
val pictureWidth = (width * contentScale).toInt().coerceAtLeast(0) val pictureWidth = (width * contentScale).toInt().coerceAtLeast(0)
val pictureHeight = (height * contentScale).toInt().coerceAtLeast(0) val pictureHeight = (height * contentScale).toInt().coerceAtLeast(0)
......
...@@ -5,8 +5,15 @@ internal object SkikoProperties { ...@@ -5,8 +5,15 @@ internal object SkikoProperties {
val vsyncEnabled: Boolean by property("skiko.vsync.enabled", default = true) val vsyncEnabled: Boolean by property("skiko.vsync.enabled", default = true)
val fpsEnabled: Boolean by property("skiko.fps.enabled", default = false) val fpsEnabled: Boolean by property("skiko.fps.enabled", default = false)
val fpsCount: Int by property("skiko.fps.count", default = 300) val fpsPeriodSeconds: Double by property("skiko.fps.periodSeconds", default = 2.0)
val fpsProbability: Double by property("skiko.fps.probability", default = 0.97)
/**
* Show long frames which is longer than [fpsLongFramesMillis].
* If [fpsLongFramesMillis] isn't defined will show frames longer than 1.5 * (1000 / displayRefreshRate)
*/
val fpsLongFramesShow: Boolean by property("skiko.fps.longFrames.show", default = false)
val fpsLongFramesMillis: Double? by property("skiko.fps.longFrames.millis", default = null)
val renderApi: GraphicsApi by lazy { val renderApi: GraphicsApi by lazy {
val environment = System.getenv("SKIKO_RENDER_API") val environment = System.getenv("SKIKO_RENDER_API")
...@@ -30,11 +37,11 @@ internal object SkikoProperties { ...@@ -30,11 +37,11 @@ internal object SkikoProperties {
System.getProperty(name)?.toBoolean() ?: default System.getProperty(name)?.toBoolean() ?: default
} }
private fun property(name: String, default: Int) = lazy { private fun property(name: String, default: Double) = lazy {
System.getProperty(name)?.toInt() ?: default System.getProperty(name)?.toDouble() ?: default
} }
private fun property(name: String, default: Double) = lazy { private fun property(name: String, default: Double?) = lazy {
System.getProperty(name)?.toDouble() ?: default System.getProperty(name)?.toDouble() ?: default
} }
} }
\ No newline at end of file
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