Skip to content

Instantly share code, notes, and snippets.

@paulhoux
Created February 14, 2019 13:37
Show Gist options
  • Save paulhoux/bfe52eccef7ddf84621881e6a628ed8c to your computer and use it in GitHub Desktop.
Save paulhoux/bfe52eccef7ddf84621881e6a628ed8c to your computer and use it in GitHub Desktop.
Fast texture loading using S3TC+LZ4 compressed textures.
/*
Copyright (c) 2019, Paul Houx - All rights reserved.
This code is intended for use with the Cinder C++ library: http://libcinder.org
Redistribution and use in source and binary forms, with or without modification, are permitted provided that
the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and
the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include "FastTexture.h"
#include <cinder/ImageIo.h>
#include <cinder/Log.h>
#include <cinder/System.h>
#include <cinder/Timer.h>
#include <cinder/Utilities.h>
#include <cinder/app/App.h>
#include <cinder/gl/Pbo.h>
#include <cinder/gl/draw.h>
#include <crunch/crnlib.h>
#include <lz4/lz4.h>
#include <lz4/lz4hc.h>
#pragma comment( lib, "crnlib.lib" )
using namespace ci;
void FastTexture::read( const fs::path &src )
{
mPath = src;
mBitmap = FastTextureCompressor::read( src );
}
void FastTexture::write( const fs::path &dst ) const
{
FastTextureCompressor::write( mBitmap, dst );
}
void FastTexture::unpack()
{
if( mBitmap.data && !mTexture && !mPendingAsyncUnpack ) {
CI_LOG_V( "Unpacking texture." );
const auto fmt = gl::Texture2d::Format().internalFormat( GL_COMPRESSED_RGBA_S3TC_DXT5_EXT ).loadTopDown();
mTexture = gl::Texture2d::createFromDds( getBitmapAsDataSource(), fmt );
}
}
void FastTexture::unpack( const ci::gl::PboRef &pbo )
{
if( mBitmap.data && !mTexture && !mPendingAsyncUnpack ) {
CI_LOG_V( "Unpacking texture using Pbo." );
const auto fmt = gl::Texture2d::Format().internalFormat( GL_COMPRESSED_RGBA_S3TC_DXT5_EXT ).intermediatePbo( pbo ).loadTopDown();
mTexture = gl::Texture2d::createFromDds( getBitmapAsDataSource(), fmt );
}
}
void FastTexture::unpackAsync()
{
if( mBitmap.data && !mTexture && !mPendingAsyncUnpack ) {
CI_LOG_V( "Unpacking texture asynchronously." );
mPendingAsyncUnpack = true;
FastTextureLoader::get().reload( shared_from_this(), true );
}
}
void FastTexture::reset()
{
if( mTexture )
CI_LOG_V( "Unloading texture." );
mTexture.reset();
mPendingAsyncUnpack = false;
FastTextureLoader::get().cancel( shared_from_this() );
}
void FastTexture::draw( const vec2 &offset ) const
{
const auto image = getTexture2d();
gl::draw( image, offset );
}
void FastTexture::draw( const Area &srcArea, const Rectf &dstRect ) const
{
const auto image = getTexture2d();
gl::draw( image, srcArea, dstRect );
}
void FastTexture::draw( const Rectf &dstRect ) const
{
const auto image = getTexture2d();
gl::draw( image, dstRect );
}
void FastTexture::drawProportional( const Area &dstArea, bool center, bool expand ) const
{
const auto image = getTexture2d();
if( image ) {
const auto bounds = Rectf( Area::proportionalFit( image->getBounds(), dstArea, center, expand ) );
gl::draw( image, bounds );
}
}
DataSourceRef FastTexture::getBitmapAsDataSource() const
{
return FastTextureCompressor::bitmapAsDataSource( mBitmap );
}
void FastTexture::replace( Bitmap bitmap, gl::Texture2dRef texture )
{
mBitmap = std::move( bitmap );
mTexture = std::move( texture );
mPendingAsyncUnpack = false;
}
void FastTexture::replaceIfAsync( Bitmap bitmap, ci::gl::Texture2dRef texture )
{
if( mPendingAsyncUnpack ) {
mBitmap = std::move( bitmap );
mTexture = std::move( texture );
mPendingAsyncUnpack = false;
}
}
// ------------------------------------------------------------------------------------------------
ScopedFastTextureBind::ScopedFastTextureBind( const FastTexture &texture )
: mCtx( gl::context() )
{
mTexture = texture.getTexture2d();
if( mTexture ) {
mTarget = mTexture->getTarget();
mTextureUnit = mCtx->getActiveTexture();
mCtx->pushTextureBinding( mTarget, mTexture->getId(), mTextureUnit );
}
}
ScopedFastTextureBind::ScopedFastTextureBind( const FastTextureRef &texture )
: mCtx( gl::context() )
{
mTexture = texture->getTexture2d();
if( mTexture ) {
mTarget = mTexture->getTarget();
mTextureUnit = mCtx->getActiveTexture();
mCtx->pushTextureBinding( mTarget, mTexture->getId(), mTextureUnit );
}
}
ScopedFastTextureBind::ScopedFastTextureBind( const FastTexture &texture, uint8_t textureUnit )
: mCtx( gl::context() )
{
mTexture = texture.getTexture2d();
if( mTexture ) {
mTarget = mTexture->getTarget();
mTextureUnit = textureUnit;
mCtx->pushTextureBinding( mTarget, mTexture->getId(), mTextureUnit );
}
}
ScopedFastTextureBind::ScopedFastTextureBind( const FastTextureRef &texture, uint8_t textureUnit )
: mCtx( gl::context() )
{
mTexture = texture->getTexture2d();
if( mTexture ) {
mTarget = mTexture->getTarget();
mTextureUnit = textureUnit;
mCtx->pushTextureBinding( mTarget, mTexture->getId(), mTextureUnit );
}
}
ScopedFastTextureBind::~ScopedFastTextureBind()
{
if( mTexture ) {
mCtx->popTextureBinding( mTarget, mTextureUnit );
}
}
// ------------------------------------------------------------------------------------------------
FastTexture::Bitmap FastTextureCompressor::compress( const fs::path &src )
{
return compressImage( loadImageFile( src ) );
}
FastTexture::Bitmap FastTextureCompressor::read( const fs::path &src )
{
FastTexture::Bitmap result;
FILE *file = nullptr;
if( 0 == fopen_s( &file, src.string().c_str(), "rb" ) ) {
uint8_t signature[4];
fread( &signature, 1, 4, file );
if( signature[0] == 'J' && signature[1] == 'A' && signature[2] == 'Z' && signature[3] == 'z' ) {
uint8_t version;
fread( &version, 1, 1, file );
fread( &result.width, 4, 1, file );
fread( &result.height, 4, 1, file );
fread( &result.compressedSizeInBytes, 4, 1, file );
fread( &result.uncompressedSizeInBytes, 4, 1, file );
result.data = std::shared_ptr<uint8_t>( new uint8_t[result.compressedSizeInBytes] );
fread( result.data.get(), result.compressedSizeInBytes, 1, file );
}
fclose( file );
}
return result;
}
void FastTextureCompressor::write( FastTexture::Bitmap bitmap, const fs::path &dst )
{
FILE *file = nullptr;
if( 0 == fopen_s( &file, dst.string().c_str(), "wb" ) ) {
uint8_t signature[] = { 'J', 'A', 'Z', 'z' };
fwrite( signature, 1, 4, file );
uint8_t version = 1;
fwrite( &version, 1, 1, file );
fwrite( &bitmap.width, 4, 1, file );
fwrite( &bitmap.height, 4, 1, file );
fwrite( &bitmap.compressedSizeInBytes, 4, 1, file );
fwrite( &bitmap.uncompressedSizeInBytes, 4, 1, file );
fwrite( bitmap.data.get(), bitmap.compressedSizeInBytes, 1, file );
fclose( file );
}
}
ci::DataSourceRef FastTextureCompressor::bitmapAsDataSource( FastTexture::Bitmap bitmap )
{
assert( bitmap.uncompressedSizeInBytes > 0 );
assert( bitmap.data );
auto decompressedBuffer = Buffer::create( bitmap.uncompressedSizeInBytes );
LZ4_decompress_fast( reinterpret_cast<const char *>( bitmap.data.get() ), static_cast<char *>( decompressedBuffer->getData() ), bitmap.uncompressedSizeInBytes );
return DataSourceBuffer::create( decompressedBuffer );
}
Surface FastTextureCompressor::loadImageFile( const fs::path &src )
{
Surface s( loadImage( src ) );
// We always need alpha.
Surface result( s.getWidth(), s.getHeight(), true );
result.copyFrom( s, s.getBounds() );
return result;
}
FastTexture::Bitmap FastTextureCompressor::compressImage( const Surface &src )
{
// Set compression parameters.
crn_comp_params compParams;
compParams.clear();
compParams.m_width = src.getWidth();
compParams.m_height = src.getHeight();
compParams.m_target_bitrate = 0;
compParams.m_num_helper_threads = std::max( 1, System::getNumCores() - 1 );
compParams.m_file_type = cCRNFileTypeDDS;
compParams.m_format = cCRNFmtDXT5;
compParams.set_flag( cCRNCompFlagPerceptual, true );
compParams.set_flag( cCRNCompFlagHierarchical, true );
compParams.m_pImages[0][0] = reinterpret_cast<const uint32_t *>( src.getData() );
if( !compParams.check() )
CI_LOG_W( "Compression parameters may not be reasonable." );
// Initialize bitmap data.
FastTexture::Bitmap result;
// Compress image.
void *crunchResult = crn_compress( compParams, result.uncompressedSizeInBytes, NULL, NULL );
if( nullptr != crunchResult ) {
result.width = src.getWidth();
result.height = src.getHeight();
result.data = std::shared_ptr<uint8_t>( new uint8_t[LZ4_compressBound( result.uncompressedSizeInBytes )] );
result.compressedSizeInBytes = LZ4_compress_HC( static_cast<const char *>( crunchResult ), reinterpret_cast<char *>( result.data.get() ), result.uncompressedSizeInBytes, result.uncompressedSizeInBytes, LZ4HC_CLEVEL_MAX );
crn_free_block( crunchResult );
}
else
CI_LOG_E( "Failed to compress image!" );
// Done.
return result;
}
// ------------------------------------------------------------------------------------------------
FastTextureStore::FastTextureStore()
{
assert( app::isMainThread() );
}
FastTextureRef FastTextureStore::load( const ci::fs::path &src )
{
assert( app::isMainThread() );
// Check if available in the store.
const auto id = hash( src );
const auto stored = retrieve( id );
if( stored )
return stored;
// Create and load texture.
const auto bitmap = FastTextureCompressor::read( src );
const auto texture = std::make_shared<FastTexture>( src, bitmap );
store( id, texture );
return texture;
}
FastTextureRef FastTextureStore::loadAsync( const ci::fs::path &src )
{
assert( app::isMainThread() );
// Check if available in the store.
const auto id = hash( src );
const auto stored = retrieve( id );
if( stored )
return stored;
// Schedule for asynchronous loading.
const auto texture = FastTextureLoader::get().load( src );
store( id, texture );
return texture;
}
void FastTextureStore::store( size_t id, const FastTextureRef &texture )
{
assert( app::isMainThread() );
mStore[id] = texture;
}
// ------------------------------------------------------------------------------------------------
void FastTextureLoader::start()
{
const unsigned threadCount = std::max( 1, System::getNumCores() - 1 );
start( threadCount );
}
void FastTextureLoader::start( unsigned threadCount )
{
const auto count = unsigned( mThreads.size() );
if( threadCount > count ) {
for( unsigned i = 0; i < threadCount - count; ++i ) {
assert( gl::context() );
// Create shared context.
gl::ContextRef ctx = gl::Context::create( gl::context() );
// Create thread.
mThreads.push_back( std::make_unique<std::thread>( std::bind( &FastTextureLoader::thread, this, ctx ) ) );
}
}
}
void FastTextureLoader::stop()
{
mQueue.cancel();
for( auto &thread : mThreads ) {
if( thread && thread->joinable() )
thread->join();
}
mThreads.clear();
// Clear queue.
Request req;
while( mQueue.tryPopBack( &req ) ) {
}
}
FastTextureRef FastTextureLoader::load( const ci::fs::path &src )
{
const auto texture = std::make_shared<FastTexture>( src );
mQueue.pushFront( { texture, false } );
return texture;
}
void FastTextureLoader::reload( const FastTextureRef &texture, bool onlyUnpack )
{
// Undo cancel.
canceled( texture );
mQueue.pushFront( { texture, onlyUnpack } );
}
void FastTextureLoader::cancel( const FastTextureRef &texture )
{
std::lock_guard<std::mutex> lock( mMutex );
const auto itr = std::find( mCanceled.begin(), mCanceled.end(), texture );
if( itr == mCanceled.end() )
mCanceled.push_back( texture );
}
bool FastTextureLoader::canceled( const FastTextureRef &texture )
{
std::lock_guard<std::mutex> lock( mMutex );
const auto itr = std::find( mCanceled.begin(), mCanceled.end(), texture );
if( itr == mCanceled.end() )
return false;
mCanceled.erase( itr );
return true;
}
void FastTextureLoader::thread( const gl::ContextRef &ctx )
{
setThreadName( "FastTextureLoader thread" );
// Enable shared context.
ctx->makeCurrent();
//
const auto pbo = gl::Pbo::create( GL_PIXEL_UNPACK_BUFFER );
const auto fmt = gl::Texture2d::Format().internalFormat( GL_COMPRESSED_RGBA_S3TC_DXT5_EXT ).intermediatePbo( pbo ).loadTopDown();
while( true ) {
Request req;
mQueue.popBack( &req );
if( !req.texture )
break;
if( canceled( req.texture ) )
continue;
try {
Timer timer{ true };
const auto existing = req.texture->getBitmap();
const auto bitmap = req.onlyUnpack && existing.data ? existing : FastTextureCompressor::read( req.texture->getPath() );
const auto texture = gl::Texture2d::createFromDds( FastTextureCompressor::bitmapAsDataSource( bitmap ), fmt );
const auto fence = glFenceSync( GL_SYNC_GPU_COMMANDS_COMPLETE, 0 );
glWaitSync( fence, 0, GL_TIMEOUT_IGNORED );
glDeleteSync( fence );
CI_LOG_V( "Loading compressed image took:" << timer.getSeconds() );
if( req.onlyUnpack )
req.texture->replaceIfAsync( bitmap, texture );
else
req.texture->replace( bitmap, texture );
}
catch( const std::exception &exc ) {
CI_LOG_EXCEPTION( "Failed to load compressed image:", exc );
}
}
}
/*
Copyright (c) 2019, Paul Houx - All rights reserved.
This code is intended for use with the Cinder C++ library: http://libcinder.org
Redistribution and use in source and binary forms, with or without modification, are permitted provided that
the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and
the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <mutex>
#include <thread>
#include <utility>
#include <cinder/ConcurrentCircularBuffer.h>
#include <cinder/Filesystem.h>
#include <cinder/Surface.h>
#include <cinder/gl/Context.h>
#include <cinder/gl/Texture.h>
#include <tools/HashHelper.h>
using FastTextureRef = std::shared_ptr<class FastTexture>;
using FastTextureWeakRef = std::weak_ptr<class FastTexture>;
class FastTexture : public std::enable_shared_from_this<FastTexture> {
public:
struct Bitmap {
uint32_t width{ 0 };
uint32_t height{ 0 };
uint32_t compressedSizeInBytes{ 0 };
uint32_t uncompressedSizeInBytes{ 0 };
std::shared_ptr<uint8_t> data;
};
static std::shared_ptr<FastTexture> create() { return std::make_shared<FastTexture>(); }
static std::shared_ptr<FastTexture> create( Bitmap bitmap ) { return std::make_shared<FastTexture>( bitmap ); }
static std::shared_ptr<FastTexture> create( const ci::fs::path &src ) { return std::make_shared<FastTexture>( src ); }
static std::shared_ptr<FastTexture> create( const ci::fs::path &src, Bitmap bitmap ) { return std::make_shared<FastTexture>( src, bitmap ); }
FastTexture() = default;
explicit FastTexture( Bitmap bitmap )
: mBitmap( std::move( bitmap ) )
{
}
explicit FastTexture( ci::fs::path src )
: mPath( std::move( src ) )
{
}
FastTexture( ci::fs::path src, Bitmap bitmap )
: mBitmap( std::move( bitmap ) )
, mPath( std::move( src ) )
{
}
//! Reads bitmap data from a file.
void read( const ci::fs::path &src );
//! Writes bitmap data to a file.
void write( const ci::fs::path &dst ) const;
//! Unpacks the compressed data from memory and creates an OpenGL texture from it.
void unpack();
//! Unpacks the compressed data from memory and creates an OpenGL texture from it. Uses the supplied \a pbo to upload the data.
void unpack( const ci::gl::PboRef &pbo );
//! Unpacks the compressed data from memory and creates an OpenGL texture from it. Unpacking is done on a background thread. Requires a prior call to FastTextureLoader::start().
void unpackAsync();
//! Deletes the OpenGL texture, but keeps the compressed data in memory.
void reset();
//! Draws the texture if available.
void draw( const ci::vec2 &offset = ci::vec2() ) const;
//! Draws the texture if available.
void draw( const ci::Area &srcArea, const ci::Rectf &dstRect ) const;
//! Draws the texture if available.
void draw( const ci::Rectf &dstRect ) const;
//! Draws the texture if available, scaled to fit the \a dstArea.
void drawProportional( const ci::Area &dstArea, bool center, bool expand = false ) const;
//! Returns the bitmap data.
Bitmap getBitmap() const { return mBitmap; }
//! Returns the bitmap data as a convenient data source.
ci::DataSourceRef getBitmapAsDataSource() const;
//! Returns the OpenGL texture.
ci::gl::Texture2dRef getTexture2d() const { return mTexture; }
//! Returns the width of the texture in pixels.
uint32_t getWidth() const { return mBitmap.width; }
//! Returns the height of the texture in pixels.
uint32_t getHeight() const { return mBitmap.height; }
//! Returns the size of the texture in pixels.
ci::ivec2 getSize() const { return { mBitmap.width, mBitmap.height }; }
//! Returns the bounds of the texture.
ci::Area getBounds() const { return { 0, 0, int( mBitmap.width ), int( mBitmap.height ) }; }
//! Returns the size in bytes of the compressed data (CPU memory).
uint32_t getCompressedBytes() const { return mBitmap.compressedSizeInBytes; }
//! Returns the size in bytes of the uncompressed data (GPU memory).
uint32_t getUncompressedBytes() const { return mBitmap.uncompressedSizeInBytes; }
//! Returns the path to the texture file.
const ci::fs::path &getPath() const { return mPath; }
//! Replaces the bitmap and texture.
void replace( Bitmap bitmap, ci::gl::Texture2dRef texture );
//! Replaces the bitmap and texture if awaiting an async unpack.
void replaceIfAsync( Bitmap bitmap, ci::gl::Texture2dRef texture );
private:
Bitmap mBitmap;
ci::gl::Texture2dRef mTexture;
ci::fs::path mPath;
mutable bool mPendingAsyncUnpack{ false };
};
// ------------------------------------------------------------------------------------------------
class ScopedFastTextureBind : private ci::Noncopyable {
public:
explicit ScopedFastTextureBind( const FastTexture &texture );
explicit ScopedFastTextureBind( const FastTextureRef &texture );
ScopedFastTextureBind( const FastTexture &texture, uint8_t textureUnit );
ScopedFastTextureBind( const FastTextureRef &texture, uint8_t textureUnit );
~ScopedFastTextureBind();
private:
ci::gl::Context * mCtx;
ci::gl::Texture2dRef mTexture;
GLenum mTarget;
uint8_t mTextureUnit;
};
// ------------------------------------------------------------------------------------------------
class FastTextureCompressor {
public:
//! Loads a regular image file and returns the compressed bitmap.
static FastTexture::Bitmap compress( const ci::fs::path &src );
//! Reads a compressed bitmap from file.
static FastTexture::Bitmap read( const ci::fs::path &src );
//! Writes a compressed bitmap to the destination file.
static void write( FastTexture::Bitmap bitmap, const ci::fs::path &dst );
//! Returns the bitmap data as a convenient data source.
static ci::DataSourceRef bitmapAsDataSource( FastTexture::Bitmap bitmap );
private:
//! Loads a regular image file.
static ci::Surface loadImageFile( const ci::fs::path &src );
//! Compresses the surface into a bitmap using S3TC texture compression, followed by LZ4 data compression.
static FastTexture::Bitmap compressImage( const ci::Surface &src );
};
// ------------------------------------------------------------------------------------------------
class FastTextureStore {
public:
static FastTextureStore &get()
{
static FastTextureStore instance;
return instance;
}
//! Loads the texture.
FastTextureRef load( const ci::fs::path &src );
//! Loads the texture asynchronously.
FastTextureRef loadAsync( const ci::fs::path &src );
//! Clears the store by deleting all cached textures.
void clear() { mStore.clear(); }
//! Returns whether a texture has already been loaded for path \a src.
bool contains( const ci::fs::path &src ) const { return contains( hash( src ) ); }
//! Returns whether a texture has already been loaded. Parameter \a id is a hashed ci::fs::path.
bool contains( size_t id ) const { return mStore.count( id ) > 0 && !mStore.at( id ).expired(); }
//! Returns the texture with hash \a id if it exists or a nullptr if it does not.
FastTextureRef retrieve( size_t id )
{
if( mStore.count( id ) > 0 )
return mStore.at( id ).lock();
return nullptr;
}
//! Stores a previously loaded texture into the texture store.
void store( const ci::fs::path &src, const FastTextureRef &texture ) { store( hash( src ), texture ); }
//! Stores a previously loaded texture into the texture store.
void store( size_t id, const FastTextureRef &texture );
//! Returns the hash of a path.
static size_t hash( const ci::fs::path &src )
{
size_t hash{ 0 };
hash_combine( hash, src.string() );
return hash;
}
private:
FastTextureStore();
std::unordered_map<size_t, FastTextureWeakRef> mStore;
std::vector<ci::fs::path> mAsyncQueue;
};
inline FastTextureRef loadFastTexture( const ci::fs::path &src )
{
return FastTextureStore::get().load( src );
}
// ------------------------------------------------------------------------------------------------
class FastTextureLoader {
FastTextureLoader() = default;
public:
static FastTextureLoader &get()
{
static FastTextureLoader instance;
return instance;
}
//! Starts the background loader threads. By default, the number of threads spawned is (number of logical cores - 1).
void start();
//! Starts the background loader threads. The number of threads is determined by \a threadCount.
void start( unsigned threadCount );
//! Terminates the background loader threads.
void stop();
//! Queues a texture for asynchronous loading. Returns the texture that will receive the data. Note that the data is not available immediately after calling!
FastTextureRef load( const ci::fs::path &src );
//! Queues a texture for asynchronous loading. If \a onlyUnpack is \c TRUE, it will only decompress the already available bitmap data.
void reload( const FastTextureRef &texture, bool onlyUnpack = false );
//! Remove \a texture from the loading queue.
void cancel( const FastTextureRef &texture );
private:
bool canceled( const FastTextureRef &texture );
void thread( const ci::gl::ContextRef &ctx );
struct Request {
FastTextureRef texture;
bool onlyUnpack;
};
std::vector<std::unique_ptr<std::thread>> mThreads;
ci::ConcurrentCircularBuffer<Request> mQueue{ 128 };
std::vector<FastTextureRef> mCanceled;
std::mutex mMutex;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment