When there’s a patch change in my plug, most controls are updated. Their rects are collected and draw()'s are called.
Update rectangles overlap; in a major update 2/3 of rect updates overlap with earlier rects.
Many controls are thus repainted multiple times. (I noted this due to semitransparent text, which gets brighter with each redraw.)
-----EDIT much later:
The above was my misunderstanding. Controls dont overlap, the update rects don’t overlap, but update rects hit the same CViewContainers multiple times.
The update code seems terribly O(N^2+), because every update causes a wide search for all controls that might be touched by the update rect.
WHAT HAPPENS
(i) We call setDirty to mark controls for update.
(ii) Send their rects to Windows.
(iii) Get back rects from Windows (sorted by Y and X, hacked into more thinner wider rects).
(iv) Then search for controls overlapping the update rects and repaint these.
In detail,
(i) Dirty controls are collected by CFrame::CollectInvalidRects.
(ii) and sent to WinAPI InvalidRect() function.
(iii) When WM_PAINT fires, win32Frame::paint asks to get them back using GetRegionData().
(There are usually more rects than we sent in. I see a bunch of 1-pixel lines split off from their controls.
This must be due to the slice-and-sort behavior. Details in github wine/dlls/gdi32/region.c.
Not sure if this is the best behavior for controls.)
-----EDIT this above is the real problem -----
(iv) Here’s the O(N^2) bit: For each rect that we get from the Windows collection, CFrame::platformDrawRect calls CViewContainer::drawRect, which loops over all CFrame children and (because they are containers) most of their subchildren, to find the ones to redraw.
In a typical update of my 35 main controls (=> 60 rects), there are 120+ loops over 10-40 controls, in all 2500+ CView object accesses.
40/60 drawRect updates are fully overlapped by previous rects, so 2/3 of the loops are unnecessary.
This may be my own fault. Maybe we’re not supposed to use CViewContainers lightly?
I have them as group boxes just to draw a nice frame.
WHAT TO DO ABOUT IT
Unless this is known behaviour solved by using the library correctly, as anybody with half a pixel for brains would,
(1) Skip the overlapping rects.
There are still multiple redraw of some controls. But it reduces accesses by 2/3, so it’s a start.
(2) In win32frame::paint: instead of calling platformDrawRect for each rect, merge/enclose them, then make a single call to platformDrawRect.
I’ve tried this, it seems to work but needs more testing. It’s a 5-line change in just one place.
(2b) The same could be done in CFrame::CollectInvalidRects::addRect (), but that’s more complex, haven’t worked it out yet.
The downside of (2) is very crude paint behavour: There’s a full-frame update when two subcontrols in different corners of the plug need repaint.
(3) Another option is to stash dirty children in a std::set and roll that in win32Frame::pain() instead of searching, then CFrame just uses the rects for sort order. (Haven’t thought through the details of this one. Some might want multiple calls with sub-rects for their controls…)
The upside here is it could end up being forgiving of sloppy programmers such as myself, since even if a child is marked dirty multiple times it would only be repainted once.
Here’s a link to instrumented win32frame.cpp and cviewcontainer.cpp to demo the problems (or not) in your code.
(though I’ll remove these before you can say “legal consequences”, so they won’t be there in a few days)
I’ve wrapped changes in the existing macro VSTGUI_LOG_COLLECT_INVALID_RECTS, so look for that
https://www.abc.se/~re/Stuff/vstgui/win32frame-rect-debug.cpp
https://www.abc.se/~re/Stuff/vstgui/cviewcontainer-rect-debug.cpp
Any thoughts?