Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Added threaded image loading sample
  • Loading branch information
Benjamin Bojko committed Jan 12, 2017
1 parent 45c23e5 commit 3376bf4
Show file tree
Hide file tree
Showing 17 changed files with 1,014 additions and 0 deletions.
24 changes: 24 additions & 0 deletions .gitignore
Expand Up @@ -151,3 +151,27 @@ $RECYCLE.BIN/
# Mac files
.DS_Store
._.DS_Store

# Xcode (see https://github.com/github/gitignore/blob/master/Global/Xcode.gitignore)
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

## Build generated
build/
DerivedData/

## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/

## Other
*.moved-aside
*.xccheckout
*.xcscmblueprint
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions samples/ThreadedImageLoadingSample/include/Resources.h
@@ -0,0 +1,9 @@
#pragma once
#include "cinder/CinderResources.h"

//#define RES_MY_RES CINDER_RESOURCE( ../resources/, image_name.png, 128, IMAGE )





Binary file not shown.
Binary file not shown.
130 changes: 130 additions & 0 deletions samples/ThreadedImageLoadingSample/src/ThreadedImageLoader.cpp
@@ -0,0 +1,130 @@

#include "ThreadedImageLoader.h"

#include "cinder/Filesystem.h"
#include "cinder/imageIo.h"

using namespace ci;
using namespace ci::app;
using namespace std;

namespace bluecadet {
namespace utils {

ThreadedImageLoader::ThreadedImageLoader(const unsigned int numThreads) {
mTasks.setup(numThreads);
}

ThreadedImageLoader::~ThreadedImageLoader() {

}

void ThreadedImageLoader::load(const std::string path, Callback callback) {
// check texture cache
auto texIt = mTextureCache.find(path);
if (texIt != mTextureCache.end()) {
callback(path, texIt->second);
return;
}

// check existing load tasks/callbacks
lock_guard<mutex> lock(mCallbackMutex);
auto cbIt = mCallbacks.find(path);
if (cbIt != mCallbacks.end()) {
mCallbacks[path].push_back(callback);
return;
}

// load file and add callback
mCallbacks[path].push_back(callback);

mTasks.addTask([=] () {
// worker thread
auto data = loadFile(path);
createSurface(path, data);

App::get()->dispatchAsync([=]{
// main thread
createTexture(path); // this could be offloaded to a timed queue for additional optimization
triggerCallbacks(path);
});
});
}

void ThreadedImageLoader::cancel(const std::string path) {
triggerCallbacks(path);
}

bool ThreadedImageLoader::hasTexture(const std::string path) const {
return mTextureCache.find(path) != mTextureCache.end();
}

const ci::gl::TextureRef ThreadedImageLoader::getTexture(const std::string path) const {
auto it = mTextureCache.find(path);
if (it == mTextureCache.end()) {
return nullptr;
}
return it->second;
}

bool ThreadedImageLoader::isLoading(const std::string path) {
lock_guard<mutex> lock(mCallbackMutex);
auto cbIt = mCallbacks.find(path);
return cbIt != mCallbacks.end();
}

void ThreadedImageLoader::triggerCallbacks(const std::string path) {
lock_guard<mutex> lock(mCallbackMutex);
auto cbIt = mCallbacks.find(path);
if (cbIt != mCallbacks.end()) {

// try to get texture
gl::TextureRef tex = nullptr;
auto texIt = mTextureCache.find(path);
if (texIt != mTextureCache.end()) {
tex = texIt->second;
}

// trigger callbacks w texture or nullptr
for (auto callback : cbIt->second) {
callback(path, tex);
}

// clear callbacks for this path
mCallbacks.erase(cbIt);
}
}

ci::ImageSourceRef ThreadedImageLoader::loadFile(const std::string path) {
return ci::loadImage(path);
}

void ThreadedImageLoader::createSurface(const std::string path, const ci::ImageSourceRef source) {
lock_guard<mutex> lock(mSurfaceMutex);
// create surface and store on cpu memory
mSurfaceCache[path] = Surface::create(source);
}

void ThreadedImageLoader::createTexture(const std::string path) {
ci::SurfaceRef surface = nullptr;

{
lock_guard<mutex> lock(mSurfaceMutex);
auto surfIt = mSurfaceCache.find(path);
if (surfIt == mSurfaceCache.end()) {
return;
}

// save surface locally so the mutex doesn't get locked for too long
surface = surfIt->second;

// remove surface from cache when done
mSurfaceCache.erase(surfIt);
}

// create texture and store data on gpu memory
mTextureCache[path] = gl::Texture::create(*surface);
}

}
}
48 changes: 48 additions & 0 deletions samples/ThreadedImageLoadingSample/src/ThreadedImageLoader.h
@@ -0,0 +1,48 @@
#pragma once

#include "cinder/app/App.h"
#include "cinder/app/RendererGl.h"
#include "cinder/gl/gl.h"
#include "cinder/gl/Texture.h"

