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

UI Behavior tweaks #3983

Merged
merged 78 commits into from Jun 2, 2018

Conversation

Projects
None yet
6 participants
@NiLuJe
Member

NiLuJe commented May 26, 2018

Okay, so, here goes!

This works in tandem (and, in fact, requires) BASE#676.

There's a lot of tiny moving parts, touching a lot of stuff.

The basic idea is twofold:

  • Make the UI behave as closely as possible like the stock reader (be it the Kindle's framework or Nickel) in terms of waveform handling.
  • Make the UI as reactive as possible, by tightening scheduling latency.

You'll see a few more black flashes, in a few new places, as well as localized black flashes. The goal being to avoid ghosting when applicable (mainly TouchMenu & DictQuickLookup).

Tested on a H2O for the non-REAGL path, and on a PW2 for the REAGL path ;).

I'm probably forgetting stuff, so, test away!

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented May 26, 2018

Current CI breakage because it's missing the bits from the base PR ;).

@KenMaltby

This comment has been minimized.

KenMaltby commented May 26, 2018

If KOReader or any other 3rd party software were to function like Nickel, I wouldn't use it. The stock software coexists with KOReader, if someone wants to do things that way they can. KOReader's UI needs to be consistent within itself, not with the device stock UI. KOReader can be used on a number of devices, the user should find the UI within KOReader the same whatever device he is running it on, to the extent possible.

@poire-z

This comment has been minimized.

Contributor

poire-z commented May 26, 2018

Wow, that's a lot of work !

Regarding:

    -- NOTE: Kobo's fb is BGR, not RGB. Handle the conversion in MuPDF if needed.
    local bgr = false
    if Device:isKobo() then
        bgr = true
    end

I'm not convinced my GloHD is BGR :)
Coverbrowser > Refresh cached book information for a few books you can check the colored cover on online bookshops > Screenshots > look at the screenshot on your computer
For me, the colors were correct before I applied your patch, but no more after (full red books are now blue (although if I didn't Refresh cached book info, they were still ok, because made previously with MuPDF and blitbuffer saved in DB re-used I guess).
So, may be there's some differences between kobo devices, or kobo firmwares?
(I'd like someone else to confirm on a GloHD, I may be becoming color blind).

@AlanSP1

This comment has been minimized.

AlanSP1 commented May 26, 2018

I'm also with @KenMaltby

Of course, I'm not sure what exactly you mean with UI behaving like nickel, but I don't want koreader becoming nickel, otherwise I'd just use nickel and never look elsewhere.

Koreader is great because it is much superior to nickel in almost every aspect, so, why would it try to emulate something so vastly inferior?

Anyway, there might be something I don't understand what "Make the UI behave as closely as possible like the stock reader (be it the Kindle's framework or Nickel)" actually means, but from my past experience with koreader, its UI worked just fine. And nickel's UI wasn't that fine from my perspective.

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented May 26, 2018

@KenMaltby & @AlanSP1: Wow, hold your horses. That sentence was incomplete (I got distracted by a white box with a red 6 on it arriving in the mail ;p). I meant that purely in terms of waveform modes handling. And, in fact, that involved auditing every setDirty calls to make them consistent...

@poire-z : Oh, joy! I'm guessing it's only on 32bpp FWs then, since I seem to recall you running on 3.something. That should make it slightly less painful to deal with ;).

@AlanSP1

This comment has been minimized.

AlanSP1 commented May 26, 2018

OK, I included possibility that I didn't understood well what's going on with this PR. And, to admit, I still don't understood it. :)

Anyway, if you think it is important and useful, OK, but I just don't want koreader follows nickel, as nickel went in wrong direction, only enabling easy usage of Kobo store, which is understandable from their perspective, but is totally unimportant from mine (and probably few others out there).

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented May 26, 2018

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented May 26, 2018

@poire-z

This comment has been minimized.

Contributor

poire-z commented May 26, 2018

Confirming the UI hasn't changed :) It's just about what gets refreshed and how and when: quick refresh, full refresh with a black flash, and now: non-full-screen black flash, like when you browse a dict definition or a wiki page, you get that black flash every 6 pages too (you never got one before, you had to diagonal swipe to trigger one when you felt you needed one).

Some quick observations (which may apply only to my Kobo, I guess this stuff can have very model specific effects):

  • First, everything seems to work fine :)
  • I enabled Flash button, that I usually have disabled, and some of them, after becoming black and then white again, seem a little remanent grey (more than I remember when we couldn't disable them), like in the dict windows, when taping on Wikipedia.
  • I also felt that these button flash were a bit slow (slower than before, I dunno, that would have to be confirmed by somebody used to that, as I disabled them - may be just because they were already that slow :).
  • In the stream: hold on word > InfoMessage "Looking up word" > dict window appears > InfoMessage closed, it gets a bit (more than I felt previously) dirty when the InfoMessage is closed over the already-or-just-being displayed Dict window.
  • The top menu always appears with a black flash of its region (the bottom menu does that once every 6 times I open it - I have Eink full refresh to every 6 pages). This is the most suprising change, and I'm not sure I like it, or I just need to get used to it.
  • Closing the top menu makes a full (but quick) screen black flash. Closing the bottom menu does not do a full screen redraw, but just a black flash of its region on close once every 6 times I close it (I have Eink full refresh to every 6 pages).
  • Diagonal small swipe in a dict window makes a black flash redraw of its window only (but the footer with the clock is still updated). Which should be fine as I usually use it to remove ghosting from that window, but if there were ghosting outside that window remaining that I would just want to get rid of, this diagonal swipe can't help now).
  • Another thing that has not changed, but I mention just in case: when hold on a word to do dict lookup, and closing, the highlight is removed after 1 or 2 s. When that highlight is removed, the word looks thinner than the words around it (it was already like that before). I often follow up with a diagonal swipe for a full refresh when it's too thin and keep catching my eye. Dunno if other refresh mode would help.

So, overwall, more black flashes, which may be good against ghosting, but give the feeling the UI is less responsive (and it may be just a feeling because of the transient black - the flashing seems quite fast thus).

@poire-z: wait, is that when checking fbgrab screenshots, or ones made with KOReader?

I don't have fbgrab, so yes, it's the png done with Koreader screenshot (long diagonal swipe).

@poire-z

This comment has been minimized.

Contributor

poire-z commented May 26, 2018

Regarding c7f7d38, may be we could add a method to UIManager to flush the dirty stack (when we know it's the right thing to do, eg when coverbrowser highjack a previous widget)?

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented May 26, 2018

@poire-z: Okay, in order:

  • Yay \o/.
  • Yeah, that's to be expected, to a certain extent, depending on device/weather. On the other hand, if the text looks garbled on the edges once the highlight is gone, then there definitely is an issue (usually, a second fast update instead of ui).
  • I'd hope not, but the intent was at least in terms of everything else to make the UI as reactive with it activated as without (hence the tighter timings in the scheduling). But that's still one to two more refreshes, so I'd still expect no flashing to 'feel' faster, and probably actually actually be slightly faster.
    If you were already using the highlights before and are used to them, in most cases, it definitely should be faster, though ;).
  • That's... interesting. I don't think I've ever had the info message disappear on me after the dict popup. It's always been gone before, and as such, the flashing on the dict popup opening clears everything up. This may depend on the device, because for instance, on my PW2, which is still miles faster than anything Kobo ever put out, it disappears so soon that I get a hint of the content of the dict popup before it's fully refreshed (i.e., it's already painted, but its proper regional refresh still hasn't been done, so I just get the region of the info message).
    I only have a single dictionary installed (or, actually, none on the PW2), so this may tweak timings in my favor ;).
  • Yep, that's by design. Granted, in terms of ghosting, the flashing on close is probably more useful than on show. FWIW, that's an idea stolen from the Kindle, where I kind of liked it (but where the menu takes way less screen real-estate than ours) ;).
  • Yeah, that's also by design. I'd like to flash on close for the bottom configdialog, too, but that led to timing issues when both are up, and you launch something from the top menu that takes a while to draw: that means that usually, the bottom configdialog closes first, and is not merged with the topmenu closing later -> blam, double flash, which feels terrible, and is actually really slow.
    I kept it partial instead of ui as a half-hearted attempt at having my cake and eating it too, which explains why it obeys your settings ;). Also, that behaves better on REAGL devices than ui would, period ;).
  • Yeah, by design too, I was kind of happy with that one, since usually the popup takes so much screen real-estate that the rest of the screen is barely usable. That, and there shouldn't really be too much ghosting behind that, unless you have custom eink full refresh settings ;). IIRC, I made another text-centric popup widget behave like that.
  • Yeah, that one bugs me, too. It bugged me 4 years ago when I initially did a similar pass, because I found comments about my experiments at the time :D.
    I haven't actually tried anything with it, but according to said comments, getting the actual region of the highlighted text was complex to impossible, so we have to do a full-screen refresh. Which means it has to be delayed a bit, is a bit slower than it should because it's full-screen, and we can't make it flash, because in most cases that'd create a double-flash. And since it's a highlight, that of course leaves a bit of reverse ghosting.
    FWIW, I'm not happy either with how both Nickel & Kindle handle that, IIRC.

