From 8c8c2591fe3780dea39d3d08e1ef193e10bc7286 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 6 Mar 2026 08:44:44 +0000 Subject: [PATCH 01/11] ref base use smart pointers --- Engine/source/T3D/assets/SoundAsset.cpp | 36 +-- Engine/source/T3D/assets/SoundAsset.h | 12 +- Engine/source/console/simObject.h | 71 ++--- Engine/source/core/util/refBase.cpp | 18 ++ Engine/source/core/util/refBase.h | 397 +++++++++--------------- 5 files changed, 216 insertions(+), 318 deletions(-) create mode 100644 Engine/source/core/util/refBase.cpp diff --git a/Engine/source/T3D/assets/SoundAsset.cpp b/Engine/source/T3D/assets/SoundAsset.cpp index f4882c50f..e8b6d3cdb 100644 --- a/Engine/source/T3D/assets/SoundAsset.cpp +++ b/Engine/source/T3D/assets/SoundAsset.cpp @@ -189,12 +189,6 @@ SoundAsset::SoundAsset() SoundAsset::~SoundAsset() { - - for (U32 i = 0; i < SFXPlayList::NUM_SLOTS; i++) - { - if(mSFXProfile[i].isProperlyAdded() && !mSFXProfile[i].isDeleted()) - mSFXProfile[i].unregisterObject(); - } if (mPlaylist.isProperlyAdded() && !mPlaylist.isDeleted()) mPlaylist.unregisterObject(); @@ -404,22 +398,17 @@ U32 SoundAsset::load() { Con::errorf("SoundAsset::initializeAsset: Attempted to load file %s but it was not valid!", mSoundFile[i]); mLoadedState = BadFileReference; - mSFXProfile[i].setDescription(NULL); - mSFXProfile[i].setSoundFileName(StringTable->insert(StringTable->EmptyString())); - mSFXProfile[i].setPreload(false); return mLoadedState; } else { - SFXProfile* trackProfile = new SFXProfile(); - trackProfile->setDescription(&mProfileDesc); - trackProfile->setSoundFileName(mSoundPath[i]); - trackProfile->setPreload(mPreload); + mSFXProfile[i] = new SFXProfile; + mSFXProfile[i]->setDescription(&mProfileDesc); + mSFXProfile[i]->setSoundFileName(mSoundPath[i]); + mSFXProfile[i]->setPreload(mPreload); + mSFXProfile[i]->registerObject(String::ToString("%s_profile_track%d", getAssetName()).c_str()); - mSFXProfile[i] = *trackProfile; - mSFXProfile[i].registerObject(String::ToString("%s_profile_track%d", getAssetName()).c_str()); - - mPlaylist.mSlots.mTrack[i] = trackProfile; + mPlaylist.mSlots.mTrack[i] = mSFXProfile[i]; } } @@ -436,18 +425,15 @@ U32 SoundAsset::load() { Con::errorf("SoundAsset::initializeAsset: Attempted to load file %s but it was not valid!", mSoundFile[0]); mLoadedState = BadFileReference; - mSFXProfile[0].setDescription(NULL); - mSFXProfile[0].setSoundFileName(StringTable->insert(StringTable->EmptyString())); - mSFXProfile[0].setPreload(false); return mLoadedState; } else { - mSFXProfile[0].setDescription(&mProfileDesc); - mSFXProfile[0].setSoundFileName(mSoundPath[0]); - mSFXProfile[0].setPreload(mPreload); - - mSFXProfile[0].registerObject(String::ToString("%s_profile", getAssetName()).c_str()); + mSFXProfile[0] = new SFXProfile; + mSFXProfile[0]->setDescription(&mProfileDesc); + mSFXProfile[0]->setSoundFileName(mSoundPath[0]); + mSFXProfile[0]->setPreload(mPreload); + mSFXProfile[0]->registerObject(String::ToString("%s_profile", getAssetName()).c_str()); } } diff --git a/Engine/source/T3D/assets/SoundAsset.h b/Engine/source/T3D/assets/SoundAsset.h index 1def01066..d005ec0c1 100644 --- a/Engine/source/T3D/assets/SoundAsset.h +++ b/Engine/source/T3D/assets/SoundAsset.h @@ -87,9 +87,9 @@ class SoundAsset : public AssetBase typedef AssetPtr ConcreteAssetPtr; protected: - StringTableEntry mSoundFile[SFXPlayList::SFXPlaylistSettings::NUM_SLOTS]; - StringTableEntry mSoundPath[SFXPlayList::SFXPlaylistSettings::NUM_SLOTS]; - SFXProfile mSFXProfile[SFXPlayList::SFXPlaylistSettings::NUM_SLOTS]; + StringTableEntry mSoundFile[SFXPlayList::SFXPlaylistSettings::NUM_SLOTS]; + StringTableEntry mSoundPath[SFXPlayList::SFXPlaylistSettings::NUM_SLOTS]; + SimObjectPtr mSFXProfile[SFXPlayList::SFXPlaylistSettings::NUM_SLOTS]; SFXDescription mProfileDesc; SFXPlayList mPlaylist; @@ -150,7 +150,7 @@ public: void copyTo(SimObject* object) override; //SFXResource* getSound() { return mSoundResource; } - Resource getSoundResource(const U32 slotId = 0) { load(); return mSFXProfile[slotId].getResource(); } + Resource getSoundResource(const U32 slotId = 0) { load(); return mSFXProfile[slotId]->getResource(); } /// Declare Console Object. DECLARE_CONOBJECT(SoundAsset); @@ -158,9 +158,9 @@ public: static bool _setSoundFile(void* object, const char* index, const char* data); U32 load() override; inline StringTableEntry getSoundPath(const U32 slotId = 0) const { return mSoundPath[slotId]; }; - SFXProfile* getSfxProfile(const U32 slotId = 0) { return &mSFXProfile[slotId]; } + SFXProfile* getSfxProfile(const U32 slotId = 0) { return mSFXProfile[slotId]; } SFXPlayList* getSfxPlaylist() { return &mPlaylist; } - SFXTrack* getSFXTrack() { load(); return mIsPlaylist ? dynamic_cast(&mPlaylist) : dynamic_cast(&mSFXProfile[0]); } + SFXTrack* getSFXTrack() { load(); return mIsPlaylist ? dynamic_cast(&mPlaylist) : dynamic_cast(mSFXProfile[0].getPointer()); } SFXDescription* getSfxDescription() { return &mProfileDesc; } bool isPlaylist(){ return mIsPlaylist; } diff --git a/Engine/source/console/simObject.h b/Engine/source/console/simObject.h index 4868a4585..1ea3a2b5d 100644 --- a/Engine/source/console/simObject.h +++ b/Engine/source/console/simObject.h @@ -40,7 +40,10 @@ #ifndef _TAML_CALLBACKS_H_ #include "persistence/taml/tamlCallbacks.h" #endif + +#ifndef _OBJECTTYPES_H_ #include "T3D/objectTypes.h" +#endif class Stream; class LightManager; @@ -606,6 +609,7 @@ class SimObject: public ConsoleObject, public TamlCallbacks /// @name Events /// @{ + //virtual void onPrepare(); /// Called when the object is added to the sim. virtual bool onAdd(); @@ -1045,56 +1049,47 @@ DefineBitfieldType(GameTypeMasksType); template< typename T > class SimObjectPtr : public WeakRefPtr< T > { - public: +public: - typedef WeakRefPtr< T > Parent; + typedef WeakRefPtr< T > Parent; - SimObjectPtr() {} - SimObjectPtr(T *ptr) { this->mReference = NULL; set(ptr); } - SimObjectPtr( const SimObjectPtr& ref ) { this->mReference = NULL; set(ref.mReference); } + SimObjectPtr() = default; + SimObjectPtr(T* ptr) { set(ptr); } + SimObjectPtr(const SimObjectPtr&) = default; + SimObjectPtr& operator=(const SimObjectPtr&) = default; + SimObjectPtr& operator=(T* ptr) + { + set(ptr); + return *this; + } - T* getObject() const { return Parent::getPointer(); } + T* getObject() const { return Parent::getPointer(); } - ~SimObjectPtr() { set((WeakRefBase::WeakReference*)NULL); } +protected: + void set(T* obj) + { + // Nothing to do if same object + if (obj && this->mWeak.lock() == obj->getWeakControl().lock()) + return; - SimObjectPtr& operator=(const SimObjectPtr ref) + // Before overwriting, check old object for auto-delete + if (auto old_ctrl = this->mWeak.lock()) { - set(ref.mReference); - return *this; - } - SimObjectPtr& operator=(T *ptr) - { - set(ptr); - return *this; - } - - protected: - void set(WeakRefBase::WeakReference * ref) - { - if( ref == this->mReference ) - return; - - if( this->mReference ) + T* old_obj = getObject(); + if (this->mWeak.use_count() == 1 && old_obj && old_obj->isAutoDeleted()) { - // Auto-delete - T* obj = this->getPointer(); - if ( this->mReference->getRefCount() == 2 && obj && obj->isAutoDeleted() ) - obj->deleteObject(); - - this->mReference->decRefCount(); - } - this->mReference = NULL; - if( ref ) - { - this->mReference = ref; - this->mReference->incRefCount(); + old_obj->destroySelf(); } } - void set(T * obj) + // Assign new weak reference + this->mWeak.reset(); + if (obj) { - set(obj ? obj->getWeakReference() : (WeakRefBase::WeakReference *)NULL); + auto obj_ctrl = obj->getWeakControl().lock(); + this->mWeak = obj_ctrl; } + } }; #endif // _SIMOBJECT_H_ diff --git a/Engine/source/core/util/refBase.cpp b/Engine/source/core/util/refBase.cpp new file mode 100644 index 000000000..2f4314742 --- /dev/null +++ b/Engine/source/core/util/refBase.cpp @@ -0,0 +1,18 @@ +#include "refBase.h" + +WeakRefBase::~WeakRefBase() +{ + if (mControl) + mControl->object = NULL; +} + +WeakControlBlock::WeakControlBlock(WeakRefBase* obj) + : object(obj) +{ +} + +WeakControlBlock::~WeakControlBlock() +{ + +} + diff --git a/Engine/source/core/util/refBase.h b/Engine/source/core/util/refBase.h index 42ce6705d..432358604 100644 --- a/Engine/source/core/util/refBase.h +++ b/Engine/source/core/util/refBase.h @@ -30,52 +30,52 @@ # include "platform/typetraits.h" #endif +#include +#include + +class WeakRefBase; + +struct WeakControlBlock +{ + explicit WeakControlBlock(WeakRefBase* obj); + ~WeakControlBlock(); + + WeakRefBase* object; +}; /// Base class for objects which can be weakly referenced /// (i.e., reference goes away when object is destroyed). class WeakRefBase { public: - - /// Weak reference to WeakRefBase. - class WeakReference + WeakRefBase() + : mControl(std::make_shared(this)) { - public: + } - [[nodiscard]] constexpr WeakRefBase* get() const { return mObject; } - [[nodiscard]] constexpr U32 getRefCount() const { return mRefCount; } + virtual ~WeakRefBase(); - constexpr void incRefCount() { mRefCount++; } - constexpr void decRefCount() { - AssertFatal( mRefCount > 0, "WeakReference - decrementing count of zero!" ); - if (--mRefCount==0) - delete this; - } - private: + // Copy constructor + WeakRefBase(const WeakRefBase&) = delete; + WeakRefBase& operator=(const WeakRefBase&) = delete; + WeakRefBase(WeakRefBase&&) = delete; + WeakRefBase& operator=(WeakRefBase&&) = delete; - friend class WeakRefBase; - constexpr explicit WeakReference(WeakRefBase *object) :mObject(object), mRefCount(0) {} + std::weak_ptr getWeakControl() + { + return mControl; + } - ~WeakReference() { AssertFatal(mObject==NULL, "Deleting weak reference which still points at an object."); } - - // Object we reference - WeakRefBase *mObject; - - // reference count for this structure (not WeakObjectRef itself) - U32 mRefCount; - }; - -public: - constexpr WeakRefBase() : mReference(NULL) {} - virtual ~WeakRefBase() { clearWeakReferences(); } - - WeakReference* getWeakReference(); + std::shared_ptr getSharedControl() + { + return mControl; + } protected: - void clearWeakReferences(); + std::shared_ptr mControl; private: - WeakReference * mReference; + }; template< typename T > class SimObjectPtr; @@ -88,100 +88,48 @@ template< typename T > class SimObjectPtr; template class WeakRefPtr { public: - constexpr WeakRefPtr() : mReference(NULL) {} - WeakRefPtr(T *ptr) : mReference(NULL) { set(ptr); } - WeakRefPtr(const WeakRefPtr & ref) { mReference = NULL; set(ref.mReference); } - - ~WeakRefPtr() { set(static_cast(NULL)); } + WeakRefPtr() = default; + WeakRefPtr(T* obj) { set(obj); } - WeakRefPtr& operator=(const WeakRefPtr& ref) - { - if (this == &ref) { return *this; } // handle self assignment ( x = x; ) - set(ref.mReference); - return *this; - } - WeakRefPtr& operator=(T *ptr) - { - set(ptr); - return *this; - } + WeakRefPtr(const WeakRefPtr&) = default; + WeakRefPtr& operator=(const WeakRefPtr&) = default; - /// Returns true if the pointer is not set. - [[nodiscard]] constexpr bool isNull() const { return mReference == NULL || mReference->get() == NULL; } - - /// Returns true if the pointer is set. - [[nodiscard]] constexpr bool isValid() const { return mReference && mReference->get(); } + WeakRefPtr& operator=(T* obj) + { + set(obj); + return *this; + } + + bool isValid() const { return getPointer() != NULL; } + bool isNull() const { return getPointer() == NULL; } [[nodiscard]] constexpr T* operator->() const { return getPointer(); } [[nodiscard]] constexpr T& operator*() const { return *getPointer(); } [[nodiscard]] constexpr operator T*() const { return getPointer(); } /// Returns the pointer. - [[nodiscard]] constexpr T* getPointer() const { return mReference ? (T*)mReference->get() : NULL; } + [[nodiscard]] constexpr T* getPointer() const + { + auto ctrl = mWeak.lock(); + if (!ctrl || !ctrl->object) + return NULL; + return (T*)(ctrl->object); + } protected: - void set(WeakRefBase::WeakReference* ref) - { - if (mReference) - mReference->decRefCount(); - mReference = NULL; - if (ref) + void set(T* obj) + { + if (!obj) { - mReference = ref; - mReference->incRefCount(); + mWeak.reset(); + return; } - } - void set(T* obj) { set(obj ? obj->getWeakReference() : NULL); } + mWeak = obj->getWeakControl(); + } private: template< typename > friend class SimObjectPtr; - WeakRefBase::WeakReference * mReference {NULL}; -}; - -/// Union of an arbitrary type with a WeakRefBase. The exposed type will -/// remain accessible so long as the WeakRefBase is not cleared. Once -/// it is cleared, accessing the exposed type will result in a NULL pointer. -template -class WeakRefUnion -{ - typedef WeakRefUnion Union; - -public: - constexpr WeakRefUnion() : mPtr(NULL) {} - constexpr WeakRefUnion(const WeakRefPtr & ref, ExposedType * ptr) : mPtr(NULL) { set(ref, ptr); } - constexpr WeakRefUnion(const Union & lock) : mPtr(NULL) { *this = lock; } - constexpr WeakRefUnion(WeakRefBase * ref) : mPtr(NULL) { set(ref, dynamic_cast(ref)); } - ~WeakRefUnion() { mWeakReference = NULL; } - - Union & operator=(const Union & ptr) - { - if (this == *ptr) { return *this; } - set(ptr.mWeakReference, ptr.getPointer()); - return *this; - } - - void set(const WeakRefPtr & ref, ExposedType * ptr) - { - mWeakReference = ref; - mPtr = ptr; - } - - [[nodiscard]] constexpr bool operator == (const ExposedType * t ) const { return getPointer() == t; } - [[nodiscard]] constexpr bool operator != (const ExposedType * t ) const { return getPointer() != t; } - [[nodiscard]] constexpr bool operator == (const Union &t ) const { return getPointer() == t.getPointer(); } - [[nodiscard]] constexpr bool operator != (const Union &t ) const { return getPointer() != t.getPointer(); } - [[nodiscard]] constexpr bool isNull() const { return mWeakReference.isNull() || !mPtr; } - - [[nodiscard]] constexpr ExposedType* getPointer() const { return !mWeakReference.isNull() ? mPtr : NULL; } - [[nodiscard]] constexpr ExposedType* operator->() const { return getPointer(); } - [[nodiscard]] constexpr ExposedType& operator*() const { return *getPointer(); } - [[nodiscard]] constexpr operator ExposedType*() const { return getPointer(); } - - [[nodiscard]] WeakRefPtr getRef() const { return mWeakReference; } - -private: - WeakRefPtr mWeakReference; - ExposedType * mPtr; + std::weak_ptr mWeak; }; /// Base class for objects which can be strongly referenced @@ -189,34 +137,40 @@ private: /// when all strong references go away, object is destroyed). class StrongRefBase : public WeakRefBase { - friend class StrongObjectRef; + friend class StrongObjectRef; public: - StrongRefBase() { mRefCount = 0; } + StrongRefBase() + { + mRefCount = 0; + } - U32 getRefCount() const { return mRefCount; } + virtual ~StrongRefBase() = default; - /// object destroy self call (from StrongRefPtr). Override if this class has specially allocated memory. - virtual void destroySelf() { delete this; } + U32 getRefCount() const { return mRefCount; } - /// Increments the reference count. - void incRefCount() - { - mRefCount++; - } + /// object destroy self call (from StrongRefPtr). Override if this class has specially allocated memory. + virtual void destroySelf() { delete this; } - /// Decrements the reference count. - void decRefCount() - { - AssertFatal(mRefCount, "Decrementing a reference with refcount 0!"); - if(!--mRefCount) - destroySelf(); - } + /// Increments the reference count. + void incRefCount() + { + mRefCount++; + } + + /// Decrements the reference count. + void decRefCount() + { + AssertFatal(mRefCount, "Decrementing a reference with refcount 0!"); + if (!--mRefCount) + destroySelf(); + } protected: - U32 mRefCount; ///< reference counter for StrongRefPtr objects + U32 mRefCount; ///< reference counter for StrongRefPtr objects }; + /// Base class for StrongRefBase strong reference pointers. class StrongObjectRef { @@ -289,55 +243,6 @@ public: T* getPointer() const { return const_cast(static_cast(mObject)); } }; -/// Union of an arbitrary type with a StrongRefBase. StrongRefBase will remain locked -/// until the union is destructed. Handy for when the exposed class will -/// become invalid whenever the reference becomes invalid and you want to make sure that doesn't -/// happen. -template -class StrongRefUnion -{ - typedef StrongRefUnion Union; - -public: - StrongRefUnion() : mPtr(NULL) {} - - StrongRefUnion(const StrongRefPtr & ref, ExposedType * ptr) : mPtr(NULL) { set(ref, ptr); } - StrongRefUnion(const Union & lock) : mPtr(NULL) { *this = lock; } - StrongRefUnion(StrongRefBase * ref) : mPtr(NULL) { set(ref, dynamic_cast(ref)); } - - ~StrongRefUnion() { mReference = NULL; } - - Union & operator=(const Union & ptr) - { - set(ptr.mReference, ptr.mPtr); - return *this; - } - - void set(const StrongRefPtr & ref, ExposedType * ptr) - { - mReference = ref; - mPtr = ptr; - } - - [[nodiscard]] constexpr bool operator == (const ExposedType * t ) const { return mPtr == t; } - [[nodiscard]] constexpr bool operator != (const ExposedType * t ) const { return mPtr != t; } - [[nodiscard]] constexpr bool operator == (const Union &t ) const { return mPtr == t.mPtr; } - [[nodiscard]] constexpr bool operator != (const Union &t ) const { return mPtr != t.mPtr; } - [[nodiscard]] constexpr bool isNull() const { return mReference.isNull() || !mPtr; } - [[nodiscard]] constexpr bool isValid() const { return mReference.isValid() && mPtr; } - - ExposedType* operator->() const { return mPtr; } - ExposedType& operator*() const { return *mPtr; } - operator ExposedType*() const { return mPtr; } - ExposedType* getPointer() const { return mPtr; } - - StrongRefPtr getRef() const { return mReference; } - -private: - StrongRefPtr mReference; - ExposedType * mPtr; -}; - /// This oxymoron is a pointer that reference-counts the referenced /// object but also NULLs out if the object goes away. /// @@ -347,97 +252,91 @@ private: /// StrongWeakRefs that keep object live as long as the superior entity doesn't /// step in and kill them (in which case, the client code sees the reference /// disappear). -template< class T > +template class StrongWeakRefPtr { public: - constexpr StrongWeakRefPtr() : mReference( NULL ) {} - constexpr StrongWeakRefPtr( T* ptr ) : mReference( NULL ) { _set( ptr ); } - ~StrongWeakRefPtr() - { - if( mReference ) - { - T* ptr = _get(); - if( ptr ) - ptr->decRefCount(); + StrongWeakRefPtr() = default; - mReference->decRefCount(); - } + StrongWeakRefPtr(T* ptr) { set(ptr); } + + StrongWeakRefPtr(const StrongWeakRefPtr& other) + { + set(other.getPointer()); } - [[nodiscard]] constexpr bool isNull() const { return ( _get() == NULL ); } - [[nodiscard]] constexpr bool operator ==( T* ptr ) const { return ( _get() == ptr ); } - [[nodiscard]] constexpr bool operator !=( T* ptr ) const { return ( _get() != ptr ); } - [[nodiscard]] constexpr bool operator !() const { return isNull(); } - [[nodiscard]] constexpr T* operator ->() const { return _get(); } - [[nodiscard]] constexpr T& operator *() const { return *( _get() ); } - - constexpr operator T*() const { return _get(); } // consider making this explicit - - T* getPointer() const { return _get(); } - - StrongWeakRefPtr& operator =( T* ptr ) + StrongWeakRefPtr& operator=(const StrongWeakRefPtr& other) { - _set( ptr ); + if (this != &other) + set(other.getPointer()); return *this; } -private: - WeakRefBase::WeakReference* mReference; - - T* _get() const + ~StrongWeakRefPtr() { - if( mReference ) - return static_cast< T* >( mReference->get() ); - else - return NULL; + release(); } - void _set( T* ptr ) + + StrongWeakRefPtr& operator=(T* ptr) + { + set(ptr); + return *this; + } + + bool isValid() const { return getPointer() != NULL; } + bool isNull() const { return getPointer() == NULL; } + [[nodiscard]] bool operator==(T* ptr) const { return getPointer() == ptr; } + [[nodiscard]] bool operator!=(T* ptr) const { return getPointer() != ptr; } + [[nodiscard]] bool operator!() const { return isNull(); } + + [[nodiscard]] T* operator->() const { return getPointer(); } + [[nodiscard]] T& operator*() const { return *getPointer(); } + constexpr operator T* () const { return getPointer(); } + + [[nodiscard]] T* getPointer() const { - if( mReference ) - { - T* old = _get(); - if( old ) - old->decRefCount(); + return mControl ? static_cast(mControl->object) : NULL; + } - mReference->decRefCount(); + +private: + std::shared_ptr mControl; + T* mPtr = NULL; + + void set(T* ptr) + { + release(); + if (!ptr) return; + + // Always hold the identity + mControl = ptr->getWeakControl().lock(); + if (!mControl) return; + + mPtr = ptr; + + // Conditionally retain object lifetime if T supports intrusive refcount + // runtime check: only strong ref if T inherits StrongRefBase + if constexpr (std::is_base_of_v) + { + mPtr->incRefCount(); + } + } + + void release() + { + if (mPtr) + { + if constexpr (std::is_base_of_v) + { + mPtr->decRefCount(); + } } - if( ptr ) - { - ptr->incRefCount(); - mReference = ptr->getWeakReference(); - mReference->incRefCount(); - } - else - mReference = NULL; + mPtr = NULL; + mControl.reset(); } }; -//--------------------------------------------------------------- - -inline void WeakRefBase::clearWeakReferences() -{ - if (mReference) - { - mReference->mObject = NULL; - mReference->decRefCount(); - mReference = NULL; - } -} - -inline WeakRefBase::WeakReference* WeakRefBase::getWeakReference() -{ - if (!mReference) - { - mReference = new WeakReference(this); - mReference->incRefCount(); - } - return mReference; -} - -//--------------------------------------------------------------- - template< typename T > struct TypeTraits< WeakRefPtr< T > > : public _TypeTraits< WeakRefPtr< T > > { From df0971d0dd1015bd4c6300fe04bd12741ab40bcf Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Thu, 2 Apr 2026 17:31:19 -0500 Subject: [PATCH 02/11] point rightclick prefab at the same method as the button --- .../game/tools/worldEditor/scripts/EditorGui.ed.tscript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Templates/BaseGame/game/tools/worldEditor/scripts/EditorGui.ed.tscript b/Templates/BaseGame/game/tools/worldEditor/scripts/EditorGui.ed.tscript index 9dcd083da..7d67f2a27 100644 --- a/Templates/BaseGame/game/tools/worldEditor/scripts/EditorGui.ed.tscript +++ b/Templates/BaseGame/game/tools/worldEditor/scripts/EditorGui.ed.tscript @@ -2101,7 +2101,7 @@ function EditorTree::onRightMouseUp( %this, %itemId, %mouse, %obj ) %popup.item[ 0 ] = "Delete" TAB "" TAB "EditorMenuEditDelete();"; %popup.item[ 1 ] = "Group" TAB "" TAB "EWorldEditor.addSimGroup( true );"; %popup.item[ 2 ] = "-"; - %popup.item[ 3 ] = "Make selected a Prefab" TAB "" TAB "EWorldEditor.makeSelectionPrefab();"; + %popup.item[ 3 ] = "Make selected a Prefab" TAB "" TAB "EditorMakePrefab();"; %popup.item[ 4 ] = "Bake selected into Mesh" TAB "" TAB "AssetBrowser.setupCreateNewAsset(\"ShapeAsset\", AssetBrowser.selectedModule, \"makeSelectedAMesh\");"; %popup.item[ 5 ] = "-"; %popup.item[ 6 ] = "Make selected a Sub-Level" TAB "" TAB "MakeSelectionASublevel();"; From 9ca436d193d8d21b4e82ee01dcb8184ab6bab953 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Sat, 4 Apr 2026 15:21:47 -0500 Subject: [PATCH 03/11] quick fix for misc sounds player and vehicle were not loading some of thier sounds. we'll want to revisit this with a more comprehensive solution in the future, but this will at least allow folks to use the systems in place --- Engine/source/T3D/player.cpp | 1 + Engine/source/T3D/rigidShape.cpp | 2 ++ Engine/source/T3D/vehicles/flyingVehicle.cpp | 1 + Engine/source/T3D/vehicles/hoverVehicle.cpp | 1 + Engine/source/T3D/vehicles/wheeledVehicle.cpp | 1 + 5 files changed, 6 insertions(+) diff --git a/Engine/source/T3D/player.cpp b/Engine/source/T3D/player.cpp index 7c7830ddc..0ccb13828 100644 --- a/Engine/source/T3D/player.cpp +++ b/Engine/source/T3D/player.cpp @@ -471,6 +471,7 @@ bool PlayerData::preload(bool server, String &errorStr) if (!server) { for (U32 i = 0; i < MaxSounds; ++i) { + _setPlayerSound(getPlayerSound(i), i); if (!isPlayerSoundValid(i)) { //return false; -TODO: trigger asset download diff --git a/Engine/source/T3D/rigidShape.cpp b/Engine/source/T3D/rigidShape.cpp index 129c0dcf9..4caac7ddb 100644 --- a/Engine/source/T3D/rigidShape.cpp +++ b/Engine/source/T3D/rigidShape.cpp @@ -348,6 +348,7 @@ bool RigidShapeData::preload(bool server, String &errorStr) if (!server) { for (S32 i = 0; i < Body::MaxSounds; i++) { + _setBodySounds(getBodySounds(i), i); if (!isBodySoundsValid(i)) { //return false; -TODO: trigger asset download @@ -356,6 +357,7 @@ bool RigidShapeData::preload(bool server, String &errorStr) for (S32 j = 0; j < Sounds::MaxSounds; j++) { + _setWaterSounds(getWaterSounds(j), j); if (!isWaterSoundsValid(j)) { //return false; -TODO: trigger asset download diff --git a/Engine/source/T3D/vehicles/flyingVehicle.cpp b/Engine/source/T3D/vehicles/flyingVehicle.cpp index 4e10c4b2a..6dfe8d8de 100644 --- a/Engine/source/T3D/vehicles/flyingVehicle.cpp +++ b/Engine/source/T3D/vehicles/flyingVehicle.cpp @@ -141,6 +141,7 @@ bool FlyingVehicleData::preload(bool server, String &errorStr) if (!server) { for (S32 i = 0; i < MaxSounds; i++) { + _setFlyingSounds(getFlyingSounds(i), i); if (!isFlyingSoundsValid(i)) { //return false; -TODO: trigger asset download diff --git a/Engine/source/T3D/vehicles/hoverVehicle.cpp b/Engine/source/T3D/vehicles/hoverVehicle.cpp index 60c24434f..43321abc3 100644 --- a/Engine/source/T3D/vehicles/hoverVehicle.cpp +++ b/Engine/source/T3D/vehicles/hoverVehicle.cpp @@ -313,6 +313,7 @@ bool HoverVehicleData::preload(bool server, String &errorStr) for (S32 i = 0; i < MaxSounds; i++) { + _setHoverSounds(getHoverSounds(i), i); if (!isHoverSoundsValid(i)) { //return false; -TODO: trigger asset download diff --git a/Engine/source/T3D/vehicles/wheeledVehicle.cpp b/Engine/source/T3D/vehicles/wheeledVehicle.cpp index 44b8b0623..dec634690 100644 --- a/Engine/source/T3D/vehicles/wheeledVehicle.cpp +++ b/Engine/source/T3D/vehicles/wheeledVehicle.cpp @@ -348,6 +348,7 @@ bool WheeledVehicleData::preload(bool server, String &errorStr) if (!server) { for (S32 i = 0; i < MaxSounds; i++) { + _setWheeledVehicleSounds(getWheeledVehicleSounds(i), i); if (!isWheeledVehicleSoundsValid(i)) { //return false; -TODO: trigger asset download From 5690c64ef62f438408f32122a3768593e6e416cf Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Mon, 6 Apr 2026 16:55:55 -0500 Subject: [PATCH 04/11] dampness rework more respect for gravity, fewer texture lookups --- .../core/rendering/shaders/gl/lighting.glsl | 25 ++++++++--------- .../game/core/rendering/shaders/lighting.hlsl | 27 +++++++++---------- 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/Templates/BaseGame/game/core/rendering/shaders/gl/lighting.glsl b/Templates/BaseGame/game/core/rendering/shaders/gl/lighting.glsl index addc16541..7ee6b4dff 100644 --- a/Templates/BaseGame/game/core/rendering/shaders/gl/lighting.glsl +++ b/Templates/BaseGame/game/core/rendering/shaders/gl/lighting.glsl @@ -434,25 +434,22 @@ void dampen(inout Surface surface, sampler2D WetnessTexture, float accumTime, fl { if (degree<=0.0) return; vec3 n = abs(surface.N); - float ang = clamp(n.z, 0.04, 0.96); + float ang = 1.1-(abs(surface.N.z)*sign(surface.N.z)); - float speed = -accumTime*(1.0-surface.linearRoughnessSq)*clamp((2.0-ang), 0.04, 0.96); - if ((n.x > 0.0) || (n.y > 0.0)) - speed *= -1.0; - vec2 wetoffset = vec2(speed,speed)*0.1; + float speed = accumTime * (1.0 - surface.linearRoughnessSq); + vec3 wetoffset = (surface.P+vec3(speed,speed,speed)) * 0.33; - vec3 wetNormal = texture(WetnessTexture, float2(surface.P.xy*0.1+wetoffset)).xyz; - wetNormal = lerp(wetNormal,texture(WetnessTexture,float2(surface.P.zx*0.1+wetoffset)).rgb ,n.y); - wetNormal = lerp(wetNormal,texture(WetnessTexture,float2(surface.P.zy*0.1+wetoffset)).rgb ,n.x); - surface.N = lerp(surface.N, wetNormal, degree); + vec3 wetNormal = texture(WetnessTexture, wetoffset.xy).xyz * (1.1-(n.z* n.z)); + wetNormal = lerp(wetNormal, texture(WetnessTexture, wetoffset.zx).rgb, n.y); + wetNormal = lerp(wetNormal, texture(WetnessTexture, wetoffset.zy).rgb, n.x); + wetNormal = normalize(wetNormal); + float wetness = wetNormal.b* degree; - float wetness = texture(WetnessTexture, vec2(surface.P.xy*0.1+wetoffset)).b; - wetness = lerp(wetness,texture(WetnessTexture,vec2(surface.P.zx*0.1+wetoffset)).b,n.y); - wetness = lerp(wetness,texture(WetnessTexture,vec2(surface.P.zy*0.1+wetoffset)).b,n.x); - wetness = pow(wetness*ang*degree,3); + wetNormal = normalize(wetNormal * 2.0 - 1.0); + surface.N = normalize(vec3(surface.N.xy + wetNormal.xy * wetness, surface.N.z)); surface.roughness = lerp(surface.roughness, 0.04f, wetness); - surface.baseColor.rgb = lerp(surface.baseColor.rgb, surface.baseColor.rgb*0.6+float3(0.4,0.4,0.4)*wetness, wetness); + surface.baseColor.rgb = lerp(surface.baseColor.rgb, surface.baseColor.rgb*0.6+vec3(0.4,0.4,0.4)*wetness, wetness); surface.metalness = lerp(surface.metalness, 0.96, wetness); updateSurface(surface); } diff --git a/Templates/BaseGame/game/core/rendering/shaders/lighting.hlsl b/Templates/BaseGame/game/core/rendering/shaders/lighting.hlsl index 8b544ca9c..6af499e4e 100644 --- a/Templates/BaseGame/game/core/rendering/shaders/lighting.hlsl +++ b/Templates/BaseGame/game/core/rendering/shaders/lighting.hlsl @@ -436,25 +436,22 @@ void dampen(inout Surface surface, TORQUE_SAMPLER2D(WetnessTexture), float accum { if (degree<=0.0) return; float3 n = abs(surface.N); - float ang = clamp(n.z, 0.04, 0.96); + float ang = 1.1-(abs(surface.N.z)*sign(surface.N.z)); - float speed = -accumTime*(1.0-surface.linearRoughnessSq)*clamp((2.0-ang), 0.04, 0.96); - if ((n.x > 0.0) || (n.y > 0.0)) - speed *= -1.0; - float2 wetoffset = float2(speed,speed)*0.1; + float speed = accumTime * (1.0 - surface.linearRoughnessSq); + float3 wetoffset = (surface.P+float3(speed,speed,speed)) * 0.33; - float3 wetNormal = TORQUE_TEX2D(WetnessTexture, float2(surface.P.xy*0.1+wetoffset)).xyz; - wetNormal = lerp(wetNormal,TORQUE_TEX2D(WetnessTexture,float2(surface.P.zx*0.1+wetoffset)).rgb ,n.y); - wetNormal = lerp(wetNormal,TORQUE_TEX2D(WetnessTexture,float2(surface.P.zy*0.1+wetoffset)).rgb ,n.x); - surface.N = lerp(surface.N, wetNormal, degree); - - float wetness = TORQUE_TEX2D(WetnessTexture, float2(surface.P.xy*0.1+wetoffset)).b; - wetness = lerp(wetness,TORQUE_TEX2D(WetnessTexture,float2(surface.P.zx*0.1+wetoffset)).b,n.y); - wetness = lerp(wetness,TORQUE_TEX2D(WetnessTexture,float2(surface.P.zy*0.1+wetoffset)).b,n.x); - wetness = pow(wetness*ang*degree,3); + float3 wetNormal = TORQUE_TEX2D(WetnessTexture, wetoffset.xy).xyz * (1.1-(n.z* n.z)); + wetNormal = lerp(wetNormal, TORQUE_TEX2D(WetnessTexture, wetoffset.zx).rgb, n.y); + wetNormal = lerp(wetNormal, TORQUE_TEX2D(WetnessTexture, wetoffset.zy).rgb, n.x); + wetNormal = normalize(wetNormal); + float wetness = wetNormal.b* degree; + + wetNormal = normalize(wetNormal * 2.0 - 1.0); + surface.N = normalize(float3(surface.N.xy + wetNormal.xy * wetness, surface.N.z)); surface.roughness = lerp(surface.roughness, 0.04f, wetness); - surface.baseColor.rgb = lerp(surface.baseColor.rgb, surface.baseColor.rgb*0.6+float3(0.4,0.4,0.4)*wetness, wetness); + surface.baseColor.rgb = lerp(surface.baseColor.rgb, surface.baseColor.rgb * 0.6 + float3(0.4, 0.4, 0.4) * wetness, wetness); surface.metalness = lerp(surface.metalness, 0.96, wetness); surface.Update(); } From d85bbc0a1f6aef1abbd47563647fdfe7e8a26e8c Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Wed, 8 Apr 2026 19:27:43 -0500 Subject: [PATCH 05/11] account for translucencies --- Templates/BaseGame/game/core/rendering/shaders/gl/lighting.glsl | 2 +- Templates/BaseGame/game/core/rendering/shaders/lighting.hlsl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Templates/BaseGame/game/core/rendering/shaders/gl/lighting.glsl b/Templates/BaseGame/game/core/rendering/shaders/gl/lighting.glsl index 7ee6b4dff..58b2881f2 100644 --- a/Templates/BaseGame/game/core/rendering/shaders/gl/lighting.glsl +++ b/Templates/BaseGame/game/core/rendering/shaders/gl/lighting.glsl @@ -449,7 +449,7 @@ void dampen(inout Surface surface, sampler2D WetnessTexture, float accumTime, fl surface.N = normalize(vec3(surface.N.xy + wetNormal.xy * wetness, surface.N.z)); surface.roughness = lerp(surface.roughness, 0.04f, wetness); - surface.baseColor.rgb = lerp(surface.baseColor.rgb, surface.baseColor.rgb*0.6+vec3(0.4,0.4,0.4)*wetness, wetness); + surface.baseColor = vec4(lerp(surface.baseColor.rgb, surface.baseColor.rgb*0.6+vec3(0.4,0.4,0.4)*wetness, wetness), max(surface.baseColor.a, 0.4* wetness)); surface.metalness = lerp(surface.metalness, 0.96, wetness); updateSurface(surface); } diff --git a/Templates/BaseGame/game/core/rendering/shaders/lighting.hlsl b/Templates/BaseGame/game/core/rendering/shaders/lighting.hlsl index 6af499e4e..b712e5ad0 100644 --- a/Templates/BaseGame/game/core/rendering/shaders/lighting.hlsl +++ b/Templates/BaseGame/game/core/rendering/shaders/lighting.hlsl @@ -451,7 +451,7 @@ void dampen(inout Surface surface, TORQUE_SAMPLER2D(WetnessTexture), float accum surface.N = normalize(float3(surface.N.xy + wetNormal.xy * wetness, surface.N.z)); surface.roughness = lerp(surface.roughness, 0.04f, wetness); - surface.baseColor.rgb = lerp(surface.baseColor.rgb, surface.baseColor.rgb * 0.6 + float3(0.4, 0.4, 0.4) * wetness, wetness); + surface.baseColor = float4(lerp(surface.baseColor.rgb, surface.baseColor.rgb * 0.6 + float3(0.4, 0.4, 0.4) * wetness, wetness), max(surface.baseColor.a, 0.4* wetness)); surface.metalness = lerp(surface.metalness, 0.96, wetness); surface.Update(); } From d6e370508fcd938e5563c0957079ed7a67a554da Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Thu, 9 Apr 2026 23:59:52 -0500 Subject: [PATCH 06/11] for some reason undo was comming in as a global method. so no temp vars --- Engine/source/gui/editor/guiInspectorTypes.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/source/gui/editor/guiInspectorTypes.cpp b/Engine/source/gui/editor/guiInspectorTypes.cpp index 09b622131..d33014ef3 100644 --- a/Engine/source/gui/editor/guiInspectorTypes.cpp +++ b/Engine/source/gui/editor/guiInspectorTypes.cpp @@ -1174,7 +1174,7 @@ GuiControl* GuiInspectorTypeColor::constructEditControl() if( inspector->isMethod( "onInspectorPreFieldModification" ) ) { dSprintf( szBuffer, sizeof( szBuffer ), - "%d.onInspectorPreFieldModification(\"%s\",\"%s\"); %s(%s, \"%d.onInspectorPostFieldModification(); %d.applyWithoutUndo\", %d.getRoot(), \"%d.applyWithoutUndo\", \"%d.onInspectorDiscardFieldModification(); %%unused=\");", + "%d.onInspectorPreFieldModification(\"%s\",\"%s\"); %s(%s, \"%d.onInspectorPostFieldModification(); %d.applyWithoutUndo\", %d.getRoot(), \"%d.applyWithoutUndo\", \"%d.onInspectorDiscardFieldModification(); %$unused=\");", inspector->getId(), getRawFieldName(), getArrayIndex(), mColorFunction, szColor, inspector->getId(), getId(), getId(), From 93b5a8d22eb26983bb4829bab42e9249610cbbe4 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 10 Apr 2026 06:42:45 +0100 Subject: [PATCH 07/11] Terrain doesnt render correctly The core of this issue was the unlock on the GFXGLTextureObject Few other bug fixes around setting a texture binding to 0, notice in clear function we now also clear the glprogram and the buffers. This seems to fix most of the warnings around id 131204 This warning was triggered every frame, now it just triggers when a shader expects a texture but none is active. --- Engine/source/environment/scatterSky.cpp | 2 +- Engine/source/gfx/gl/gfxGLDevice.cpp | 4 + Engine/source/gfx/gl/gfxGLTextureManager.cpp | 8 +- Engine/source/gfx/gl/gfxGLTextureObject.cpp | 103 ++++++------------- 4 files changed, 39 insertions(+), 78 deletions(-) diff --git a/Engine/source/environment/scatterSky.cpp b/Engine/source/environment/scatterSky.cpp index 40d0e5137..394b9769b 100644 --- a/Engine/source/environment/scatterSky.cpp +++ b/Engine/source/environment/scatterSky.cpp @@ -1094,7 +1094,7 @@ void ScatterSky::_render( ObjectRenderInst *ri, SceneRenderState *state, BaseMat } else { - GFX->setCubeTexture( 0, NULL ); + GFX->setTexture( 0, NULL ); mShaderConsts->setSafe( mUseCubemapSC, 0.0f ); } diff --git a/Engine/source/gfx/gl/gfxGLDevice.cpp b/Engine/source/gfx/gl/gfxGLDevice.cpp index 8ec644b8a..c5b166a0e 100644 --- a/Engine/source/gfx/gl/gfxGLDevice.cpp +++ b/Engine/source/gfx/gl/gfxGLDevice.cpp @@ -640,6 +640,10 @@ void GFXGLDevice::copyResource(GFXTextureObject* pDst, GFXCubemap* pSrc, const U void GFXGLDevice::clear(U32 flags, const LinearColorF& color, F32 z, U32 stencil) { + glUseProgram(0); + mCurrentShader = NULL; + mCurrentConstBuffer = NULL; + // Make sure we have flushed our render target state. _updateRenderTargets(); diff --git a/Engine/source/gfx/gl/gfxGLTextureManager.cpp b/Engine/source/gfx/gl/gfxGLTextureManager.cpp index c6c73fbee..5e769dbd7 100644 --- a/Engine/source/gfx/gl/gfxGLTextureManager.cpp +++ b/Engine/source/gfx/gl/gfxGLTextureManager.cpp @@ -480,10 +480,9 @@ bool GFXGLTextureManager::_loadTexture(GFXTextureObject *aTexture, GBitmap *pDL) } } - if(!ImageUtil::isCompressedFormat(pDL->getFormat())) + if (mipLevels > 1 && !ImageUtil::isCompressedFormat(pDL->getFormat())) glGenerateMipmap(texture->getBinding()); - glBindTexture(target, 0); return true; } @@ -560,10 +559,9 @@ bool GFXGLTextureManager::_loadTexture(GFXTextureObject *aTexture, DDSFile *dds) } } - if (numMips != 1 && !isCompressed) + if (numMips > 1 && !isCompressed) glGenerateMipmap(texture->getBinding()); - glBindTexture(target, 0); return true; } @@ -608,7 +606,7 @@ bool GFXGLTextureManager::_refreshTexture(GFXTextureObject *texture) _loadTexture(texture, texture->mBitmap); if(texture->mDDS) - return false; + _loadTexture(texture, texture->mDDS); usedStrategies++; } diff --git a/Engine/source/gfx/gl/gfxGLTextureObject.cpp b/Engine/source/gfx/gl/gfxGLTextureObject.cpp index 81ca6fa9d..0b58c19b6 100644 --- a/Engine/source/gfx/gl/gfxGLTextureObject.cpp +++ b/Engine/source/gfx/gl/gfxGLTextureObject.cpp @@ -102,79 +102,36 @@ GFXLockedRect* GFXGLTextureObject::lock(U32 mipLevel /*= 0*/, RectI* inRect /*= void GFXGLTextureObject::unlock(U32 mipLevel /*= 0*/, U32 faceIndex /*= 0*/) { - if (!mLockedRect.bits) - return; + if (!mLockedRect.bits) + return; - PROFILE_SCOPE(GFXGLTextureObject_unlock); + // I know this is in unlock, but in GL we actually do our submission in unlock. + PROFILE_SCOPE(GFXGLTextureObject_lockRT); - PRESERVE_TEXTURE(mBinding); - glBindTexture(mBinding, mHandle); + PRESERVE_TEXTURE(mBinding); + glBindTexture(mBinding, mHandle); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mBuffer); + glBufferData(GL_PIXEL_UNPACK_BUFFER, (mLockedRectRect.extent.x + 1) * (mLockedRectRect.extent.y + 1) * mBytesPerTexel, mFrameAllocatorPtr, GL_STREAM_DRAW); + S32 z = getDepth(); + if (mBinding == GL_TEXTURE_3D) + glTexSubImage3D(mBinding, mipLevel, mLockedRectRect.point.x, mLockedRectRect.point.y, z, + mLockedRectRect.extent.x, mLockedRectRect.extent.y, z, GFXGLTextureFormat[mFormat], GFXGLTextureType[mFormat], NULL); + else if (mBinding == GL_TEXTURE_2D) + glTexSubImage2D(mBinding, mipLevel, mLockedRectRect.point.x, mLockedRectRect.point.y, + mLockedRectRect.extent.x, mLockedRectRect.extent.y, GFXGLTextureFormat[mFormat], GFXGLTextureType[mFormat], NULL); + else if (mBinding == GL_TEXTURE_1D) + glTexSubImage1D(mBinding, mipLevel, (mLockedRectRect.point.x > 1 ? mLockedRectRect.point.x : mLockedRectRect.point.y), + (mLockedRectRect.extent.x > 1 ? mLockedRectRect.extent.x : mLockedRectRect.extent.y), GFXGLTextureFormat[mFormat], GFXGLTextureType[mFormat], NULL); - // --- Save pixel store state --- - GLint prevUnpackAlign; - glGetIntegerv(GL_UNPACK_ALIGNMENT, &prevUnpackAlign); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - - const U32 width = mLockedRectRect.extent.x; - const U32 height = mLockedRectRect.extent.y; - const U32 depth = getDepth(); - - if (mBinding == GL_TEXTURE_3D) - { - glTexSubImage3D( - mBinding, - mipLevel, - mLockedRectRect.point.x, - mLockedRectRect.point.y, - 0, - width, - height, - depth, - GFXGLTextureFormat[mFormat], - GFXGLTextureType[mFormat], - mLockedRect.bits - ); - } - else if (mBinding == GL_TEXTURE_2D) - { - glTexSubImage2D( - mBinding, - mipLevel, - mLockedRectRect.point.x, - mLockedRectRect.point.y, - width, - height, - GFXGLTextureFormat[mFormat], - GFXGLTextureType[mFormat], - mLockedRect.bits - ); - } - else if (mBinding == GL_TEXTURE_1D) - { - glTexSubImage1D( - mBinding, - mipLevel, - mLockedRectRect.point.x, - width, - GFXGLTextureFormat[mFormat], - GFXGLTextureType[mFormat], - mLockedRect.bits - ); - } - - // --- Restore state --- - glPixelStorei(GL_UNPACK_ALIGNMENT, prevUnpackAlign); - - mLockedRect.bits = NULL; - - FrameAllocator::setWaterMark(mFrameAllocatorMark); - mFrameAllocatorMark = 0; - mFrameAllocatorPtr = NULL; - -#ifdef TORQUE_DEBUG - glCheckErrors(); + mLockedRect.bits = NULL; +#if TORQUE_DEBUG + AssertFatal(mFrameAllocatorMarkGuard == FrameAllocator::getWaterMark(), ""); #endif + FrameAllocator::setWaterMark(mFrameAllocatorMark); + mFrameAllocatorMark = 0; + mFrameAllocatorPtr = NULL; } void GFXGLTextureObject::release() @@ -281,7 +238,6 @@ bool GFXGLTextureObject::copyToBmp(GBitmap * bmp) } // face } // mip - glBindTexture(mBinding, 0); return true; } @@ -298,6 +254,9 @@ void GFXGLTextureObject::updateTextureSlot(const GFXTexHandle& texHandle, const const GLenum srcTarget = srcTex->getBinding(); // source binding const bool srcIsCube = (srcTarget == GL_TEXTURE_CUBE_MAP || srcTarget == GL_TEXTURE_CUBE_MAP_ARRAY); + PRESERVE_TEXTURE(srcTarget); + PRESERVE_TEXTURE(dstTarget); + // Determine list of faces to copy from source U32 firstFace = 0; U32 faceCount = 1; @@ -435,9 +394,6 @@ void GFXGLTextureObject::updateTextureSlot(const GFXTexHandle& texHandle, const GFXGLTextureFormat[mFormat], GFXGLTextureType[mFormat], buffer); } } - - glBindTexture(dstTarget, 0); - glBindTexture(srcTarget, 0); } } @@ -462,6 +418,9 @@ void GFXGLTextureObject::initSamplerState(const GFXSamplerStateDesc &ssd) void GFXGLTextureObject::bind(U32 textureUnit) { + if (!mHandle || mIsZombie) + return; + glActiveTexture(GL_TEXTURE0 + textureUnit); glBindTexture(mBinding, mHandle); GFXGL->getOpenglCache()->setCacheBindedTex(textureUnit, mBinding, mHandle); From 02b3f3d0a1b789293fdc6a88bb37b67cc8f644d2 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 10 Apr 2026 08:34:59 +0100 Subject: [PATCH 08/11] Update gfxGLDevice.cpp clearing the active program caused issues during a target bake as we call glClear after setting the shader etc. As this warning id 131204 seems to be a product of doing this, everything i read up on it says it is safe to silence it. --- Engine/source/gfx/gl/gfxGLDevice.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Engine/source/gfx/gl/gfxGLDevice.cpp b/Engine/source/gfx/gl/gfxGLDevice.cpp index c5b166a0e..efe84e873 100644 --- a/Engine/source/gfx/gl/gfxGLDevice.cpp +++ b/Engine/source/gfx/gl/gfxGLDevice.cpp @@ -104,6 +104,12 @@ void APIENTRY glDebugCallback( if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) return; + // This warning id appears safe to ignore, we clear states + // but leave programs active when rendering to a target + // this produces a warning during glClear + if (id == 131204) + return; + const char* srcStr = "UNKNOWN"; const char* typeStr = "UNKNOWN"; const char* sevStr = "UNKNOWN"; @@ -640,10 +646,6 @@ void GFXGLDevice::copyResource(GFXTextureObject* pDst, GFXCubemap* pSrc, const U void GFXGLDevice::clear(U32 flags, const LinearColorF& color, F32 z, U32 stencil) { - glUseProgram(0); - mCurrentShader = NULL; - mCurrentConstBuffer = NULL; - // Make sure we have flushed our render target state. _updateRenderTargets(); From dead75f45818806982bce350ba28558db9f159a1 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 10 Apr 2026 16:48:30 +0100 Subject: [PATCH 09/11] Update gfxGLDevice.cpp --- Engine/source/gfx/gl/gfxGLDevice.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Engine/source/gfx/gl/gfxGLDevice.cpp b/Engine/source/gfx/gl/gfxGLDevice.cpp index efe84e873..0dd26f436 100644 --- a/Engine/source/gfx/gl/gfxGLDevice.cpp +++ b/Engine/source/gfx/gl/gfxGLDevice.cpp @@ -57,6 +57,9 @@ #include "gfx/gl/tGL/tXGL.h" #endif +// #131204 - Texture state usage warning: The texture object (0) bound to texture image unit 0 +#define GL_LOW_WARN_TEXTURE_STATE 131204 + GFXAdapter::CreateDeviceInstanceDelegate GFXGLDevice::mCreateDeviceInstance(GFXGLDevice::createInstance); GFXDevice *GFXGLDevice::createInstance( U32 adapterIndex ) @@ -104,10 +107,8 @@ void APIENTRY glDebugCallback( if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) return; - // This warning id appears safe to ignore, we clear states - // but leave programs active when rendering to a target - // this produces a warning during glClear - if (id == 131204) + // Silence: Texture state usage warning: The texture object (0) bound to texture image unit 0 + if (id == GL_LOW_WARN_TEXTURE_STATE) return; const char* srcStr = "UNKNOWN"; From f622d97224a699693c22e757a59199e5030e193c Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 10 Apr 2026 17:00:58 +0100 Subject: [PATCH 10/11] Update gfxGLDevice.cpp --- Engine/source/gfx/gl/gfxGLDevice.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Engine/source/gfx/gl/gfxGLDevice.cpp b/Engine/source/gfx/gl/gfxGLDevice.cpp index 0dd26f436..a39d9bfd6 100644 --- a/Engine/source/gfx/gl/gfxGLDevice.cpp +++ b/Engine/source/gfx/gl/gfxGLDevice.cpp @@ -57,9 +57,24 @@ #include "gfx/gl/tGL/tXGL.h" #endif +#pragma region GL WARNINGS + // #131204 - Texture state usage warning: The texture object (0) bound to texture image unit 0 #define GL_LOW_WARN_TEXTURE_STATE 131204 +// #131169 - Framebuffer detailed info: The driver allocated storage for renderbuffer 2. (severity: low) +#define GL_LOW_WARN_FRAMEBUFFER 131169 + +// #131185 - Buffer detailed info: Buffer object 1 (bound to GL_ELEMENT_ARRAY_BUFFER_ARB, usage hint is GL_ENUM_88e4) +// will use VIDEO memory as the source for buffer object operations. (severity: low) +#define GL_LOW_WARN_VIDEO_MEMORY 131185 + +// #131218 - Program/shader state performance warning: Vertex shader in program # +// is being recompiled based on GL state. (severity: medium) +#define GL_MED_WARN_PERFORMANCE_RECOMPILE 131218 + +#pragma endregion + GFXAdapter::CreateDeviceInstanceDelegate GFXGLDevice::mCreateDeviceInstance(GFXGLDevice::createInstance); GFXDevice *GFXGLDevice::createInstance( U32 adapterIndex ) From a78c0b430923e07e65035978a49e342632e1a454 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 10 Apr 2026 17:41:27 +0100 Subject: [PATCH 11/11] Update bitmapPng.cpp update bitmapPng reading from libpng to have context in warnings and errors, also add a bit of a better structure around the read and write functions. Interlaced PNG's were never being accounted for before so add the png_set_interlace_handling for interlaced pngs --- .../source/gfx/bitmap/loaders/bitmapPng.cpp | 861 ++++++++---------- 1 file changed, 390 insertions(+), 471 deletions(-) diff --git a/Engine/source/gfx/bitmap/loaders/bitmapPng.cpp b/Engine/source/gfx/bitmap/loaders/bitmapPng.cpp index 60a5e5965..83b60edfb 100644 --- a/Engine/source/gfx/bitmap/loaders/bitmapPng.cpp +++ b/Engine/source/gfx/bitmap/loaders/bitmapPng.cpp @@ -41,15 +41,16 @@ #endif -static bool sReadPNG(const Torque::Path& path, GBitmap* bitmap); -static bool sReadStreamPNG(Stream& stream, GBitmap* bitmap, U32 len); - /// Compression levels for PNGs range from 0-9. /// A value outside that range will cause the write routine to look for the best compression for a given PNG. This can be slow. - +static bool sReadPNG(const Torque::Path& path, GBitmap* bitmap); +static bool sReadStreamPNG(Stream& stream, GBitmap* bitmap, U32 len); static bool sWritePNG(const Torque::Path& path, GBitmap* bitmap, U32 compressionLevel); static bool sWriteStreamPNG(const String& bmType, Stream& stream, GBitmap* bitmap, U32 compressionLevel); -static bool _writePNG(GBitmap *bitmap, Stream &stream, U32 compressionLevel, U32 strategy, U32 filter); + +// Internal functions used. +static bool _readStreamPNG(Stream& stream, GBitmap* bitmap, U32 len, const char* filename); +static bool _writePNG(GBitmap *bitmap, Stream &stream, U32 compressionLevel, U32 strategy, U32 filter, const char* filename = "unknown"); static struct _privateRegisterPNG { @@ -72,425 +73,394 @@ static struct _privateRegisterPNG } } sStaticRegisterPNG; - -//-------------------------------------- Replacement I/O for standard LIBPng -// functions. we don't wanna use -// FILE*'s... -static void pngReadDataFn(png_structp png_ptr, - png_bytep data, - png_size_t length) +//----------------------------------------------------------------------------- +// PNG context passed to libpng for error reporting +//----------------------------------------------------------------------------- +struct PngContext { - AssertFatal(png_get_io_ptr(png_ptr) != NULL, "No stream?"); + const char* filename; + explicit PngContext(const char* f) : filename(f) {} +}; - Stream *strm = (Stream*)png_get_io_ptr(png_ptr); - bool success = strm->read(length, data); - AssertFatal(success, "pngReadDataFn - failed to read from stream!"); +//----------------------------------------------------------------------------- +// libpng callbacks +//----------------------------------------------------------------------------- +static void pngReadDataFn(png_structp png_ptr, png_bytep data, png_size_t length) +{ + Stream* strm = (Stream*)png_get_io_ptr(png_ptr); + AssertFatal(strm, "pngReadDataFn - No stream."); + if (!strm->read(length, data)) + Con::errorf("pngReadDataFn - Failed to read %u bytes from stream.", (U32)length); } - -//-------------------------------------- -static void pngWriteDataFn(png_structp png_ptr, - png_bytep data, - png_size_t length) +static void pngWriteDataFn(png_structp png_ptr, png_bytep data, png_size_t length) { - AssertFatal(png_get_io_ptr(png_ptr) != NULL, "No stream?"); - - Stream *strm = (Stream*)png_get_io_ptr(png_ptr); - bool success = strm->write(length, data); - AssertFatal(success, "pngWriteDataFn - failed to write to stream!"); + Stream* strm = (Stream*)png_get_io_ptr(png_ptr); + AssertFatal(strm, "pngWriteDataFn - No stream."); + if (!strm->write(length, data)) + Con::errorf("pngWriteDataFn - Failed to write %u bytes to stream.", (U32)length); } +static void pngFlushDataFn(png_structp /*png_ptr*/) {} -//-------------------------------------- -static void pngFlushDataFn(png_structp /*png_ptr*/) +static png_voidp pngMallocFn(png_structp /*png_ptr*/, png_size_t size) { return FrameAllocator::alloc(size); } +static void pngFreeFn(png_structp /*png_ptr*/, png_voidp /*mem*/) {} + +static png_voidp pngRealMallocFn(png_structp /*png_ptr*/, png_size_t size) { return (png_voidp)dMalloc(size); } +static void pngRealFreeFn(png_structp /*png_ptr*/, png_voidp mem) { dFree(mem); } + +static void pngFatalErrorFn(png_structp png_ptr, png_const_charp pMessage) { - // -} - -static png_voidp pngMallocFn(png_structp /*png_ptr*/, png_size_t size) -{ - return FrameAllocator::alloc(size); -} - -static void pngFreeFn(png_structp /*png_ptr*/, png_voidp /*mem*/) -{ -} - -static png_voidp pngRealMallocFn(png_structp /*png_ptr*/, png_size_t size) -{ - return (png_voidp)dMalloc(size); -} - -static void pngRealFreeFn(png_structp /*png_ptr*/, png_voidp mem) -{ - dFree(mem); -} - -//-------------------------------------- -static void pngFatalErrorFn(png_structp /*png_ptr*/, - png_const_charp pMessage) -{ - AssertISV(false, avar("Error reading PNG file:\n %s", pMessage)); -} - - -//-------------------------------------- -static void pngWarningFn(png_structp, png_const_charp /*pMessage*/) -{ - // AssertWarn(false, avar("Warning reading PNG file:\n %s", pMessage)); -} - - -//-------------------------------------- -bool sReadPNG(const Torque::Path& path, GBitmap* bitmap) -{ - FileStream* readPng = new FileStream; - - if (!readPng->open(path.getFullPath(), Torque::FS::File::Read)) + const char* filename = "unknown"; + if (png_ptr) { - Con::printf("Failed to open PNG :%s", path.getFullFileName().c_str()); - return false; + PngContext* ctx = (PngContext*)png_get_error_ptr(png_ptr); + if (ctx && ctx->filename) + filename = ctx->filename; + } + Con::errorf("pngFatalErrorFn - Fatal error in '%s': %s", filename, pMessage); + AssertISV(false, avar("libpng fatal error in '%s':\n%s", filename, pMessage)); +} + +static void pngWarningFn(png_structp png_ptr, png_const_charp pMessage) +{ +#if TORQUE_DEBUG + const char* filename = "unknown"; + if (png_ptr) + { + PngContext* ctx = (PngContext*)png_get_error_ptr(png_ptr); + if (ctx && ctx->filename) + filename = ctx->filename; + } + Con::warnf("pngWarningFn - Warning in '%s': %s", filename, pMessage); +#endif +} + +//----------------------------------------------------------------------------- +// RAII guards for png read/write structs +//----------------------------------------------------------------------------- +struct PngReadGuard +{ + png_structp png_ptr = nullptr; + png_infop info_ptr = nullptr; + png_infop end_info = nullptr; + PngContext ctx; + + explicit PngReadGuard(const char* filename) : ctx(filename) {} + + bool init() + { + png_ptr = png_create_read_struct_2(PNG_LIBPNG_VER_STRING, + &ctx, pngFatalErrorFn, pngWarningFn, + NULL, pngRealMallocFn, pngRealFreeFn); + if (!png_ptr) + { + Con::errorf("PngReadGuard - png_create_read_struct_2 failed for '%s'.", ctx.filename); + return false; + } + + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + { + Con::errorf("PngReadGuard - png_create_info_struct (info) failed for '%s'.", ctx.filename); + return false; + } + + end_info = png_create_info_struct(png_ptr); + if (!end_info) + { + Con::errorf("PngReadGuard - png_create_info_struct (end) failed for '%s'.", ctx.filename); + return false; + } + + return true; } - if (!sReadStreamPNG(*readPng, bitmap, U32_MAX)) + ~PngReadGuard() { - Con::printf("Failed to read PNG :%s", path.getFullFileName().c_str()); - return false; + if (png_ptr) + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); } +}; - readPng->close(); - - return true; -} - -static bool sReadStreamPNG(Stream& stream, GBitmap* bitmap, U32 len) +struct PngWriteGuard { - PROFILE_SCOPE(sReadPNG); - static const U32 cs_headerBytesChecked = 8; + png_structp png_ptr = nullptr; + png_infop info_ptr = nullptr; + PngContext ctx; - U8 header[cs_headerBytesChecked]; - stream.read(cs_headerBytesChecked, header); + explicit PngWriteGuard(const char* filename) : ctx(filename) {} - bool isPng = png_check_sig(header, cs_headerBytesChecked) != 0; - if (isPng == false) + bool init(bool useFrameAllocator = true) { - AssertWarn(false, "GBitmap::readPNG: stream doesn't contain a PNG"); - return false; + png_ptr = png_create_write_struct_2(PNG_LIBPNG_VER_STRING, + &ctx, pngFatalErrorFn, pngWarningFn, + NULL, + useFrameAllocator ? pngMallocFn : pngRealMallocFn, + useFrameAllocator ? pngFreeFn : pngRealFreeFn); + if (!png_ptr) + { + Con::errorf("PngWriteGuard - png_create_write_struct_2 failed for '%s'.", ctx.filename); + return false; + } + + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + { + Con::errorf("PngWriteGuard - png_create_info_struct failed for '%s'.", ctx.filename); + return false; + } + + return true; } - U32 prevWaterMark = FrameAllocator::getWaterMark(); - png_structp png_ptr = png_create_read_struct_2(PNG_LIBPNG_VER_STRING, - NULL, - pngFatalErrorFn, - pngWarningFn, - NULL, - pngRealMallocFn, - pngRealFreeFn); - - if (png_ptr == NULL) + ~PngWriteGuard() { - FrameAllocator::setWaterMark(prevWaterMark); - return false; + if (png_ptr) + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); } +}; - png_infop info_ptr = png_create_info_struct(png_ptr); - if (info_ptr == NULL) - { - png_destroy_read_struct(&png_ptr, - (png_infopp)NULL, - (png_infopp)NULL); +//----------------------------------------------------------------------------- +// Shared helpers +//----------------------------------------------------------------------------- +static GFXFormat _determineReadFormat(png_structp png_ptr, png_infop info_ptr, bool& transAlpha) +{ + S32 bit_depth, color_type; + png_uint_32 width, height; + png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL); - FrameAllocator::setWaterMark(prevWaterMark); - return false; - } - - png_infop end_info = png_create_info_struct(png_ptr); - if (end_info == NULL) - { - png_destroy_read_struct(&png_ptr, - &info_ptr, - (png_infopp)NULL); - - FrameAllocator::setWaterMark(prevWaterMark); - return false; - } - - png_set_read_fn(png_ptr, &stream, pngReadDataFn); - - // Read off the info on the image. - png_set_sig_bytes(png_ptr, cs_headerBytesChecked); - png_read_info(png_ptr, info_ptr); - - // OK, at this point, if we have reached it ok, then we can reset the - // image to accept the new data... - // - bitmap->deleteImage(); - - png_uint_32 width; - png_uint_32 height; - S32 bit_depth; - S32 color_type; - - png_get_IHDR(png_ptr, info_ptr, - &width, &height, // obv. - &bit_depth, &color_type, // obv. - NULL, // interlace - NULL, // compression_type - NULL); // filter_type - - // First, handle the color transformations. We need this to read in the - // data as RGB or RGBA, _always_, with a maximal channel width of 8 bits. - // - bool transAlpha = false; + transAlpha = false; GFXFormat format = GFXFormatR8G8B8; - // Strip off any 16 bit info - // - if (bit_depth == 16 && color_type != PNG_COLOR_TYPE_GRAY) - { + if (bit_depth == 16 && color_type != PNG_COLOR_TYPE_GRAY) png_set_strip_16(png_ptr); - } - // Expand a transparency channel into a full alpha channel... - // - if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { png_set_expand(png_ptr); transAlpha = true; } - if (color_type == PNG_COLOR_TYPE_PALETTE) + switch (color_type) { + case PNG_COLOR_TYPE_PALETTE: png_set_expand(png_ptr); format = transAlpha ? GFXFormatR8G8B8A8 : GFXFormatR8G8B8; - } - else if (color_type == PNG_COLOR_TYPE_GRAY) - { + break; + case PNG_COLOR_TYPE_GRAY: png_set_expand(png_ptr); - - if (bit_depth == 16) - format = GFXFormatR5G6B5; - else - format = GFXFormatA8; - } - else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) - { + format = (bit_depth == 16) ? GFXFormatR5G6B5 : GFXFormatA8; + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: png_set_expand(png_ptr); png_set_gray_to_rgb(png_ptr); format = GFXFormatR8G8B8A8; - } - else if (color_type == PNG_COLOR_TYPE_RGB) - { - format = transAlpha ? GFXFormatR8G8B8A8 : GFXFormatR8G8B8; + break; + case PNG_COLOR_TYPE_RGB: png_set_expand(png_ptr); - } - else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA) - { + format = transAlpha ? GFXFormatR8G8B8A8 : GFXFormatR8G8B8; + break; + case PNG_COLOR_TYPE_RGB_ALPHA: png_set_expand(png_ptr); format = GFXFormatR8G8B8A8; + break; + default: + Con::errorf("_determineReadFormat - Unrecognised color_type %d.", color_type); + break; } - // Update the info pointer with the result of the transformations - // above... - png_read_update_info(png_ptr, info_ptr); + return format; +} - png_uint_32 rowBytes = png_get_rowbytes(png_ptr, info_ptr); - if (format == GFXFormatR8G8B8) +static bool _validateRowBytes(const char* filename, GFXFormat format, U32 rowBytes, U32 width) +{ + const U32 expected = + (format == GFXFormatR8G8B8) ? width * 3 : + (format == GFXFormatR8G8B8A8) ? width * 4 : + (format == GFXFormatR5G6B5) ? width * 2 : 0; + + if (expected && rowBytes != expected) { - AssertFatal(rowBytes == width * 3, - "Error, our rowbytes are incorrect for this transform... (3)"); + Con::errorf("_validateRowBytes - '%s': rowBytes %d != expected %d for format %d.", + filename, rowBytes, expected, format); + return false; } - else if (format == GFXFormatR8G8B8A8) + return true; +} + +static void _applyWriteIHDR(png_structp png_ptr, png_infop info_ptr, GFXFormat format, U32 width, U32 height) +{ + switch (format) { - AssertFatal(rowBytes == width * 4, - "Error, our rowbytes are incorrect for this transform... (4)"); + case GFXFormatR8G8B8: + png_set_IHDR(png_ptr, info_ptr, width, height, 8, + PNG_COLOR_TYPE_RGB, NULL, NULL, NULL); + break; + case GFXFormatR8G8B8A8: + case GFXFormatR8G8B8X8: + case GFXFormatR8G8B8A8_LINEAR_FORCE: + png_set_IHDR(png_ptr, info_ptr, width, height, 8, + PNG_COLOR_TYPE_RGB_ALPHA, NULL, NULL, NULL); + break; + case GFXFormatA8: + png_set_IHDR(png_ptr, info_ptr, width, height, 8, + PNG_COLOR_TYPE_GRAY, NULL, NULL, NULL); + break; + case GFXFormatR5G6B5: + { + png_set_IHDR(png_ptr, info_ptr, width, height, 16, + PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + png_color_8_struct sigBit = { 0 }; + sigBit.gray = 16; + png_set_sBIT(png_ptr, info_ptr, &sigBit); + png_set_swap(png_ptr); + break; } - else if (format == GFXFormatR5G6B5) + default: + break; + } +} + +static bool _isSupportedWriteFormat(GFXFormat format) +{ + switch (format) { - AssertFatal(rowBytes == width * 2, - "Error, our rowbytes are incorrect for this transform... (2)"); + case GFXFormatR8G8B8: + case GFXFormatR8G8B8A8: + case GFXFormatR8G8B8X8: + case GFXFormatR8G8B8A8_LINEAR_FORCE: + case GFXFormatA8: + case GFXFormatR5G6B5: + return true; + default: + return false; + } +} + +//----------------------------------------------------------------------------- +// Core read / write +//----------------------------------------------------------------------------- +static bool _readStreamPNG(Stream& stream, GBitmap* bitmap, U32 /*len*/, const char* filename = "unknown") +{ + PROFILE_SCOPE(sReadPNG); + + static const U32 cs_headerBytesChecked = 8; + U8 header[cs_headerBytesChecked]; + stream.read(cs_headerBytesChecked, header); + + if (!png_check_sig(header, cs_headerBytesChecked)) + { + Con::errorf("_readStreamPNG - '%s' does not have a valid PNG signature.", filename); + return false; } - // actually allocate the bitmap space... - bitmap->allocateBitmap(width, height, - false, // don't extrude miplevels... - format); // use determined format... + U32 prevWaterMark = FrameAllocator::getWaterMark(); - // Set up the row pointers... - png_bytep* rowPointers = new png_bytep[ height ]; + PngReadGuard guard(filename); + if (!guard.init()) + { + FrameAllocator::setWaterMark(prevWaterMark); + return false; + } + + png_set_read_fn(guard.png_ptr, &stream, pngReadDataFn); + png_set_sig_bytes(guard.png_ptr, cs_headerBytesChecked); + png_read_info(guard.png_ptr, guard.info_ptr); + + bool transAlpha = false; + GFXFormat format = _determineReadFormat(guard.png_ptr, guard.info_ptr, transAlpha); + + png_uint_32 number_of_passes = png_set_interlace_handling(guard.png_ptr); + png_read_update_info(guard.png_ptr, guard.info_ptr); + + png_uint_32 width = png_get_image_width(guard.png_ptr, guard.info_ptr); + png_uint_32 height = png_get_image_height(guard.png_ptr, guard.info_ptr); + png_uint_32 rowBytes = png_get_rowbytes(guard.png_ptr, guard.info_ptr); + + if (!_validateRowBytes(filename, format, rowBytes, width)) + { + FrameAllocator::setWaterMark(prevWaterMark); + return false; + } + + bitmap->deleteImage(); + bitmap->allocateBitmap(width, height, false, format); + + png_bytep* rowPointers = new png_bytep[height]; U8* pBase = (U8*)bitmap->getBits(); - for (U32 i = 0; i < height; i++) rowPointers[i] = pBase + (i * rowBytes); - // And actually read the image! - png_read_image(png_ptr, rowPointers); + png_read_image(guard.png_ptr, rowPointers); + delete[] rowPointers; - // We're outta here, destroy the png structs, and release the lock - // as quickly as possible... - //png_read_end(png_ptr, end_info); - delete [] rowPointers; - png_read_end(png_ptr, NULL); - png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + png_read_end(guard.png_ptr, NULL); - // Ok, the image is read in, now we need to finish up the initialization, - // which means: setting up the detailing members, init'ing the palette - // key, etc... - // - // actually, all of that was handled by allocateBitmap, so we're outta here - // - - // Check this bitmap for transparency bitmap->checkForTransparency(); - FrameAllocator::setWaterMark(prevWaterMark); - return true; } -//-------------------------------------------------------------------------- -static bool _writePNG(GBitmap *bitmap, Stream &stream, U32 compressionLevel, U32 strategy, U32 filter) +static bool _writePNG(GBitmap* bitmap, Stream& stream, U32 compressionLevel, U32 strategy, U32 filter, const char* filename) { - GFXFormat format = bitmap->getFormat(); + GFXFormat format = bitmap->getFormat(); - // ONLY RGB bitmap writing supported at this time! - AssertFatal( format == GFXFormatR8G8B8 || - format == GFXFormatR8G8B8A8 || - format == GFXFormatR8G8B8X8 || - format == GFXFormatA8 || - format == GFXFormatR5G6B5 || - format == GFXFormatR8G8B8A8_LINEAR_FORCE, "_writePNG: ONLY RGB bitmap writing supported at this time."); - - if ( format != GFXFormatR8G8B8 && - format != GFXFormatR8G8B8A8 && - format != GFXFormatR8G8B8X8 && - format != GFXFormatA8 && - format != GFXFormatR5G6B5 && format != GFXFormatR8G8B8A8_LINEAR_FORCE) - return false; - - png_structp png_ptr = png_create_write_struct_2(PNG_LIBPNG_VER_STRING, - NULL, - pngFatalErrorFn, - pngWarningFn, - NULL, - pngMallocFn, - pngFreeFn); - if (png_ptr == NULL) - return (false); - - png_infop info_ptr = png_create_info_struct(png_ptr); - if (info_ptr == NULL) + if (!_isSupportedWriteFormat(format)) { - png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + Con::errorf("_writePNG - '%s': Unsupported format %d.", filename, format); return false; } - png_set_write_fn(png_ptr, &stream, pngWriteDataFn, pngFlushDataFn); + PngWriteGuard guard(filename); + if (!guard.init(true)) + return false; - // Set the compression level and image filters - png_set_compression_window_bits(png_ptr, 15); - png_set_compression_level(png_ptr, compressionLevel); - png_set_filter(png_ptr, 0, filter); + png_set_write_fn(guard.png_ptr, &stream, pngWriteDataFn, pngFlushDataFn); + png_set_compression_window_bits(guard.png_ptr, 15); + png_set_compression_level(guard.png_ptr, compressionLevel); + png_set_filter(guard.png_ptr, 0, filter); - // Set the image information here. Width and height are up to 2^31, - // bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on - // the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY, - // PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB, - // or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or - // PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST - // currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED + _applyWriteIHDR(guard.png_ptr, guard.info_ptr, format, bitmap->getWidth(), bitmap->getHeight()); + png_write_info(guard.png_ptr, guard.info_ptr); - U32 width = bitmap->getWidth(); - U32 height = bitmap->getHeight(); - - if (format == GFXFormatR8G8B8) - { - png_set_IHDR(png_ptr, info_ptr, - width, height, // the width & height - 8, PNG_COLOR_TYPE_RGB, // bit_depth, color_type, - NULL, // no interlace - NULL, // compression type - NULL); // filter type - } - else if (format == GFXFormatR8G8B8A8 || format == GFXFormatR8G8B8X8 || format == GFXFormatR8G8B8A8_LINEAR_FORCE) - { - png_set_IHDR(png_ptr, info_ptr, - width, height, // the width & height - 8, PNG_COLOR_TYPE_RGB_ALPHA, // bit_depth, color_type, - NULL, // no interlace - NULL, // compression type - NULL); // filter type - } - else if (format == GFXFormatA8) - { - png_set_IHDR(png_ptr, info_ptr, - width, height, // the width & height - 8, PNG_COLOR_TYPE_GRAY, // bit_depth, color_type, - NULL, // no interlace - NULL, // compression type - NULL); // filter type - } - else if (format == GFXFormatR5G6B5) - { - png_set_IHDR(png_ptr, info_ptr, - width, height, // the width & height - 16, PNG_COLOR_TYPE_GRAY, // bit_depth, color_type, - PNG_INTERLACE_NONE, // no interlace - PNG_COMPRESSION_TYPE_DEFAULT, // compression type - PNG_FILTER_TYPE_DEFAULT); // filter type - - png_color_8_struct sigBit = { 0 }; - sigBit.gray = 16; - png_set_sBIT(png_ptr, info_ptr, &sigBit ); - - png_set_swap( png_ptr ); - } - - png_write_info(png_ptr, info_ptr); FrameAllocatorMarker marker; - png_bytep* row_pointers = (png_bytep*)marker.alloc( height * sizeof( png_bytep ) ); - for (U32 i=0; i(bitmap->getAddress(0, i)); - - png_write_image(png_ptr, row_pointers); - - // Write S3TC data if present... - // Write FXT1 data if present... - - png_write_end(png_ptr, info_ptr); - png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + const U32 height = bitmap->getHeight(); + png_bytep* rowPointers = (png_bytep*)marker.alloc(height * sizeof(png_bytep)); + for (U32 i = 0; i < height; i++) + rowPointers[i] = const_cast(bitmap->getAddress(0, i)); + png_write_image(guard.png_ptr, rowPointers); + png_write_end(guard.png_ptr, guard.info_ptr); return true; } - -//-------------------------------------------------------------------------- -bool sWritePNG(const Torque::Path& path, GBitmap* bitmap, U32 compressionLevel) +//----------------------------------------------------------------------------- +// Public interface +//----------------------------------------------------------------------------- +static bool sReadPNG(const Torque::Path& path, GBitmap* bitmap) { - FileStream* writePng = new FileStream; - - if (!writePng->open(path.getFullPath(), Torque::FS::File::Write)) + FileStream readPng; + if (!readPng.open(path.getFullPath(), Torque::FS::File::Read)) { - Con::printf("Failed to open PNG :%s", path.getFullFileName().c_str()); + Con::errorf("sReadPNG - Failed to open '%s'.", path.getFullPath().c_str()); return false; } - if (!sWriteStreamPNG("png", *writePng, bitmap, compressionLevel)) - { - Con::printf("Failed to write PNG :%s", path.getFullFileName().c_str()); - return false; - } + bool result = _readStreamPNG(readPng, bitmap, U32_MAX, path.getFullPath().c_str()); + if (!result) + Con::errorf("sReadPNG - Failed to decode '%s'.", path.getFullPath().c_str()); - writePng->close(); - - return true; + readPng.close(); + return result; } -static bool sWriteStreamPNG(const String& bmType, Stream& stream, GBitmap* bitmap, U32 compressionLevel) +static bool sWriteStreamPNG(const String& /*bmType*/, Stream& stream, GBitmap* bitmap, U32 compressionLevel) { U32 waterMark = FrameAllocator::getWaterMark(); - if ( compressionLevel < 10 ) + if (compressionLevel < 10) { bool retVal = _writePNG(bitmap, stream, compressionLevel, 0, PNG_ALL_FILTERS); FrameAllocator::setWaterMark(waterMark); @@ -501,195 +471,144 @@ static bool sWriteStreamPNG(const String& bmType, Stream& stream, GBitmap* bitma U8* buffer = new U8[1 << 22]; // 4 Megs. Should be enough... MemStream* pMemStream = new MemStream(1 << 22, buffer, false, true); - const U32 zStrategies[] = { Z_DEFAULT_STRATEGY, - Z_FILTERED }; - const U32 pngFilters[] = { PNG_FILTER_NONE, - PNG_FILTER_SUB, - PNG_FILTER_UP, - PNG_FILTER_AVG, - PNG_FILTER_PAETH, - PNG_ALL_FILTERS }; + const U32 zStrategies[] = { Z_DEFAULT_STRATEGY, Z_FILTERED }; + const U32 pngFilters[] = { PNG_FILTER_NONE, PNG_FILTER_SUB, PNG_FILTER_UP, + PNG_FILTER_AVG, PNG_FILTER_PAETH, PNG_ALL_FILTERS }; - U32 minSize = 0xFFFFFFFF; - U32 bestStrategy = 0xFFFFFFFF; - U32 bestFilter = 0xFFFFFFFF; - U32 bestCLevel = 0xFFFFFFFF; + U32 minSize = U32_MAX, bestCLevel = 0, bestStrategy = 0, bestFilter = 0; - for (U32 cl = 0; cl <=9; cl++) + for (U32 cl = 0; cl <= 9; cl++) { - for (U32 zs = 0; zs < 2; zs++) + for (U32 zs = 0; zs < 2; zs++) { - for (U32 pf = 0; pf < 6; pf++) + for (U32 pf = 0; pf < 6; pf++) { pMemStream->setPosition(0); - U32 waterMarkInner = FrameAllocator::getWaterMark(); - if (_writePNG(bitmap, *pMemStream, cl, zStrategies[zs], pngFilters[pf]) == false) - AssertFatal(false, "Handle this error!"); + if (!_writePNG(bitmap, *pMemStream, cl, zStrategies[zs], pngFilters[pf])) + { + Con::errorf("sWriteStreamPNG - Compression search failed at cl=%d zs=%d pf=%d.", cl, zs, pf); + FrameAllocator::setWaterMark(waterMarkInner); + continue; + } FrameAllocator::setWaterMark(waterMarkInner); - if (pMemStream->getPosition() < minSize) + if (pMemStream->getPosition() < minSize) { minSize = pMemStream->getPosition(); - bestStrategy = zs; - bestFilter = pf; - bestCLevel = cl; + bestCLevel = cl; bestStrategy = zs; bestFilter = pf; } } } } - AssertFatal(minSize != 0xFFFFFFFF, "Error, no best found?"); delete pMemStream; - delete [] buffer; + delete[] buffer; + if (minSize == U32_MAX) + { + Con::errorf("sWriteStreamPNG - No valid compression result found."); + FrameAllocator::setWaterMark(waterMark); + return false; + } - bool retVal = _writePNG(bitmap, stream, - bestCLevel, - zStrategies[bestStrategy], - pngFilters[bestFilter]); + bool retVal = _writePNG(bitmap, stream, bestCLevel, zStrategies[bestStrategy], pngFilters[bestFilter]); FrameAllocator::setWaterMark(waterMark); - return retVal; } -//-------------------------------------------------------------------------- -// Stores PNG stream data -struct DeferredPNGWriterData { - png_structp png_ptr; - png_infop info_ptr; - U32 width; - U32 height; -}; -DeferredPNGWriter::DeferredPNGWriter() : - mData( NULL ), - mActive(false) +static bool sWritePNG(const Torque::Path& path, GBitmap* bitmap, U32 compressionLevel) { - mData = new DeferredPNGWriterData(); + FileStream writePng; + if (!writePng.open(path.getFullPath(), Torque::FS::File::Write)) + { + Con::errorf("sWritePNG - Failed to open '%s' for writing.", path.getFullPath().c_str()); + return false; + } + + bool retVal = sWriteStreamPNG("png", writePng, bitmap, compressionLevel); + if (!retVal) + Con::errorf("sWritePNG - Failed to encode '%s'. Format: %d, Size: %dx%d.", + path.getFullPath().c_str(), bitmap->getFormat(), bitmap->getWidth(), bitmap->getHeight()); + + writePng.close(); + return retVal; } + +static bool sReadStreamPNG(Stream& stream, GBitmap* bitmap, U32 len) +{ + return _readStreamPNG(stream, bitmap, len, "unknown (stream only)"); +} + +//----------------------------------------------------------------------------- +// DeferredPNGWriter +//----------------------------------------------------------------------------- +struct DeferredPNGWriterData +{ + PngWriteGuard guard; + U32 width = 0; + U32 height = 0; + + explicit DeferredPNGWriterData(const char* filename) : guard(filename) {} +}; + +DeferredPNGWriter::DeferredPNGWriter() : mData(nullptr), mActive(false) {} + DeferredPNGWriter::~DeferredPNGWriter() { delete mData; } -bool DeferredPNGWriter::begin( GFXFormat format, S32 width, S32 height, Stream &stream, U32 compressionLevel ) -{ - // ONLY RGB bitmap writing supported at this time! - AssertFatal( format == GFXFormatR8G8B8 || - format == GFXFormatR8G8B8A8 || - format == GFXFormatR8G8B8X8 || - format == GFXFormatA8 || - format == GFXFormatR5G6B5, "_writePNG: ONLY RGB bitmap writing supported at this time."); - - if ( format != GFXFormatR8G8B8 && - format != GFXFormatR8G8B8A8 && - format != GFXFormatR8G8B8X8 && - format != GFXFormatA8 && - format != GFXFormatR5G6B5 ) - return false; - - mData->png_ptr = png_create_write_struct_2(PNG_LIBPNG_VER_STRING, - NULL, - pngFatalErrorFn, - pngWarningFn, - NULL, - pngRealMallocFn, - pngRealFreeFn); - if (mData->png_ptr == NULL) - return (false); - - mData->info_ptr = png_create_info_struct(mData->png_ptr); - if (mData->info_ptr == NULL) +bool DeferredPNGWriter::begin(GFXFormat format, S32 width, S32 height, Stream& stream, U32 compressionLevel) +{ + if (!_isSupportedWriteFormat(format)) { - png_destroy_write_struct(&mData->png_ptr, (png_infopp)NULL); + Con::errorf("DeferredPNGWriter::begin - Unsupported format %d.", format); return false; } - png_set_write_fn(mData->png_ptr, &stream, pngWriteDataFn, pngFlushDataFn); + mData = new DeferredPNGWriterData("DeferredPNGWriter"); - // Set the compression level and image filters - png_set_compression_window_bits(mData->png_ptr, 15); - png_set_compression_level(mData->png_ptr, compressionLevel); - png_set_filter(mData->png_ptr, 0, PNG_ALL_FILTERS); - - // Set the image information here. Width and height are up to 2^31, - // bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on - // the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY, - // PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB, - // or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or - // PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST - // currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED - - if (format == GFXFormatR8G8B8) + if (!mData->guard.init(false)) { - png_set_IHDR(mData->png_ptr, mData->info_ptr, - width, height, // the width & height - 8, PNG_COLOR_TYPE_RGB, // bit_depth, color_type, - NULL, // no interlace - NULL, // compression type - NULL); // filter type - } - else if (format == GFXFormatR8G8B8A8 || format == GFXFormatR8G8B8X8) - { - png_set_IHDR(mData->png_ptr, mData->info_ptr, - width, height, // the width & height - 8, PNG_COLOR_TYPE_RGB_ALPHA, // bit_depth, color_type, - NULL, // no interlace - NULL, // compression type - NULL); // filter type - } - else if (format == GFXFormatA8) - { - png_set_IHDR(mData->png_ptr, mData->info_ptr, - width, height, // the width & height - 8, PNG_COLOR_TYPE_GRAY, // bit_depth, color_type, - NULL, // no interlace - NULL, // compression type - NULL); // filter type - } - else if (format == GFXFormatR5G6B5) - { - png_set_IHDR(mData->png_ptr, mData->info_ptr, - width, height, // the width & height - 16, PNG_COLOR_TYPE_GRAY, // bit_depth, color_type, - PNG_INTERLACE_NONE, // no interlace - PNG_COMPRESSION_TYPE_DEFAULT, // compression type - PNG_FILTER_TYPE_DEFAULT); // filter type - - png_color_8_struct sigBit = { 0 }; - sigBit.gray = 16; - png_set_sBIT(mData->png_ptr, mData->info_ptr, &sigBit ); - - png_set_swap( mData->png_ptr ); + Con::errorf("DeferredPNGWriter::begin - Failed to init PNG write structs. Format: %d, Size: %dx%d.", format, width, height); + return false; } - png_write_info(mData->png_ptr, mData->info_ptr); - + mData->width = width; + mData->height = height; + + png_set_write_fn(mData->guard.png_ptr, &stream, pngWriteDataFn, pngFlushDataFn); + png_set_compression_window_bits(mData->guard.png_ptr, 15); + png_set_compression_level(mData->guard.png_ptr, compressionLevel); + png_set_filter(mData->guard.png_ptr, 0, PNG_ALL_FILTERS); + + _applyWriteIHDR(mData->guard.png_ptr, mData->guard.info_ptr, format, width, height); + png_write_info(mData->guard.png_ptr, mData->guard.info_ptr); + mActive = true; - return true; } -void DeferredPNGWriter::append( GBitmap* bitmap, U32 rows) + +void DeferredPNGWriter::append(GBitmap* bitmap, U32 rows) { - AssertFatal(mActive, "Cannot append to an inactive DeferredPNGWriter!"); - - U32 height = getMin( bitmap->getHeight(), rows); + AssertFatal(mActive, "DeferredPNGWriter::append - Writer is not active."); + U32 height = getMin(bitmap->getHeight(), rows); FrameAllocatorMarker marker; - png_bytep* row_pointers = (png_bytep*)marker.alloc( height * sizeof( png_bytep ) ); - for (U32 i=0; i(bitmap->getAddress(0, i)); - png_write_rows(mData->png_ptr, row_pointers, height); + png_write_rows(mData->guard.png_ptr, row_pointers, height); } + void DeferredPNGWriter::end() { - AssertFatal(mActive, "Cannot end an inactive DeferredPNGWriter!"); - - png_write_end(mData->png_ptr, mData->info_ptr); - png_destroy_write_struct(&mData->png_ptr, (png_infopp)NULL); + AssertFatal(mActive, "DeferredPNGWriter::end - Writer is not active."); + png_write_end(mData->guard.png_ptr, mData->guard.info_ptr); mActive = false; }