Threading issue with Graphics::drawText


#1

Hi!

I think I found a threading issue with Graphics::drawText

My application have two threads:

  • The main thread displaying a lots of juce::Component
  • The second thread starting early at application initialization, does internal computation without display anything.

This second thread does some off-screen text rendering on local juce::Image like this:

static juce::Image createImageFromText(const String& text)
{
    juce::Image image(juce::Image::ARGB, 320, 180, false);
    juce::Graphics g(image);
    g.fillAll(juce::Colours::black);
    g.setColour(juce::Colours::red);
    g.setFont(juce::Font(12));
    g.drawText(text, 0, 0, 320, 180, juce::Justification::centred, false);
    return image;
}

Sometime, at application start-up, the second thread crash in the following callstack:

juce::WindowsTypeface::getOutlineForGlyph+0x140
juce::Typeface::getEdgeTableForGlyph+0x52
juce::RenderingHelpers::CachedGlyphEdgeTable<juce::RenderingHelpers::SoftwareRendererSavedState>::generate+0x87
juce::RenderingHelpers::GlyphCache<juce::RenderingHelpers::CachedGlyphEdgeTable<juce::RenderingHelpers::SoftwareRendererSavedState>,juce::RenderingHelpers::SoftwareRendererSavedState>::findOrCreateGlyph+0x7f
juce::RenderingHelpers::GlyphCache<juce::RenderingHelpers::CachedGlyphEdgeTable<juce::RenderingHelpers::SoftwareRendererSavedState>,juce::RenderingHelpers::SoftwareRendererSavedState>::drawGlyph+0x29
juce::RenderingHelpers::SoftwareRendererSavedState::drawGlyph+0xaf
juce::GlyphArrangement::draw+0x111
juce::GlyphArrangement::draw+0x28
juce::Graphics::drawText+0x10d
juce::Graphics::drawText+0x40
juce::Graphics::drawText+0x49
createImageFromText+0x127

 

It seems that the static GlyphCache& class GlyphCache::getInstance() function in juce_RenderingHelpers.h is not robust to multi-threaded initialization race condition.

Inherited class from GlyphCache do not seems to benefit from the powerfull juce_DeclareSingleton/juce_ImplementSingleton marco as others multi-threaded singleton in Juce.

Is it a design choice ?

Is juce::Graphics can only be created in the main thread ?
 

My application running on Windows 7 64bit or Windows 10 64bit

My application do not call the juce_opengl module.

I'm building the last Juce version in 64bit with JUCE_DLL=1 and JUCE_USE_DIRECTWRITE=0

With Visual Studio 2010 SP1 or Visual Studio 2015 update 1.

(I encounter crash with both compiler in both Windows platform)

 

Thanks in advance for your help on this subject.

Regards,

 

 

 


#2

None of the graphics/GUI code is designed to be thread-safe, it all expects to be called from the GUI thread.

A technique that usually works better for creating images like that would be to just generate them when they're first needed rather than pre-emptively on a thread.


#3

In fact, my secondary thread is not related to UI at all: it is a kind of “video exporter” whose output is a folder with one image per frame. I also have another use-case, in which I want to use the Font rendering engine in my rendering thread.

I understand that a widget system (juce_gui_basics, juce_gui_extra…) is expected to be mono-threaded because of the underlying os / event system. However, I’m rather surprised concerning the juce_graphics thread design.

Indeed, this module contains collection of atomic classes (juce::Image, juce::Colour, juce::Point, juce::GlyphArrangement…) that can be useful in non-UI related scenario.

In fact, juce_graphics works nearly perfectly in my multithreaded application, except in the rare boot case. That’s why I was pointing the singleton initialization problem and also why I would like to promote the ScopedLock based juce_DeclareSingleton on GlyphCache.


#4

You know, as long as you make sure your thread is started after the GUI thread has drawn some text, then you're probably totally safe. There'd only be a race condition at startup if the thread and GUI both try to initialise the fonts at the same time.


#5

Indeed, thanks, for the tips. I will try this hack.


#6

ok for the suggested hack, but alexdlabs has a point here:

Indeed, this module contains collection of atomic classes (juce::Image, juce::Colour, juce::Point, juce::GlyphArrangement…) that can be useful in non-UI related scenario.

Is it safe to use offscreen Graphics instances (that render to Image instances) in background threads?


#7

It's... usually safe. But not guaranteed.

