Why is SolidColourEdgeTableRenderer showing up then?
I did some performance tests with setPaintingIsUnclipped() way back when and didn’t see any appreciable difference in the profile.
I believe that the top spot of getARGB() is a symptom of calling PixelARGB::blend(). There’s no good reason for this function to be called when drawing solid rectangles. That, and because we’re seeing SolidColourEdgeTableRenderer makes me think that your response to Component::paint() is drawing anti-aliased polygons which is a ticket to the slow lane.
Maybe it’s showing up because I’m using the CoreGraphics renderer?
[EDIT: NO I WASN’T]
Maybe it’s showing up because I’m using the CoreGraphics renderer?[/quote]
Hmm…what’s the point of using CoreGraphics if it doesn’t rasterize polygons for you using some kind of “extra” (hardware acceleration if available)? Try turning it off and look at the profile.
There should be absolutely NO blending of any sort going on if you are only going through YourLevelMeter::paint().
Looking at the profile and the Juce code, I see some things I don’t like:
juce_RectangleList.cpp
Rectangle<int> RectangleList::getRectangle (const int index) const noexcept
{
if (isPositiveAndBelow (index, rects.size()))
return rects.getReference (index);
return Rectangle<int>();
}
Say what? Every call to getRectangle() is bounds-tested?
The prominence of RectangleList::subtract probably has to do with EdgeTableRegion::clipToRectangleList. Obviously the run time of clipToRectangleList() is linear with the number of rectangles and we’re paying for that ridiculous isPositiveAndBelow which is totally unnecessary. Going from 60 to 30 rectangles in the rectangle list will cut the number of calls to clipToRectangleList() exactly in half, consistent with your observation of performPendingRepaintsNow() behavior.
It looks like Jules has some profiling to do, after all of the changes to rendering some hot spots have reared their ugly head again!
This is troubling:
juce_mac_CoreGraphicsContext.cpp
bool CoreGraphicsContext::clipToRectangleList (const RectangleList& clipRegion)
{
if (clipRegion.isEmpty())
{
CGContextClipToRect (context, CGRectMake (0, 0, 0, 0));
lastClipRectIsValid = true;
lastClipRect = Rectangle<int>();
return false;
}
else
{
const int numRects = clipRegion.getNumRectangles();
HeapBlock <CGRect> rects (numRects);
for (int i = 0; i < numRects; ++i)
{
const Rectangle<int>& r = clipRegion.getRectangle(i);
rects[i] = CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight());
}
CGContextClipToRects (context, rects, numRects);
lastClipRectIsValid = false;
return ! isClipEmpty();
}
}
Heap block allocation, followed by O(N) operation where N =~ number of rectangles in the clip (30 or 60 in your case).
If all else fails, instead of calling repaint() on a batch of 30 and then performPendingRepaintsNow() try doing them one at a time so the clip only has a single rectangle.
Anyway, that’s about all I can think of. Sniff around and find out why there’s blending going on, and why if you’re using CoreGraphics that JUCE is doing the rasterization manually. I will be interested to see what we find tomorrow because my app performs like a champ on Windows but on the Macintosh when I resize my main application window it is clunky and moves like my grandmother in a nursing home.
I didn’t use the CoreGraphics renderer, I checked. Calling performAnyPendingRepaints() on every component makes the CPU jump up a lot. It’s not a good idea, I tried it too.
Anyway, the core problem seems to be that when the list op clipping rectangles gets as high as 60, JUCE is spending more time with doing manipulations on that list then the on the actual drawing itself.
Why don’t you use the CoreGraphics Renderer?
It makes no big difference in speed here. Tried it. BTW, this example uses 60 components. Now imagine having hundreds of components on screen. I don’t wanna know how many intersections have to be checked there, how many regions have to be excluded etc…
zamrate, are you actually using an optimised release build??
Your profile looks very suspicious - the top entry is Array::getReference(), which is a method that the compiler should have optimised down to a couple of machine instructions, unless you’ve got debugging enabled, in which case it would do some assertion checking using isPositiveAndBelow(), which is the second item in your list…
This was a debug build, yes, BUT: the difference is also there when making the release build on Windows, just tried it.
In fact, on Windows, with an optimized release build, I get 12% CPU usage when using getPeer()::performanAnyPendingRepaints() on each 30 level meters, and above 20% CPU usage when not doing that. So this whole clipping regions stuff really takes time. I had setOpaque() and setPaintingIsUnclipped() both set to true on all my components FWIW.
I suggest doing some performance tests with lots of components on screen, as in a real app with possibly hundreds of components.
Try leaving setPaintingIsUnclipped() to false. Ironically, setting this causes MORE clip rectangles in the RectangleList rather than less! “Unclipped” doesn’t mean ignore all clipping, it means don’t reduce the clipping region to the intersection of the clip region and the component’s bounds.
In a normal paint scenario, paintComponentAndChildren() should reduce your 60 rectangles down to 1 when an individual Component paints (since its bounds only overlaps one dirty rectangle). But when you set the unclipped painting, it never removes the rectangles that don’t overlap the Component bounds. Therefore, you end up in paint() with a RectangleList that has 60 rectangles instead of 1 even though they are non-overlapping.
At least, I think thats how it works (Jules?)
You never explained why anti-aliased polygon drawing is taking place either, when it should all be fillRects()
Yes, that’s worth a try. If you’ve written a paint method that’s guaranteed not to draw beyond its own bounds, then it’ll save a bunch of rectangle list operations. Read the comments for it though, you have to be a bit careful when you use it.
The only operations that it saves are when there are components that overlap on top of the component being drawn. For the use-case in the original post, nothing is saved (since no components are on top). In fact, turning off clipping causes the RectangleList to be EVEN BIGGER! This is because reduceClipRegion() doesn’t get called when child.flags.dontClipGraphicsFlag == true:
juce_Component.cpp
...
if (child.flags.dontClipGraphicsFlag)
{
child.paintWithinParentContext (g);
}
else if (g.reduceClipRegion (child.getBounds()))
{
...
paintWithinParentContext() is getting a RectangleList with 60 rectangles in it, instead of just one.
Still haven’t heard an explanation for why anti-aliased polygons are being drawn.
This is the call stack on Windows, using software-renderer. Seems fillRect() fills line by line? Anyway, the class you though that would not be in there, SolidColourTableEdgeRenderer is definitely in there!
DrawSpeedTest.exe!juce::SoftwareRendererClasses::SolidColourEdgeTableRenderer<juce::PixelRGB,0>::handleEdgeTableLineFull(const int x=54, const int width=10) Line 96 C++
DrawSpeedTest.exe!juce::SoftwareRendererClasses::ClipRegion_RectangleList::SubRectangleIterator::iterate<juce::SoftwareRendererClasses::SolidColourEdgeTableRenderer<juce::PixelRGB,0> >(juce::SoftwareRendererClasses::SolidColourEdgeTableRenderer<juce::PixelRGB,0> & r={...}) Line 1562 C++
DrawSpeedTest.exe!juce::SoftwareRendererClasses::ClipRegionBase::renderSolidFill<juce::SoftwareRendererClasses::ClipRegion_RectangleList::SubRectangleIterator,juce::PixelRGB>(juce::SoftwareRendererClasses::ClipRegion_RectangleList::SubRectangleIterator & iter={...}, const juce::Image::BitmapData & destData={...}, const juce::PixelARGB & fillColour={...}, const bool replaceContents=false, juce::PixelRGB * __formal=0x00000000) Line 1165 C++
DrawSpeedTest.exe!juce::SoftwareRendererClasses::ClipRegion_RectangleList::fillRectWithColour(juce::Image::BitmapData & destData={...}, const juce::Rectangle<int> & area={...}, const juce::PixelARGB & colour={...}, bool replaceContents=false) Line 1475 + 0x18 bytes C++
DrawSpeedTest.exe!juce::LowLevelGraphicsSoftwareRenderer::SavedState::fillRect(const juce::Rectangle & r={…}, const bool replaceContents=false) Line 1870 + 0x8f bytes C++
DrawSpeedTest.exe!juce::LowLevelGraphicsSoftwareRenderer::fillRect(const juce::Rectangle & r={…}, const bool replaceExistingContents=false) Line 2185 C++
DrawSpeedTest.exe!juce::Graphics::fillRect(int x=0, int y=0, int width=10, int height=300) Line 335 + 0x41 bytes C++
DrawSpeedTest.exe!LevelMeter::paint(juce::Graphics & g={…}) Line 29 C++
DrawSpeedTest.exe!juce::Component::paintComponent(juce::Graphics & g={…}) Line 1805 C++[/code]
DrawSpeedTest.exe!juce::Component::paintComponentAndChildren(juce::Graphics & g={…}) Line 1821 C++
DrawSpeedTest.exe!juce::Component::paintEntireComponent(juce::Graphics & g={…}, const bool ignoreAlphaLevel=false) Line 1919 C++
DrawSpeedTest.exe!juce::Component::paintWithinParentContext(juce::Graphics & g={…}) Line 1811 C++
DrawSpeedTest.exe!juce::Component::paintComponentAndChildren(juce::Graphics & g={…}) Line 1854 + 0xc bytes C++
DrawSpeedTest.exe!juce::Component::paintEntireComponent(juce::Graphics & g={…}, const bool ignoreAlphaLevel=false) Line 1919 C++
DrawSpeedTest.exe!juce::Component::paintWithinParentContext(juce::Graphics & g={…}) Line 1811 C++
DrawSpeedTest.exe!juce::Component::paintComponentAndChildren(juce::Graphics & g={…}) Line 1875 C++
DrawSpeedTest.exe!juce::Component::paintEntireComponent(juce::Graphics & g={…}, const bool ignoreAlphaLevel=true) Line 1919 C++
DrawSpeedTest.exe!juce::ComponentPeer::handlePaint(juce::LowLevelGraphicsContext & contextToPaintTo={…}) Line 137 C++
Line by line is how polygons get rasterized. JUCE feels that you need a 4-point polygon fill instead of the special case of solid colour opaque rectangle fill with no transform.
juce::LowLevelGraphicsSoftwareRenderer::SavedState::fillRect(const juce::Rectangle<int> & r={...}, const bool replaceContents=false) Line 1870 + 0x8f bytes C++
juce::LowLevelGraphicsSoftwareRenderer::fillRect(const juce::Rectangle<int> & r={...}, const bool replaceExistingContents=false) Line 2185 C++
Get a breakpoint in your call to Graphics::fillRect() from Component::paint() and step into SoftwareRendererSavedState::fillRect (const Rectangle& r, const bool replaceContents) (juce_RenderingHelpers.h) and figure out why its going into fillShape() or fillPath() instead of fillRectWithColour():
juce_RenderingHelpers.h
void fillRect (const Rectangle<int>& r, const bool replaceContents)
{
if (clip != nullptr)
{
if (transform.isOnlyTranslated)
{
if (fillType.isColour())
{
Image::BitmapData destData (image, Image::BitmapData::readWrite);
clip->fillRectWithColour (destData, transform.translated (r), fillType.colour.getPixelARGB(), replaceContents);
}
else
{
const Rectangle<int> totalClip (clip->getClipBounds());
const Rectangle<int> clipped (totalClip.getIntersection (transform.translated (r)));
if (! clipped.isEmpty())
fillShape (new ClipRegions::RectangleListRegion (clipped), false);
}
}
else
{
Path p;
p.addRectangle (r);
fillPath (p, AffineTransform::identity);
}
}
}
It seems that either fillType.isColour() is false for you, or you set a matrix where transform.isOnlyTranslated is false.
Listen, you have the call stack there. I made it especially for you. And you can clearly see that fillRect() calls fillRectWithColour() there?!!
BTW not only paths are filled using the EdgeTableRenderer, everything is, even fonts, AFAIK.
