Component repaint issue


#1

Hi Jules,

I’m seeing something unusual on OSX and was wondering if this is the intended behavior.

Calling repaint on certain component at the same time can make the whole UI redraw itself.
However this do not happen on Windows. Only the necessary Component are drawn on Windows while a lots are called on OSX.

However this doesn’t happen with all the component repaint(the ones that have called repaint theirslef). Looks like to be some kind of repaint rectangle simplification but I’m not sure
(all repaint rect are summed creating a bigger zone especially if the component asking repaint are on top left and bottom right)

As said previously this doesn’t happen on Windows. (git version from around two weeks)

Is this normal ?

Thanks,


#2

Not entirely sure, but on OSX I let the OS handle the repaint regions. It’s quite possible that when the dirty region becomes sufficiently complex, the OS just throws it away and uses the bounding rectangle instead. I’ve never seen that happen myself, but it’s certainly something that may be possible.


#3

I see.
Will dig a bit further see when it happens then

Thanks,


#4

Would it be possible to add back
[NSView getRectsBeingDrawn:count:] even with core graphics ?

From what I’ve read on the net and what I’ve checked in the code
The rect given to drawRect is indeed a merge of the two small rect I redraw if call successively. Of course one it top left and the other is bottom right :slight_smile:

http://www.cocoadev.com/index.pl?HowToSpeedUpDrawing

Thanks,


#5

[quote]Would it be possible to add back
[NSView getRectsBeingDrawn:count:] even with core graphics ?[/quote]

I don’t understand what you mean by “add back”…

Juce already already uses getRectsBeingDrawn to clip the Graphics object to the smallest possible set of rectangles. My point was that if the OS decides to only give us one rectangle to draw, there’s nothing that can be done about it - presumably if what you’re seeing is different from win32, then that must be what’s happening. I’ve already done everything that’s possible to only redraw the areas that the OS thinks need redrawing.


#6

I mean add back because if I check NSViewComponentPeer::drawRect
getRectsBeingDrawn is only called in the else so not happening if usingCoreGraphics is true

It could be used to have the exact clipBounds in coreGraphics as well.


#7

Sorry, I see what you mean.

…but it’s not needed for CoreGraphics, because the CGContextRef that is provided already contains the clipping region that the system wants you to use.


#8

In fact, the problem seems that in Component::paintComponentAndChildren, it checks for a clipping rectangle and not a more complex region.

I maybe don’t see this in Windows because it results in multiple consecutive paint instead of only one.

CoreGraphicsContext::getClipBounds could use getRectsBeingDrawn instead of CGContextGetClipBoundingBox

To Achieve the same results on mac it would means either split in multiple paint for each rect of the region or changing the whole clipping rect impl of Juce to handle region instead.

Tricky in both case :frowning:


#9

No, there’s nothing wrong with any of the Component code. I think what you’re seeing is because of the way LowLevelGraphicsContext::clipRegionIntersects() works - on win32, it can use the clipping rectangle to see if any part of the complex region intersects the rectangle, but on the mac there’s no equivalent so it has to just see if the bounding box intersects the rectangle.

It’d be impossible to use getRectsBeingDrawn to help, because that only tells you the initial clip region, and the graphics context’s clip region is constantly changing.


#10

If I read the code correctly, Component::paintComponentAndChildren only calls getClipBounds() which returns a rect then test it again its own bounds.
(juce_Component.cpp line 1802 and 1835)
Graphics::drawText indeed call clipRegionIntersects
Maybe paintComponentAndChildren should do the same thing

The impl of clipRegionIntersects on mac is another issue but so far even with the right info in clipRegionIntersects it wouldn’t work.

Thanks


#11

Ok, good point there… but if you say that you don’t see the problem on windows, then it can’t be because of paintComponentAndChildren doing something wrong, otherwise it wouldn’t work on windows either…?


#12

Right ! I think it works on Windows because of getClipBounds is impl using SavedState and clip that looks like to be saved when going from one child to another
and not static impl like on OSX . This is just a guess.
More knowledge how it really works in the Software renderer would help though.

Moreover I don’t understand why clipRegionIntersects couldn’t use getRectsBeingDrawn instead of CGContextGetClipBoundingBox (through getClipBounds) when !lastClipRectIsValid in CoreGraphicsContext.

Thanks a lot for looking into it.
I know that’s the kind of things you don’t want to mess with once it’s done.


#13

Because the context’s bounding box is constantly getting changed, it doesn’t just always stay the same as the view’s original clip region. In fact there may not even be a view if the context was created to draw onto an image.


#14

Ok I’ve found a fix which after test works fine.

