OpenGL fast Viewport


#1

I’ve implemented a “fast Viewport” class for OSX and Windows. On Windows, it uses ScrollWindowEx to scroll the part that needs to be scrolled and so only the small rectangle that needs to be repainted of the Viewport’s contained Component gets repainted. On OSX I do this via NSView’s scrollRect function. It works very well, and it brings a massive speed increase to Viewport, in the range of 1000% easily. Even on very slow computers, the most complex Component will still scroll at high speed, without any noticeable FPS drop.
Now I’d like to do create such a “fast Viewport” for the OpenGLRenderer. The only things I actually need to be able to do is: Move a region on screen, invalidate next region, wait until paint finished. How do I achieve this?


#2

Is it really necessary with OpenGL?
Games render amazingly complex scenes made of bajillions of faces with crazy pixel shaders and render it all from scratch 50 times per second,
so at least if it’s a desktop application which isn’t some cutting-edge video game, I’d assume that simply using OpenGL would usually suffice.


#3

I was under the impression that using buffering via a call to setBufferedToImage() would have the same impact on scroll speed, but I might be wrong?


#4

JUCE’s OpenGL renderer has its limits too. Just check how many lines you can draw in a 1/50 second and you’ll see that it’s actually not that fast. Plus, this method of scrolling would always use even less CPU.

Why would setBufferedToImage() have any impact on scrolling speed in a Viewport? Whenever the Viewport scrolls, the Component repaints ENTIRELY, not just the part that is being scrolled in the Viewport. In a Viewport, using setBufferedToImage will actually slow down things, as there’s the additional task of drawing the image instead of drawing immediately on screen.


#5

Zamrate, are you willing to share your fast ViewPort? We would really benefit from such a class.


#6

Here’s a code example for Windows. Only the unhidden portion of the contained component will be invalidated and hence its paint method will have its Graphics.getClipBounds() set to just the area that needs to be redrawn. The cpu load for scrolling a complex waveform a few pixels only will be greatly reduced.

A drawback is that the Component.bounds member have to be made public. AFAIK there’s no other way to “move” a component without invoking an invalidation of the whole component which in turn will set Graphics.getClipBounds() to cover the whole component. Then there’s no way to know what part of it that needs to be (re)painted.

class MyViewport : public Viewport 
{
public:
	MyViewport(const String& name) : Viewport(name) {}
	void setViewPosition (const int xPixelsOffset, const int yPixelsOffset);
	void scrollBarMoved (ScrollBar *scrollBarThatHasMoved, double newRangeStart);
	Point<int> viewportPosToCompPos (const Point<int>& pos) const;
};


//----------- from the windows header --------------

const int SW_SCROLLCHILDREN = 0x0001;  /* Scroll children within *lprcScroll. */
const int SW_INVALIDATE     = 0x0002;  /* Invalidate after scrolling */
const int SW_ERASE          = 0x0004;  /* If SW_INVALIDATE, don't send WM_ERASEBACKGROUND */
const int SW_SMOOTHSCROLL   = 0x0010;  /* Use smooth scrolling */


typedef struct tagRECT
{
    int    left;
    int    top;
    int    right;
    int    bottom;
} RECT;

typedef int HWND;

extern "C" {
	extern
int
__stdcall
ScrollWindowEx(
    __in HWND hWnd,
    __in int dx,
    __in int dy,
    __in_opt const RECT *prcScroll,
    __in_opt const RECT *prcClip,
    __in_opt void *hrgnUpdate,
    __out_opt RECT *prcUpdate,
    __in uint32 flags);
}

//------------------------------------

void MyViewport::scrollBarMoved (ScrollBar* scrollBarThatHasMoved, double newRangeStart)
{
    const int newRangeStartInt = roundToInt (newRangeStart);

    if (scrollBarThatHasMoved == getHorizontalScrollBar())
    {
        setViewPosition (newRangeStartInt, getViewPositionY());
    }
    else if (scrollBarThatHasMoved == getVerticalScrollBar())
    {
        setViewPosition (getViewPositionX(), newRangeStartInt);
    }
}


Point<int> MyViewport::viewportPosToCompPos (const Point<int>& pos) const
{
	Component *contentComp = getViewedComponent();
   return Point<int> (jmax (jmin (0, getMaximumVisibleWidth()  - contentComp->getWidth()),  jmin (0, -(pos.x))),
                       jmax (jmin (0, getMaximumVisibleHeight() - contentComp->getHeight()), jmin (0, -(pos.y))));
}


void MyViewport::setViewPosition (const int xPixelsOffset, const int yPixelsOffset)
{
	Point<int> pos = getTopLevelComponent()->getLocalPoint(this, Point<int>(0, 0));
	RECT rcScroll = { pos.x, pos.y, pos.x + getWidth(), pos.y + getHeight() };
	
	if (getHorizontalScrollBar()->isVisible())
		rcScroll.bottom -= getHorizontalScrollBar()->getHeight();

	if (getVerticalScrollBar()->isVisible())
		rcScroll.right -= getVerticalScrollBar()->getWidth();

	Component *contentComp = getViewedComponent();

	HWND hwnd = (HWND)contentComp->getWindowHandle();
	int dx = xPixelsOffset + contentComp->getX();
	int dy = yPixelsOffset + contentComp->getY();

	ScrollWindowEx(hwnd, -dx, -dy, &rcScroll, &rcScroll, 0, 0, SW_INVALIDATE);
	
	contentComp->bounds.setPosition(-xPixelsOffset, -yPixelsOffset);
}