/* 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; };