Skip to content

Fix display lock starvation

Romain Vimont requested to merge rom1v/vlc:display_lock_starvation into master

The display_lock is locked by the vout thread for long periods of time. In particular, it is held while waiting between the calls to the vout display module callbacks prepare() and display(), to avoid display state changes (like display dimensions).

As a consequence, other threads could not acquire the lock in a timely manner (including from the UI thread), causing important concrete issues, among which:

In summary, the vout thread loop consists in the following steps:

  • process control events
  • prepare the picture
  • wait until the target PTS
  • display the picture

The picture is prepared in advance because the preparation time may vary a lot, which could cause the picture to be displayed at an inaccurate time.

current

It is not possible to "just" release the lock between the vout display ops prepare() and display():

  • the display state must not change between these calls
  • the vout display API guarantees that display() (if not NULL) will always be called after prepare() (in other words, it is currently not possible to cancel/abandon a prepare() call without calling display())
  • it seems that some vout display (e.g. Android, decklink) might not easily "cancel" a prepare() call anyway (the frame may be "scheduled" to be displayed at some point in the future)

However, the whole preparation is composed of several steps:

  • interactive filters (core): They are not impacted by display state changes (and do not lock the display_lock).
  • SPU rendering (core): SPU rendering duration may vary a lot (depending on whether there are SPU or not). However, it could be done without display_lock.
  • vout display prepare() (module): In practice, the module prepare() duration does not vary significantly. Currently, for some vouts, it is responsible for texture upload (which can be time consuming), but we plan to move that part to a separate step (a converter).

For illustration purposes, here is a graph showing the time taken by SPU rendering and vout display prepare() with an OpenGL vout (already posted here). In this example, the prepare() duration is small and stable, and consists mainly in texture upload (which we plan to move to an earlier step):

spu_prepare_opengl

Thus, to prevent lock starvation, this patchset proposes the following changes:

  • render SPU without display_lock by using a copy of the display state (format and config)
  • estimate the vout display prepare() duration separately, to call it just before display()
  • wait until target PTS minus the estimated prepare() duration with display_lock unlocked
  • abandon the "pre-rendering" result and start again if the actual display state has changed once the lock is re-acquired
  • call prepare() on the vout display, wait a bit (if necessary) for the target PTS, then call display()

Before:

current

After:

core_prepare_nolock

There are now 2 separate waitings (the grey rectangles).

The first one is expected to be long to absorb the variability of SPU rendering duration. While waiting, the display_lock is unlocked, so it does not block other threads from acquiring it. If the display state changes while unlocked, then the current waiting is interrupted immediately, the pre-rendered picture is abandoned and pre-rendering is started again.

on_resize_1

The second one (between prepare() and display()) is expected to be short (few milliseconds at most). Its purpose is to attempt to call display() at the expected PTS, while absorbing the prepare() duration variability. If a caller (the UI thread) needs the display_lock (on resize for example), this waiting is interrupted and display() is called immediately to release the display_lock as soon as possible. EDIT: this behavior is abandoned for now, this second wait is not interrupted.

on_resize_2

Thank you very much for your time and ideas for all these display lock discussions 😉


Possible next steps to do:

  • upload GPU textures in a converter (so that it is not done in prepare())
  • on resize, re-render the current picture as soon as possible (currently, no rendering will be done before the PTS of the next picture or the VOUT_REDISPLAY_DELAY)
  • do not return from the resize callback before the rendering at the new size is complete (frame-perfect)
Edited by Romain Vimont

Merge request reports