If you're just drawing with the software renderer, or the CoreGraphics one, then it's probably completely fine. Nothing in the rendering code is locked, but it also doesn't have any shared state that I can think of.

OpenGL would be a problem though, because GL can only be used on the thread that has currently activated the GL context.


#8

It seems I’m having this same problem, and finally stumbling into this thread I’ve been able to finally work-around it somewhat …

I have an opengl rendering thread (i.e. using OpenGLRenderer) …

Within this thread, I’m sometimes creating opengl textures containing text, by creating a Graphics object for an Image object, and calling the Graphics::drawText() on the graphics object to draw to that image, and then creating the texture from that Image. I reasonably assumed that this should be a safe thing to do.

However, this has continued to occasionally cause random crashes (i.e. in GlyphArrangement::draw), though only/much-more on Windows (not on OSX). Though it’s only now that I finally understood that this (use of openglrendering thread and drawing of text) was the related / the cause…

It started to make sense when I read this thread about threading issues, as one way I found to make it more/quite likely to crash was to bring-up menus (which I guess causes more text rendering on the UI thread, while on OSX I use native menus, hence no/little other rendering of text besides what I’m doing, at least unless i.e. I very occasionally throw-up some dialog-box, hence why the problem doesn’t occur there / much less often).

So it looks like it’s these same threading issues causing my crashes. And so I’ve worked-around this for now by getting a MessageManager lock over the graphics.drawText() calls. However, even though I am caching these textures, this does obviously start to defeat the purpose of having this opengl rendering thread as it’s own thread, if doing any too-regular text drawing. So, any hints as to what particular bit of code/structure might try to protect with its own mutex within juce’s text-drawing/glyph-arranging to solve this? (rather than mutexing the global message-manager-lock which is no doubt unnecessary here)


#9

Ug, I spoke a little too soon though - it appears one can’t actually simply get a MessageManager lock inside the opengl rendering thread, as this can cause deadlocks on i.e. shutdown.

And passing current-thread to message-manager (to cause it to try-lock and abort if the thread has been signaled to exit) doesn’t appear to solve this it seems … (plus the implementation in MessageManagerLock makes the locking mechanism a bit too spinny for my liking). So just another reason to avoid the global lock here and just identify what needs protecting and protect it with a different mutex I guess?


#10

Seems putting a separate (global/static) mutex in juce_GraphicsContext.cpp, and holding it around all uses of GlyphArrangement, very significantly reduces the amount of crashing, without adding any deadlocking (and without the other downsides of having to share the message-manager-lock) …

However I did still manage to make the windows build crash once, but yeah, it’s not clear yet if this is related or some other totally separate issue. At least its a big improvement and on the right track …


#11

I just planned to make some background renderings, i agree there should be put some effort to make these classes thread safe, at least when used from one thread. Maybe the glyph cache should be generated for one instance per thread (thread local value) than there is no need for any locking


#12

No big deal here, its only a boot race condition.
As Julian first proposal sayd:
this be easily resolved by calling a little function in your main application thread: (your JUCEApplication::initialise function for instance)

static juce::Image uglyRandomCrashWorkaround()
{
  juce::Image image(juce::Image::ARGB, 64, 64, false);
  juce::Graphics g(image);
  g.fillAll(juce::Colours::black);
  g.setColour(juce::Colours::red);
  g.setFont(juce::Font(12));
  g.drawText("http://www.juce.com/forum/topic/threading-issue-graphicsdrawtext", 0, 0, 64, 64, juce::Justification::centred, false);
  return image;
}

#13

No its more than just a boot race condition - these crashes happen at a wide-variety of time-points following app-start, when plenty of text-rendering has occurred prior (and are due to threading issues, as I’ve ascertained through adding locking that should otherwise be unnecessary, and seeing that it reduces the crashes).

Also, I would suggest that - if this is considered to be the case, juce absolutely should simply just add a mutex around the corresponding initialisation code (that they believe needs to be protected from being called from two places at once), rather than suggest such workarounds. Such mutexing has virtually no cost unless the mutex actually needs to wait, which is only if+when its preventing precisely this crash.


#14

Having the same issue here now on a background thread.
Because of compatibility reasons (channel-design-api) for an older plugin I’m still on JUCE 4.0.
It seems to be windows related!
Has anybody an idea how this could be fixed? Maybe just adding a simple Mutex somewhere?

