PDA

View Full Version : Is there a good way to redraw only when changed?



daxfohl
03-22-2011, 04:34 PM
I'm developing an intensive care monitor using OpenGL ES (WinCE / PowerVR SGX) for the user interface. Mainly I just wanted OpenGL for the waveforms so we could do cool fades and whatnot on them. The rest of the interface I was just going to use Windows GDI. But our lead developer insisted that using OpenGL for everything would speed up performance because it uses the graphics accelerator.

So that's what I've done, but the text rendering is pretty slow. Which makes sense since OpenGL has to redraw it every frame, while GDI (as far as I know) doesn't use any processor cycles unless the text changes, which is relatively infrequent. But it's annoying that redrawing all this mostly static text is causing the waveforms to render at lower fps. Is there any way to cajole OpenGL to only redraw the text when it changes?

I've tried using FBOs and rendering each text field to texture on changed frames, then rendering from that texture on unchanged frames, but that's still a lot slower than GDI. I also tried just not doing a glClear between each frame and only rendering changed items, but that just gave weird unpredictable results.

It seems like there should be a way to just say "hey OpenGL don't change any of this stuff on this part of the screen" and have that cost nothing, but I can't find the way to do it. Or was my old manager (he's since left the company) just wrong when he said that you can always use OpenGL to do the same thing faster than the native graphics API?

dukey
03-22-2011, 05:38 PM
How are you rendering text?

daxfohl
03-22-2011, 06:06 PM
Fairly straightforward, just bind to the texture and set the color, vertex, letter coordinate, and element data, and draw:

glBindTexture(GL_TEXTURE_2D, _font.TextureId);
glColorPointer(4, 0, _colors);
glVertexPointer(3, 0, _screenPositions);
glTexCoordPointer(2, 0, _letterTexCoords);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, GlyphStripElementIndexHandle);
glDrawElements(GL_TRIANGLES, _length * 6, GL_UNSIGNED_SHORT, 0);

Is there a more efficient way of doing this? I've tried putting the arrays in VBOs but that doesn't seem to make any difference. I haven't tried interleaving everything into a single array, but wouldn't think that would make much difference either. But it also seems to sidestep the question of, do I really need to re-render all this stuff for each frame in the first place. (Though if there was a much more efficient way of rendering it, then I guess I wouldn't mind rendering every frame).

Simon Arbon
03-22-2011, 08:24 PM
Setup a scissor box around the part of the screen that contains the waveform that you want to update every frame with:
void glScissor( int left, int bottom, sizei width, sizei height );

When you have drawn the GUI the first time and now only want to update the wave form, do:
glEnable( GL_SCISSOR_TEST );

When you need to change the GUI do:
glDisable( GL_SCISSOR_TEST );
glCear( GL_COLOR_BUFFER_BIT );
glDrawElements(GL_TRIANGLES, _length * 6, GL_UNSIGNED_SHORT, 0); //Draw GUI
glEnable( GL_SCISSOR_TEST );
Then go back to only drawing the changing waveform until the next change to the GUI.

P.S. If you are double-buffered and using SwapBuffers in 'swap' mode then you need to draw the GUI, SwapBuffers, then draw it again before you enable the scissor test.

Ilian Dinev
03-22-2011, 11:40 PM
Try: draw the text with GDI into a window-sized DIB. Upload with glTexSubImage2D. Draw that texture with a fullscreen quad/tri. Experiment with different RGB bit-depths (888,565,8888) of the DIB.

How often does the text change? I.e every 5 frames roughly, or 500+ frames?

daxfohl
03-22-2011, 11:42 PM
@Simon Hmm, that makes sense that it should work, but it isn't working. It just makes everything transparent (i.e. I can see the desktop background) outside the scissor box. It seems like the OpenGL ES implementation must be doing an implicit glClear after each call to eglSwapBuffers. Maybe it's worth trying it out in OpenVG. That would have probably made the most sense for this app in the first place, but I was just new to graphics at the time and OpenGL seemed to have far more reference material.

Simon Arbon
03-23-2011, 01:16 AM
Did you write the GDI parts twice like i said in the P.S.?
If by 'transparent' you mean you can see through the GUI objects then its likely that eglSwapBuffers is switching rapidly between a framebuffer with the GUI drawn and one where it isnt.
You should have something like this:


if NeedToUpdateGUI
{
NeedToUpdateGUI = False;
glDisable( GL_SCISSOR_TEST );
glClear( GL_COLOR_BUFFER_BIT );
DrawGUI;
eglSwapBuffers;
glClear( GL_COLOR_BUFFER_BIT );
DrawGUI;
glEnable( GL_SCISSOR_TEST );
}
glClear( GL_COLOR_BUFFER_BIT );
DrawWaveform;
eglSwapBuffers;

daxfohl
03-23-2011, 08:41 AM
Yeah, that's what I'm doing. No, by transparent, I mean totally not there I have a box of waves in the middle of the screen and outside of that is just the WinCE background. However the text fields are all showing up in the first two frames of the app, so it's obviously getting written to the framebuffer at that initialization. Just seems to get cleared whenever I do a buffer swap. If I just eliminate glClear from the app entirely and only draw the text fields on the first two frames, I get the same behavior. I don't think GL ES supports single buffering.

ZbuffeR
03-23-2011, 08:53 AM
Simon : swapbuffers can be implemented as anything, it could be a copy from a single internal buffer to front buffer, or new uninitialized memory each time. This is not robust.

Ilian advice sound good, did you try ?

daxfohl
03-23-2011, 08:56 AM
@Ilian That should give me about the same performance as rendering to an FBO and using that texture, right? Especially for the non-changing frames it should be identical because it's just rendering from a texture in either case, right?

Right now I'm using a different FBO for every "region" on the screen (i.e., one for ECG stuff, one for ventilator settings, etc 14 in total). Using a single fullscreen FBO was something I was considering might help, but I haven't tried it yet. I'll try messing around with RGB depths too; that's something I hadn't considered yet.

Text will change maybe once per second overall, but some regions more frequently than others. i.e. Heart rate may change fairly often, but patient info field will stay constant through the entire treatment. Currently the framerate is about 15 on the SGX, but with text rendering removed I get 30.

Ilian Dinev
03-23-2011, 10:21 AM
So, bake the patient-info into a DIB, which you upload once as a POT-sized texture. Splash it onscreen with a quad. Draw onto other POT-sized DIBs the often-changing text (only when it changes), upload to another texture. Splash those onscreen.
If the often-changing text is very short and simplistic (digits mostly, and 1-2 special symbols), it might be better to draw those digits+symbols every frame with triangles (like you initially did with FBOs, but without FBOs - directly onto the backbuffer).

I haven't toyed to optimize for an SGX yet, so I don't know at all which approaches will be fast; you'll have to test them all :( . There could be differences in speed of sampling from FBO-bound textures vs regular textures (i.e texel sampling requiring texels to always be swizzled), so I'd really try everything.

Simon Arbon
03-23-2011, 07:24 PM
I found this in the egl spec:

To set an attribute for an EGLSurface, call
EGLBoolean eglSurfaceAttrib(EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint value);

If attribute is EGL_SWAP_BEHAVIOR, then value specifies the effect on the color buffer of posting a surface with eglSwapBuffers (see section 3.9). A value of EGL_BUFFER_PRESERVED indicates that color buffer contents are unaffected, while EGL_BUFFER_DESTROYED indicates that color buffer contents may be destroyed or changed by the operation.
If value is EGL_BUFFER_PRESERVED, and the EGL_SURFACE_TYPE attribute of the EGLConfig used to create surface does not contain EGL_SWAP_BEHAVIOR_PRESERVED_BIT, a EGL_BAD_MATCH error is generated.
The initial value of EGL_SWAP_BEHAVIOR is chosen by the implementation.
Which seems to say that if you use an EGLConfig containing EGL_SWAP_BEHAVIOR_PRESERVED_BIT and you set the EGL_SWAP_BEHAVIOR surface attribute to EGL_BUFFER_PRESERVED then it should behave exactly like a single-buffered framebuffer would.

Of course this mode might itself reduce performance depending on how both modes are implimented, and your particular implimentation might not support the BUFFER_PRESERVED mode anyway.
If thats the case then Ilian's suggestion is your best option.

daxfohl
03-24-2011, 09:52 AM
@Simon: Gah, I think that's probably the answer! However, adding "EGL_SURFACE_TYPE, EGL_SWAP_BEHAVIOR_PRESERVED_BIT" to my config attribs returns zero compatible configurations on eglChooseConfigs on the PowerVR drivers :(. So, looks like I'm going to be re-rendering quads every frame. However looks like there are several possibilities to try to optimize things.