lv_drivers icon indicating copy to clipboard operation
lv_drivers copied to clipboard

Running Wayland driver based on frame timer?

Open tokyovigilante opened this issue 2 years ago • 7 comments

Hi,

I'm working on a Linux app which I'm targeting at an embedded device which will probably render to the framebuffer, however am using the Wayland driver to develop the app on Desktop (and may eventually use Wayland on device).

I'm using Swift which has a good libdispatch-based API to monitor for Wayland input events, and have set up a frame timer callback to monitor for output frame events. However I can't quite seem to get this working well without an additional while loop to call some combination of lv_wayland_timer_handler(), _lv_disp_refr_timer() or lv_tick_inc().

Ideally input and output would be decoupled so that LVGL events and input would trigger a re-render of the UI, which is then scheduled for a frame callback. However this is somewhat different to the way LVGL and the Wayland driver seems to want to work, which is to just run a timer with a 5ms (or so) interrupt and process both input and output in one function (lv_wayland_timer_handler). Ideally input events would be processed as they come, and if there is a need to re-render the screen, this is done no more frequently than in a frame callback to reduce power consumption.

Is there appetite to modify the Wayland driver in this way?

tokyovigilante avatar Apr 20 '22 13:04 tokyovigilante

@simplejack-src has just implemented something similar here. Does it work for your use case? You still run a while loop but the process will suspend completely if there is no event.

embeddedt avatar Apr 20 '22 13:04 embeddedt

Just to add on, there a quick example in the PR (#204) doing exactly that. poll() is used to wait on the Wayland file-descriptor until a Wayland event arrives. This works best with a high LV_INDEV_DEF_READ_PERIOD value (I use 60000) so the input timers don't request a very short sleep interval (which to be fair is a bit of a hack until LVGL is able to implement async/event driven input).

The 5ms timer you referenced is implemented as the original "classic" driver method (wherein everything runs at a constant tick interval). However, if your using lv_wayland_timer_handler() (which replaces lv_timer_handler(), as it calls this internally) this timer is disabled (as you are expected to call lv_wayland_timer_handler() when there is work to be done), i.e. poll().

Using this I'm able to run a pretty silent application (i.e. sleeps unless there's something worth doing).

ghost avatar Apr 20 '22 14:04 ghost

Thanks, I was working from your example which is very helpful. However with Swift I need to integrate with a libdispatch-based event loop, the application calls dispatchMain() after initialisation which processes events and dispatch queues. I have a function to fire a callback when there is a Wayland event to process, replacing the poll, but this does not seem to trigger further events, nor does calling lv_wayland_timer_handler() in the frame callback. I assume this is because lv_wayland_timer_handler() needs to be called after the timeout returned by the previous call, but I can't quite figure out how to integrate this cleanly.

let fd = lv_wayland_get_fd()
_loopDispatchSource = DispatchSource.makeReadSource(fileDescriptor: fd, queue: DispatchQueue.main)
_loopDispatchSource.setEventHandler() {
    let time = lv_wayland_timer_handler()
}
_loopDispatchSource.activate()

tokyovigilante avatar Apr 20 '22 19:04 tokyovigilante

I don't have any Swift experience (or GCD/libdispatch), but off-hand I'd say the issue (given your snippet above) might be that you need at least a single run through of lv_wayland_timer_handler() before waiting for an event on the Wayland file-descriptor?

ghost avatar Apr 20 '22 20:04 ghost

Thanks, I think the issue was that basically if there are no Wayland events, there is nothing triggering a redraw, so the frame timer wasn't coming back to rerun lv_wayland_timer_handler().

I've settled on scheduling another block to run at the intervals I'm getting back from lv_wayland_timer_handler() whether it is from an event or otherwise as below, although I did note a lot of 0ms intervals (mouse movement etc are all Wayland events so probably not representative of a touch-based interface) so set a 10ms minimum interval in my code. Doesn't make much sense to refresh faster than the display refresh interval in any event.

class WaylandLVGLDriver {

    private var _display: UnsafeMutablePointer<lv_disp_t>
    private var _tickTimer: DispatchSourceTimer
    private var _loopDispatchSource: DispatchSourceRead
    private var _lvMinimumTickTime: UInt32 = 10

    init () throws {

        lv_init()
        lv_wayland_init()

        guard let display = lv_wayland_create_window(800, 480, "SwiftLVGL", nil) else {
            throw Error(message: "Wayland LVGL display creation failed")
        }
        _display = display

        let fd = lv_wayland_get_fd()
        _loopDispatchSource = DispatchSource.makeReadSource(fileDescriptor: fd, queue: DispatchQueue.main)
        _tickTimer = DispatchSource.makeTimerSource(flags: .strict, queue: DispatchQueue.main)

        _loopDispatchSource.setEventHandler() { [self] in
            let time = max(lv_wayland_timer_handler(), _lvMinimumTickTime)
            _tickTimer.suspend()
            _tickTimer.schedule(deadline: .now() + DispatchTimeInterval.milliseconds(Int(time)), leeway: .microseconds(100))
            _tickTimer.resume()
        }
        _loopDispatchSource.resume()

        _tickTimer.setEventHandler { [self ] in
            let time = max(lv_wayland_timer_handler(), _lvMinimumTickTime)
            _tickTimer.schedule(deadline: .now() + DispatchTimeInterval.milliseconds(Int(time)), leeway: .microseconds(100))
        }
        let time = Int(lv_wayland_timer_handler())
        _tickTimer.schedule(deadline: .now() + DispatchTimeInterval.milliseconds(Int(time)), leeway: .microseconds(100))
        _tickTimer.resume()
    }

    deinit {
        _tickTimer.cancel()
        _loopDispatchSource.cancel()
        lv_wayland_deinit()
    }

}

tokyovigilante avatar Apr 22 '22 20:04 tokyovigilante

@simplejack-src has just implemented something similar here. Does it work for your use case? You still run a while loop but the process will suspend completely if there is no event.

I used @simplejack-src 's implementation. It seems flood events block UI rendering. https://github.com/lvgl/lv_drivers/issues/225

symfund avatar Jun 29 '22 02:06 symfund

This issue or pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Apr 20 '23 03:04 stale[bot]