#include "ThreadedTaskQueue.h"

namespace bluecadet {
namespace utils {

class ThreadedImageLoader {
// texture will be nullptr if no success or canceled
typedef std::function<void(const std::string path, ci::gl::TextureRef textureOrNull)> Callback;

public:

ThreadedImageLoader(const unsigned int numThreads = 3);
virtual ~ThreadedImageLoader();

void load(const std::string path, Callback callback);
void cancel(const std::string path);

bool isLoading(const std::string path);
bool hasTexture(const std::string path) const;
const ci::gl::TextureRef getTexture(const std::string path) const;

protected:
ci::ImageSourceRef loadFile(const std::string path); // on worker thread
void createSurface(const std::string path, const ci::ImageSourceRef source); // on worker thread

void createTexture(const std::string path); // on main thread
void triggerCallbacks(const std::string path); // on main thread

std::map<std::string, std::vector<Callback>> mCallbacks;
std::map<std::string, ci::gl::TextureRef> mTextureCache;
std::map<std::string, ci::SurfaceRef> mSurfaceCache;

std::mutex mCallbackMutex;
std::mutex mSurfaceMutex;

ThreadedTaskQueue mTasks;

};

}
}
@@ -0,0 +1,62 @@
#include "cinder/app/App.h"
#include "cinder/app/RendererGl.h"
#include "cinder/gl/gl.h"

#include "ThreadedImageLoader.h"

using namespace ci;
using namespace ci::app;
using namespace std;
using namespace bluecadet::utils;

class ThreadedImageLoadingSampleApp : public App {
public:
void setup() override;
void mouseDown( MouseEvent event ) override;
void update() override;
void draw() override;

ThreadedImageLoader mLoader;

};

void ThreadedImageLoadingSampleApp::setup()
{
console() << "starting setup..." << endl;
mLoader.load(getAssetPath("blue.png").string(), [=] (const string path, gl::TextureRef texture) {
console() << "loaded " << path << endl;
});
mLoader.load(getAssetPath("white.png").string(), [=] (const string path, gl::TextureRef texture) {
console() << "loaded " << path << endl;
});
mLoader.load(getAssetPath("small-cadet_black-01.png").string(), [=] (const string path, gl::TextureRef texture) {
console() << "loaded " << path << endl;
});
console() << "...setup complete" << endl;
}

void ThreadedImageLoadingSampleApp::mouseDown( MouseEvent event )
{
}

void ThreadedImageLoadingSampleApp::update()
{
}

void ThreadedImageLoadingSampleApp::draw()
{
gl::clear(Color(0, 0, 0));
gl::enableAlphaBlending();

// getTexture will return nullptr if it doesn't exist, so the below would be safe
gl::draw(mLoader.getTexture(getAssetPath("blue.png").string()));
gl::draw(mLoader.getTexture(getAssetPath("white.png").string()));
gl::draw(mLoader.getTexture(getAssetPath("small-cadet_black-01.png").string()));

// you can also check for textures explicitly
// if (mLoader.hasTexture(getAssetPath("small-cadet_black-01.png").string())) {
// gl::draw(mLoader.getTexture(getAssetPath("small-cadet_black-01.png").string()));
// }
}

CINDER_APP( ThreadedImageLoadingSampleApp, RendererGl )
3 changes: 3 additions & 0 deletions samples/ThreadedImageLoadingSample/vc2013/Resources.rc
@@ -0,0 +1,3 @@
#include "../include/Resources.h"

1 ICON "..\\resources\\cinder_app_icon.ico"
@@ -0,0 +1,26 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ThreadedImageLoadingSample", "ThreadedImageLoadingSample.vcxproj", "{1E7BB131-F2F6-4913-824D-FB927A2D9171}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
Release|Win32 = Release|Win32
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1E7BB131-F2F6-4913-824D-FB927A2D9171}.Debug|Win32.ActiveCfg = Debug|Win32
{1E7BB131-F2F6-4913-824D-FB927A2D9171}.Debug|Win32.Build.0 = Debug|Win32
{1E7BB131-F2F6-4913-824D-FB927A2D9171}.Release|Win32.ActiveCfg = Release|Win32
{1E7BB131-F2F6-4913-824D-FB927A2D9171}.Release|Win32.Build.0 = Release|Win32
{1E7BB131-F2F6-4913-824D-FB927A2D9171}.Debug|x64.ActiveCfg = Debug|x64
{1E7BB131-F2F6-4913-824D-FB927A2D9171}.Debug|x64.Build.0 = Debug|x64
{1E7BB131-F2F6-4913-824D-FB927A2D9171}.Release|x64.ActiveCfg = Release|x64
{1E7BB131-F2F6-4913-824D-FB927A2D9171}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

0 comments on commit 3376bf4

Please sign in to comment.