It’s a start but what do you think ?

modification in NSViewComponentPeer::drawRect

[code]if (usingCoreGraphics)
{
CoreGraphicsContext context (cg, (float) [view frame].size.height);

    const NSRect* rects = 0;
    NSInteger numRects = 0;
    [view getRectsBeingDrawn: &rects count: &numRects];
    
    for (uint i = 0; i < numRects; i++)
    {
      CGContextSaveGState(cg);
      CGContextClipToRect(cg, CGRectMake(rects[i].origin.x, rects[i].origin.y, rects[i].size.width, rects[i].size.height));
      insideDrawRect = true;
      handlePaint (context);
      insideDrawRect = false;
      CGContextRestoreGState(cg);
    }
}[/code]

#15

Oh no, that’s not good at all! It’s very inefficient to make repeated calls to paint(), and could even end up with adjacent areas getting out of step with each other (e.g. if the drawing is animated based on the current time).


#16

:frowning:

I don’t see how it could be out of step with each other as I don’t change the global time but I won’t argue much on this.

Anyway, it would means having dedicated clipping class like in the SoftwareRenderer on Windows and init with the results of getRectsBeingDrawn in order to fix this properly.
Otherwise I was thinking of customizing this a bit in order to only apply it in corner cases: clipping rect way much bigger than the sum of rects being drawn, but I agree this is far from perfect.

I would be happy to have region support in CoreGraphics but after some search on the net, it doesn’t seem to be possible and this kinda sucks :frowning:


#17

To be fair, CoreGraphics does a great job of handling the clip region internally - the only problem is that it doesn’t provide a way to find out whether a rectangle intersects the current region.


#18

Yep.
I think I’m out of luck regarding this one example :frowning:


#19

Hi,

Sorry to dig up an old thread, but I’m having a problem with this too. I have two components that update frequently, but for each update all the components which sit between these two (and the parent component and even the content component in the background) are redrawn each time too :confused:

The point of failure seems to be in juce_Component.cpp line 1918, where Graphics::reduceClipRegion() calls CoreGraphicsContext::clipToRectangle() which incorrectly determines that the extra components should be painted. It seems to boil down to the fact that (as Jules mentioned) when CoreGraphics is managing the clip regions there is no way to test if our component (rectangle) intersects the clip region or not, but if this is the case then I think Juce should maintain it’s own RectangleList of clip rectangles (as it does in the SoftwareRenderer), because performancewise it is really crippling as it stands!

A small example to demonstrate:

[code]class testRedraw:public Component
{
public:
testRedraw(Rectangle pos, String name)
{
setBounds(pos);
setName(name);
setOpaque(true);
}
virtual void paint (Graphics& g)
{
Rectangle r = g.getClipBounds();
printf(“painting %s: (%d, %d, %d, %d)\n”, (const char*)getName().toUTF8(), r.getX(), r.getY(), r.getWidth(), r.getHeight() );
g.fillAll(Colour(0xFF000000 | rand()));
g.setColour(Colour(0xFF000000));
g.drawRect®;
}
} *test1, *test2, *test3;

class MyContentComponent : public Component, public Timer
{
public:
MyContentComponent()
{
setName(“MyContentComponent”);
setSize(500, 400);
addAndMakeVisible(test1 = new testRedraw(Rectangle(20,20,100,100), “A”));
addAndMakeVisible(test2 = new testRedraw(Rectangle(140,20,100,100), “B”));
addAndMakeVisible(test3 = new testRedraw(Rectangle(260,20,100,100), “C”));
startTimer(500);
}
virtual void timerCallback ()
{
test1->repaint();
test3->repaint();
}
virtual void paint (Graphics& g)
{
printf(“painting content component…!\n”);
g.fillAll(Colour(0xFFFFFFFF));
}
};[/code]

outputs:

painting content component...! painting A: (0, 0, 100, 100) painting B: (0, 0, 100, 100) painting C: (0, 0, 100, 100) painting content component...! painting A: (0, 0, 100, 100) painting B: (0, 0, 100, 100) painting C: (0, 0, 100, 100)
etc…

Any ideas how this can be improved??


#20

I don’t think that’s even possible, TBH, because of possible affine transforms, etc. You either have to go with CoreGraphics all the way, or not.

One possible idea that I think I mentioned on another thread would be to do the test differently,so to test whether a rectangle intersects the clip region, you’d do it destructively, i.e.

  • save the graphics state
  • clip to the rectangle in question
  • see if the clip is now empty or not
  • restore the state

…which is a horribly long-winded way to do it. I’ve not tried it myself, would be interested if anyone wants to experiment.