#Rich Text - Uncreatively Named Text Handling Prototype Tool

1 messages · Page 1 of 1 (latest)

molten galleon
#

My little prototype project for figuring out the end-all text rendering and input support lib for integrating into my game engine. If you can type it I should be able to render it (and not segfault my engine when pressing backspace on a multi-unit codepoint like I do currently).
https://github.com/forenoonwatch/rich-text

verbal surge
#

I see many faces in that zalgo

old harbor
#

smiling ones too

molten galleon
#

one common side effect of text rendering is hallucinations

old harbor
#

heh

molten galleon
#

this might also be a good time to test whether my ICU cmake build actually works on msvc

old harbor
#

if you need testing on lunix you can hit me up

#

i use arch btw

verbal surge
#

When I’m old and I’ll have nothing better to do, I’ll read this:
https://en.wikipedia.org/wiki/Computers_and_Typesetting

Computers and Typesetting is a 5-volume set of books by Donald Knuth published in 1986 describing the TeX and Metafont systems for digital typography. Knuth's computers and typesetting project was the result of his frustration with the lack of decent software for the typesetting of mathematical and technical documents. The results of this proj...

#

It’s incredible how Knuth got most of it right, but then everyone forgor

molten galleon
#

luckily I have my laptop with kubuntu to start with

#

the funny thing is, I'm not even doing it "the hard way" and NIHing everything

#

I'm pretty much at the end of where "all inclusive" stops including all

old harbor
#

best way of doing

molten galleon
#

you'd think I would just be able to plug a stack of 3 industry standard libs (ICU, harfbuzz, freetype), and go along with my life

#

but it's just the beginning

verbal surge
#

Just embed Chromium in your app/game, ez

molten galleon
#

that's not good

#

oh, could this be related to the thing sean mentioned the other day about MSVC and utf8

verbal surge
#

Yeah, UTF-8 and MSVC don’t work that well. I just assume that it can only do ASCII and put UTF-8 in JSON/txt files

#

It’s really weird that they’re doing UTF-8 directly in their source files

molten galleon
#

well it is an official unicode org library

#

wow apparently unless you define this, windows.h #defines min and max

#

oh now I'm really in the tough stuff

#

looks like building the ICU data lib gives me error LNK2001: unresolved external symbol _DllMainCRTStartup

#

and all the stuff that interacts with icu-le-hb gives me inconsistent dll linkage, but I'm gonna call that the easy one because I bet I can figure out what define triggers that to line up nicely

molten galleon
#

oh boy almost got it building on MSVC

#

in other news, PSA to all you guys, if you don't wanna bloat your repos with raw font data, use file(DOWNLOAD), peep my fonts/CMakeLists.txt for that

#

ok awesome, MSVC build officially works, tomorrow I will fix linux

verbal surge
molten galleon
#

Alright, I am finally building on Linux, which means I can finally get back into the meat of it

#

So, in order to handle text input properly, the main problem I have to solve is building a TLAS (Text Layout Acceleration Structure), basically something that can let me quickly convert from a pixel pos to a text index, and from a text index back to a pixel pos

#

so far from my investigation into the wacky world of text, it seems like the granularity you need to express in the TLAS is by character break, and if your glyph count is < your character break count you have to just evenly subdivide the glyph bounds to get break positions. The remaining question there is what you'd do otherwise, when your glyph count is > than your character break count

#

the two things I'd probably test are the leading edge of the first glyph, and the min(leadingEdge) of all glyphs, and of course I say "leading edge" because it's reversed in RTL text

#

and I'm only thinking horizontal text atm, but for vertical text it should all be the same with the primary and secondary axis flipped

#

another interesting thing to note is that the sample algorithm ICU provides for paragraph layout decides that the best way to handle line height is by taking the max(lineHeight) and imposing it on all lines, so here where I render all lines as size 24 and line 2 as size 48, all lines now get way more spacing

#

I'm pretty sure roblox does the same thing from my tests, fancier engines like MS Word don't, I personally don't like it aesthetically but comes with the convenience of being able to very quickly solve for your secondary axis

#

actually it's absolutely not what they do

#

so I won't do that

#

and it seems the LineHeight value, which acts as a scalar on top of the computed line height, works relative to whatever the RichText computed for the font

#

so, line height = max(line lineHeight across font runs)

#

meaning I can't use a cheeky uniform (int)(mouseY / lineHeight) vertical lookup

#

minor loss

#

all fine in the name of pretty text

molten galleon
#

so, right now, just to be somewhat space efficient I am thinking of making the TLAS (and yes I am gonna continue unashamedly calling it that for lack of a better name) have 2 phases of lookup, a binary search to find the line, and then another binary search to find the X offset

#

when your cursor falls anywhere on the line it counts as being on that line, so you don't need the full text hitbox, but the thing I need to ponder is how to encode the horizontal data

#

in this example here 1 line is pictured as made of 3 text runs, and of course the middle one is arabic script so it's RTL, meaning in terms of string index, the low end of the index is on the right side of the arabic block

#

I guess keeping highlights in byte order is what causes this behavior, and obviously I can technically do better, but I kinda like this little text rendering easter egg that's literally everywhere

#

I guess you start to experience an interesting tradeoff there, and I need to think more to see how their implementation probably handles it

molten galleon
#

okay I think it was perhaps obvious, it's because highlight box rendering is completely disjoint from selection, and because your cursor is basically implemented as 2 indices, selectionStart and cursorPosition, so if you have:

AB[CD[EFGHI
  ^  ^

with DEF RTL as above, your byte layout is

AB[CFE]DGHI
  ^   ^

(selectionStart and cursorPosition marked)

#

so now you don't have to depend on render information about visual runs to highlight a contiguous block of text to do a non-visual operation (e.g. ctrl+x, delete, etc)

molten galleon
#

hmm been prematurely bikeshedding the TLAS idea, I think I need to store lookups for pixel indices by line -> by run -> by glyph so I can get an O(log n) lookup but then I was thinking... why?

#

I mean you do several O(n) passes on your text anyway as part of rendering it, and I'll deal with Big Text when I get there

#

plus when dealing with BigText you still need to do a layout pass at least once to figure out what portion fits in frame

molten galleon
#

ugh, just doing horizontal char advances and the logic is already stupidly convoluted

#

not to mention it doesn't even work right yet

old harbor
#

: )

#

famous words

#

"doesnt even work right yet"

molten galleon
#

apparently nvim secretly adds a newline when saving a file and doesn't want to show it

#

probably great to avoid that whole "file needs to end with a newline" thing with old C compilers

#

wow I messed up my logic so hard because I trusted the line counter in nvim instead of using n++

#

however I still need to fix

  • many-to-one char:glyph layouts
  • layout-generated line breaks as opposed to \n (require dealing with the affinity bit like in the article)
  • word break logic shouldn't actually rely on the ICU wordbreak iterator, but that's unrelated to cursor positioning
molten galleon
#

after this little study (n = 3), it seems that affinity is generally a mouse click only thing, and otherwise advancing is doing by the cbrk iterator as normal

#

but you can clearly tell that you're on the same character position with a different bit affinity based on your mouse cursor

#

and I can tell the reason for this choice is entirely because otherwise you would create a dependency between your visual layout info and your text, whereas doing it this way means you only depend on the content of your text to determine the next cursor position, except with your mouse which is inextricably tied to doing a visual query

molten galleon
#

and this covers many chars to one glyph, in LTR and RTL, the first code so far that I have to change algorithms explicitly due to direction because of the format icu::LayoutEngine gives glyph data, which is a problem for another day. First 2 are Segoe UI الله, second is ffi, which it seems notepad a) ligaturizes and b) splits exactly like I expected

verbal surge
molten galleon
#

interesting

#