Of particular note regarding the 'length' of the black flashes: in the topmenu, it's never cleared as a bit of manual optimization, so it'll stay on until whatever you clicked on actually does its thing ;).
In every other cases, it should mostly come down to your screen for how fast it actually is ;).

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented May 26, 2018

@poire-z : Re c7f7d38

Hmm, that's not stupid... I'm not quite sure how feasible that'd be, timing-wise, though?

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented May 26, 2018

Another special case: the virtual keyboard. It's been using "fast" for quite a while, and if you're tapping moderately fast, I can somewhat reliably make it 'blank' individual keys, or even create a jagged diagonal blank in a corner (usually by holding the delete button).

I made the layout switches flashing to have an easy way to 'clear' things up, but I'm open to suggestions.

AFAIR, that's not new and/or caused by this PR, but I'd still like to hear people's experience with it.

Unfortunately, if it bothers people too much, I'm afraid the only viable solution is to forgo "fast" for the virtual keyboard, and go with "ui", which will definitely feel slightly sluggish (I tried).

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented May 26, 2018

@poire-z : Yeah, confirmed the screenshot thing. It's also assuming everything's peachy in RGB land ^^.

The good news is that's not device/FW specific, the bad news is that one might be annoying to handle if I have to do a conversion by myself ^^

EDIT: Yay, nope, trivial-ish to fix :).

EDIT²: Fixed in koreader/koreader-base@aae67b8

@poire-z

This comment has been minimized.

Contributor

poire-z commented May 27, 2018

Regarding c7f7d38, may be we could add a method to UIManager to flush the dirty stack

I'm not quite sure how feasible that'd be, timing-wise, though?

Refreshes-timing wise or developer-time wise ? :) I meant flush in the sense of delete, erase, clean, clear, empty, so I guess UIManager:clearDirtyStack() self._dirty={} end, or less radical as CoverBrowser knows the widget it is hijacking: UIManager:clearWidgetFromDirtyStack(widget) self._dirty[widget] = nil end would do

RBG->BGR conversion on Kobo

OK :)
No way to have the pixmap bgr directly the first time it is made in mupdf.renderImage():
local pixmap = W.mupdf_get_pixmap_from_image(context(), image, nil, nil, nil, nil)
that would avoid the need (time, RAM usage) to make a 2nd pixmap bgr_pixmap later as you did (feels right but strange to do more work to have a pixmap with the right colors when we are only on a grey device and nobody ever noticed they were wrong before you did :)

like in the dict windows, when taping on Wikipedia

Yeah, that's to be expected, to a certain extent, depending on device/weather

I woke up in the middle of the night with lower temperatures just to check :) still greyish. Strange it happens on the dict window, but not when selecting multiple words and you get that buttontable: the Wikipedia button flash but get real white there.

top and bottom menus black flash

Yeah, that's also by design

I undersood that after I read your adventure via the commit message.
If I can't get used to it, may be we could add alongside the Flash button and menu items and Flash keyboard a new Flash widgets and menu, so one can choose between Quality screen with no ghosting and Feeling of quickness but possibly dirty.

Diagonal small swipe in a dict window makes a black flash redraw of its window only

Yeah, by design too, I was kind of happy with that one, since usually the popup takes so much screen real-estate that the rest of the screen is barely usable. That, and there shouldn't really be too much ghosting behind that

On this one, it feels like bad design :) It's the user explicitely requesting a refresh, so assuming he only wants the inner window refreshed may be wrong. Better to make a full screen redraw: he asks for it, he can wait the extra time for full screen (a dict window may take only half the screen if you didn't set it to be full screen).

Of particular note regarding the 'length' of the black flashes: in the topmenu, it's never cleared as a bit of manual optimization, so it'll stay on until whatever you clicked on actually does its thing ;).

Really clever :)

@poire-z

This comment has been minimized.

Contributor

poire-z commented May 27, 2018

Another small thing: while in coverbrowser, after having deleted cache, when browsing directories and you have background indexing of the 9-10 books currentl displayed, 1 out of 6 gets its region black-flash refresh, while 5 out of 6 get the classic refresh without black flash. I think in that case, all of them should have the classic no-black-flash refresh.

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented May 27, 2018

@poire-z

This comment has been minimized.

Contributor

poire-z commented May 27, 2018

