embedded-graphics / embedded-graphics

A no_std graphics library for embedded applications
Apache License 2.0
957 stars 90 forks source link

[feature request] Support `async` version of `DrawTarget`. #622

Open richard-uk1 opened 3 years ago

richard-uk1 commented 3 years ago

Currently embedded-graphics only support interrupt-driven concurrency (I mean that if you want to do work while some DMA is writing out data to a screen, you have to use an interrupt). Using async it should be possible to write a version of DrawTarget that allows other work to be done while it is waiting for I/O.

I think this feature request is blocked on async traits, which in turn is blocked on GATs, so it is not (yet) possible to implement.

jacobrosenthal commented 3 years ago

For buffered displays, the flush fn isnt in trait at all right? Thats the bigger cost call I think, I was able to just asyncify it https://github.com/jamwaffles/ssd1331/pull/13

The bigger issue going to be to pick an async trait while the community explores. I used the embassy one there

jamwaffles commented 3 years ago

Experimenting in drivers is a good approach for sure, and I agree that the flush functionality for buffered drivers is going to be a large part of the total draw time.

The current e-g iterator API lends itself well to being interrupted, so I'm not sure I'd see a need to add async support to e-g itself, although the decision will be much easier to make when official language support for async traits, etc is added.

richard-uk1 commented 2 years ago

I think I'm mis-understanding a little bit here. I have a nrf52832 with a st7789 display. There isn't enough member on-chip to hold a framebuffer, so pixels have to be generated then sent directly to the display (over SPI). In the ideal case (not how it works for me yet) you have 2 buffers, and fill one with pixels while another is going out over SPI via DMA. Then once both jobs are done, you set the second buffer off over DMA and fill the first. Rinse and repeat until you've sent out data for the whole screen (or whatever subregion you're redrawing). Because filling the buffer is much faster than the DMA (slow SPI), you can then do something else while you're waiting for the DMA to finish. So in this scenario, is "flushing" setting off the DMA for one buffer or the other?

The current e-g iterator API lends itself well to being interrupted, so I'm not sure I'd see a need to add async support to e-g itself, although the decision will be much easier to make when official language support for async traits, etc is added.

Can you say more about what you mean by this?

jamwaffles commented 2 years ago

Thanks for the deeper explanation. I think I understand what you mean now - my comment above doesn't make much sense.

IIUC, you'd like something like this pseudo-Rust (iterators can't do chunks() or produce slices, pixel coords aren't checked for contiguousness, etc, etc):

impl DrawTarget for Disp {
    async fn draw_iter<I>(&mut self, pixels: impl Iterator<Item = Pixel>) {
        for chunk in pixels.into_iter().chunks(16) {
            self.fill_half_buf(&chunk);
            // Executor can go do other stuff while this is waiting
            self.swap_and_send_half_buf().await;
        }
    }
}

Which is currently impossible with the non-async DrawTarget. My original comment was suggesting that you could do the async purely in the display driver and leave e-g alone but that won't work :facepalm:.

I'd be tempted to use async_trait with a new AsyncDrawTarget under a feature flag if we want to experiment with this on the e-g side. async_trait is luckily no_std, so might be ok but are there any other limitations you know of?

Can you say more about what you mean by this?

Please ignore me - I didn't think this through properly.

ValouBambou commented 8 months ago

What is the current state of this async implementation of the trait ? I would be really interested to have this for the same DMA purpose that was suggested initially. Async functions in trait are now stabilized (we some caveats) since rust 1.75 without the need of the extra dependency. So we should be able to add this to embedded-graphics. Is there any other difficult point that I missed ?

TG-Techie commented 7 months ago

Is this a feature e-g would be interested in having developed further?

As in a more formalized version using something like the maybe_async macro like @ValouBambou suggested in #747