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

Running Wayland driver based on frame timer? #207

Open
tokyovigilante opened this issue Apr 20, 2022 · 7 comments
Open

Running Wayland driver based on frame timer? #207

tokyovigilante opened this issue Apr 20, 2022 · 7 comments
Labels

Comments

@tokyovigilante
Copy link

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?

@embeddedt
Copy link
Member

@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.

@ghost
Copy link

ghost commented Apr 20, 2022

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).

@tokyovigilante
Copy link
Author

tokyovigilante commented Apr 20, 2022

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()

@ghost
Copy link

ghost commented Apr 20, 2022

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?

@tokyovigilante
Copy link
Author

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()
    }

}

@symfund
Copy link

symfund commented Jun 29, 2022

@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.
#225

@stale
Copy link

stale bot commented Apr 20, 2023

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.

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