A few more observations:

  • 1 out of 6 Notifications is regional-flashed-black (I think it doesn't need any, it just catches the eye when it's too late to read it :)
  • unlike what I seem to remember reading in one of your commit message, changing dictionary in a dict window (swipe left/right) does not trigger any 1-out-of-6 black-flash-refresh - only scrolling (swipe up/down) in one definition does
  • about the initial black flash when opening the menu: if the menu you happen to be on when opening is short (ie: 4 items), it's all clean - then, when you switch to a longer menu (8 items), you get the top half all clean, and the lower half with ghosting from the text below that wasn't black-flashed - the separation is noticable (and it feels less clean than a homogenous ghosting like we may have had previously).
@NiLuJe

This comment has been minimized.

Member

NiLuJe commented May 27, 2018

@poire-z:

Refreshes-timing wise or developer-time wise ? :) I meant flush in the sense of delete, erase, clean, clear, empty, so I guess UIManager:clearDirtyStack() self._dirty={} end, or less radical as CoverBrowser knows the widget it is hijacking: UIManager:clearWidgetFromDirtyStack(widget) self._dirty[widget] = nil end would do

I meant in terms of not being sure we'll get to clear the stack before it gets processed, but I like the idea, so I guess I'll just try and see what happens ;).

OK :)
No way to have the pixmap bgr directly the first time it is made in mupdf.renderImage():
local pixmap = W.mupdf_get_pixmap_from_image(context(), image, nil, nil, nil, nil)
that would avoid the need (time, RAM usage) to make a 2nd pixmap bgr_pixmap later as you did (feels right but strange to do more work to have a pixmap with the right colors when we are only on a grey device and nobody ever noticed they were wrong before you did :)

Yeah, I checked after the fact, no way to do that through that API, unfortunately (you can when rendering a pixmap from a pdf page, if I understood the API correctly, though).

I woke up in the middle of the night with lower temperatures just to check :) still greyish. Strange it happens on the dict window, but not when selecting multiple words and you get that buttontable: the Wikipedia button flash but get real white there.

Lower temps should net you slower refreshes... as will higher temps :D. There's usually a sweetspot around the usual room-temperature (I'd say ~20-25°C). Things are less drastic that they used to be since Carta screens, though ;). Unless you attempt stupid experiments like putting the device in the fridge ;D.

I undersood that after I read your adventure via the commit message.
If I can't get used to it, may be we could add alongside the Flash button and menu items and Flash keyboard a new Flash widgets and menu, so one can choose between Quality screen with no ghosting and Feeling of quickness but possibly dirty.

I'm not overly fond of extra settings just for one thing, but I get the idea nonetheless ;). FWIW, there's also a small 'behind-the-scene' plus to making it flash: that'll ensure waiting for completion of the previous update (i.e., block if need be before showing the menu). Since we mostly not asynchronous, this should only have very, very little impact on anything, but, still ;p.
I'm not against putting all of that behind the "flash_ui" thingy, though ;).

On this one, it feels like bad design :) It's the user explicitely requesting a refresh, so assuming he only wants the inner window refreshed may be wrong. Better to make a full screen redraw: he asks for it, he can wait the extra time for full screen (a dict window may take only half the screen if you didn't set it to be full screen).

Ooh, I didn't know you could tweak the dict popup size, so, yeah, I see your point ;)

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented May 27, 2018

1 out of 6 Notifications is regional-flashed-black (I think it doesn't need any, it just catches the eye when it's too late to read it :)

Yeah, possibly only on close or something, right? But yeah, that's probably not a great idea.

unlike what I seem to remember reading in one of your commit message, changing dictionary in a dict window (swipe left/right) does not trigger any 1-out-of-6 black-flash-refresh - only scrolling (swipe up/down) in one definition does

That's quite possible, I don't think the codepath is exactly the same. I'll check.

about the initial black flash when opening the menu: if the menu you happen to be on when opening is short (ie: 4 items), it's all clean - then, when you switch to a longer menu (8 items), you get the top half all clean, and the lower half with ghosting from the text below that wasn't black-flashed - the separation is noticable (and it feels less clean than a homogenous ghosting like we may have had previously).

That's indeed a risk. I didn't notice it much on either devices, but it makes sense. I have two solutions:

  • Make that initial flash full-screen
  • Or drop it

So, basically a choice between ghosting or not ;p. Given the size of our menus, the latency difference between a partial and full flash should be minimal (heck, it might even be faster on some devices, because of... reasons? :D).

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented May 27, 2018

unlike what I seem to remember reading in one of your commit message, changing dictionary in a dict window (swipe left/right) does not trigger any 1-out-of-6 black-flash-refresh - only scrolling (swipe up/down) in one definition does

That's quite possible, I don't think the codepath is exactly the same. I'll check.

Yep, update() is doing an "ui", and that's what happens on dict change, AFAICT.
I have no strong opinion on that one, so if you want to try w/ "partial", just say the word ;).

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented May 27, 2018

1 out of 6 Notifications is regional-flashed-black (I think it doesn't need any, it just catches the eye when it's too late to read it :)

Yeah, possibly only on close or something, right? But yeah, that's probably not a great idea.

Hmm, do you happen to remember which notifications? InfoMessage is using "ui", so, that's not it.

@poire-z

This comment has been minimized.

Contributor

poire-z commented May 27, 2018

(about Notification) Yeah, possibly only on close or something, right?
Hmm, do you happen to remember which notifications? InfoMessage is using "ui", so, that's not it.

Yes, on close.
No prob with InfoMessage indeed. For easy multiple Notifications without any other thing, I used the kobolight plugin (swipe up/down on the left side).

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented May 27, 2018

To expand a bit on the BGR thing: we could also do it "right" and create a couple new BGRA BB types and use that on Kobos... But that's a whole lot of code for basically "fixing" an "invisible" issue ;p.

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented May 27, 2018

Yes, on close.
No prob with InfoMessage indeed. For easy multiple Notifications without any other thing, I used the kobolight plugin (swipe up/down on the left side).

Ah, right, managed to reproduce it, thanks. (Took a few tries: the flash was being optimized away by the driver because it's a small region, which happens, sometimes, for... reasons? :D).

EDIT: ... and the widget is indeed named "Notification", I'm stupid :D.

@poire-z

This comment has been minimized.

Contributor

poire-z commented May 27, 2018

(about top menu opening) - Make that initial flash full-screen - Or drop it
So, basically a choice between ghosting or not ;p. Given the size of our menus, the latency difference between a partial and full flash should be minimal (heck, it might even be faster on some devices, because of... reasons? :D).

Try a full-flash on TouchMenu open, see how that feels.

Dunno if a full flash will change much: the 4items menu will be clean, the book text under too, but when you switch to the 8 items menu, the top 4 items will move from mostly white to mostly white, so clean - the bottom 4 items will move from previously book test to mostly white with possible ghosting from the book text.

I'm personally not fond of the flash on menu open, and I was ok with the occasional ghosting (so I would chose drop it :) - but I can revert some of these black flashes in my own post install patch). I'm quite allergic to change :) so we really need feedback from other users, if they feel it brings improvement.
I really wonder if the feeling may depend on the device (may be the black flashes are smoother/faster on kindle, or is it no black flash thanks to REAGL?)

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented May 27, 2018

