Juce 8: Decline in Windows font rendering quality

I’m using Helvetica Neue TTF fonts in my app. I recently upgraded from 7 to 8 and these fonts look terrible on Windows. Here are before and after screenshots:


The code for grabbing the font:

if (weight == "bold") {
                static Font font(Font(Typeface::createSystemTypefaceFor(FontBinary::helveticaNeueBold_ttf, FontBinary::helveticaNeueBold_ttfSize)));
                return font;
            }
            else if (weight == "roman") {
                static Font font(Font(Typeface::createSystemTypefaceFor(FontBinary::helveticaNeueRoman_ttf, FontBinary::helveticaNeueRoman_ttfSize)));
                return font;
            }
            else if (weight == "light") {
                static Font font(Font(Typeface::createSystemTypefaceFor(FontBinary::helveticaNeueLight_ttf, FontBinary::helveticaNeueLight_ttfSize)));
                return font;
            }
            else { // medium is default
                static Font font(Font(Typeface::createSystemTypefaceFor(FontBinary::helveticaNeueMedium_ttf, FontBinary::helveticaNeueMedium_ttfSize)));
                return font;
            }

And setting the font:

nameLabelFont = std::make_unique<Font>(GUIUtility::getFont("roman"));

I tried updating to the latest develop branch but it didn’t help. Any ideas?

I was facing similar issues when I switched to Juce 8.
In my case they were caused by the d2d font hinting. The fonts I use had hinting data that just didn’t look good at all using the d2d renderer and the old software renderer did not use the hinting info. I ended up using FontForge to strip my fonts of all the hinting information which gave results that are a lot closer to what my fonts looked like in Juce 7 and earlier.

1 Like

Thanks for the suggestion. Is there a way to simply tell juce to use the software renderer? Or are we stuck with d2d now? I’ve run into other issues as well that I suspect may be related to the same thing (blurry graphics, and window sizing).

You could wrap the code below with some Windows macros like JUCE_WINDOWS:

void MainPanel::parentHierarchyChanged() {
    if (const auto peer = getPeer()) {
        peer->setCurrentRenderingEngine(0);
    }
}

However,

  • I am not sure whether the font issue will get solved as JUCE 8 uses harfbuzz to render font
  • AFAIK, the software renderer on JUCE 8 is not as good as the software renderer on JUCE 7 (might be biased)

I would suggest changing to Direct2D ASAP.

1 Like

Both statements are false:

Harfbuzz shapes the text, doesn’t render anything
The software rendered is identical in JUCE8 to JUCE7

Thanks for the correction. TBH I am not familiar with text rendering at all :slight_smile: So to be precise

  • rendering engine uses the hinting info
  • Harfbuzz does not use the hinting info

Am I correct here?

I do see a drop in performance if I use software rendering in JUCE 8. Perhaps it is because I have to render some changing text really fast. IIRC, HarfBuzz is heavier than the shaping engine in JUCE 7.

an observation, these examples are scaled different.
In the JUCE 8 one fonts and the combo-box are couple of pixels shorter.

Is the JUCE 8 one getting scaled down a bit somehow?, and therefore losing detail?

Harfbuzz shapes the text, then returns the glyphs for rendering. It seems to keep the hinting information.

Harfbuzz is slower than the old shaping engine. The old engine barely did anything, whereas Harfbuzz supports ligatures, emojis, etc.

1 Like

This worked. I’m not sure how to apply this to popup menu, so I’ll likely just change the font for those. Thanks for your help.

Have you had performance impacts from this? Using this makes my fonts look way better on Windows, but it also seems to slam paint() to be super slow (especially with animations and moving images)

peer->setCurrentRenderingEngine(0);

This will use the old software renderer, so it’s likely to be much slower (no hardware acceleration) unless you’re working on a project that’s been optimised specifically for that renderer.

The software renderer also doesn’t do very sophisticated font rendering - in most cases, the new default renderer will do a better job of displaying fonts in the way the font designer intended.

in most cases, the new default renderer will do a better job of displaying fonts in the way the font designer intended.

Hmm, in my experience this hasn’t been the case. I primarily develop on Mac and the fonts look great on macOS and iOS, but the Windows fonts seem to be lagging behind. All of the below examples are using the latest version of JUCE 8 in release mode.

Here’s using an OTF with hints manually stripped using FontForge (which seems to be the best I can do after an afternoon of wrestling with it):


Screenshot 2025-04-15 162458

Here’s with setCurrentRenderingEngine(0):


Screenshot 2025-04-15 162615

Am I crazy? I’ve been staring at this for a while so I’m losing some confidence, but the
setCurrentRenderingEngine(0) version looks pretty clearly better to me than the default rendering engine. I’m not really sure why one is chunkier than the other, but the thinner version from setCurrentRenderingEngine(0) feels much more readable and is prettier to me. I suspect the old rendering engine + a 500 weight version of the font would be perfect. The default version looks blocky and aliased in comparison.

The font is Avenir fwiw.

