Last active
February 26, 2019 22:42
-
-
Save paulhoux/2e4502c3540c4c5c4cb5718d74023516 to your computer and use it in GitHub Desktop.
Example of a background loading thread with a progress bar. Meant to be used with Cinder: https://libcinder.org
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "cinder/Utilities.h" | |
#include "cinder/app/App.h" | |
#include "cinder/app/RendererGl.h" | |
#include "cinder/gl/gl.h" | |
using namespace ci; | |
using namespace app; | |
using namespace std; | |
class ProgressBarApp : public App { | |
public: | |
void setup() override; | |
void cleanup() override; | |
void draw() override; | |
void drawTextures(); | |
void drawProgress(); | |
void drawSpinner(); | |
float getProgress() const; | |
private: | |
void threadFn( gl::ContextRef ctx ); | |
std::unique_ptr<std::thread> mThread; // Our background loading thread. | |
size_t mCount{ 0 }; // The number of files to load. | |
std::vector<fs::path> mQueue; // The list of files to load. | |
mutable std::mutex mQueueMutex; // Controls access to the queue. | |
std::vector<gl::Texture2dRef> mTextures; // The list of loaded textures. | |
mutable std::mutex mTexturesMutex; // Controls access to the loaded textures. | |
std::atomic_bool mTerminated{ true }; // Will make sure our background thread is properly terminated. | |
}; | |
void ProgressBarApp::setup() | |
{ | |
// Look in the assets folder and find all PNG files in there. | |
const auto assets = getAssetPath( "" ); | |
for( const auto &entry : fs::directory_iterator( assets ) ) { | |
const auto &file = entry.path(); | |
if( is_regular_file( file ) && file.extension() == ".png" ) | |
mQueue.push_back( file ); | |
} | |
// Count the files. | |
mCount = mQueue.size(); | |
// Now create a shared OpenGL context, that we use to load and create textures on a background thread. | |
gl::ContextRef backgroundCtx = gl::Context::create( gl::context() ); | |
// Create our background thread, which will load the images in the queue. | |
mTerminated = false; | |
mThread = std::make_unique<std::thread>( std::bind( &ProgressBarApp::threadFn, this, backgroundCtx ) ); | |
} | |
void ProgressBarApp::cleanup() | |
{ | |
// Terminate our background thread. | |
mTerminated = true; | |
// Now wait until it is actually terminated. | |
if( mThread && mThread->joinable() ) | |
mThread->join(); | |
// Done. | |
mThread.reset(); | |
} | |
void ProgressBarApp::draw() | |
{ | |
// Clear the window. | |
gl::clear(); | |
gl::color( 1, 1, 1 ); | |
// Draw the loaded textures. | |
drawTextures(); | |
if( getProgress() < 1.0f ) { | |
// Draw the progress bar. | |
drawProgress(); | |
// Draw an animated spinner. | |
drawSpinner(); | |
} | |
} | |
void ProgressBarApp::drawTextures() | |
{ | |
// Make sure we're the only ones accessing the list of textures. | |
// The background thread might access the list at the same time and that would be bad. | |
std::lock_guard<std::mutex> lock( mTexturesMutex ); | |
const int cols = getWindowWidth() / 100; | |
for( int i = 0; i < int( mTextures.size() ); ++i ) { | |
const int x = ( i % cols ) * 100; | |
const int y = ( i / cols ) * 100; | |
const auto &texture = mTextures[i]; | |
const auto area = Area::proportionalFit( texture->getBounds(), { x, y, x + 100, y + 100 }, true, true ); | |
gl::draw( texture, area ); | |
} | |
// Note: 'lock' will go out of scope and release the lock for us. | |
} | |
void ProgressBarApp::drawProgress() | |
{ | |
gl::ScopedModelMatrix scpModel; | |
const auto size = vec2( 256, 16 ); | |
gl::translate( getWindowCenter() ); | |
gl::translate( -0.5f * size ); | |
gl::drawSolidRect( { 0, 0, getProgress() * size.x, size.y } ); | |
gl::drawStrokedRect( { 0, 0, size.x, size.y } ); | |
} | |
void ProgressBarApp::drawSpinner() | |
{ | |
gl::ScopedModelMatrix scpModel; | |
const float angle = float( getElapsedSeconds() ); | |
const float step = glm::radians( 30.0f ); | |
const float radius = 25.0f; | |
gl::translate( getWindowCenter() + vec2( 0, 100 ) ); | |
for( int i = 0; i < 12; ++i ) { | |
gl::drawSolidCircle( radius * vec2( glm::sin( i * step - angle ), glm::cos( i * step - angle ) ), 3.0f ); | |
} | |
} | |
float ProgressBarApp::getProgress() const | |
{ | |
// First, we need to make sure we're the only ones accessing the queue. | |
// The background thread might access the queue at the same time and that would be bad. | |
std::lock_guard<std::mutex> lock( mQueueMutex ); | |
// Now calculate and return the progress. It's a value between 0 and 1. | |
if( mCount > 0 ) | |
return float( mCount - mQueue.size() ) / mCount; | |
return 1.0f; | |
} | |
void ProgressBarApp::threadFn( gl::ContextRef ctx ) | |
{ | |
// Let's name the thread, making it easier to debug it. | |
setThreadName( "Loader thread" ); | |
// Enable the OpenGL context. | |
ctx->makeCurrent(); | |
// Now load all images one by one. | |
while( !mTerminated ) { | |
// Let's first wait a little, making it easier to see how it works. | |
std::this_thread::sleep_for( std::chrono::milliseconds( 200 ) ); | |
// Make sure we're the only ones accessing the queue. | |
// The main thread might access the queue at the same time and that would be bad. | |
std::unique_lock<std::mutex> lockQueue( mQueueMutex ); | |
// If we're done, go back to sleep. 'lockQueue' will go out of scope and release the lock for us. | |
if( mQueue.empty() ) | |
continue; | |
// Now grab a file from the queue. | |
const auto file = mQueue.back(); | |
mQueue.pop_back(); | |
// We're done with the queue, so we can release our lock now. | |
lockQueue.unlock(); | |
// Load the image. | |
const auto texture = gl::Texture2d::create( loadImage( file ) ); | |
// Make sure OpenGL has finished uploading it to the GPU. | |
auto fence = gl::Sync::create(); | |
fence->clientWaitSync(); | |
// Make sure we're the only ones accessing the list of textures. | |
// The main thread might access the list at the same time and that would be bad. | |
std::unique_lock<std::mutex> lockTextures( mTexturesMutex ); | |
// Finally, add it to the list of loaded textures. | |
mTextures.push_back( texture ); | |
// Note: 'lockTextures' will go out of scope and release the lock for us. | |
} | |
// Bye bye! | |
} | |
CINDER_APP( ProgressBarApp, RendererGl ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment