Manual redraw

Hello OF community,

Firstly I would like to thanks everyone who has contributed to provide us such amazing frameworks! more I learn, more I perceive the endless possibility you’ve offered to artists…

Actually, I’m learning C++ through openFrameworks, which is far more motivating than the other tutorials I’ve done by the past :slight_smile:
This being said, many codes I’ll show you in this post could looks insanely ugly for you, so don’t hesitate to punch me in the face if needed!

I’m currently trying to build free a Application that can send/receive OSC message to Reaper (the awesome DAW from Cockos); At this point, everything seems to work correctly excepting that: My App use 30% of CPU!

After a quick search on the forum I think I’ve almost identified the main problem,
all my knobs, sliders, buttons are constantly redrawn on every frames, which seems to be very inefficient.

I’m using OF 0.07 pre-release (from the download section) within codeBlocks
on a PC running under Win 7.

Here’s how I’ve used the ofxOSC addon and a simple RotaryKnob class.

GUI.h

  
  
#ifndef GUI_H_INCLUDED  
#define GUI_H_INCLUDED  
  
#include "ofMain.h"  
  
class RotaryKnob  
{  
    public:  
    RotaryKnob();  
    ~RotaryKnob();  
    void setValue(float value);  
    float getValue()const;  
    void draw(int x, int y, int w, int h, int nFrames);  
    bool isMouseHover(int x, int y);  
    void setFocus(bool focus);  
    bool isFocused() const;  
    void mouseDragged(float x, float y);  
    ofImage m_knobImg;  
  
    private:  
  
    int m_knobX, m_knobY;  
    float m_value;  
    bool m_hover, m_focus;  
};  
  
#endif // GUI_H_INCLUDED  
  

GUI.cpp

  
  
#include "GUI.h"  
  
RotaryKnob::RotaryKnob(): m_value(0.5){}  
  
RotaryKnob::~RotaryKnob(){}  
  
float RotaryKnob::getValue() const  
{  
    return m_value;  
}  
void RotaryKnob::setValue(float value)  
{  
    if(value != m_value)  
    {  
        m_value = value;  
    }  
}  
void RotaryKnob::draw (int x, int y, int w, int h, int nFrames)  
{  
    m_knobX = x;  
    m_knobY = y;  
    float currentFrame(m_value * nFrames);  
    int cropY = (int)currentFrame * h;  
    m_knobImg.loadImage("images/pan1_anim.png");  
    m_knobImg.crop(0, cropY, w, h);  
    m_knobImg.draw(x,y);  
    ofDrawBitmapString ("Value : " + ofToString(m_value), 45, 30);  
}  
bool RotaryKnob::isMouseHover (int x, int y)  
{  
    if ((x > m_knobX && x < (m_knobX + m_knobImg.width))&&(y > m_knobY && y < (m_knobY + m_knobImg.height)))  
    {  
        m_hover = true;  
    }  
    else  
    {  
        m_hover = false;  
    }  
    return m_hover;  
}  
void RotaryKnob::setFocus(bool focus)  
{  
    m_focus = focus;  
}  
bool RotaryKnob::isFocused() const  
{  
    return m_focus;  
}  
void RotaryKnob::mouseDragged(float x, float y)  
{  
    m_value = ofNormalize(y, m_knobY + m_knobImg.width, m_knobY);  
}  
  

testApp.h

  
  
#pragma once  
  
#include "GUI.h"  
#include "ofMain.h"  
#include "ofxOsc.h"  
  
#define HOST "localhost"  
#define PORT 8000  
#define NUM_MSG_STRINGS 20  
  
//--------------------------------------------------------  
class testApp : public ofBaseApp{  
  
	public:  
  
		void setup();  
		void update();  
		void draw();  
		void mouseDragged(int x, int y, int button);  
		void mousePressed(int x, int y, int button);  
		void mouseReleased(int x, int y, int button);  
  
		ofxOscSender        sender;  
		ofxOscReceiver	    receiver;  
  
    private:  
  
        RotaryKnob          myKnob;  
};  
  

testApp.cpp

  
  
#include "testApp.h"  
  
//--------------------------------------------------------------  
void testApp::setup()  
{  
    ofSetFrameRate(25);  
    ofBackground(333);  
	// open connection to HOST:PORT  
	sender.setup( HOST, PORT );  
	receiver.setup( 9000 );  
}  
  
