CFrame paint pessimisation [Windows]

A user interface toolkit mainly for audio plug-ins (VST, AudioUnit, etc).
Posts: 3
Joined: Fri May 25, 2018 9:30 pm

CFrame paint pessimisation [Windows]

Postby squarewave » Mon Oct 08, 2018 12:39 am

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.)

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.


(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.)

(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.

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 ... -debug.cpp ... -debug.cpp

Any thoughts?

Posts: 3
Joined: Fri May 25, 2018 9:30 pm

Re: CFrame paint pessimisation [Windows]

Postby squarewave » Tue Oct 09, 2018 10:17 am

To follow up after some more testing,
My local fix is simple & crude: Use the BeginPaint bounding rect to redraw and completely skip the GetRegionData rects.
This way there is only one search for controls to draw and no double-painting, but still some uncalled-for repaints.

Only 2 line changes.
In win32paint.cpp / Win32Frame::paint() function:

Code: Select all

        if (deviceContext)
            deviceContext->setClipRect (updateRect);
++            getFrame()->platformDrawRect(drawContext, updateRect);

--            DWORD len = GetRegionData(rgn, 0, NULL);
++            DWORD len = 0; //GetRegionData(rgn, 0, NULL);  // Skip the region rects Mikado stack

The last line and the following block should ofc be deleted if this stands.

Had to chase around for GetRegionData info.
Feng Yuan "Windows Graphics Programming" (2000), ch 9.6 has a detailed (and convoluted) description of memory layout and warns about memory costs. He suggests using it for bitmap masking and color keying for transparency. Fairly advanced old-school stuff.
(PS now I get it: Remember round windows anyone? A bit cheesy, never quite worked? Those.
There's a function to get the exclusion region then you can blit from your bitmap and background.)

Wine description of Regions is easier to follow;
Region functions create the widest possible bands from its rects, and returns them strictly sorted by Y first, then X.
See: ... 2/region.c

So in my case there's 1- and 2-pixel bands because some controls have a border, then there's a same-size but borderless control way off to the left or right.
The control sticking up/down is sliced into 2-3 bands and the full search+paint is applied 2-3 times per call.
This will vary depending on app layouts.

Feeding the dirty rects into InvalidateRect, to tell Windows the area that will be updated, is the right and simple thing to do.
Windows also assumes paint will cover entire smallest rect covering all parts (it's what we get back in BeginPaint struct).
But while it would be useful to get the original rects back (or even better, the controls themselves), the Region logic has the wrong behavior for the task.


Arne Scheffler
Posts: 201
Joined: Mon Jun 20, 2016 7:53 am

Re: CFrame paint pessimisation [Windows]

Postby Arne Scheffler » Mon Oct 15, 2018 9:44 am

Hi rasmus,
as you have seen, VSTGUI is not optimized for redrawing overlapped controls, so avoid it. Your naiv solution may works for your simple case, but if you have continuous updating regions in the upper left and lower right you're redrawing the whole area which is slow on some platforms.


Return to “VSTGUI”

Who is online

Users browsing this forum: No registered users and 1 guest