Cinder-SdfText: Initial Release (WIP)

Next challenge: laying out text that runs from right to left (Arabic) with all kinds of ligatures and what not. But that is not for the faint of heart. :sweat:

Edit: by the way, are you sure you render the text using pre-multiplied alpha, Hai? If not, you may want to change the shader to: Color.rgb = uFgColor.rgb;.

Left: wrong blend mode. … Right: correct blend mode.

Ola Hai,

I’ve taken a bunch of snapshots of SDF text, you can find the album here:

Most of the images are from text that looks great, even at smaller sizes. Some of them exhibit flaws, though, and I’ve indicated them here or there. Have fun looking at your great work.

-Paul

@paul.houx This thing wouldn’t be where it is without your efforts! Thanks for jumping in! Also thanks for making the album, I went through it - some useful stuff in there to track down more of the formatting issues.

I updated the shader to handle a premultiply toggle. I’m not sure if my formulation is 100% correct. If you have some time, would you mind taking a look?

Thanks,
Hai

Tweaked it a little. There ya go!

Cinder-SdfText has come a long way since the initial release! Much of this is thanks to the might hands of @paul.houx and contributions from @ryanbartley and @num3ric.

Recnt New Features:

  • Save/Load - allows you to cache the generated SDF texture atlases so you don’t have to generate them every time. The same cache can be used for multiple font sizes.
  • Alignment - horizontal alignment for left, right, center, and justify.
  • New Samples - several new samples to demonstrate new features.

Obligatory Screenshot

2 Likes

All 400 fonts on my system, loaded, rendered and recorded in real time.

This looks fantastic, awesome work guys.

Is this the kind of thing that could go into the core Cinder library for rendering text in the future? And if not what are its limitations / advantages over TextureFont apart from the obvious quality / resolution benefits etc. Is it more / less computationally expensive to initialise / render text on the fly for example?

Excited to get to grips with this, if theres anything I might be able to help with to bring this to OSX too let me know :slight_smile:

http://felixfaire.com/

Hi,

Hai’s (and a bit of my) work was based on the TextureFont classes, so in general they are similar. Here are the differences:

  • Instead of rendering the glyph as a bitmap, we use MSDFGEN to generate a special 3-channel signed distance field per glyph. This takes considerably more time, meaning that creating the texture on-the-fly (dynamically) would cause a drop in frame rate if done on the main thread. The code is not multi-threaded yet (but could be made that way, it’s 100% CPU-only). In the meantime, consider creating the font texture during development, then saving it as a binary file for ultra-fast loading. Methods to load and save the file are provided.
  • To render text, a mesh needs to be constructed that takes the texture and draws the glyphs in the right location. The code for this is almost identical to TextureFont, but we also added left, centered, right and justified alignment. The code is only slightly slower because of this, but the difference is hardly noticeable in most cases. More layout options are not provided at this time, since that should really be handled more generically in Cinder. This solution was designed to render high quality text efficiently in OpenGL. It wasn’t our goal to replace Adobe Indesign. Lastly, if you intent to use static text, consider creating the mesh once and reuse it. It’s on our TODO list to provide an out-of-the-box solution for this in the form of a SdfTextMesh class.
  • The text must be rendered using a shader that is a little bit more complex than just a simple texture lookup. The shader calculates several derivatives in order to properly determine the distance from each fragment to the actual glyph outline, then using this distance as a threshold to render the anti-aliased glyph at any possible size. Calculating the derivatives requires internal synchronization in the GPU that makes this a relatively slow process, but any desktop GPU should have absolutely no problem rendering a large amount of SDF text. There is no OpenGL ES shader support at this time (although this can be added - the shader code does not require exotic functionality), so we haven’t tested on iOS to measure performance, but I am pretty sure it will be viable to use SDF text on a mobile platform.

-Paul

P.S.: as for integration into the Cinder code: I would love to see that. It should completely replace TextureFont in my opinion, but that would require support for dynamic texture creation (adding missing glyphs on-the-fly), preferably on a separate thread. I would also break out most of the layout code and use it not only for TextureFont, but also for TextBox and basically anywhere text is used. As you can understand, this takes quite some engineering to do it properly. I know proper text handling is high on the wish list, so hopefully we’ll see some of this in the core soon. It’s just a matter of time.

By the way, here’s an example of an SDF texture that is generated by this solution:

This is wonderful stuff. You guys are amazing. :fireworks:

Added a SdfTextMesh class to cache the geometry for text.