Yes. Our Windows app became so sluggish using the software renderer with Juce 8 that we ended up reverting back to Juce 7.

In our case our Windows app performed well with Juce 7, but when using the software renderer it Juce 8 it became very sluggish. Is there a reason why the software renderer would perform more slowly in Juce 8 and than it does in 7?

I wonder whether you’re creating lots of intermediate Images, calling setBufferedToImage, or similar. Temporary images will use D2D by default unless you explicitly request a different format, and drawing D2D images to a software graphics context may be slow.

This is only a guess - the best way to know for sure would be to run your app in a profiler while it’s doing lots of graphically-intensive operations, and to check which functions are taking a long time to execute.

It’s also worth mentioning that performance has improved a bit in general since the first versions of JUCE 8, so if you only tested a very early version, it might be worth having another go with the current develop branch. We’ve also got a few more improvements in this area on the way.

1 Like

Do you have any recommendations on font rendering in Windows? I’m just trying to get closer to something the legacy renderer produced, which also feels much closer to what macOS produces. It feels like this should work OOTB for the most part but I’m willing to wrestle with it more since the text/font is obviously such a huge part of the look and feel of a plug-in.

If you want font performance to excel, one simple rule is this: Count your HWND’s.

Every single popupmenu is a new Window surface, which comes with a context switch on the HWND which is used to describe it - this means the device context (HDC) gets switched whenever a popupmenu is shown/hidden, and a new rendering context is created/destroyed and inserted into the Windows message loop.

This context switch can involve a non-trivial cost, particularly if:

  • The menu is opened/closed frequently.
  • There’s a change in font rendering pipeline, especially if GDI vs. DirectWrite or ClearType gets invoked dynamically.

Fonts in windows are cached per-HDC. A new HWND = new HDC.

If a font hasn’t been used on this context before, Windows might need to:

  • Recalculate font metrics.
  • Load font glyphs into the cache for that HDC.
  • Initialize font smoothing (ClearType settings).

To mitigate this (and also to future-proof your work for additional platforms, such as iOS, where popupmenu’s are really to be eschewed), don’t use popup menus! Instead, use sliding Components, bound to the outer (main) window context, and program them to function as panels -animating in and out (rather than popping a window off and on).

You’ll give your user a better user experience, and you’ll reduce the number of HWND/HWDC’s your app is going to need to manage - and this can be very important when dealing with fonts with high glyph counts.

You can use Visual Studios’ “Spy++” tool to count the sheer number of HWND’s are associated with your project. View → Windows, filter by Process ID, and count your HWND’s. Every one of those is a Message Loop insertion/deletion, and a context switch - including penalties for font loads, and font smoothing, etc. - every time your popupmenu is instantiated (and destroyed).

You can count HDC’s with Process Explorer (Sysinternals).

I’ve also seen code like this floating around:

// Enumerate HWND's
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) {
    DWORD pid = 0;
    GetWindowThreadProcessId(hwnd, &pid);
    if (pid == (DWORD)lParam) {
        // Count or log HWNDs here
    }
    return TRUE;
}

void CountHWNDs(DWORD targetPid) {
    EnumWindows(EnumWindowsProc, (LPARAM)targetPid);
}

//Count GDI Objects (includes HDC's):
#include <windows.h>
#include <psapi.h>

DWORD gdiCount = GetGuiResources(processHandle, GR_GDIOBJECTS);
DWORD userCount = GetGuiResources(processHandle, GR_USEROBJECTS);

See also: GDIView by NirSoft.

I’m afraid I don’t have a recommendation for producing this behaviour. In general, text rendered by the native Windows and macOS APIs will look slightly different. Maybe a good starting point would be to try rendering the same font outside of a JUCE app (e.g. in a web browser, notepad/textedit etc.) on each platform and to compare the results that you get. If you see the same results on both platforms, then perhaps there’s some option we can set in JUCE in order to provide more consistent rendering. Otherwise, if you also see rendering differences in apps using the platform text rendering stack, then you may need to decide between using the JUCE software renderer (slower, less sophisticated, but consistent between platforms) or the native renderers (faster but will produce platform-specific results).

For anyone else going totally crazy from this, I found it mentioned in another thread that standalone and VST3 versions of a plugin can look much different. Like most devs (I imagine), I primarily test in standalone, but it seems like there’s a big difference when it comes to fonts. I just compiled as a VST3, fired it up in Bitwig, and it looks great :slight_smile:

Mac standalone looks great, so it didn’t occur to me that the same might not be true in Windows (in fact, one of the things driving me nuts was how terrible my fonts looked in standalone compared to Visual Studio’s own UI text).

It seems like manually removing hints from the font with something like FontForge suffices, and then letting the VST3 host deal with the rest.

I’ve seen recommendations to use OTF instead of TTF for Windows, but this hasn’t mattered in my experience (and trying to transition to the OTF version of a font was very painful for me since the font size seemed to be rendered totally differently).

I’ve seen recommendations to use OTF instead of TTF for Windows

.. for each HWND, a differing font rendering pipeline .. init your fonts once, not with each context switch ..