99using Prowl . Editor . Thumbnails ;
1010using Prowl . Editor . Importers ;
1111using Prowl . Runtime ;
12- using Prowl . Editor . GUI . Panels ;
1312using Prowl . Editor . Projects . Scripting ;
1413using Prowl . Editor . Projects ;
1514
@@ -30,6 +29,9 @@ public class EditorAssetDatabase : IAssetDatabase
3029 private readonly Dictionary < string , Guid > _pathToGuid = new ( StringComparer . OrdinalIgnoreCase ) ;
3130 private readonly ConcurrentDictionary < Guid , EngineObject > _loadedAssets = new ( ) ;
3231 private readonly ConcurrentDictionary < Guid , ( Guid parentGuid , int index ) > _subAssetIndex = new ( ) ;
32+ // GPU-uploaded thumbnail cache. Main-thread-only (texture creation isn't thread-safe), same
33+ // as _pathToGuid every UI that shows asset thumbnails shares this instead of keeping its own.
34+ private readonly Dictionary < Guid , Runtime . Resources . Texture2D ? > _thumbnailTextures = new ( ) ;
3335 // Per-thread re-entrancy guard that breaks dependency/sub-asset cycles during deserialize.
3436 [ ThreadStatic ] private static HashSet < Guid > ? _loadingStack ;
3537 // Serializes the deserialize body so concurrent loads of the same asset collapse onto one.
@@ -628,6 +630,7 @@ private bool RunImport(AssetEntry entry)
628630 _subAssetIndex . TryRemove ( oldGuid , out _ ) ;
629631 if ( _loadedAssets . TryRemove ( oldGuid , out var oldObj ) ) oldObj ? . Dispose ( ) ;
630632 ThumbnailGenerator . DeleteThumbnail ( oldGuid , _project . ThumbnailsPath ) ;
633+ InvalidateThumbnailTexture ( oldGuid ) ;
631634 string oldCache = GetCachePath ( oldGuid ) ;
632635 if ( File . Exists ( oldCache ) ) try { File . Delete ( oldCache ) ; } catch { }
633636 }
@@ -643,10 +646,12 @@ private bool RunImport(AssetEntry entry)
643646 // Update dependency graph
644647 _dependencies . SetDependencies ( entry . Guid , ctx . Dependencies ) ;
645648
646- // Queue thumbnail generation (lazy, one per frame)
649+ // Queue thumbnail generation (lazy, one per frame) and drop any cached GPU texture for
650+ // the old content so the next access rebuilds it from the freshly generated thumbnail.
647651 {
648652 string ? sourceFile = ctx . MainAsset is Runtime . Resources . Texture2D ? absolutePath : null ;
649653 ThumbnailGenerator . Enqueue ( entry . Guid , ctx . MainAsset , sourceFile ) ;
654+ InvalidateThumbnailTexture ( entry . Guid ) ;
650655 }
651656
652657 return true ;
@@ -810,6 +815,57 @@ internal static void ClearScenesAndPrefabForReload()
810815 /// <summary>Load a cached thumbnail for an asset. Returns (width, height, pixels) or null.</summary>
811816 public ( int width , int height , byte [ ] pixels ) ? LoadThumbnail ( Guid guid ) => ThumbnailGenerator . LoadThumbnail ( guid , _project . ThumbnailsPath ) ;
812817
818+ /// <summary>
819+ /// Resolve an asset GUID to its cached GPU thumbnail texture, building it from the on-disk
820+ /// pixel cache (<see cref="LoadThumbnail"/>) on first use. Returns null when no thumbnail
821+ /// exists yet. Shared by every UI that shows asset thumbnails (Project panel, asset pickers,
822+ /// Terrain layer tiles, etc.) so there's one GPU copy per asset, invalidated automatically
823+ /// whenever this database reimports or deletes that asset.
824+ /// </summary>
825+ public Runtime . Resources . Texture2D ? GetThumbnailTexture ( Guid guid )
826+ {
827+ if ( guid == Guid . Empty ) return null ;
828+
829+ if ( _thumbnailTextures . TryGetValue ( guid , out var cached ) )
830+ return cached ;
831+
832+ var thumb = LoadThumbnail ( guid ) ;
833+ if ( thumb == null ) return null ;
834+
835+ try
836+ {
837+ var ( w , h , pixels ) = thumb . Value ;
838+ var tex = new Runtime . Resources . Texture2D ( ( uint ) w , ( uint ) h , false , TextureImageFormat . Color4b ) ;
839+ tex . SetData < byte > ( pixels ) ;
840+ tex . SetTextureFilters ( TextureMin . Linear , TextureMag . Linear ) ;
841+ _thumbnailTextures [ guid ] = tex ;
842+ return tex ;
843+ }
844+ catch
845+ {
846+ _thumbnailTextures [ guid ] = null ;
847+ return null ;
848+ }
849+ }
850+
851+ /// <summary>Dispose and drop a single cached thumbnail texture so it's rebuilt from disk on next access.</summary>
852+ public void InvalidateThumbnailTexture ( Guid guid )
853+ {
854+ if ( _thumbnailTextures . TryGetValue ( guid , out var tex ) )
855+ {
856+ tex ? . Dispose ( ) ;
857+ _thumbnailTextures . Remove ( guid ) ;
858+ }
859+ }
860+
861+ /// <summary>Dispose and clear every cached thumbnail texture (e.g. after the thumbnail size setting changes).</summary>
862+ public void ClearThumbnailTextureCache ( )
863+ {
864+ foreach ( var tex in _thumbnailTextures . Values )
865+ tex ? . Dispose ( ) ;
866+ _thumbnailTextures . Clear ( ) ;
867+ }
868+
813869 // ================================================================
814870 // Asset CRUD
815871 // ================================================================
@@ -962,12 +1018,14 @@ public void DeleteAsset(string relativePath)
9621018 var entry = _guidToEntry . TryGetValue ( guid , out var e ) ? e : null ;
9631019 DisposeAndRemove ( guid ) ;
9641020 ThumbnailGenerator . DeleteThumbnail ( guid , _project . ThumbnailsPath ) ;
1021+ InvalidateThumbnailTexture ( guid ) ;
9651022 if ( entry ? . SubAssets != null )
9661023 foreach ( var sub in entry . SubAssets )
9671024 {
9681025 DisposeAndRemove ( sub . Guid ) ;
9691026 _subAssetIndex . TryRemove ( sub . Guid , out _ ) ;
9701027 ThumbnailGenerator . DeleteThumbnail ( sub . Guid , _project . ThumbnailsPath ) ;
1028+ InvalidateThumbnailTexture ( sub . Guid ) ;
9711029
9721030 // Clean sub-asset cache file
9731031 string subCachePath = GetCachePath ( sub . Guid ) ;
@@ -1192,14 +1250,14 @@ public void Reimport(Guid guid)
11921250 foreach ( var sub in entry . SubAssets )
11931251 DisposeAndRemove ( sub . Guid ) ;
11941252
1195- // Clear old thumbnails and invalidate UI cache
1253+ // Clear old thumbnails and invalidate the cached GPU texture
11961254 ThumbnailGenerator . DeleteThumbnail ( guid , _project . ThumbnailsPath ) ;
1197- ProjectPanel . InvalidateThumbnail ( guid ) ;
1255+ InvalidateThumbnailTexture ( guid ) ;
11981256 if ( entry . SubAssets != null )
11991257 foreach ( var sub in entry . SubAssets )
12001258 {
12011259 ThumbnailGenerator . DeleteThumbnail ( sub . Guid , _project . ThumbnailsPath ) ;
1202- ProjectPanel . InvalidateThumbnail ( sub . Guid ) ;
1260+ InvalidateThumbnailTexture ( sub . Guid ) ;
12031261 }
12041262
12051263 entry . NeedsReimport = true ;
@@ -1451,6 +1509,8 @@ public void Dispose()
14511509 _watcher ? . Dispose ( ) ;
14521510 _watcher = null ;
14531511
1512+ ClearThumbnailTextureCache ( ) ;
1513+
14541514 // Clear the global registrations if they still point at this instance, so a torn-down
14551515 // database (e.g. between tests) doesn't leave dangling statics behind.
14561516 if ( Instance == this ) Instance = null ;
0 commit comments