study #2: word break iteration, and a closer look at boundaries of bidi text runs. Here's where I'm actually starting to get divergent behavior between my samples:

  • Word jumps are always to the beginning of a word (in byte order). Forward goes to the beginning of the next word, back goes to the beginning of the previous word. Easy enough and onsistent among all 3.
  • Words are blocks of non-whitespace characters, word boundaries are just found by checking state transitions of whitespce<->non-whitespace and not using a word brk iterator.
  • Chromium (#2) is the only engine of the 3 that doesn't respect the non-breaking space in buffi&nbsp;ng. I am currently of the opinion this is incorrect.
  • Notepad (#1) and Chromium place the cursor at the end of the buffi ng run for the start of the arabic run, while Roblox (#3) places it at the beginning (right side) of the arabic run. I don't think there's a right answer here but it's an interesting divergence.
#

bonus sample, VS code, very much the black sheep, forward jump goes to the end of the next word, backwards jump goes to the beginning of the previous word. NBSP compliant. Interesting behavior, I will have to keep it in mind for when feature creep keeps me working on a text editor for the next 5 years

molten galleon
#

study #3: Home/End, Ctrl+Home/Ctrl+End

  • Ctrl+Home and Ctrl+End are really simple, just jump to char 0 or end of string. Nothing much to say about it
  • Home/End keep you on the current line and don't always drive you towards the start/end of the string like word jumping. Simple enough
  • Home/End respects soft line breaks (confirmed in all 3 cases), meaning that despite its simplicitly a Home/End jump is actually a post-layout query
#

speaking of which, despite pango seeming like the de facto lib to use for a text stack, it has 0 documentation anywhere on input support, same with cairo or graphite or anything of that ilk

molten galleon
livid patrol
molten galleon
#

I make sure to turn my audio off for these so I don't have to re-record them when I finally put together an article

#

although I might have to crunch them down to gifs so I don't accidentally kill my github storage limit

#

study #4: Vertical Text Movement

  • Like mentioned in The Article, vertical text positioning is a pixel-position-based operation
  • All 3 samples show similar general behavior: The final cursor position is moved towards the closest character boundary, whether that's in front or behind the current cursor position
  • Chromium is a little different in that it clearly caches your horizontal position upon a vertical move, and always picks the closest position on each line to this initial cached position. You can see how it differs in the l-a test between the 3. The notepad sample cascades forwards as you move up and down because the cursor always ends up closer to the start of the next character, the Roblox sample does the opposite, likely due to the different font (Source Sans Pro), while Chromium shows no signs of cursor cascading.
livid patrol
#

Can you render Egyptian Hieroglyph D053

old harbor
#

aka the man-in-the-wall-symbol most likely

livid patrol
#

𓀓

old harbor
#

yeah exactly what i thunk

#

a man climbing out of a wall

molten galleon
#

yeah I just have to map the font

#

speaking of which, this is the best tool ever for figuring out fallback schemes if I didn't already post this

old harbor
#

thats the browser dev tools neh?

molten galleon
#

yeah

#

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts
then navigate to here to find the file

#

then I just add it to my generic fallback faces

#

just paste it in my sample.txt (looks tiny in n++)

#

and viola

old harbor
#

noice

molten galleon
#

bonus showing off my cursor nav

livid patrol
#

sorry, but that's not Egyptian Hieroglyph D053. I didn't want to spoil it

molten galleon
#

ah you're right, it's a016

#

looks like Segoe UI Historic doesn't map it

#

let's see if there's a noto hieroglyph

#

💀

#

why did microsoft omit this hieroglyph frog_think

old harbor
#

its on the index

molten galleon
#

the valve index?

old harbor
#

: )

#

the bad things index aka list

molten galleon
#

I wonder if this hieroglyph was on the rosetta stone

old harbor
#

it might have been a dick move 😛

molten galleon
#

wow people make writing articles look easy

#

I've been spending a few days getting all my notes into article form and I'm still barely up to where my code is at

old harbor
#

it takes time

molten galleon
#

btw good article

old harbor
#

i always read graphmemes when i see graphemes

molten galleon
livid patrol
#

Really cool how you go into details and investigating how each editor handles various "edge cases"

molten galleon
#

it's pure paranoia tbh, it all comes from just wanting to make it work right once lol

#

and of course my impression is that there's clearly a de facto "standard" that you'd expect out of a text editor, subconsciously

#

but no one has ever documented it

#

except maybe john chromium and john win32 textbox

livid patrol
#

It shows that you have attention for detail

#

It's a big part of what makes or breaks a text editing experience

molten galleon
#

Hmm rereading this I realize that my word break checks are nowhere near rigorous enough actually, I haven't tested non-alphabetical characters without whitespace. My dissertation is in shambles

#

also writing this made me realize that I need to change my font family file schema, and that I still don't have a good answer for affinity

molten galleon
#

today we're gonna talk about implementing mouse selection, but first we're gonna have to talk about parallel universes

verbal surge
molten galleon
#

just 2 sections left of the blog post and I'll have the preliminary research done (and I'll just have to implement everything to match)

#

also noticing in this shitty .NET banking program that some text boxes don't let you CTRL+A so I can't fast-clear them

#

also my work PC and secondary monitor are different resolution and DPI, I now understand why people put so much effort to do DPI-aware scaling because guess what it also doesn't do correctly

#

but that's a story for another day

old harbor
#

ctrl+a being disabled could mean nobody wants you to extract (copy paste) from there or paste into

#

could be a sensitive field, password box or there is a need for users to enter manually every time because of various reasons

#

dpi scaling is also a pain in the ass, because there are like 3 or 4 modes and it depends on what OS and version you are

molten galleon
#

it's the filter box for table/column names

old harbor
#

ah

#

custom wndproc 😛 and ctrl+a simply not handled, is that a component set from ComponentOne/Telerik/TheOtherThirdBigCompetitor?

molten galleon
#

hard to say but it's shitty corporate software

#

the worst thing by far is that they disallow you from modifying the databases that back the program (even though you can see them) because it violates their service agreement or whatever, however they don't utilize this assumption at all for performance i.e. for caching, and everything falls through to who knows how many queries to do basic shit

#

and in the new versions they made the latency worse because they queue basic operations into the scheduler of the HPC cluster instead of doing it on the app server

#

which picks up jobs like every 30-60 seconds

#

and now here I am, sunday morning 9am with 2 other guys from my team painstakingly making manual edits through the shittiest UI known to man

old harbor
#

the fick

#

why on a sunday

#

are you on some oncall shit?

molten galleon
#

just how the crunch goes, it takes so long and we have to get it done before the end of the month because the system is used to do monthly reporting to the government

#

banking in a nutshell

#

we have like ~1 week window per month where analysts aren't using the system

#

so we have to crunch every second

molten galleon
#

and what would be a 5 minute SQL script run and test is a 3 man, week-long death march

old harbor
#

: (

#

i hope you get at least well compensated for this weekend bs

old harbor
molten galleon
#

nice, this guy is going pretty hard

#

however the mark of the beast

old harbor
#

: D

old harbor
#

i wonder what ui thing that is

#

seems to be a highly customized dear imgui

molten galleon
#

damn not bad

old harbor
#

with some sort of layouting engine around it and event/signals ala Qt

verbal surge
old harbor
molten galleon
#

yeah its got a tiny amount of schizo particles

#

but that's part of the fun of engine dev I guess

verbal surge
#

Most sane "my own GUI" developer

molten galleon
#

so true

old harbor
#

but he hired a cute chic in front of some cheap ass green screen setup to convey the sales pitch

molten galleon
#

thank god I took the time to write my sharticle and document all these dumb details, otherwise I would've forgotten everything by now

#

I've been converting the data model for paragraph layout info to my own instead of coping with ICU's weird ass decisions

#

for some reason ICU opts to reverse the in memory order of RTL text indices, which literally helps nothing

#

every algorithm is only made worse by it

#

and I had to basically branch to 2 different impls for LTR or RTL

molten galleon
#

also intra-cluster positioning is a lot more complex than it initially seemed when I trusted ICU's data, ICU's LayoutEngine gives you 1 glyph index per Code Unit (as in per u16 value, not per codepoint), with unmatched mappings being tombstoned with 0xFFFF

#

and this technically almost works out because ICU forces you to deal with UTF16 and none of my test data (Arabic and Latin) is multi-CU in UTF16, but arabic is 2CU in UTF8

old harbor
#

its quite fascinating how complex fonts and icuisms actually are

molten galleon
#

it's how complex writing is

old harbor
#

and that i know 0 about it, besides characters in fonts have a bunch of properties

#

hehe

molten galleon
#

because ancient people didn't consult the unicode org before putting symbols on bones and clay tablets

old harbor
#

they should have!

#

i wonder how weird all this will be in another 2k years

molten galleon
#

yeah I wonder, the digital age has already changed typography pretty significantly

#

things like dictionaries and Unicode and digital fonts definitely do a lot to slow the evolution of written language

old harbor
#

i think you can do PhDs about this stuff

molten galleon
#

finally got all the different cursor navigation types working, including positional affinity (picking a side when a position in a string represents 2 distinct visual positions)

#

definitely the majority of the complexity

#

but this time it's using my own layout data model so it shouldn't need any further changes when I convert to UTF-8, which is the next step

old harbor
#

fonts and icu in general must be a pain in the ass, i admire your work : )

molten galleon
#

trust me they are lmao

#

what I'm about to do is probably the most sketchy and error prone part

old harbor
#

i see that on a regular basis when you edit shit in any editor

molten galleon
#

which is implementing some of ICU's forced UTF-16 algorithms to work in UTF8 and making sure I don't regress

old harbor
#

sometimes ursors go left, sometimes not, somtimes it works majeekally sometimes it does not at all

#

as if there is no consistent rule somehow

#

or a lot of western software vendors dont give a fuck about RTL

molten galleon
#

what's funny is that you can tell how much VS Code in particular just did their own thing

#

I strongly suspect they don't have a proper unicode dataset

old harbor
#

heh

molten galleon
#

but they have interesting decisions that fit only with a code editor vs. a general text editor

old harbor
#

isnt MS part of the consortium?

molten galleon
#

yeah but I don't think that trickled down to VSC

old harbor
#

yeah

molten galleon
#

they have some very clearly NIH editor decisions

old harbor
#

tbh,it wouldnt surprise me that they data they collect via telemetry just told them that no many RTL isms are happening really, even if its used in major RTL countries

#

and therefore it has no high prio to get it right

molten galleon
#

but some are interesting, like instead of using the cursor pixel pos for vertical navigation they use the cursor index in the line

