NiLuJe / FBInk

FrameBuffer eInker, a small tool & library to print text & images to an eInk Linux framebuffer
https://www.mobileread.com/forums/showthread.php?t=299110
GNU General Public License v3.0
315 stars 23 forks source link

Musings and ideas for a potential v2 API #43

Open shermp opened 4 years ago

shermp commented 4 years ago

Carrying on from our discussions in shermp/go-fbink-v2/pull/9, I just wanted to jot down a few ideas rolling around in my skull while they are fresh.

Feel free to ignore, or wait on this.

That's one initial thought from me. I'm generally not a fan of getter/setters, coming from a Python/C background, but in this case it might help prevent API breakage by hiding internal structs etc.

NiLuJe commented 4 years ago

Just a quick braindump, too, some of which was detailed a tiny bit in the linked PR ;).

That said, a truly modular library model is definitely not a bad idea, and certainly a fairly minor (famous last words ;p) hurdle once the API is taken care of ;).

shermp commented 4 years ago

If you need to keep the fbfd AUTO behaviour, I suppose you could pass that as a parameter to the context 'constructor', and use the same logic as you already are to test it.

NiLuJe commented 4 years ago

I think I've mentioned this before, but I'm not really a CS person, and I've never managed to get into OOP, so designing a cool API is really not my forte ^^.

So, yeah, please feel free to throw stuff at the wall, and we'll see what sticks ^^.

shermp commented 4 years ago

I'm not an OOP person myself, although I did take a class on it. I tend to take some of the bits I find useful, but I'm hardly one to preach the OOP gospel!

From a bindings perspective, not having to deal with lots of structs (externally) is kind of a nice thought, which is why I'm a little partial to the getter/setter idea here.

shermp commented 4 years ago

Changing tack slightly, from an internal FBInk standpoint, would it be worth investigating using put_pixels variants? I was just looking at the if-ladder in draw_image() and thinking yikes!

As well as simplifying the if-else ladder(s), I'm wondering if the individual calls to get_pixel/put_pixel allows the compiler to auto-vectorize that code at all. From what little I've read about SIMD, and my gut instinct would make me think that blitting code is currently not being SIMD optimized by the compiler.

Thoughts?

NiLuJe commented 4 years ago

Variants in which way, exactly?