//--------------------------------------------------------------  
void testApp::update()  
{  
	// check for waiting messages  
	while( receiver.hasWaitingMessages() )  
	{  
		// get the next message  
		ofxOscMessage rec;  
		receiver.getNextMessage( &rec );  
  
		if ( rec.getAddress() == "/track/pan" )  
		{  
		    myKnob.setValue(rec.getArgAsFloat(0));  
		}  
	}  
  
}  
  
//--------------------------------------------------------------  
void testApp::draw(){  
  
    ofSetColor(180);  
	myKnob.draw(45, 45, 109, 109, 54);  
}  
  
//--------------------------------------------------------------  
void testApp::mouseDragged(int x, int y, int button)  
{  
    if (myKnob.isFocused() == true)  
    {  
        myKnob.mouseDragged(x,y);  
        //send OSC  
        ofxOscMessage m;  
        m.setAddress( "/track/pan" );  
        m.addFloatArg(myKnob.getValue());  
        sender.sendMessage( m );  
    }  
}  
  
//--------------------------------------------------------------  
void testApp::mousePressed(int x, int y, int button)  
{  
    if (myKnob.isMouseHover(x,y)== true)  
    {  
        myKnob.setFocus(true);  
    }  
}  
  
//--------------------------------------------------------------  
void testApp::mouseReleased(int x, int y, int button)  
{  
    myKnob.setFocus(false);  
}  
  

As you can see the ofImage (109 x 5995 px png image strip) is reloaded on every frames.
I’ve tried many things as suggested in other posts in the forum:

  • ofSetBackgroundAuto(false); in the testApp.cpp. But This apparently not work for me, the background still refreshed on every frames.

  • Using statement directly within the RotaryKnob::draw method to define whenever the redraw should occur or not. That can be an acceptable solution but since ofSetBackgroundAuto(false) dont work (for me) the knob simply disappear.

Please, be sure I’ve googled/searched a lot in this forum before boring you with this, but I’m actually stuck.
Any Help will be very welcome.
Thanks by advance.
Jean

reaOSC preview:

Here’s the codeBlocks project:
MyFirstKnob.zip

Hi Jean,

That is a huge image for a knob. Is there any way to break it up, rotate and composite, fewer, smaller images?

It is normal to draw the image every frame, but you only need to load the image into memory once. So you may want to add a load function into your knob class and call it in setup from your testApp.
You will run into problems with the cropping of the image, so you may need to make a vector of images in the load function. Then store the cropped images in the vector and then draw the corresponding image based on the currentFrame.

You could also load the image once and then shift the texcoords by binding it to a quad. But you should check on the max size of a gl texture before using that large image.

Hope this helps.

Nick

Thanks Nick for your quick reply,
And yes I have to admit this example use an excessively big image, your suggestion about the vector seems to be a very nice idea.
But when you say “It is normal to draw the image every frame” do you mean that there is no way do it manualy?
Even though I don’t use this image strip knob my app still use 15-20% cpu!
This is clearly due to the redraws, I’ve tried for example to not draw all the buttons that you can see in my screenshot, in this case the app use a reasonable 1% cpu.

Let me precise that all buttons are loaded once with the setup.

The crop function is also an expensive pixel operation.
Not sure what you mean by “do you mean that there is no way do it manualy?” Can you clarify?

Of course,
I would like to be able to redraw my button only if it state has changed.
ie : my button could be loaded on the setup then drawn once, then redrawn only when needed.

Thanks for your time Nick.

Silent,
those are some slick graphics!
Obviously I’m sure it’s mostly a talent thing :wink:
But do you have any tips or tutorials to recommend? I’m assuming it’s illustrator?

Cheers
Alex

Hi,

You read a 600.000 pixel image from disk into memory every draw for every knob. For 100 knobs and 30 FPS that’s like 2 GB of reading images per second. Thats why it’s slow with high cpu usage. You do need to draw it every time otherwise the knob will disappear when not clicking on it.

For using the same ‘tactic’ of cropping, just move line 24 from GUI.cpp to your knob constructor, so it will only read the image once but it will still be in the memory waiting for you to draw it.

  
  
RotaryKnob::RotaryKnob(): m_value(0.5){  
   m_knobImg.loadImage("images/pan1_anim.png");   
}    
...  
void RotaryKnob::draw (int x, int y, int w, int h, int nFrames)    
{    
    m_knobX = x;    
    m_knobY = y;    
    float currentFrame(m_value * nFrames);    
    int cropY = (int)currentFrame * h;    
    m_knobImg.crop(0, cropY, w, h);    
    m_knobImg.draw(x,y);    
    ofDrawBitmapString ("Value : " + ofToString(m_value), 45, 30);    
}    
  
  

PS awesome graphics!