#

before I read about it/tested it I actually thought that's what most programs did

old harbor
#

ah

molten galleon
#

but I can see why they'd pick it as a code editor feature since code is way more structured by char index

#

but for a 'normal' text editor you use the pixel pos because A) it 'feels' better for the user and B) it's compatible with different line justification

#

like if your lines are all centered in the text box

#

and yeah I think for code editors in general international text is a lower priority

old harbor
#

yeah usually text is also monospaced, i would assume

#

besides the 3 excentric people who use some handwrittenstyle font

molten galleon
#

in VSC I couldn't tell if it was, it didn't seem monospaced

#

but notepad++ is super monospaced

#

and the arabic text looks super compressed

old harbor
#

ah interesting

molten galleon
#

one thing I haven't bothered with yet (since it's a huge optimization rabbit hole I'm nowhere close to tackling) is fast paths for huge text

old harbor
#

perhaps people usually code in latin, and only comment in CJK/arabic/nonlatin

molten galleon
#

monospaced text is convenient because you can use simple scans on the string to roughly figure out what you need to do more expensive computations on

old harbor
#

hmm perhaps you could open an issue in vscode and ask how they did or what their train of thought is/was wrt to all this, as a question

molten galleon
#

yeah, some of the things I looked into are definitely open source and I could dig into that but also huge and fairly annoying to read

#

like chromium

#

there's also stuff I didn't even consider, like gtk or libreoffice probably both have huge discussion chains about this stuff

#

but they're probably deep in mailing list hell

old harbor
#

i can imagine

molten galleon
#

oh my god the ICU bidirectional iterator test code is 5000 lines long

old harbor
#

sheesh hehe

#

but +1 for tests

molten galleon
#

no way I can port this crap without tests

molten galleon
#

"Java programmer was here"
"how do you know?"

#

hmmm, time to strategerize

#

currently porting the BiDi and script run finder tests to Catch2 so I don't have to mess with putting together a CMakeLists for ICU's ctestfw

#

originally my plan was to take in the uscript and ubidi code as my own and port it to UTF-8 as part of my own lib, but considering how complex they both are, and considering the complexity of updating ICU versions from there on, I should probably do the smart thing and just do the minimum possible work to port them from using a UChar* string to using UText like the rest of their not-awful API does

#

that probably has the highest chance of being accepted as a PR as well, although that's not strictly a priority since I already have a somewhat altered fork I can't upstream in whole

#

I gave their existing PRs a quick look and unfortunately no one has submitted that yet

molten galleon
#

tfw some of the BiDi tests actually fail but ICU doesn't emit them properly as failures and just sets some random bool for verbose logging

#

ended up building ctestfw after all

#

maybe it's because I'm using cintltest and not intltest

#

yeah awesome, one test case fails, no one catches it

#

thanks ICU

molten galleon
#

hmm okay I understand why they never ported it to UText, the implementation is really tied to u16, I might have to go with my original option and just maintain my own secondary set of ubidi_ functions that are implemented as UTF-8 at best

#

man I'm really in for it now

#

at least they left some hints

#

you can also tell this is super old (minus the fact the file create date is listed as august 6 1999) because they list "any code point always needs the same number of code units" as an invariant of UTF-16, which was an assumption made back in the day before they realized they were going to need > 65536 codepoints

#

though that 2nd assumption is probably the most annoying thing about UTF-16 from the perspective of porting it to UTF-8, so many 'important' characters fit into 1 CU in UTF-16 that these algorithms are implemented without decoding the codepoints as they iterate

old harbor
#

: (

#

before yielding to insanity, perhaps stop and make the stuff you have so far work better/faster/nicer/ provide better tooling or wrap it in a library of sorts

livid patrol
#

deccer wants to steal your work domeboy

#

||jk hehe||

molten galleon
#

there's basically enough to go off of so far

#

it's just UTF-16 based

#

which means any interaction with the string has to be describe in U16 code unit indices

#

meaning rich text/formatting, cursor positioning, even if the rest of your app is in UTF-8

#

I just have to do a 1:1 conversion of the script run and bidi run algorithms to UTF-8 and basically drop them in where the originals used to be

#

and everything will be perfect

#

though the bidi algorithm is probably one of, if not the most complex parts of ICU

molten galleon
#

I've just finished adding a copy of the paragraph layout builder that doesn't depend on icu-le-hb/layoutex and populates the layout info by invoking harfbuzz and ICU directly, still UTF-16 though, but at least it's written in a way that I can hopefully just replace references to UTF-16 with UTF-8 and everything will magically work

#

I need to really creep up on the UTF-8 thing because porting the bidi/script algorithms will invariably suck

#

but at least I'm slowly building up a good test suite

#

the next step is probably to make "emulated" UTF-8 which uses the UTF-16 functions but just reconciles all the character indices after, which will give me a good set of reference data

#

but ultimately that impl is not a good stopping point to get UTF-8 compatibility because both UTF-16 and UTF-8 have indeterminate string index to codepoint mappings so I'll have to do a ton of linear scans through the src and dst strings to fix up the places I use char indices (which are a lot)

#

also, as far as FOSS implementations of the Unicode Bidirectional Algorithm go, there are only 2 I can find period, ICU's which is in UTF-16, and GNU fribidi, which is UTF-32, neither of which are any more useful than the other because text editing is like the one use case where the implementation encoding actually matters

#

but fribidi is LGPL so it's not "avoid looking at" tier, at least

#

also it's worth nothing I'd more or less be done if I just coped and used UTF-16 as my text editing format, as I assume most applications do

molten galleon
#

almost done with encoding nonsense, I have a build_paragraph_layout_utf8 that is almost free of linear scans for encoding fixups, just have to deal with the bidi algorithm finally...

#

porting the ICU script run iterator took all of 5 minutes and even helped me save a completely useless malloc it does

#

the bidi iterator will not be so kind

molten galleon
#

at about where I expected, not so bad overall, gonna have to start making calls about policy and being careful about data from this point though

#

all that's left to convert are things like this where it expects to read a char16_t and check if it's a bidi control char, which would be a 1-CU value in UTF-16 but not so in UTF-8, so in places like these I'm gonna have to carefully convert the whole algorithm to walk by codepoint

#

and make sure that everything that uses the visual/logical maps also walks the mapping by codepoint

#

this w3 article is really good, worth the read

old harbor
#

earlier today, i was tempted to start yet another project : (

#

an opengl based text editor : )

#

with code selection and all that shit obviously

#

but, then i had to think about what it means and what rats tail it all is hehe, thats when i remberd icu and DR and his fight-the-icu-isms

molten galleon
#

yep

#

but it's soon getting to a happy place

#

this is like the worst part by far

#

...actually there's a ton more

#

this is one of those "oh well this is a tight project I can finish and move on to other stuff" occasions

#

I thought I'd just be writing some glue code between libs

#

but the more you look the more things you can do/should do/want to do with text

#

there's the editing logic, the layout logic (what I'm dealing with now), I still need to take another pass at formatting, another pass at rendering, memory management, threading, etc etc

old harbor
#

i got rid of that inception of mine, i can barely code any simple shit

molten galleon
#

stuff like threading and memory management can get super out of hand too because your optimization model basically depends on what kind of text you're dealing with

#

e.g. for something like a game, or a program like excel, you have lots of short strings you run layout on

#

but stuff like winword or an IDE have more or less a bigass string you need to process in a completely different way

old harbor
#

yeah

#

i can imagine

#

i was also thinking about windows the os, earlier this morning

#

about how it handles all the controls and shit, since those are all handles... and each of them can receive messages

#

even "labels" to represent text

#

its quite unrelated to text rendering itself or icu in any way

molten galleon
#

newer versions of windows actually do ship using ICU lol

old harbor
#

but i was also wondering how the os processes all of these things... moving the mouse must triger som "foreach (handle : visiblehandles) { sendmousemessage(handle);"

#

hehe

#

i think there were even bug reports about the icu parts windows shipped in the past years

#

and people exploiting it

molten galleon
#

yeah I definitely think they have all sorts of acceleration structures to filter events spatially and by what's focused though

old harbor
#

they have it for paint events too at least

#

still crazy how fluid it all works

#

even with 150k, 200k handles flying around

molten galleon
#

I think that's been around since the earliest versions of windows, back when painting was super expensive and they had to be careful about overdraw for simple window rects

old harbor
#

yeah

#

since win 1.0

#

if you ignore all the isms windows has/had/willhave, its actually quite a cool system

#

that messaging thing

molten galleon
#

yeah

molten galleon
#

I have started to feel my hubris of lazy copy-paste porting the ICU bidi algorithm...

#

though I think this lib https://github.com/Tehreer/SheenBidi might be the new area of investigation, and I think I really need to start building good benchmarks, since the point of contention with the ICU algorithm is: "if I am working with natively UTF-8 data, is it faster and/or less memory intensive to work with a UTF-8 native implementation of the BiDi algorithm, rather than allocate and fill a buffer with a different encoding, and fix up indices after"

#

