Unify mesh color resolution and section finalization

Cached meshes retained color-dependent flags and section ordering from the palette used when they were saved. Finalize fresh piece, synth, and cached mesh sections on the UI thread so color indices, flags, and ordering all reflect the current palette before publication.

Keep color codes in private mesh sections until finalization and remove separate color-code vectors, worker palette snapshots, and the early color-resolution path. Geometry conversion now runs without global color access or texture lookup callbacks.
This commit is contained in:
Leonardo Zide committed 2026-10-15 14:11:05 -07:00
1 parent f4fe4f8e87
commit 9f7e4d3a41
8 files changed
+84 -150

No files matched your search

+6 -34
View File
@@ -267,7 +267,7 @@ void lcAssetLoader::RunWorker()
if (RequestedPiece->MeshData)
{
Completed.PieceResult.Mesh.reset(RequestedPiece->MeshData->CreateMeshResolved(RequestedPiece->ColorTranslucency, RequestedPiece->DefaultColorIndex, nullptr));
Completed.PieceResult.Mesh.reset(RequestedPiece->MeshData->CreateMesh(nullptr));
RequestedPiece->MeshData.reset();
if (!Completed.PieceResult.Mesh)
@@ -289,7 +289,7 @@ void lcAssetLoader::RunWorker()
RequestedSynth->MeshData.reset();
else if (RequestedSynth->MeshData)
{
Completed.SynthMesh.reset(RequestedSynth->MeshData->CreateMeshResolved(RequestedSynth->ColorTranslucency, RequestedSynth->DefaultColorIndex, &RequestedSynth->Cancelled));
Completed.SynthMesh.reset(RequestedSynth->MeshData->CreateMesh(&RequestedSynth->Cancelled));
RequestedSynth->MeshData.reset();
}
else
@@ -476,8 +476,7 @@ void lcAssetLoader::ProcessPieceCompletion(Completion& Completed)
}
else if (Completed.PieceResult.MeshData)
{
// Register color indices on the UI thread, then queue the expensive mesh conversion.
ResolveMeshColors(*Completed.PieceResult.MeshData, RequestedPiece->ColorTranslucency, RequestedPiece->DefaultColorIndex);
// Queue the expensive geometry conversion; colors are finalized after it completes.
RequestedPiece->MeshData = std::move(Completed.PieceResult.MeshData);
Info->mState = lcPieceInfoState::Queued;
@@ -490,20 +489,9 @@ void lcAssetLoader::ProcessPieceCompletion(Completion& Completed)
}
else if (Completed.PieceResult.Mesh)
{
RequestedPiece->ColorTranslucency.clear();
RequestedPiece->StagedMesh = std::move(Completed.PieceResult.Mesh);
size_t ColorIndex = 0;
for (lcMeshLod& Lod : RequestedPiece->StagedMesh->mLods)
{
for (int SectionIdx = 0; SectionIdx < Lod.NumSections; SectionIdx++)
{
lcMeshSection& Section = Lod.Sections[SectionIdx];
if (Completed.PieceResult.FromCache)
Section.ColorIndex = lcGetColorIndex(Completed.PieceResult.ColorCodes[ColorIndex++]);
}
}
RequestedPiece->StagedMesh->FinalizeSections();
RequestedPiece->SaveCache = !Completed.PieceResult.FromCache;
}
@@ -549,8 +537,7 @@ void lcAssetLoader::ProcessSynthCompletion(Completion& Completed)
if (Completed.SynthMeshData)
{
// Resolve global colors here, then send expensive conversion back to a worker.
ResolveMeshColors(*Completed.SynthMeshData, RequestedSynth->ColorTranslucency, RequestedSynth->DefaultColorIndex);
// Queue the expensive geometry conversion; colors are finalized after it completes.
RequestedSynth->MeshData = std::move(Completed.SynthMeshData);
QMutexLocker QueueLock(&mQueueMutex);
@@ -568,8 +555,8 @@ void lcAssetLoader::ProcessSynthCompletion(Completion& Completed)
return;
}
RequestedSynth->ColorTranslucency.clear();
RequestedSynth->StagedMesh = std::move(Completed.SynthMesh);
RequestedSynth->StagedMesh->FinalizeSections();
const bool Ready = ResolveAndQueueMeshTextures(*RequestedSynth->StagedMesh, std::vector<QString>(),
std::map<QString, std::shared_ptr<const lcEmbeddedData>>(), RequestedSynth->LoadPriority, RequestedSynth->Error);
@@ -611,19 +598,6 @@ void lcAssetLoader::ProcessTextureCompletion(Completion& Completed)
}
}
void lcAssetLoader::ResolveMeshColors(lcLibraryMeshData& MeshData, std::vector<bool>& ColorTranslucency, int& DefaultColorIndex)
{
// Global color registration belongs on the UI thread; conversion uses this copied palette.
MeshData.ResolveColors();
ColorTranslucency.clear();
ColorTranslucency.reserve(gColorList.size());
for (const lcColor& Color : gColorList)
ColorTranslucency.push_back(Color.Translucent);
DefaultColorIndex = gDefaultColor;
}
bool lcAssetLoader::ResolveAndQueueMeshTextures(lcMesh& Mesh, const std::vector<QString>& SearchDirectories,
const std::map<QString, std::shared_ptr<const lcEmbeddedData>>& EmbeddedData, lcPieceLoadPriority LoadPriority, QString& Error)
{
@@ -1457,7 +1431,6 @@ void lcAssetLoader::PauseQueuedWork()
if (RequestedPiece->MeshData)
{
RequestedPiece->MeshData.reset();
RequestedPiece->ColorTranslucency.clear();
RequestedPiece->Info->mState = lcPieceInfoState::Queued;
}
@@ -1480,7 +1453,6 @@ void lcAssetLoader::PauseQueuedWork()
RequestedSynth->MeshData.reset();
RequestedSynth->StagedMesh.reset();
RequestedSynth->ColorTranslucency.clear();
if (std::find(mSynthQueue.begin(), mSynthQueue.end(), RequestedSynth) == mSynthQueue.end())
mSynthQueue.push_back(RequestedSynth);
-6
View File
@@ -42,7 +42,6 @@ struct lcPieceBuildResult
std::unique_ptr<lcLibraryMeshData> MeshData;
std::unique_ptr<lcMesh> Mesh;
std::vector<quint32> ColorCodes;
bool FromCache = false;
bool EmptyGeometry = false;
QString Error;
@@ -113,8 +112,6 @@ private:
bool Terminal = false; // The wait has settled; this does not imply success.
bool Succeeded = false; // Published successfully, including valid empty geometry.
std::unique_ptr<lcLibraryMeshData> MeshData; // Present when the next worker phase is mesh conversion.
std::vector<bool> ColorTranslucency;
int DefaultColorIndex = 0;
std::unique_ptr<lcMesh> StagedMesh;
bool EmptyGeometry = false;
bool SaveCache = false;
@@ -146,8 +143,6 @@ private:
std::atomic_bool Cancelled = false;
bool Terminal = false;
std::unique_ptr<lcLibraryMeshData> MeshData; // Present when the next worker phase is mesh conversion.
std::vector<bool> ColorTranslucency;
int DefaultColorIndex = 0;
std::unique_ptr<lcMesh> StagedMesh;
QString Error;
};
@@ -197,7 +192,6 @@ private:
void ProcessPieceCompletion(Completion& Completed);
void ProcessSynthCompletion(Completion& Completed);
void ProcessTextureCompletion(Completion& Completed);
static void ResolveMeshColors(lcLibraryMeshData& MeshData, std::vector<bool>& ColorTranslucency, int& DefaultColorIndex);
bool ResolveAndQueueMeshTextures(lcMesh& Mesh, const std::vector<QString>& SearchDirectories,
const std::map<QString, std::shared_ptr<const lcEmbeddedData>>& EmbeddedData, lcPieceLoadPriority LoadPriority, QString& Error);
static bool MeshTexturesSettled(const lcMesh& Mesh, QString& Error);
+1 -1
View File
@@ -1874,7 +1874,7 @@ lcPieceBuildResult lcPiecesLibrary::BuildPieceData(const lcPieceSourceSnapshot&
Result.FromCache = true;
Result.Mesh.reset(new lcMesh);
if (!Result.Mesh->FileLoad(CacheData, Result.ColorCodes))
if (!Result.Mesh->FileLoad(CacheData))
Result.Mesh.reset();
return Result;
+52 -5
View File
@@ -432,10 +432,8 @@ void lcMesh::ExportWavefrontIndices(lcFile& File, int DefaultColorIndex, int Ver
ExportWavefrontIndices<GLuint>(File, DefaultColorIndex, VertexOffset);
}
bool lcMesh::FileLoad(lcMemFile& File, std::vector<quint32>& ColorCodes)
bool lcMesh::FileLoad(lcMemFile& File)
{
ColorCodes.clear();
quint32 FileId, FileVersion, Flags;
if (File.ReadU32(&FileId, 1) != 1 || FileId != LC_MESH_FILE_ID || File.ReadU32(&FileVersion, 1) != 1 ||
@@ -487,8 +485,7 @@ bool lcMesh::FileLoad(lcMemFile& File, std::vector<quint32>& ColorCodes)
File.ReadU32(&DrawCount, 1) != 1 || File.ReadU16(&PrimtiveType, 1) != 1)
return false;
Section.ColorIndex = 0;
ColorCodes.push_back(ColorCode);
Section.ColorIndex = static_cast<int>(ColorCode);
Section.DrawOffset = DrawOffset;
Section.DrawCount = DrawCount;
Section.PrimitiveType = (lcMeshPrimitiveType)PrimtiveType;
@@ -538,6 +535,56 @@ bool lcMesh::FileLoad(lcMemFile& File, std::vector<quint32>& ColorCodes)
return true;
}
void lcMesh::FinalizeSections()
{
mFlags &= ~(lcMeshFlag::HasDefault | lcMeshFlag::HasSolid | lcMeshFlag::HasTranslucent);
for (lcMeshLod& Lod : mLods)
{
for (int SectionIdx = 0; SectionIdx < Lod.NumSections; SectionIdx++)
{
lcMeshSection& Section = Lod.Sections[SectionIdx];
Section.ColorIndex = lcGetColorIndex(static_cast<quint32>(Section.ColorIndex));
if (Section.PrimitiveType != LC_MESH_TRIANGLES && Section.PrimitiveType != LC_MESH_TEXTURED_TRIANGLES)
continue;
if (Section.ColorIndex == gDefaultColor)
mFlags |= lcMeshFlag::HasDefault;
else if (lcIsColorTranslucent(Section.ColorIndex))
mFlags |= lcMeshFlag::HasTranslucent;
else
mFlags |= lcMeshFlag::HasSolid;
}
if (Lod.NumSections > 1)
std::sort(Lod.Sections, Lod.Sections + Lod.NumSections, [](const lcMeshSection& Left, const lcMeshSection& Right)
{
if (Left.PrimitiveType != Right.PrimitiveType)
{
const lcMeshPrimitiveType PrimitiveOrder[] =
{
LC_MESH_TRIANGLES,
LC_MESH_TEXTURED_TRIANGLES,
LC_MESH_LINES,
LC_MESH_CONDITIONAL_LINES
};
return std::find(std::begin(PrimitiveOrder), std::end(PrimitiveOrder), Left.PrimitiveType) <
std::find(std::begin(PrimitiveOrder), std::end(PrimitiveOrder), Right.PrimitiveType);
}
const bool LeftTranslucent = lcIsColorTranslucent(Left.ColorIndex);
const bool RightTranslucent = lcIsColorTranslucent(Right.ColorIndex);
if (LeftTranslucent != RightTranslucent)
return !LeftTranslucent;
return Left.ColorIndex > Right.ColorIndex;
});
}
}
bool lcMesh::FileSave(lcMemFile& File)
{
File.WriteU32(LC_MESH_FILE_ID);
+6 -2
View File
@@ -34,7 +34,7 @@ struct lcVertexConditional
struct lcMeshSection
{
int ColorIndex;
int ColorIndex; // Holds a color code until FinalizeSections() converts it to an index.
int DrawOffset; // Index-buffer byte offset, or first conditional vertex.
int DrawCount; // Index count, or conditional vertex count.
lcMeshPrimitiveType PrimitiveType;
@@ -84,9 +84,13 @@ public:
void Create(quint16 (&NumSections)[LC_NUM_MESH_LODS], int VertexCount, int TexturedVertexCount, int ConditionalVertexCount, int IndexCount);
void CreateBox();
bool FileLoad(lcMemFile& File, std::vector<quint32>& ColorCodes);
bool FileLoad(lcMemFile& File);
bool FileSave(lcMemFile& File);
// Set colors and flags, then sort sections using the current palette on the UI thread.
// Converts stored color codes to indices once, before publishing the mesh.
void FinalizeSections();
template<typename IndexType>
void ExportPOVRay(lcFile& File, const char* MeshName, const char** ColorTable);
void ExportPOVRay(lcFile& File, const char* MeshName, const char** ColorTable);
+7 -92
View File
@@ -606,39 +606,6 @@ lcMeshLoaderMaterial* lcLibraryMeshData::GetTexturedMaterial(quint32 ColorCode,
return Material;
}
static bool lcMeshLoaderFinalSectionCompare(const lcMeshLoaderFinalSection& a, const lcMeshLoaderFinalSection& b, const std::vector<bool>& ColorTranslucency)
{
if (a.PrimitiveType != b.PrimitiveType)
{
const lcMeshPrimitiveType PrimitiveOrder[LC_MESH_NUM_PRIMITIVE_TYPES] =
{
LC_MESH_TRIANGLES,
LC_MESH_TEXTURED_TRIANGLES,
LC_MESH_LINES,
LC_MESH_CONDITIONAL_LINES
};
for (int PrimitiveType = 0; PrimitiveType < LC_MESH_NUM_PRIMITIVE_TYPES; PrimitiveType++)
{
const lcMeshPrimitiveType Primitive = PrimitiveOrder[PrimitiveType];
if (a.PrimitiveType == Primitive)
return true;
if (b.PrimitiveType == Primitive)
return false;
}
}
const bool TranslucentA = ColorTranslucency[a.Color];
const bool TranslucentB = ColorTranslucency[b.Color];
if (TranslucentA != TranslucentB)
return !TranslucentA;
return a.Color > b.Color;
}
quint32 lcLibraryMeshData::AddTexturedVertex(const lcVector3& Position, const lcVector3& Normal, const lcVector2& TexCoords)
{
for (int VertexIndex = static_cast<int>(mTexturedVertices.size()) - 1; VertexIndex >= 0; VertexIndex--)
@@ -803,33 +770,7 @@ void lcLibraryMeshData::GenerateTexturedVertices()
}
}
lcMesh* lcLibraryMeshData::CreateMesh(const std::function<lcTexture*(const char*)>& TextureLookup)
{
ResolveColors();
std::vector<bool> ColorTranslucency;
ColorTranslucency.reserve(gColorList.size());
for (const lcColor& Color : gColorList)
ColorTranslucency.push_back(Color.Translucent);
return CreateMeshInternal(TextureLookup, ColorTranslucency, gDefaultColor, nullptr);
}
void lcLibraryMeshData::ResolveColors()
{
for (const std::unique_ptr<lcMeshLoaderMaterial>& Material : mMaterials)
Material->Color = lcGetColorIndex(Material->Color);
}
lcMesh* lcLibraryMeshData::CreateMeshResolved(const std::vector<bool>& ColorTranslucency, int DefaultColorIndex, const std::atomic_bool* Cancelled)
{
// Keep project texture lookup on the UI thread after conversion.
const std::function<lcTexture*(const char*)> DeferTexture = [](const char*) -> lcTexture* { return nullptr; };
return CreateMeshInternal(DeferTexture, ColorTranslucency, DefaultColorIndex, Cancelled);
}
lcMesh* lcLibraryMeshData::CreateMeshInternal(const std::function<lcTexture*(const char*)>& TextureLookup, const std::vector<bool>& ColorTranslucency, int DefaultColorIndex, const std::atomic_bool* Cancelled)
lcMesh* lcLibraryMeshData::CreateMesh(const std::atomic_bool* Cancelled)
{
if (Cancelled && Cancelled->load(std::memory_order_relaxed))
return nullptr;
@@ -890,10 +831,6 @@ lcMesh* lcLibraryMeshData::CreateMeshInternal(const std::function<lcTexture*(con
}
NumSections[LodIdx] = static_cast<quint16>(FinalSections[LodIdx].size());
std::sort(FinalSections[LodIdx].begin(), FinalSections[LodIdx].end(), [&ColorTranslucency](const lcMeshLoaderFinalSection& Left, const lcMeshLoaderFinalSection& Right)
{
return lcMeshLoaderFinalSectionCompare(Left, Right, ColorTranslucency);
});
}
if (Cancelled && Cancelled->load(std::memory_order_relaxed))
@@ -962,9 +899,9 @@ lcMesh* lcLibraryMeshData::CreateMeshInternal(const std::function<lcTexture*(con
bool SectionsWritten;
if (Mesh->mIndexType == GL_UNSIGNED_SHORT)
SectionsWritten = WriteSections<quint16>(Mesh, FinalSections, BaseVertices, TextureLookup, ColorTranslucency, DefaultColorIndex, Cancelled);
SectionsWritten = WriteSections<quint16>(Mesh, FinalSections, BaseVertices, Cancelled);
else
SectionsWritten = WriteSections<quint32>(Mesh, FinalSections, BaseVertices, TextureLookup, ColorTranslucency, DefaultColorIndex, Cancelled);
SectionsWritten = WriteSections<quint32>(Mesh, FinalSections, BaseVertices, Cancelled);
if (!SectionsWritten)
{
@@ -1063,7 +1000,7 @@ lcMesh* lcLibraryMeshData::CreateMeshInternal(const std::function<lcTexture*(con
}
template<typename IndexType>
bool lcLibraryMeshData::WriteSections(lcMesh* Mesh, const std::vector<lcMeshLoaderFinalSection> (&FinalSections)[LC_NUM_MESH_LODS], int(&BaseVertices)[LC_NUM_MESHDATA_TYPES], const std::function<lcTexture*(const char*)>& TextureLookup, const std::vector<bool>& ColorTranslucency, int DefaultColorIndex, const std::atomic_bool* Cancelled)
bool lcLibraryMeshData::WriteSections(lcMesh* Mesh, const std::vector<lcMeshLoaderFinalSection> (&FinalSections)[LC_NUM_MESH_LODS], int(&BaseVertices)[LC_NUM_MESHDATA_TYPES], const std::atomic_bool* Cancelled)
{
int NumIndices = 0;
@@ -1077,22 +1014,12 @@ bool lcLibraryMeshData::WriteSections(lcMesh* Mesh, const std::vector<lcMeshLoad
const lcMeshLoaderFinalSection& FinalSection = FinalSections[LodIdx][SectionIndex];
lcMeshSection& DstSection = Mesh->mLods[LodIdx].Sections[SectionIndex];
DstSection.ColorIndex = FinalSection.Color;
DstSection.ColorIndex = static_cast<int>(FinalSection.Color);
DstSection.PrimitiveType = FinalSection.PrimitiveType;
DstSection.DrawCount = 0;
DstSection.TextureName = QString::fromLatin1(FinalSection.Name);
if (!FinalSection.Name[0])
DstSection.Texture = nullptr;
else
{
if (TextureLookup)
DstSection.Texture = TextureLookup(FinalSection.Name);
else if (mMeshLoader)
DstSection.Texture = lcGetPiecesLibrary()->FindTexture(FinalSection.Name, mMeshLoader->mCurrentProject, mMeshLoader->SearchProjectFolder());
else
DstSection.Texture = lcGetPiecesLibrary()->FindTexture(FinalSection.Name, nullptr, false);
}
DstSection.Texture = nullptr;
DstSection.DrawOffset = NumIndices * sizeof(IndexType);
@@ -1156,19 +1083,7 @@ bool lcLibraryMeshData::WriteSections(lcMesh* Mesh, const std::vector<lcMeshLoad
if (!AddSection(Section.get(), MeshDataType))
return false;
if (DstSection.PrimitiveType == LC_MESH_TRIANGLES || DstSection.PrimitiveType == LC_MESH_TEXTURED_TRIANGLES)
{
if (DstSection.ColorIndex == DefaultColorIndex)
Mesh->mFlags |= lcMeshFlag::HasDefault;
else
{
if (ColorTranslucency[DstSection.ColorIndex])
Mesh->mFlags |= lcMeshFlag::HasTranslucent;
else
Mesh->mFlags |= lcMeshFlag::HasSolid;
}
}
else
if (DstSection.PrimitiveType != LC_MESH_TRIANGLES && DstSection.PrimitiveType != LC_MESH_TEXTURED_TRIANGLES)
Mesh->mFlags |= lcMeshFlag::HasLines;
if (DstSection.PrimitiveType == LC_MESH_TEXTURED_TRIANGLES)
+3 -5
View File
@@ -165,9 +165,8 @@ public:
mMeshLoader = MeshLoader;
}
lcMesh* CreateMesh(const std::function<lcTexture*(const char*)>& TextureLookup = {});
void ResolveColors();
lcMesh* CreateMeshResolved(const std::vector<bool>& ColorTranslucency, int DefaultColorIndex, const std::atomic_bool* Cancelled);
// Build private geometry with color codes; the UI thread calls FinalizeSections().
lcMesh* CreateMesh(const std::atomic_bool* Cancelled);
void AddVertices(lcMeshDataType MeshDataType, size_t VertexCount, int* BaseVertex, lcMeshLoaderVertex** VertexBuffer);
void AddIndices(lcMeshDataType MeshDataType, lcMeshPrimitiveType PrimitiveType, quint32 ColorCode, size_t IndexCount, quint32** IndexBuffer);
void AddMeshData(const lcLibraryMeshData& Data, const lcMatrix44& Transform, quint32 CurrentColorCode, bool InvertWinding, bool InvertNormals, lcMeshLoaderTextureMap* TextureMap, lcMeshDataType OverrideDestIndex);
@@ -192,8 +191,7 @@ protected:
quint32 AddTexturedVertex(const lcVector3& Position, const lcVector3& Normal, const lcVector2& TexCoords);
template<typename IndexType>
bool WriteSections(lcMesh* Mesh, const std::vector<lcMeshLoaderFinalSection> (&FinalSections)[LC_NUM_MESH_LODS], int (&BaseVertices)[LC_NUM_MESHDATA_TYPES], const std::function<lcTexture*(const char*)>& TextureLookup, const std::vector<bool>& ColorTranslucency, int DefaultColorIndex, const std::atomic_bool* Cancelled);
lcMesh* CreateMeshInternal(const std::function<lcTexture*(const char*)>& TextureLookup, const std::vector<bool>& ColorTranslucency, int DefaultColorIndex, const std::atomic_bool* Cancelled);
bool WriteSections(lcMesh* Mesh, const std::vector<lcMeshLoaderFinalSection> (&FinalSections)[LC_NUM_MESH_LODS], int (&BaseVertices)[LC_NUM_MESHDATA_TYPES], const std::atomic_bool* Cancelled);
static bool UpdateMeshBoundingBox(lcMesh* Mesh, const std::atomic_bool* Cancelled);
template<typename IndexType>