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

overlay:update() is ignored during pause events #8350

Closed
darsain opened this issue Nov 30, 2020 · 14 comments
Closed

overlay:update() is ignored during pause events #8350

darsain opened this issue Nov 30, 2020 · 14 comments

Comments

@darsain
Copy link

darsain commented Nov 30, 2020

  • mpv 0.32.0-821-g38275338ee built on Sun Nov 22 17:00:03 +08 2020
  • shinchiro
  • Windows 10

Description

Assuming:

local ov = mp.create_osd_overlay('ass-events')

Updating the ov.data and calling ov:update() during pause event on windows will be ignored and not update the overlay.

Reproduction steps

I've isolated the bug into this script: https://0x0.st/i7mf.lua

It renders a pause indicator when playback is paused.

  1. install the script
  2. open any video on windows
  3. spam pause

Most of the time, the indicator won't be rendered when video is paused.

In my tests, at least 40ms delay is needed before calling ov:update() for it to render consistently, but that might differ for others.

Log file

https://0x0.st/i7mW.txt

@avih
Copy link
Member

avih commented Nov 30, 2020

Can reproduce with this smaller test case:

mp.observe_property("pause", "bool", function(n, v)
    mp.osd_message("pause: " .. (v and "yes" or "no"))
end)

It seems that when the ass overlay is updated from the pause observer right after it was changed from playing to paused doesn't get displayed.

However, if the display is further delayed by some ms, then it does seem to work, e.g.

mp.observe_property("pause", "bool", function(n, v)
    mp.add_timeout(50/1000, function()
        mp.osd_message("pause: " .. (v and "yes" or "no"))
    end)
end)

On my system I found out that a delay of 50ms (as above) seem to always work, while 10ms usually is not enough.

This does suggest a possible race condition in mpv when a request to update the ASS overlay happens right after/when mpv is entering paused state.

@avih
Copy link
Member

avih commented Nov 30, 2020

@darsain can you confirm that you only see the issue when you try to update the overlay from the pause observer? or does it also fail to update from other triggers? for instance I tried to update it from a 500ms repeating interval timer, and it always worked.

@darsain
Copy link
Author

darsain commented Nov 30, 2020

I've not run into this in any other place yet, but I wasn't hunting for it either.

@avih
Copy link
Member

avih commented Nov 30, 2020

Thanks, so for now we only know that it happens when trying to update the overlay right after/when the player gets paused.

@darsain
Copy link
Author

darsain commented Dec 1, 2020

According to tomasklaen/uosc#44 (comment) this might not be windows only.

@po5
Copy link
Contributor

po5 commented Dec 1, 2020

Following up on tomasklaen/uosc#44 (comment)
I do get both "pause: no" and "pause: yes", however "pause: yes" sometimes doesn't display.
Added a print() to pause.lua to make sure it was being registered and can confirm this is only an issue with the osd.
Strangely, with --no-config I could reproduce this much more easily. I think I've isolated it to SSimSuperRes.glsl, go figure.
Having that shader seems to mitigate the issue, but I may just be going crazy.

Attached are logs both with and without --no-config
issue8350-no-config.txt
issue8350.txt

@avih
Copy link
Member

avih commented Dec 1, 2020

@po5 thanks. Does it get "fixed" if you use this instead at pause.lua?

mp.observe_property("pause", "bool", function(n, v)
    mp.add_timeout(100/1000, function()
        mp.osd_message("pause: " .. (v and "yes" or "no"))
    end)
end)

@po5
Copy link
Contributor

po5 commented Dec 1, 2020

It does "fix" it. Pausing no longer randomly skips "pause: yes", and there's only one case I've found where the message doesn't get displayed:
It's possible to make "pause: no" not display if, from a paused state, unpausing and pausing again really fast.
Certainly not as severe as before, because you pretty much have to try to even get that to happen.

@avih
Copy link
Member

avih commented Dec 1, 2020

It's possible to make "pause: no" not display if, from a paused state, unpausing and pausing again really fast.

Is that with the additional 100/1000 delay? or with the original script?

@po5
Copy link
Contributor

po5 commented Dec 1, 2020

Dang, had the words "With this script" in the original edit but removed it for whatever reason.
This is with the updated 100/1000 delay script.

@avih
Copy link
Member

avih commented Dec 1, 2020

:) thanks.

