Rotate Component Efficiently


#1

So whats the best way to rotate a component efficiently…

Problem: (turntable effect like serato DJ)
I have a 160 x 160 drawing a couple of eclipses and a rectangular marker off centre which is drawn.

My first attempt was to use affine transform rotate. Which worked but it triggers a redraw on every event frame which eats CPU 6-7% so not great.

My next attempt was to have a second inner component to represent the marker / rectangle which is a lot smaller and use affine rotate on just that element. CPU was still high 5% so I figured the affine rotation is actually creating the max area need and drawing into it…

So the plan is to either use an OpenGL context and let the graphics card do what it does best or change the rectangle to a circle and just update its position in a circular motion as apposed to rotating it.

Anyone else have a more efficient way?


#2

When you’re filling a path, adding a transform makes absolutely zero difference. Have you actually profiled your code properly to see where the CPU is really going?

Sure, GL might be a bit faster but it depends on the platform, and on OSX the normal CoreGraphics 2D is very fast. Most likely you’re just doing unnecessary work somewhere, or underestimating how much work it takes to constantly redraw a window when things are changing. 6-7% CPU to animate a window full of moving sliders sounds pretty reasonable.


#3

I’m treating the Component class like I would a MovieClip in Flash AS3 or the Canvas Element in HTML5. So its only the Turntable that I want to redraw at most not the entire window for obvious performance reasons. (in that example its only one element that is moving and its a a couple of children deep, those other circle are just another type of Knob I created for test purposes)

I have had success with this in Flash doing the same exercise. I apply the rotateTransform and show redraw region and it clearly shows a bounding box just around the MovieClip. I actually managed to do a full web based DJ app. 2 turntables scalable waveform 3 band eq and high pass low pass on each turntable. A baby mixer and beat detection and sync. I’m currently just trying to recreate that as an exercise to learn Juce.

I thought Juce worked in the same was as a lot of frameworks do. I noticed it had a repaint with bounding rectangle which is very handy but does the applying Affine transform force a repaint of the whole window. (Think thats the core of what I’m trying to achieve here. Just want only the are which Im focusing on to be re drawn)

I suggested the OpenGL content as a couple of circles and a marker is such a simple instruction set for the CPU to create. When I played round with the likes of SDL way back I seem to remember doing something similar wouldn’t even show on the CPU.


#4

If you make the components you redraw regularly opaque, it won’t cause the parent to also redraw. You then need to draw any background on this component, though.


#5

Not sure if its relevant for your use case but in an app I’m working on which needs a complex metering graph to rotate, I use bufferedToImage on the component and then perform the necessary rotation (from its parent) when new data comes in. And as @ckhf mentioned you also need to make sure all your components are opaque.

However, it may (or may not) be more efficient to calculate your line each time if thats all you need to rotate. Some code extracted from a previous version of my meter:

            Point<float> startPoint, endPoint;
            Line<float> line;
            Rectangle<int> localBounds = getLocalBounds();
            float angle = normalisedData * 360 - 90;  
            // You will want to adjust these values for your own use-case
            // normalisedData is a float between 0 and 1; 
            float piAngle = angle * M_PI / 180;
            
            startPoint.x = localBounds.getWidth() / 2;
            startPoint.y = localBounds.getHeight() / 2;
            
            line.setStart (startPoint);

            endPoint.x = (localBounds.getWidth()  / 2) + (localBounds.getWidth()  / 2 - 30) * std::cos (piAngle);
            endPoint.y = (localBounds.getHeight() / 2) + (localBounds.getHeight() / 2 - 30) * std::sin (piAngle);
            
            line.setEnd (endPoint);

#6

Cheers. In AS3 I use a MovieClip cacheAsBitmap function so I am assuming bufferedToImage will work similar and take advantage of blitting to make moving stuff more efficient.


#7

Think this demonstrates my issue clearly. I have a component marker which is that little dot in the top left hand corner… The app AudioSandbox is playing audio and those other knobs control a couple of little plugins HP/LP Bit Resampler and SampleRate convertor. All great and all running fine around 3% CPU which is what you’d expect for that load. Note this line commented out
// marker->setTopLeftPosition(markerEndX, markerEndY);

No when I uncomment the line to set the position of the marker which is tiny the marker spins around in the circle fine. But there is a huge CPU hike for such a little operation.

Now is this because the underlying Components are not cached as bitmaps ergo it does a bounds check and redraws right up through the component hierarchy which is what I suspect is happening as that would explain the CPU hike>

My apologies for the confusion but coming from doing WPF .Net Apps AS3 Flash apps and HTML5 Canvas apps they all are very efficient when moving small objects round in this way and either render up through the components in just the affected areas or some just sum the graphical bitmap data (bitmap cache) in the affected areas. None of which redraw sub components which intersect there previous and updated bounds…

PS. Just so you know I’m calling that update function every 20 ms. So effectively 50 times per second and not hammering it in a loop.


#8

Why don’t you just redraw the dot in the correct position rather than rotating the whole thing? Use code similar to what I posted above to calculate the position.


#9

Thats what I am doing. I have drawn the dot once in the marker component then I am just doing this to reposition. No transforms…

    float markerEndX = (sinf(rotation) * marker->offset) +(getWidth()/2);
    float markerEndY = (getHeight()/2) - (cosf(rotation) * marker->offset);
    
    marker->setTopLeftPosition(markerEndX, markerEndY);

Note the rotation variable is just the angle from the centre…


#10

Also I have set all nested components to Opaque and there is a small reduction in CPU but not nearly as much as I was expecting.