Open Suprndm opened 6 years ago
This never was a feature. Maybe they changed something in Forms... Did you update to a newer forms version?
SkiaSharp will always render as fast as possible, with as many frames as possible.
Did you maybe change devices, OS versions or platform?
I will have a look, but I don't think anything significant has changed.
I see the change, and I think it has to do with the fact that we are no longer using the old GLSurfaceView
to render the content, but the new TextureView
.
Unfortunately, I can't seem to find where the original code limits the framerate. Also, I am not sure that we want to limit the frame rate.
I will look further and see what I can do.
Ok, I see
I use Skiasharp as a game renderer. And the physical Engine of the game is based on the frame rate, which is wrong ! It should have a separated refresh rate based on time.
I knew I had to make this change anytime soon. Now is just the perfect timing to do it =)
I was curious about what produced this change. Thank you for the hints of answers. The current app impacted by this change makes a massive use of SKCanvas.DrawBitmap(...)
. It does mean that the perfomances of SkiaSharp have increased, which is very good !
As it is not really an issue, should I close now ?
Let's leave it open, and I will try and find a real reason the GLSurfaceView
has a frame rate limit, but the TextureView
does not.
Just for reference later on in life:
This is the code I used the measure the FPS:
Stopwatch sw = Stopwatch.StartNew();
TimeSpan last;
private void OnPaint(object sender, SKPaintGLSurfaceEventArgs e)
{
var c = sw.Elapsed;
var ts = c - last;
last = c;
var fps = 1.0 / (ts.TotalSeconds);
var fpsString = fps.ToString("0,000.00");
Log.Debug("FPS", fpsString);
}
Also, with regards to the frame rate... It is never good to use a "constant" frame rate - either an assumption or using some limiter API. There is never a guarantee because another app may suddenly use the CPU/GPU, or particular logic in a particular frame may take longer to run, or even the time it takes to swap buffers may change between frames due to reasons.
I really need to look at this. Noticing 1.2K FPS in some emulators.
A simple FPS snippet: https://gist.github.com/mattleibow/4eb9cd26bcaaaefd5b2f7499d21bf4bd
I mean, isn't that good to have the FPS uncapped. As a user of SkiaSharp for backend game renderer as well, I find it quiet good improvement. Also if you are looking for the cause of capped vs uncapped FPS, then the explanation could be simple: GLSurfaceView probably uses V-Sync (*glSwapInterval call), when TextureView doesn't use that.
Having uncapped is nice, but a heavy drain. Also, it is no point having the updates faster than the screen - that wastes resources.
In the case of "capping" it is usually with regards to the screen refresh rate.
We might be able to look at Choreographer: https://stackoverflow.com/questions/55028881/how-to-determine-device-screen-refresh-start-stop-on-mobile-devices
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.Choreographer;
private class DisplayFrameSync extends Thread implements Choreographer.FrameCallback {
private volatile Handler signal;
private Consumer<Long> update;
public DisplayFrameSync(Consumer<Long> update) {
super();
this.update = update;
}
@Override
public void run() {
setName("DisplayFrameSyncThread");
Looper.prepare();
signal = new Handler() {
public void handleMessage(Message msg) {
Looper.myLooper().quit();
}
};
Choreographer.getInstance().postFrameCallback(this);
Looper.loop();
Choreographer.getInstance().removeFrameCallback(this);
}
public void signal() {
if (this.signal != null) {
this.signal.sendEmptyMessage(0);
this.signal = null;
}
}
@Override
public void doFrame(long timeNano) {
if (this.update != null) {
this.update.accept(timeNano);
}
Choreographer.getInstance().postFrameCallback(this);
}
}
I am getting
Maybe this could also be useful: https://developer.android.com/games/sdk/frame-pacing It mentions Choreographer as good but sub-optimal and suggests the Frame Pacing Library (which uses Choreographer but plus some additional optimizations).
A little bit of a luxury problem, but as you said, it drains the battery unnecessarily.
Matthew, can you point me into some code places in SkiaSharp where the Choreographer or even the Frame Pacing Library has to be put? I am quite unexperienced with this whole rendering thing and SkiaSharp, but would maybe have some time to experiment. Or would it have to be put into Xamarin.Forms even? 😮
Anyway, this whole SkiaSharp is really an excelent piece of work! 🐱💻
Frame Pacing Library is going to be a headache to add to a Xamarin project, being a C++ library. Has anyone any experience with that? I know its possible, but probably not worth the hassle?
I wonder how bad just using the choreographer is. Android calls it suboptimal, but what is suboptimal really? ;)
I've also looked at how monogame handles it frame pacing. [https://github.com/MonoGame/MonoGame/blob/fca271a296f0d2ebeec817a07c67dfd43139659f/MonoGame.Framework/Game.cs](Mono Game) They just use a timer setup. Could be enough?
Its hard to find information on this. I guess not many people use skia with a renderloop.
Would say Skiasharp not capping anything out-of-the box is rather a good thing. The more freedom we have the more we can create. For capping FPS i could suggest the following code:
// can exec this globally at app startup.
// every canvas can subscribe then to `OnFrame`.
protected static void SetupFrameLooper()
{
Tasks.StartDelayed(TimeSpan.FromMilliseconds(1), async () =>
{
await StartFrameLooperAsync(CancellationToken.None);
});
}
protected static async Task StartFrameLooperAsync(CancellationToken cancellationToken)
{
var frameStopwatch = new Stopwatch();
var loopStopwatch = Stopwatch.StartNew();
long lastFrameEnd = loopStopwatch.ElapsedMilliseconds;
var targetIntervalMs = 1000.0 / 120.0; // target fps
while (!cancellationToken.IsCancellationRequested)
{
frameStopwatch.Restart(); // Start measuring frame execution time
// Render DrawnView
OnFrame?.Invoke(0);
frameStopwatch.Stop();
var frameExecutionTimeMs = frameStopwatch.Elapsed.TotalMilliseconds;
var elapsedTimeSinceLastFrame = loopStopwatch.ElapsedMilliseconds - lastFrameEnd;
var timeToWait = targetIntervalMs - elapsedTimeSinceLastFrame - frameExecutionTimeMs;
if (timeToWait > 0)
Thread.Sleep(TimeSpan.FromMilliseconds(timeToWait));
lastFrameEnd = loopStopwatch.ElapsedMilliseconds;
}
}
In your callback you could check if the frame is dirty/other checks and invalidate the canvas in a proper way.
Description
Application framerate went from a capped 60fps to a ~300fps.
Upgrading to SkiaSharp.Views.Forms version="1.60.1" from version="1.59.3" Using SKGLView with HasRenderLoop to true,
Expected behavior
Like in version="1.59.3" framerate should not go higher than 60fps
Actual behavior
Framerate is not fixed, going from 60fps to 1000fps. It modifies all the animations of the application that are frame based.
Basic Information
Framerate is based on the elapsedTime between each PaintSurface event.