juce::WindowsTypeface::getOutlineForGlyph(int glyphNumber=0x0000004e, juce::Path & glyphPath={...}) Line 432	C++
    juce::Typeface::getEdgeTableForGlyph(int glyphNumber=0x0000004e, const juce::AffineTransform & transform={...}, float fontHeight=10.0000000) Line 123	C++
    juce::RenderingHelpers::CachedGlyphEdgeTable<juce::RenderingHelpers::SoftwareRendererSavedState>::generate(const juce::Font & newFont={...}, const int glyphNumber=0x0000004e) Line 301	C++
    juce::RenderingHelpers::GlyphCache<juce::RenderingHelpers::CachedGlyphEdgeTable<juce::RenderingHelpers::SoftwareRendererSavedState>,juce::RenderingHelpers::SoftwareRendererSavedState>::findOrCreateGlyph(const juce::Font & font={...}, int glyphNumber=0x0000004e) Line 197	C++
    juce::RenderingHelpers::GlyphCache<juce::RenderingHelpers::CachedGlyphEdgeTable<juce::RenderingHelpers::SoftwareRendererSavedState>,juce::RenderingHelpers::SoftwareRendererSavedState>::drawGlyph(juce::RenderingHelpers::SoftwareRendererSavedState & target={...}, const juce::Font & font={...}, const int glyphNumber=0x0000004e, juce::Point<float> pos={...}) Line 176	C+
    juce::RenderingHelpers::SoftwareRendererSavedState::drawGlyph(int glyphNumber=0x0000004e, const juce::AffineTransform & trans={...}) Line 2493	C++
    juce::RenderingHelpers::StackBasedLowLevelGraphicsContext<juce::RenderingHelpers::SoftwareRendererSavedState>::drawGlyph(int glyphNumber=0x0000004e, const juce::AffineTransform & t={...}) Line 2665	C++
    juce::GlyphArrangement::draw(const juce::Graphics & g={...}, const juce::AffineTransform & transform={...}) Line 797	C++
    juce::GlyphArrangement::draw(const juce::Graphics & g={...}) Line 765	C++
    juce::Graphics::drawFittedText(const juce::String & text={...}, const juce::Rectangle<int> & area={...}, 

Unhandled exception at 0x00007FFB925CB253 (MyPlugin.aaxplugin) in ProTools.exe: 0xC0000005: Access violation reading location 0x0000000038FC2048.

#15

Here is example Code to recreate the problem:

Its build x64 and WITHOUT direct write

#if JUCE_USE_DIRECTWRITE != 0
#error // this example works only when DIRECTWRITE is Disabled
#endif


class MainContentComponent : public Component, public Timer, public Thread
{
public:
    //==============================================================================
    MainContentComponent()
		: Thread("BackgroundThread")
	{
		setSize(600, 400);
		startTimer(1);
	}

	~MainContentComponent() 
	{
		stopThread(100);
	};

    void paint (Graphics& g)
	{
		g.fillAll(Colour(0xff001F36));
		g.setFont(Font(16.0f));
		g.setColour(Colours::white);

		static char s = 0;
		s++;
		g.drawFittedText(String::createStringFromData(&s, sizeof(s)), 0, 0, 400, 400, Justification::centred, 1, 1.);



		if (!isThreadRunning())
		{
			// Start thread after first use of drawText
			startThread();
		};
	}

	void run() override
	{
		Image testimage(Image::ARGB, 400, 400, false);

		Graphics g(testimage);

		char s = 128;
		while (!threadShouldExit())
		{
			g.drawFittedText(String::createStringFromData(&s, sizeof(s)), 0, 0, 400, 400, Justification::centred, 1, 1.);
			Thread::sleep(1);
		};
	};

    void resized() 
	{
		// This is called when the MainContentComponent is resized.
		// If you add any child components, this is where you should
		// update their positions.
	}

	void timerCallback()
	{
		repaint();
	}

private:
    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};

[SOLVED] Messed up fonts and DirectWrite
Font::getStringWidth on non-message thread
#16

I could recreate the problem with current code from the development branch @jules @fabian

Just use create a new application with projucer (without JUCE_USE_DIRECTWRITE) paste the example code above, start debugging. After a minute you have the error

(I hardly remember why i disabled JUCE_USE_DIRECTWRITE, i remeber there were problems in the past, maybe a quick fix is to enable it again, i will furthermore investigate if there is a problem with JUCE_USE_DIRECTWRITE)