From 0707225e13b04d2584b9e2061cadba97aac483a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Wed, 15 Jul 2026 01:42:46 -0300 Subject: [PATCH] Add shared texture lifetime scaffolding - introduce process-wide ResourceId, resource handles, and shared metrics - manage textures through factory-owned control blocks and a weak live registry - defer final texture destruction until display-time batch work is flushed - diagnose externally retained textures during Engine shutdown - migrate texture identity APIs and consumers from Uint32 to ResourceId - decouple texture memory accounting from the TextureFactory singleton - replace UITextureViewer callbacks with generation-based weak snapshots - remove obsolete texture context-reload APIs and loader callback registry - add registry, identity, memory, restart, and deferred-release tests - mark Stage 1 complete in the shared-resource architecture plan --- .../resource_shared_ownership_architecture.md | 38 ++- include/eepp/graphics/ninepatch.hpp | 2 +- include/eepp/graphics/particlesystem.hpp | 5 +- include/eepp/graphics/resource.hpp | 57 ++++ include/eepp/graphics/sprite.hpp | 18 +- include/eepp/graphics/texture.hpp | 22 +- include/eepp/graphics/textureatlas.hpp | 16 +- include/eepp/graphics/texturefactory.hpp | 102 +++--- include/eepp/graphics/textureloader.hpp | 17 +- include/eepp/graphics/textureregion.hpp | 10 +- src/eepp/graphics/framebuffer.cpp | 4 +- src/eepp/graphics/ninepatch.cpp | 6 +- src/eepp/graphics/particlesystem.cpp | 4 +- src/eepp/graphics/sprite.cpp | 28 +- src/eepp/graphics/texture.cpp | 42 ++- src/eepp/graphics/textureatlas.cpp | 16 +- src/eepp/graphics/texturefactory.cpp | 309 +++++++++++------- src/eepp/graphics/textureloader.cpp | 46 +-- src/eepp/graphics/textureregion.cpp | 22 +- src/eepp/ui/tools/uitextureviewer.cpp | 102 ++++-- src/eepp/ui/tools/uitextureviewer.hpp | 22 +- src/eepp/window/engine.cpp | 3 + src/eepp/window/window.cpp | 2 + src/tests/test_all/test.cpp | 3 - src/tests/test_all/test.hpp | 4 +- .../resource_prerequisite_tests.cpp | 94 ++++++ src/tools/uieditor/uieditor.cpp | 2 +- src/tools/uieditor/uieditor.hpp | 2 +- 28 files changed, 654 insertions(+), 344 deletions(-) create mode 100644 include/eepp/graphics/resource.hpp diff --git a/.agent/plans/resource_shared_ownership_architecture.md b/.agent/plans/resource_shared_ownership_architecture.md index 59b59e88a..70a789ec3 100644 --- a/.agent/plans/resource_shared_ownership_architecture.md +++ b/.agent/plans/resource_shared_ownership_architecture.md @@ -1,7 +1,7 @@ # eepp shared-resource ownership architecture -Status: active implementation baseline; Stage 0 and prerequisite fixes complete; Stage 1 is next, -2026-07-14. +Status: active implementation baseline; Stage 0, prerequisite fixes, and Stage 1 complete; +Stage 2 is next, 2026-07-15. This document freezes the contracts that must be true before the public texture API is changed. The implementation may refine names and small mechanics, but changing an invariant below requires an @@ -114,7 +114,9 @@ destruction contract used by scene nodes; it is not a general arbitrary-thread G These concepts are distinct: -- `ResourceId` is immutable and process-unique across Engine recreation in tests. +- `ResourceId` is immutable and process-unique across Engine recreation in tests. For textures it + is the value returned by `Texture::getTextureId()`; there is no separate factory-internal texture + ID. - `ResourceKey` is the immutable canonical semantic lookup key. Equality compares the complete key, never only a hash. - `displayName` is diagnostic text and may change without changing identity or catalog indexes. @@ -536,6 +538,22 @@ Drawable ownership defects remain assigned to their structural Stage 4 replaceme ### Stage 1: texture lifetime scaffolding, with old factory retention still active +Status: complete, 2026-07-15. Stable process-wide ResourceId, shared ResourceMetrics, +eepp-compatible resource aliases/deleter, and TextureFactory's weak live-texture registry are +implemented. +`Texture::getTextureId()` now returns that ResourceId directly, and every identity-based texture +API and stored consumer uses ResourceId; the only other texture identifier is the OpenGL handle. +The factory now retains the single TexturePtr control block internally while public texture APIs +still return raw pointers, preserving its old strong-retention behavior until Stage 2. Texture +memory updates no longer reach the factory singleton, and texture destruction no longer unregisters +itself through a singleton callback. Final handle release now queues Texture destruction in the +factory; `Window::display()` collects only after batch flush, and Engine shutdown performs a final +collection while its context remains valid. Debug assertions enforce graphics-thread release and +collection, while shutdown diagnostics report and defensively release GPU payloads from surviving +external handles. TextureLoader's static callback registry is removed. UITextureViewer reconciles +weak snapshots only when the atomic live-registry generation changes and strongly retains only the +currently enlarged texture. + Implement stable ResourceId, ResourceMetrics, centralized eepp-compatible handle creation, the weak TextureFactory live registry, TextureFactory's deferred released-texture queue, shutdown diagnostics, and graphics-thread assertions. Integrate collection into Window::display() after batch flush and @@ -722,12 +740,10 @@ Remove raw-owning `ResourceManager` only when no subclass or consumer depends - `EE_MEMORY_MANAGER`, supported debug/release configurations, static builds, and shared-library builds. -## 12. First implementation deliverable +## 12. Next implementation deliverable -The next coding deliverable is Stage 1 TextureFactory-specific lifetime scaffolding. It adds weak -live-texture observation and deferred texture collection while preserving current factory retention, -removes TextureLoader's static callback registry, and migrates UITextureViewer to the live registry. - -Stage 1 must also audit TextureFactory's uncalled context-recovery-era reload/grab/ungrab APIs and -remove them if repository and history inspection confirm they are obsolete. Stage 2 then changes -public texture APIs and migrates all holders in one cut. +Stage 1 is complete, including removal of the obsolete context-recovery APIs and TextureLoader +callback registry, weak UITextureViewer observation, and display/shutdown texture collection. The +next coding deliverable is the complete Stage 2 TexturePtr ownership cut described above: change +the public APIs and migrate every texture holder while temporary factory retention keeps the +repository behavior stable. diff --git a/include/eepp/graphics/ninepatch.hpp b/include/eepp/graphics/ninepatch.hpp index 595f9fc7f..dd2ba9915 100644 --- a/include/eepp/graphics/ninepatch.hpp +++ b/include/eepp/graphics/ninepatch.hpp @@ -22,7 +22,7 @@ class EE_API NinePatch : public DrawableResource { SideCount }; - static NinePatch* New( const Uint32& TexId, int left, int top, int right, int bottom, + static NinePatch* New( ResourceId textureId, int left, int top, int right, int bottom, const Float& pixelDensity = 1, const std::string& name = "" ); static NinePatch* New( Texture* tex, int left, int top, int right, int bottom, diff --git a/include/eepp/graphics/particlesystem.hpp b/include/eepp/graphics/particlesystem.hpp index 5eec5935b..3d9dea7ed 100644 --- a/include/eepp/graphics/particlesystem.hpp +++ b/include/eepp/graphics/particlesystem.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include using namespace EE::System; @@ -49,7 +50,7 @@ class EE_API ParticleSystem { /** Creates the new effect * @param Effect Number of the effect. * @param NumParticles Number of particles - * @param TexId Texture Id to render the particles + * @param textureId Texture identity used to render the particles * @param Pos Initial position * @param PartSize Size of the particles * @param AnimLoop Loop the animation? @@ -61,7 +62,7 @@ class EE_API ParticleSystem { * @param speed The speed on x axis (used for NoFx) * @param Acc The acceleration of the particle (used for NoFx) */ - void create( const ParticleEffect& Effect, const Uint32& NumParticles, const Uint32& TexId, + void create( const ParticleEffect& Effect, const Uint32& NumParticles, ResourceId textureId, const Vector2f& Pos, const Float& PartSize = 16.0f, const bool& AnimLoop = false, const Uint32& NumLoops = 1, const ColorAf& color = ColorAf( 1.0f, 1.0f, 1.0f, 1.0f ), diff --git a/include/eepp/graphics/resource.hpp b/include/eepp/graphics/resource.hpp new file mode 100644 index 000000000..b271bdc34 --- /dev/null +++ b/include/eepp/graphics/resource.hpp @@ -0,0 +1,57 @@ +#ifndef EE_GRAPHICS_RESOURCE_HPP +#define EE_GRAPHICS_RESOURCE_HPP + +#include +#include +#include + +#include + +namespace EE { namespace Graphics { + +class Texture; + +/** Immutable identity assigned once when a resource is created. Resource IDs are process-wide and + * are not reset when Engine state is recreated by tests. */ +class ResourceId { + public: + constexpr ResourceId() = default; + explicit constexpr ResourceId( Uint64 value ) : mValue( value ) {} + + constexpr Uint64 value() const { return mValue; } + explicit constexpr operator bool() const { return mValue != 0; } + + constexpr bool operator==( const ResourceId& other ) const { return mValue == other.mValue; } + constexpr bool operator!=( const ResourceId& other ) const { return !( *this == other ); } + constexpr bool operator<( const ResourceId& other ) const { return mValue < other.mValue; } + + private: + Uint64 mValue{ 0 }; +}; + +template using ResourcePtr = std::shared_ptr; +template using ResourceWeakPtr = std::weak_ptr; + +/** Shared accounting state kept by both a resource and its live-registry record. */ +class ResourceMetrics { + public: + std::size_t getMemoryBytes() const { return mMemoryBytes.load( std::memory_order_relaxed ); } + + private: + friend class Texture; + + void setMemoryBytes( std::size_t bytes ) { + mMemoryBytes.store( bytes, std::memory_order_relaxed ); + } + + std::atomic mMemoryBytes{ 0 }; +}; + +/** Deleter used by eepp resource control blocks so EE_MEMORY_MANAGER sees the matching eeDelete. */ +template struct ResourceDeleter { + void operator()( T* resource ) const noexcept { eeDelete( resource ); } +}; + +}} // namespace EE::Graphics + +#endif diff --git a/include/eepp/graphics/sprite.hpp b/include/eepp/graphics/sprite.hpp index 1bd4ec13b..9ce4d4f95 100644 --- a/include/eepp/graphics/sprite.hpp +++ b/include/eepp/graphics/sprite.hpp @@ -33,7 +33,7 @@ class EE_API Sprite : public Drawable { static Sprite* New( TextureRegion* TextureRegion ); - static Sprite* New( const Uint32& TexId, const Sizef& DestSize = Sizef( 0, 0 ), + static Sprite* New( ResourceId textureId, const Sizef& DestSize = Sizef( 0, 0 ), const Vector2i& offset = Vector2i( 0, 0 ), const Rect& TexSector = Rect( 0, 0, 0, 0 ) ); @@ -61,12 +61,12 @@ class EE_API Sprite : public Drawable { Sprite( TextureRegion* TextureRegion ); /** Creates a Sprite instance that holds a new TextureRegion from a texture already loaded. - * @param TexId The texture Id used to create the TextureRegion + * @param textureId The texture identity used to create the TextureRegion * @param DestSize The destination size of the TextureRegion created * @param offset The offset added to the position of the frame ( the TextureRegion ) * @param TexSector The sector of the texture used by the TextureRegion to be rendered */ - Sprite( const Uint32& TexId, const Sizef& DestSize = Sizef( 0, 0 ), + Sprite( ResourceId textureId, const Sizef& DestSize = Sizef( 0, 0 ), const Vector2i& offset = Vector2i( 0, 0 ), const Rect& TexSector = Rect( 0, 0, 0, 0 ) ); virtual ~Sprite(); @@ -194,13 +194,13 @@ class EE_API Sprite : public Drawable { bool createStatic( TextureRegion* TextureRegion ); /** Creates an static sprite (no animation). It creates a new TextureRegion. - * @param TexId The internal Texture Id + * @param textureId The texture identity * @param DestSize The destination size of the TextureRegion created * @param offset The offset added to the position of the frame ( the TextureRegion ) * @param TexSector The texture sector to be rendered ( default all the texture ) * @return True if success */ - bool createStatic( const Uint32& TexId, const Sizef& DestSize = Sizef( 0, 0 ), + bool createStatic( ResourceId textureId, const Sizef& DestSize = Sizef( 0, 0 ), const Vector2i& offset = Vector2i( 0, 0 ), const Rect& TexSector = Rect( 0, 0, 0, 0 ) ); @@ -221,13 +221,13 @@ class EE_API Sprite : public Drawable { void createAnimation( const unsigned int& SubFramesNum = 1 ); /** Add a frame to the sprite (on the current sub frame) - * @param TexId The internal Texture Id + * @param textureId The texture identity * @param DestSize The destination size of the frame * @param offset The offset added to the position of the frame * @param TexSector The texture sector to be rendered ( default all the texture ) * @return The frame position or 0 if fails */ - unsigned int addFrame( const Uint32& TexId, const Sizef& DestSize = Sizef( 0, 0 ), + unsigned int addFrame( ResourceId textureId, const Sizef& DestSize = Sizef( 0, 0 ), const Vector2i& offset = Vector2i( 0, 0 ), const Rect& TexSector = Rect( 0, 0, 0, 0 ) ); @@ -275,7 +275,7 @@ class EE_API Sprite : public Drawable { const Rect& TexSector = Rect( 0, 0, 0, 0 ) ); /** Add a frame on an specific subframe to the sprite - * @param TexId The internal Texture Id + * @param textureId The texture identity * @param NumFrame The Frame Number * @param NumSubFrame The Sub Frame Number * @param DestSize The destination size of the frame @@ -283,7 +283,7 @@ class EE_API Sprite : public Drawable { * @param TexSector The texture sector to be rendered ( default all the texture ) * @return True if success */ - bool addSubFrame( const Uint32& TexId, const unsigned int& NumFrame, + bool addSubFrame( ResourceId textureId, const unsigned int& NumFrame, const unsigned int& NumSubFrame, const Sizef& DestSize = Sizef( 0, 0 ), const Vector2i& offset = Vector2i( 0, 0 ), const Rect& TexSector = Rect( 0, 0, 0, 0 ) ); diff --git a/include/eepp/graphics/texture.hpp b/include/eepp/graphics/texture.hpp index 9d4b21386..af6dbee3a 100644 --- a/include/eepp/graphics/texture.hpp +++ b/include/eepp/graphics/texture.hpp @@ -5,11 +5,16 @@ #include #include #include +#include #include #include namespace EE { namespace Graphics { +class Texture; +using TexturePtr = ResourcePtr; +using TextureWeakPtr = ResourceWeakPtr; + class EE_API Texture : public DrawableResource, public Image, private NonCopyable { public: /** @enum TextureFilter Defines the texture filter used. */ @@ -154,7 +159,7 @@ class EE_API Texture : public DrawableResource, public Image, private NonCopyabl ** @param y Y offset in the texture where to copy the source image */ void update( Image* image, Uint32 x = 0, Uint32 y = 0 ); - /** Replaces the current texture with the image provided, reusing the current texture id. */ + /** Replaces the current texture with the image provided, reusing the current OpenGL handle. */ void replace( Image* image ); /** Flip the texture ( rotate the texture 90ยบ ). Warning: This is flipped in memory, a real @@ -289,11 +294,11 @@ class EE_API Texture : public DrawableResource, public Image, private NonCopyabl virtual bool isStateful() { return false; } - /** Set the texture factory internal id of the texture */ - void setTextureId( const Uint32& id ); + /** @return The process-wide identity assigned to this texture. */ + ResourceId getTextureId() const; - /** @return The texture factory internal id of the texture */ - const Uint32& getTextureId() const; + /** @return Shared diagnostic memory accounting for this texture. */ + const std::shared_ptr& getResourceMetrics() const; /** Reload the texture from the current local copy. */ void reload(); @@ -351,7 +356,8 @@ class EE_API Texture : public DrawableResource, public Image, private NonCopyabl const Uint32& memSize = 0, const Uint8* data = NULL ); std::string mFilepath; - Uint32 mTexId; + ResourceId mTextureId; + std::shared_ptr mResourceMetrics; int mTexture; unsigned int mImgWidth; @@ -370,6 +376,10 @@ class EE_API Texture : public DrawableResource, public Image, private NonCopyabl Uint8* iLock( const bool& ForceRGBA, const bool& KeepFormat ); void iTextureFilter( const Filter& filter ); + + void setResourceData( ResourceId resourceId, std::shared_ptr resourceMetrics ); + + void updateResourceMemorySize(); }; }} // namespace EE::Graphics diff --git a/include/eepp/graphics/textureatlas.hpp b/include/eepp/graphics/textureatlas.hpp index 4576d511d..cda6c8002 100644 --- a/include/eepp/graphics/textureatlas.hpp +++ b/include/eepp/graphics/textureatlas.hpp @@ -26,36 +26,36 @@ class EE_API TextureAtlas : public ResourceManager { /** Creates and add to the texture atlas a TextureRegion from a Texture. It will use the full *Texture as a TextureRegion. - * @param TexId The texture id + * @param textureId The texture identity * @param Name The texture name ( if any ) */ - TextureRegion* add( const Uint32& TexId, const std::string& Name = "" ); + TextureRegion* add( ResourceId textureId, const std::string& Name = "" ); /** Creates and add to the texture atlas a TextureRegion of the indicated part of the texture. - * @param TexId The texture id + * @param textureId The texture identity * @param SrcRect The texture part that will be used as the TextureRegion. * @param Name The texture name ( if any ) */ - TextureRegion* add( const Uint32& TexId, const Rect& SrcRect, const std::string& Name = "" ); + TextureRegion* add( ResourceId textureId, const Rect& SrcRect, const std::string& Name = "" ); /** Creates and add to the texture atlas a TextureRegion of the indicated part of the texture. - * @param TexId The texture id + * @param textureId The texture identity * @param SrcRect The texture part that will be used as the TextureRegion. * @param DestSize The destination size that the TextureRegion will have when rendered. * @param Name The texture name ( if any ) */ - TextureRegion* add( const Uint32& TexId, const Rect& SrcRect, const Sizef& DestSize, + TextureRegion* add( ResourceId textureId, const Rect& SrcRect, const Sizef& DestSize, const std::string& Name = "" ); /** Creates and add to the texture atlas a TextureRegion of the indicated part of the texture. - * @param TexId The texture id + * @param textureId The texture identity * @param SrcRect The texture part that will be used as the TextureRegion. * @param DestSize The destination size that the TextureRegion will have when rendered. * @param Offset The offset that will be added to the position passed when any Draw call is *used. * @param Name The texture name ( if any ) */ - TextureRegion* add( const Uint32& TexId, const Rect& SrcRect, const Sizef& DestSize, + TextureRegion* add( ResourceId textureId, const Rect& SrcRect, const Sizef& DestSize, const Vector2i& Offset, const std::string& Name = "" ); /** Creates and add to the texture atlas a TextureRegion from a Texture. It will use the full diff --git a/include/eepp/graphics/texturefactory.hpp b/include/eepp/graphics/texturefactory.hpp index f3386fbc6..0966ada64 100644 --- a/include/eepp/graphics/texturefactory.hpp +++ b/include/eepp/graphics/texturefactory.hpp @@ -11,6 +11,15 @@ using namespace EE::System; namespace EE { namespace Graphics { +struct TextureRegistryRecord { + ResourceId id; + std::string displayName; + TextureWeakPtr texture; + std::shared_ptr metrics; +}; + +using TextureRegistrySnapshot = std::vector; + /** @brief The Texture Manager Class. Here we do all the textures stuff. (Singleton Class) */ class EE_API TextureFactory : protected Mutex { SINGLETON_DECLARE_HEADERS( TextureFactory ) @@ -127,11 +136,11 @@ class EE_API TextureFactory : protected Mutex { const bool& CompressTexture = false, const bool& KeepLocalCopy = false, const Image::FormatConfiguration& imageformatConfiguration = Image::FormatConfiguration() ); - /** Removes and Unload the Texture Id - * @param TexId + /** Removes and unloads the texture identified by @p textureId. + * @param textureId The process-wide texture identity. * @return True if was removed */ - bool remove( Uint32 TexId ); + bool remove( ResourceId textureId ); /** Removes and Unload the Texture * @param texture The texture pointer @@ -139,17 +148,14 @@ class EE_API TextureFactory : protected Mutex { */ bool remove( Texture* texture ); - /** Reload all loaded textures to recover the OpenGL context */ - void reloadAllTextures(); - - /** Bind the the internal Texture Id indicated. This is useful if you are rendering a texture + /** Binds the texture identity indicated. This is useful if you are rendering a texture * outside this class. - * @param TexId The internal Texture Id + * @param textureId The process-wide texture identity. * @param coordinateType Use normalized or pixel coordinates * @param textureUnit The Texture Unit binded * @param forceRebind Force the texture bind (even if is already binded ). */ - void bind( const Uint32& TexId, + void bind( ResourceId textureId, Texture::CoordinateType coordinateType = Texture::CoordinateType::Normalized, const Uint32& textureUnit = 0, const bool& forceRebind = false ); @@ -165,22 +171,16 @@ class EE_API TextureFactory : protected Mutex { const Uint32& TextureUnit = 0, const bool& forceRebind = false ); /** - * @param TexId The internal Texture Id - * @return The OpenGL Texture Id (texture handler) - */ - Uint32 getTextureId( const Uint32& TexId ); - - /** - * @return The real current texture id (OpenGL Texture Id) - * @param TextureUnit The Texture Unit binded + * @return The currently bound OpenGL texture handle. + * @param TextureUnit The bound texture unit. */ int getCurrentTexture( const Uint32& TextureUnit = 0 ) const; - /** Set the current internal texture id. This will set the TexId as the current texture binded. - * @param TexId The real current texture id (OpenGL Texture Id) + /** Sets the currently bound OpenGL texture handle. + * @param textureHandle The OpenGL texture handle. * @param TextureUnit The Texture Unit binded */ - void setCurrentTexture( const int& TexId, const Uint32& TextureUnit ); + void setCurrentTexture( const int& textureHandle, const Uint32& TextureUnit ); /** Returns the number of textures loaded */ Uint32 getTextureCount(); @@ -188,6 +188,23 @@ class EE_API TextureFactory : protected Mutex { /** @return All the active textures */ std::vector getTextures(); + /** @return A non-owning diagnostic snapshot of every currently live texture. */ + TextureRegistrySnapshot snapshotTextures(); + + /** Removes expired records from the diagnostic live-texture registry. */ + void purgeExpiredTextures(); + + /** @return The generation of the live-texture registry. It changes when a texture is created or + * its last owning handle is released. */ + Uint64 getLiveTextureGeneration() const; + + /** Destroys textures whose final owning handle was released. Must run on the graphics thread + * after pending batches have been flushed and while a context is current. */ + void collectReleasedTextures(); + + /** @return The number of textures waiting for graphics-thread destruction. */ + std::size_t getPendingReleaseCount(); + /** Active a texture unit */ void setActiveTextureUnit( const Uint32& Unit ); @@ -197,20 +214,14 @@ class EE_API TextureFactory : protected Mutex { */ unsigned int getValidTextureSize( const unsigned int& Size ); - /** Determine if the TextureId passed exists */ - bool existsId( const Uint32& TexId ); + /** Determines whether the texture identity exists in the factory. */ + bool existsId( ResourceId textureId ); - /** Determine if the TextureId passed exists */ + /** Determines whether the texture is retained by the factory. */ bool exists( const Texture* tex ); - /** @return A pointer to the Texture */ - Texture* getTexture( const Uint32& TexId ); - - /** Get a local copy for all the textures */ - void grabTextures(); - - /** Reload all the grabbed textures */ - void ungrabTextures(); + /** @return The texture matching @p textureId, or null if it is not factory-retained. */ + Texture* getTexture( ResourceId textureId ); /** @return The memory used by the textures (in bytes) */ unsigned int getTextureMemorySize(); @@ -218,7 +229,7 @@ class EE_API TextureFactory : protected Mutex { /** It's possible to create textures outside the texture factory loader, but the library will * need to know of this texture, so it's necessary to push the texture to the factory. * @param Filepath The Texture path ( if exists ) - * @param TexId The OpenGL Texture Id + * @param textureHandle The OpenGL texture handle. * @param Width Texture Width * @param Height Texture Height * @param ImgWidth Image Width. @@ -231,7 +242,7 @@ class EE_API TextureFactory : protected Mutex { * @param MemSize The size of the texture in memory ( just if you need to specify the real size * in memory, just useful to calculate the total texture memory ). */ - Texture* pushTexture( const std::string& Filepath, const Uint32& TexId, + Texture* pushTexture( const std::string& Filepath, const Uint32& textureHandle, const unsigned int& Width, const unsigned int& Height, const unsigned int& ImgWidth, const unsigned int& ImgHeight, const bool& Mipmap, const unsigned int& Channels, @@ -261,23 +272,32 @@ class EE_API TextureFactory : protected Mutex { std::vector mCurrentTexture; - UnorderedMap mTextures; + using TextureMap = UnorderedMap; - unsigned int mMemSize; + struct LiveTextureRecord { + ResourceId id; + TextureWeakPtr texture; + std::shared_ptr metrics; + }; - Uint32 mTextureIdSeq; + struct TextureDeleter { + void operator()( Texture* texture ) const noexcept; + }; + + TextureMap mTextures; + UnorderedMap mLiveTextures; + std::vector mReleasedTextures; + std::atomic mLiveTextureGeneration{ 0 }; Texture::CoordinateType mLastCoordinateType; void unloadTextures(); - bool mErasing; + void resetTextureBinding( const Texture* texture ); - bool isErasing(); + void queueReleasedTexture( Texture* texture ); - void removeReference( Texture* Tex ); - - void updateMemorySize( Uint32 oldSize, Uint32 newSize ); + void diagnoseLiveTexturesAtShutdown(); }; }} // namespace EE::Graphics diff --git a/include/eepp/graphics/textureloader.hpp b/include/eepp/graphics/textureloader.hpp index 9ae14ca4b..2f5706ba6 100644 --- a/include/eepp/graphics/textureloader.hpp +++ b/include/eepp/graphics/textureloader.hpp @@ -1,8 +1,6 @@ #ifndef EE_GRAPHICS_TEXTURELOADER #define EE_GRAPHICS_TEXTURELOADER -#include - #include #include #include @@ -14,12 +12,6 @@ namespace EE { namespace Graphics { /** @brief The Texture loader loads a texture in synchronous or asynchronous mode. */ class EE_API TextureLoader { public: - typedef std::function OnTextureLoaded; - - static Uint32 pushLoadedCallback( const OnTextureLoaded& cb ); - - static void popLoadedCallback( const Uint32& cbId ); - /** Load a Texture from stream * @param Stream The io stream instance * @param Mipmap Use mipmaps? @@ -104,8 +96,8 @@ class EE_API TextureLoader { /** @return The file path to the texture ( if any ) */ const std::string& getFilepath() const; - /** @return The texture internal id */ - Uint32 getId() const; + /** @return The process-wide texture identity. */ + ResourceId getId() const; /** @return The texture instance ( if it was loaded ). */ Texture* getTexture() const; @@ -144,9 +136,6 @@ class EE_API TextureLoader { void reset(); private: - static UnorderedMap sCbs; - static std::atomic sNumCbs; - bool mLoaded{ false }; bool mTexLoaded{ false }; bool mDirectUpload{ false }; @@ -161,8 +150,6 @@ class EE_API TextureLoader { void loadFromPack(); void loadFromPixels(); void loadFromStream(); - - void notifyLoaded(); }; }} // namespace EE::Graphics diff --git a/include/eepp/graphics/textureregion.hpp b/include/eepp/graphics/textureregion.hpp index eabdde324..177a7e892 100644 --- a/include/eepp/graphics/textureregion.hpp +++ b/include/eepp/graphics/textureregion.hpp @@ -13,15 +13,15 @@ class EE_API TextureRegion : public DrawableResource { public: static TextureRegion* New(); - static TextureRegion* New( const Uint32& TexId, const std::string& name = "" ); + static TextureRegion* New( ResourceId textureId, const std::string& name = "" ); - static TextureRegion* New( const Uint32& TexId, const Rect& srcRect, + static TextureRegion* New( ResourceId textureId, const Rect& srcRect, const std::string& name = "" ); - static TextureRegion* New( const Uint32& TexId, const Rect& srcRect, const Sizef& destSize, + static TextureRegion* New( ResourceId textureId, const Rect& srcRect, const Sizef& destSize, const std::string& name = "" ); - static TextureRegion* New( const Uint32& TexId, const Rect& srcRect, const Sizef& destSize, + static TextureRegion* New( ResourceId textureId, const Rect& srcRect, const Sizef& destSize, const Vector2i& offset, const std::string& name = "" ); static TextureRegion* New( Texture* tex, const std::string& name = "" ); @@ -73,7 +73,7 @@ class EE_API TextureRegion : public DrawableResource { virtual ~TextureRegion(); /** Set the Texture Id that holds the TextureRegion. */ - void setTextureId( const Uint32& TexId ); + void setTextureId( ResourceId textureId ); /** Set the Texture that holds the TextureRegion. */ void setTexture( Texture* texture ); diff --git a/src/eepp/graphics/framebuffer.cpp b/src/eepp/graphics/framebuffer.cpp index 8631539f0..369f3e61d 100644 --- a/src/eepp/graphics/framebuffer.cpp +++ b/src/eepp/graphics/framebuffer.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include using namespace EE::Graphics::Private; @@ -41,7 +42,8 @@ FrameBuffer::FrameBuffer( EE::Window::Window* window ) : } FrameBuffer::~FrameBuffer() { - eeSAFE_DELETE( mTexture ); + if ( mTexture && TextureFactory::existsSingleton() ) + TextureFactory::instance()->remove( mTexture ); FrameBufferManager::instance()->remove( this ); } diff --git a/src/eepp/graphics/ninepatch.cpp b/src/eepp/graphics/ninepatch.cpp index 8cf1dae50..e0136e7a7 100644 --- a/src/eepp/graphics/ninepatch.cpp +++ b/src/eepp/graphics/ninepatch.cpp @@ -4,10 +4,10 @@ namespace EE { namespace Graphics { -NinePatch* NinePatch::New( const Uint32& TexId, int left, int top, int right, int bottom, +NinePatch* NinePatch::New( ResourceId textureId, int left, int top, int right, int bottom, const Float& pixelDensity, const std::string& name ) { - return eeNew( NinePatch, ( TextureFactory::instance()->getTexture( TexId ), left, top, right, - bottom, pixelDensity, name ) ); + return eeNew( NinePatch, ( TextureFactory::instance()->getTexture( textureId ), left, top, + right, bottom, pixelDensity, name ) ); } NinePatch* NinePatch::New( Texture* tex, int left, int top, int right, int bottom, diff --git a/src/eepp/graphics/particlesystem.cpp b/src/eepp/graphics/particlesystem.cpp index 4dcd6a79d..c85da4698 100644 --- a/src/eepp/graphics/particlesystem.cpp +++ b/src/eepp/graphics/particlesystem.cpp @@ -39,7 +39,7 @@ ParticleSystem::~ParticleSystem() { } void ParticleSystem::create( const ParticleEffect& Effect, const Uint32& NumParticles, - const Uint32& TexId, const Vector2f& Pos, const Float& PartSize, + ResourceId textureId, const Vector2f& Pos, const Float& PartSize, const bool& AnimLoop, const Uint32& NumLoops, const ColorAf& Color, const Vector2f& Pos2, const Float& AlphaDecay, const Vector2f& Speed, const Vector2f& Acc ) { @@ -47,7 +47,7 @@ void ParticleSystem::create( const ParticleEffect& Effect, const Uint32& NumPart mEffect = Effect; mPos = Pos; mPCount = NumParticles; - mTexture = TextureFactory::instance()->getTexture( TexId ); + mTexture = TextureFactory::instance()->getTexture( textureId ); mLoop = AnimLoop; mLoops = NumLoops; mColor = Color; diff --git a/src/eepp/graphics/sprite.cpp b/src/eepp/graphics/sprite.cpp index 247b04e77..a073343fd 100644 --- a/src/eepp/graphics/sprite.cpp +++ b/src/eepp/graphics/sprite.cpp @@ -23,9 +23,9 @@ Sprite* Sprite::New( TextureRegion* TextureRegion ) { return eeNew( Sprite, ( TextureRegion ) ); } -Sprite* Sprite::New( const Uint32& TexId, const Sizef& DestSize, const Vector2i& offset, +Sprite* Sprite::New( ResourceId textureId, const Sizef& DestSize, const Vector2i& offset, const Rect& TexSector ) { - return eeNew( Sprite, ( TexId, DestSize, offset, TexSector ) ); + return eeNew( Sprite, ( textureId, DestSize, offset, TexSector ) ); } Sprite* Sprite::fromGif( IOStream& stream ) { @@ -53,10 +53,10 @@ Sprite::Sprite( TextureRegion* TextureRegion ) : Drawable( Drawable::SPRITE ) { createStatic( TextureRegion ); } -Sprite::Sprite( const Uint32& TexId, const Sizef& DestSize, const Vector2i& Offset, +Sprite::Sprite( ResourceId textureId, const Sizef& DestSize, const Vector2i& Offset, const Rect& TexSector ) : Drawable( Drawable::SPRITE ) { - createStatic( TexId, DestSize, Offset, TexSector ); + createStatic( textureId, DestSize, Offset, TexSector ); } Sprite::~Sprite() { @@ -150,7 +150,7 @@ void Sprite::cleanUpResources() { if ( isTextureOwner() && texture && TextureFactory::instance()->exists( texture ) ) { - eeSAFE_DELETE( texture ); + TextureFactory::instance()->remove( texture ); } if ( isTextureRegionOwner() ) @@ -325,12 +325,12 @@ bool Sprite::createStatic( TextureRegion* TextureRegion ) { return true; } -bool Sprite::createStatic( const Uint32& TexId, const Sizef& DestSize, const Vector2i& offset, +bool Sprite::createStatic( ResourceId textureId, const Sizef& DestSize, const Vector2i& offset, const Rect& TexSector ) { - if ( TextureFactory::instance()->existsId( TexId ) ) { + if ( TextureFactory::instance()->existsId( textureId ) ) { reset(); - addFrame( TexId, DestSize, offset, TexSector ); + addFrame( textureId, DestSize, offset, TexSector ); return true; } @@ -443,11 +443,11 @@ unsigned int Sprite::addFrame( TextureRegion* TextureRegion ) { return id; } -unsigned int Sprite::addFrame( const Uint32& TexId, const Sizef& DestSize, const Vector2i& Offset, +unsigned int Sprite::addFrame( ResourceId textureId, const Sizef& DestSize, const Vector2i& Offset, const Rect& TexSector ) { unsigned int id = framePos(); - if ( addSubFrame( TexId, id, mCurrentSubFrame, DestSize, Offset, TexSector ) ) + if ( addSubFrame( textureId, id, mCurrentSubFrame, DestSize, Offset, TexSector ) ) return id; return 0; @@ -472,16 +472,16 @@ bool Sprite::addSubFrame( Texture* tex, const unsigned int& NumFrame, return false; } -bool Sprite::addSubFrame( const Uint32& TexId, const unsigned int& NumFrame, +bool Sprite::addSubFrame( ResourceId textureId, const unsigned int& NumFrame, const unsigned int& NumSubFrame, const Sizef& DestSize, const Vector2i& Offset, const Rect& TexSector ) { - if ( !TextureFactory::instance()->existsId( TexId ) ) + if ( !TextureFactory::instance()->existsId( textureId ) ) return false; - Texture* Tex = TextureFactory::instance()->getTexture( TexId ); + Texture* Tex = TextureFactory::instance()->getTexture( textureId ); TextureRegion* S = GlobalTextureAtlas::instance()->add( TextureRegion::New() ); - S->setTextureId( TexId ); + S->setTextureId( textureId ); if ( TexSector.Right > 0 && TexSector.Bottom > 0 ) S->setSrcRect( TexSector ); diff --git a/src/eepp/graphics/texture.cpp b/src/eepp/graphics/texture.cpp index 9cb5d3e61..412dee7ca 100644 --- a/src/eepp/graphics/texture.cpp +++ b/src/eepp/graphics/texture.cpp @@ -33,6 +33,7 @@ Texture::Texture() : DrawableResource( Drawable::TEXTURE ), Image(), mFilepath( "" ), + mTextureId(), mTexture( 0 ), mImgWidth( 0 ), mImgHeight( 0 ), @@ -53,10 +54,6 @@ Texture::Texture( const Uint32& texture, const unsigned int& width, const unsign Texture::~Texture() { deleteTexture(); - - if ( !TextureFactory::instance()->isErasing() ) { - TextureFactory::instance()->removeReference( this ); - } } void Texture::deleteTexture() { @@ -94,6 +91,7 @@ void Texture::create( const Uint32& texture, const unsigned int& width, const un mImgWidth = imgwidth; mImgHeight = imgheight; mSize = MemSize; + updateResourceMemorySize(); mClampMode = ClampMode; mFilter = Filter::Linear; @@ -214,7 +212,7 @@ bool Texture::unlock( const bool& KeepData, const bool& Modified ) { #ifndef EE_GLES if ( ( mFlags & TEX_FLAG_LOCKED ) ) { Int32 width = mWidth, height = mHeight; - unsigned int NTexId = 0; + unsigned int textureHandle = 0; if ( Modified || ( mFlags & TEX_FLAG_MODIFIED ) ) { ScopedTexture saver( mTexture ); @@ -223,8 +221,8 @@ bool Texture::unlock( const bool& KeepData, const bool& Modified ) { flags = ( mClampMode == ClampMode::ClampRepeat ) ? ( flags | SOIL_FLAG_TEXTURE_REPEATS ) : flags; - NTexId = SOIL_create_OGL_texture( reinterpret_cast( &mPixels[0] ), &width, - &height, mChannels, mTexture, flags ); + textureHandle = SOIL_create_OGL_texture( reinterpret_cast( &mPixels[0] ), + &width, &height, mChannels, mTexture, flags ); iTextureFilter( mFilter ); @@ -237,9 +235,11 @@ bool Texture::unlock( const bool& KeepData, const bool& Modified ) { if ( !KeepData ) clearCache(); + updateResourceMemorySize(); + mFlags &= ~TEX_FLAG_LOCKED; - if ( (int)NTexId == mTexture || !Modified ) + if ( (int)textureHandle == mTexture || !Modified ) return true; } @@ -425,12 +425,24 @@ void Texture::applyClampMode() { } } -void Texture::setTextureId( const Uint32& id ) { - mTexId = id; +ResourceId Texture::getTextureId() const { + return mTextureId; } -const Uint32& Texture::getTextureId() const { - return mTexId; +const std::shared_ptr& Texture::getResourceMetrics() const { + return mResourceMetrics; +} + +void Texture::setResourceData( ResourceId resourceId, + std::shared_ptr resourceMetrics ) { + eeASSERT( !mTextureId ); + mTextureId = resourceId; + mResourceMetrics = std::move( resourceMetrics ); +} + +void Texture::updateResourceMemorySize() { + if ( mResourceMetrics ) + mResourceMetrics->setMemoryBytes( mSize ); } void Texture::reload() { @@ -463,7 +475,6 @@ void Texture::reload() { mTexture = SOIL_create_OGL_texture( reinterpret_cast( &mPixels[0] ), &width, &height, mChannels, mTexture, flags ); - Uint32 oldSize = mSize; mSize = mWidth * mHeight * mChannels; if ( getMipmap() ) { @@ -477,7 +488,7 @@ void Texture::reload() { } } - TextureFactory::instance()->updateMemorySize( oldSize, mSize ); + updateResourceMemorySize(); } iTextureFilter( mFilter ); @@ -574,9 +585,8 @@ void Texture::replace( Image* image ) { mHeight = mImgHeight = height; mChannels = image->getChannels(); - Uint32 oldSize = mSize; mSize = mWidth * mHeight * mChannels; - TextureFactory::instance()->updateMemorySize( oldSize, mSize ); + updateResourceMemorySize(); if ( hasLocalCopy() ) { // Renew the local copy diff --git a/src/eepp/graphics/textureatlas.cpp b/src/eepp/graphics/textureatlas.cpp index 7f4e04b2c..09d4355e6 100644 --- a/src/eepp/graphics/textureatlas.cpp +++ b/src/eepp/graphics/textureatlas.cpp @@ -37,23 +37,23 @@ TextureRegion* TextureAtlas::add( TextureRegion* textureRegion ) { return ResourceManager::add( textureRegion ); } -TextureRegion* TextureAtlas::add( const Uint32& TexId, const std::string& Name ) { - return add( TextureRegion::New( TexId, Name ) ); +TextureRegion* TextureAtlas::add( ResourceId textureId, const std::string& Name ) { + return add( TextureRegion::New( textureId, Name ) ); } -TextureRegion* TextureAtlas::add( const Uint32& TexId, const Rect& SrcRect, +TextureRegion* TextureAtlas::add( ResourceId textureId, const Rect& SrcRect, const std::string& Name ) { - return add( TextureRegion::New( TexId, SrcRect, Name ) ); + return add( TextureRegion::New( textureId, SrcRect, Name ) ); } -TextureRegion* TextureAtlas::add( const Uint32& TexId, const Rect& SrcRect, const Sizef& DestSize, +TextureRegion* TextureAtlas::add( ResourceId textureId, const Rect& SrcRect, const Sizef& DestSize, const std::string& Name ) { - return add( TextureRegion::New( TexId, SrcRect, DestSize, Name ) ); + return add( TextureRegion::New( textureId, SrcRect, DestSize, Name ) ); } -TextureRegion* TextureAtlas::add( const Uint32& TexId, const Rect& SrcRect, const Sizef& DestSize, +TextureRegion* TextureAtlas::add( ResourceId textureId, const Rect& SrcRect, const Sizef& DestSize, const Vector2i& Offset, const std::string& Name ) { - return add( TextureRegion::New( TexId, SrcRect, DestSize, Offset, Name ) ); + return add( TextureRegion::New( textureId, SrcRect, DestSize, Offset, Name ) ); } TextureRegion* TextureAtlas::add( Texture* tex, const std::string& Name ) { diff --git a/src/eepp/graphics/texturefactory.cpp b/src/eepp/graphics/texturefactory.cpp index d566a25ca..3d267c4fd 100644 --- a/src/eepp/graphics/texturefactory.cpp +++ b/src/eepp/graphics/texturefactory.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -9,18 +10,22 @@ #include #include #include +#include #include namespace EE { namespace Graphics { SINGLETON_DECLARE_IMPLEMENTATION( TextureFactory ) +static std::atomic sResourceIdSequence{ 0 }; + +static ResourceId nextResourceId() { + return ResourceId( sResourceIdSequence.fetch_add( 1, std::memory_order_relaxed ) + 1 ); +} + TextureFactory::TextureFactory() : mCurrentTexture( EE_MAX_TEXTURE_UNITS ), - mMemSize( 0 ), - mTextureIdSeq( 0 ), - mLastCoordinateType( Texture::CoordinateType::Normalized ), - mErasing( false ) {} + mLastCoordinateType( Texture::CoordinateType::Normalized ) {} const Texture::CoordinateType& TextureFactory::getLastCoordinateType() const { return mLastCoordinateType; @@ -28,6 +33,28 @@ const Texture::CoordinateType& TextureFactory::getLastCoordinateType() const { TextureFactory::~TextureFactory() { unloadTextures(); + collectReleasedTextures(); + diagnoseLiveTexturesAtShutdown(); +} + +void TextureFactory::TextureDeleter::operator()( Texture* texture ) const noexcept { + if ( !texture ) + return; + + if ( TextureFactory* factory = TextureFactory::existsSingleton() ) { + factory->queueReleasedTexture( texture ); + return; + } + + // Engine shutdown defensively releases the GPU payload of contract-violating survivors. Such + // late handles can still release their CPU object safely without recreating any singleton. + if ( texture->getHandle() == 0 ) { + eeDelete( texture ); + return; + } + + eePRINTL( "Texture released after TextureFactory destruction with a live OpenGL handle" ); + eeASSERTM( false, Texture_released_after_TextureFactory_destruction ); } Texture* TextureFactory::createEmptyTexture( const unsigned int& Width, const unsigned int& Height, @@ -96,7 +123,7 @@ TextureFactory::loadFromFile( const std::string& Filepath, const bool& Mipmap, return myTex.getTexture(); } -Texture* TextureFactory::pushTexture( const std::string& Filepath, const Uint32& TexId, +Texture* TextureFactory::pushTexture( const std::string& Filepath, const Uint32& textureHandle, const unsigned int& Width, const unsigned int& Height, const unsigned int& ImgWidth, const unsigned int& ImgHeight, const bool& Mipmap, const unsigned int& Channels, @@ -105,27 +132,29 @@ Texture* TextureFactory::pushTexture( const std::string& Filepath, const Uint32& const Uint32& MemSize ) { Lock l( *this ); - Texture* Tex = NULL; - Uint32 Pos; - std::string FPath( Filepath ); FileSystem::filePathRemoveProcessPath( FPath ); - Pos = ++mTextureIdSeq; - Tex = mTextures[Pos] = eeNew( Texture, () ); + const ResourceId resourceId = nextResourceId(); + auto metrics = std::make_shared(); + TexturePtr texture( eeNew( Texture, () ), TextureDeleter() ); + Texture* Tex = texture.get(); + Tex->setResourceData( resourceId, metrics ); - Tex->create( TexId, Width, Height, ImgWidth, ImgHeight, Mipmap, Channels, FPath, ClampMode, - CompressTexture, MemSize ); - Tex->setTextureId( Pos ); + Tex->create( textureHandle, Width, Height, ImgWidth, ImgHeight, Mipmap, Channels, FPath, + ClampMode, CompressTexture, MemSize ); + TextureWeakPtr weakTexture( texture ); + mTextures.emplace( resourceId.value(), std::move( texture ) ); + mLiveTextures.emplace( resourceId.value(), + LiveTextureRecord{ resourceId, std::move( weakTexture ), metrics } ); + mLiveTextureGeneration.fetch_add( 1, std::memory_order_release ); if ( LocalCopy ) { Tex->lock(); Tex->unlock( true, false ); } - mMemSize += MemSize; - return Tex; } @@ -172,103 +201,81 @@ void TextureFactory::bind( const Texture* texture, Texture::CoordinateType coord } } -void TextureFactory::bind( const Uint32& TexId, Texture::CoordinateType coordinateType, +void TextureFactory::bind( ResourceId textureId, Texture::CoordinateType coordinateType, const Uint32& textureUnit, const bool& forceRebind ) { - bind( getTexture( TexId ), coordinateType, textureUnit, forceRebind ); + bind( getTexture( textureId ), coordinateType, textureUnit, forceRebind ); } void TextureFactory::unloadTextures() { - Lock l( *this ); + TextureMap textures; + { + Lock l( *this ); + textures = std::move( mTextures ); + std::fill( mCurrentTexture.begin(), mCurrentTexture.end(), 0 ); + } - mErasing = true; - - for ( auto& texture : mTextures ) - eeSAFE_DELETE( texture.second ); - - mErasing = false; - - mTextures.clear(); - mTextureIdSeq = 0; + // DrawableResource destruction emits callbacks, so release factory ownership without holding + // the registry/factory mutex. + textures.clear(); Log::debug( "Textures Unloaded." ); } -bool TextureFactory::remove( Uint32 TexId ) { - Lock l( *this ); +bool TextureFactory::remove( ResourceId textureId ) { + TexturePtr texture; + { + Lock l( *this ); + auto it = mTextures.find( textureId.value() ); + if ( it == mTextures.end() ) + return false; - Texture* Tex; - auto it = mTextures.find( TexId ); - - if ( it != mTextures.end() && NULL != ( Tex = it->second ) ) { - removeReference( Tex ); - - mErasing = true; - eeDelete( Tex ); - mErasing = false; - - return true; + texture = std::move( it->second ); + mTextures.erase( it ); + resetTextureBinding( texture.get() ); } - return false; + texture.reset(); + return true; } bool TextureFactory::remove( Texture* texture ) { - Lock l( *this ); + TexturePtr removed; + { + Lock l( *this ); + auto it = std::find_if( mTextures.begin(), mTextures.end(), [texture]( const auto& pair ) { + return pair.second.get() == texture; + } ); + if ( it == mTextures.end() ) + return false; - auto it = std::find_if( mTextures.begin(), mTextures.end(), - [texture]( const auto& pair ) { return pair.second == texture; } ); - if ( it != mTextures.end() ) { - removeReference( texture ); - - mErasing = true; - eeDelete( texture ); - mErasing = false; - - return true; + removed = std::move( it->second ); + mTextures.erase( it ); + resetTextureBinding( removed.get() ); } - return false; + + removed.reset(); + return true; } -void TextureFactory::removeReference( Texture* Tex ) { - Lock l( *this ); - - auto it = mTextures.find( Tex->getTextureId() ); - if ( it == mTextures.end() || it->second != Tex ) +void TextureFactory::resetTextureBinding( const Texture* texture ) { + if ( !texture ) return; - mMemSize -= Tex->getMemSize(); - - int glTexId = Tex->getHandle(); - - mTextures.erase( it ); - + const int glTexId = texture->getHandle(); for ( Uint32 i = 0; i < EE_MAX_TEXTURE_UNITS; i++ ) { if ( mCurrentTexture[i] == (Int32)glTexId ) mCurrentTexture[i] = 0; } } -void TextureFactory::updateMemorySize( Uint32 oldSize, Uint32 newSize ) { - Lock l( *this ); - - mMemSize -= oldSize; - mMemSize += newSize; -} - -bool TextureFactory::isErasing() { - Lock l( *this ); - - return mErasing; -} - int TextureFactory::getCurrentTexture( const Uint32& TextureUnit ) const { eeASSERT( TextureUnit < EE_MAX_TEXTURE_UNITS ); return mCurrentTexture[TextureUnit]; } -void TextureFactory::setCurrentTexture( const int& TexId, const Uint32& TextureUnit ) { +void TextureFactory::setCurrentTexture( const int& textureHandle, const Uint32& TextureUnit ) { eeASSERT( TextureUnit < EE_MAX_TEXTURE_UNITS ); - mCurrentTexture[TextureUnit] = TexId; + mCurrentTexture[TextureUnit] = textureHandle; } std::vector TextureFactory::getTextures() { @@ -278,7 +285,7 @@ std::vector TextureFactory::getTextures() { textures.reserve( mTextures.size() ); for ( const auto& texture : mTextures ) { - Texture* Tex = texture.second; + Texture* Tex = texture.second.get(); if ( Tex ) textures.push_back( Tex ); @@ -287,44 +294,117 @@ std::vector TextureFactory::getTextures() { return textures; } -void TextureFactory::reloadAllTextures() { - Lock l( *this ); +TextureRegistrySnapshot TextureFactory::snapshotTextures() { + struct LockedTextureRecord { + LiveTextureRecord record; + TexturePtr texture; + }; - for ( const auto& texture : mTextures ) { - Texture* Tex = texture.second; + std::vector liveTextures; + { + Lock l( *this ); + liveTextures.reserve( mLiveTextures.size() ); + for ( auto it = mLiveTextures.begin(); it != mLiveTextures.end(); ) { + TexturePtr texture = it->second.texture.lock(); + if ( !texture ) { + it = mLiveTextures.erase( it ); + continue; + } - if ( Tex ) - Tex->reload(); + liveTextures.push_back( { it->second, std::move( texture ) } ); + ++it; + } } - Log::debug( "Textures Reloaded." ); + TextureRegistrySnapshot snapshot; + snapshot.reserve( liveTextures.size() ); + for ( const auto& live : liveTextures ) { + snapshot.push_back( + { live.record.id, live.texture->getName(), live.record.texture, live.record.metrics } ); + } + return snapshot; } -void TextureFactory::grabTextures() { +void TextureFactory::purgeExpiredTextures() { Lock l( *this ); - - for ( const auto& texture : mTextures ) { - Texture* Tex = texture.second; - - if ( Tex && !Tex->hasLocalCopy() ) { - Tex->lock(); - Tex->setGrabbed( true ); - } + for ( auto it = mLiveTextures.begin(); it != mLiveTextures.end(); ) { + if ( it->second.texture.expired() ) + it = mLiveTextures.erase( it ); + else + ++it; } } -void TextureFactory::ungrabTextures() { +Uint64 TextureFactory::getLiveTextureGeneration() const { + return mLiveTextureGeneration.load( std::memory_order_acquire ); +} + +void TextureFactory::queueReleasedTexture( Texture* texture ) { + eeASSERTM( Window::Engine::existsSingleton() && Window::Engine::isMainThread(), + Texture_final_release_must_run_on_the_graphics_thread ); + Lock l( *this ); + mReleasedTextures.push_back( texture ); + mLiveTextureGeneration.fetch_add( 1, std::memory_order_release ); +} - for ( const auto& texture : mTextures ) { - Texture* Tex = texture.second; +void TextureFactory::collectReleasedTextures() { + eeASSERTM( Window::Engine::existsSingleton() && Window::Engine::isMainThread(), + Texture_collection_must_run_on_the_graphics_thread ); - if ( NULL != Tex && Tex->isGrabbed() ) { - Tex->reload(); - Tex->unlock(); - Tex->setGrabbed( false ); + std::vector releasedTextures; + for ( ;; ) { + { + Lock l( *this ); + if ( mReleasedTextures.empty() ) { + if ( releasedTextures.capacity() > mReleasedTextures.capacity() ) + mReleasedTextures.swap( releasedTextures ); + break; + } + releasedTextures.swap( mReleasedTextures ); + for ( Texture* texture : releasedTextures ) + resetTextureBinding( texture ); + } + + for ( Texture* texture : releasedTextures ) + eeDelete( texture ); + + releasedTextures.clear(); + } + + purgeExpiredTextures(); +} + +std::size_t TextureFactory::getPendingReleaseCount() { + Lock l( *this ); + return mReleasedTextures.size(); +} + +void TextureFactory::diagnoseLiveTexturesAtShutdown() { + std::vector survivors; + { + Lock l( *this ); + for ( const auto& record : mLiveTextures ) { + if ( TexturePtr texture = record.second.texture.lock() ) + survivors.emplace_back( std::move( texture ) ); } } + + if ( survivors.empty() ) + return; + + Log::error( "TextureFactory shutdown found %zu externally retained texture(s).", + survivors.size() ); + for ( const TexturePtr& texture : survivors ) { + Log::error( "Texture %llu ('%s') survived shutdown with %zu external owner(s).", + static_cast( texture->getTextureId().value() ), + texture->getName().c_str(), texture.use_count() - 1 ); + texture->deleteTexture(); + } + + eeASSERTM( false, Texture_handles_must_be_released_before_Engine_shutdown ); + survivors.clear(); + collectReleasedTextures(); } void TextureFactory::setActiveTextureUnit( const Uint32& Unit ) { @@ -338,25 +418,25 @@ unsigned int TextureFactory::getValidTextureSize( const unsigned int& Size ) { return Math::nextPowOfTwo( Size ); } -bool TextureFactory::existsId( const Uint32& TexId ) { +bool TextureFactory::existsId( ResourceId textureId ) { Lock l( *this ); - return mTextures.find( TexId ) != mTextures.end(); + return mTextures.find( textureId.value() ) != mTextures.end(); } bool TextureFactory::exists( const Texture* tex ) { Lock l( *this ); return std::find_if( mTextures.begin(), mTextures.end(), [tex]( const auto& pair ) { - return pair.second == tex; + return pair.second.get() == tex; } ) != mTextures.end(); } -Texture* TextureFactory::getTexture( const Uint32& TexId ) { +Texture* TextureFactory::getTexture( ResourceId textureId ) { Lock l( *this ); - auto it = mTextures.find( TexId ); - return it != mTextures.end() ? it->second : NULL; + auto it = mTextures.find( textureId.value() ); + return it != mTextures.end() ? it->second.get() : NULL; } Texture* TextureFactory::getByName( const std::string& Name ) { @@ -372,16 +452,21 @@ Uint32 TextureFactory::getTextureCount() { unsigned int TextureFactory::getTextureMemorySize() { Lock l( *this ); - return mMemSize; + std::size_t memorySize = 0; + for ( const auto& texture : mLiveTextures ) { + if ( !texture.second.texture.expired() ) + memorySize += texture.second.metrics->getMemoryBytes(); + } + return static_cast( memorySize ); } Texture* TextureFactory::getByHash( const String::HashType& hash ) { Lock l( *this ); - Uint32 latestId = 0; + Uint64 latestId = 0; Texture* latestTexture = NULL; for ( const auto& texture : mTextures ) { - Texture* tTex = texture.second; + Texture* tTex = texture.second.get(); if ( NULL != tTex && texture.first > latestId && tTex->getHashName() == hash ) { latestId = texture.first; diff --git a/src/eepp/graphics/textureloader.cpp b/src/eepp/graphics/textureloader.cpp index cb79091ed..c31615108 100644 --- a/src/eepp/graphics/textureloader.cpp +++ b/src/eepp/graphics/textureloader.cpp @@ -23,19 +23,6 @@ using namespace EE::Graphics::Private; namespace EE { namespace Graphics { -UnorderedMap TextureLoader::sCbs = {}; -std::atomic TextureLoader::sNumCbs = 0; - -Uint32 TextureLoader::pushLoadedCallback( const OnTextureLoaded& cb ) { - Uint32 newCb = ++sNumCbs; - sCbs.insert( { newCb, cb } ); - return newCb; -} - -void TextureLoader::popLoadedCallback( const Uint32& cbId ) { - sCbs.erase( cbId ); -} - TextureLoader::TextureLoader( IOStream& Stream, const bool& Mipmap, const Texture::ClampMode& ClampMode, const bool& CompressTexture, const bool& KeepLocalCopy ) : @@ -279,14 +266,9 @@ void TextureLoader::loadFromStream() { } } -void TextureLoader::notifyLoaded() { - for ( const auto& cb : sCbs ) - cb.second( cb.first, mTexture ); -} - void TextureLoader::loadFromPixels() { if ( !mLoaded && mTexLoaded && Engine::isEngineRunning() ) { - Uint32 tTexId = 0; + Uint32 textureHandle = 0; if ( NULL != mPixels ) { int width = mImgWidth; @@ -330,14 +312,14 @@ void TextureLoader::loadFromPixels() { if ( mDirectUpload ) { if ( Image::Format::DDS == mImgType ) { - tTexId = SOIL_direct_load_DDS_from_memory( + textureHandle = SOIL_direct_load_DDS_from_memory( mPixels, mSize, SOIL_CREATE_NEW_ID, flags, 0 ); } else if ( Image::Format::PVR == mImgType ) { - tTexId = SOIL_direct_load_PVR_from_memory( + textureHandle = SOIL_direct_load_PVR_from_memory( mPixels, mSize, SOIL_CREATE_NEW_ID, flags, 0 ); } else if ( Image::Format::PKM == mImgType ) { - tTexId = SOIL_direct_load_PKM_from_memory( mPixels, mSize, - SOIL_CREATE_NEW_ID, flags ); + textureHandle = SOIL_direct_load_PKM_from_memory( + mPixels, mSize, SOIL_CREATE_NEW_ID, flags ); } } else { if ( NULL != mColorKey ) { @@ -353,8 +335,8 @@ void TextureLoader::loadFromPixels() { eeSAFE_DELETE( tImg ); } - tTexId = SOIL_create_OGL_texture( mPixels, &width, &height, mChannels, - SOIL_CREATE_NEW_ID, flags ); + textureHandle = SOIL_create_OGL_texture( + mPixels, &width, &height, mChannels, SOIL_CREATE_NEW_ID, flags ); } } @@ -364,7 +346,7 @@ void TextureLoader::loadFromPixels() { } } - if ( tTexId ) { + if ( textureHandle ) { mWidth = width; mHeight = height; @@ -391,21 +373,21 @@ void TextureLoader::loadFromPixels() { } mTexture = TextureFactory::instance()->pushTexture( - mFilepath, tTexId, width, height, mImgWidth, mImgHeight, mMipmap, mChannels, - mClampMode, mCompressTexture || mIsCompressed, mLocalCopy, mSize ); + mFilepath, textureHandle, width, height, mImgWidth, mImgHeight, mMipmap, + mChannels, mClampMode, mCompressTexture || mIsCompressed, mLocalCopy, mSize ); if ( mFilepath.empty() ) { Log::instance()->writef( mTE.getElapsedTimeAndReset() >= Milliseconds( 1 ) ? LogLevel::Info : LogLevel::Debug, - "Texture ID %d loaded in %s.", mTexture->getTextureId(), + "Texture ID %llu loaded in %s.", + static_cast( mTexture->getTextureId().value() ), mTE.getElapsedTimeAndReset().toString() ); } else { Log::info( "Texture %s loaded in %4.3f ms.", mFilepath.c_str(), mTE.getElapsedTimeAndReset().asMilliseconds() ); } - notifyLoaded(); } else { Log::warning( "Failed to create texture. Reason: %s", SOIL_last_result() ); } @@ -437,8 +419,8 @@ void TextureLoader::loadFromPixels() { } } -Uint32 TextureLoader::getId() const { - return mTexture != nullptr ? mTexture->getTextureId() : 0; +ResourceId TextureLoader::getId() const { + return mTexture != nullptr ? mTexture->getTextureId() : ResourceId{}; } void TextureLoader::setColorKey( RGB Color ) { diff --git a/src/eepp/graphics/textureregion.cpp b/src/eepp/graphics/textureregion.cpp index 14a83cf07..4137fb8b5 100644 --- a/src/eepp/graphics/textureregion.cpp +++ b/src/eepp/graphics/textureregion.cpp @@ -14,25 +14,25 @@ TextureRegion* TextureRegion::New() { return eeNew( TextureRegion, () ); } -TextureRegion* TextureRegion::New( const Uint32& TexId, const std::string& name ) { - return eeNew( TextureRegion, ( TextureFactory::instance()->getTexture( TexId ), name ) ); +TextureRegion* TextureRegion::New( ResourceId textureId, const std::string& name ) { + return eeNew( TextureRegion, ( TextureFactory::instance()->getTexture( textureId ), name ) ); } -TextureRegion* TextureRegion::New( const Uint32& TexId, const Rect& srcRect, +TextureRegion* TextureRegion::New( ResourceId textureId, const Rect& srcRect, const std::string& name ) { return eeNew( TextureRegion, - ( TextureFactory::instance()->getTexture( TexId ), srcRect, name ) ); + ( TextureFactory::instance()->getTexture( textureId ), srcRect, name ) ); } -TextureRegion* TextureRegion::New( const Uint32& TexId, const Rect& srcRect, const Sizef& destSize, +TextureRegion* TextureRegion::New( ResourceId textureId, const Rect& srcRect, const Sizef& destSize, const std::string& name ) { - return eeNew( TextureRegion, - ( TextureFactory::instance()->getTexture( TexId ), srcRect, destSize, name ) ); + return eeNew( TextureRegion, ( TextureFactory::instance()->getTexture( textureId ), srcRect, + destSize, name ) ); } -TextureRegion* TextureRegion::New( const Uint32& TexId, const Rect& srcRect, const Sizef& destSize, +TextureRegion* TextureRegion::New( ResourceId textureId, const Rect& srcRect, const Sizef& destSize, const Vector2i& offset, const std::string& name ) { - return eeNew( TextureRegion, ( TextureFactory::instance()->getTexture( TexId ), srcRect, + return eeNew( TextureRegion, ( TextureFactory::instance()->getTexture( textureId ), srcRect, destSize, offset, name ) ); } @@ -117,8 +117,8 @@ TextureRegion::~TextureRegion() { clearCache(); } -void TextureRegion::setTextureId( const Uint32& TexId ) { - mTexture = TextureFactory::instance()->getTexture( TexId ); +void TextureRegion::setTextureId( ResourceId textureId ) { + mTexture = TextureFactory::instance()->getTexture( textureId ); } void TextureRegion::setTexture( Texture* texture ) { diff --git a/src/eepp/ui/tools/uitextureviewer.cpp b/src/eepp/ui/tools/uitextureviewer.cpp index 4d861fbf8..672c684d8 100644 --- a/src/eepp/ui/tools/uitextureviewer.cpp +++ b/src/eepp/ui/tools/uitextureviewer.cpp @@ -1,5 +1,4 @@ #include "uitextureviewer.hpp" -#include #include #include #include @@ -11,26 +10,26 @@ UITextureViewer* UITextureViewer::New() { return eeNew( UITextureViewer, () ); } -UITextureViewer::~UITextureViewer() { - TextureLoader::popLoadedCallback( mLoaderCb ); - for ( const auto& cb : mCbs ) { - if ( !cb.first->popResourceChangeCallback( cb.second ) ) { - eePRINTL( "UITextureViewer::~UITextureViewer popResourceChangeCallback failed" ); - eeASSERT( false ); - } - } -} +UITextureViewer::~UITextureViewer() {} UITextureViewer::UITextureViewer() : UIRelativeLayout( "textureviewer" ) { init(); } -void UITextureViewer::setImage( Drawable* drawable ) { +void UITextureViewer::setImage( TexturePtr texture ) { UIImage* imageView = mImageLayout->findByType( UI_TYPE_IMAGE ); if ( imageView == nullptr ) return; + mSelectedTexture = std::move( texture ); mImageLayout->setEnabled( true )->setVisible( true ); - imageView->setDrawable( drawable ); + imageView->setDrawable( mSelectedTexture.get() ); +} + +void UITextureViewer::clearSelectedTexture() { + if ( UIImage* imageView = mImageLayout->findByType( UI_TYPE_IMAGE ) ) + imageView->setDrawable( nullptr ); + mSelectedTexture.reset(); + mImageLayout->setEnabled( false )->setVisible( false ); } void UITextureViewer::init() { @@ -48,21 +47,15 @@ void UITextureViewer::init() { mGridLayout = findByType( UI_TYPE_GRID_LAYOUT ); mImageLayout = findByClass( "image_container" ); - auto hideImg = [this]( const Event* ) { - mImageLayout->setEnabled( false )->setVisible( false ); - }; + auto hideImg = [this]( const Event* ) { clearSelectedTexture(); }; mImageLayout->on( Event::MouseClick, hideImg ); mImageLayout->on( Event::KeyDown, [hideImg]( const Event* event ) { if ( event->asKeyEvent()->getKeyCode() == KEY_ESCAPE ) hideImg( event ); } ); - std::vector textures = TextureFactory::instance()->getTextures(); - for ( Texture* texture : textures ) - insertTexture( texture ); - - mLoaderCb = TextureLoader::pushLoadedCallback( - [this]( Uint32, Texture* tex ) { insertTexture( tex ); } ); + refreshTextures(); + subscribeScheduledUpdate(); } static std::string getTextureDescription( Texture* tex ) { @@ -70,28 +63,65 @@ static std::string getTextureDescription( Texture* tex ) { tex->getHeight() ); } -void UITextureViewer::insertTexture( Texture* tex ) { +void UITextureViewer::scheduledUpdate( const Time& ) { + TextureFactory* textureFactory = TextureFactory::existsSingleton(); + if ( textureFactory && textureFactory->getLiveTextureGeneration() != mLastRegistryGeneration ) + refreshTextures(); +} + +void UITextureViewer::refreshTextures() { + TextureFactory* textureFactory = TextureFactory::existsSingleton(); + if ( !textureFactory ) + return; + + const Uint64 registryGeneration = textureFactory->getLiveTextureGeneration(); + TextureRegistrySnapshot textures = textureFactory->snapshotTextures(); + for ( auto& texture : mTextures ) + texture.second.seen = false; + + for ( const TextureRegistryRecord& texture : textures ) { + auto found = mTextures.find( texture.id.value() ); + if ( found == mTextures.end() ) { + insertTexture( texture ); + } else { + found->second.seen = true; + found->second.texture = texture.texture; + } + } + + for ( auto texture = mTextures.begin(); texture != mTextures.end(); ) { + if ( !texture->second.seen || texture->second.texture.expired() ) { + if ( texture->second.preview ) + texture->second.preview->close(); + texture = mTextures.erase( texture ); + } else { + ++texture; + } + } + + mLastRegistryGeneration = registryGeneration; +} + +void UITextureViewer::insertTexture( const TextureRegistryRecord& record ) { + TexturePtr texture = record.texture.lock(); + if ( !texture ) + return; + UIImage* img = UIImage::New(); - std::string uid( String::format( "%llu", reinterpret_cast( tex ) ) ); - img->setDrawable( tex ) + std::string uid( + String::format( "texture-%llu", static_cast( record.id.value() ) ) ); + img->setDrawable( texture.get() ) ->setScaleType( UIScaleType::FitInside ) ->setClasses( { "texture-preview", uid } ) - ->setTooltipText( getTextureDescription( tex ) ) + ->setTooltipText( getTextureDescription( texture.get() ) ) ->setGravity( UI_HALIGN_CENTER | UI_VALIGN_CENTER ) ->setEnabled( true ) ->setParent( mGridLayout ) - ->onClick( [this, tex]( auto ) { setImage( tex ); } ); - Uint32 cb = tex->pushResourceChangeCallback( - [this, uid]( Uint32, DrawableResource::Event event, DrawableResource* res ) { - if ( event == DrawableResource::Event::Unload ) { - auto found = mGridLayout->findByClass( uid ); - if ( found && mCbs.erase( static_cast( res ) ) ) - found->close(); - } else if ( event == DrawableResource::Change ) { - getTextureDescription( static_cast( res ) ); - } + ->onClick( [this, weakTexture = record.texture]( auto ) { + if ( TexturePtr selectedTexture = weakTexture.lock() ) + setImage( std::move( selectedTexture ) ); } ); - mCbs.insert( { tex, cb } ); + mTextures.emplace( record.id.value(), TextureEntry{ record.texture, img, true } ); } }}} // namespace EE::UI::Tools diff --git a/src/eepp/ui/tools/uitextureviewer.hpp b/src/eepp/ui/tools/uitextureviewer.hpp index abffb70d9..174721770 100644 --- a/src/eepp/ui/tools/uitextureviewer.hpp +++ b/src/eepp/ui/tools/uitextureviewer.hpp @@ -1,6 +1,7 @@ #ifndef EE_UITEXTUREVIEWER_HPP #define EE_UITEXTUREVIEWER_HPP +#include #include using namespace EE::UI; @@ -17,19 +18,32 @@ class EE_API UITextureViewer : public UIRelativeLayout { virtual ~UITextureViewer(); + virtual void scheduledUpdate( const Time& time ); + protected: + struct TextureEntry { + TextureWeakPtr texture; + UIWidget* preview{ nullptr }; + bool seen{ false }; + }; + UIGridLayout* mGridLayout{ nullptr }; UIRelativeLayout* mImageLayout{ nullptr }; - UnorderedMap mCbs; - Uint32 mLoaderCb{ 0 }; + UnorderedMap mTextures; + TexturePtr mSelectedTexture; + Uint64 mLastRegistryGeneration{ 0 }; UITextureViewer(); void init(); - void insertTexture( Texture* tex ); + void refreshTextures(); - void setImage( Drawable* ); + void insertTexture( const TextureRegistryRecord& record ); + + void setImage( TexturePtr texture ); + + void clearSelectedTexture(); }; } // namespace Tools diff --git a/src/eepp/window/engine.cpp b/src/eepp/window/engine.cpp index 14eb64545..983280783 100644 --- a/src/eepp/window/engine.cpp +++ b/src/eepp/window/engine.cpp @@ -112,6 +112,9 @@ Engine::~Engine() { Graphics::Private::VertexBufferManager::destroySingleton(); + if ( TextureFactory* textureFactory = TextureFactory::existsSingleton() ) + textureFactory->collectReleasedTextures(); + TextureFactory::destroySingleton(); // Shader and renderer destructors issue GL commands. Programs must go first while GLi and the diff --git a/src/eepp/window/window.cpp b/src/eepp/window/window.cpp index 86dfb3bba..cd2067817 100644 --- a/src/eepp/window/window.cpp +++ b/src/eepp/window/window.cpp @@ -461,6 +461,8 @@ void Window::clear() { void Window::display( bool clear ) { GlobalBatchRenderer::instance()->draw(); + if ( TextureFactory* textureFactory = TextureFactory::existsSingleton() ) + textureFactory->collectReleasedTextures(); swapBuffers(); diff --git a/src/tests/test_all/test.cpp b/src/tests/test_all/test.cpp index 79380eebf..49b26cb99 100644 --- a/src/tests/test_all/test.cpp +++ b/src/tests/test_all/test.cpp @@ -1977,9 +1977,6 @@ void EETest::input() { mWindow->maximize(); } - if ( KM->isKeyUp( KEY_F4 ) ) - TF->reloadAllTextures(); - if ( KM->isAltPressed() && KM->isKeyUp( KEY_RETURN ) ) { mWindow->toggleFullscreen(); } diff --git a/src/tests/test_all/test.hpp b/src/tests/test_all/test.hpp index c30a35e80..162626033 100644 --- a/src/tests/test_all/test.hpp +++ b/src/tests/test_all/test.hpp @@ -49,7 +49,7 @@ class EETest : private Thread { Int32 x, y; Uint32 lasttick; - std::vector TN; + std::vector TN; std::vector TNP; std::vector Tiles; @@ -96,7 +96,7 @@ class EETest : private Thread { Interpolation2d WP; Int32 PartsNum; - Uint32 Cursor[1]; + ResourceId Cursor[1]; Texture* CursorP[1]; std::string mInfo; diff --git a/src/tests/unit_tests/resource_prerequisite_tests.cpp b/src/tests/unit_tests/resource_prerequisite_tests.cpp index f7253345d..6de3da3a2 100644 --- a/src/tests/unit_tests/resource_prerequisite_tests.cpp +++ b/src/tests/unit_tests/resource_prerequisite_tests.cpp @@ -1,5 +1,6 @@ #include "utest.hpp" +#include #include #include #include @@ -203,6 +204,99 @@ UTEST( ResourcePrerequisites, textureAtlasLoaderAcceptsFilterBeforeAtlasExists ) Texture::Filter::Nearest ); } +UTEST( ResourcePrerequisites, textureRegistryTracksStableIdentityAndMemoryWithoutOwning ) { + createLifecycleTestWindow( "Texture registry identity test" ); + TextureFactory* factory = TextureFactory::instance(); + Texture* first = factory->createEmptyTexture( 2, 3, 4, Color::Transparent, false, + Texture::ClampMode::ClampToEdge, false, false, + "registry-first" ); + Texture* second = factory->createEmptyTexture( 1, 1, 4, Color::Transparent, false, + Texture::ClampMode::ClampToEdge, false, false, + "registry-second" ); + ASSERT_TRUE( first != nullptr ); + ASSERT_TRUE( second != nullptr ); + + const ResourceId firstId = first->getTextureId(); + const ResourceId secondId = second->getTextureId(); + EXPECT_TRUE( static_cast( firstId ) ); + EXPECT_TRUE( static_cast( secondId ) ); + EXPECT_TRUE( firstId != secondId ); + + first->setName( "registry-first-renamed" ); + TextureRegistrySnapshot snapshot = factory->snapshotTextures(); + auto firstRecord = + std::find_if( snapshot.begin(), snapshot.end(), + [firstId]( const auto& record ) { return record.id == firstId; } ); + ASSERT_TRUE( firstRecord != snapshot.end() ); + EXPECT_TRUE( firstRecord->displayName == "registry-first-renamed" ); + ASSERT_TRUE( firstRecord->metrics != nullptr ); + EXPECT_EQ( firstRecord->metrics->getMemoryBytes(), static_cast( 2 * 3 * 4 ) ); + EXPECT_EQ( factory->getTextureMemorySize(), 2u * 3u * 4u + 4u ); + first->resize( 4, 3 ); + EXPECT_EQ( firstRecord->metrics->getMemoryBytes(), static_cast( 4 * 3 * 4 ) ); + EXPECT_EQ( factory->getTextureMemorySize(), 4u * 3u * 4u + 4u ); + + TextureWeakPtr firstWeak = firstRecord->texture; + TexturePtr retainedFirst = firstWeak.lock(); + ASSERT_TRUE( retainedFirst != nullptr ); + ASSERT_TRUE( factory->remove( first ) ); + EXPECT_FALSE( firstWeak.expired() ); + EXPECT_EQ( factory->getTextureMemorySize(), 4u * 3u * 4u + 4u ); + retainedFirst.reset(); + EXPECT_TRUE( firstWeak.expired() ); + + snapshot = factory->snapshotTextures(); + EXPECT_TRUE( std::none_of( snapshot.begin(), snapshot.end(), + [firstId]( const auto& record ) { return record.id == firstId; } ) ); + EXPECT_EQ( factory->getTextureMemorySize(), 4u ); + + Engine::destroySingleton(); + + createLifecycleTestWindow( "Texture registry identity restart test" ); + Texture* afterRestart = TextureFactory::instance()->createEmptyTexture( 1, 1 ); + ASSERT_TRUE( afterRestart != nullptr ); + EXPECT_TRUE( secondId < afterRestart->getTextureId() ); + Engine::destroySingleton(); +} + +UTEST( ResourcePrerequisites, textureFinalReleaseWaitsForDisplayCollection ) { + EE::Window::Window* window = createLifecycleTestWindow( "Texture deferred release test" ); + TextureFactory* factory = TextureFactory::instance(); + Texture* texture = factory->createEmptyTexture( 2, 2 ); + ASSERT_TRUE( texture != nullptr ); + + bool unloaded = false; + texture->pushResourceChangeCallback( + [&unloaded]( Uint32, DrawableResource::Event event, DrawableResource* ) { + if ( event == DrawableResource::Event::Unload ) + unloaded = true; + } ); + + TextureRegistrySnapshot snapshot = factory->snapshotTextures(); + auto record = std::find_if( snapshot.begin(), snapshot.end(), [texture]( const auto& entry ) { + return entry.id == texture->getTextureId(); + } ); + ASSERT_TRUE( record != snapshot.end() ); + TextureWeakPtr weakTexture = record->texture; + TexturePtr retainedTexture = weakTexture.lock(); + ASSERT_TRUE( retainedTexture != nullptr ); + + ASSERT_TRUE( factory->remove( texture ) ); + GlobalBatchRenderer::instance()->setTexture( texture ); + GlobalBatchRenderer::instance()->batchQuad( 0, 0, 2, 2 ); + retainedTexture.reset(); + + EXPECT_TRUE( weakTexture.expired() ); + EXPECT_FALSE( unloaded ); + EXPECT_EQ( factory->getPendingReleaseCount(), static_cast( 1 ) ); + + window->display( false ); + + EXPECT_TRUE( unloaded ); + EXPECT_EQ( factory->getPendingReleaseCount(), static_cast( 0 ) ); + Engine::destroySingleton(); +} + UTEST( ResourcePrerequisites, unsignedVariantPreservesTypeAndValue ) { const unsigned int value = std::numeric_limits::max(); Variant original( value ); diff --git a/src/tools/uieditor/uieditor.cpp b/src/tools/uieditor/uieditor.cpp index e855cc1bb..e2b039c02 100644 --- a/src/tools/uieditor/uieditor.cpp +++ b/src/tools/uieditor/uieditor.cpp @@ -151,7 +151,7 @@ void App::loadImage( std::string path ) { std::string filename( FileSystem::fileRemoveExtension( FileSystem::fileNameFromPath( path ) ) ); Texture* tex = TextureFactory::instance()->loadFromFile( path ); if ( tex ) { - Uint32 texId = tex->getTextureId(); + ResourceId texId = tex->getTextureId(); TextureRegion* texRegion = GlobalTextureAtlas::instance()->add( texId, filename ); mImagesLoaded[texId] = texRegion; } diff --git a/src/tools/uieditor/uieditor.hpp b/src/tools/uieditor/uieditor.hpp index 960d50a2d..cd8b289b9 100644 --- a/src/tools/uieditor/uieditor.hpp +++ b/src/tools/uieditor/uieditor.hpp @@ -194,7 +194,7 @@ class App : public UICodeEditorSplitter::Client { IniFile mIni; Uint32 mRecentProjectEventClickId{ 0xFFFFFFFF }; Uint32 mRecentFilesEventClickId{ 0xFFFFFFFF }; - std::map mImagesLoaded; + std::map mImagesLoaded; std::map mFontsLoaded; UpdateListener* mListener{ nullptr }; std::string mConfigPath;