Skip to content

Instantly share code, notes, and snippets.

@ofTheo
Last active December 20, 2015 21:09
Show Gist options
  • Save ofTheo/6195470 to your computer and use it in GitHub Desktop.
Save ofTheo/6195470 to your computer and use it in GitHub Desktop.
threaded texture loading for openFrameworks for desktop ( just cpu ) and iOS ( cpu and gpu ). explanation here: http://forum.openframeworks.cc/index.php/topic,13032.msg56223.html#msg56223
//
// threadedTexture.cpp
// iPhone_OSX_BuildTheo
//
// Created by Theodore Watson on 3/18/13.
//
//
#include "threadedLoader.h"
threadedLoader::threadedLoader(){
bListenerAdded = false;
bSetup = false;
}
threadedLoader::~threadedLoader(){
clear();
}
void threadedLoader::setup(float timePer){
// cout << "threadedLoader :: setup : | " << ofGetElapsedTimef() << endl;
if( !bListenerAdded ){
ofAddListener(ofEvents().update, this, &threadedLoader::update);
bListenerAdded = true;
}
timePerImage = timePer;
timeLastLoad = 0.0;
bSetup = true;
}
bool threadedLoader::isSetup(){
return bSetup;
}
void threadedLoader::clear(){
stopThread();
if( bListenerAdded ){
ofRemoveListener(ofEvents().update, this, &threadedLoader::update);
bListenerAdded = false;
}
mutex.lock();
basePtrQ.clear();
mutex.unlock();
mutex2.lock();
basePtrGLQ.clear();
mutex2.unlock();
}
void threadedLoader::addToQueue(string path, threadedBaseObject * objPtr){
path = ofToDataPath(path, true);
if( bListenerAdded == false && objPtr->needsGLLoad() ){
setup();
}
objPtr->setupForThreadedLoad(path);
objPtr->setState(threadedBaseObject::STATE_LOADING_DISK);
if(isThreadRunning()){
mutex.lock();
basePtrQ.push_back(objPtr);
mutex.unlock();
} else {
// cout << "threadedLoader :: addToQueue : thread is not running, going to start it now | " << ofGetElapsedTimef() << endl;
basePtrQ.push_back(objPtr);
startThread(false);
}
}
bool threadedLoader::hasInQueue( threadedBaseObject* objPtr ) {
bool bFound = false;
mutex.lock();
for(int i = 0; i < basePtrQ.size(); i++ ) {
if(basePtrQ[i] == objPtr){
bFound = true;
break;
}
}
mutex.unlock();
if( bFound ){
return true;
}
mutex2.lock();
for(int i = 0; i < basePtrGLQ.size(); i++ ) {
if(basePtrGLQ[i] == objPtr){
bFound = true;
break;
}
}
mutex2.unlock();
return bFound;
}
void threadedLoader::removeFromQueue( threadedBaseObject* objPtr ) {
mutex.lock();
for(int i = 0; i < basePtrQ.size(); i++ ) {
if(basePtrQ[i] == objPtr) {
basePtrQ.erase( basePtrQ.begin()+i );
break;
}
}
mutex.unlock();
mutex2.lock();
for(int i = 0; i < basePtrGLQ.size(); i++ ) {
if(basePtrGLQ[i] == objPtr){
basePtrGLQ.erase( basePtrGLQ.begin() + i );
break;
}
}
mutex2.unlock();
}
void threadedLoader::update(ofEventArgs & args){
if( basePtrGLQ.size() ){
float t = ofGetElapsedTimef();
if( t - timeLastLoad < timePerImage ) return;
threadedBaseObject * obj = basePtrGLQ[0];
//cout << " loading in main thread " << obj->getFilePath() << endl;
obj->loadGL();
obj->updateLoadTime();
obj->setState(threadedBaseObject::STATE_LOADED);
obj->onLoadComplete();
mutex2.lock();
basePtrGLQ.erase(basePtrGLQ.begin(), basePtrGLQ.begin()+1);
mutex2.unlock();
timeLastLoad = ofGetElapsedTimef();
}else if( basePtrQ.size() == 0 && isThreadRunning() ){
stopThread();
}
}
void threadedLoader::threadedFunction(){
while( isThreadRunning() && basePtrQ.size() ){
threadedBaseObject * obj = basePtrQ[0];
if(!obj->load()) {
// cout << "could not load it " << endl;
obj->loadError();
} else {
if( obj->needsGLLoad() ){
// cout << " adding to GL load " << obj->getFilePath() << endl;
obj->setState(threadedBaseObject::STATE_LOADING_GL);
mutex2.lock();
basePtrGLQ.push_back(obj);
mutex2.unlock();
} else {
obj->updateLoadTime();
obj->setState(threadedBaseObject::STATE_LOADED);
obj->onLoadComplete();
}
}
mutex.lock();
basePtrQ.erase(basePtrQ.begin(), basePtrQ.begin()+1);
mutex.unlock();
ofSleepMillis(10);
}
stopThread();
}
string threadedLoader::toString() {
stringstream ss;
mutex.lock();
ss << "basePtrQ --------------------- " << endl;
for(int i = 0; i < basePtrQ.size(); i++ ) {
ss << "[" <<i<<"]: " << basePtrQ[i]->getFilePath() << endl;
}
mutex.unlock();
mutex2.lock();
ss << "basePtrGLQ --------------------- " << endl;
for(int i = 0; i < basePtrGLQ.size(); i++ ) {
ss << "["<<i<<"]: " << basePtrGLQ[i]->getFilePath() << endl;
}
mutex2.unlock();
return ss.str().c_str();
}
//
// threadedLoader.h
// iPhone_OSX_BuildTheo
//
// Created by Theodore Watson on 3/20/13.
//
//
#pragma once
#include "ofMain.h"
class threadedBaseObject{
public:
typedef enum{
STATE_EMPTY = 0,
STATE_LOADING_DISK,
STATE_LOADING_GL,
STATE_LOADED,
STATE_ERROR
}loadedState;
//------------------------------------
threadedBaseObject(){
state = STATE_EMPTY;
}
virtual void setupForThreadedLoad(string path){
filePath = ofToDataPath(path, true);
}
virtual bool needsGLLoad() = 0;
virtual bool load() = 0;
//overide this if you have need to load things on the main thread
virtual void loadGL(){
}
void loadError() {
ofLogError( "threadedLoader :: error loading " + getFilePath() );
setState( threadedBaseObject::STATE_ERROR );
}
virtual bool isReady(){
return ( state == STATE_LOADED );
}
virtual void updateLoadTime(){
timeLoaded = ofGetElapsedTimef();
}
virtual void setState(threadedBaseObject::loadedState stateIn){
state = stateIn;
}
virtual threadedBaseObject::loadedState getState(){
return state;
}
bool isLoading() {
return (state == STATE_LOADING_DISK || state == STATE_LOADING_GL);
}
virtual void onLoadComplete() {
}
string getFilePath(){
return filePath;
}
protected:
float timeLoaded;
loadedState state;
string filePath;
};
class threadedLoader : public ofThread{
public:
threadedLoader();
~threadedLoader();
bool isSetup();
void setup(float timePerFile = 0.3);
void update(ofEventArgs & args);
void clear();
void addToQueue(string path, threadedBaseObject * tex);
bool hasInQueue( threadedBaseObject * objPtr );
void removeFromQueue( threadedBaseObject* objPtr );
void threadedFunction();
string toString();
protected:
vector <threadedBaseObject *> basePtrQ;
vector <threadedBaseObject *> basePtrGLQ;
ofMutex mutex;
ofMutex mutex2;
bool bListenerAdded;
bool bSetup;
float timePerImage;
float timeLastLoad;
};
//
// threadedTexture.h
// iPhone_OSX_BuildTheo
//
// Created by Theodore Watson on 3/18/13.
//
//
#pragma once
#include "ofMain.h"
#include "threadedLoader.h"
class threadedTexture : public ofTexture, public threadedBaseObject{
public:
//------------------------------------
threadedTexture();
void clear();
bool load();
void loadGL();
bool needsGLLoad();
void setTex2d( bool aB );
void queueTextureWrap( GLint aWrap );
void setFlipTex( bool aB );
//------------------------------------
void draw(float x, float y);
void draw(float x, float y, float z);
void draw(float x, float y, float w, float h);
void drawScaledWidth(float x, float y, float z, float w);
void drawScaledHeight(float x, float y, float z, float h);
void drawScaledPercent(float x, float y, float z, float pct);
void draw(float x, float y, float z, float w, float h);
//------------------------------------
//overide this to make your own placeholder
void customDraw(float x, float y, float z, float w, float h);
ofPixels * pix;
bool bNeedsGLLoad;
bool bCustomDraw;
bool bTex2d;
GLint wrapMode;
bool bFlipTex;
};
typedef threadedLoader threadedTextureLoader;
//
// threadedTexture.mm
// iPhone_OSX_BuildTheo
//
// Created by Theodore Watson on 3/18/13.
//
//
#include "threadedTextureLoader.h"
static bool bDevice = ( ! ( TARGET_IPHONE_SIMULATOR ) ) && ( TARGET_OS_IPHONE ) ;
static bool biOS = TARGET_OS_IPHONE ;
#if (TARGET_OS_IPHONE)
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGL.h>
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"
EAGLContext * contextB = NULL;
EAGLContext * contextA = NULL;
#endif
//------------------------------------
threadedTexture::threadedTexture(){
bCustomDraw = true;
pix = NULL;
wrapMode = GL_CLAMP_TO_EDGE;
setFlipTex( false );
bTex2d = false;
bNeedsGLLoad = true;
}
void threadedTexture::clear(){
if( pix != NULL ){
pix->clear();
delete pix;
pix = NULL;
}
ofTexture::clear();
state = STATE_EMPTY;
}
bool threadedTexture::load(){
//we need to grab the current context and create a new context.
//this just happens once - so don't worry :)
#if (TARGET_OS_IPHONE)
if(contextB == NULL){
ofxiOSEAGLView * view = ofxiPhoneGetGLView();
//from http://developer.apple.com/library/ios/qa/qa1612/_index.html
contextA = view.context;
contextB = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1 sharegroup:contextA.sharegroup];
if (!contextB || ![EAGLContext setCurrentContext:contextB]) {
cout << " error setting context " << endl;
}else{
cout << " success context " << endl;
}
}
#endif
if( pix == NULL ){
pix = new ofPixels();
}
bool bLoadOk = ofLoadImage(*pix, filePath );
//this is where the magic is - right now we are in a thread, but we switch to our second context and do the upload while staying in the thread.
#if (TARGET_OS_IPHONE)
if( contextB ){
[EAGLContext setCurrentContext:contextB];
loadGL();
glFlush();
// cout << " loading in thread!! " << endl;
[EAGLContext setCurrentContext:contextA];
bNeedsGLLoad = false;
}
#endif
return bLoadOk;
}
void threadedTexture::loadGL(){
if( pix ){
int glType = GL_RGB;
if( pix->getImageType() == OF_IMAGE_COLOR_ALPHA ){
glType = GL_RGBA;
}else if( pix->getImageType() == OF_IMAGE_GRAYSCALE ){
glType = GL_LUMINANCE;
}
if(bTex2d) {
allocate(pix->getWidth(), pix->getHeight(), glType, false );
loadData( *pix );
setTextureWrap( wrapMode, wrapMode );
} else {
allocate(pix->getWidth(), pix->getHeight(), glType );
loadData( *pix );
}
if(bFlipTex) {
getTextureData().bFlipTexture = true;
}
pix->clear();
delete pix;
pix = NULL;
}
bCustomDraw = false;
}
bool threadedTexture::needsGLLoad(){
return bNeedsGLLoad;
}
void threadedTexture::setTex2d( bool aB ) {
bTex2d = aB;
}
void threadedTexture::queueTextureWrap( GLint aWrap ) {
wrapMode = aWrap;
}
void threadedTexture::setFlipTex( bool aB ) {
bFlipTex = aB;
}
//------------------------------------
void threadedTexture::draw(float x, float y){
if(isReady())
threadedTexture::draw(x,y,0,getWidth(),getHeight());
}
//------------------------------------
void threadedTexture::draw(float x, float y, float z){
if(isReady())
threadedTexture::draw(x,y,z,getWidth(),getHeight());
}
//------------------------------------
void threadedTexture::draw(float x, float y, float w, float h){
threadedTexture::draw(x,y,0,w,h);
}
//------------------------------------
void threadedTexture::drawScaledWidth(float x, float y, float z, float w){
if( getWidth() == 0.0 )return;
float h = w * ( getHeight() / getWidth() );
draw(x,y,z,w,h);
}
//------------------------------------
void threadedTexture::drawScaledHeight(float x, float y, float z, float h){
if( getHeight() == 0.0 )return;
float w = h * ( getWidth() / getHeight() );
draw(x,y,z,w,h);
}
//------------------------------------
void threadedTexture::drawScaledPercent(float x, float y, float z, float pct){
draw(x,y,z,getWidth() * pct, getHeight() * pct);
}
//------------------------------------
void threadedTexture::draw(float x, float y, float z, float w, float h){
if(bCustomDraw){
customDraw(x, y, z, w, h);
}
if(isReady()){
ofTexture::draw(x,y,z,w,h);
}
}
//------------------------------------
//overide this to make your own placeholder
void threadedTexture::customDraw(float x, float y, float z, float w, float h){
if( w == 0.0 ) w = 128;
if( h == 0.0 ) h = 128;
ofPushStyle();
ofSetColor(0,0,0);
ofFill();
ofRect(x, y, z, w, h);
ofNoFill();
ofSetColor(255, 255, 255);
ofRect(x, y, z, w, h);
ofPopStyle();
}
@ofZach
Copy link

ofZach commented Jan 14, 2015

this should be an ADDON !!
super useful 5 stars out 5 would use again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment