mirror of
https://github.com/leozide/leocad.git
synced 2026-10-27 06:20:55 +00:00
Separate texture management from library source access
Texture lookup, references, decoding, and scheduling were mixed into the pieces library. Introduce lcTextureManager to own texture identities, references, and CPU decoding, while keeping source lookup and file indexes in the library and asynchronous scheduling, uploads, retries, and publication in lcAssetLoader. Add lcFileSourceSnapshot and OpenFile to return owned disk or memory streams. The library opens files on the existing loading thread and passes lcFile references to the manager. Image decoding uses QFile or existing memory buffers directly, preserving file cursors without an extra adapter. Hide loader-only texture services, centralize failure state and diagnostics in lcTexture::SetFailed, and use each zip archive internal extraction lock instead of a library-wide mutex. Workers are drained before archive replacement or texture-manager teardown. Validation: debug build, file ownership and cursor checks, texture lookup and decoding checks, bounded upload-retry and reference-release checks, 800 concurrent archive reads, and matching native GPU renders for embedded and disk textures.
This commit is contained in:
1 parent
3019471e71
commit
1098c2de75
13 files changed
+471
-242
No files matched your search
+25
-4
@@ -164,14 +164,35 @@ void Image::Resize(int width, int height)
|
||||
mHeight = height;
|
||||
}
|
||||
|
||||
bool Image::FileLoad(lcMemFile& File)
|
||||
bool Image::FileLoad(lcFile& File)
|
||||
{
|
||||
QImage Image;
|
||||
|
||||
const unsigned char* Buffer = File.mBuffer + File.mPosition;
|
||||
const size_t BufferLength = File.mFileSize - File.mPosition;
|
||||
if (lcMemFile* MemoryFile = dynamic_cast<lcMemFile*>(&File))
|
||||
{
|
||||
if (!MemoryFile->mBuffer || MemoryFile->mPosition >= MemoryFile->mFileSize)
|
||||
return false;
|
||||
|
||||
if (!Image.loadFromData(Buffer, (int)BufferLength))
|
||||
const size_t Length = MemoryFile->mFileSize - MemoryFile->mPosition;
|
||||
|
||||
if (Length > static_cast<size_t>(std::numeric_limits<int>::max()) || !Image.loadFromData(MemoryFile->mBuffer + MemoryFile->mPosition, static_cast<int>(Length)))
|
||||
return false;
|
||||
}
|
||||
else if (lcDiskFile* DiskFile = dynamic_cast<lcDiskFile*>(&File))
|
||||
{
|
||||
QFile& Device = DiskFile->GetFile();
|
||||
const qint64 Position = Device.pos();
|
||||
|
||||
if (Position < 0)
|
||||
return false;
|
||||
|
||||
const bool Loaded = Image.load(&Device, nullptr);
|
||||
const bool Restored = Device.seek(Position);
|
||||
|
||||
if (!Loaded || !Restored)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
CopyFromQImage(Image, *this);
|
||||
|
||||
+2
-1
@@ -27,7 +27,8 @@ public:
|
||||
int GetBPP() const;
|
||||
bool HasAlpha() const;
|
||||
|
||||
bool FileLoad(lcMemFile& File);
|
||||
// Reads disk or memory files from the current position, preserving the cursor.
|
||||
bool FileLoad(lcFile& File);
|
||||
bool FileLoad(const QString& FileName);
|
||||
|
||||
void Resize(int Width, int Height);
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
constexpr QEvent::Type lcAssetLoaderCompletionEvent = static_cast<QEvent::Type>(QEvent::User + 1);
|
||||
|
||||
lcPartBuildResult::~lcPartBuildResult() = default;
|
||||
lcTextureBuildResult::~lcTextureBuildResult() = default;
|
||||
|
||||
lcAssetLoader::lcAssetLoader(lcPiecesLibrary* Library)
|
||||
: mLibrary(Library)
|
||||
@@ -502,7 +501,7 @@ void lcAssetLoader::QueueTexture(lcTexture* Texture, Priority LoadPriority)
|
||||
return;
|
||||
}
|
||||
|
||||
Texture->AddRef();
|
||||
mLibrary->AddTextureReference(Texture);
|
||||
Texture->mState = lcTextureState::Queued;
|
||||
|
||||
std::shared_ptr<TextureRequest> RequestedTexture = std::make_shared<TextureRequest>();
|
||||
@@ -785,14 +784,12 @@ void lcAssetLoader::UploadTextureRequest(const std::shared_ptr<TextureRequest>&
|
||||
Texture->Upload(Context);
|
||||
else
|
||||
{
|
||||
Texture->mState = lcTextureState::Failed;
|
||||
Texture->mLoadFailure = lcTextureLoadError::ContextActivationFailed;
|
||||
Texture->SetFailed(lcTextureLoadError::ContextActivationFailed, QString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Texture->mState = lcTextureState::Failed;
|
||||
Texture->mLoadFailure = lcTextureLoadError::ContextUnavailable;
|
||||
Texture->SetFailed(lcTextureLoadError::ContextUnavailable, QString());
|
||||
}
|
||||
|
||||
if (PreviousContext && PreviousSurface)
|
||||
@@ -824,11 +821,12 @@ void lcAssetLoader::UploadTextureRequest(const std::shared_ptr<TextureRequest>&
|
||||
|
||||
if (!Texture->IsReady())
|
||||
{
|
||||
Texture->mState = lcTextureState::Failed;
|
||||
lcTextureLoadError Error = Texture->mLoadFailure;
|
||||
|
||||
if (Texture->mLoadFailure == lcTextureLoadError::None)
|
||||
Texture->mLoadFailure = lcTextureLoadError::UploadFailed;
|
||||
if (Error == lcTextureLoadError::None)
|
||||
Error = lcTextureLoadError::UploadFailed;
|
||||
|
||||
Texture->SetFailed(Error, Texture->mLoadFailureDetails);
|
||||
Texture->DiscardDecodedImage();
|
||||
}
|
||||
|
||||
@@ -981,9 +979,7 @@ bool lcAssetLoader::ProcessCompletions()
|
||||
}
|
||||
else
|
||||
{
|
||||
Texture->mState = lcTextureState::Failed;
|
||||
Texture->mLoadFailure = Completed.TextureResult.Error;
|
||||
Texture->mLoadFailureDetails = Completed.TextureResult.ErrorDetails;
|
||||
Texture->SetFailed(Completed.TextureResult.Error, std::move(Completed.TextureResult.ErrorDetails));
|
||||
|
||||
FinishTextureRequest(RequestedTexture);
|
||||
}
|
||||
|
||||
+2
-23
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "lc_library.h"
|
||||
#include "lc_texturemanager.h"
|
||||
#include "piece.h"
|
||||
|
||||
class PieceInfo;
|
||||
@@ -51,28 +52,6 @@ struct lcPartBuildResult
|
||||
QString Error;
|
||||
};
|
||||
|
||||
struct lcTextureSourceSnapshot
|
||||
{
|
||||
std::shared_ptr<const lcEmbeddedData> EmbeddedData;
|
||||
QString FilePath;
|
||||
int ArchiveType = -1;
|
||||
int ArchiveIndex = -1;
|
||||
};
|
||||
|
||||
struct lcTextureBuildResult
|
||||
{
|
||||
lcTextureBuildResult() = default;
|
||||
~lcTextureBuildResult();
|
||||
lcTextureBuildResult(lcTextureBuildResult&&) = default;
|
||||
lcTextureBuildResult& operator=(lcTextureBuildResult&&) = default;
|
||||
lcTextureBuildResult(const lcTextureBuildResult&) = delete;
|
||||
lcTextureBuildResult& operator=(const lcTextureBuildResult&) = delete;
|
||||
|
||||
std::unique_ptr<Image> DecodedImage;
|
||||
lcTextureLoadError Error{};
|
||||
QString ErrorDetails;
|
||||
};
|
||||
|
||||
// Owned by lcPiecesLibrary. UI-thread entry points schedule private worker builds;
|
||||
// UI-thread completion processing resolves colors, uploads textures, and publishes meshes.
|
||||
// See docs/asset-loading.md for readiness, ownership, and caller policies.
|
||||
@@ -139,7 +118,7 @@ private:
|
||||
{
|
||||
lcTexture* Texture;
|
||||
quint64 Generation;
|
||||
lcTextureSourceSnapshot Source;
|
||||
lcFileSourceSnapshot Source;
|
||||
Priority LoadPriority;
|
||||
qint64 EnqueuedAt;
|
||||
bool Running = false;
|
||||
|
||||
@@ -513,6 +513,11 @@ public:
|
||||
mFile.setFileName(FileName);
|
||||
}
|
||||
|
||||
QFile& GetFile()
|
||||
{
|
||||
return mFile;
|
||||
}
|
||||
|
||||
long GetPosition() const override
|
||||
{
|
||||
return mFile.pos();
|
||||
|
||||
+151
-180
@@ -7,6 +7,7 @@
|
||||
#include "pieceinf.h"
|
||||
#include "lc_colors.h"
|
||||
#include "lc_texture.h"
|
||||
#include "lc_texturemanager.h"
|
||||
#include "lc_category.h"
|
||||
#include "lc_application.h"
|
||||
#include "lc_context.h"
|
||||
@@ -38,6 +39,7 @@ enum class lcLibraryCacheFlag : quint32
|
||||
|
||||
lcPiecesLibrary::lcPiecesLibrary()
|
||||
{
|
||||
mTextureManager = std::make_unique<lcTextureManager>();
|
||||
mAssetLoader = std::unique_ptr<lcAssetLoader>(new lcAssetLoader(this));
|
||||
mThumbnailManager = std::unique_ptr<lcThumbnailManager>(new lcThumbnailManager(this));
|
||||
mLoadingMesh = std::unique_ptr<lcMesh>(new lcMesh);
|
||||
@@ -113,10 +115,7 @@ void lcPiecesLibrary::Unload()
|
||||
|
||||
mSources.clear();
|
||||
|
||||
for (lcTexture* Texture : mTextures)
|
||||
delete Texture;
|
||||
|
||||
mTextures.clear();
|
||||
mTextureManager->Clear();
|
||||
mTextureDiskEntries.clear();
|
||||
|
||||
for (std::map<QString, int>& Entries : mTextureArchiveEntries)
|
||||
@@ -505,55 +504,6 @@ bool lcPiecesLibrary::RemapProjectPiece(PieceInfo* Info, const QString& ProjectD
|
||||
return true;
|
||||
}
|
||||
|
||||
QString lcPiecesLibrary::FindProjectTextureFile(const QString& ProjectPath, const QString& TextureName)
|
||||
{
|
||||
QDir Directory(ProjectPath);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
const auto SkipEmptyParts = Qt::SkipEmptyParts;
|
||||
#else
|
||||
const auto SkipEmptyParts = QString::SplitBehavior::SkipEmptyParts;
|
||||
#endif
|
||||
const QStringList PathComponents = (TextureName + QLatin1String(".png")).split(QLatin1Char('/'), SkipEmptyParts);
|
||||
|
||||
for (int ComponentIdx = 0; ComponentIdx < PathComponents.size(); ComponentIdx++)
|
||||
{
|
||||
const QString& Component = PathComponents[ComponentIdx];
|
||||
const bool LastComponent = ComponentIdx == PathComponents.size() - 1;
|
||||
|
||||
if (Component == QLatin1String(".") || Component == QLatin1String(".."))
|
||||
{
|
||||
if (LastComponent || !Directory.cd(Component))
|
||||
return QString();
|
||||
continue;
|
||||
}
|
||||
|
||||
QFileInfo Entry(Directory, Component);
|
||||
if (LastComponent ? !Entry.isFile() : !Entry.isDir())
|
||||
{
|
||||
Entry = QFileInfo();
|
||||
for (const QFileInfo& Candidate : Directory.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System))
|
||||
{
|
||||
if (Candidate.fileName().compare(Component, Qt::CaseInsensitive) == 0 &&
|
||||
(LastComponent ? Candidate.isFile() : Candidate.isDir()))
|
||||
{
|
||||
Entry = Candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (LastComponent)
|
||||
return Entry.isFile() ? Entry.absoluteFilePath() : QString();
|
||||
|
||||
if (!Entry.isDir())
|
||||
return QString();
|
||||
|
||||
Directory.setPath(Entry.absoluteFilePath());
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
lcTexture* lcPiecesLibrary::FindTexture(const char* TextureName, Project* CurrentProject, bool SearchProjectFolder)
|
||||
{
|
||||
QString ProjectPath;
|
||||
@@ -584,18 +534,68 @@ lcTexture* lcPiecesLibrary::FindTexture(const char* TextureName, Project* Curren
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
lcTexture* lcPiecesLibrary::FindTextureDeferred(const char* TextureName, const std::vector<QString>& SearchDirectories, const std::map<QString, std::shared_ptr<const lcEmbeddedData>>& EmbeddedData)
|
||||
QString lcPiecesLibrary::FindProjectTextureFile(const QString& ProjectPath, const QString& TextureName)
|
||||
{
|
||||
QDir Directory(ProjectPath);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
const auto SkipEmptyParts = Qt::SkipEmptyParts;
|
||||
#else
|
||||
const auto SkipEmptyParts = QString::SplitBehavior::SkipEmptyParts;
|
||||
#endif
|
||||
const QStringList PathComponents = (TextureName + QLatin1String(".png")).split(QLatin1Char('/'), SkipEmptyParts);
|
||||
|
||||
for (int ComponentIdx = 0; ComponentIdx < PathComponents.size(); ComponentIdx++)
|
||||
{
|
||||
const QString& Component = PathComponents[ComponentIdx];
|
||||
const bool LastComponent = ComponentIdx == PathComponents.size() - 1;
|
||||
|
||||
if (Component == QLatin1String(".") || Component == QLatin1String(".."))
|
||||
{
|
||||
if (LastComponent || !Directory.cd(Component))
|
||||
return QString();
|
||||
continue;
|
||||
}
|
||||
|
||||
QFileInfo Entry(Directory, Component);
|
||||
|
||||
if (LastComponent ? !Entry.isFile() : !Entry.isDir())
|
||||
{
|
||||
Entry = QFileInfo();
|
||||
|
||||
for (const QFileInfo& Candidate : Directory.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System))
|
||||
{
|
||||
if (Candidate.fileName().compare(Component, Qt::CaseInsensitive) == 0 &&
|
||||
(LastComponent ? Candidate.isFile() : Candidate.isDir()))
|
||||
{
|
||||
Entry = Candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (LastComponent)
|
||||
return Entry.isFile() ? Entry.absoluteFilePath() : QString();
|
||||
|
||||
if (!Entry.isDir())
|
||||
return QString();
|
||||
|
||||
Directory.setPath(Entry.absoluteFilePath());
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
lcFileSourceSnapshot lcPiecesLibrary::ResolveTextureSource(const char* TextureName, const std::vector<QString>& SearchDirectories, const std::map<QString, std::shared_ptr<const lcEmbeddedData>>& EmbeddedData, QString& ProjectPath)
|
||||
{
|
||||
ProjectPath.clear();
|
||||
|
||||
if (!TextureName[0] || strlen(TextureName) >= LC_TEXTURE_NAME_LEN)
|
||||
return nullptr;
|
||||
return {};
|
||||
|
||||
QMutexLocker LoadLock(&mLoadMutex);
|
||||
|
||||
std::shared_ptr<const lcEmbeddedData> SelectedData;
|
||||
QString FilePath;
|
||||
QString ResolvedProjectPath;
|
||||
int ArchiveType = -1;
|
||||
int ArchiveIndex = -1;
|
||||
const bool ArchiveLibrary = static_cast<bool>(mZipFiles[static_cast<int>(lcZipFileType::Official)]);
|
||||
lcFileSourceSnapshot Source;
|
||||
const QString Name = QString::fromLatin1(TextureName);
|
||||
const QString Candidates[] = { QLatin1String("textures/") + Name, Name };
|
||||
const QString Folders[] = { QStringLiteral("p/"), QStringLiteral("parts/"), QStringLiteral("models/"), QStringLiteral("unofficial/p/"), QStringLiteral("unofficial/parts/") };
|
||||
@@ -609,7 +609,7 @@ lcTexture* lcPiecesLibrary::FindTextureDeferred(const char* TextureName, const s
|
||||
|
||||
if (Embedded != EmbeddedData.end())
|
||||
{
|
||||
SelectedData = Embedded->second;
|
||||
Source.EmbeddedData = Embedded->second;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -618,21 +618,21 @@ lcTexture* lcPiecesLibrary::FindTextureDeferred(const char* TextureName, const s
|
||||
if (Directory.isEmpty())
|
||||
continue;
|
||||
|
||||
FilePath = FindProjectTextureFile(Directory, Candidate);
|
||||
Source.FilePath = FindProjectTextureFile(Directory, Candidate);
|
||||
|
||||
if (!FilePath.isEmpty())
|
||||
if (!Source.FilePath.isEmpty())
|
||||
{
|
||||
ResolvedProjectPath = Directory;
|
||||
ProjectPath = Directory;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!FilePath.isEmpty())
|
||||
if (!Source.FilePath.isEmpty())
|
||||
break;
|
||||
|
||||
for (const QString& Folder : Folders)
|
||||
{
|
||||
if (mZipFiles[static_cast<int>(lcZipFileType::Official)])
|
||||
if (ArchiveLibrary)
|
||||
{
|
||||
const bool Unofficial = Folder.startsWith(QLatin1String("unofficial/"));
|
||||
const int Type = static_cast<int>(Unofficial ? lcZipFileType::Unofficial : lcZipFileType::Official);
|
||||
@@ -643,12 +643,13 @@ lcTexture* lcPiecesLibrary::FindTextureDeferred(const char* TextureName, const s
|
||||
else
|
||||
EntryName = QLatin1String("ldraw/") + Folder + Candidate + QLatin1String(".png");
|
||||
|
||||
const auto Entry = mTextureArchiveEntries[Type].find(QDir::cleanPath(EntryName).toUpper());
|
||||
const std::map<QString, int>& Entries = mTextureArchiveEntries[Type];
|
||||
const auto Entry = Entries.find(QDir::cleanPath(EntryName).toUpper());
|
||||
|
||||
if (Entry != mTextureArchiveEntries[Type].end())
|
||||
if (Entry != Entries.end())
|
||||
{
|
||||
ArchiveType = Type;
|
||||
ArchiveIndex = Entry->second;
|
||||
Source.ArchiveType = Type;
|
||||
Source.ArchiveIndex = Entry->second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -659,136 +660,107 @@ lcTexture* lcPiecesLibrary::FindTextureDeferred(const char* TextureName, const s
|
||||
|
||||
if (Entry != mTextureDiskEntries.end())
|
||||
{
|
||||
FilePath = Entry->second;
|
||||
Source.FilePath = Entry->second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!FilePath.isEmpty() || ArchiveIndex != -1)
|
||||
if (!Source.FilePath.isEmpty() || Source.ArchiveIndex != -1)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!FilePath.isEmpty())
|
||||
if (!Source.FilePath.isEmpty())
|
||||
{
|
||||
const QString CanonicalPath = QFileInfo(FilePath).canonicalFilePath();
|
||||
FilePath = CanonicalPath.isEmpty() ? QFileInfo(FilePath).absoluteFilePath() : CanonicalPath;
|
||||
const QString CanonicalPath = QFileInfo(Source.FilePath).canonicalFilePath();
|
||||
Source.FilePath = CanonicalPath.isEmpty() ? QFileInfo(Source.FilePath).absoluteFilePath() : CanonicalPath;
|
||||
}
|
||||
|
||||
for (lcTexture* Texture : mTextures)
|
||||
{
|
||||
if (Texture->mEmbeddedData != SelectedData || Texture->mFilePath != FilePath || Texture->mArchiveType != ArchiveType || Texture->mArchiveIndex != ArchiveIndex)
|
||||
continue;
|
||||
|
||||
Texture->AddRef();
|
||||
|
||||
return Texture;
|
||||
}
|
||||
|
||||
if (!SelectedData && FilePath.isEmpty() && ArchiveIndex == -1)
|
||||
return nullptr;
|
||||
|
||||
lcTexture* Texture = new lcTexture(LC_TEXTURE_MIPMAPS);
|
||||
|
||||
lcstrcpy(Texture->mName, TextureName);
|
||||
Texture->mEmbeddedData = SelectedData;
|
||||
Texture->mFilePath = FilePath;
|
||||
Texture->mProjectPath = ResolvedProjectPath;
|
||||
Texture->mArchiveType = ArchiveType;
|
||||
Texture->mArchiveIndex = ArchiveIndex;
|
||||
Texture->SetTemporary(bool(SelectedData) || !ResolvedProjectPath.isEmpty());
|
||||
Texture->AddRef();
|
||||
|
||||
mTextures.push_back(Texture);
|
||||
|
||||
return Texture;
|
||||
}
|
||||
|
||||
lcTextureSourceSnapshot lcPiecesLibrary::SnapshotTextureSource(const lcTexture* Texture) const
|
||||
{
|
||||
lcTextureSourceSnapshot Source;
|
||||
|
||||
Source.EmbeddedData = Texture->mEmbeddedData;
|
||||
Source.FilePath = Texture->mFilePath;
|
||||
Source.ArchiveType = Texture->mArchiveType;
|
||||
Source.ArchiveIndex = Texture->mArchiveIndex;
|
||||
|
||||
return Source;
|
||||
}
|
||||
|
||||
lcResult<QByteArray> lcPiecesLibrary::DecodeEmbeddedData(const lcEmbeddedData& Data)
|
||||
lcTexture* lcPiecesLibrary::FindTextureDeferred(const char* TextureName, const std::vector<QString>& SearchDirectories, const std::map<QString, std::shared_ptr<const lcEmbeddedData>>& EmbeddedData)
|
||||
{
|
||||
if (!Data.Error.isEmpty())
|
||||
return lcUnexpected(Data.Error);
|
||||
QString ProjectPath;
|
||||
const lcFileSourceSnapshot Source = ResolveTextureSource(TextureName, SearchDirectories, EmbeddedData, ProjectPath);
|
||||
|
||||
if (!Data.HasValidEncoding())
|
||||
return lcUnexpected(tr("Invalid Base64 data for embedded texture '%1'.").arg(Data.FileName));
|
||||
|
||||
return QByteArray::fromBase64(Data.EncodedData);
|
||||
return mTextureManager->AcquireTexture(TextureName, Source, ProjectPath);
|
||||
}
|
||||
|
||||
lcTextureBuildResult lcPiecesLibrary::BuildTextureData(const lcTextureSourceSnapshot& Source)
|
||||
lcFileSourceSnapshot lcPiecesLibrary::SnapshotTextureSource(const lcTexture* Texture) const
|
||||
{
|
||||
lcTextureBuildResult Result = DecodeTextureData(Source);
|
||||
return mTextureManager->SnapshotTextureSource(Texture);
|
||||
}
|
||||
|
||||
if (Result.DecodedImage)
|
||||
Result.DecodedImage->ResizePow2();
|
||||
lcTextureBuildResult lcPiecesLibrary::BuildTextureData(const lcFileSourceSnapshot& Source)
|
||||
{
|
||||
const lcResult<std::unique_ptr<lcFile>> File = OpenFile(Source);
|
||||
|
||||
if (!File)
|
||||
{
|
||||
lcTextureBuildResult Result;
|
||||
Result.Error = lcTextureLoadError::DecodeFailed;
|
||||
Result.ErrorDetails = File.error();
|
||||
return Result;
|
||||
}
|
||||
|
||||
lcTextureBuildResult Result = mTextureManager->BuildTextureData(*File.value());
|
||||
|
||||
if (!Result.DecodedImage && Source.EmbeddedData)
|
||||
Result.ErrorDetails = tr("Could not decode embedded texture '%1'.").arg(Source.EmbeddedData->FileName);
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
lcTextureBuildResult lcPiecesLibrary::DecodeTextureData(const lcTextureSourceSnapshot& Source)
|
||||
lcResult<std::unique_ptr<lcFile>> lcPiecesLibrary::OpenFile(const lcFileSourceSnapshot& Source)
|
||||
{
|
||||
lcTextureBuildResult Result;
|
||||
|
||||
Result.DecodedImage.reset(new Image);
|
||||
|
||||
bool Loaded = false;
|
||||
|
||||
if (Source.EmbeddedData)
|
||||
{
|
||||
const lcResult<QByteArray> Decoded = DecodeEmbeddedData(*Source.EmbeddedData);
|
||||
const lcEmbeddedData& Data = *Source.EmbeddedData;
|
||||
|
||||
if (Decoded)
|
||||
{
|
||||
lcMemFile TextureFile;
|
||||
const QByteArray& Bytes = Decoded.value();
|
||||
TextureFile.WriteBuffer(Bytes.constData(), Bytes.size());
|
||||
TextureFile.Seek(0, SEEK_SET);
|
||||
Loaded = Result.DecodedImage->FileLoad(TextureFile);
|
||||
if (!Data.Error.isEmpty())
|
||||
return lcUnexpected(Data.Error);
|
||||
|
||||
if (!Loaded)
|
||||
Result.ErrorDetails = tr("Could not decode embedded texture '%1'.").arg(Source.EmbeddedData->FileName);
|
||||
}
|
||||
else
|
||||
Result.ErrorDetails = Decoded.error();
|
||||
if (!Data.HasValidEncoding())
|
||||
return lcUnexpected(tr("Invalid Base64 data for embedded file '%1'.").arg(Data.FileName));
|
||||
|
||||
const QByteArray Bytes = QByteArray::fromBase64(Data.EncodedData);
|
||||
std::unique_ptr<lcMemFile> File(new lcMemFile);
|
||||
|
||||
if (File->WriteBuffer(Bytes.constData(), Bytes.size()) != static_cast<size_t>(Bytes.size()))
|
||||
return lcUnexpected(tr("Could not read embedded file '%1'.").arg(Data.FileName));
|
||||
|
||||
File->Seek(0, SEEK_SET);
|
||||
return std::unique_ptr<lcFile>(std::move(File));
|
||||
}
|
||||
else if (!Source.FilePath.isEmpty())
|
||||
Loaded = Result.DecodedImage->FileLoad(Source.FilePath);
|
||||
else if (Source.ArchiveType >= 0 && Source.ArchiveType < static_cast<int>(lcZipFileType::Count) && Source.ArchiveIndex >= 0)
|
||||
|
||||
if (!Source.FilePath.isEmpty())
|
||||
{
|
||||
lcMemFile TextureFile;
|
||||
bool Extracted = false;
|
||||
std::unique_ptr<lcDiskFile> File(new lcDiskFile(Source.FilePath));
|
||||
|
||||
{
|
||||
QMutexLocker TextureLock(&mTextureMutex);
|
||||
const std::unique_ptr<lcZipFile>& Archive = mZipFiles[Source.ArchiveType];
|
||||
if (!File->Open(QIODevice::ReadOnly))
|
||||
return lcUnexpected(tr("Could not open file '%1'.").arg(Source.FilePath));
|
||||
|
||||
if (Archive)
|
||||
Extracted = Archive->ExtractFile(Source.ArchiveIndex, TextureFile);
|
||||
}
|
||||
|
||||
if (Extracted)
|
||||
Loaded = Result.DecodedImage->FileLoad(TextureFile);
|
||||
return std::unique_ptr<lcFile>(std::move(File));
|
||||
}
|
||||
|
||||
if (!Loaded)
|
||||
{
|
||||
Result.DecodedImage.reset();
|
||||
Result.Error = lcTextureLoadError::DecodeFailed;
|
||||
}
|
||||
if (Source.ArchiveType < 0 || Source.ArchiveType >= static_cast<int>(lcZipFileType::Count) || Source.ArchiveIndex < 0)
|
||||
return lcUnexpected(tr("Invalid archive file source."));
|
||||
|
||||
return Result;
|
||||
std::unique_ptr<lcMemFile> File(new lcMemFile);
|
||||
|
||||
// Source archives are stable until workers are drained; extraction locks
|
||||
// the selected archive internally without serializing unrelated archives.
|
||||
const std::unique_ptr<lcZipFile>& Archive = mZipFiles[Source.ArchiveType];
|
||||
|
||||
if (!Archive || static_cast<size_t>(Source.ArchiveIndex) >= Archive->mFiles.size())
|
||||
return lcUnexpected(tr("Could not read archive entry %1.").arg(Source.ArchiveIndex));
|
||||
|
||||
if (!Archive->ExtractFile(Source.ArchiveIndex, *File))
|
||||
return lcUnexpected(tr("Could not read archive file '%1'.").arg(QString::fromLatin1(Archive->mFiles[Source.ArchiveIndex].file_name)));
|
||||
|
||||
File->Seek(0, SEEK_SET);
|
||||
return std::unique_ptr<lcFile>(std::move(File));
|
||||
}
|
||||
|
||||
bool lcPiecesLibrary::Load(const QString& LibraryPath, bool ShowProgress)
|
||||
@@ -2305,32 +2277,31 @@ void lcPiecesLibrary::UnloadUnusedParts()
|
||||
|
||||
bool lcPiecesLibrary::LoadTexture(lcTexture* Texture)
|
||||
{
|
||||
lcTextureBuildResult Result = DecodeTextureData(SnapshotTextureSource(Texture));
|
||||
const lcFileSourceSnapshot Source = SnapshotTextureSource(Texture);
|
||||
const lcResult<std::unique_ptr<lcFile>> File = OpenFile(Source);
|
||||
|
||||
if (!Result.DecodedImage)
|
||||
if (!File)
|
||||
{
|
||||
Texture->mState = lcTextureState::Failed;
|
||||
Texture->mLoadFailure = Result.Error;
|
||||
Texture->mLoadFailureDetails = std::move(Result.ErrorDetails);
|
||||
Texture->SetFailed(lcTextureLoadError::DecodeFailed, File.error());
|
||||
return false;
|
||||
}
|
||||
|
||||
Texture->SetImage(std::move(*Result.DecodedImage));
|
||||
const bool Loaded = mTextureManager->LoadTexture(Texture, *File.value());
|
||||
|
||||
return true;
|
||||
if (!Loaded && Source.EmbeddedData)
|
||||
Texture->SetFailed(Texture->mLoadFailure, tr("Could not decode embedded texture '%1'.").arg(Source.EmbeddedData->FileName));
|
||||
|
||||
return Loaded;
|
||||
}
|
||||
|
||||
void lcPiecesLibrary::AddTextureReference(lcTexture* Texture)
|
||||
{
|
||||
mTextureManager->AddTextureReference(Texture);
|
||||
}
|
||||
|
||||
void lcPiecesLibrary::ReleaseTexture(lcTexture* Texture)
|
||||
{
|
||||
QMutexLocker LoadLock(&mLoadMutex);
|
||||
|
||||
if (Texture->Release() == 0 && Texture->IsTemporary())
|
||||
{
|
||||
std::vector<lcTexture*>::iterator TextureIt = std::find(mTextures.begin(), mTextures.end(), Texture);
|
||||
if (TextureIt != mTextures.end())
|
||||
mTextures.erase(TextureIt);
|
||||
delete Texture;
|
||||
}
|
||||
mTextureManager->ReleaseTexture(Texture);
|
||||
}
|
||||
|
||||
bool lcPiecesLibrary::SupportsStudStyle() const
|
||||
|
||||
+24
-13
@@ -14,12 +14,21 @@ class lcZipFile;
|
||||
class lcLibraryMeshData;
|
||||
class lcThumbnailManager;
|
||||
class lcAssetLoader;
|
||||
class lcTextureManager;
|
||||
struct lcPartSourceSnapshot;
|
||||
struct lcPartBuildResult;
|
||||
struct lcEmbeddedData;
|
||||
struct lcTextureSourceSnapshot;
|
||||
struct lcTextureBuildResult;
|
||||
|
||||
// Copied before dispatch; opening a source never consults live project state.
|
||||
struct lcFileSourceSnapshot
|
||||
{
|
||||
std::shared_ptr<const lcEmbeddedData> EmbeddedData;
|
||||
QString FilePath;
|
||||
int ArchiveType = -1;
|
||||
int ArchiveIndex = -1;
|
||||
};
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
using lcLibraryLoadMutex = QRecursiveMutex;
|
||||
#else
|
||||
@@ -234,9 +243,6 @@ public:
|
||||
lcTexture* FindTexture(const char* TextureName, Project* CurrentProject, bool SearchProjectFolder);
|
||||
// UI-thread lookup/acquisition only, without decoding or upload; caller releases the reference.
|
||||
lcTexture* FindTextureDeferred(const char* TextureName, const std::vector<QString>& SearchDirectories, const std::map<QString, std::shared_ptr<const lcEmbeddedData>>& EmbeddedData);
|
||||
lcTextureSourceSnapshot SnapshotTextureSource(const lcTexture* Texture) const;
|
||||
// Worker decode service: returns private pixels/errors without GL upload or publication.
|
||||
lcTextureBuildResult BuildTextureData(const lcTextureSourceSnapshot& Source);
|
||||
bool LoadTexture(lcTexture* Texture);
|
||||
void ReleaseTexture(lcTexture* Texture);
|
||||
|
||||
@@ -249,6 +255,9 @@ public:
|
||||
std::vector<PieceInfo*> GetPartsFromSet(const std::vector<std::string>& PartIds) const;
|
||||
std::string GetPartId(const PieceInfo* Info) const;
|
||||
|
||||
// Opens a resolved source on the reading thread, positioned at zero.
|
||||
// Caller owns the file; archive replacement requires draining workers first.
|
||||
lcResult<std::unique_ptr<lcFile>> OpenFile(const lcFileSourceSnapshot& Source);
|
||||
void GetPrimitiveFile(lcLibraryPrimitive* Primitive, std::function<void(lcFile& File)> Callback);
|
||||
void GetPieceFile(const char* FileName, std::function<void(lcFile& File)> Callback);
|
||||
|
||||
@@ -283,10 +292,6 @@ public:
|
||||
std::map<std::string, PieceInfo*> mPieces;
|
||||
int mNumOfficialPieces;
|
||||
|
||||
std::vector<lcTexture*> mTextures;
|
||||
std::map<QString, QString> mTextureDiskEntries;
|
||||
std::map<QString, int> mTextureArchiveEntries[static_cast<int>(lcZipFileType::Count)];
|
||||
|
||||
QDir mLibraryDir;
|
||||
|
||||
bool mBuffersDirty;
|
||||
@@ -300,6 +305,14 @@ signals:
|
||||
void GeneratedMeshSettled(PieceInfo* Info);
|
||||
void ColorsLoaded();
|
||||
|
||||
private:
|
||||
lcFileSourceSnapshot SnapshotTextureSource(const lcTexture* Texture) const;
|
||||
// Worker decode service: returns private pixels/errors without GL upload or publication.
|
||||
lcTextureBuildResult BuildTextureData(const lcFileSourceSnapshot& Source);
|
||||
void AddTextureReference(lcTexture* Texture);
|
||||
static QString FindProjectTextureFile(const QString& ProjectPath, const QString& TextureName);
|
||||
lcFileSourceSnapshot ResolveTextureSource(const char* TextureName, const std::vector<QString>& SearchDirectories, const std::map<QString, std::shared_ptr<const lcEmbeddedData>>& EmbeddedData, QString& ProjectPath);
|
||||
|
||||
protected:
|
||||
friend class lcAssetLoader;
|
||||
|
||||
@@ -319,9 +332,6 @@ protected:
|
||||
bool ReadDirectoryCacheFile(const QString& FileName, lcMemFile& CacheFile);
|
||||
bool WriteDirectoryCacheFile(const QString& FileName, lcMemFile& CacheFile);
|
||||
|
||||
static lcResult<QByteArray> DecodeEmbeddedData(const lcEmbeddedData& Data);
|
||||
lcTextureBuildResult DecodeTextureData(const lcTextureSourceSnapshot& Source);
|
||||
static QString FindProjectTextureFile(const QString& ProjectPath, const QString& TextureName);
|
||||
static bool IsStudPrimitive(const char* FileName);
|
||||
static bool IsStudStylePrimitive(const char* FileName);
|
||||
static qint32 MeshCacheSettingsKey(lcStudStyle StudStyle, bool StudCylinderColorEnabled);
|
||||
@@ -334,6 +344,8 @@ protected:
|
||||
void ReleaseBuffers();
|
||||
|
||||
std::vector<std::unique_ptr<lcLibrarySource>> mSources;
|
||||
std::map<QString, QString> mTextureDiskEntries;
|
||||
std::map<QString, int> mTextureArchiveEntries[static_cast<int>(lcZipFileType::Count)];
|
||||
// Each project maps its local filenames to PieceInfo pointers. This map tracks the
|
||||
// project for each local PieceInfo, or nullptr after that project is destroyed.
|
||||
std::unordered_map<PieceInfo*, Project*> mProjectPieces;
|
||||
@@ -348,8 +360,6 @@ protected:
|
||||
std::unordered_map<QThread*, std::vector<lcLibraryPrimitive*>> mPrimitiveLoadStacks;
|
||||
std::unordered_map<lcLibraryPrimitive*, lcLibraryPrimitive*> mPrimitiveDependencies;
|
||||
|
||||
// Serializes texture source access, including archive extraction by workers.
|
||||
QMutex mTextureMutex;
|
||||
// Sparse errors avoid storing a QString in every catalog identity.
|
||||
mutable QMutex mPieceErrorMutex;
|
||||
std::unordered_map<const PieceInfo*, QString> mFailedPartErrors;
|
||||
@@ -357,6 +367,7 @@ protected:
|
||||
lcStudStyle mStudStyle;
|
||||
bool mStudCylinderColorEnabled;
|
||||
|
||||
std::unique_ptr<lcTextureManager> mTextureManager;
|
||||
std::unique_ptr<lcAssetLoader> mAssetLoader;
|
||||
std::unique_ptr<lcThumbnailManager> mThumbnailManager;
|
||||
std::unique_ptr<lcMesh> mLoadingMesh;
|
||||
|
||||
+11
-6
@@ -2,6 +2,7 @@
|
||||
#include "lc_texture.h"
|
||||
#include "lc_application.h"
|
||||
#include "lc_library.h"
|
||||
#include "lc_file.h"
|
||||
#include "image.h"
|
||||
#include "lc_glextensions.h"
|
||||
#include "lc_string.h"
|
||||
@@ -170,8 +171,7 @@ bool lcTexture::Load(const QString& FileName, int Flags)
|
||||
|
||||
if (!Image.FileLoad(FileName))
|
||||
{
|
||||
mState = lcTextureState::Failed;
|
||||
mLoadFailure = lcTextureLoadError::DecodeFailed;
|
||||
SetFailed(lcTextureLoadError::DecodeFailed, QString());
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -187,8 +187,7 @@ bool lcTexture::Load(lcMemFile& File, int Flags)
|
||||
|
||||
if (!Image.FileLoad(File))
|
||||
{
|
||||
mState = lcTextureState::Failed;
|
||||
mLoadFailure = lcTextureLoadError::DecodeFailed;
|
||||
SetFailed(lcTextureLoadError::DecodeFailed, QString());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -219,6 +218,13 @@ void lcTexture::DiscardDecodedImage()
|
||||
mImages.clear();
|
||||
}
|
||||
|
||||
void lcTexture::SetFailed(lcTextureLoadError Error, QString Details)
|
||||
{
|
||||
mState = lcTextureState::Failed;
|
||||
mLoadFailure = Error;
|
||||
mLoadFailureDetails = std::move(Details);
|
||||
}
|
||||
|
||||
void lcTexture::SetImage(std::vector<Image>&& Images, int Flags)
|
||||
{
|
||||
mImages = std::move(Images);
|
||||
@@ -253,8 +259,7 @@ void lcTexture::Upload(lcContext* Context)
|
||||
mTexture = 0;
|
||||
}
|
||||
|
||||
mState = lcTextureState::Failed;
|
||||
mLoadFailure = lcTextureLoadError::UploadFailed;
|
||||
SetFailed(lcTextureLoadError::UploadFailed, QString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -58,6 +58,8 @@ public:
|
||||
void SetImage(std::vector<Image>&& Images, int Flags = 0);
|
||||
void AdoptDecodedImage(Image&& Image, int Flags = 0);
|
||||
void DiscardDecodedImage();
|
||||
// Records a failure without discarding pixels retained for upload retries.
|
||||
void SetFailed(lcTextureLoadError Error, QString Details);
|
||||
void Upload(lcContext* Context);
|
||||
void Unload();
|
||||
|
||||
@@ -66,7 +68,7 @@ public:
|
||||
return mTexture != 0 || !mImages.empty();
|
||||
}
|
||||
|
||||
// Only to be called by lcPiecesLibrary.
|
||||
// Reference bookkeeping belongs to lcTextureManager.
|
||||
void AddRef()
|
||||
{
|
||||
mRefCount.ref();
|
||||
@@ -77,7 +79,7 @@ public:
|
||||
return mRefCount.loadRelaxed();
|
||||
}
|
||||
|
||||
// Only to be called by lcPiecesLibrary.
|
||||
// Reference bookkeeping belongs to lcTextureManager.
|
||||
bool Release()
|
||||
{
|
||||
const bool InUse = mRefCount.deref();
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
#include "lc_global.h"
|
||||
#include "lc_texturemanager.h"
|
||||
#include "lc_library.h"
|
||||
#include "lc_file.h"
|
||||
#include "lc_string.h"
|
||||
|
||||
lcTextureBuildResult::~lcTextureBuildResult() = default;
|
||||
|
||||
lcTextureManager::~lcTextureManager()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
void lcTextureManager::Clear()
|
||||
{
|
||||
QMutexLocker Lock(&mMutex);
|
||||
|
||||
for (lcTexture* Texture : mTextures)
|
||||
delete Texture;
|
||||
|
||||
mTextures.clear();
|
||||
}
|
||||
|
||||
void lcTextureManager::AddTextureReference(lcTexture* Texture)
|
||||
{
|
||||
QMutexLocker Lock(&mMutex);
|
||||
|
||||
Texture->AddRef();
|
||||
}
|
||||
|
||||
lcTexture* lcTextureManager::AcquireTexture(const char* TextureName, const lcFileSourceSnapshot& Source, const QString& ProjectPath)
|
||||
{
|
||||
if (!TextureName[0] || strlen(TextureName) >= LC_TEXTURE_NAME_LEN)
|
||||
return nullptr;
|
||||
|
||||
if (!Source.EmbeddedData && Source.FilePath.isEmpty() && Source.ArchiveIndex == -1)
|
||||
return nullptr;
|
||||
|
||||
QMutexLocker Lock(&mMutex);
|
||||
|
||||
for (lcTexture* Texture : mTextures)
|
||||
{
|
||||
if (Texture->mEmbeddedData != Source.EmbeddedData || Texture->mFilePath != Source.FilePath || Texture->mArchiveType != Source.ArchiveType || Texture->mArchiveIndex != Source.ArchiveIndex)
|
||||
continue;
|
||||
|
||||
Texture->AddRef();
|
||||
|
||||
return Texture;
|
||||
}
|
||||
|
||||
lcTexture* Texture = new lcTexture(LC_TEXTURE_MIPMAPS);
|
||||
|
||||
lcstrcpy(Texture->mName, TextureName);
|
||||
Texture->mEmbeddedData = Source.EmbeddedData;
|
||||
Texture->mFilePath = Source.FilePath;
|
||||
Texture->mProjectPath = ProjectPath;
|
||||
Texture->mArchiveType = Source.ArchiveType;
|
||||
Texture->mArchiveIndex = Source.ArchiveIndex;
|
||||
Texture->SetTemporary(bool(Source.EmbeddedData) || !ProjectPath.isEmpty());
|
||||
Texture->AddRef();
|
||||
|
||||
mTextures.push_back(Texture);
|
||||
|
||||
return Texture;
|
||||
}
|
||||
|
||||
lcFileSourceSnapshot lcTextureManager::SnapshotTextureSource(const lcTexture* Texture) const
|
||||
{
|
||||
lcFileSourceSnapshot Source;
|
||||
|
||||
Source.EmbeddedData = Texture->mEmbeddedData;
|
||||
Source.FilePath = Texture->mFilePath;
|
||||
Source.ArchiveType = Texture->mArchiveType;
|
||||
Source.ArchiveIndex = Texture->mArchiveIndex;
|
||||
|
||||
return Source;
|
||||
}
|
||||
|
||||
lcTextureBuildResult lcTextureManager::BuildTextureData(lcFile& File)
|
||||
{
|
||||
lcTextureBuildResult Result = DecodeTextureData(File);
|
||||
|
||||
if (Result.DecodedImage)
|
||||
Result.DecodedImage->ResizePow2();
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
lcTextureBuildResult lcTextureManager::DecodeTextureData(lcFile& File)
|
||||
{
|
||||
lcTextureBuildResult Result;
|
||||
|
||||
Result.DecodedImage.reset(new Image);
|
||||
|
||||
if (!Result.DecodedImage->FileLoad(File))
|
||||
{
|
||||
Result.DecodedImage.reset();
|
||||
Result.Error = lcTextureLoadError::DecodeFailed;
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
bool lcTextureManager::LoadTexture(lcTexture* Texture, lcFile& File)
|
||||
{
|
||||
lcTextureBuildResult Result = DecodeTextureData(File);
|
||||
|
||||
if (!Result.DecodedImage)
|
||||
{
|
||||
Texture->SetFailed(Result.Error, std::move(Result.ErrorDetails));
|
||||
return false;
|
||||
}
|
||||
|
||||
Texture->SetImage(std::move(*Result.DecodedImage));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void lcTextureManager::ReleaseTexture(lcTexture* Texture)
|
||||
{
|
||||
QMutexLocker Lock(&mMutex);
|
||||
|
||||
if (Texture->Release() == 0 && Texture->IsTemporary())
|
||||
{
|
||||
std::vector<lcTexture*>::iterator TextureIt = std::find(mTextures.begin(), mTextures.end(), Texture);
|
||||
if (TextureIt != mTextures.end())
|
||||
mTextures.erase(TextureIt);
|
||||
delete Texture;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "lc_texture.h"
|
||||
|
||||
class lcFile;
|
||||
struct lcFileSourceSnapshot;
|
||||
|
||||
struct lcTextureBuildResult
|
||||
{
|
||||
lcTextureBuildResult() = default;
|
||||
~lcTextureBuildResult();
|
||||
|
||||
lcTextureBuildResult(lcTextureBuildResult&&) = default;
|
||||
lcTextureBuildResult& operator=(lcTextureBuildResult&&) = default;
|
||||
lcTextureBuildResult(const lcTextureBuildResult&) = delete;
|
||||
lcTextureBuildResult& operator=(const lcTextureBuildResult&) = delete;
|
||||
|
||||
std::unique_ptr<Image> DecodedImage;
|
||||
lcTextureLoadError Error{};
|
||||
QString ErrorDetails;
|
||||
};
|
||||
|
||||
// Owns texture identities, references, and CPU decoding of resolved sources.
|
||||
// lcAssetLoader owns pending requests and asynchronous upload/publication.
|
||||
class lcTextureManager
|
||||
{
|
||||
public:
|
||||
lcTextureManager() = default;
|
||||
~lcTextureManager();
|
||||
|
||||
lcTextureManager(const lcTextureManager&) = delete;
|
||||
lcTextureManager(lcTextureManager&&) = delete;
|
||||
lcTextureManager& operator=(const lcTextureManager&) = delete;
|
||||
lcTextureManager& operator=(lcTextureManager&&) = delete;
|
||||
|
||||
// The owner drains workers and releases mesh dependencies before clearing.
|
||||
void Clear();
|
||||
|
||||
// UI-thread acquisition only; each returned reference must be released.
|
||||
lcTexture* AcquireTexture(const char* Name, const lcFileSourceSnapshot& Source, const QString& ProjectPath);
|
||||
|
||||
void AddTextureReference(lcTexture* Texture);
|
||||
void ReleaseTexture(lcTexture* Texture);
|
||||
|
||||
lcFileSourceSnapshot SnapshotTextureSource(const lcTexture* Texture) const;
|
||||
|
||||
// Worker-safe private CPU build; never publishes or uploads the result.
|
||||
lcTextureBuildResult BuildTextureData(lcFile& File);
|
||||
|
||||
// Existing synchronous path; SetImage retains its thread-dependent upload behavior.
|
||||
bool LoadTexture(lcTexture* Texture, lcFile& File);
|
||||
|
||||
private:
|
||||
lcTextureBuildResult DecodeTextureData(lcFile& File);
|
||||
|
||||
QMutex mMutex;
|
||||
std::vector<lcTexture*> mTextures;
|
||||
};
|
||||
+49
-1
@@ -76,7 +76,8 @@ new evidence and update this record with any agreed change.
|
||||
|
||||
| Owner | Responsibility | Source |
|
||||
| --- | --- | --- |
|
||||
| `lcPiecesLibrary` | Asset identities, references, source discovery, archives, primitive and mesh caches, settings, shared mesh buffers, and public forwarding APIs. | [lc_library.h](../common/lc_library.h), [lc_library.cpp](../common/lc_library.cpp) |
|
||||
| `lcPiecesLibrary` | Piece identities and references, all source discovery and lookup, texture file indexes, archives, primitive and mesh caches, settings, shared mesh buffers, and public forwarding APIs. Owns the texture manager and supplies its archive reader. | [lc_library.h](../common/lc_library.h), [lc_library.cpp](../common/lc_library.cpp) |
|
||||
| `lcTextureManager` | Texture identities and references, snapshots, and private CPU decoding of resolved sources. | [lc_texturemanager.h](../common/lc_texturemanager.h), [lc_texturemanager.cpp](../common/lc_texturemanager.cpp) |
|
||||
| `lcAssetLoader` | Pending requests, worker scheduling, private results, texture upload, publication, cancellation, readiness waits, and deferred settlement notifications. | [lc_assetloader.h](../common/lc_assetloader.h), [lc_assetloader.cpp](../common/lc_assetloader.cpp) |
|
||||
| `PieceInfo` / `lcTexture` | Published geometry or texture data, readiness state, and asset metadata. | [pieceinf.h](../common/pieceinf.h), [lc_texture.h](../common/lc_texture.h) |
|
||||
| Projects, models, and views | Project-local names, required-asset traversal, generated-piece ownership, bounds, fallback display, cameras, and redraws. | [project.cpp](../common/project.cpp), [lc_model.cpp](../common/lc_model.cpp), [piece.cpp](../common/piece.cpp) |
|
||||
@@ -86,6 +87,53 @@ asset acquisition, release, scheduling, waiting, and invalidation. Workers use
|
||||
source snapshots and the library's build services; they do not consult the
|
||||
active project or publish into live models.
|
||||
|
||||
The texture manager is owned by the library and owns the only texture registry.
|
||||
`FindTextureDeferred()` resolves embedded data, project-directory files, and
|
||||
indexed library files in the library, then asks the manager to acquire an identity
|
||||
for that resolved source. Resolution preserves the prefixed search order and
|
||||
canonicalizes disk paths. The project directory is passed separately to acquisition
|
||||
so the manager preserves temporary-texture metadata without searching files itself.
|
||||
Library texture APIs forward reference changes, snapshots, decoding, and the
|
||||
existing synchronous loading path to the manager. `FindTexture()` still gathers
|
||||
the explicit project's context and waits through the loader. Texture requests,
|
||||
priority promotion, cancellation, upload/retries, and part dependency settlement
|
||||
remain in `lcAssetLoader`; its in-flight texture references are acquired through
|
||||
the manager. `lcFileSourceSnapshot` lives in `lc_library.h`; texture build results live
|
||||
in `lc_texturemanager.h`. File snapshots contain no project/texture metadata.
|
||||
The library's `SnapshotTextureSource()`, `BuildTextureData()`, and
|
||||
`AddTextureReference()` are private loader services, accessible through the
|
||||
existing `lcAssetLoader` friendship.
|
||||
|
||||
The library calls `OpenFile()` on the reading thread and passes the opened
|
||||
`lcFile&` to the texture manager for decoding. `BuildTextureData()` continues to
|
||||
run on a worker, so file opening and archive extraction stay on that worker. The
|
||||
manager stores no file-reader callback or library instance.
|
||||
Success returns an owned `lcFile` positioned at zero: disk sources retain
|
||||
an open `lcDiskFile`, archive entries and decoded embedded data use `lcMemFile`.
|
||||
Opening never searches for another source or parses its contents. Failures carry
|
||||
an error; the caller retains the result while consuming the file. Archive access
|
||||
is unlocked before image decoding, and returned memory files are independent of
|
||||
their archive or embedded source. Each `lcZipFile` serializes its own extraction;
|
||||
the library adds no archive-wide mutex. Archive replacement and closure remain
|
||||
protected by draining/pausing workers first. Texture identities use the
|
||||
manager's own mutex; source indexes and source resolution belong to the library.
|
||||
Library unload drains loader work, destroys meshes that release texture references,
|
||||
clears the texture manager, then closes archives. Workers must be drained before
|
||||
the manager is cleared or archive indexes are replaced.
|
||||
|
||||
`Image::FileLoad(lcFile&)` decodes disk files through their existing `QFile` and
|
||||
memory files through `QImage::loadFromData()`, without an additional device adapter
|
||||
or disk-to-memory copy. Disk cursor restoration runs on success and failure;
|
||||
memory decoding leaves the cursor unchanged and validates the remaining buffer
|
||||
length. Qt handles disk reads, seeking, EOF, and I/O errors. Files/cursors are
|
||||
private to each request, not shared between workers. The filename-based image
|
||||
entry point retains its existing behavior.
|
||||
|
||||
`lcTexture::SetFailed()` assigns readiness and failure diagnostics together,
|
||||
clearing stale details when none are supplied. Synchronous loading and loader
|
||||
failures use the same setter. It retains decoded pixels for bounded upload
|
||||
retries; terminal request handling explicitly discards them when required.
|
||||
|
||||
`FindPiece()` returns a borrowed identity, without acquiring a reference. Lookup
|
||||
checks the supplied project's name index, then the shared library catalog, then
|
||||
optionally an external file beside the project. Creating a missing identity
|
||||
|
||||
@@ -237,6 +237,7 @@ SOURCES += \
|
||||
common/lc_stringcache.cpp \
|
||||
common/lc_synth.cpp \
|
||||
common/lc_texture.cpp \
|
||||
common/lc_texturemanager.cpp \
|
||||
common/lc_thumbnailmanager.cpp \
|
||||
common/lc_timelinewidget.cpp \
|
||||
common/lc_traintrack.cpp \
|
||||
@@ -320,6 +321,7 @@ HEADERS += \
|
||||
common/lc_stringcache.h \
|
||||
common/lc_synth.h \
|
||||
common/lc_texture.h \
|
||||
common/lc_texturemanager.h \
|
||||
common/lc_thumbnailmanager.h \
|
||||
common/lc_timelinewidget.h \
|
||||
common/lc_traintrack.h \
|
||||
|
||||
Reference in new issue
Block a user