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.
This commit is contained in:
Leonardo Zide committed 2026-10-13 11:13:29 -07:00
1 parent b69db29428
commit aa986287a0
8 files changed
+29 -50

No files matched your search

+1 -1
View File
@@ -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
+1 -5
View File
@@ -1186,14 +1186,10 @@ bool lcContext::UploadTexture(lcTexture* Texture)
}
}
if (Texture->GetImageCount() == Faces)
CurrentImage++;
delete[] MipBuffer;
}
else
CurrentImage++;
CurrentImage++;
Target++;
}
+2 -1
View File
@@ -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
+1 -1
View File
@@ -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;
+19 -38
View File
@@ -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<QImage>&& Images, int Flags)
void lcTexture::SetImages(std::vector<QImage>&& 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)
+1 -4
View File
@@ -52,8 +52,7 @@ public:
static QImage PrepareImage(QImage Image);
void SetImage(QImage Image, int Flags);
void SetImage(std::vector<QImage>&& Images, int Flags);
void AdoptDecodedImage(QImage Image, int Flags);
void SetImages(std::vector<QImage>&& 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<QImage> mImages;
+1
View File
@@ -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;
+3
View File
@@ -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