0

I am creating a custom renderer, that needs to display whatever I have rendered in my Vulkan engine. For this I have a VulkanSurfaceView, which inherits from MetalKit.MTKView on iOS, and from Android.Views.SurfaceView and ISurfaceHolderCallback on Android.

For iOS I can simply do this, which will draw a new frame continually, as long as the view is in focus:

public class VulkanSurfaceView : MTKView, IVulkanAppHost
{
    ...

    public override void Draw()
    {
        Renderer.Tick();
        base.Draw();
    }
}

However, on Android I have to do this, where I call Invalidate() from within the OnDraw method, else it is only called once. I think this code smells a bit, and I am not sure, if this is the "good" way of doing it. Is my solution okay? If not, does anyone have a better idea?

public class VulkanSurfaceView : SurfaceView, ISurfaceHolderCallback, IVulkanAppHost
{
    ...

    protected override void OnDraw(Canvas? canvas)
    {
        Renderer.Tick();
        base.OnDraw(canvas);
        Invalidate();
    }
}
  • Maybe create a `Timer`, and Invalidate in timer's `Elapsed` method? If control is named `mycontrol`, do `mycontrol.Invalidate();` in Elapsed method. OR BETTER: if you have a "game loop" with `UpdateTick` method that is doing all the updates for the tick, put an `Action` or `event EventHandler` where that game loop is, have your control set action to `() => mycontrol.Invalidate();`, and have `UpdateTick` invoke that action or handler. – ToolmakerSteve May 11 '22 at 18:49
  • Both great ideas. I have already had a solution utilizing timers, but I got a much lower framerate. I did not do exactly as you suggest, and perhaps I have simply done something wrong. I will try out your ideas again :) – JustAGuyWithALoaf May 12 '22 at 19:18
  • If you still have trouble getting a good frame rate, then a "brute force" approach is to run a timer at a high frame rate, BUT have it ONLY draw when something has changed. `public bool HasChanged;` ... `if (HasChanged) { HasChanged = false; ...redraw code... }`. Given a high timer frame rate, might want to avoid "piling up" redraws by [one of two "safe" timer implementations](https://stackoverflow.com/a/70295920/199364). – ToolmakerSteve May 12 '22 at 19:34
  • Thank you, that is a great idea! I will try it out. – JustAGuyWithALoaf May 12 '22 at 21:41

2 Answers2

0

Did you try calling setWillNotDraw(false) in your surfaceCreated method ? Refer the link

Bhargavi
  • 184
  • 12
  • I have done that, yes, but that does not make the Draw() function run continuously. Sadly. – JustAGuyWithALoaf May 12 '22 at 19:16
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community May 13 '22 at 20:10
0

Thank you to @ToolmakerSteve.

I created a Timer where I call Invalidate() if a new frame has been requested (by me via a simple bool). For anyone interested I do it like so:

protected override void OnDraw(Canvas? canvas) // Just to show the updated OnDraw-method
{
    Renderer.Tick();
    base.OnDraw(canvas);
}

public void SurfaceCreated(ISurfaceHolder holder)
{
    TickTimer = new System.Threading.Timer(state =>
    {
        AndroidApplication.SynchronizationContext.Send(_ => { if (NewFrameRequested) Invalidate(); }, state);

        try { TickTimer.Change(0, Timeout.Infinite); } catch (ObjectDisposedException) { }

    }, null, 0, Timeout.Infinite);
}

For now it is very simple, but it works and will probably grow. The reason for my initial bad framerate with this method was a misunderstanding of the "dueTime" of the Timer (see Timer Class), which I though was the framerate sought. This is actually the time between frames, which seems obvious now.

As @Bhargavi also kindly mentioned you need to set "setWillNotDraw(false)" if OnDraw is not being called when invalidating the view.