though I would have to figure out if SheenBidi is :

  • any faster or less memory intensive to begin with
  • actually has non-naive handling of UTF-8 to begin with (e.g. it doesn't just pack a buffer with its own UTF-16/32 crap before running the algo)
  • actually up to date with the standard without modification, ICU UBiDi has some generated data tables inside it and I might have to see if I need to feed them through. Since this is disconnected from the unicode standard and the unicode standard is a live spec, basically any decision I make is gonna require continuous fixup
old harbor
#

i admire your dedication

#

font/unicodeisms would bore me to death

molten galleon
#

I'm trying to weasel my way through it, this is an investigation into how, really

#

perpetual search for "why doesn't a library exist that just does [x]"

old harbor
#

: )

#

are you going to write about the findings too?

molten galleon
#

yeah its worth noting

#

though at some level it's a sort of triumphant "this is how I figured out how to avoid learning how to implement the bidi algorithm myself in the most optimal way possible"

old harbor
#

hehe

molten galleon
#

though right now it's also not just "can I find a library to do x for me" it's "can I find a library to do x for me with as little waste as possible" because everything to do with ICU does so many extra conversions, allocations, data copies/shuffles, etc etc

old harbor
#

and build up some sort of modular framework perhaps?

molten galleon
#

yeah that's the goal

#

though realistically if you want it to be The Best™️ for your use case you'd have to fork it and tear it to shreds

#

making my lib stack not wasteful is the first half of the puzzle, the other half is making it multithreaded and whittling down its execution time further

old harbor
#

its visual editorisms reminded me of ugly uis and of your taste of ugly uis 😄

#

the editor is WinForms

molten galleon
#

unfortunately his webpage is modern

old harbor
#

: )

molten galleon
#

hmm I need to build some good test data

#

luckily the ICU tests are full of nice little strings to copy paste to start with, but to get a good profiling range I need to build some test data in various sizes, since the test strings in ICU are generally classified as "tiny"

#

and from the shitty preliminary benchmarking and profiling I've been doing, in the "tiny" case, basically nothing even registers on the clock except the allocations, which is exactly what I expected to start off with

#

since my main angle of attack was just to find whatever ways to ellide/remove allocations

#

I think the easiest way is to just randomly combine the little test strings into a really long string and then test views into it of different lengths

#

I think if I randomly assemble a string up to 16MiB that should be sufficient

#

and run profiles/unit tests on powers of 2 or 4 starting from somewhere around 64-256 bytes

#

I also need to figure out if Catch2 or Google Benchmark let you name benchmarks at runtime, I've only ever seen them use string literals like

BENCHMARK("Bench Name") { do_thing(); };

so I might have to paste each test bucket manually

molten galleon
#

already not passing basic equivalency tests on basic strings... fuck fuck fuck

#

the silver lining is that unlike ICU, sheenbidi seems a lot simpler to fork and mess with

#

although it's quickly turning from bold and brash to belongs in the trash

molten galleon
#

gonna really have to think about this one

#

I'm slowly starting to understand the UBA though

molten galleon
#

Okay I think I've somewhat triaged the extents of the differences:

  • the UBA Paragraph parsing rules are implemented identically in both, SheenBidi actually does them a little better because it gives me the separator offsets which I need for layout later
  • Calculating logical level runs is a pretty simple operation to do (although sheenbidi doesn't provide it directly), and it seems to work out identically if I memcpy ICU's levels over SheenBidi's
  • Level gen is almost identical between both, actually, with 2 differences: ICU has some kind of post-processing called adjustWSLevels which performs some level recalc for non-graphic characters, for these test strings it causes the majority of the level diffs. The other difference is that ICU seems to have a fastpath to early out for monodirectional runs which causes some suboptimal levelgen in SheenBidi but the directionality stays correct
  • Visual run gen is different in ICU and SheenBidi even using the same level data, I haven't been able to find a root cause for this yet though
molten galleon
#

hmmm this honestly surprised me, taking a break from figuring out how to reconcile correctness and looking purely at speed, ICU UBiDi is actually significantly slower across the board

#

to the point I actually had to tone down my test string max size from 16MiB to 1MiB in google benchmark, and all the way down to 16KiB in catch2

#

in fact I had to get google benchmark because I thought I broke catch2 because I thought it got stuck somewhere

molten galleon
#
  • Hypothesis 1: The reason for the disparity is because UBiDi calculates across paragraph boundaries, but SheenBidi works per paragraph. Need to check this
  • Hypothesis 2: Some of the visual run transpositions are pretty far, I might have to completely redo my editor memory model since I've only been testing a max run level of 1 bleakekw
molten galleon
#

Hypothesis 1: somewhat correct?

#

removing paragraph separators cut ICU execution time in half but minimally affected sheen

#

but ICU is still an order of magnitude slower

#

Things left to investigate

  • Allocation size/frequency, although it's starting to matter less considering just how much sheen ran laps around ICU
  • How much should I care about the extent sheen generates differences from ICU
  • How much of a pessimization will I suffer from attempting to make sheen conformant with ICU's tests
molten galleon
#

top: chromium, bottom: notepad, same file

#

I dumped ICU's test case strings

#

they don't even render them the same order bleakekw

#

in notepad it even messed up the alignment and rendered the dots outside the margin

#

I am going to take the blue pill, use sheenbidi, and pretend all of this was a dream

molten galleon
molten galleon
#

so minor losses in my clean "anything editor related can be bounded in O(log strlen)" assumption, and my assumption I wouldn't have to edit the datamodel again... but slowly returning basic functionality

molten galleon
#

I definitely underestimated bidirectional text

molten galleon
#

okay very important point, I've found a minimal example that you can shake up a line such that it does not end on its highest logical interval

molten galleon
old harbor
#

sweet : )

#

how long do the tests run?

molten galleon
#

pretty much instantly, it's not a perf test or a particularly exhaustive test

#

it's just a comparison test to ICU's LayoutEx layout generator vs mine

#

couple of mixed run strings to warm it up and then one of the bidi test strings

#

it just has a lot of assertions because I make sure the lines, runs, char positions, char indices, glyphs, etc all match perfectly

#

and this is only the UTF-16:reference test, still have to fix the UTF-8 one back up

#

then it's ready for porting to sheenbidi

#

and then I am finally, finally done with layout logic and encodings

old harbor
#

Cool, and yea, unit tests are supposed to finish instantly, or as kwik as possible at least

molten galleon
#

woah

#

gcc debugs your bidi control chars

#

although I doubt this is wrong because I pulled this test string straight out of the unicode spec

#

actually nvm I removed text7

molten galleon
#

and just like that, the layout builder is now fully UTF-8 native, no more converting to UTF-16 and back

old harbor
#

noice

old harbor
molten galleon
#

don't think so, it looks very mobile/webby though

old harbor
#

i need to see what it actually is too, and only saw "c++" tbh, i ignored rust/js

#

ah it has a "Pricing" thing on their website :3

molten galleon
#

grabbing my little string of death

#

lays out fine

#

I love all the little things you can see happening

#
  • the negated text color not matching the highlighted region
  • a brave (and ill informed) attempt at visual-order highlighting
  • layout being applied separately in the highlighted region, causing a dynamic reorder
#

lmao

#

I feel vindicated in my struggle

#

I started typing ffi, which ligaturizes in a lot of fonts

#

it shits the bed

#

you can't sub-select, the cursor jumps around as I type

#

can't believe this guy is trying to charge for this

#

though, what this at least tells me is that this guy isn't just transpiling to web controls and this is a fully NIH UI solution that's running in the browser

#

⁉️

old harbor
#

haha

molten galleon
#

interestingly though this is one of the things I'm looking at right now

#

rendering highlighting I mean

old harbor
#

looks proper

molten galleon
#

that's notepad

old harbor
#

if you ask my uninitiated ass

#

ah

molten galleon
#

this image just shows off the main things to think about

#

this is 1 glyph, so the visual effect of highlighting needs to be split

old harbor
#

im sure you will never be able to use anything but your own stuff

#

with all the background knowledge you have with that font and bidi and IME stuff

#

everything will be shit sort of hehe

molten galleon
#

OSes and browsers do just fine, generally

old harbor
#

yea i mean custom libs

molten galleon
#

only things where people actually went and double checked I guess

old harbor
#

did you check out RmlUI yet?

#

i also kinda wished the ui factorio based their ui on was not dead

molten galleon
#

I haven't played with rmlui but the idea seems solid at least, just from looking at it

old harbor
#

oki

#

perhaps ill just forget about that slint thing, didnt look like it has many controls anyway

molten galleon
#

their focus is weird

#

a custom UI for web is inherently kinda sus because fundamentally web has the best UI, pretty much

#

using HTML/CSS as the interface isn't always the most amazing, but there's also plenty of tooling to make it not suck

#

and that's what RmlUI gets right

old harbor
#

i see

#

im not a fan of css at all, because of its stupid semantics - naming of those mostly

#

the box model is also unintuitive, instead of top left right bottom they have it other way around and stuff like that

#

the whole flexbox bs 😄

molten galleon
#

yeah, it's definitely not great but I think it's the best decision for "UI solutions you expect people to actually use"

#

my actual UI design "ideology" is based on roblox primarily because that's what I'm familiar with and what I'm used to working against, but I don't actually like designing UI so I just need the familiarity for it to suck less

