FR: Callback or other mechanism for exposing Component debugging/timing

Probably a long shot, but it keeps bubbling to the surface so I’ll throw it out there.

It would be amazing if the framework helped us access paint call frequency / times. For example the ability to supply a callback function to be run “beforePaint” and “afterPaint”. Or a macro that when enabled dumps some internal timing information from paintComponentAndChildren into the component’s properties.

Just ideas!

It came up again because I’m working on the next version of my component inspector and would love to display the last paint timings of the selected component in the inspector to aid with debugging.

For now, I’ll give this a go by inheriting from a custom component — I just wanted to share a concrete example of what could be built if debugging/timing abilities were exposed.

This is somewhat relevant, so just making sure you’re aware of the VBlankAttachment class. All such attachments are triggered before the paint() calls for that particular frame. So this probably meets the beforePaint requirement. I can’t recall anything for afterPaint though.

:exploding_head: Woah, this is new! Interesting…

This looks like a great way to get notified of the when the peer starts painting. For example, to calculate the overall frame rate (or one half of the data needed to determine overall paint time).

I couldn’t figure out how to only get notified when the call pertains to a specific component, though…

I tried adding an VBlankAttachment with a DBG in it for a component and it constantly fires (vs. firing when the specific component’s is dirty, visible, etc). It seems like the component relationship exists only to choose which peer the callback is attached to? So it doesn’t seem like it would be usable for a per-component beforePaint

Btw, looks like the filenames have a typo: VBlankAttachement.h (extra e).

Thanks Attila!

You’re right, it’s called at every VBLank event, and it’s main use case is to update the Component’s state in lockstep with the screen refresh and have smooth animations that way. So you’d actually call repaint() from inside a VBlank callback when necessary.

The Component relationship is indeed for deciding which peer to attach to.

I can see now that it’s not exactly useful for your use case, because it doesn’t in any way line up with the paint times of one particular Component. In fact first all VBlankAttachment will be notified, then all Components that need it will be redrawn. So there will be a varying amount of time before the VBlank and the call time of paint() for one particular Component depending on how long other components took to paint.

I’d like to say it’s the VBlankAttaché’s job to relay these events, so there’s an extra ment at the end :wink:, but you’re right.

1 Like

:rofl:

Ahhhh, awesome, so this is the underpinning of animation support? Meaning, I can toss out all the timers I’m using for animation and replace them with a VBlankAttachment which passes a callback that calls repaint() to have perfectly synced frames. Looking forward to giving that a go!

I can see now that it’s not exactly useful for your use case

It will let me add an FPS counter to the inspector (which is cool!), but I’m hoping the framework can provide a bit of debugging/introspection/hooks at the per-component level…

I have implemented this several times and it is very useful. Most recently for reFX, please see the reFX repo and consider implementing something similar. JUCE/juce_Component.cpp at reFX/develop · reFX/JUCE · GitHub

It’s two macros, START_PAINT/END_PAINT and if they aren’t defined then there is no performance impact at all.

It allows for some super cool visualizations like this that make it immediately obvious when you have painting performance issues:

9 Likes

Ur my hero!

This is exactly the type of tooling I’d love to be able to build for the community. Guarded with a macro (so it can also run in Release) is perfect, though for everyone to take advantage of the tool as a module, callbacks would need to be in JUCE proper….

Loving the histogram — thanks for sharing this!

2 Likes

If it can be of any help, I have this little class template that acts as a wrapper for any Component, to inject code to be executed together with its paint() method.
See the doc there for explaination

#pragma once

/** Adds to a Component a std::function member that is called when its paint()
 method is invoked.

 Usage example:
 ```
 auto label = WithLambdaPaint <juce::Label> ("my label", "Some text");

 label.onPaint = [this] (Graphics& g)
 {
    // this code is "injected" before the actual Label::paint()
    g.setColour (Colours::green);
    g.fillRect (label.getLocalBounds ().reduced (10));

    paintBaseClass (g);    // <- this calls Label::paint()
 };
 ```
 */
template <typename BaseComponent>
class WithLambdaPaint : public BaseComponent
{
public:

    using BaseComponent::BaseComponent; // import the same constructors of the base class

public:

    /** When this member has a value, the `paint()` callback of this class
     invokes it and returns. In that case, it's up to the assigned functional to
     call the paint behaviour of `BaseComponent`, if desired.
     Because `paint()` is a protected function of JUCE Components, the public
     `paintBaseClass()` function is provided, which can be called in place of
     `BaseComponent::paint()` where needed.
     When this member has no value, the `paint()` callback of this class invokes
     `BaseComponent::paint()` directly, preserving the existing behaviour of
     `BaseComponent` when it is not customized via `onPaint`. */
    std::function <void (juce::Graphics&)> onPaint;

    void paintBaseClass (juce::Graphics& g)
    {
        BaseComponent::paint (g);
    }
    
public: // overridden methods

    void paint (juce::Graphics& g) override
    {
        if (onPaint)
            onPaint (g);
        else
            BaseComponent::paint (g);
    }
};
3 Likes

Love it! Thanks for sharing, helpful to see, using a base class was the general route I planned on taking for the inspector for now…

That is a great development tool indeed !

And thanks for sharing reFX JUCE fork ! It contains a bunch of fixes i was looking for, but never had the time to investigate into JUCE internals ! Really hope some of those fixes will get into JUCE someday, but i my usual feeling is coming back on this one…

1 Like

Just to followup, I’ve been using a template method pattern-ish thing as a stopgap to track paint times on my components:

class MelatoninComponent : public juce::Component
{
public:
    void paint (juce::Graphics& g) final
    {
        // Log start time here with juce::Time::getHighResolutionTicks()

        timedPaint (g); // will calls the derived class' paintContent method

        // Log end time here
    }

protected:
    // derived classes implement this
    virtual void timedPaint (juce::Graphics& g) {}
};

class DerivedComponent : public MelatoninComponent
{
protected:
    void timedPaint (juce::Graphics& g) override
    {
        // normal component painting stuff here
    }
};

I hand-roll most components anyway, so they are already derived from MelatoninComponent (which has additional features like padding, etc) and rename my paint methods to timedPaint. So this solution doesn’t work for juce::Components buried in stock widgets.

Would still love it if the JUCE team added some simple hooks (such as @RolandMR shared) in juce_Component.h — a small addition with a lot of upside for JUCE tooling!

1 Like

lol I wanted to investigate how other apps / frameworks display paint timing metrics, specifically how they display times that include children vs. exclusive.

Google had other things in mind. Escalates quickly!

4 Likes