I don't know when it will be looked at, but I'm trying to collect useful info if someone tries to tackle it.

Obviously the additional delay is a workaround hack, but it also serves as an indication to the conditions which affect the issue.

Thanks again.

@Akemi Akemi added the scripting label May 7, 2021
dexeonify added a commit to dexeonify/mpv-config that referenced this issue Dec 29, 2021
If you pause or resume the video too quickly, either by clicking the
pause/play button or through keypress, the OSC does not update itself
immediately. This workaround adds a small delay after pausing so that
the OSC can update itself promptly.

Original commit from: cyl0/ModernX@8cd17a3
Code taken from here: mpv-player/mpv#8155 (comment)
Related issues:
mpv-player/mpv#8172
mpv-player/mpv#8350
@Hrxn
Copy link
Contributor

Hrxn commented Jan 18, 2022

According to darsain/uosc#44 (comment) this might not be windows only.

Maybe the title of this issue should be changed then?

chengsun added a commit to chengsun/mpv that referenced this issue Jan 21, 2022
This manifested most frequently as a bug where the OSD fails to update
after pausing or single-stepping forwards (issues mpv-player#8172, mpv-player#8350).

In `render_frame`, the vo thread takes the lock once before rendering
the frame, and once after. The lock is relinquished whilst the frame is
actually rendering.

The redraw_request flag was being cleared whilst holding the lock
*after* the video frame has been rendered; however this frame was
rendered according to the request fetched whilst holding the lock the
first time. If the OSD was updated during the rendering of this frame,
the redraw request is cleared once the frame finishes rendering even
though the updated OSD is not included in the rendered frame.
chengsun added a commit to chengsun/mpv that referenced this issue Jan 21, 2022
This manifested most frequently as a bug where the OSD fails to update
after pausing or single-stepping forwards (issues mpv-player#8172, mpv-player#8350).

In `render_frame`, the vo thread takes the lock once before rendering
the frame, and once after. The lock is relinquished whilst the frame is
actually rendering.

The `redraw_request` flag was being cleared whilst holding the lock
*after* the video frame has been rendered; however this frame was
rendered according to the request fetched whilst holding the lock the
first time. If the OSD was updated during the rendering of this frame,
the redraw request is cleared once the frame finishes rendering even
though the updated OSD is not included in the rendered frame.

This patch fixes the race condition by clearing the `redraw_request`
flag *before* the video frame rendering starts. That way, a redraw
request that comes in during frame rendering will not be cleared without
another call to `render_frame` or `do_redraw`.
chengsun added a commit to chengsun/mpv that referenced this issue Jan 21, 2022
This manifested most frequently as a bug where the OSD fails to update
after pausing or single-stepping forwards (issues mpv-player#8172, mpv-player#8350).

In `render_frame`, the vo thread takes the lock once before rendering
the frame, and once after. The lock is relinquished whilst the frame is
actually rendering.

The `redraw_request` flag was being cleared whilst holding the lock
*after* the video frame has been rendered; however this frame was
rendered according to the request fetched whilst holding the lock the
first time. If the OSD was updated during the rendering of this frame,
the redraw request is cleared once the frame finishes rendering even
though the updated OSD is not included in the rendered frame.

This patch fixes the race condition by clearing the `redraw_request`
flag *before* the video frame rendering starts. That way, a redraw
request that comes in during frame rendering will not be cleared without
another call to `render_frame` or `do_redraw`. However, we need to
ensure that if the frame is dropped, we set `redraw_request` again if it
was previously set. See the newly added comment in the code.
chengsun added a commit to chengsun/mpv that referenced this issue Jan 21, 2022
This manifested most frequently as a bug where the OSD fails to update
after pausing or single-stepping forwards (issues mpv-player#8172, mpv-player#8350).

In `render_frame`, the vo thread takes the lock once before rendering
the frame, and once after. The lock is relinquished whilst the frame is
actually rendering.

The `redraw_request` flag was being cleared whilst holding the lock
*after* the video frame has been rendered; however this frame was
rendered according to the request fetched whilst holding the lock the
first time. If the OSD was updated during the rendering of this frame,
the redraw request is cleared once the frame finishes rendering even
though the updated OSD is not included in the rendered frame.

This patch fixes the race condition by clearing the `redraw_request`
flag *before* the video frame rendering starts. That way, a redraw
request that comes in during frame rendering will not be cleared without
another call to `render_frame` or `do_redraw`. However, we need to
ensure that if the frame is dropped, we set `redraw_request` again if it
was previously set. See the newly added comment in the code.
chengsun added a commit to chengsun/mpv that referenced this issue Jan 21, 2022
This manifested most frequently as a bug where the OSD fails to update
after pausing or single-stepping forwards (issues mpv-player#8172, mpv-player#8350).

In `render_frame`, the vo thread takes the lock once before rendering
the frame, and once after. The lock is relinquished whilst the frame is
actually rendering.

The `redraw_request` flag was being cleared whilst holding the lock
*after* the video frame has been rendered; however this frame was
rendered according to the request fetched whilst holding the lock the
first time. If the OSD was updated during the rendering of this frame,
the redraw request is cleared once the frame finishes rendering even
though the updated OSD is not included in the rendered frame.

This patch fixes the race condition by clearing the `redraw_request`
flag *before* the video frame rendering starts. That way, a redraw
request that comes in during frame rendering will not be cleared without
another call to `render_frame` or `do_redraw`. However, we need to
ensure that if the frame is dropped, we set `redraw_request` again if it
was previously set. See the newly added comment in the code.
avih pushed a commit to avih/mpv that referenced this issue Jan 25, 2022
This manifested most frequently as a bug where the OSD fails to update
after pausing or single-stepping forwards (issues mpv-player#8172, mpv-player#8350).

In `render_frame`, the vo thread takes the lock once before rendering
the frame, and once after. The lock is relinquished whilst the frame is
actually rendering.

The redraw_request flag was being cleared whilst holding the lock
*after* the video frame has been rendered; however this frame was
rendered according to the request fetched whilst holding the lock the
first time. If the OSD was updated during the rendering of this frame,
the redraw request is cleared once the frame finishes rendering even
though the updated OSD is not included in the rendered frame.
@Dudemanguy Dudemanguy added core:osd and removed os:win labels Mar 1, 2023
@Dudemanguy Dudemanguy changed the title overlay:update() is ignored during pause events on windows overlay:update() is ignored during pause events Mar 1, 2023
@Dudemanguy
Copy link
Member

I was typing up a big long post, but I discovered something unexpected while doing so I'll update on this later. But this is not windows-only. I can trivially reproduce it on linux. I only looked at the osd_msg case but overlays is probably similar. #9735 appears to fix this, but I think that's just an accident/band-aid.

This absolutely is limited to only pause events though. I can say that for certain. When playback is active, the playloop will always force another redraw (duh), so this kind of race can only happen with pause.

Dudemanguy added a commit to Dudemanguy/mpv that referenced this issue Mar 2, 2023
There is a very subtle race in vo that can manifest itself on pause
events. In the renderloop, render_frame, unsurprisingly, does the heavy
lifting of actually queuing up and flipping the frames. This is called
during normal playback. Sometimes various parts of the player can make a
redraw request which will latter trigger another render of the frame
later down in the loop (do_redraw). Because these requests can happen at
essentially anytime, sometimes the redraw request will happen *before*
do_redraw and it'll be caught in render_frame. When this happens,
the existing render_frame run works perfectly fine as a redraw so it
clears out the request which is sensible. Normally this is all locked of
course, but there's one catch. render_frame has to unlock itself when
propagating down into specific VOs/backends. That's what causes this
bug.

While render_frame is unlocked, other parts of the player can send
redraw requests which will cause in->request_redraw to become true. The
logic in the code always clears out this parameter after a successful
render, but this isn't correct. When in->request_become becomes true in
the middle of render_frame, there needs to be one more draw afterwards
to reflect whatever actually changed (usually the OSD). Instead, this
gets simply discarded. If you rapidly spam pause while rendering things
to the OSD at the same time, it's possible to for the last render to
behind a frame and appear as if your osd event was ignored.

Once you realize what is happening, the fix is quite simple. Just store
the initial value of in->request_redraw before the unlock step. After we
do the render step and unlock again, only set in->request_redraw to
false if there was an initial redraw request. We just finished doing a
redraw, so it is safe to clear. Otherwise, leave in->request_redraw
alone. If it is initially false, then it will still be false and nothing
changes. However if it updated to true in the middle of the rendering,
this value is now preserved so we can go and call do_redraw later and
show what that last frame was meant to be when you pause. One
unfortunate thing about this design is that it is technically possible
for other internal things in vo to update during that unlocked period.
Hopefully, that doesn't actually happen and only redraw requests work
like this.

Fixes mpv-player#8172 and mpv-player#8350.
Dudemanguy added a commit to Dudemanguy/mpv that referenced this issue Mar 2, 2023
There is a very subtle race in vo that can manifest itself on pause
events. In the renderloop, render_frame, unsurprisingly, does the heavy
lifting of actually queuing up and flipping the frames. This is called
during normal playback. Sometimes various parts of the player can make a
redraw request which will latter trigger another render of the frame
later down in the loop (do_redraw). Because these requests can happen at
essentially anytime, sometimes the redraw request will happen *before*
do_redraw and it'll be caught in render_frame. When this happens,
the existing render_frame run works perfectly fine as a redraw so it
clears out the request which is sensible. Normally this is all locked of
course, but there's one catch. render_frame has to unlock itself when
propagating down into specific VOs/backends. That's what causes this
bug.

While render_frame is unlocked, other parts of the player can send
redraw requests which will cause in->request_redraw to become true. The
logic in the code always clears out this parameter after a successful
render, but this isn't correct. When in->request_become becomes true in
the middle of render_frame, there needs to be one more draw afterwards
to reflect whatever actually changed (usually the OSD). Instead, this
gets simply discarded. If you rapidly spam pause while rendering things
to the OSD at the same time, it's possible to for the last render to
be behind a frame and appear as if your osd event was ignored.

Once you realize what is happening, the fix is quite simple. Just store
the initial value of in->request_redraw before the unlock step. After we
do the render step and unlock again, only set in->request_redraw to
false if there was an initial redraw request. We just finished doing a
redraw, so it is safe to clear. Otherwise, leave in->request_redraw
alone. If it is initially false, then it will still be false and nothing
changes. However if it updated to true in the middle of the rendering,
this value is now preserved so we can go and call do_redraw later and
show what that last frame was meant to be when you pause. One
unfortunate thing about this design is that it is technically possible
for other internal things in vo to update during that unlocked period.
Hopefully, that doesn't actually happen and only redraw requests work
like this.

Fixes mpv-player#8172 and mpv-player#8350.
Dudemanguy added a commit that referenced this issue Mar 2, 2023
There is a very subtle race in vo that can manifest itself on pause
events. In the renderloop, render_frame, unsurprisingly, does the heavy
lifting of actually queuing up and flipping the frames. This is called
during normal playback. Sometimes various parts of the player can make a
redraw request which will latter trigger another render of the frame
later down in the loop (do_redraw). Because these requests can happen at
essentially anytime, sometimes the redraw request will happen *before*
do_redraw and it'll be caught in render_frame. When this happens,
the existing render_frame run works perfectly fine as a redraw so it
clears out the request which is sensible. Normally this is all locked of
course, but there's one catch. render_frame has to unlock itself when
propagating down into specific VOs/backends. That's what causes this
bug.

While render_frame is unlocked, other parts of the player can send
redraw requests which will cause in->request_redraw to become true. The
logic in the code always clears out this parameter after a successful
render, but this isn't correct. When in->request_become becomes true in
the middle of render_frame, there needs to be one more draw afterwards
to reflect whatever actually changed (usually the OSD). Instead, this
gets simply discarded. If you rapidly spam pause while rendering things
to the OSD at the same time, it's possible to for the last render to
be behind a frame and appear as if your osd event was ignored.

Once you realize what is happening, the fix is quite simple. Just store
the initial value of in->request_redraw before the unlock step. After we
do the render step and unlock again, only set in->request_redraw to
false if there was an initial redraw request. We just finished doing a
redraw, so it is safe to clear. Otherwise, leave in->request_redraw
alone. If it is initially false, then it will still be false and nothing
changes. However if it updated to true in the middle of the rendering,
this value is now preserved so we can go and call do_redraw later and
show what that last frame was meant to be when you pause. One
unfortunate thing about this design is that it is technically possible
for other internal things in vo to update during that unlocked period.
Hopefully, that doesn't actually happen and only redraw requests work
like this.

Fixes #8172 and #8350.
@Dudemanguy
Copy link
Member

Fixed by #11395.

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

No branches or pull requests

6 participants