From d7b17ae0a6804f6d82a91b1bdfaba6be57078585 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 14 Dec 2023 16:05:15 +0100 Subject: [PATCH] Fix deadlock in asset thumbnails rendering queue when texture streaming fails #2057 --- .../Content/Thumbnails/ThumbnailRequest.cs | 18 +++++++++++++----- .../Content/Thumbnails/ThumbnailsModule.cs | 7 +++++++ .../Graphics/Textures/StreamingTexture.cpp | 1 + .../Engine/Graphics/Textures/TextureBase.cpp | 5 +++++ Source/Engine/Graphics/Textures/TextureBase.h | 5 +++++ Source/Engine/Streaming/StreamableResource.h | 6 ++++-- Source/Engine/Streaming/Streaming.cpp | 3 ++- 7 files changed, 37 insertions(+), 8 deletions(-) diff --git a/Source/Editor/Content/Thumbnails/ThumbnailRequest.cs b/Source/Editor/Content/Thumbnails/ThumbnailRequest.cs index 6e60602ff..40ad17720 100644 --- a/Source/Editor/Content/Thumbnails/ThumbnailRequest.cs +++ b/Source/Editor/Content/Thumbnails/ThumbnailRequest.cs @@ -35,6 +35,11 @@ namespace FlaxEditor.Content.Thumbnails /// The finalized state. /// Disposed, + + /// + /// The request has failed (eg. asset cannot be loaded). + /// + Failed, }; /// @@ -78,6 +83,14 @@ namespace FlaxEditor.Content.Thumbnails Proxy = proxy; } + internal void Update() + { + if (State == States.Prepared && (!Asset || Asset.LastLoadFailed)) + { + State = States.Failed; + } + } + /// /// Prepares this request. /// @@ -85,11 +98,8 @@ namespace FlaxEditor.Content.Thumbnails { if (State != States.Created) throw new InvalidOperationException(); - - // Prepare Asset = FlaxEngine.Content.LoadAsync(Item.Path); Proxy.OnThumbnailDrawPrepare(this); - State = States.Prepared; } @@ -101,9 +111,7 @@ namespace FlaxEditor.Content.Thumbnails { if (State != States.Prepared) throw new InvalidOperationException(); - Item.Thumbnail = icon; - State = States.Rendered; } diff --git a/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs b/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs index 2b830c933..b213cb798 100644 --- a/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs +++ b/Source/Editor/Content/Thumbnails/ThumbnailsModule.cs @@ -120,6 +120,8 @@ namespace FlaxEditor.Content.Thumbnails internal static bool HasMinimumQuality(TextureBase asset) { + if (asset.HasStreamingError) + return true; // Don't block thumbnails queue when texture fails to stream in (eg. unsupported format) var mipLevels = asset.MipLevels; var minMipLevels = Mathf.Min(mipLevels, 7); return asset.IsLoaded && asset.ResidentMipLevels >= Mathf.Max(minMipLevels, (int)(mipLevels * MinimumRequiredResourcesQuality)); @@ -499,6 +501,7 @@ namespace FlaxEditor.Content.Thumbnails var request = _requests[i]; try { + request.Update(); if (request.IsReady) { isAnyReady = true; @@ -507,6 +510,10 @@ namespace FlaxEditor.Content.Thumbnails { request.Prepare(); } + else if (request.State == ThumbnailRequest.States.Failed) + { + _requests.RemoveAt(i--); + } } catch (Exception ex) { diff --git a/Source/Engine/Graphics/Textures/StreamingTexture.cpp b/Source/Engine/Graphics/Textures/StreamingTexture.cpp index 09552ec06..ea70a9d9e 100644 --- a/Source/Engine/Graphics/Textures/StreamingTexture.cpp +++ b/Source/Engine/Graphics/Textures/StreamingTexture.cpp @@ -296,6 +296,7 @@ Task* StreamingTexture::UpdateAllocation(int32 residency) // Setup texture if (texture->Init(desc)) { + Streaming.Error = true; LOG(Error, "Cannot allocate texture {0}.", ToString()); } if (allocatedResidency != 0) diff --git a/Source/Engine/Graphics/Textures/TextureBase.cpp b/Source/Engine/Graphics/Textures/TextureBase.cpp index 181955fce..c3d8898a5 100644 --- a/Source/Engine/Graphics/Textures/TextureBase.cpp +++ b/Source/Engine/Graphics/Textures/TextureBase.cpp @@ -223,6 +223,11 @@ void TextureBase::SetTextureGroup(int32 textureGroup) } } +bool TextureBase::HasStreamingError() const +{ + return _texture.Streaming.Error; +} + BytesContainer TextureBase::GetMipData(int32 mipIndex, int32& rowPitch, int32& slicePitch) { BytesContainer result; diff --git a/Source/Engine/Graphics/Textures/TextureBase.h b/Source/Engine/Graphics/Textures/TextureBase.h index e47bb548d..befe16069 100644 --- a/Source/Engine/Graphics/Textures/TextureBase.h +++ b/Source/Engine/Graphics/Textures/TextureBase.h @@ -148,6 +148,11 @@ public: /// API_PROPERTY() void SetTextureGroup(int32 textureGroup); + /// + /// Returns true if texture streaming failed (eg. pixel format is unsupported or texture data cannot be uploaded to GPU due to memory limit). + /// + API_PROPERTY() bool HasStreamingError() const; + public: /// /// Gets the mip data. diff --git a/Source/Engine/Streaming/StreamableResource.h b/Source/Engine/Streaming/StreamableResource.h index 866ec8469..27ae0a089 100644 --- a/Source/Engine/Streaming/StreamableResource.h +++ b/Source/Engine/Streaming/StreamableResource.h @@ -111,8 +111,9 @@ public: struct StreamingCache { int64 LastUpdate = 0; - int32 TargetResidency = 0; int64 TargetResidencyChange = 0; + int32 TargetResidency = 0; + bool Error = false; SamplesBuffer QualitySamples; }; @@ -131,7 +132,8 @@ public: /// /// Stops the streaming (eg. on streaming fail). /// - void ResetStreaming(); + /// True if streaming failed. + void ResetStreaming(bool error = true); protected: diff --git a/Source/Engine/Streaming/Streaming.cpp b/Source/Engine/Streaming/Streaming.cpp index 19b845fb1..a7b2cad44 100644 --- a/Source/Engine/Streaming/Streaming.cpp +++ b/Source/Engine/Streaming/Streaming.cpp @@ -84,8 +84,9 @@ void StreamableResource::RequestStreamingUpdate() Streaming.LastUpdate = 0; } -void StreamableResource::ResetStreaming() +void StreamableResource::ResetStreaming(bool error) { + Streaming.Error = error; Streaming.TargetResidency = 0; Streaming.LastUpdate = DateTime::MaxValue().Ticks; }