Ongoing issues with repaint area on OS/X


#1

I have an ongoing issue where the repaint area on OS/X is far, far too big - and thus my program’s CPU usage becomes extremely high.

My main page has a cursor moving in front of a waveform. Unfortunately, even tiny cursor movements generate a repaint for the entire waveform. When the cursor is moving rapidly, as it does when it is entirely zoomed in, this results in my program being so CPU-bound it won’t even reply to menu commands.

I first encountered this issue last year. The code has changed a lot since then, we’ve finishing through beta testing and found it solid, but I still have this issue.

I have instrumented my code - I am never directly calling repaint. All I am doing is moving this tall, thin (<10 pixels) cursor in front of my generic Component, and yet when paint is called, all or almost all of the visible canvas is repainted every time.

I had some complicated hack before involving internalRepaint - which doesn’t work any more. I could rewrite it using CachedComponentImage - but when you look at that solution, you can see why the internalRepaint hack was stupid.

Basically, what I’d have to do was implement CachedComponentImage but simply ignore any calls to invalidate, instead invalidating “by hand”.

This is a terrible solution. It resulted in bugs: when I brought up the “about” screen (a floating component), it would (of course) mess up the waveform and it wouldn’t know about it. When I minimize and maximize, it (of course) doesn’t know it needs to repaint and the whole waveform is messed up - I never even managed to fix that issue.

My current solution is simply to prevent the user from zooming in too far so the cursor can’t move too fast - but that’s really lame :frowning: since that makes the minimum window about 10 seconds.

I’m VERY open to the idea that it’s some bug of mine, but I’m hard-pressed to see what it could be now I have everything instrumented… I move the thin component a few pixels, and then I get a repaint for a huge area, my code literally isn’t called in the interim.


#2

I instrumented Juce and did some experiments and I think I can see the problem!!

I’m still not at all convinced I’m doing anything wrong - it seems like Juce is doing its repaints at the wrong level… perhaps there’s a simple call I don’t know to make… or some obvious structural error?

Let’s name my components.

[list]* Parent is the top-level (content) component.

  • Parent contains Waveform (the one I’m having repaint issues with).
  • Parent also contains Controls (which contains my clocks and things).
  • And Waveform contains Cursor, the time cursor, which is moved across it.
    [/list]
    (There are other components but I’m 95% sure they are uninvolved.)

I’m logging every component’s call to internalRepaintUnchecked (lots of output!) as well as calls to Waveform::paint and it’s instructive.

If I turn off the cursor movement, I see a lot of correct repaints to my clocks, and to Controls and Parent, which is just as I expect. And I don’t get any paint() calls at all to Waveform.

If I turn the cursor movement back on again, I see that Parent is getting repaints for the cursor (all the way to the left in my tests) and for the clock (most of the way to the right). So Parent’s dirty rectangle is more or less the entire window… and it’s this dirty rectangle that’s being used to repaint the Waveform!

So this proves several things to me.

[list]* I believe my components are in fact behaving as expected.

  • Now that I have consistent, if weird, behavior, I see how to work around it, if I had to.
  • That workaround would be pretty annoying - I’d have to create a CachedComponentImage, render my layout to that, and keep two separate dirty rectangles.[/list]

But I feel there has to be a better, more Jucey way.


#3

Sure, make your “Cursor” a inside a new TopLevelWindow and no more in your waveform.
Juce will create a new peer for it, so the OS will be able to compute the clip area and only that part will be repaint.


#4

If I recall, doesn’t Component have a “always invalidate entire Component on mouse move” flag somewhere?

ah yes Component::setRepaintsOnMouseActivity()

???


#5

I have tracked the issue down to Component::internalRepaintUnchecked.

In many animation systems, if you dirty the (proper) child of a top-level, opaque component, you don’t dirty the opaque component’s parent. It appears in Juce that this is NOT the case - look at Component::internalRepaintUnchecked, where a repaint on a lightweight child component causes a repaint of its parent even if the child is opaque.

The reason, unfortunately, seems to be that paintOverChildren might be non-trivial, and in that case a parent Component might need to know that even its opaque children were dirtied. This is unfortunate, because using paintOverChildren is not so common, and many applications are taking a pretty serious performance hit for a feature they never use.

It’s particularly tragic in my case, since none of the parents do any painting at all…

I have a Good Solution for this, which I’m write up in a separate thread.

TheVinn: the mouse shouldn’t be implicated here…? I just press play and the Cursor starts moving - I get this phenomenon even if the cursor isn’t in the window at all.

X-Ryl669: In fact, that would kinda work. But that’s a solution that works badly for me, and many applications. I have a lot of cursors - I only animate the one - and they’re all logically contained within the Waveform. The cursors are not opaque (I draw a thin cursor but I allow you to grab the cursor several pixels beyond the visible cursor…) which is very expensive for top-level windows.

If I had to go that way, I’d move the Waveform component to be the top-level, heavyweight component - which is logically closer to the truth.


#6

Whoops!! When I saw “Cursor” I assumed you meant that when the mouse moves over the waveform, the Component redraws over and over. Now I see you mean the Cursor is a graphical indication of the play position.


#7

Yes, it was definitely my fault in just telling you the issue, not showing you a picture!

See my more complete notes in the next thread…


#8

Because of that, i split my waveform into many parts / in separate components, which can be painted separately. I have master class for this, which is managing the parts.

A similar technique is used in modern tablet computers, splitting a website in several parts and use them as gpu-texture, to realize where smooth scrolling…


#9

Good idea, I’m probably going to do something like this in the next release of my code (I’d love to see the code if you cared to share it…!)

I should add that this did come to a fairly satisfactory resolution. I moved all of my rendering into cache which was helpful. I jiggled my screen layout in a way I was planning to anyway that tended to cut down my refresh rectangles quite a bit.

I was deliberately testing this with an unoptimized version of the code. It’s a habit I’ve had for years - on the theory that it exposes issues that people on old machines will be seeing.

Putting these altogether, less than an hour’s work, and I got good results. If I let you scroll all the way in, there are still some issues, but I can let you scroll in as much as you’d reasonably need without issue.