Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenGlControlBase stops rendering randomly. #17865

Open
kkwpsv opened this issue Jan 2, 2025 · 3 comments
Open

OpenGlControlBase stops rendering randomly. #17865

kkwpsv opened this issue Jan 2, 2025 · 3 comments
Labels

Comments

@kkwpsv
Copy link
Contributor

kkwpsv commented Jan 2, 2025

Describe the bug

A OpenGlControlBase control which continuously call RequestNextFrameRendering in OnOpenGlRender, will stop rendering after a random period of time. OnOpenGlRender will never be called until until something else triggers rendering like resizing window.__

To Reproduce

A continuously rendering OpenGlControlBase control and waiting for a memont.

Expected behavior

No response

Avalonia version

11.2.3

OS

No response

Additional context

I have debugged and found the direct reason. It's related to compositor. But I am not sure how to fix it.

In Compositor.RequestCompositionBatchCommitAsync method:
image
When pending is null (usually not), we will call _triggerCommitRequested directly. It will call MediaContext.ICompositorScheduler.CommitRequested finally.

And in MediaContext.ICompositorScheduler.CommitRequested:
image
The compositor is already in _requestedCommits, and ScheduleRender will not be called. So the control won't render anymore.

The call stack at this time is like this (the line numbers are decompiled by Visual Studio, just ignore them):
image

In MediaContext.CommitCompositorsWithThrottling:
image
_requestedCommits will be cleared after CommitCompositor. So calling MediaContext.ICompositorScheduler.CommitRequested insides CommitCompositor will always cause this issue.

So, apparently, a workaround is not calling RequestNextFrameRendering directly in OnOpenGlRender but via Dispatcher.Post.
In this sample, RequestNextFrameRendering is called directly. But in this sample, it's called via UIThread.Post.
Is this a bug or designed to calling RequestNextFrameRendering via UIThread.Post?

@kkwpsv kkwpsv added the bug label Jan 2, 2025
@timunie
Copy link
Contributor

timunie commented Jan 2, 2025

I'd think you need to give the renderer time to finish, that's what Dispatcher ensures. So probably by design.

@kekekeks can you confirm that?

@kkwpsv
Copy link
Contributor Author

kkwpsv commented Jan 3, 2025

If it's by design, i think we should prevent to call RequestNextFrameRendering directly in OnOpenGlRender.

@Coloryr
Copy link
Contributor

Coloryr commented Jan 3, 2025

try use RequestAnimationFrame?

/// <summary>
/// Fps限制器
/// </summary>
public class FpsTimer
{
    private readonly OpenGlControlBase _render;
    private TopLevel _top;
    private readonly Timer _timer;
    private bool _pause = true;
    private bool _last;

    public bool LowFps { get; set; }
    public Action<int>? FpsTick { private get; init; }
    public bool Pause
    {
        get
        {
            return _pause;
        }
        set
        {
            //不改变
            if (_pause == value)
            {
                return;
            }
            //暂停 -> 继续
            if (_pause && value == false)
            {
                _top ??= TopLevel.GetTopLevel(_render) ?? throw new Exception();
                _pause = false;
                _timer.Start();
                Go();
            }
            else //暂停
            {
                _pause = true;
                _timer.Stop();
            }
        }
    }
    public int NowFps { get; private set; }

    public FpsTimer(OpenGlControlBase render)
    {
        _render = render;
        _timer = new(TimeSpan.FromSeconds(1));
        _timer.BeginInit();
        _timer.AutoReset = true;
        _timer.Elapsed += Timer_Elapsed;
        _timer.EndInit();
    }

    private void Go()
    {
        if (!_pause)
        {
            _top.RequestAnimationFrame((t) =>
            {
                if (LowFps)
                {
                    _last = !_last;
                    if (_last)
                    {
                        _render.RequestNextFrameRendering();
                        NowFps++;
                    }
                }
                else
                {
                    _render.RequestNextFrameRendering();
                    NowFps++;
                }
                Go();
            });
        }
    }

    private void Timer_Elapsed(object? sender, ElapsedEventArgs e)
    {
        if (!Pause)
        {
            FpsTick?.Invoke(NowFps);
        }
        NowFps = 0;
    }

    public void Close()
    {
        _pause = true;
        _timer.Stop();
        _timer.Close();
        _timer.Dispose();
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants