Accelerating strokePath / fillRectList using opengl


#1

I have an app that draws 2d line-graphs with a lot of points. Using graphics' strokePath / fillRectList this becomes really slow (i.e. can go below 1 fps with 'only' a few thousand points. Note that whether or not I limit the number of points being passed with each call, as some forum members suggested, doesn't help the situation hugely).

So I tried using an approach like in OpenGlDemo - creating a graphics object from the glRenderer, and then using these strokePath / fillRectList functions on that graphics object, but this also results in the same performance.

From profiling, it appears that all of the cost is not in the actual graphics drawing, but in the juce code that is fiddling with the values passed before passing them on to the lower-level APIs (i.e. opengl), i.e. in all kinds of 'EdgeTable' stuff.

So the assumption would be that calling opengl functions directly should avoid all that overhead, and get an order of magnitude or so improvement pretty easily .... however I've gotten really stuck trying to simply i.e. draw a single line with opengl using direct opengl calls such as glScalef, glTranslatef, glBegin(GL_LINE_STRIP) + glVertex2f + glEnd / glDrawArrays etc ..., and just have it actually appear on the screen. Note I have experience using opengl before, I just seem stuck with fitting getting this basic opengl 2d line-drawing setup working within the juce framework.

I would be really grateful if someone could provide a simple 'hello worldy' example of how one does a most basic 2d opengl thing in juce like this - draw a single line with opengl in the renderOpenGL() method of one's OpenGLRenderer. I've been trying using various examples but can't get anything that actually results in anything being visible on the screen - as soon as I have that I'm sure I'd be fine .... Ideally this line drawing wouldn't clash with whatever (i.e. line/text-drawing) calls are being made before/after through the juce functions on the graphics object, i.e. so I can just replace the performance critical parts but still make use of the juce Graphics functions for everything else.


#2

I narrowed the issue down a little - I can draw with opengl if I do it as the first thing in renderOpenGL() .... but if I do anything like call drawBackground2DStuff() as in the demo renderer (creating a graphics object and drawing with that), then following/in-the-middle of that, I don't see anything from any attempts I make to draw with opengl, i.e. something I'm not able to work out how to reset with how the scene/opengl settings are twiddled with by juce's drawing stuff, along the lines of the comment in the juce demo openglrenderer 'Having used the juce 2D renderer, it will have messed-up a whole load of GL state, so we need to initialise some important settings before doing our normal GL 3D drawing..', but nothing I take from there or come-up with seems to help ...

i.e. the following opengl code draws a square , as long as we haven't used the juce 2d renderer before it within the renderOpenGl() call, in which case it or anything else I try doesn't seem to produce anything:

glEnable (GL_DEPTH_TEST);
glDepthFunc (GL_LESS);
glEnable (GL_BLEND);
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
                
const float desktopScale = (float) openGlCtx->getRenderingScale();
glViewport (0, 0, roundToInt (desktopScale * c.getWidth()), roundToInt (desktopScale * c.getHeight()));
                                
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
                
glOrtho(0, c.getWidth(), c.getHeight(), 0, -1, 1);
                
glColor3f(0.7f, 0.7f, 0.7f);
glBegin(GL_LINE_LOOP);
glVertex2f(5,5);
glVertex2f(25,5);
glVertex2f(25,25);
glVertex2f(5,25);
glEnd();

            '


#3

Well yes, the juce GL 2D renderer will stamp all over whatever GL state it wants to, so it's up to you to make sure everything you need is initialised correctly for what you're trying to do.


#4

Yeah I understand that, the trouble is just that for the life of me I can't work out what I'm not doing (i.e. in the previously posted code, which is trying to reset everything, and works fine if done at the start of the renderOpenGL call, just not if any juce GL 2d renderer stuff has happened) to 'fully' reset it such that what I do actually appears. So any tips there I'd be really grateful for. At least, from what you write, I'm assuming that this is perfectly workable in theory (interspersing direct opengl with juce GL 2d renderer stuff), as long as the direct opengl stuff sets the GL state right before every time it tries to do anything.

Also ... generally of course I'd rather not be making things this messy - the preferable general/ideal solution would be that the juce functions are improved to avoid the overhead .... when calling fillRectList / strokePath, these calls at least in my case would seem to be mappable very directly+simple to what opengl is taking, i.e. all the code that juce is inserting there, and which gets very (orders of magnitude more) expensive in certain scenarios - the EdgeList stuff - should at least in such cases be avoidable, i.e. where its iterating thru every y value and sorting all the values, copying stuff around, etc ... some of the stuff its doing apparently even means the amount of items passed in each call have to be minimized for optimum performance (there's forum posts pointing out that the optimum is like ~15, and that seems about right from my experience), presumably indication of an expontential nature to parts of that juce code .... as its otherwise usually desirable+optimal to pass far more points - as many things as possible - in each single opengl call etc ....

Also note for example, while its not clear to me really all that its trying to do there and what parts of what its trying to achieve are the most costly, it does appear to me that one thing its trying to do is remove points too close to each other, as a kind of performance optimization, and its worth noting that the cost of the things its doing appears to be far greater than any saving it gains with such code, at least in my scenario (plus in my case I was already doing exactly such removal with relatively inexpensive code) .....

Similarly with text drawing, which would become the big bottleneck if the line-drawing were even halfway to being as fast as it can be, I might try to see how hard it is to create an automated mechanism that allows one to still use the existing juce higher-level functions, but have them automatically keep the textures generated in a cache and when the exact-same text-drawing call is made the next frame, re-use said textures ... I see there is this ImageCache in juce already, so am hoping that is well-suited to such a job ....


#5

Sorry, wish I had time to discuss in detail, but the quick answer to all this is "it's not as easy as it sounds!"


#6

Sure, I understand - nothing is ever as easy as it sounds :) But if you/anyone have any tips just on the point of what I might be missing in that opengl code I pasted to reset the scene so that what I do following is actually visible, that'd be really appreciated.


#7

Sorry, I don't have an encyclopedic knowledge of all the GL state values! It's always some minor thing that you forget to set...


#8

Sure, well I appreciate the quick responses in any case, thanks ....

I was unable to solve the resetting opengl state issue, but rather than hitting my head endlessly further against that wall, opted for a solution for now where any remaining juce drawing calls go to a graphics object which writes to a window-sized image (rather than a graphics object based on the opengl context), and then that image is drawn to the opengl context as the last thing - this allowed me to leave the juce+opengl code still 'intertwined/interspersed' while i transition most of it to opengl, and is already allowing the graphing of like over an order of magnitude more data ...


#9

@mbevin how did you get glBegin, glVertex2f and blend functions as they don’t appear in Juce?


#10

You can include the gl.h header etc to get opengl functions. However, on iOS you can’t use the deprecated opengl 1.x functions like glBegin (as that’ll clash with the opengl 2 context juce sets up, and it seems there no way around this on iOS).


#11

Cheers dude. No thats fine, Like you I just needed a way to directly draw to the context instead of paint or shader route.