Files
leocad/common/lc_texture.cpp
T
Leonardo Zide aa986287a0 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.
2026-10-13 11:13:29 -07:00

270 lines
6.4 KiB
C++

#include "lc_global.h"
#include "lc_texture.h"
#include "lc_context.h"
#include "lc_glextensions.h"
lcTexture* gGridTexture;
lcTexture::lcTexture(int Flags)
: mFlags(Flags)
{
}
lcTexture::~lcTexture()
{
Unload();
}
void lcTexture::CreateGridTexture()
{
constexpr int NumLevels = 9;
mImages.resize(NumLevels);
const QImage* Previous = nullptr;
for (int ImageLevel = 0; ImageLevel < NumLevels; ImageLevel++)
{
QImage& GridImage = mImages[ImageLevel];
const int GridSize = 256 >> ImageLevel;
GridImage = QImage(GridSize, GridSize, QImage::Format_Alpha8);
if (Previous)
{
for (int y = 0; y < GridSize - 1; y++)
{
for (int x = 0; x < GridSize - 1; x++)
{
const quint8 a = Previous->constScanLine(y * 2)[x * 2] > 64 ? 255 : 0;
const quint8 b = Previous->constScanLine(y * 2)[x * 2 + 1] > 64 ? 255 : 0;
const quint8 c = Previous->constScanLine(y * 2 + 1)[x * 2] > 64 ? 255 : 0;
const quint8 d = Previous->constScanLine(y * 2 + 1)[x * 2 + 1] > 64 ? 255 : 0;
GridImage.scanLine(y)[x] = (a + b + c + d) / 4;
}
int x = GridSize - 1;
const quint8 a = Previous->constScanLine(y * 2)[x * 2];
const quint8 c = Previous->constScanLine(y * 2 + 1)[x * 2];
GridImage.scanLine(y)[x] = (a + c) / 2;
}
int y = GridSize - 1;
for (int x = 0; x < GridSize - 1; x++)
{
const quint8 a = Previous->constScanLine(y * 2)[x * 2];
const quint8 b = Previous->constScanLine(y * 2)[x * 2 + 1];
GridImage.scanLine(y)[x] = (a + b) / 2;
}
int x = GridSize - 1;
GridImage.scanLine(y)[x] = Previous->constScanLine(y)[x];
}
else
{
const float Radius1 = (80 >> ImageLevel) * (80 >> ImageLevel);
const float Radius2 = (72 >> ImageLevel) * (72 >> ImageLevel);
quint8* TempBuffer = new quint8[GridSize * GridSize];
for (int y = 0; y < GridSize; y++)
{
quint8* Pixel = TempBuffer + y * GridSize;
memset(Pixel, 0, GridSize);
const float y2 = (y - GridSize / 2) * (y - GridSize / 2);
if (Radius1 <= y2)
continue;
if (Radius2 <= y2)
{
const int x1 = sqrtf(Radius1 - y2);
for (int x = GridSize / 2 - x1; x < GridSize / 2 + x1; x++)
Pixel[x] = 255;
}
else
{
const int x1 = sqrtf(Radius1 - y2);
const int x2 = sqrtf(Radius2 - y2);
for (int x = GridSize / 2 - x1; x < GridSize / 2 - x2; x++)
Pixel[x] = 255;
for (int x = GridSize / 2 + x2; x < GridSize / 2 + x1; x++)
Pixel[x] = 255;
}
}
for (int y = 0; y < GridSize - 1; y++)
{
for (int x = 0; x < GridSize - 1; x++)
{
const quint8 a = TempBuffer[x + y * GridSize];
const quint8 b = TempBuffer[x + 1 + y * GridSize];
const quint8 c = TempBuffer[x + (y + 1) * GridSize];
const quint8 d = TempBuffer[x + 1 + (y + 1) * GridSize];
GridImage.scanLine(y)[x] = (a + b + c + d) / 4;
}
int x = GridSize - 1;
const quint8 a = TempBuffer[x + y * GridSize];
const quint8 c = TempBuffer[x + (y + 1) * GridSize];
GridImage.scanLine(y)[x] = (a + c) / 2;
}
int y = GridSize - 1;
for (int x = 0; x < GridSize - 1; x++)
{
const quint8 a = TempBuffer[x + y * GridSize];
const quint8 b = TempBuffer[x + 1 + y * GridSize];
GridImage.scanLine(y)[x] = (a + b) / 2;
}
int x = GridSize - 1;
GridImage.scanLine(y)[x] = TempBuffer[x + y * GridSize];
delete[] TempBuffer;
}
Previous = &GridImage;
}
mRefCount = 1;
mFlags = LC_TEXTURE_WRAPU | LC_TEXTURE_WRAPV | LC_TEXTURE_MIPMAPS | LC_TEXTURE_ANISOTROPIC;
mState = lcTextureState::Decoded;
lcContext* Context = lcContext::GetGlobalOffscreenContext();
Context->MakeCurrent();
Upload(Context);
}
QImage lcTexture::PrepareImage(QImage Image)
{
if (Image.isNull())
return QImage();
QImage::Format Format = QImage::Format_Alpha8;
if (Image.format() != QImage::Format_Alpha8)
Format = Image.hasAlphaChannel() ? QImage::Format_RGBA8888 : QImage::Format_RGB888;
int Width = 1;
int Height = 1;
while (Width <= Image.width() / 2 && Width < 32768)
Width *= 2;
while (Height <= Image.height() / 2 && Height < 32768)
Height *= 2;
if (Width != Image.width() || Height != Image.height())
Image = Image.scaled(Width, Height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
return Image.convertToFormat(Format);
}
void lcTexture::SetImage(QImage SourceImage, int Flags)
{
QImage TextureImage = PrepareImage(std::move(SourceImage));
if (TextureImage.isNull())
{
SetFailed(lcTextureLoadError::DecodeFailed, QString());
return;
}
mImages.clear();
mImages.emplace_back(std::move(TextureImage));
mFlags = Flags;
mState = lcTextureState::Decoded;
}
void lcTexture::DiscardDecodedImage()
{
mImages.clear();
}
void lcTexture::SetFailed(lcTextureLoadError Error, QString Details)
{
mState = lcTextureState::Failed;
mLoadFailure = Error;
mLoadFailureDetails = std::move(Details);
}
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;
mState = lcTextureState::Decoded;
}
void lcTexture::Upload(lcContext* Context)
{
if (!NeedsUpload())
return;
mWidth = mImages[0].width();
mHeight = mImages[0].height();
const bool Uploaded = Context->UploadTexture(this);
if (Uploaded && mTexture)
{
mImages.clear();
mState = lcTextureState::Ready;
mLoadFailure = lcTextureLoadError::None;
mLoadFailureDetails.clear();
}
else
{
if (mTexture)
{
QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &mTexture);
mTexture = 0;
}
SetFailed(lcTextureLoadError::UploadFailed, QString());
}
}
void lcTexture::Unload()
{
if (mTexture)
{
QOpenGLContext* GLContext = QOpenGLContext::currentContext();
if (GLContext)
GLContext->functions()->glDeleteTextures(1, &mTexture);
else if (qApp && QThread::currentThread() == qApp->thread())
{
if (lcContext* Context = lcContext::GetGlobalOffscreenContext())
{
Context->MakeCurrent();
if (QOpenGLContext* OffscreenContext = QOpenGLContext::currentContext())
{
OffscreenContext->functions()->glDeleteTextures(1, &mTexture);
OffscreenContext->doneCurrent();
}
}
}
}
mTexture = 0;
mState = lcTextureState::Unrequested;
mLoadFailure = lcTextureLoadError::None;
mLoadFailureDetails.clear();
}