left junco
#

I've barely used it but among the real time graphics GUI libraries it seems like the most likely to be usable out of the box for more complicated arbitrary UI

#

RmlUI

molten galleon
#

other than text, UI is really just a bunch of image rects and I think it's not too crazy to figure out how you want to do stuff

#

for bigger outfits how you want to do stuff usually means "have something compatible with a webdev's tool stack so you can hire web devs and ignore this inane crap"

left junco
#

Yeah it depends on how high level you need

old harbor
#

yeah its rectangles with textures, an arrange (to measure all the controls) pass and a layout pass

left junco
#

It's creating and arranging those rectangles that's the hard part lol

old harbor
#

o make them look neat, yeah 😄

molten galleon
#

yeah, at least that has its limits of complexity and it mostly doesn't impact the user when it's suboptimal

#

text is the exception because as soon as you have a text box you have some smartass like me pasting magic runes trying to break it

old harbor
#

X_X

molten galleon
#

I think you might be in the perfect market for this demongod because you're making a multiplayer game

old harbor
#

if i can detect that you are running my stuff, ill just replace all text input controls with labels

molten galleon
#

which is really one of the main reasons a game might start to intake arbitrary strings of non-ascii

left junco
#

Yeah true hmm

old harbor
#

epecially when Shooting Range Simulator expands into the asian market 😄

left junco
#

Right now I support ASCII only lol

old harbor
#

typical american move 😄

molten galleon
#

you'll want well formatted and HD rendered arabic for scenario immersion

old harbor
#

im sure you meant iso 8859-1

molten galleon
#

windows codepage 1252

left junco
#

Haha

old harbor
#

in UTF32-BE

left junco
#

I'll add it to the extremely long list of things I should add someday

#

But legitimately not a bad idea

molten galleon
#

your chat message was dropped because you forgot to send a BOM at the start

old harbor
#

hey its on the list at least

molten galleon
#

it basically fits the exact problem I'm trying to solve, low friction plug n play solution for arbitrary text to just work in a game engine

#

without imposing UI on you

left junco
#

I wouldn't mind having the ability to use text as textures, like so that a sign can just render text rather than needing an ultra HD image on disk

molten galleon
#

the thing that you expect you should get from just adding freetype

left junco
#

Yeah

molten galleon
#

or just freetype+harfbuzz, or just freetype+harfbuzz+icu

#

and so on

old harbor
#

instead its just libDR now

molten galleon
#

I've found that msdf is extremely suboptimal

left junco
#

Which one does star citizen use

#

My friend showed me that game and I was pretty impressed by the text

molten galleon
#

that's a very deccer question lol

#

if it's just the 2D text they could just be software rasterizing (e.g. freetype) at the desired size

#

3D text is a bit more challenging because you can't just get good fidelity out of 1 baked size

left junco
#

It's like street signs and stuff

#

They look very crisp up close

molten galleon
#

and since SC is a modern game I wouldn't be surprised if they just do that, in tehir own way

#

in fact SDF text seems like a bit of a weird hack for the modern day since you should have plenty of computational power to rasterize glyphs in realtime

molten galleon
#

I wonder what the easiest way to do this is though, all this is doing is rendering the highlight color and rendering the subregion inverted, I don't think I can do this with changing blend op because it still needs to mix alpha

#

I think I might just emit these as separate rects

#

it's also interesting a really minor detail with notepad/the windows OS text box is that the cursor is double inverted

#

it's 1 - the blue, and inverts the text back to black

old harbor
#

isnt it one_src minus 1?

#

err other way around

molten galleon
#

yeah but I think I'd mess up the alpha mixing of the font

#

it's 1 - dst color

#

if you set it up as a blend op

#

but then it would operate on the post-blended alpha

old harbor
#

i never got blend modes right the first time

molten galleon
#

I just remember dst = what's already there

molten galleon
#

works perfectly, no color inversion yet though

old harbor
#

looks great already

molten galleon
#

fixed my rendering for text formatting to properly use interval data, so now underlines and strikeouts are continuous

old harbor
#

sweet : )

#

outline looks neat too

molten galleon
#

just need to combine this with color inversion from highlighting and that should be it for most of the user state machine driven render crap

#

though I still need to implement text formatting features that revolve around glyph substitutions

#

like synthetic bold/italic, or smallcaps

old harbor
#

are you going to cook all that into some sort of libDR?

molten galleon
#

yeah, I still haven't sorted out how the final API should look but the framework is generally there

old harbor
#

noice

#

perhaps as a bonus you can provide some dearimgui integration 😄

molten galleon
#

I'll have to see how easy it is to plug into imgui (I doubt it's too hard tbh)

old harbor
#

imgui will basically just be the client sending in character data, right?

#

"abc"

molten galleon
#

yeah, it's more about sending back the result rects or whatever

old harbor
#

and your middleware will know how to cook the font atlas, and then imgui will spit out quads again

#

yeah

molten galleon
#

I assume there's some function somewhere in imgui that's its internal layoutText() or drawText() or something

#

and I just have to delete it and drop in calls to my lib

old harbor
#

: )

#

lets dig, once you think your stuff is ready

#

over a weekend or so

molten galleon
#

yeah, I think I'm getting closer to my goal

old harbor
#

probably have to think about/invent some ImGui::PushStyle-esque-isms to handle various text formatting things then

molten galleon
#

I just need to

  • wrap up highlight rendering
  • add text addition/deletion logic and clipboard support
  • deal with glyph substitution related stuff (which I've learned includes rendering tabs as well)
molten galleon
#

I have simple XML style markup tags

old harbor
#

ah i see

molten galleon
old harbor
#

so some embedded "css" basically, which needs to be interpreted somehow, and turned into style instructures per run, which are sent to imgui somehow to tell your middleware

molten galleon
#

this is what the above text looks like when I click on it

#

yeah, CSS is actually so much worse for this

#

I have some equivalent CSS for some of these formats since it's basically the only other place I can test formatted text

old harbor
#

css is probably the wrong word, it looks like xml 🙂

molten galleon
#

and yeah right now I just provide a function to generate runs from inline formatting

old harbor
#

ah nice

molten galleon
#

but that's something you can generate yourself

#

for example, from code formatting rules or even a LSP

old harbor
#

im sure we can cook something up then

molten galleon
#

yeah definitely

molten galleon
#

in chromium, only the actual glyph colors are inverted and not the strokes or underlines/strikeouts

#

they also have a second set of washed out colors for when focus is lost

#

shall I use this excuse to be lazy?

#

not inverting the strikeout and underlines is definitely kinda crusty and I don't wanna copy that

#

but the stroke can clip outside of the bounds of the highlight box so I'll just conveniently ignore that

old harbor
#

illallowit.gif

molten galleon
#

super satisfying when it just works

molten galleon
#

also small research dump:
https://developer.mozilla.org/en-US/docs/Web/CSS/font-synthesis
CSS provides the ability to toggle 4 different features of font synthesis: weight (think boldness), style (italic), smallcaps, and position (meaning subscript and superscript)
https://github.com/RazrFalcon/resvg/issues/297#issuecomment-667674132
I found this random github issue that directly quotes some code out of Blender for how they call freetype to synthesize bold and italic. I also checked inside MuPDF and they do it pretty much the same way, luckily it seems that for bold and italic you just need to create a subfont of the main font and mess with its master settings. The harfbuzz API has the functions hb_font_set_synthetic_bold and hb_font_set_synthetic_slant as well, I'll have to read the source code on those and see what they do, the implication is that you need to always set them if you're putting together a synthetic font to let the shaper know about the changes, but idk if that's necessary with a hb_ft font.

When it comes to smallcaps, looking in MuPDF I found that they seem to invoke that by passing a HB_TAG('s','m','c','p') tag to the shaper, I believe smcp is an OpenType tag that makes it select a different charmap for looking up smallcaps glyphs in a font assuming you already have them, this I'll definitely have to do some more digging with though, because I dunno how I'll synthesize them if they're missing or if I'm not using an opentype font. I think the issue is largely similar for subscript/superscripts

molten galleon
old harbor
#

noice 🙂

#

it needs the typing sound from this thing... one sec

#

ah fuck, windows builds are broken for this thing 😄

balmy gulch
livid patrol
old harbor
#

thats not really old trance 🙂 but illallowitjiff.gif

molten galleon
#

@old harbor

#

they slide off your active list if they haven't been talked in for a few days

#

'tis what 'tis

old harbor
#

yes but

#

it should be visible in the gallery

#

weird : )

#

so my question is, do you know whether cmake knows how to config the compiler when it comes to -j XX

#

will it always pass the maximum available cores/threads or just use whatever is default and not use -j

#

there is CMAKE_BUILD_PARALLEL_LEVEL but im not sure if i should be touching it or not

#

but i would like to have all my cores used when compiling anything

molten galleon
#

that's specifically for cmake --build, aka if you wanna be using the ultra platform agnostic build mode

#

you'll have to investigate what it chooses by default

#

I think otherwise it depends on the generator target, make for example defaults to single threaded but ninja is intelligent about being parallel