@poire-z : I purposefully made it a black flash even on REAGL devices, but if we switch to flashpartial, it'll flash on other devices, but not on REAGL (flashpartial is basically partial on REAGL, since REAGL is always FULL) ;p.

Ironically, I think I tried that or another menu as REAGL, and it's a bit jarring for some strange reason... It doesn't NOT work, but I'm just not a fan for some reason I can't quite put my finger on... ^^.

FWIW, I get roughly the same experience as you on my H2O (even with the full-flash), so, let's see what people say, but I'm not against keeping that one "ui" onShow (the extra wait-for-complete we gain behind the scenes with flash* should not be critical in our case).

And making every menu navigation flash feels terrible, so that's not an option if one wanted to go the "KILL THE GHOSTING§!§§!" way ;).

@poire-z

This comment has been minimized.

Contributor

poire-z commented May 27, 2018

I'm not against putting all of that behind the "flash_ui" thingy, though ;).

Why not (woud avoid me patching :).
But logically, flash_ui is for getting animations. That other setting would be more about quality antighosting with flash vs no black-flash but possible ghosting. I can imagine people who'd like one and not the other.

(about dict window) Yep, update() is doing an "ui", and that's what happens on dict change, AFAICT.
I have no strong opinion on that one, so if you want to try w/ "partial", just say the word ;).

I would may be disable flashing for all - but logically, if you flash when scrolling 6 pages of a single definition, you should flash when switching 6 definitions, so yes, "word" !
(I'm still not up to date on the refresh namings :)

To expand a bit on the BGR thing: we could also do it "right" and create a couple new BGRA BB types and use that on Kobos... But that's a whole lot of code for basically "fixing" an "invisible" issue ;p.

Indeed, that would be a bit overkill : better to the conversion it a I/O boundaries.
(I guess it's fine with RGB16 conversions, as it's the green in the middle that takes 6 bits, the red and blue on the sides take both 5 bits, so the RGB32 (even if BGR32) <=> RGB16 should work in both cases)

@Frenzie

This comment has been minimized.

Member

Frenzie commented May 27, 2018

I haven't really been able to keep up with the discussion but when you say invisible do you mean truly invisible or mostly invisible? (I mean, not that I've noticed anything wrong in grayscale…)

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented May 27, 2018

@poire-z: True, I'm not disputing the logic behind it (because I agree with it), what's bothering me is more the fact that that'd be a setting for one thing in one place ;).

@Frenzie: According to the maths, and a quick test re-Grayscaling both versions of a snaphhot, "mostly". And a mostly of the "now that I've seen it, I can't unsee it" kind :D.

EDIT: For science: inside CRe, everything's peachy (and already grayscale).

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented Jul 7, 2018

Sidebar: since it's a simple black rectangle, it's a pretty good candidate for switching to refreshFast instead of refreshUI ;).

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented Jul 7, 2018