It’s relatively straight forward to use. A ‘run’ is created for each section of text. You can have multiple runs in a single SdfTextMesh object. Each run supports most of the features from SdfText::DrawOptions. Internally, SdfTextMesh caches gl::BatchRef objects on an as needed basis corresponding to textures and glyphs.

Example looks something like this:
mSdfTextMesh = gl::SdfTextMesh::create();

	std::string blockText( "Some realyy long text." );
	Rectf blockBoundsRect( 40, mSdfText->getAscent() + 40, getWindowWidth() - 40, getWindowHeight() - 40 );
	gl::SdfTextMesh::Run::Options blockOptions = gl::SdfTextMesh::Run::Options().setAlignment( mAlignment ).setJustify( mJustify );
	mBlockRun = mSdfTextMesh->appendTextWrapped( blockText, mSdfText, blockBoundsRect, blockOptions );

	vec2 baseline = vec2( 10, 30 );
	mAlignmentRun = gl::SdfTextMesh::Run::create( "LEFT", mSdfText, baseline );
	mSdfTextMesh->appendText( mAlignmentRun );

	baseline = vec2( 10, getWindowHeight() - mSdfText->getDescent() );
	mFpsRun = gl::SdfTextMesh::Run::create( "fps", mSdfText, baseline );
	mSdfTextMesh->appendText( mFpsRun );

	float fontNameWidth = mSdfText->measureString( mSdfText->getName() ).x;
	baseline = vec2( getWindowWidth() - fontNameWidth - 10, getWindowHeight() - mSdfText->getDescent() );
	mSdfTextMesh->appendText( mFont.getName(), mSdfText, baseline );

Obligatory Screenshot

1 Like

Initial version of OS X has been added. There are still some issues:

  1. SDF is reversed for some fonts.
  2. System fonts are not being loaded as expected, e.g. Helvetica. Probably needs an update to tell FreeType to pull the specific style if requested and Regular if nothing is requested.
  3. Some of the sample projects do not have Xcode projects yet.

If anyone wants to brave an iOS version, please feel free!

Obligatory Screenshots

1 Like

I tried to build this with XCode 6.2, but the project file is incompatible. Probably a newer version. Then I tried with cmake, but I had to make some modifications similar to Petros’s pull request to readLittle, writeLittle calls. And probably for similar reasons boost::algorithm::trim_right_copy (here and here) gives errors for char32_t. Errors here

Are you using the latest version? Both of those issues should be resolved.

Thanks. The newer version builds fine. I tried this two days ago, and didn’t notice these changes in the OS X commits.

Added build files for iOS for Cinder-SdfText. The following samples also have build projects updates:

  • Basic
  • BasicMesh
  • MeshPages
  • StarWars

I don’t currently have an iOS device that’s provisioned to run apps - if anyone wants to run test it and report back, that would be helpful!

Obligatory Screenshot

Hey.

Had mixed results building on iOS (iPhone 6S)

Basic - Success
MeshPages - Success

BasicMesh - Fail
StarWars - Fail
I get a runtime error for both of these:
|error | void cinder::app::AppBase::executeLaunch()[137] Uncaught exception, type: cinder::Exception, what: Invalid out stream
libc++abi.dylib: terminating with uncaught exception of type cinder::Exception: Invalid out stream

Would you mind double checking these? A stack trace from the line that’s throwing the exception would be helpful.

The provisioning stuff finally came through for me and I was able to run all 4 samples on my 6S+ without issue.Here’s a screenshot of Star Wars:

I have encountered a strange issue with measureString. It does not seem to return the correct size if I change the font size. With size 24 it seems to be ok, but otherwise it differs from the drawn string.

This is drawn with Helvetica on OSX with the BasicApp sample.

I draw the string and the rect like this:

gl::translate( vec2( 100 ) );
vec2 strSize = mSdfText->measureString( str );
Rectf rect( vec2( 0, -strSize.y ), vec2( strSize.x, 0 ) );
gl::drawStrokedRect( rect );
mSdfText->drawString( str, vec2( 0 ) );

Am I missing something? Should I use the size in different way instead of assuming that the size and the drawn string match?

Hi Gabor,

can you try to make the following change?

(To avoid confusion: on the left side is the current version, the right side is what I think it should be.)

I am using this block in a large project at the moment and had to make a few changes when I ran into some issues. I’ll probably create a PR when I’ve finished working on the project, but you can try this now if you want to.

-Paul

Hi Paul,

Thanks. I made the change, but the bounding box size still doesn’t match the text size.

-Gabor