(There's already one variant per bitdepth, which the insane ladder in draw_image takes advantage of. Speaking of that ladder, most/all of it gets hoisted properly, so it's fugly as hell, but it doesn't perform too badly, as far as branching is concerned at least).

But, yeah, none of that gets vectorized, mostly because the loops are too complex, and/or the bounds are unclear/dynamic IIRC.

(Which is why anything than can go through a memcpy or fill_rect instead of put_pixel* is a happy camper ^^).

(The vectorization diagnostics are much more readable since GCC 9).

shermp commented 4 years ago

put_pixels with an s. a function for each bit depth that takes an input buffer, coordinates, dimensions, stride etc, and plots pixels with inversions/alpha as necessary, without needing to use individual function calls to put/get pixels.

I'm also a bit surprised you haven't made put/get_pixel macros or inline functions to be honest. Whether that would make a measurable improvment or not I don't know...

NiLuJe commented 4 years ago

IIRC, every *_pixel_* functions gets inlined properly (except when going through a function pointer, which I think is no longer the case anywhere, precisely to allow inlining to kick-in, IIRC).

(The inline keyword is more of a suggestion than anything, the compiler generally figures these things out on its own just fine, especially for static functions. Which is everything in FBInk, since it's built as an amalgam. What it does is make the compiler emit a diagnostic when it thinks inlining would be a loss. As for always_inline, it will enforce inlining, and slightly tweak that diagnostic's wording accordingly ;p).

shermp commented 4 years ago

Huh. Ok. Fair enough.

NiLuJe commented 4 years ago

KOReader's C blitter does use a few macros, but I found it a bit of a PITA to work with in practice ;p.

Granted, it has to bend its back to follow the legacy Lua API of the Lua blitter, which doesn't help ^^.

shermp commented 4 years ago

I don't blame you on the macro's, I avoid 'em like the plague myself generally.

NiLuJe commented 4 years ago

Okay, apparently they're not inlined everywhere (at a glance, specifically in print_ot). It'd been a while since I checked, I'll look into it ;).

NiLuJe commented 4 years ago

Oh, or when I last checked, having them inlined where the compiler wasn't already doing so was a loss. Can't recall -_-".

Will test again ^^.

NiLuJe commented 4 years ago

For ref.

diff --git a/fbink_internal.h b/fbink_internal.h
index 87a9f69..d167b56 100644
--- a/fbink_internal.h
+++ b/fbink_internal.h
@@ -450,24 +450,24 @@ static void rotate_coordinates_nop(FBInkCoordinates* restrict __attribute__((unu

 static inline uint16_t pack_rgb565(uint8_t, uint8_t, uint8_t);

-static void put_pixel_Gray4(const FBInkCoordinates* restrict, const FBInkPixel* restrict);
-static void put_pixel_Gray8(const FBInkCoordinates* restrict, const FBInkPixel* restrict);
-static void put_pixel_RGB24(const FBInkCoordinates* restrict, const FBInkPixel* restrict);
-static void put_pixel_RGB32(const FBInkCoordinates* restrict, const FBInkPixel* restrict);
-static void put_pixel_RGB565(const FBInkCoordinates* restrict, const FBInkPixel* restrict);
+static inline void put_pixel_Gray4(const FBInkCoordinates* restrict, const FBInkPixel* restrict);
+static inline void put_pixel_Gray8(const FBInkCoordinates* restrict, const FBInkPixel* restrict);
+static inline void put_pixel_RGB24(const FBInkCoordinates* restrict, const FBInkPixel* restrict);
+static inline void put_pixel_RGB32(const FBInkCoordinates* restrict, const FBInkPixel* restrict);
+static inline void put_pixel_RGB565(const FBInkCoordinates* restrict, const FBInkPixel* restrict);
 // NOTE: We pass coordinates by value here, because a rotation transformation *may* be applied to them,
 //       and that's a rotation that the caller will *never* care about.
-static void put_pixel(FBInkCoordinates, const FBInkPixel* restrict, bool);
+static inline void put_pixel(FBInkCoordinates, const FBInkPixel* restrict, bool);
 // NOTE: On the other hand, if you happen to be calling function pointers directly,
 //       it's left to you to not do anything stupid ;)

-static void get_pixel_Gray4(const FBInkCoordinates* restrict, FBInkPixel* restrict);
-static void get_pixel_Gray8(const FBInkCoordinates* restrict, FBInkPixel* restrict);
-static void get_pixel_RGB24(const FBInkCoordinates* restrict, FBInkPixel* restrict);
-static void get_pixel_RGB32(const FBInkCoordinates* restrict, FBInkPixel* restrict);
-static void get_pixel_RGB565(const FBInkCoordinates* restrict, FBInkPixel* restrict);
+static inline void get_pixel_Gray4(const FBInkCoordinates* restrict, FBInkPixel* restrict);
+static inline void get_pixel_Gray8(const FBInkCoordinates* restrict, FBInkPixel* restrict);
+static inline void get_pixel_RGB24(const FBInkCoordinates* restrict, FBInkPixel* restrict);
+static inline void get_pixel_RGB32(const FBInkCoordinates* restrict, FBInkPixel* restrict);
+static inline void get_pixel_RGB565(const FBInkCoordinates* restrict, FBInkPixel* restrict);
 // NOTE: Same as put_pixel ;)
-static void get_pixel(FBInkCoordinates, FBInkPixel* restrict);
+static inline void get_pixel(FBInkCoordinates, FBInkPixel* restrict);

 #if defined(FBINK_WITH_IMAGE) || defined(FBINK_WITH_OPENTYPE)
 // This is only needed for alpha blending in the image or OpenType codepath ;).
fbink.c: In function 'fbink_print_ot':
fbink.c:373:5: warning: inlining failed in call to 'put_pixel.constprop': call is unlikely and code size would grow [-Winline]
     put_pixel(FBInkCoordinates coords, const FBInkPixel* restrict px, bool is_rgb565)
     ^
fbink.c:4845:7: note: called from here
       put_pixel(paint_point, &pixel, false);
       ^
fbink.c:373:5: warning: inlining failed in call to 'put_pixel.constprop': call is unlikely and code size would grow [-Winline]
     put_pixel(FBInkCoordinates coords, const FBInkPixel* restrict px, bool is_rgb565)
     ^
fbink.c:4858:8: note: called from here
        put_pixel(paint_point, &bgP, true);
        ^
fbink.c:373:5: warning: inlining failed in call to 'put_pixel.constprop': call is unlikely and code size would grow [-Winline]
     put_pixel(FBInkCoordinates coords, const FBInkPixel* restrict px, bool is_rgb565)
     ^
fbink.c:4861:8: note: called from here
        put_pixel(paint_point, &fgP, true);
        ^
fbink.c:373:5: warning: inlining failed in call to 'put_pixel.constprop': call is unlikely and code size would grow [-Winline]
     put_pixel(FBInkCoordinates coords, const FBInkPixel* restrict px, bool is_rgb565)
     ^
fbink.c:4866:8: note: called from here
        put_pixel(paint_point, &pixel, false);
        ^
fbink.c:373:5: warning: inlining failed in call to 'put_pixel.constprop': call is unlikely and code size would grow [-Winline]
     put_pixel(FBInkCoordinates coords, const FBInkPixel* restrict px, bool is_rgb565)
     ^
fbink.c:4885:8: note: called from here
        put_pixel(paint_point, &bgP, true);
        ^
fbink.c:373:5: warning: inlining failed in call to 'put_pixel.constprop': call is unlikely and code size would grow [-Winline]
     put_pixel(FBInkCoordinates coords, const FBInkPixel* restrict px, bool is_rgb565)
     ^
fbink.c:4896:8: note: called from here
        put_pixel(paint_point, &pixel, false);
        ^
fbink.c:525:5: warning: inlining failed in call to 'get_pixel': call is unlikely and code size would grow [-Winline]
     get_pixel(FBInkCoordinates coords, FBInkPixel* restrict px)
     ^
fbink.c:4889:8: note: called from here
        get_pixel(paint_point, &fb_px);
        ^
fbink.c:373:5: warning: inlining failed in call to 'put_pixel.constprop': call is unlikely and code size would grow [-Winline]
     put_pixel(FBInkCoordinates coords, const FBInkPixel* restrict px, bool is_rgb565)
     ^
fbink.c:4912:7: note: called from here
       put_pixel(paint_point, &pixel, false);
       ^
fbink.c:525:5: warning: inlining failed in call to 'get_pixel': call is unlikely and code size would grow [-Winline]
     get_pixel(FBInkCoordinates coords, FBInkPixel* restrict px)
     ^
fbink.c:4909:7: note: called from here
       get_pixel(paint_point, &fb_px);
       ^
fbink.c:373:5: warning: inlining failed in call to 'put_pixel.constprop': call is unlikely and code size would grow [-Winline]
     put_pixel(FBInkCoordinates coords, const FBInkPixel* restrict px, bool is_rgb565)
     ^
fbink.c:4931:8: note: called from here
        put_pixel(paint_point, &pixel, false);
        ^
fbink.c:525:5: warning: inlining failed in call to 'get_pixel': call is unlikely and code size would grow [-Winline]
     get_pixel(FBInkCoordinates coords, FBInkPixel* restrict px)
     ^
fbink.c:4928:8: note: called from here
        get_pixel(paint_point, &fb_px);
        ^
fbink.c:373:5: warning: inlining failed in call to 'put_pixel.constprop': call is unlikely and code size would grow [-Winline]
     put_pixel(FBInkCoordinates coords, const FBInkPixel* restrict px, bool is_rgb565)
     ^
fbink.c:4949:8: note: called from here
        put_pixel(paint_point, &pixel, false);
        ^
fbink.c:525:5: warning: inlining failed in call to 'get_pixel': call is unlikely and code size would grow [-Winline]
     get_pixel(FBInkCoordinates coords, FBInkPixel* restrict px)
     ^
fbink.c:4936:8: note: called from here
        get_pixel(paint_point, &fb_px);
        ^
fbink.c:373:5: warning: inlining failed in call to 'put_pixel.constprop': call is unlikely and code size would grow [-Winline]
     put_pixel(FBInkCoordinates coords, const FBInkPixel* restrict px, bool is_rgb565)
     ^
fbink.c:4967:7: note: called from here
       put_pixel(paint_point, &pixel, false);
       ^
fbink.c:525:5: warning: inlining failed in call to 'get_pixel': call is unlikely and code size would grow [-Winline]
     get_pixel(FBInkCoordinates coords, FBInkPixel* restrict px)
     ^
fbink.c:4963:7: note: called from here
       get_pixel(paint_point, &fb_px);
       ^
fbink.c:373:5: warning: inlining failed in call to 'put_pixel.constprop': call is unlikely and code size would grow [-Winline]
     put_pixel(FBInkCoordinates coords, const FBInkPixel* restrict px, bool is_rgb565)
     ^
fbink.c:4983:8: note: called from here
        put_pixel(paint_point, &fgP, true);
        ^
fbink.c:373:5: warning: inlining failed in call to 'put_pixel.constprop': call is unlikely and code size would grow [-Winline]
     put_pixel(FBInkCoordinates coords, const FBInkPixel* restrict px, bool is_rgb565)
     ^
fbink.c:4997:8: note: called from here
        put_pixel(paint_point, &pixel, false);
        ^
fbink.c:525:5: warning: inlining failed in call to 'get_pixel': call is unlikely and code size would grow [-Winline]
     get_pixel(FBInkCoordinates coords, FBInkPixel* restrict px)
     ^
fbink.c:4987:8: note: called from here
        get_pixel(paint_point, &fb_px);
        ^
fbink.c:373:5: warning: inlining failed in call to 'put_pixel.constprop': call is unlikely and code size would grow [-Winline]
     put_pixel(FBInkCoordinates coords, const FBInkPixel* restrict px, bool is_rgb565)
     ^
fbink.c:5013:7: note: called from here
       put_pixel(paint_point, &pixel, false);
       ^
fbink.c:525:5: warning: inlining failed in call to 'get_pixel': call is unlikely and code size would grow [-Winline]
     get_pixel(FBInkCoordinates coords, FBInkPixel* restrict px)
     ^
fbink.c:5010:7: note: called from here
       get_pixel(paint_point, &fb_px);
       ^
fbink.c: In function 'draw_image':
fbink.c:496:5: warning: inlining failed in call to 'get_pixel_RGB565': call is unlikely and code size would grow [-Winline]
     get_pixel_RGB565(const FBInkCoordinates* restrict coords, FBInkPixel* restrict px)
     ^
fbink.c:7019:7: note: called from here
       get_pixel_RGB565(&coords, &bg_px);
NiLuJe commented 4 years ago

And at a very very quick glance, enforcing 'em nets a noticeable win.

Now I have to rememebr when I last checked and why it wasn't so at the time... :D

NiLuJe commented 4 years ago

Chiefly affects print_ot, across all bitdepths.

shermp commented 4 years ago

Back to the hypothetical v2 API, just had a quick thought, maybe consider dumping the whole rows/columns thing? Seems a simple offset/dimensions system would cause a lot less implementation headache.

NiLuJe commented 4 years ago

But I luuuuurrve it ;p.

(Also, it's inherited from eips, and I just find it practical for the fixed-cell rendering codepath. Practical to use, say, certainly not to make it work more or less properly inside FBInk :D).

shermp commented 4 years ago

I guess from my perspective, most of the complexities appear to come from trying to mix the fixed cell layout with pixel based adjustments for centering, padding etc. Seems a bit counter-intuitive to me, but hey, I never used eips, so what do I know...?

NiLuJe commented 4 years ago

Yeah, I definitely get that.

I say "it makes sense as a user", but then I also remember when I was wondering WTF were the lab126 guys on when I first played with it, soooooo :D.

I do like it, once you wrap your head around it. I do agree that it only makes sense in the fixed-cell codepath, which means the whole thing clashes a bit once you start mixing the two together ;).

NiLuJe commented 4 years ago

IIRC, I wasn't mixing the two together (inside the fixed-cell codepath, I mean) at the start. Then the whole "let's handle the weird H2O viewport" thing happen, and here I was with a huge pixel shift grain of sand in my shiny row/column machinery ^^...

And then image support happen, where you probably definitely expected pixels...

Then I went, hey, might be fun to align images to text...

And that's how you end up with a baby monster :D.

NiLuJe commented 4 years ago

Incidentally, once the two started getting mixed together, the fact that you could essentially ignore one whole side of it altogether was kinda neat (i.e., you can use the fixed-cell codepath entirely with pixel values, or entirely w/ row/col).

That doesn't help the code, but at least that insanity doesn't bleed though too much to the user (except in the "there's seventy billion different settings" kind of way ^^).

NiLuJe commented 4 years ago

Whee, found the traces of my previous inline tests:

The first once I can understand, results were poor because stuff was still going through function pointers; but the second one is interesting: that's what made me switch away from function pointers in put_pixel (at least during the experiment). I don't remember why I didn't keep the inlining at the time, though :D.

That was in the middle of the qimagescale stuff, so, err, probably a lot of stuff going on...

hdhbdh commented 4 years ago

I have fixed it.

hdhbdh commented 4 years ago

Thanks for the help with the Amazon Kobo.

hdhbdh commented 4 years ago

@shermp told me to do this.

shermp commented 4 years ago

Just for the record, I was asleep during this current bout of nonsense.

NiLuJe commented 4 years ago

Hey, there's now a fancy new? Report/Block button, kudos GitHub! :).

(If it isn't new, I failed to notice it during the last round of trolling last year or so).

pgaskin commented 4 years ago

Yes, that's new (it was added around halfway through last year). Unfortunately, it still says this issue was "Fixed by hdhbdh/Kobo-Reader#1", though.

NiLuJe commented 4 years ago

Huh. The fact that this is picked up in the title comments (or whatever that area is called) across unconnected repos in the first place is... weird?

(I mean, it's great when it works for actually connected repos, which comes in handy with the various KOReader submodules for cross-references, for instance), but across unconnected (not the same owner/organization/commit rights) feels like an oversight? One that's ripe for the taking for trolling opportunities...

NiLuJe commented 3 years ago

Note to self before I entirely forget about it:

On the subject of setters/getters. Having recently played with ZSTD, I kinda like the approach of a single setter/getter function, which takes a pointer to an opaque struct (e.g., it's just an empty forward declaration in the public header), and you choose what to get/set via a list of constants from an enum as the actual argument.

That is all ;p.

NiLuJe commented 3 years ago

Note to self, again: