From aa986287a0b91282c58c815feb9ec6f84439e216 Mon Sep 17 00:00:00 2001 From: Leonardo Zide Date: Tue, 13 Oct 2026 11:13:29 -0700 Subject: [PATCH] Separate texture image assignment from OpenGL upload SetImage previously uploaded automatically on the UI thread while the asset loader needed a separate AdoptDecodedImage path to control context switching and retries. Make SetImage and the renamed SetImages prepare and store pixels only, remove AdoptDecodedImage and LoadImages, and upload generated atlases explicitly. CacheStrings now accepts the caller's current rendering context. Retain cube-map faces and supplied mipmaps through SetImages. Advance the image index after each face so a supplied mip chain does not leave the next face reading the previous face's last mip level. Validation: full application build and diff checks passed. OpenGL readback checks covered explicit setters, all six cube faces with generated and supplied mipmaps, narrow and custom-stride image uploads, alignment restoration, failure cleanup, and all nine grid levels against the previous generator. --- common/lc_assetloader.cpp | 2 +- common/lc_context.cpp | 6 +---- common/lc_stringcache.cpp | 3 ++- common/lc_stringcache.h | 2 +- common/lc_texture.cpp | 57 +++++++++++++-------------------------- common/lc_texture.h | 5 +--- common/lc_viewsphere.cpp | 1 + docs/asset-loading.md | 3 +++ 8 files changed, 29 insertions(+), 50 deletions(-) diff --git a/common/lc_assetloader.cpp b/common/lc_assetloader.cpp index 8c4d51a2..ac029dee 100644 --- a/common/lc_assetloader.cpp +++ b/common/lc_assetloader.cpp @@ -974,7 +974,7 @@ bool lcAssetLoader::ProcessCompletions() mLibrary->ReleaseTexture(Texture); else if (!Completed.TextureResult.DecodedImage.isNull()) { - Texture->AdoptDecodedImage(std::move(Completed.TextureResult.DecodedImage), LC_TEXTURE_MIPMAPS); + Texture->SetImage(std::move(Completed.TextureResult.DecodedImage), LC_TEXTURE_MIPMAPS); if (Texture->NeedsUpload()) UploadTextureRequest(RequestedTexture); else diff --git a/common/lc_context.cpp b/common/lc_context.cpp index a52d7772..a55c106b 100644 --- a/common/lc_context.cpp +++ b/common/lc_context.cpp @@ -1186,14 +1186,10 @@ bool lcContext::UploadTexture(lcTexture* Texture) } } - if (Texture->GetImageCount() == Faces) - CurrentImage++; - delete[] MipBuffer; } - else - CurrentImage++; + CurrentImage++; Target++; } diff --git a/common/lc_stringcache.cpp b/common/lc_stringcache.cpp index c2bd8daf..a2fc52f7 100644 --- a/common/lc_stringcache.cpp +++ b/common/lc_stringcache.cpp @@ -28,7 +28,7 @@ void lcStringCache::Reset() mTexture = nullptr; } -void lcStringCache::CacheStrings(const QStringList& Strings) +void lcStringCache::CacheStrings(lcContext* Context, const QStringList& Strings) { bool Update = false; @@ -99,6 +99,7 @@ void lcStringCache::CacheStrings(const QStringList& Strings) } mTexture->SetImage(std::move(TextureImage), LC_TEXTURE_LINEAR); + mTexture->Upload(Context); } void lcStringCache::GetStringDimensions(int* cx, int* cy, const QString& String) const diff --git a/common/lc_stringcache.h b/common/lc_stringcache.h index 24075810..1cc0497b 100644 --- a/common/lc_stringcache.h +++ b/common/lc_stringcache.h @@ -14,7 +14,7 @@ public: void Initialize(lcContext* Context); void Reset(); - void CacheStrings(const QStringList& Strings); + void CacheStrings(lcContext* Context, const QStringList& Strings); void GetStringDimensions(int* cx, int* cy, const QString& String) const; void DrawStrings(lcContext* Context, const lcMatrix44* Transforms, const QStringList& Strings) const; diff --git a/common/lc_texture.cpp b/common/lc_texture.cpp index 07ab4df6..fd2004ac 100644 --- a/common/lc_texture.cpp +++ b/common/lc_texture.cpp @@ -158,14 +158,6 @@ QImage lcTexture::PrepareImage(QImage Image) } void lcTexture::SetImage(QImage SourceImage, int Flags) -{ - AdoptDecodedImage(std::move(SourceImage), Flags); - - if (mState == lcTextureState::Decoded) - LoadImages(); -} - -void lcTexture::AdoptDecodedImage(QImage SourceImage, int Flags) { QImage TextureImage = PrepareImage(std::move(SourceImage)); @@ -193,12 +185,28 @@ void lcTexture::SetFailed(lcTextureLoadError Error, QString Details) mLoadFailureDetails = std::move(Details); } -void lcTexture::SetImage(std::vector&& Images, int Flags) +void lcTexture::SetImages(std::vector&& Images, int Flags) { + if (Images.empty()) + { + SetFailed(lcTextureLoadError::DecodeFailed, QString()); + return; + } + + for (QImage& Image : Images) + { + Image = PrepareImage(std::move(Image)); + + if (Image.isNull()) + { + SetFailed(lcTextureLoadError::DecodeFailed, QString()); + return; + } + } + mImages = std::move(Images); mFlags = Flags; - - LoadImages(); + mState = lcTextureState::Decoded; } void lcTexture::Upload(lcContext* Context) @@ -231,33 +239,6 @@ void lcTexture::Upload(lcContext* Context) } } -bool lcTexture::LoadImages() -{ - for (QImage& Image : mImages) - { - Image = PrepareImage(std::move(Image)); - - if (Image.isNull()) - { - SetFailed(lcTextureLoadError::DecodeFailed, QString()); - return false; - } - } - - mState = lcTextureState::Decoded; - - if (QThread::currentThread() == qApp->thread()) - { - lcContext* Context = lcContext::GetGlobalOffscreenContext(); - - Context->MakeCurrent(); - - Upload(Context); - } - - return true; -} - void lcTexture::Unload() { if (mTexture) diff --git a/common/lc_texture.h b/common/lc_texture.h index d0897d82..5440ac5e 100644 --- a/common/lc_texture.h +++ b/common/lc_texture.h @@ -52,8 +52,7 @@ public: static QImage PrepareImage(QImage Image); void SetImage(QImage Image, int Flags); - void SetImage(std::vector&& Images, int Flags); - void AdoptDecodedImage(QImage Image, int Flags); + void SetImages(std::vector&& Images, int Flags); void DiscardDecodedImage(); // Records a failure without discarding pixels retained for upload retries. void SetFailed(lcTextureLoadError Error, QString Details); @@ -133,8 +132,6 @@ public: QString mLoadFailureDetails; protected: - bool LoadImages(); - bool mTemporary = false; QAtomicInt mRefCount = 0; std::vector mImages; diff --git a/common/lc_viewsphere.cpp b/common/lc_viewsphere.cpp index 42ee373d..e2c32d7f 100644 --- a/common/lc_viewsphere.cpp +++ b/common/lc_viewsphere.cpp @@ -133,6 +133,7 @@ void lcViewSphere::CreateResources(lcContext* Context) } mTexture->SetImage(std::move(AtlasImage), LC_TEXTURE_LINEAR); + mTexture->Upload(Context); const int SphereVertexCount = (mSubdivisions + 1) * (mSubdivisions + 1) * 6; mSphereIndexCount = mSubdivisions * mSubdivisions * 6 * 6; diff --git a/docs/asset-loading.md b/docs/asset-loading.md index 9d037421..99adf50a 100644 --- a/docs/asset-loading.md +++ b/docs/asset-loading.md @@ -139,6 +139,9 @@ needed. CPU mipmap generation copies pixel rows without padding into its existin working buffer; its filtering, OpenGL formats, and shaders remain unchanged. Each transfer sets its required unpack alignment, and texture upload restores the caller's unpack alignment on every exit. Pack alignment is left unchanged. +`SetImage()` and `SetImages()` only prepare and store pixels. Generated texture +callers upload explicitly, while the loader controls upload attempts and retries. +`SetImages()` retains cube-map faces and supplied mip levels. `lcTexture::SetFailed()` assigns readiness and failure diagnostics together, clearing stale details when none are supplied. Decoding and upload failures use