[pid 14266] 18:33:44 [4028648c] ioctl(3, MXCFB_SEND_UPDATE, {update_region={top=0, left=0, width=758, height=1024}, waveform_mode=WAVEFORM_MODE_REAGL, update_mode=UPDATE_MODE_FULL, update_marker=321, hist_bw_waveform_mode=WAVEFORM_MODE_REAGL, hist_gray_waveform_mode=WAVEFORM_MODE_REAGL, temp=TEMP_USE_AUTO, flags=0, alt_buffer_data={phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x41a84428) = 0
[pid 14266] 18:33:44 [4028648c] ioctl(3, MXCFB_WAIT_FOR_UPDATE_COMPLETE, {update_marker=321, collision_test=0}, 0x41a844d8) = 0x1c2
[pid 14266] 18:33:45 [4028648c] ioctl(3, MXCFB_WAIT_FOR_UPDATE_SUBMISSION, {321}, 0x41a846a8) = 0
[pid 14266] 18:33:45 [4028648c] ioctl(3, MXCFB_SEND_UPDATE, {update_region={top=59, left=0, width=5, height=31}, waveform_mode=WAVEFORM_MODE_GC16_FAST, update_mode=UPDATE_MODE_PARTIAL, update_marker=322, hist_bw_waveform_mode=WAVEFORM_MODE_DU, hist_gray_waveform_mode=WAVEFORM_MODE_GC16_FAST, temp=TEMP_USE_AUTO, flags=0, alt_buffer_data={phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x41a84610) = 0
[pid 14266] 18:33:46 [4028648c] ioctl(3, MXCFB_WAIT_FOR_UPDATE_SUBMISSION, {322}, 0x41a84940) = 0
[pid 14266] 18:33:46 [4028648c] ioctl(3, MXCFB_SEND_UPDATE, {update_region={top=59, left=0, width=5, height=31}, waveform_mode=WAVEFORM_MODE_GC16_FAST, update_mode=UPDATE_MODE_PARTIAL, update_marker=323, hist_bw_waveform_mode=WAVEFORM_MODE_DU, hist_gray_waveform_mode=WAVEFORM_MODE_GC16_FAST, temp=TEMP_USE_AUTO, flags=0, alt_buffer_data={phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x41a848a8) = 0
[pid 14266] 18:33:47 [4028648c] ioctl(3, MXCFB_WAIT_FOR_UPDATE_COMPLETE, {update_marker=323, collision_test=0}, 0x41c7cfe8) = 0
[pid 14266] 18:33:47 [4028648c] ioctl(3, MXCFB_SEND_UPDATE, {update_region={top=0, left=0, width=758, height=1024}, waveform_mode=WAVEFORM_MODE_REAGL, update_mode=UPDATE_MODE_FULL, update_marker=324, hist_bw_waveform_mode=WAVEFORM_MODE_REAGL, hist_gray_waveform_mode=WAVEFORM_MODE_REAGL, temp=TEMP_USE_AUTO, flags=0, alt_buffer_data={phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x41c7cf50) = 0
[pid 14266] 18:33:47 [4028648c] ioctl(3, MXCFB_WAIT_FOR_UPDATE_COMPLETE, {update_marker=324, collision_test=0}, 0x41c7d000) = 0x1c2
[pid 14266] 18:33:47 [4028648c] ioctl(3, MXCFB_WAIT_FOR_UPDATE_SUBMISSION, {324}, 0x41c7d2c8) = 0
[pid 14266] 18:33:47 [4028648c] ioctl(3, MXCFB_SEND_UPDATE, {update_region={top=400, left=0, width=5, height=31}, waveform_mode=WAVEFORM_MODE_GC16_FAST, update_mode=UPDATE_MODE_PARTIAL, update_marker=325, hist_bw_waveform_mode=WAVEFORM_MODE_DU, hist_gray_waveform_mode=WAVEFORM_MODE_GC16_FAST, temp=TEMP_USE_AUTO, flags=0, alt_buffer_data={phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x41c7d230) = 0
[pid 14266] 18:33:49 [4028648c] ioctl(3, MXCFB_WAIT_FOR_UPDATE_SUBMISSION, {325}, 0x481e8818) = 0
[pid 14266] 18:33:49 [4028648c] ioctl(3, MXCFB_SEND_UPDATE, {update_region={top=400, left=0, width=5, height=31}, waveform_mode=WAVEFORM_MODE_GC16_FAST, update_mode=UPDATE_MODE_PARTIAL, update_marker=326, hist_bw_waveform_mode=WAVEFORM_MODE_DU, hist_gray_waveform_mode=WAVEFORM_MODE_GC16_FAST, temp=TEMP_USE_AUTO, flags=0, alt_buffer_data={phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x481e8780) = 0
[pid 14266] 18:33:50 [4028648c] ioctl(3, MXCFB_WAIT_FOR_UPDATE_SUBMISSION, {326}, 0x4cb8d7a0) = 0
[pid 14266] 18:33:50 [4028648c] ioctl(3, MXCFB_WAIT_FOR_UPDATE_COMPLETE, {update_marker=326, collision_test=0}, 0x4cb8d7b0) = 0
[pid 14266] 18:33:50 [4028648c] ioctl(3, MXCFB_SEND_UPDATE, {update_region={top=0, left=0, width=758, height=1024}, waveform_mode=WAVEFORM_MODE_GC16, update_mode=UPDATE_MODE_FULL, update_marker=327, hist_bw_waveform_mode=WAVEFORM_MODE_DU, hist_gray_waveform_mode=WAVEFORM_MODE_GC16, temp=TEMP_USE_AUTO, flags=0, alt_buffer_data={phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x4cb8d6f8) = 0
[pid 14266] 18:33:50 [4028648c] ioctl(3, MXCFB_WAIT_FOR_UPDATE_COMPLETE, {update_marker=327, collision_test=0}, 0x4cb8d9c8) = 0x1c3
[pid 14266] 18:33:51 [4028648c] ioctl(3, MXCFB_WAIT_FOR_UPDATE_SUBMISSION, {327}, 0x4cb8db00) = 0
[pid 14266] 18:33:51 [4028648c] ioctl(3, MXCFB_SEND_UPDATE, {update_region={top=59, left=0, width=5, height=31}, waveform_mode=WAVEFORM_MODE_GC16_FAST, update_mode=UPDATE_MODE_PARTIAL, update_marker=328, hist_bw_waveform_mode=WAVEFORM_MODE_DU, hist_gray_waveform_mode=WAVEFORM_MODE_GC16_FAST, temp=TEMP_USE_AUTO, flags=0, alt_buffer_data={phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x4cb8da68) = 0
[pid 14266] 18:33:52 [4028648c] ioctl(3, MXCFB_WAIT_FOR_UPDATE_SUBMISSION, {328}, 0x4cb8df98) = 0
[pid 14266] 18:33:52 [4028648c] ioctl(3, MXCFB_SEND_UPDATE, {update_region={top=59, left=0, width=5, height=31}, waveform_mode=WAVEFORM_MODE_GC16_FAST, update_mode=UPDATE_MODE_PARTIAL, update_marker=329, hist_bw_waveform_mode=WAVEFORM_MODE_DU, hist_gray_waveform_mode=WAVEFORM_MODE_GC16_FAST, temp=TEMP_USE_AUTO, flags=0, alt_buffer_data={phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x4cb8df00) = 0

You can clearly see the flow: REAGL (page) -> GC16_FAST (mark) -> GC16_FAST (unmark)
Matching exactly with the flow when flashing: GC16 (flashing page) -> GC16_FAST (mark) -> GC16_FAST (unmark).

REAGL + the Kindle-specific wait-for-submission ioctl pretty much ensure a deterministic order ;).

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented Jul 7, 2018

Can't reproduce on my H2O, everything's in the right order, too.

[pid  1662] 18:47:36 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=10, left=0, width=1080, height=1430}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=182, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2c414d90) = 0
[pid  1662] 18:47:36 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=91, left=0, width=14, height=40}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=183, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2c414f18) = 0
[pid  1662] 18:47:37 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=91, left=0, width=14, height=40}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=184, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2c415198) = 0
[pid  1662] 18:47:39 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=10, left=0, width=1080, height=1430}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=185, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2c408fc0) = 0
[pid  1662] 18:47:40 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=613, left=0, width=14, height=40}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=186, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2fa9df10) = 0
[pid  1662] 18:47:41 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=613, left=0, width=14, height=40}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=187, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2fa9e370) = 0
[pid  1662] 18:47:42 [2ac131f6] ioctl(3, MXCFB_WAIT_FOR_UPDATE_COMPLETE_V1, {187}, 0x3004aaa8) = 0
[pid  1662] 18:47:42 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=10, left=0, width=1080, height=1430}, waveform_mode=NTX_WFM_MODE_GC16, update_mode=UPDATE_MODE_FULL, update_marker=188, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x3004aa18) = 0
[pid  1662] 18:47:42 [2ac131f6] ioctl(3, MXCFB_WAIT_FOR_UPDATE_COMPLETE_V1, {188}, 0x3004ac98) = 0x3b1
[pid  1662] 18:47:43 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=91, left=0, width=14, height=40}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=189, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x3004adf0) = 0
[pid  1662] 18:47:44 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=91, left=0, width=14, height=40}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=190, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2c3a6568) = 0
[pid  1662] 18:47:45 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=10, left=0, width=1080, height=1430}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=191, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2c380958) = 0
[pid  1662] 18:47:45 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=613, left=0, width=14, height=40}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=192, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2d7e61b8) = 0
[pid  1662] 18:47:46 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=613, left=0, width=14, height=40}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=193, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2d7e63f8) = 0
[pid  1662] 18:47:48 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=10, left=0, width=1080, height=1430}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=194, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2d7eaff8) = 0
[pid  1662] 18:47:48 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=91, left=0, width=14, height=40}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=195, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2d7eb0f0) = 0
[pid  1662] 18:47:49 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=91, left=0, width=14, height=40}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=196, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2d7eb570) = 0
@NiLuJe

This comment has been minimized.

Member

NiLuJe commented Jul 7, 2018

Same after switching to refreshFast (if only to make the Kobo strace log easier to parse ^^).