old harbor
#

but it would make sense to expect cmake configging my system it should make use of all cores, neh?

molten galleon
old harbor
#

or rather, it makes s ense to compile with all cores, with or without cmake

#

yeah i saw that too

molten galleon
#

so it uses the tool's default by default

molten galleon
old harbor
#

hmm

molten galleon
#

if you're using ninja (which you should be) I wouldn't worry

old harbor
#

ja ninja is default here

molten galleon
#

yeah so then it'll just parallelize by default and everything should be fine

old harbor
#

oki

molten galleon
#

oh boy I think I am pretty close to the approximate shape of my library API

#

just need to take another pass at my font registry

olive sapphire
#

@molten galleon by the way, do you just re-render for size changes? does this dynamically render text with freetype and put it into the atlas? may I get a resource on doing subpixel aliasing with the GPU, without using framebuffers?

molten galleon
#

uhhh

#

I just render it at whatever size you request and put it in the atlas yeah

#

not exactly sure what you mean by subpixel aliasing with the GPU here though, I just use my vertex shader make sure my quads stay pixel aligned so I don't have any smudging

#

the subpixel AA comes baked from however freetype generates it

olive sapphire
molten galleon
#

I just use regular blending so its whatever lol

olive sapphire
#

Huh, you should try some other backgrounds because it might get ugly.

molten galleon
#

idk I've never had any issues

#

are you rendering glyphs like {r,r,r,r} * (color, 1.0)

#

my blending was shitty when I first tried rendering text because I didn't realize you're supposed to just use the font data as the alpha

#

in which case it works on pretty much every background

#

alpha'd textures blend fine in general, otherwise pngs would always suck

olive sapphire
molten galleon
#

oh you're looking for subpixel aa for sdfs

#

like the stuff with fwidth?

olive sapphire
#

Nah, I think i'm dropping SDFs for now. The cost tradeoff doesn't make sense

#

It's a shame because I spent a lot of time researching and making tools for using them. Oh well.

olive sapphire
#

The text rendering is just bad with the shader and the distance field.

molten galleon
#

is this raster or sdf

olive sapphire
#

SDF

#

~30x30

#

It runs at the same FPS, at about 6-7000 though so it's definitely still efficient, but making it look decent is hard.

#

@molten galleon do you use a 3-channel atlas or a single channel one?

molten galleon
#

I have a single channel atlas for most stuff and a second one for color glyphs

olive sapphire
#

Oh yeah, and whatever algorithm I use still produces some weird kind of artifact on the SDF itself:

#

(texture was malloced so there's some random heap data in there)

#

this is causing even the straight lines to be wiggly:

livid patrol
#

calloc pls

molten galleon
#

yeah you should get rid of those weird artifacts

olive sapphire
molten galleon
#

I mean the ones from the malloc to start

#

you should eliminate unknowns

olive sapphire
#

Those never even show up on the output, it's just the algorithm's artifacts.

#

That's because I zoom in a tiny bit when I provide UVs, because even hitting the edge of the texture causes there to be lines.

#

Overall, SDFs have been unreliable.

livid patrol
#

address mode issue?

olive sapphire
#

?

livid patrol
#

oh wait this is an atlas, rip

#

you should figure out why there are lines. maybe adding a texel border around the glyphs would help

olive sapphire
#

Yeah, that's also been something I've been trying to work on.

molten galleon
#

you should absolutely pad your atlas as a start

olive sapphire
#

That would be a massive waste of space.

molten galleon
#

not really

livid patrol
#

would it though

olive sapphire
olive sapphire
#

You said you still use grayscale atlases, how do you do subpixel with them?

olive sapphire
#

It has some flaws, but maybe a shader can fix them

#

I'm rendering them at 96 px height this time, but still not the best.

#

Still have not figured out what this fringing is, so I guess my results aren't too reliable though.

#

This is a completely new algorithm so maybe there's some error in how I'm copying them.

molten galleon
molten galleon
#

lmao it also just occurred to me I've been answering wrong all last night

#

by subpixel aa I originally assumed you meant dealing with adjusting AA to subpixel glyph positions, which is one of the problems you have with cached raster glyphs, and I inded just solve that by pixel-aligning my glyphs

#

but I don't use multichannel subpixel AA like ClearType or something would, just too many issues like you outlined above, lots of complexity for very little gain at 1080p and above

olive sapphire
olive sapphire
old harbor
#

worst case you turn it into a heightmap generator : )

olive sapphire
#

???

old harbor
#

and i was trying to be funny

molten galleon
# olive sapphire Oh, is there a certain method to pixel alignment? I haven't really gone into the...
#version 460

layout (location = 0) uniform vec2 u_invScreenSize;
layout (location = 1) uniform vec4 u_extents;
layout (location = 2) uniform vec4 u_texCoords;
layout (location = 3) uniform vec4 u_color;

layout (location = 0) out vec2 v_texCoord;
layout (location = 1) out vec4 v_color;

void main() {
    uint b = 1 << (gl_VertexID % 6);
    vec2 baseCoord = vec2((0x19 & b) != 0, (0xB & b) != 0);
    gl_Position = vec4(floor(fma(baseCoord, u_extents.zw, u_extents.xy)) * u_invScreenSize, 0, 1);
    gl_Position.xy = fma(gl_Position.xy, vec2(2.0, -2.0), vec2(-1.0, 1.0));
    v_texCoord = fma(baseCoord, u_texCoords.zw, u_texCoords.xy);
    v_color = u_color;
}

nothing in particular, I just floor the final pixel positions in pixel-viewport-space to make sure that image sampling doesn't end up blurry

#

it's just how I do my general UI shaders

#

enjoy my highly readable quad shader

olive sapphire
#

You really don't care about supporting 3.3 or anything like that do you?

#

I don't fully understand what this is doing yet.

molten galleon
#

not in the slightest

#

my test program is 4.6 but my 'real' program is vulkan 1.2

#

I just store texcoords (x, y, width, height) in a vec4

#

and I just like using fused multiply-adds

#

somehow more readable to me

olive sapphire
#

0b00011001 & b != 0, 0b1011 & b != 0 so you're checking whether vertices are in certain indices here.

molten galleon
#

nah I generated those thanks to a neat trick I saw jaker post a while back

#

the first 2 lines generate the 6 vertices of a quad made of 2 triangles

#

so (magic number & b) != 0 just generates either a 0 or a 1 for that component

#

via the bitmask

olive sapphire
#

Yeah, I think I understand that part but I don't know why you're specifically checking for those indices

molten galleon
#

because doing the bool operation forces it to either be a 0 or a 1

olive sapphire
#

I know but what's so special about the first, 4th and 5th vertex

#

and then the first, second and 4th for the second component

molten galleon
#

because these are vertices in a quad

olive sapphire
#

You're missing the third and the 6th.

#

Maybe I'm being stupid but what do the other 3 lines even do?

molten galleon
#

the bitmask generates a vec2 vertex position like (0, 0), (0, 1), (1, 0), (1, 1) based on the vertex ID

olive sapphire
#

it seems like you're multiplying by an extents uniform, which involves screensize, and then you multiply everything with 2.0 and -2.0?

molten galleon
#

the 3rd like transforms it by rect extents (x, y, width height) and puts it into NDC

#

so -1 to 1

olive sapphire
#

What are extents?

molten galleon
#

(x, y, width height) in pixels

#

so (50, 50, 120, 60) draws a 120x60 rect at pixel position 50,50 from the top left

#

all the rest of the math just transforms that into GL coordinate space

olive sapphire
molten galleon
#

it doesnt identify anything

#

it generates them

#

I am abusing a boolean operator to force it to be a certain value

olive sapphire
#

Wait, are you not batching anything? Are you supplying uniforms for every single quad and then making a separate draw call for them???

molten galleon
#

yeah this is my GL test program because I literally don't care

olive sapphire
#

Yeah this might not be useful for me, I'm currently batching shapes, text and images all at once.

molten galleon
#
#version 450
#extension GL_EXT_scalar_block_layout: require
#extension GL_EXT_buffer_reference: require

layout (location = 0) out vec2 texCoord;
layout (location = 1) out vec4 outColor;
layout (location = 2) out uvec2 outImageIndices;

struct Instance {
    vec4 texLayout;
    vec4 color;
    mat3x2 transform;
    uvec2 imageIndices;
};

layout (buffer_reference, scalar) readonly buffer Instances {
    Instance data[];
};

layout (push_constant) uniform PushConstants {
    vec2 invHalfSize;
    Instances instances;
} constants;

void main() {
    Instance inst = constants.instances.data[gl_VertexIndex / 6];
    uint b = 1 << (gl_VertexIndex % 6);
    vec2 baseCoord = vec2((0x1C & b) != 0, (0xE & b) != 0);

    gl_Position = vec4(inst.transform * vec3(baseCoord - 0.5, 1.0), 0.5, 1.0);
    gl_Position.xy = floor(gl_Position.xy) * constants.invHalfSize - 1.0;
    texCoord = fma(baseCoord, inst.texLayout.zw, inst.texLayout.xy);
    outColor = inst.color;
    outImageIndices = inst.imageIndices;
}

