Jump to content

mcufont internals


king2

Recommended Posts

Going deeper and deeper into uGFX. Some question was born when looking on font engine sources.

1. mf_justify.cmf_render_aligned(). Rendering string with left align is simple, center alignment  is just left render with some shit by calculated coordinates, but right alignment is different, using different approach, drawing character from right to left, from end to start. Why not calculate shift as it was done for center alignment and use same routine?

2. gdispGDrawString() and gdispGFillString() differs only by one line, why using two separate functions instead of one? Same with other *Box functions?

3. How everything works (assume that everything is simple and we are not using word wrapping):

  • somebody calls gdispGDrawString(), here we are starting
  • gdisp.c: gdispGDrawString() from calls mf_render_aligned with drawcharglyph() as callback
  • mf_justify.c: mf_render_aligned()  calls render_left() (for example) with same drawcharglyph() as callback
  • mf_justify.crender_left() calls callback drawcharglyph() for each character, giving coordinates of character and character itself
  • gdisp.cdrawcharglyph() calls mf_render_character() giving drawcharline() as callback, with same arguments
  • mf_font.c: mf_render_character() calls font->render_character(), which is for example mf_bwfont_render_character()
  • mf_bwfont.c: mf_bwfont_render_character() calls render_char()
  • mf_bwfont.c: render_char() makes some magic inside, calling its callback drawcharline() from time to time
  • gdisp.cdrawcharline() takes out of its pocket current color we are using and paints a one line
  • many times, many lines, with blending or not
  • wow, we have painted one character

Am I right? Everything goes this way? To be honest, in place of compiler and MCU I would broke my brain in the middle of this chain. I will not ask why everything was done this way, but at least, have I understood all this right way?

Thanks you in advance!

Link to comment
Share on other sites

12 hours ago, king2 said:

1. mf_justify.cmf_render_aligned(). Rendering string with left align is simple, center alignment  is just left render with some shit by calculated coordinates, but right alignment is different, using different approach, drawing character from right to left, from end to start. Why not calculate shift as it was done for center alignment and use same routine?

It's not using the same rendering function because calculating the width of a string is very heavy operation. It takes a lot of time to complete as it is not a deterministic operation. The actual string width depends on many factors such as kerning. There's no way of quickly telling the width of a string beside actually rendering it. Therefore, directly rendering the characters to the display is a lot more efficient than first calculating the string width. However, that is simply not possible when center aligning something because you don't know at which position you have to start. With left and right alignment you know where to start. Having a dedicated function for right-alignment rendering allows to directly render to the display like with left-alignment too without the need to compute the width first.
Additionally having a separate function for rendering right-aligned strings would allow to render RTL (right-to-left) languages properly too without those calculation overheads.

 

12 hours ago, king2 said:

2. gdispGDrawString() and gdispGFillString() differs only by one line, why using two separate functions instead of one? Same with other *Box functions?

The main reason for this is to keep the API consistent. All GDISP calls have a DrawXxx() and a FillXxx() equivalent. No need to break the API for the string functions. We like it consistent.
Of course you could argue that one can have one function with a parameter that specifies that and then a wrapper function to save a little bit of code space but as mentioned in an earlier thread where this question showed up another reason for this is that some compilers have had very hard times working with conditional function pointers.

 

12 hours ago, king2 said:

3. How everything works (assume that everything is simple and we are not using word wrapping):

[...]

Am I right? Everything goes this way? To be honest, in place of compiler and MCU I would broke my brain in the middle of this chain. I will not ask why everything was done this way, but at least, have I understood all this right way?

That sounds about right :)
Font rendering is a heavy operation. That is why often pixmaps are used to render strings internally before stuffing them to the actual display. That drastically reduces flickering and increases performance a lot.

Link to comment
Share on other sites

15 minutes ago, Joel Bodenmann said:

The main reason for this is to keep the API consistent. All GDISP calls have a DrawXxx() and a FillXxx() equivalent. No need to break the API for the string functions. We like it consistent.
Of course you could argue that one can have one function with a parameter that specifies that and then a wrapper function to save a little bit of code space but as mentioned in an earlier thread where this question showed up another reason for this is that some compilers have had very hard times working with conditional function pointers.

But you can just use two #define with function with last parameter set to one or zero, for example (fill or not)!

Edited by king2
Link to comment
Share on other sites

I mentioned another reason for that: Some compilers that our users and customers are using have problems with conditional function pointers.

Also, there's more than just the callback function that changes. The init structure and so on is not the same as well. The function would basically be split in half internally. There's not much gain.

Link to comment
Share on other sites

#define is a function of preprocessor, so compiler will see just one function called with different parameters. Which conditional pointer you are referring to?

As I see from function definition, they uses almost same arguments, and differs by initialization of GDisplay structure and calling clip/fillarea only.

Maybe compiler will optimize parts of code, but I'm not sure.

But I'm sure that similar functions in code will make further development more complicated and add codesize overhead. Chained callback everywhere will make changes almost impossible not for authors of library :)

Link to comment
Share on other sites

In this case the differences in the init sequence and the differences in the routine alone were sufficient to make it worthwhile to have to separate routines. They may look similar but there are differences. Another cost for making them a common routine is the extra parameter. This extra parameter has effectively traded off RAM usage for code size. As you know RAM usage is far more important on embedded processors. Also, once you get beyond about 4 parameters on ARM platforms the cost of the parameters increases as they cause register definitions to be pushed onto the stack. This affects optimisation all the way done the function stack (which in this case is quite deep). There is also an extra conditional branch. While these are small overheads, they still make it combined routine slower and more expensive than the separate routines.

On top of that - the ugfx style is to have two different routines for Draw versus Fill. That therefore completed the argument. There is very little value in combining them into one routine as the amount of common code saved is not high enough to warrant the other costs.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...