[pid  1743] 18:51:26 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=10, left=0, width=1080, height=1430}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=113, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x33874008) = 0
[pid  1743] 18:51:26 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=91, left=0, width=14, height=40}, waveform_mode=NTX_WFM_MODE_A2, update_mode=UPDATE_MODE_PARTIAL, update_marker=114, temp=TEMP_USE_AMBIENT, flags=EPDC_FLAG_FORCE_MONOCHROME, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x33874748) = 0
[pid  1743] 18:51:27 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=91, left=0, width=14, height=40}, waveform_mode=NTX_WFM_MODE_A2, update_mode=UPDATE_MODE_PARTIAL, update_marker=115, temp=TEMP_USE_AMBIENT, flags=EPDC_FLAG_FORCE_MONOCHROME, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x33874a20) = 0
[pid  1743] 18:51:29 [2ac131f6] ioctl(3, MXCFB_WAIT_FOR_UPDATE_COMPLETE_V1, {115}, 0x33884a60) = 0
[pid  1743] 18:51:29 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=10, left=0, width=1080, height=1430}, waveform_mode=NTX_WFM_MODE_GC16, update_mode=UPDATE_MODE_FULL, update_marker=116, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x33889238) = 0
[pid  1743] 18:51:29 [2ac131f6] ioctl(3, MXCFB_WAIT_FOR_UPDATE_COMPLETE_V1, {116}, 0x3387cdc8) = 0x3b1
[pid  1743] 18:51:29 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=613, left=0, width=14, height=40}, waveform_mode=NTX_WFM_MODE_A2, update_mode=UPDATE_MODE_PARTIAL, update_marker=117, temp=TEMP_USE_AMBIENT, flags=EPDC_FLAG_FORCE_MONOCHROME, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x33889470) = 0
[pid  1743] 18:51:30 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=613, left=0, width=14, height=40}, waveform_mode=NTX_WFM_MODE_A2, update_mode=UPDATE_MODE_PARTIAL, update_marker=118, temp=TEMP_USE_AMBIENT, flags=EPDC_FLAG_FORCE_MONOCHROME, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x338898b0) = 0
[pid  1743] 18:51:33 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=10, left=0, width=1080, height=1430}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=119, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x3388d898) = 0
[pid  1743] 18:51:33 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=91, left=0, width=14, height=40}, waveform_mode=NTX_WFM_MODE_A2, update_mode=UPDATE_MODE_PARTIAL, update_marker=120, temp=TEMP_USE_AMBIENT, flags=EPDC_FLAG_FORCE_MONOCHROME, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x3388d950) = 0
[pid  1743] 18:51:34 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=91, left=0, width=14, height=40}, waveform_mode=NTX_WFM_MODE_A2, update_mode=UPDATE_MODE_PARTIAL, update_marker=121, temp=TEMP_USE_AMBIENT, flags=EPDC_FLAG_FORCE_MONOCHROME, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x3388dfb0) = 0
[pid  1743] 18:51:36 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=10, left=0, width=1080, height=1430}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=122, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x3389fd30) = 0
[pid  1743] 18:51:36 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=613, left=0, width=14, height=40}, waveform_mode=NTX_WFM_MODE_A2, update_mode=UPDATE_MODE_PARTIAL, update_marker=123, temp=TEMP_USE_AMBIENT, flags=EPDC_FLAG_FORCE_MONOCHROME, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x338aa458) = 0
[pid  1743] 18:51:37 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=613, left=0, width=14, height=40}, waveform_mode=NTX_WFM_MODE_A2, update_mode=UPDATE_MODE_PARTIAL, update_marker=124, temp=TEMP_USE_AMBIENT, flags=EPDC_FLAG_FORCE_MONOCHROME, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x338aa698) = 0
[pid  1743] 18:51:38 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=10, left=0, width=1080, height=1430}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=125, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x338ae480) = 0
[pid  1743] 18:51:38 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=91, left=0, width=14, height=40}, waveform_mode=NTX_WFM_MODE_A2, update_mode=UPDATE_MODE_PARTIAL, update_marker=126, temp=TEMP_USE_AMBIENT, flags=EPDC_FLAG_FORCE_MONOCHROME, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x338ae738) = 0
[pid  1743] 18:51:39 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=91, left=0, width=14, height=40}, waveform_mode=NTX_WFM_MODE_A2, update_mode=UPDATE_MODE_PARTIAL, update_marker=127, temp=TEMP_USE_AMBIENT, flags=EPDC_FLAG_FORCE_MONOCHROME, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x338ae978) = 0

NiLuJe added a commit to NiLuJe/koreader that referenced this pull request Jul 7, 2018

Refresh goto link markers as "fast" instead of UI
They're a small black rectangle, perfect candidate :).

re koreader#3983
@poire-z

This comment has been minimized.

Contributor

poire-z commented Jul 7, 2018

Looks like in the good order too on my GloHD:
2 partials + 1 full flashing at the end:

18:52:19 [2acac1bc] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=0, left=0, width=1448, height=1072}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=69, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2d8aeef0) = 0
18:52:19 [2acac1bc] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=1044, left=753, width=38, height=28}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=70, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2d8b7ad8) = 0
18:52:20 [2acac1bc] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=1044, left=753, width=38, height=28}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=71, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2d8b7f38) = 0



18:52:23 [2acac1bc] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=0, left=0, width=1448, height=1072}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=72, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2dc983e8) = 0
18:52:23 [2acac1bc] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=1044, left=1192, width=38, height=28}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=73, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2dc99dd0) = 0
18:52:24 [2acac1bc] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=1044, left=1192, width=38, height=28}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=74, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2dc9a230) = 0



18:52:26 [2acac1bc] ioctl(3, MXCFB_WAIT_FOR_UPDATE_COMPLETE_V1, {74}, 0x2dcbd378) = 0
18:52:26 [2acac1bc] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=0, left=0, width=1448, height=1072}, waveform_mode=NTX_WFM_MODE_GC16, update_mode=UPDATE_MODE_FULL, update_marker=75, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2dcbd2e8) = 0
18:52:26 [2acac1bc] ioctl(3, MXCFB_WAIT_FOR_UPDATE_COMPLETE_V1, {75}, 0x2dcbd568) = 0x1c0
18:52:26 [2acac1bc] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=1044, left=753, width=38, height=28}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=76, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2dcbd800) = 0
18:52:27 [2acac1bc] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=1044, left=753, width=38, height=28}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=77, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2dcbde70) = 0

And even with that partial one, where a different timestamp is logged for each, the marker was visible on the previous page:

18:54:57 [2acac1bc] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=0, left=0, width=1448, height=1072}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=78, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2dcf1178) = 0
18:54:58 [2acac1bc] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=1044, left=1192, width=38, height=28}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=79, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2dcf14a0) = 0
18:54:59 [2acac1bc] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=1044, left=1192, width=38, height=28}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=80, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2dcf1900) = 0

(Again, nothing to fix ! it's quite smooth as it is :) (except may be a faster partial refresh :)

@poire-z

This comment has been minimized.

Contributor

poire-z commented Jul 7, 2018

Marker on previous page still happens with refreshFast.

18:58:58 [2ac631bc] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=0, left=0, width=1448, height=1072}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=58, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2d758cc0) = 0
18:58:58 [2ac631bc] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=1044, left=1192, width=38, height=28}, waveform_mode=NTX_WFM_MODE_A2, update_mode=UPDATE_MODE_PARTIAL, update_marker=59, temp=TEMP_USE_AMBIENT, flags=EPDC_FLAG_FORCE_MONOCHROME, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2bf2a1f0) = 0
18:58:59 [2ac631bc] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=1044, left=1192, width=38, height=28}, waveform_mode=NTX_WFM_MODE_A2, update_mode=UPDATE_MODE_PARTIAL, update_marker=60, temp=TEMP_USE_AMBIENT, flags=EPDC_FLAG_FORCE_MONOCHROME, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2bf98f48) = 0


18:59:00 [2ac631bc] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=0, left=0, width=1448, height=1072}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=61, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2b7d5848) = 0
18:59:00 [2ac631bc] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=1044, left=753, width=38, height=28}, waveform_mode=NTX_WFM_MODE_A2, update_mode=UPDATE_MODE_PARTIAL, update_marker=62, temp=TEMP_USE_AMBIENT, flags=EPDC_FLAG_FORCE_MONOCHROME, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2d1f6430) = 0
18:59:01 [2ac631bc] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=1044, left=753, width=38, height=28}, waveform_mode=NTX_WFM_MODE_A2, update_mode=UPDATE_MODE_PARTIAL, update_marker=63, temp=TEMP_USE_AMBIENT, flags=EPDC_FLAG_FORCE_MONOCHROME, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2d248618) = 0

NiLuJe added a commit to NiLuJe/koreader that referenced this pull request Jul 7, 2018

Refresh goto link markers as "fast" instead of "ui"
It's a small black rectangle, perfect candidate :)

Re koreader#3983
@NiLuJe

This comment has been minimized.

Member

NiLuJe commented Jul 7, 2018

And if I quickly switch page before the unmark, it properly gets zapped by CRe.

[pid  1743] 19:12:32 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=91, left=0, width=14, height=40}, waveform_mode=NTX_WFM_MODE_A2, update_mode=UPDATE_MODE_PARTIAL, update_marker=606, temp=TEMP_USE_AMBIENT, flags=EPDC_FLAG_FORCE_MONOCHROME, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x300ce6e0) = 0
[pid  1743] 19:12:32 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=10, left=0, width=1080, height=1430}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=607, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x3385e340) = 0
[pid  1743] 19:12:33 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=91, left=0, width=14, height=40}, waveform_mode=NTX_WFM_MODE_A2, update_mode=UPDATE_MODE_PARTIAL, update_marker=608, temp=TEMP_USE_AMBIENT, flags=EPDC_FLAG_FORCE_MONOCHROME, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x3385e618) = 0
[pid  1743] 19:12:33 [2ac131f6] ioctl(3, MXCFB_SEND_UPDATE_V1_NTX, {update_region={top=10, left=0, width=1080, height=1430}, waveform_mode=WAVEFORM_MODE_AUTO, update_mode=UPDATE_MODE_PARTIAL, update_marker=609, temp=TEMP_USE_AMBIENT, flags=0, alt_buffer_data={virt_addr=(nil), phys_addr=0, width=0, height=0, alt_update_region={top=0, left=0, width=0, height=0}}}, 0x2ff6bd00) = 0

(i.e., that final A2 unmark is effectively a noop, both in terms of ioctl and visually ;)).

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented Jul 7, 2018

So, yeah, AFAICT, everything's behaving as it should ;).

At most, there's a bit of sneakiness on the eInk driver's part, as a 10x10 (or whatever, it's small ^^) rectangle is simply faster to handle than a full-screen refresh, even PARTIAL.

The fact that the flow is made much clearer by a flashing (FULL) update is basically a side-effect of a large part of the raison d'être of flashing updates: they're blocking, and they make sure they're completed before processing any other updates ;).
Here, since we make sure the page update ioctl gets sent first, that means the marker will by design be delayed until the page is done ;).

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented Jul 7, 2018

I'll frame-step through a recording on my H2O to check if the tight timings on PARTIAL is as obvious as on your GloHD, because I'm not seeing it with the naked eye (it's fast, but I don't get the impression of seeing it on the previous page).

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented Jul 7, 2018

And to answer one of your original question: yes, the hardware (and the driver) can process stuff in parallel.

IIRC, depending on the HW & the driver, it's usually something between 10 & 20 (!) updates in parallel, a fact @geekmaster used & abused in his "high" framerate animation/video demos back in the day (Hell, I think that was even before Carta, when things were even more limited).

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented Jul 7, 2018

And it keeps getting better:

#define EPDC_V1_NUM_LUTS        16
#define EPDC_V1_MAX_NUM_UPDATES 20
#define EPDC_V2_NUM_LUTS        64
#define EPDC_V2_MAX_NUM_UPDATES 64

(The V2 controller is (hopefully) used on Mk. 7 hardware, as well as by the Kindle Oasis 2).

@poire-z

This comment has been minimized.

Contributor

poire-z commented Jul 7, 2018

May be the hw/driver does too much stuff with WAVEFORM_MODE_AUTO.
I just tried with:

        self.waveform_fast = C.WAVEFORM_MODE_A2
        self.waveform_ui = C.WAVEFORM_MODE_A2 -- was AUTO
        self.waveform_flashui = self.waveform_ui
        self.waveform_full = C.NTX_WFM_MODE_GC16
        self.waveform_partial = C.WAVEFORM_MODE_A2 -- was AUTO

and it's indeed faster (the marker only shows on next page) - but it's super dirty :)

@Frenzie

This comment has been minimized.

Member

Frenzie commented Jul 7, 2018

No time to read all this (whoa, guys :-p) but I just wanted to add that black flashes feel slower to me even if I think they're probably the same speed.

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented Jul 7, 2018

Yeah, you can't get any faster than A2, but, well, it's monochrome ;p.

Even flashing A2 (... when you can get it to flash :D) if significantly faster (as in, it blocks for much less time).

I added some code to log the exact amount of time a FULL update blocks for in FBInk (for SCIENCE!), and it was pretty significant (like a slash from ~650ms to ~200ms, IIRC).

EDIT:

A2:

Waited 180ms for completion of flashing update 2137

AUTO (-> GC16, actually):

Waited 540ms for completion of flashing update 2164

Granularity is kind of shitty (depends in part on the kernel's HZ, which is 100 here), so assume everything can be +/- 50 to 100ms ;).

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented Jul 7, 2018

Yeah, when framestepping through a recording of my H2O, I'm also seeing it pop up on the next page only ;).

Which could also just mean that my H2O manages to render & update the next page faster than the end of the scheduling timer of the marker ;).

@robert00s robert00s referenced this pull request Jul 15, 2018

Closed

Ghosts in menu #4079

@poire-z

This comment has been minimized.

Contributor

poire-z commented Jul 28, 2018

Mhhh, just noticing (may be because of higher temperatures) that there is some rémanence (is afterglow the right english translation?) of my little markers.

refreshfast2
(on the left of 175, 176 & 177)

This happens when refreshFast is used, but it does not happen if I switch it back to refreshUI (then, it's all nice white). (of course, they disappear on the next full flashing refresh).

So, dunno if we should switch it back to refreshUI, or just consider that a device defect that should not question the generic rules of when to use which that usually works well (I can of course patch this for my own use if they'll keep on bothering me in the winter :)

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented Jul 28, 2018

(In this context, usually, ghosting or reverse ghosting ;)).

It's mildly interesting that UI handles it better (and... somewhat surprising, even GC16 can't always handle that perfectly, there's plain physics involved past a certain point ^^), and might be platform specific (I'm fairly sure it'd be more or less identical on a Kindle, for instance) ;).

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented Jul 28, 2018

Yep, not a hint of anything on a PW2 ;).

(Although REAGL, so, fast is DU+Monochrome, not A2+Monochrome).

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented Jul 28, 2018

Very, very faint ghosting on the H2O (about as bad as anywhere else, basically :D).

Switching to UI for the unmark helps mildly, and that's not the only place we do that, so, why not ;p.

NiLuJe added a commit to NiLuJe/koreader that referenced this pull request Jul 28, 2018

Switch to UI for the unmark event of navigation hints markers
Might help on non-REAGL devices, and doesn't hurt there.

re koreader#3983
@poire-z

This comment has been minimized.

Contributor

poire-z commented Jul 28, 2018

Oh, right, the unmark one was enough :) Thanks!

NiLuJe added a commit that referenced this pull request Jul 29, 2018

Tweak/unbreak my CPUFreq experiment (#4119)
* Only switch to ondemand when we actually can, and when it's better than the current governor...
  This potentially leaves Mk.5 in the lurch, but there's no perfect solution there :/.

* Switch to UI for the unmark event of navigation hints markers
Might help on non-REAGL devices, and doesn't hurt there (re #3983).

* Switch SQLite DBs to WAL
Sounds nice in theory, behaves fine in practice.
@poire-z

This comment has been minimized.

Contributor

poire-z commented Aug 6, 2018

Found some interesting article about waveforms (no real friendly url):
https://viewer.heropunch.io/channel/fread.ink
http://between-two-worlds.dk:8807/%rCqrzMf6VgFih0duxPS17uSEqsC/3mSex5y2p77gH4Y=.sha256

(I like to know what D U G C L mean in the name they make :))
Pasting for reference:

  {MODE_INIT, "INIT (panel initialization / clear screen to white)"},
  {MODE_DU, "DU (direct update, gray to black/white transition, 1bpp)"},
  {MODE_GC16, "GC16 (high fidelity, flashing, 4bpp)"},
  {MODE_GC16_FAST, "GC16_FAST (medium fidelity, 4bpp)"},
  {MODE_A2, "A2 (animation update, fastest and lowest fidelity)"},
  {MODE_GL16, "GL16 (high fidelity from white transition, 4bpp)"},
  {MODE_GL16_FAST, "GL16_FAST (medium fidelity from white transition, 4bpp)"},
  {MODE_DU4, "DU4 (direct update, medium fidelity, text to text, 2bpp)"},
  {MODE_REAGL, "REAGL (non-flashing, ghost-compensation)"},
  {MODE_REAGLD, "REAGLD (non-flashing, ghost-compensation with dithering)"},
  {MODE_GL4, "GL4 (2-bit from white transition, 2bpp)"},
  {MODE_GL16_INV, "GL16_INV (high fidelity for black transition, 4bpp)"},

from https://github.com/fread-ink/inkwave/blob/4123fb278f6f22300c463565b9333f87d4ae182a/main.c#L44-L58

complementing:
https://github.com/koreader/koreader-base/blob/f87b99f4b8fec490873af779d0d9d079a39d7d77/ffi-cdecl/include/mxcfb-kindle.h#L124-L132

(Also: https://patentimages.storage.googleapis.com/03/5c/8a/796216ca1bfc1f/US20130300779A1.pdf )

I'll try GL16 or GL16_FAST (I see it's what is used on kindle for waveform_partial) if it's faster than AUTO for partial.

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented Aug 6, 2018

@poire-z : Didn't have to look too far ;). (It's been in lab126's kernel headers for as long as I can remember ;)).

GL16_FAST & GC16_FAST were Kindle specific (they're gone on the KOA2).
GL16 is what AUTO often fall backs to (... when GL16_FAST is not available ^^), being the nice middle ground that it is ;).

DU4 was potentially a fun experiment, but it's again, Kindle only (appeared during the PW2 prototyping stage, AFAICT), and probably requires a text/rendering stack tuned for it (because 2bpp). Arguably, even GC16 requires a tuned rendering stack for best results, but, hey ;D.

EDIT: c.f., also this, if you want to make sense of lab126's board codenames, to figure out where those waveforms come from ;).

EDIT²: Can't remember where those defines are hidden OTOH, but V220 refers to a Carta (gen1) substrate. Pearl was V110 or something like that IIRC.

EDIT³: And here for a rough recap of what you can use depending on the device.

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented Aug 6, 2018

That patent is fun, though, if a bit pipe-dreamy (Yay, hardware accelerated PDF rendering? :D Also, I want to see their 64bit Cortex A9 ?!).

That's basically what's done in any decent device when panning an image f.g., (panning in A2 -> GC16 when stopped), except it proposes a deeper integration, instead of leaving it to the user-space to decide.

Funnily enough, I was going to mentioning mostly failed experiments of doing multi-waveform updates in steps, like that, with a *4 in the middle ;).

@poire-z

This comment has been minimized.

Contributor

poire-z commented Aug 6, 2018

I'll try GL16 or GL16_FAST

No GL16_FAST on Kobo, but it shares the same number as WAVEFORM_MODE_GLR16.
Anyway, tried both, no noticable change from WAVEFORM_MODE_AUTO.
I'll live with a partial slower than a full then.

@NiLuJe

This comment has been minimized.

Member

NiLuJe commented Aug 6, 2018

GLR16 is REAGL, FWIW ;).

Which waveform mode ends up at which index is entirely at the discretion of the driver, which explains why it can be wildly different ;). (Hell, the KOA2 wreaked havoc on that front, even withing lab126's convention).

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