this is my vulkan shader

#

different magic numbers for reasons I forget

olive sapphire
#

Do you plan on batching in the future?

molten galleon
#

the vulkan one does batching

#

the GL one doesn't

#

because the purpose of my text test program (currently) isn't to show off how fast I can blit quads

#

and one of my renderer goals was to touch GL as little as possible

olive sapphire
#

Wait so why is the Vulkan shader referencing OpenGL Extensions and is written in what is basically GLSL?

#

I've never written Vulkan so that's new.

molten galleon
#

because the shader is in GLSL so the extensions are still prefixed with GL_

olive sapphire
molten galleon
#

neither of those extensions are actually available in opengl

#

literally everything else

#

text formatting, text editing, text layouting (bidi handling, line breaking)

#

dealing with font fallbacks and caches

#

font synthetics

olive sapphire
#

This is overengineered for games, underengineered for being a library, and you don't seem to care about performance, size or quality.

molten galleon
#

everything you need for rendering fonts that isn't actually shading them

#

its a little overengineered for games but shader performance isn't my immediate goal

#

writing text shaders comes later

#

when I'm done it will absolutely be good enough for any text workload though

olive sapphire
#

Hm what do you mean by "isn't actually shading them"

#

You're using the shaders right there

molten galleon
#

yeah because I need to see the glyphs lol

olive sapphire
#

Huh, so do you upload the textures and everything yourself, but expect the user to write the shading program and deal with your specific uniforms and data formats?

molten galleon
#

what I mean is that it's not currently my objective to R&D the aspect of my text lib that actually puts the symbols on the screen

#

because that's not currently the interesting part or the most confounding problem

#

though obv I'm gonna do that at some point for the sake of my own use cases

olive sapphire
#

I don't know what the goal is, because "putting pixels on the screen" is the whole point of text rendering, and making it seamless and correct is why people research it so much. I don't understand what even you will use this for in the future.

molten galleon
olive sapphire
#

Almost everyone already uses the libraries you're using, like freetype and harfbuzz, which are relatively easy to use and integrate into your game at the large cost of binary size.

molten galleon
#

nah the whole core problem is that even if you have freetype, harfbuzz, and ICU, a lot of your work isn't done

#

if that was all there is to it I would've been done months ago and would've moved on

olive sapphire
#

I have already read them, and most people still ignore them and it doesn't make a difference to 99% of the people playing games or even doing serious text editing.

molten galleon
#

ok? that's kinda their own problem lol

olive sapphire
molten galleon
#

they do, they just exist isolated in their own domains

#

that's the source of my surprise

#

it's not that the solutions don't exist, it's that they don't exist in a plug and play way from a single point

#

really doing text right is usually isolated to stuff like browser engines or operating systems

olive sapphire
#

They do, they're just usually behind a paywall because they're premium tools and a lot of professional research has been put into them from people working in the graphics industry for decades.

molten galleon
#

I don't see your point tho

olive sapphire
#

I am just confused on what your overall goal is with this project. You haven't even provided any docs or anything on how to use the API.

molten galleon
#

it's still indev lol

#

and mostly non-finalized

olive sapphire
#

If you outline the API now, you will know what to build towards and can start recieving feedback immediately.

#

Also, have you finished RTL?

molten galleon
#

lol my goal is simple, I need for my personal projects a simple API for providing text formatting and layout data for feeding a list of common text effects and text handling features I care about, and I'm structuring it as its own library because no single gathering point exists for this

#

yeah

olive sapphire
molten galleon
#

my layout API is pretty much finalized, I just have to clean up some junk with it and add some documentation to the methods

#

the big thing I'm doing R&D for right now is my font registry

#

because I wanna put a bound on the amount of memory I load for the shitload of font files I want

#

but keep it fast and multithreadable

olive sapphire
#

Are you going to generate and ship font files?

molten galleon
#

nah I wanna handle normal ttf/otf

#

the problem is, in order to have rich text formatting, even if you're only using a single font family, you often need multiple faces

#

and usually the way a program handles this is bespoke

olive sapphire
molten galleon
#

e.g. chromium has its own little algorithm for choosing what face it'll render some characters with, windows has its font registry, various linux desktop envs have their own font registries

olive sapphire
#

You mean like the third argument for FT_New_Face?

molten galleon
#

no

olive sapphire
#

Wouldn't you just query those and leave it up to the user?

molten galleon
#

and no

#

a really good exercise is to write out some text in different languages in a browser and check the Computed section in inspect element

#

the arguments for FT_New_Face are about picking faces from a single font file, which are usually different styles for a single thing

#

but you have to use information about/from the text in order to pick them

olive sapphire
molten galleon
#

for example, a lot of fonts are partitioned by script (as in language script: latin, cyrillic, han, hiragana, devanagari, etc etc etc)

#

and special symbols have their own fallbacks

#

math symbols, emoji, etc etc

#

on top of that they're partitioned by weight (boldness) and style (italicness)

olive sapphire
#

Hm

molten galleon
#

so for any given string, you actually have to take some passes at it and figure out what fonts you need, which is also complicated by if you want formatting

#

like bolding some part or doing a smallcaps substitution

#

which is why you need a font registry, rendering a single string with a single font still means you need information about other fonts you might need to pick

olive sapphire
#

Are you talking about fallbacks and stuff like that?

molten galleon
#

yeah

olive sapphire
#

Shouldn't the user worry about them though?

#

Like you can't try to query system fonts on every machine, that's not reliable and can be different depending on platforms.

molten galleon
#

if they want to they don't have to use my library, this is for people like me (in the position of the user) where I ust want to build my registry and have a tool that handles it

#

what fonts you put into the registry and their relationship have to be input by the user, yes

#

so

#

if you want to register stuff in C:/Windows/Fonts to your registry, that's your problem

olive sapphire
#

Oh ok, so why do you need your own registry?

old harbor
molten galleon
#

to store that information and be able to hand it to the layout engine

#

like the browser does

#

my text is in times new roman, but how does it know what font to pick for an emoji?

#

it checks the registry and gets a font from it

olive sapphire
molten galleon
#

yeah how you build your pool of fonts is up to you

old harbor
#

think 80/20 cases

#

your nieche x will have problems if it wants nieche-isms

molten galleon
#

it's just that a good text renderer/layout engine needs information about more than just 1 font

#

my font registry is a tool to serve my API the data that it needs in a nice way

olive sapphire
#

Oh ok

old harbor
#

on windows its c:/windows/fonts... on lunix its some /usr/... on mac its idk where /Fonts perhaps

#

plus you can always add fonts from custom paths

molten galleon
#

the details are up to "the user"™️ but generally speaking for a proper text rendering solution you need to build metadata that lives at a higher level than just 1 font file

olive sapphire
#

So do you just try to see if different fonts have a glyph through a fallback chain, or do you have designated fonts like Segui UI for emojis, Consolas for monospace etc?

molten galleon
#

right now fonts are organized into families which have files that represent a weight, a style, and a script (or scripts)

#

with the option to have one of the files be a general fallback for all scripts

#

and on top of that I have a general fallback chain that it queries one at a time to fill in stuff that is common/inherited script like math symbols or emoji

olive sapphire
molten galleon
#

consider this test/example data

#

you don't need any of mine

olive sapphire
#

This is actually a pretty rich and graceful system

molten galleon
#

the repo is slowly transitioning from "experimenting monorepo" to a library

#

so I am gonna clean up and explicitly mark more stuff that's given as an example vs stuff that's the core lib

olive sapphire
#

If you could do some research into SDFs and shaders for them, I'd really appreciate it by the way @molten galleon. If you want my source for the two generation algorithms I used for single-channel SDFs, I can DM them to you.

molten galleon
#

I did my brief digging into MSDF and decided that for now SDF based solutions just kinda suck, lemme find what I have

#

I have a pretty short list of stuff I'm looking to try as my universal rendering solution, I think I've posted it before

olive sapphire
molten galleon
#

just the Qin et al stuff Jasper mentioned a while ago, and just rendering vectors

#

I bet you can find a lot of good vector rendering implementations especially if you steal from nanovg or similar

olive sapphire
#

nanovg is really slow with them.

#

It still renders them on the CPU.

#

AFAIK.

molten galleon
#

but htis is a great source

#

comes with implementation notes and a webgl demo

#

this entire pdf is 13ms/frame on my machine, but this is webgl and definitely not the best you can do

olive sapphire
#

The demo is running sub 10fps on my machine.

molten galleon
#

it doesn't give fps it's giving ms per frame I'm pretty sure

olive sapphire
#

It's not using GPU?

old harbor
#

you have to dig into this a bit more

#

and not wait for DR to give you the solution : )

olive sapphire
#

Of course, I have

olive sapphire
#

And it didn't have a frame time counter on my page:

molten galleon
#

thats odd

old harbor
#

if you are using a laptop

#

check that your dedicated gpu is the primary one

#

if you have one

olive sapphire
#

I have the latest browser with full WebGPU, WebGL2, OpenGL 4.6 support and a dedicated GPU.

old harbor
#

thats not what i asked

olive sapphire
#

I mean I only have one, so it should be the only thing it uses. Other WebGL things like shadertoy use the full gpu.

old harbor
#

whatever

olive sapphire
#

wow the atlas for that whole page really is just:

molten galleon
#

yep that's the benefit of rendering vectors

#

also it's not a bs approximation like you'd get from something generally SDF-based, a vector font is truly a vector font

#

so why not render vectors

olive sapphire
#

This is a cool resource.

molten galleon
#

hmm, mupdf has a pretty cool LRU cache actually, I think I might go down a rabbit hole implementing general purpose LRU caches

#

though I need to brush up on thread_local and techniques around it, because I want to figure out how to make something that dynamically scales with thread count

#

actually there's not much to it, except I (obviously) can't make thread_local stuff part of an object, so I probably have to design my font cache as if there can only ever be one (I hesitate to say singleton but that's the word that comes to mind)

left junco
#

It's basically exactly the same as static data

#

i.e. static storage duration data

molten galleon
#

I wonder if there's some convenient trick with wait free lists to lazily register per-thread data to some central object

#

yeah I was hoping I could squeeze a little more out of it

left junco
#

Or what's stopping you

molten galleon
#

ok I'm back, uh, idk I'm just kinda fleshing out the idea in my head

left junco
#

I assume you could make the default constructor do the registration

#

With some global registry

molten galleon
#

for font caches my main thing would be to keep some thread-local FT_Library/FT_Face/hb_font_t's around to fulfill their various use cases, kinda having a hard time figuring out how much about them I wanna store per-thread and how I'd keep them from sticking around too long

left junco
#

With some global registry

molten galleon
#

lol nice internet

#

for actual main font data (i.e. loading the file from memory) it's simple enough because that'll just be read into the main LRU cache, but each thread would build its own thread_local cache of FT/hb stuff which would effectively act as a view into it

left junco
#

Lmao I blame discord

molten galleon
#

though another completely unrelated idea that uses thread local stuff I've been wrestling with was for vulkan deletion queues

#

for that I was thinking you would have lazily-allocated-and-registered queues on whatever threads happened to ditch an object into the deletion queue, but they'd stay centrally registered somewhere in case that thread dies, and for when deletion time comes around

#

you can gather all currently available queues of stuff to maybe delete, dispatch them to n worker threads, and safely iterate them

#

and instead of always contending for a single queue or always negotiating with each thread for each queue, there's just a single atomic flag for whether it's deletion time or not

#

and when that flag is set and you still want to delete something, you just defer that to yet another queue

#

but that's just a random idea I had roughly based on how potrick described what he wants for daxa, it has made me start to wonder if there are some neat established tricks for lazily registering per-thread data to some kind of object with minimal contention, and temporarily taking ownership of it from a different thread or threads to do some kind of gathering work

molten galleon
#

hmmm ok I thought about this more and it led me to fun requirements like "wait free hash table" and I think this is way too much for a deletion queue in vulkan, let alone a font cache

livid patrol
#

I thought wait-freedom has only been proven to be practical for extremely simple data structures

#

Like a queue or something

#

A hash map seems way too complex according to my intuition but I have 0 facts to back it up

molten galleon
#

yeah I have no idea how practical one would be

livid patrol
#

I read somewhere that it's technically possible to implement any data structure with a number of wait-free building blocks, but there is no guarantee that it's anywhere close to fast

molten galleon
#

yeah, also my rough idea is something like

struct DeletionQueue {
  std::vector<Thing> m_queues[THREAD_COUNT];
  MultiReaderMutex m_mutex; // I forget what these are actually called

  void queue_delete(Thing t) {
    if (m_mutex.try_lock_reader()) {
      m_queues[get_current_thread_queue_index()].push_back(t);
      m_mutex.unlock();
    }
    else {
      // Put it into some kind of thread local secondary backup queue to dump later
    }
  }

  void flush() {
    m_mutex.lock_writer();
    // dispatch flushing the valid handles in all queues on my own worker threads
    m_mutex.unlock();
  }
}
#

this should allow you to build a deletion queue that has no blocking and minimal contention when submitting an object to be queued for deletion

#

but the challenge is how would you generalize it to handle an arbitrary count of threads you don't necessarily control

nocturne hill
#

try_lock_reader already creates some contention

molten galleon
#

yeah it's like one atomic CAS though and it won't ever block the thread trying to submit something to delete

#

not the end of the world

nocturne hill
#

this looks overcomplicated

molten galleon
#

yeah I mean I had the idea in my head so I was just investigating how feasible it is

nocturne hill
#

you can just collect stuff-to-be-deleted into a list (e.g. std::vector) and then append it to a per-VkQueue delet queue at submit time or whateverrr

molten galleon
#

I think daxa has something very roughly resembling this idea, potentially implemented in a completely different way though

#

yeah, though you'd have to lock it to delete something

#

which is probably not too bad in reality, but the possibility seemed intriguing

nocturne hill
#

well yes locks are good actually

#

efficient waiting

#

given that submit is a relatively heavy operation this seems like a good time

#

you need to lock stuff anyways because access to VkQueue needs to be externally synced

molten galleon
#

I don't think I'd need to sync access to any queue to destroy stuff, I can just calculate when it's safe to delete an object based on frames in flight or timeline semaphore values like you would with any other deletion queue implementation

molten galleon
#

also for the hash map thing - I don't think I would need a wait free hashmap, just some kind of fixed size structure like

std::pair<std::queue<Thing>, std::mutex> m_queues[QUEUE_CAPACITY];

void queue_delete(Thing t) {
  auto& [queue, mutex] = m_queues[hash_index(GetCurrentThreadId())];
  std::lock_guard lock(mutex);
  queue.push_back(t);
}
#

if my problem/goal would be to reduce overall contention at least

#

but yeah it's probably overcomplicated, but definitely interesting

olive sapphire
#

BTW, don't ever use STL hashmaps. They're extremely slow.

old harbor
#

they are fine

#

you dont always need nanosecond-trading-realtime-capabilities everywhere

olive sapphire
#

When I used C++ hashmaps, they were taking 30-50 microseconds per query. I know for a fact that hashmaps can be 100x faster than that easily. The reason I found that was that they were being a performance bottleneck when I was querying uniform locations, characters according to their glyphset, etc.

#

Weirdly enough, I made a naive implementation without any optimizations in about 100 lines of C, and it was less than .2 microseconds per query, proving my point.

old harbor
#

if you use them in a hotpath sure

#

if you use them to collect resources to be cleared later, its not going to kill anyone

olive sapphire
#

I would say if you're using them for uniforms, it might just be better to query the uniform location every call than to use a filthy slow STL hashmap.

molten galleon
#

no you'd wanna use explicit uniform locations

olive sapphire
#

?

molten galleon
#

layout (location = 0) uniform u_whatever; and whatnot

#

also yeah while the STL hashmap is hilariously bad it also... mostly doesn't matter for my use case, not to mention requires propogating an opinionated container since this is a middleman lib

olive sapphire
#

I didn't know that was even possible, but I wouldn't trust it as much since shaders can change easily.

molten galleon
#

I like maltke skarupke's flat_hash_map, I also like absl::flat_hash_map, eastl has a good one

#

but I'd have to probably set some kind of compile flag to let me override the hashmap if/when I really care

olive sapphire
molten galleon
#

in vulkan you straight up can't have named uniform locations*

*unless you use shader reflection to go very far out of your way to reimplement it out of spite or something

olive sapphire
#

Only in OpenGL? Why would Vulkan regress?

molten galleon
#

because it's not a regression, working with strings in deep render code is just an antipattern

#

a good mantra to follow is that a shader isn't a resource, it's part of your code

olive sapphire
#

Oh I thought you meant numbered. Opengl has confused me with its terminology by constantly referring to numbers as "names"

nocturne hill
#

idk what your query is but 30-50 microseconds per .find is not realistic even with shitty impls

olive sapphire
#

I was, it was required for performance querying.

nocturne hill
#

so it likely was a mistake on your side

#

but ye whatever

molten galleon
#

you always profile in release

#

debug numbers don't really matter

olive sapphire
#

It doesn't provide symbols when I'm in release.

nocturne hill
#

idk if ur using ms toolchain but youk now...... generally you can compile select TUs with optimizations

#

and keep your debug info

olive sapphire
#

It also actually inlines my inline functions, which makes it hard to do anything regarding them.

nocturne hill
#

clang -O3 -g -c IMPORTANT.c

olive sapphire
#

yeah I'm using MSVC

nocturne hill
#

well with msvc I'm sure you could've figured something out as well

old harbor
#

doesnt cmake come with a configuration for that scenario already?

olive sapphire
#

I mean I didn't even know release could have symbols.

old harbor
#

Relsomething

nocturne hill
#

that's indeed opts with debug info yes

#

but opts destroy debug experience either way

olive sapphire
#

CMake doesn't for my setup.

nocturne hill
#

RelWithDebInfo

#

what

old harbor
#

thats what i meant

nocturne hill
#

I actually always use debugoptimized for my programs because I don't have a lot of ceepeepee components and so it doesn't blow out blob size with debug info

#

and at my previous job we also always used debugoptimized for our ue projects because debug was unusable

#

900MiB .pdb 💀