[Bug] Serious degrade of text updating in CodeEditorComponent/AsyncUpdater JUCE8

So I ran a similar experiment to your timerCallback one with a 10ms interval, here were my findings

  • With your test project (CodeEditorTest3), I see a similar pattern of results. However, my results aren’t as extreme, instead of ~75ms I was getting ~25ms

  • If I stop adding messages to the CodeEditor and instead log them using DBG I don’t see the same issue

  • Disabling font fallback on the font in the CodeEditor significantly improves the situation for me, although it doesn’t eradicate the issue (the peak callback time dropped to ~15ms)

  • Increasing the timer interval to roughly the peak time (in my case~25ms) effectively avoids the pattern

  • An interesting observation I had was that the issue doesn’t occur until the text exceeds the end of the visible page, causing scrolling

  • In addition to that observation I found if the window is made small enough with fewer lines of text I no longer observe the same behaviour (at least at a 10ms interval), and a larger window gets worse

  • In a profiler the hot path is preoccupied by shaping and other text/font rendering

  • If I do a git bisect the first commit that demonstrates this issue is Remove TextLayout::createNativeLayout() · juce-framework/JUCE@5195545 · GitHub

  • After further investigation the only reason that commit causes issues is because of a switch from the native text layout to a standard text layout

  • If the standard text layout is enabled for prior commits, the first commit that demonstrates the issue is Typeface: Implement platform typefaces using Harfbuzz hb_font_t · juce-framework/JUCE@0d2e34f · GitHub

In summary I’m still not convinced there is a problem with AsyncUpdater or Timer. The observed behaviour appears to be caused by the newer text rendering. As I said before we are aware of this and some work will be carried out but the performance is not likely to ever be exactly the same as it was before.

@anthony-nicholls Thank you for your detailed look into this!

I can confirm this. It does seem to support your theories.

How does one accomplish this? Is it a lot of modifications, or relatively simple?

I only used the Timer in the one example as a test. The AsyncUpdater really seems the way to go here, I wonder how that would affect this?

I hope you can fix this to a degree that the CodeEditorComponent is useable again as a real-time debugging tool. Thanks for all you and the team do.

For those following this thread with much interest, albeit possibly quite glib, could you guys please update us with the OS you are testing on - is it Windows or MacOS? What style of scrollbar do you have enabled on your EditorComponents?

My experience with other frameworks has led me to always be quite suspicious of issues surrounding scrolling performance, in general, as different OS’s have different problems with this, imho …

Very simple, you just need to call setFallbackEnabled (false) on the font in question. For example…

auto f = messageDisplay->getFont();
f.setFallbackEnabled (false);
messageDisplay->setFont (f);

I found it slightly more erratic to measure but fundamentally I suspect it is just subject to the same issue but I think because it doesn’t have a set time in which it should be arriving the overall measurements are just more erratic. It’s easier to spot the pattern with Timer.

MacOS 11.10.7. Intel. All of the discussions in here reference a test app with whatever the default scrollbar is; also in my real app I’m doing nothing special about a scrollbar; it’s the default.

I can confirm that using this:

auto f = messageDisplay->getFont();
f.setFallbackEnabled (false);
messageDisplay->setFont (f);

…cuts the impact of the issue approximately in half. Certainly worth using for now, in my use case.

1 Like

I can confirm that disabling font fallback improves text rendering performance quite a bit. Still not at juce 7 levels though, and disabling fallback is not an option if you have any customers who want to use their own languages as text input. Which we do.

As I’ve said above text rendering will inherently be more expensive than it was before because there is more it can do. How did you get on with using the AffineTransform? anything you can do on your end to prevent the need to reshape the text should have a very positive impact on performance. We already perform caching for this reason but if significant amounts of text are constantly being reshaped the cache isn’t going to help.

You’ve also said “some work is or will be carried out to improve things” or something to that effect - how’s it going?

Does this include scale modification? This is a pretty common way of handling window resizing. Does having a component at non unity scale severely impact text rendering performance?

There’s on going work relating to the TextEditor, which is in progress, it’s hard to say how long that might take.

OK, here’s an example project showing very poor performance of a simple tableview with scroller. Scroll up and down with click-drag or with mouse wheel.

I switch global paths between juce6 and juce8 and the difference is masssssssive.

There are 2 versions of TableListBoxModel subclasses in here you can try - one which uses drawText for every cell paint, and another which creates juce::Labels for every cell and then never touches them again. In both cases, the Juce8 performance is very poor compared to juce6. Haven’t tried juce7 as I don’t have a copy lying around but in earlier tests 7 was comparable to 6.

Yes I tried in debug and in Release.

testTextScroll1.zip (6.9 KB)

Sorry, I should add that I’m testing using juce 8.0.6

@thecargocult thanks for sharing. I think the problem here is just that the cache gets overwhelmed with all the different glyph arrangements so everything keeps getting reshaped as a result. Could you please try applying the following change and letting me know how that works for you?

JUCE-dev-14-22-34.patch (701 Bytes)

2 Likes

Wow - the difference is like night and day.
Could this be the underlying issue for all the other reported text performance issues?

1 Like

As always it’s a little complicated, in a lot of cases increasing the cache size may well improve things but each case warrants a closer investigation as other methods may be even better. The problem with growing the cache is you’re just pushing the problem out. That being said I think it should be bigger than it currently is. There may be other more intelligent ways we could cache (or choose not to!) in some circumstances too.

I’m fairly sure this will ultimately boil down to an AffineTransform, I wouldn’t expect the font size to change as a result of window resizing. Unless you are tracking your own scale factor and multiplying by some font size? If it does come down to an AffineTransform there should be no additional reshaping. If you have a specific use case in mind you’re concerned about please share an example and I’ll try to confirm either way for you.

1 Like

Thanks for the quick reply!

That would be something like that:

#pragma once
#include <JuceHeader.h>

struct ScaledComponent : public juce::Component {
    void paint(juce::Graphics& g) override {
        g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId));
        g.setFont(juce::FontOptions(40.0f));
        g.setColour(juce::Colours::white);
        g.drawText("Hello World!", getLocalBounds(), juce::Justification::centred, true);
    }
};

class MainComponent : public juce::Component {
public:
    MainComponent() {
        setSize(refWidth, refWidth);
        addAndMakeVisible(scaledComponent);
    }

    void resized() override {
        scaledComponent.setBounds(0, 0, refWidth, getHeight());
        scaledComponent.setTransform(juce::AffineTransform::scale(getScale()));
    }

private:
    float getScale() {
        return (float)getWidth() / refWidth;
    }

    int refWidth = 600;
    ScaledComponent scaledComponent;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

In that example should I expect an increased load when the scale is not 1.0f ?
(Not while resizing, but once resized with a non-unity scale)

I’v not tested (I’ll confirm tomorrow), but just by looking at it I wouldn’t expect so as the font size remains the same and the Component has an AffineTransform applied via setTransform. So reshaping is already being avoided.

If there were a problem then in most cases it would only have an impact while resizing. It’s only in more extreme cases (such as lots of different text spread across lots of components) that the cache could become so overwhelmed that it’s effectively made redundant.

1 Like

So would this cache thing affect the original issue I reported here? (rapidly scrolling text in a CodeEditorComponent)

from what I’ve seen I would expect it to make a difference. Anywhere in our app that we had lots of text being scrolled around, this cache size change has improved performance.