Rendering text without SpriteFont

So I have a slight issue with my game’s UI framework. My game is HEAVILY UI-driven and uses a lot of text. The UI is also user-customizable in just about every aspect you could think of - colors, sizes, locations, fonts, etc.

MonoGame has support for rendering text strings using SpriteBatch and SpriteFonts, but this won’t work in my case as I need to let the user choose from any installed font on their system and render text in that font.

The logical thing to do would be, when the font is set, or when the game is loaded, I generate a spritefont from it at runtime and cache that spritefont for use with rendering text. You…can’t do that, because SpriteFont's constructor is internal, not public. So even if you could find some way of loading the font data into memory, read out and render each glyph to a texture, etc, there’s no way you can use that data in a SpriteFont because you can’t even create an instance of a SpriteFont without going through Content Pipeline.

So the next best thing I bet would be to use System.Windows.Forms.TextRenderer and System.Drawing to measure the text you want to render using the font you want to render it in, then create a bitmap to render the text into, then grab the bitmap’s pixel data using LockBits and Marshal.Copy, create a Texture2D matching the size of the bitmap and drop the bitmap’s pixel data into the texture and render it. This is what I currently do in my game.

However, GDI (which is what’d be used for text rendering) is hardly hardware accelerated. It is quite slow, especially when you’re doing lots of sequential renders AND on top of that marshalling that data out of GDI and into MonoGame. Takes a lot of CPU and RAM is what I’m saying.

Since my game does a lot of text rendering, I tried mitigating this by caching any text render results and only rerender if the text or font changes. This helps A LOT for most parts of the game, but there are some parts that are constantly changing the text - for example the framerate counter, the date/time in the desktop panel (my game takes place in a fictional operating system), or when the user’s typing in the Terminal.

The problem is even with all this caching, in those cases, there’s a lot of GDI stuff going on and it takes up precious CPU and RAM, and especially in the Terminal where the user’s either trying to work really fast to hack another player or NPC, the GDI rendering tends to REALLY slow them down when the terminal has lots of text.

The only fix in my head would be either to disallow customization of fonts (not doing that - the UI customization is a big part of the game and makes development a lot easier too), or write my own implementation of SpriteFont. Option B still requires GDI, but only when loading the font data. I’d still also need to write code to find each glyph in the texture, which is probably a nightmare (hence why SpriteFont exists, probably so monogame can deal with that for you)

But wait! Things like DirectWrite exist. What if I used that to render the text directly onto the screen or whatever render target I have set? Surely since DirectWrite is ontop of DirectX that can’t be too hard…but oh wait. My game uses OpenGL, because it is cross-platform. Can’t do that.

There’s gotta be some way of rendering text in MonoGame with hardware acceleration and specifying a text string, font family, style and size, and optionally alignment, wrap width, etc, just like you can in GDI. But since my googling comes up inconclusive, I came here for advice on it. If anyone can help me tackle this issue, it’d be greatly appreciated.

(Also, on the topic of cross-platform, GDI isn’t totally usable in Linux and GDI+ is POORLY implemented on it as well, and one of the devs on my team who runs linux had to write his own text rendering backend for our engine in C++ that wraps pangomm/cairomm. It’s fast, looks beautiful, but is extremely hard to compile on Windows. I want to get a text rendering backend in that’s fully compatible on both linux and windows if possible.)

You could include MGCB and the required dependencies for the target platform in the runtime to build a SpriteFont when users load a new font, then just load it as usual with a ContentManager.

I have no clue what MGCB is, and how I’d set that up…

MGCB is the tool that MG uses to build content. It is used behind the scenes by the Pipeline Tool, but can be used directly from the command line as well (as explained in the docs page linked above).

While the MG content pipeline is made specifically to be used at build time, it’s not restricted to it. If you include the MGCB executable and dependencies you need, you can get all the functionality of the content pipeline in your game, including support to build .xnb SpriteFont files from system fonts. You can use MGCB by using the command line interface, like the Pipeline Tool does, or use the public API it exposes.

I hope that helped clarify things a bit. I’ll set up an example later this week if you want.

1 Like

All I really need to know is where I’d find the assemblies I’d have to reference. From there I should be able to figure things out on my own :stuck_out_tongue:

If you use an installed version of MG, the assemblies can be found in the installation folder. The full path on Windows should be C:\Program Files (x86)\MSBuild\MonoGame\v3.0\Tools. That contains both MGCB.exe and MonoGame.Framework.Content.Pipeline.dll. I said before you could use the public API exposed by MGCB, I was mistaken. MGCB is just the CLI front-end for the content pipeline, it does not expose enough functionality to reference it and use as a library. You can use the MonoGame.Framework.Content.Pipeline.dll if you want to not use the CLI, examples of how to use that can be found in the MG test suite. Check out the Importer/Processor tests if you go this route. The Pipeline Tool shows how you can use the MGCB CLI; link.

1 Like

Wait, better idea, if I could some how grab the file location of the System.Drawing.Font I want to render text in, would I not be able to use that file along with SharpFont to render text that way?

Surely that can’t be too hard right?

You could use something like TrueTypeSharp, a TrueType font rasterizer written in C#, to generate the bitmap data of a string using a selected font. This can be set on a texture and then rendered using SpriteBatch. While it is not SpriteFont, you must remember that SpriteFont is not the only way to render text in a MonoGame application. It is just one way to render text.

I had a test branch from a few years ago that used TrueTypeSharp in the content build pipeline to generate the SpriteFont data in order to remove the dependence on the FreeType native binaries. It was all working, but not quite accurate enough with XNA’s original GDI rasterization to warrant replacement.

That might just end up being what I’ll use. I’ll let you know how it goes :stuck_out_tongue:

Seems like TrueTypeSharp is even slower than my current solution. It clogs up the game thread even with all the caching stuff going on on my end! I can’t even get the game to boot beyond a white screen! And this is just for text measurement.

Also it seems that it doesn’t support word/char wrap or text alignment. This is a big issue.

It’s a rasterizer, not a layout tool. The other option would be using FreeType and the C# bindings for it.

Ahh, well, I’m looking for something like System.Windows.Forms.TextRenderer except not reliant on GDI, and also cross-platform.

Alright, SharpFont’s being even more of a donkey to me. I have to use SharpFont 2.5.3 because that supports .NET 4.5, and when I create a new SharpFont.Library I get a BadImageException saying an attempt was made to load a program with an incorrect format.

BadImage is usually because of a bitness mismatch. In this case I guess your native TrueType binary was built for 32 bit and you’re running 64 bit or vice-versa.

Thing is changing the target architecture between x86, x64 and Any CPU made no difference.

Although this topic is now really about how to load a system font during load or dynamically.

If anyone is still interested in rendering text later and comes across this, then here is a very old example that might help get you started.

You can see glyphs is just a descriptor struct that has a few easily replicated variables you can mimic yourself and those just tell the drawstring method were to get the source rectangle from in a texture and how to offset its destination with kerning.

So I use your System.Drawing method. I get 45-55 FPS on my game in a full-screen windowed mode, with lots of text rendered on top of an ISO map. I thought I’d share the code I use:

FPSSB.Begin()  // spritebatch
Dim tfh As Int32 = TextFont.GetHeight
Dim s As String = "FPS: " + (CInt(FC.AverageFramesPerSecond * 100) / 100).ToString  //FC= Framerate Counter Class
Dim Graphics As System.Drawing.Graphics
Dim trect As New System.Drawing.Rectangle(0, 0, 110, tfh)
Dim Bitmap As Bitmap = New Bitmap(CInt(110), tfh) // the bitmap that will hold the text
Graphics = System.Drawing.Graphics.FromImage(Bitmap) //tell the graphics object that it will be using the bitmap
Graphics.FillRectangle(New SolidBrush(System.Drawing.Color.Black), trect)
Dim YellowSolidBrush As New SolidBrush(Drawing.Color.Gold)
Graphics.DrawString(s, TextFont, YellowSolidBrush, 0, 0)
Dim MemStream As System.IO.MemoryStream = New System.IO.MemoryStream //set aside a portion of memory to hold the bitmap
 Bitmap.Save(MemStream, System.Drawing.Imaging.ImageFormat.Png) // save the bitmap to the portion of memory
 MemStream.Position = 0 // necessary
 Dim ChoiceListTexture As Texture2D = Texture2D.FromStream(XNAGraphics.GraphicsDevice, MemStream) // retrieve it
 FPSSB.Draw(ChoiceListTexture, New Vector2(0, 0), c) // c= new Color(255,255,255,128) - gives semi-transparency
 FPSSB.End()

Screenshot

1 Like

Sorry to necro - if anyone stumbles across this thread, there’s a great solution (using the SpriteFontPlus NuGet package. You can read more about it in the second post of this thread.

This https://github.com/ryancheung/SkiaTextRenderer could be an alternative to drawing text in MonoGame