From e5d2e09f021cc956be53e3e3aceb37d3af910c2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 13 Dec 2023 10:45:42 +0100 Subject: [PATCH 1/4] Some code simplification --- Common/Render/ManagedTexture.cpp | 35 ++++++++++++++------------------ Common/Render/ManagedTexture.h | 5 +---- Common/UI/AsyncImageFileView.cpp | 1 + Common/UI/AsyncImageFileView.h | 2 +- 4 files changed, 18 insertions(+), 25 deletions(-) diff --git a/Common/Render/ManagedTexture.cpp b/Common/Render/ManagedTexture.cpp index 601d0c8976b7..1a894acc16ae 100644 --- a/Common/Render/ManagedTexture.cpp +++ b/Common/Render/ManagedTexture.cpp @@ -116,7 +116,7 @@ bool TempImage::LoadTextureLevels(const uint8_t *data, size_t size, ImageFileTyp return numLevels > 0; } -Draw::Texture *CreateTextureFromTempImage(Draw::DrawContext *draw, const uint8_t *data, size_t dataSize, const TempImage &image, bool generateMips, const char *name) { +Draw::Texture *CreateTextureFromTempImage(Draw::DrawContext *draw, const TempImage &image, bool generateMips, const char *name) { using namespace Draw; _assert_(image.levels[0] != nullptr && image.width[0] > 0 && image.height[0] > 0); @@ -147,7 +147,7 @@ Draw::Texture *CreateTextureFromFileData(Draw::DrawContext *draw, const uint8_t if (!image.LoadTextureLevels(data, dataSize, type)) { return nullptr; } - Draw::Texture *texture = CreateTextureFromTempImage(draw, data, dataSize, image, generateMips, name); + Draw::Texture *texture = CreateTextureFromTempImage(draw, image, generateMips, name); image.Free(); return texture; } @@ -165,8 +165,16 @@ Draw::Texture *CreateTextureFromFile(Draw::DrawContext *draw, const char *filena return texture; } -bool ManagedTexture::LoadFromFileData(const uint8_t *data, size_t dataSize, ImageFileType type, bool generateMips, const char *name) { +bool ManagedTexture::LoadFromFile(const std::string &filename, ImageFileType type, bool generateMips) { + INFO_LOG(SYSTEM, "ManagedTexture::LoadFromFile (%s)", filename.c_str()); generateMips_ = generateMips; + size_t fileSize; + uint8_t *buffer = g_VFS.ReadFile(filename.c_str(), &fileSize); + if (!buffer) { + filename_.clear(); + ERROR_LOG(IO, "Failed to read file '%s'", filename.c_str()); + return false; + } // Free the old texture, if any. if (texture_) { @@ -175,33 +183,20 @@ bool ManagedTexture::LoadFromFileData(const uint8_t *data, size_t dataSize, Imag } TempImage image; - if (!image.LoadTextureLevels(data, dataSize, type)) { + if (!image.LoadTextureLevels(buffer, fileSize, type)) { return false; } - texture_ = CreateTextureFromTempImage(draw_, data, dataSize, image, generateMips, name); + texture_ = CreateTextureFromTempImage(draw_, image, generateMips, filename.c_str()); image.Free(); - return texture_ != nullptr; -} -bool ManagedTexture::LoadFromFile(const std::string &filename, ImageFileType type, bool generateMips) { - INFO_LOG(SYSTEM, "ManagedTexture::LoadFromFile (%s)", filename.c_str()); - generateMips_ = generateMips; - size_t fileSize; - uint8_t *buffer = g_VFS.ReadFile(filename.c_str(), &fileSize); - if (!buffer) { - filename_.clear(); - ERROR_LOG(IO, "Failed to read file '%s'", filename.c_str()); - return false; - } - bool retval = LoadFromFileData(buffer, fileSize, type, generateMips, filename.c_str()); - if (retval) { + if (texture_) { filename_ = filename; } else { filename_.clear(); ERROR_LOG(IO, "Failed to load texture '%s'", filename.c_str()); } delete[] buffer; - return retval; + return texture_ != 0; } std::unique_ptr CreateManagedTextureFromFile(Draw::DrawContext *draw, const char *filename, ImageFileType type, bool generateMips) { diff --git a/Common/Render/ManagedTexture.h b/Common/Render/ManagedTexture.h index 9e59144b75e2..8db54592101a 100644 --- a/Common/Render/ManagedTexture.h +++ b/Common/Render/ManagedTexture.h @@ -16,15 +16,12 @@ enum ImageFileType { class ManagedTexture { public: - ManagedTexture(Draw::DrawContext *draw) : draw_(draw) { - } + ManagedTexture(Draw::DrawContext *draw) : draw_(draw) {} ~ManagedTexture() { if (texture_) texture_->Release(); } - bool LoadFromFile(const std::string &filename, ImageFileType type = ImageFileType::DETECT, bool generateMips = false); - bool LoadFromFileData(const uint8_t *data, size_t dataSize, ImageFileType type, bool generateMips, const char *name); Draw::Texture *GetTexture(); // For immediate use, don't store. int Width() const { return texture_->Width(); } int Height() const { return texture_->Height(); } diff --git a/Common/UI/AsyncImageFileView.cpp b/Common/UI/AsyncImageFileView.cpp index a29941a64e98..9948a9986fe3 100644 --- a/Common/UI/AsyncImageFileView.cpp +++ b/Common/UI/AsyncImageFileView.cpp @@ -2,6 +2,7 @@ #include "Common/UI/AsyncImageFileView.h" #include "Common/UI/Context.h" #include "Common/Render/DrawBuffer.h" +#include "Common/Render/ManagedTexture.h" AsyncImageFileView::AsyncImageFileView(const Path &filename, UI::ImageSizeMode sizeMode, UI::LayoutParams *layoutParams) : UI::Clickable(layoutParams), canFocus_(true), filename_(filename), color_(0xFFFFFFFF), sizeMode_(sizeMode), textureFailed_(false), fixedSizeW_(0.0f), fixedSizeH_(0.0f) {} diff --git a/Common/UI/AsyncImageFileView.h b/Common/UI/AsyncImageFileView.h index 5acaca30a50d..36e8cf85938a 100644 --- a/Common/UI/AsyncImageFileView.h +++ b/Common/UI/AsyncImageFileView.h @@ -1,10 +1,10 @@ #pragma once #include "Common/UI/View.h" -#include "Common/Render/ManagedTexture.h" #include "Common/File/Path.h" class UIContext; +class ManagedTexture; // AsyncImageFileView loads a texture from a file, and reloads it as necessary. // TODO: Actually make async, doh. From f8927df9a644fb6837f96a45280b616fc5a05877 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 13 Dec 2023 11:45:48 +0100 Subject: [PATCH 2/4] Load savestate screenshots in the background --- Common/Render/ManagedTexture.cpp | 171 ++++++++++++++++--------------- Common/Render/ManagedTexture.h | 56 ++++++++-- Common/Thread/Waitable.h | 5 + Common/UI/AsyncImageFileView.cpp | 2 +- UI/MiscScreens.cpp | 2 +- UI/Store.cpp | 2 +- 6 files changed, 143 insertions(+), 95 deletions(-) diff --git a/Common/Render/ManagedTexture.cpp b/Common/Render/ManagedTexture.cpp index 1a894acc16ae..0aeef5ba513d 100644 --- a/Common/Render/ManagedTexture.cpp +++ b/Common/Render/ManagedTexture.cpp @@ -16,31 +16,51 @@ #include "Common/Log.h" #include "Common/TimeUtil.h" #include "Common/Render/ManagedTexture.h" +#include "Common/Thread/ThreadManager.h" +#include "Common/Thread/Waitable.h" + +// TODO: It really feels like we should be able to simplify this. +class TextureLoadTask : public Task { +public: + TextureLoadTask(std::string_view filename, ImageFileType type, bool generateMips, TempImage *tempImage, ManagedTexture::LoadState *state, LimitedWaitable *waitable) + : filename_(filename), type_(type), generateMips_(generateMips), tempImage_(tempImage), state_(state), waitable_(waitable) {} + + TaskType Type() const override { return TaskType::IO_BLOCKING; } + TaskPriority Priority() const override { return TaskPriority::NORMAL; } + + void Run() override { + size_t fileSize; + uint8_t *buffer = g_VFS.ReadFile(filename_.c_str(), &fileSize); + if (!buffer) { + filename_.clear(); + ERROR_LOG(IO, "Failed to read file '%s'", filename_.c_str()); + *state_ = ManagedTexture::LoadState::FAILED; + return; + } -// For UI images loaded from disk, loaded into RAM, generally staged for upload. -// The reason for the separation is so that the image can be loaded and decompressed on a thread, -// and then only uploaded to the GPU on the main thread. -struct TempImage { - ~TempImage() { - _dbg_assert_(levels[0] == nullptr); - } - Draw::DataFormat fmt = Draw::DataFormat::UNDEFINED; - ImageFileType type = TYPE_UNKNOWN; - uint8_t *levels[16]{}; // only free the first pointer, they all point to the same buffer. - int zimFlags = 0; - int width[16]{}; - int height[16]{}; - int numLevels = 0; - - bool LoadTextureLevels(const uint8_t *data, size_t size, ImageFileType typeSuggestion = DETECT); - void Free() { - if (levels[0]) { - free(levels[0]); - memset(levels, 0, sizeof(levels)); + if (!tempImage_->LoadTextureLevels(buffer, fileSize, type_)) { + *state_ = ManagedTexture::LoadState::FAILED; + return; } - } + delete[] buffer; + *state_ = ManagedTexture::LoadState::SUCCESS; + waitable_->Notify(); + } + +private: + LimitedWaitable *waitable_; + std::string filename_; + TempImage *tempImage_; + ImageFileType type_; + bool generateMips_; + ManagedTexture::LoadState *state_; }; +TempImage::~TempImage() { + // Make sure you haven't forgotten to call Free. + _dbg_assert_(levels[0] == nullptr); +} + static Draw::DataFormat ZimToT3DFormat(int zim) { switch (zim) { case ZIM_RGBA8888: return Draw::DataFormat::R8G8B8A8_UNORM; @@ -50,24 +70,24 @@ static Draw::DataFormat ZimToT3DFormat(int zim) { static ImageFileType DetectImageFileType(const uint8_t *data, size_t size) { if (size < 4) { - return TYPE_UNKNOWN; + return ImageFileType::UNKNOWN; } if (!memcmp(data, "ZIMG", 4)) { - return ZIM; + return ImageFileType::ZIM; } else if (!memcmp(data, "\x89\x50\x4E\x47", 4)) { - return PNG; + return ImageFileType::PNG; } else if (!memcmp(data, "\xff\xd8\xff\xe0", 4) || !memcmp(data, "\xff\xd8\xff\xe1", 4)) { - return JPEG; + return ImageFileType::JPEG; } else { - return TYPE_UNKNOWN; + return ImageFileType::UNKNOWN; } } bool TempImage::LoadTextureLevels(const uint8_t *data, size_t size, ImageFileType typeSuggestion) { - if (typeSuggestion == DETECT) { + if (typeSuggestion == ImageFileType::DETECT) { typeSuggestion = DetectImageFileType(data, size); } - if (typeSuggestion == TYPE_UNKNOWN) { + if (typeSuggestion == ImageFileType::UNKNOWN) { ERROR_LOG(G3D, "File (size: %d) has unknown format", (int)size); return false; } @@ -77,12 +97,12 @@ bool TempImage::LoadTextureLevels(const uint8_t *data, size_t size, ImageFileTyp zimFlags = 0; switch (typeSuggestion) { - case ZIM: + case ImageFileType::ZIM: numLevels = LoadZIMPtr((const uint8_t *)data, size, width, height, &zimFlags, levels); fmt = ZimToT3DFormat(zimFlags & ZIM_FORMAT_MASK); break; - case PNG: + case ImageFileType::PNG: if (1 == pngLoadPtr((const unsigned char *)data, size, &width[0], &height[0], &levels[0])) { numLevels = 1; fmt = Draw::DataFormat::R8G8B8A8_UNORM; @@ -96,7 +116,7 @@ bool TempImage::LoadTextureLevels(const uint8_t *data, size_t size, ImageFileTyp } break; - case JPEG: + case ImageFileType::JPEG: { int actual_components = 0; unsigned char *jpegBuf = jpgd::decompress_jpeg_image_from_memory(data, (int)size, &width[0], &height[0], &actual_components, 4); @@ -165,58 +185,56 @@ Draw::Texture *CreateTextureFromFile(Draw::DrawContext *draw, const char *filena return texture; } -bool ManagedTexture::LoadFromFile(const std::string &filename, ImageFileType type, bool generateMips) { - INFO_LOG(SYSTEM, "ManagedTexture::LoadFromFile (%s)", filename.c_str()); - generateMips_ = generateMips; - size_t fileSize; - uint8_t *buffer = g_VFS.ReadFile(filename.c_str(), &fileSize); - if (!buffer) { - filename_.clear(); - ERROR_LOG(IO, "Failed to read file '%s'", filename.c_str()); - return false; - } - - // Free the old texture, if any. +Draw::Texture *ManagedTexture::GetTexture() { if (texture_) { - texture_->Release(); - texture_ = nullptr; + return texture_; + } else if (state_ == LoadState::SUCCESS) { + if (taskWaitable_) { + taskWaitable_->WaitAndRelease(); + taskWaitable_ = nullptr; + } + // Image load is done, texture creation is not. + texture_ = CreateTextureFromTempImage(draw_, pendingImage_, generateMips_, filename_.c_str()); + pendingImage_.Free(); } + return texture_; +} - TempImage image; - if (!image.LoadTextureLevels(buffer, fileSize, type)) { - return false; - } - texture_ = CreateTextureFromTempImage(draw_, image, generateMips, filename.c_str()); - image.Free(); +ManagedTexture::ManagedTexture(Draw::DrawContext *draw, std::string_view filename, ImageFileType type, bool generateMips) + : draw_(draw), filename_(filename), type_(type), generateMips_(generateMips) +{ + INFO_LOG(SYSTEM, "ManagedTexture::ManagedTexture (%s)", filename_.c_str()); + StartLoadTask(); +} - if (texture_) { - filename_ = filename; - } else { - filename_.clear(); - ERROR_LOG(IO, "Failed to load texture '%s'", filename.c_str()); +ManagedTexture::~ManagedTexture() { + // Stop any pending loads. + if (taskWaitable_) { + taskWaitable_->WaitAndRelease(); + pendingImage_.Free(); } - delete[] buffer; - return texture_ != 0; + if (texture_) + texture_->Release(); } -std::unique_ptr CreateManagedTextureFromFile(Draw::DrawContext *draw, const char *filename, ImageFileType type, bool generateMips) { - INFO_LOG(SYSTEM, "ManagedTexture::CreateFromFile (%s)", filename); - if (!draw) - return std::unique_ptr(); - // TODO: Load the texture on a background thread. - ManagedTexture *mtex = new ManagedTexture(draw); - if (!mtex->LoadFromFile(filename, type, generateMips)) { - delete mtex; - return std::unique_ptr(); - } - return std::unique_ptr(mtex); +void ManagedTexture::StartLoadTask() { + _dbg_assert_(!taskWaitable_); + taskWaitable_ = new LimitedWaitable(); + g_threadManager.EnqueueTask(new TextureLoadTask(filename_, type_, generateMips_, &pendingImage_, &state_, taskWaitable_)); } void ManagedTexture::DeviceLost() { INFO_LOG(G3D, "ManagedTexture::DeviceLost(%s)", filename_.c_str()); + if (taskWaitable_) { + taskWaitable_->WaitAndRelease(); + pendingImage_.Free(); + } if (texture_) texture_->Release(); texture_ = nullptr; + if (state_ == LoadState::SUCCESS) { + state_ = LoadState::PENDING; + } } void ManagedTexture::DeviceRestored(Draw::DrawContext *draw) { @@ -230,17 +248,8 @@ void ManagedTexture::DeviceRestored(Draw::DrawContext *draw) { return; } - // Vulkan: Can't load textures before the first frame has started. - // Should probably try to lift that restriction again someday.. - loadPending_ = true; -} - -Draw::Texture *ManagedTexture::GetTexture() { - if (loadPending_) { - if (!LoadFromFile(filename_, ImageFileType::DETECT, generateMips_)) { - ERROR_LOG(IO, "ManagedTexture failed: '%s'", filename_.c_str()); - } - loadPending_ = false; + if (state_ == LoadState::PENDING) { + // Kick off a new load task. + StartLoadTask(); } - return texture_; } diff --git a/Common/Render/ManagedTexture.h b/Common/Render/ManagedTexture.h index 8db54592101a..ce6554e0981b 100644 --- a/Common/Render/ManagedTexture.h +++ b/Common/Render/ManagedTexture.h @@ -1,27 +1,51 @@ #pragma once +#include +#include #include #include "Common/GPU/thin3d.h" #include "Common/UI/View.h" #include "Common/File/Path.h" -enum ImageFileType { +enum class ImageFileType { PNG, JPEG, ZIM, DETECT, - TYPE_UNKNOWN, + UNKNOWN, }; +class TextureLoadTask; +class LimitedWaitable; + +// For UI images loaded from disk, loaded into RAM, generally staged for upload. +// The reason for the separation is so that the image can be loaded and decompressed on a thread, +// and then only uploaded to the GPU on the main thread. +struct TempImage { + ~TempImage(); + Draw::DataFormat fmt = Draw::DataFormat::UNDEFINED; + ImageFileType type = ImageFileType::UNKNOWN; + uint8_t *levels[16]{}; // only free the first pointer, they all point to the same buffer. + int zimFlags = 0; + int width[16]{}; + int height[16]{}; + int numLevels = 0; + + bool LoadTextureLevels(const uint8_t *data, size_t size, ImageFileType typeSuggestion = ImageFileType::DETECT); + void Free() { + if (levels[0]) { + free(levels[0]); + memset(levels, 0, sizeof(levels)); + } + } +}; + +// Managed (will auto-reload from file) and async. For use in UI. class ManagedTexture { public: - ManagedTexture(Draw::DrawContext *draw) : draw_(draw) {} - ~ManagedTexture() { - if (texture_) - texture_->Release(); - } - bool LoadFromFile(const std::string &filename, ImageFileType type = ImageFileType::DETECT, bool generateMips = false); + ManagedTexture(Draw::DrawContext *draw, std::string_view filename, ImageFileType type = ImageFileType::DETECT, bool generateMips = false); + ~ManagedTexture(); Draw::Texture *GetTexture(); // For immediate use, don't store. int Width() const { return texture_->Width(); } int Height() const { return texture_->Height(); } @@ -29,15 +53,25 @@ class ManagedTexture { void DeviceLost(); void DeviceRestored(Draw::DrawContext *draw); + enum class LoadState { + PENDING, + FAILED, + SUCCESS, + }; + private: + void StartLoadTask(); + Draw::Texture *texture_ = nullptr; Draw::DrawContext *draw_; std::string filename_; // Textures that are loaded from files can reload themselves automatically. bool generateMips_ = false; - bool loadPending_ = false; + ImageFileType type_ = ImageFileType::DETECT; + TextureLoadTask *loadTask_ = nullptr; + LimitedWaitable *taskWaitable_ = nullptr; + TempImage pendingImage_; + LoadState state_ = LoadState::PENDING; }; Draw::Texture *CreateTextureFromFileData(Draw::DrawContext *draw, const uint8_t *data, size_t dataSize, ImageFileType type, bool generateMips, const char *name); Draw::Texture *CreateTextureFromFile(Draw::DrawContext *draw, const char *filename, ImageFileType type, bool generateMips); - -std::unique_ptr CreateManagedTextureFromFile(Draw::DrawContext *draw, const char *filename, ImageFileType fileType, bool generateMips); diff --git a/Common/Thread/Waitable.h b/Common/Thread/Waitable.h index dcceefd046c8..de33ed2049f1 100644 --- a/Common/Thread/Waitable.h +++ b/Common/Thread/Waitable.h @@ -43,6 +43,11 @@ class LimitedWaitable : public Waitable { cond_.notify_all(); } + // For simple polling. + bool Ready() const { + return triggered_; + } + private: std::condition_variable cond_; std::mutex mutex_; diff --git a/Common/UI/AsyncImageFileView.cpp b/Common/UI/AsyncImageFileView.cpp index 9948a9986fe3..117e35448e90 100644 --- a/Common/UI/AsyncImageFileView.cpp +++ b/Common/UI/AsyncImageFileView.cpp @@ -79,7 +79,7 @@ void AsyncImageFileView::DeviceRestored(Draw::DrawContext *draw) { void AsyncImageFileView::Draw(UIContext &dc) { using namespace Draw; if (!texture_ && !textureFailed_ && !filename_.empty()) { - texture_ = CreateManagedTextureFromFile(dc.GetDrawContext(), filename_.c_str(), DETECT, true); + texture_ = std::unique_ptr(new ManagedTexture(dc.GetDrawContext(), filename_.c_str(), ImageFileType::DETECT, true)); if (!texture_.get()) textureFailed_ = true; } diff --git a/UI/MiscScreens.cpp b/UI/MiscScreens.cpp index da9b5b0bd6c7..d0d212760a74 100644 --- a/UI/MiscScreens.cpp +++ b/UI/MiscScreens.cpp @@ -296,7 +296,7 @@ void UIBackgroundInit(UIContext &dc) { const Path bgJpg = GetSysDirectory(DIRECTORY_SYSTEM) / "background.jpg"; if (File::Exists(bgPng) || File::Exists(bgJpg)) { const Path &bgFile = File::Exists(bgPng) ? bgPng : bgJpg; - bgTexture = CreateTextureFromFile(dc.GetDrawContext(), bgFile.c_str(), DETECT, true); + bgTexture = CreateTextureFromFile(dc.GetDrawContext(), bgFile.c_str(), ImageFileType::DETECT, true); } } diff --git a/UI/Store.cpp b/UI/Store.cpp index 8f31b2711952..83618d55ef8d 100644 --- a/UI/Store.cpp +++ b/UI/Store.cpp @@ -183,7 +183,7 @@ void HttpImageFileView::Draw(UIContext &dc) { } if (!textureData_.empty()) { - texture_ = CreateTextureFromFileData(dc.GetDrawContext(), (const uint8_t *)(textureData_.data()), (int)textureData_.size(), DETECT, false, "store_icon"); + texture_ = CreateTextureFromFileData(dc.GetDrawContext(), (const uint8_t *)(textureData_.data()), (int)textureData_.size(), ImageFileType::DETECT, false, "store_icon"); if (!texture_) textureFailed_ = true; textureData_.clear(); From d549817245704f06c9165a3e908ba0e579fc2a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 13 Dec 2023 12:21:12 +0100 Subject: [PATCH 3/4] Add missing check --- Common/UI/Context.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Common/UI/Context.cpp b/Common/UI/Context.cpp index a0fe2506bf85..c92bbb794f6a 100644 --- a/Common/UI/Context.cpp +++ b/Common/UI/Context.cpp @@ -22,8 +22,10 @@ UIContext::~UIContext() { sampler_->Release(); delete fontStyle_; delete textDrawer_; - uitexture_->Release(); - fontTexture_->Release(); + if (uitexture_) + uitexture_->Release(); + if (fontTexture_) + fontTexture_->Release(); } void UIContext::Init(Draw::DrawContext *thin3d, Draw::Pipeline *uipipe, Draw::Pipeline *uipipenotex, DrawBuffer *uidrawbuffer) { From 1ac780aacc0eb2e2a0d87d029e7e0a19dedc60b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 13 Dec 2023 12:26:11 +0100 Subject: [PATCH 4/4] Reduce flicker on savestate screenshot load --- Common/Render/ManagedTexture.h | 4 ++++ Common/UI/AsyncImageFileView.cpp | 14 ++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Common/Render/ManagedTexture.h b/Common/Render/ManagedTexture.h index ce6554e0981b..f90ee457870f 100644 --- a/Common/Render/ManagedTexture.h +++ b/Common/Render/ManagedTexture.h @@ -53,6 +53,10 @@ class ManagedTexture { void DeviceLost(); void DeviceRestored(Draw::DrawContext *draw); + bool Failed() const { + return state_ == LoadState::FAILED; + } + enum class LoadState { PENDING, FAILED, diff --git a/Common/UI/AsyncImageFileView.cpp b/Common/UI/AsyncImageFileView.cpp index 117e35448e90..845e2ad86ab9 100644 --- a/Common/UI/AsyncImageFileView.cpp +++ b/Common/UI/AsyncImageFileView.cpp @@ -100,12 +100,14 @@ void AsyncImageFileView::Draw(UIContext &dc) { dc.DrawText(text_.c_str(), bounds_.centerX(), bounds_.centerY(), 0xFFFFFFFF, ALIGN_CENTER | FLAG_DYNAMIC_ASCII); } } else { - if (!filename_.empty()) { - // draw a black rectangle to represent the missing screenshot. - dc.FillRect(UI::Drawable(0xFF000000), GetBounds()); - } else { - // draw a dark gray rectangle to represent no save state. - dc.FillRect(UI::Drawable(0x50202020), GetBounds()); + if (!texture_ || texture_->Failed()) { + if (!filename_.empty()) { + // draw a black rectangle to represent the missing screenshot. + dc.FillRect(UI::Drawable(0xFF000000), GetBounds()); + } else { + // draw a dark gray rectangle to represent no save state. + dc.FillRect(UI::Drawable(0x50202020), GetBounds()); + } } if (!text_.empty()) { dc.DrawText(text_.c_str(), bounds_.centerX(), bounds_.centerY(), 0xFFFFFFFF, ALIGN_CENTER | FLAG_DYNAMIC_ASCII);