Whats the fastest way to draw a bunch (~20) of moving text labels on top of a GL waveform view?


#1

I'm making a wave editor. The waveform is drawn in GL and is super fast and lovely...but drawing the labels on top...oofff. I've been trying with

ScopedPointer<LowLevelGraphicsContext> glRenderer(createOpenGLGraphicsContext(
        context, getTopLevelComponent()->getWidth(), getTopLevelComponent()->getHeight()));

        Graphics g(*glRenderer);
        g.drawText(...);

But calling createOpenGLGraphicsContext every frame at 60Hz is slow.  The killer is this: glGetIntegerv (GL_FRAMEBUFFER_BINDING, &fb);

Also I've tried drawing Label components with setBufferedToImage(true) using the normal method: too slow.

Any suggestions? Should I try GlyphArrangements?

Thanks,

Rob
 


#2

I might be missing some of the subtleties of your issue, but it sounds like you have a waveform (frequent/constant updating) being drawn below labels (moving but not often changed). I would create an image and draw all of the labels onto it directly. Whenever the labels change, clear the image and re-draw the new ones. Then in the actual paint callback, simply draw the image after the waveform. We use something similar in one of our plugins. Here's some code: 


Image labelImage; (declared as a class variable in the header)
bool labelsChanged;

void paint (Graphics& g)
{
//paint waveform here

if(labelsChanged || !labelImage.isValid())
{
paintLabels();
}

g.drawImage(labelImage, 0, 0, width, height, 0, 0, width, height, false);
}

void paintLabels()
{
if(!labelImage.isValid())
{
labelImage = Image (Image::ARGB, width, height, true);
}

labelImage.clear(labelImage.getBounds());
Graphics labelGraphics(labelImage);

//draw your labels here
labelGraphics.drawText("whatever, 0, 0, 100, 40, Justification::left, false);

}

 

 

 


#3

Hey, thanks for the reply. More info: I'm making a wave editor, and the text labels are for a grid to show regular points in time (e.g. 1s, 2s, 3s...) in the file. They only change when the user zooms and scrolls, but I'm want this to be super smooth and sexy.

Based on what you said, I think I might try caching the labels to images then drawing those. OpenGL shouldn't struggle with drawing 30 odd textured quads...right??

Rob


#4

No, that wouldn't be a problem.


#5

In straight OpenGL no. But I mean 30 calls to g.drawImage(...) in the paint method at 60Hz. Would that be reasonable?


#6

Well, I guess I should just try it. Thanks


#7

I've had something where I rendered the labels to an image, turned them into an OpenGL texture and handed it all off to OpenGL to deal with ... ?  I don't know if that's the right solution ?


#8

Do you mean into a single texture altas? Or each label having a different texture.


#9

I'm buggered if I can find my code for this now.  But I think it was a texture per label.  


#10

If you're rendering using an attached OpenGLContext, there actually shouldn't be any problems as images created on an active context should reside on the graphics card's memory as textures. It may not be an exact science, and you should probably test it.. 30 software images (small i suppose) should still be doable easily but it will obviously be slower than OpenGL.


#11

Right. One problem I was finding with mixing OpenGL and Juce drawing stuff was that I had to create the Graphics object every frame like this:

ScopedPointer<LowLevelGraphicsContext> glRenderer(createOpenGLGraphicsContext( context, getTopLevelComponent()->getWidth(), getTopLevelComponent()->getHeight()));

Graphics g(*glRenderer); 

And this was pretty slow due it calling glGetIntegerv(GL_FRAMEBUFFER_BINDING, &fb) every frame (as profiling showed). So maybe I should keep everything in the pure OpenGL world?


#12

Interesting. Yeah, building off of what I was talking about above with the single image, I'd also keep a pointer to its associated graphics context in memory so that you won't have to recreate it every frame but can still draw to it whenever the labels are updated. The main thing I was trying to emphasize was using a single image for all of the labels, drawing everything to it at once. Then you'd only draw that one image every frame, so there wouldn't be ~30 calls to drawImage(). Anyway, lots of ways to skin a cat and it's probably best to keep everything in pure OpenGL if you have the chops. Be sure to let us know what you chose and how it performs, since it's an interesting/common scenario. 


#13

Seconding what my teammate has said. Cache it to a single image, and leave it to update only when something large happens, like changing text colors or something similar.

Another thing to check when you're having redraw issues is a define in AppConfig.h:


// juce_gui_basics flags:
#ifndef    JUCE_ENABLE_REPAINT_DEBUGGING
 //#define JUCE_ENABLE_REPAINT_DEBUGGING
#endif
 

Just remove the commented out line. That will rainbow-paint everything on your panel that is redrawing. Occasionally, what seems like a performance issue with one drawing component can occasionally be a more inisidious issue with a control that's redrawing constantly.

Also, if you're the same Rob Clouth, your new Hidden Structures EP is fantastic. I've been listening to it a lot this week!


#14

Thanks I'll try that. And yeah that's me! Cheers matey!! Glad you like it and cheers for the suggestion.


#15

Do you mean drawing each label that appears on screen to a large texture and storing the texture coord to pass into the shader? I like to avoid that if I can, just to avoid the hassle. But if the image-per-label thing is too slow I'll try that next.

I tried keeping a reference to the created context but it failed when trying to reuse it. Maybe I need to make it active or something. I'll try again.

For sure I'll post the method I find best.

 

Cheers


#16

Ok I've solved the text drawing speed using an cache of Images in a map, that maps the String to the Image.

struct CachedTextImage
{
   String text;
   int64 lastAccessed;
   Image image;
};

map<String, ScopedPointer<CachedTextImage>> textImageMap;
typedef map<String, ScopedPointer<CachedTextImage>>::iterator TextImageMapIter;

Image BufferView::getTextAsImage(String text, Font font, Colour colour)
{
    CachedTextImage* textImage = textImageMap[text];

    if (textImage == nullptr)
    {
        // make the image
        textImage = new CachedTextImage();
        textImage->text = text;
        textImage->lastAccessed = Time::currentTimeMillis();
        int width = font.getStringWidth(text) + 5;
        int height = font.getHeight();
        textImage->image = Image(Image::ARGB, width, height, true);
        Graphics& g = (Graphics)textImage->image;
        g.setColour(colour);
        g.setFont(font);
        g.drawText(text, 0, 0, 50, 20, Justification::topLeft);
        textImageMap[text] = textImage;

        if (textImageMap.size() > 100)
        {
            // find oldest text image and remove it
            CachedTextImage* oldestTextImage = textImage;
            for (TextImageMapIter iter = textImageMap.begin(); iter != textImageMap.end(); iter++)
            {
                if (iter->second && iter->second->lastAccessed < oldestTextImage->lastAccessed)
                    oldestTextImage = iter->second;
            }

            textImageMap.erase(oldestTextImage->text);
        }
    }

    return textImage->image;
}

This makes the text rendering much faster. But still the problem remains of recreating the context every frame with
createOpenGLGraphicsContext. I've tried creating it in newOpenGLContextCreated(), but if I try to use it in renderOpenGL() it fails trying to access a nullptr...has anyone tried this before?
 


#17

Had to look it up on iTunes. I really like "The Galaxy Collapsed into a Point" - a perfect listen while reading Iain M. Banks :)


#18

Yeah additional props on the EP. Middle section of Smallest Measurable Space is particularly nice. 


#19

Thanks folks! Big fan of Banks by the way.