You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Tools/RC/ResourceCompiler/PakHelpers.cpp

557 lines
17 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#include "ResourceCompiler_precompiled.h"
#include "PakHelpers.h"
#include "PathHelpers.h"
#include "StringHelpers.h"
#include "StringUtils.h"
#include "FileUtil.h"
#include "IRCLog.h"
#include <AzCore/base.h>
//////////////////////////////////////////////////////////////////////////
bool PakHelpers::PakEntry::MakeSortableStreamingSuffix(const string& suffix, string* sortable, int nDigits, int nIncrement)
{
int nMipmap = 0;
char cAttached = '\0';
char verify[32] = "";
// Scan, recreate and compare to verify if the given string is a valid streaming suffix
#if AZ_TRAIT_USE_SECURE_CRT_FUNCTIONS
int nTokens = sscanf_s(suffix.data(), "%d%c", &nMipmap, &cAttached, 1);
#else
int nTokens = sscanf(suffix.data(), "%d%c", &nMipmap, &cAttached);
#endif
switch (nTokens)
{
case 2:
// .dds.0a, .dds.1a, etc.
azsnprintf(verify, sizeof(verify), "%d%c", nMipmap, cAttached);
break;
case 1:
// .dds.0, .dds.1, etc.
azsnprintf(verify, sizeof(verify), "%d", nMipmap);
break;
default:
// .dds.a
#if AZ_TRAIT_USE_SECURE_CRT_FUNCTIONS
if (sscanf_s(suffix.data(), "%c", &cAttached, 1) == 1)
#else
if (sscanf(suffix.data(), "%c", &cAttached) == 1)
#endif
{
azsnprintf(verify, sizeof(verify), "%c", cAttached);
nTokens = 2;
}
break;
}
if (!StringHelpers::Equals(suffix, verify) || ((nTokens > 1) && (cAttached != 'a')))
{
return false;
}
// Recreate a streaming suffix we can use to sort (constant length)
if (sortable)
{
char digits[32];
if (nDigits > 0)
{
azsnprintf(digits, sizeof(digits), "%%0%dd", nDigits);
}
else
{
cry_strcpy(digits, "%d");
}
if (nTokens > 1)
{
cry_strcat(digits, "%c");
}
switch (nTokens)
{
case 2:
azsnprintf(verify, sizeof(verify), digits, nMipmap + nIncrement, cAttached);
break;
case 1:
azsnprintf(verify, sizeof(verify), digits, nMipmap + nIncrement);
break;
default:
break;
}
*sortable = verify;
}
return true;
}
string PakHelpers::PakEntry::GetRealFilename() const
{
const string sRealFilename = PathHelpers::Join(m_rcFile.m_sourceLeftPath,
m_rcFile.m_sourceInnerPathAndName);
return sRealFilename;
}
string PakHelpers::PakEntry::GetStreamingSuffix() const
{
const size_t splitter = m_rcFile.m_sourceInnerPathAndName.find_last_of('.');
if (splitter == string::npos)
{
return string();
}
// for split DDS files the file extension is .dds, .dds.1, .dds.a, .dds.1a ...
// Take the number part, and return just the mip number and alpha name
const string suffix = m_rcFile.m_sourceInnerPathAndName.substr(splitter + 1);
string sortablesuffix;
if (!MakeSortableStreamingSuffix(suffix, &sortablesuffix))
{
return string();
}
return sortablesuffix;
}
string PakHelpers::PakEntry::GetExtension() const
{
size_t splitter = m_rcFile.m_sourceInnerPathAndName.find_last_of('.');
if (splitter == string::npos)
{
return string();
}
// for DDS files on consoles the file extension is .dds.0, .dds.1, .dds.0a, .dds.1a ...
// Skip the number part, and return the actual file extension without the mip number and alpha name
const string extension = m_rcFile.m_sourceInnerPathAndName.substr(splitter + 1);
if (!MakeSortableStreamingSuffix(extension))
{
return extension;
}
const string tmp = m_rcFile.m_sourceInnerPathAndName.substr(0, splitter);
splitter = tmp.find_last_of('.');
if (splitter == string::npos)
{
return string();
}
return tmp.substr(splitter + 1);
}
string PakHelpers::PakEntry::GetNameWithoutExtension(bool bFilenameOnly) const
{
string name = m_rcFile.m_sourceInnerPathAndName;
if (bFilenameOnly)
{
name = PathHelpers::GetFilename(name);
}
size_t splitter = name.find_last_of('.');
if (splitter == string::npos)
{
return name;
}
// for DDS files on consoles the file extension is .dds.0, .dds.1, .dds.0a, .dds.1a ...
// Skip the number part, and return the actual file extension without the mip number and alpha name
const string extension = name.substr(splitter + 1);
if (MakeSortableStreamingSuffix(extension))
{
name = name.substr(0, splitter);
}
splitter = name.find_last_of('.');
if (splitter == string::npos)
{
return name;
}
return name.substr(0, splitter);
}
string PakHelpers::PakEntry::GetDirnameWithoutFile(bool bRootdirOnly) const
{
string name = m_rcFile.m_sourceInnerPathAndName;
if (bRootdirOnly)
{
const size_t splitter = name.find_first_of("\\/");
if (splitter != string::npos)
{
name = name.substr(0, splitter);
}
else
{
name = "";
}
}
else
{
name = PathHelpers::GetDirectory(name);
}
return name;
}
PakHelpers::ETextureType PakHelpers::PakEntry::GetTextureType() const
{
const string sLowerCaseName = StringHelpers::MakeLowerCase(PathHelpers::RemoveExtension(m_rcFile.m_sourceInnerPathAndName));
if (StringHelpers::EndsWith(sLowerCaseName, "_diff"))
{
return eTextureType_Diffuse;
}
if (StringHelpers::EndsWith(sLowerCaseName, "_ddn") ||
StringHelpers::EndsWith(sLowerCaseName, "_ddna"))
{
return eTextureType_Normal;
}
if (StringHelpers::EndsWith(sLowerCaseName, "_spec"))
{
return eTextureType_Specular;
}
if (StringHelpers::EndsWith(sLowerCaseName, "_detail"))
{
return eTextureType_Detail;
}
if (StringHelpers::EndsWith(sLowerCaseName, "_mask"))
{
return eTextureType_Mask;
}
if (StringHelpers::EndsWith(sLowerCaseName, "_sss"))
{
return eTextureType_SubSurfaceScattering;
}
if (StringHelpers::EndsWith(sLowerCaseName, "_cm") ||
StringHelpers::EndsWith(sLowerCaseName, "_cubemap"))
{
return eTextureType_Cubemap;
}
if (StringHelpers::EndsWith(sLowerCaseName, "_cch"))
{
return eTextureType_Colorchart;
}
if (StringHelpers::EndsWith(sLowerCaseName, "_displ") ||
StringHelpers::EndsWith(sLowerCaseName, "_dmap"))
{
return eTextureType_Displacement;
}
return eTextureType_Undefined;
}
//////////////////////////////////////////////////////////////////////////
namespace
{
struct StreamingFileOrder
{
bool operator()(const PakHelpers::PakEntry& left, const PakHelpers::PakEntry& right) const
{
// first sort by extension
const string& extA = left.m_extension;
const string& extB = right.m_extension;
int res = StringHelpers::CompareIgnoreCase(extA, extB);
if (res != 0)
{
return res < 0;
}
if (StringHelpers::EqualsIgnoreCase(extA, "dds"))
{
// then sort dds textures by type
const PakHelpers::ETextureType eAType = left.m_textureType;
const PakHelpers::ETextureType eBType = right.m_textureType;
if (eAType != eBType)
{
return eAType < eBType;
}
// then sort by name
const string& shortNameA = PathHelpers::Join(left.m_innerDir, left.m_baseName);
const string& shortNameB = PathHelpers::Join(right.m_innerDir, right.m_baseName);
res = StringHelpers::CompareIgnoreCase(shortNameA, shortNameB);
if (res != 0)
{
return res < 0;
}
if (left.m_sourceFileSize != right.m_sourceFileSize)
{
return left.m_sourceFileSize < right.m_sourceFileSize;
}
}
else
{
/*
// The following code is commented out because some of the files
// are loaded directly from an alphabetical resource list which breaks
// the whole loading times
if (left.m_sourceFileSize != right.m_sourceFileSize)
{
return left.m_sourceFileSize < right.m_sourceFileSize;
}
*/
}
res = StringHelpers::CompareIgnoreCase(
left.m_rcFile.m_sourceInnerPathAndName, right.m_rcFile.m_sourceInnerPathAndName);
return res < 0;
}
};
struct SizeFileOrder
{
bool operator()(const PakHelpers::PakEntry& left, const PakHelpers::PakEntry& right) const
{
return left.m_sourceFileSize < right.m_sourceFileSize;
}
};
struct AlphabeticalFileOrder
{
bool operator()(const PakHelpers::PakEntry& left, const PakHelpers::PakEntry& right) const
{
const int res = StringHelpers::CompareIgnoreCase(
left.m_rcFile.m_sourceInnerPathAndName, right.m_rcFile.m_sourceInnerPathAndName);
return res < 0;
}
};
struct StreamingSuffixFileOrder
: public AlphabeticalFileOrder
{
bool operator()(const PakHelpers::PakEntry& left, const PakHelpers::PakEntry& right) const
{
// first sort by streaming suffix
const string& sfxA = left.m_streamingSuffix;
const string& sfxB = right.m_streamingSuffix;
// Empty suffices always at the end of the PAK, no matter what
if (!sfxA.empty() || !sfxB.empty())
{
if (sfxA.empty())
{
return true;
}
if (sfxB.empty())
{
return false;
}
const int res = StringHelpers::CompareIgnoreCase(sfxA, sfxB);
if (res != 0)
{
return res < 0;
}
}
// then sort by name
const string& shortNameA = left.m_baseName;
const string& shortNameB = right.m_baseName;
int res = StringHelpers::CompareIgnoreCase(shortNameA, shortNameB);
if (res != 0)
{
return res < 0;
}
// then sort by extension
const string& extA = left.m_extension;
const string& extB = right.m_extension;
res = StringHelpers::CompareIgnoreCase(extA, extB);
if (res != 0)
{
return res < 0;
}
// then alphabetically
return AlphabeticalFileOrder::operator()(left, right);
}
};
}
//////////////////////////////////////////////////////////////////////////
size_t PakHelpers::CreatePakEntryList(
const std::vector<RcFile>& files,
std::map<string, std::vector<PakHelpers::PakEntry> >& pakEntries,
PakHelpers::ESortType eSortType,
PakHelpers::ESplitType eSplitType,
const string& sPakName)
{
const string sPakBase = PathHelpers::RemoveExtension(PathHelpers::GetFilename(sPakName));
string sPakDir;
size_t entryCount = 0;
for (std::vector<RcFile>::const_iterator it = files.begin(); it != files.end(); ++it)
{
const RcFile& file = *it;
string sNewPakName = sPakName;
bool bSkip = false;
PakEntry entry;
entry.m_rcFile = file;
entry.m_rcFile.m_sourceLeftPath = PathHelpers::ToPlatformPath(file.m_sourceLeftPath);
entry.m_rcFile.m_sourceInnerPathAndName = PathHelpers::ToPlatformPath(file.m_sourceInnerPathAndName);
entry.m_rcFile.m_targetLeftPath = PathHelpers::ToPlatformPath(file.m_targetLeftPath);
// cache values used for fast sorting
entry.m_streamingSuffix = entry.GetStreamingSuffix();
entry.m_extension = entry.GetExtension();
entry.m_textureType = entry.GetTextureType();
entry.m_baseName = entry.GetNameWithoutExtension(true);
entry.m_innerDir = entry.GetDirnameWithoutFile(false);
entry.m_sourceFileSize = FileUtil::GetFileSize(entry.GetRealFilename());
if (eSplitType == eSplitType_ExtensionMipmap)
{
if (entry.m_sourceFileSize >= 0)
{
if (StringHelpers::EqualsIgnoreCase(entry.m_extension, "dds"))
{
if (!entry.m_streamingSuffix.empty())
{
const string szPlainSuffix = entry.m_rcFile.m_sourceInnerPathAndName.substr(entry.m_rcFile.m_sourceInnerPathAndName.find_last_of('.') + 1);
string szIncrementedSuffix = "";
if (entry.MakeSortableStreamingSuffix(szPlainSuffix, &szIncrementedSuffix, 0, 1))
{
string tmpFilename = PathHelpers::Join(file.m_sourceLeftPath, entry.m_innerDir);
tmpFilename = PathHelpers::Join(tmpFilename, entry.m_baseName + "." + entry.m_extension + "." + szIncrementedSuffix);
entry.m_bIsLastMip = !FileUtil::FileExists(tmpFilename);
}
}
if (entry.m_bIsLastMip)
{
sNewPakName = sPakName + string("streaming\\dds_high.pak");
}
else
{
sNewPakName = sPakName + string("streaming\\dds_low.pak");
}
}
else
{
sNewPakName = sPakName + string("streaming\\") + entry.m_extension + string(".pak");
}
}
else
{
bSkip = true;
}
}
else if (eSplitType == eSplitType_Suffix)
{
// filter files without suffices > 0 into different pak
if (!entry.m_streamingSuffix.empty())
{
const string tmpFilename = PathHelpers::Join(file.m_sourceLeftPath,
entry.m_innerDir + "\\" + entry.m_baseName + "." + entry.m_extension + ".1");
if (!FileUtil::FileExists(tmpFilename))
{
entry.m_streamingSuffix = "";//"00o";
}
}
sNewPakName = sPakDir + sPakBase + "-m" + entry.m_streamingSuffix + string(".pak");
}
else if (eSplitType == eSplitType_Basedir)
{
sNewPakName = sPakDir + entry.GetDirnameWithoutFile(true) + string(".pak");
}
else
{
if (eSortType == eSortType_Size)
{
bSkip = !(entry.m_sourceFileSize >= 0);
}
}
if (StringHelpers::EqualsIgnoreCase(entry.m_extension, "$dds") ||
StringHelpers::EqualsIgnoreCase(entry.m_extension, "pak"))
{
bSkip = true;
}
if (!bSkip)
{
pakEntries[sNewPakName].push_back(entry);
++entryCount;
}
}
// sort the entries by requested sorting operator
for (std::map<string, std::vector<PakEntry> >::iterator it = pakEntries.begin(); it != pakEntries.end(); ++it)
{
std::vector<PakEntry>& files = it->second;
// Sort by type
switch (eSortType)
{
case PakHelpers::eSortType_NoSort:
{
RCLog("Using sort method to add to pack : nosort");
break;
}
case PakHelpers::eSortType_Size:
{
RCLog("Using sort method to add to pack : size");
// Sort alphabetically first so there is a consistent ordering before sorting by size
// (to ensure that files with the same size are ordered by name)
std::sort(files.begin(), files.end(), AlphabeticalFileOrder());
std::stable_sort(files.begin(), files.end(), SizeFileOrder());
break;
}
case PakHelpers::eSortType_Streaming:
{
RCLog("Using sort method to add to pack : streaming");
std::sort(files.begin(), files.end(), StreamingFileOrder());
break;
}
case PakHelpers::eSortType_Suffix:
{
RCLog("Using sort method to add to pack : suffix");
std::sort(files.begin(), files.end(), StreamingSuffixFileOrder());
break;
}
case PakHelpers::eSortType_Alphabetically:
{
RCLog("Using sort method to add to pack : alphabetically");
std::sort(files.begin(), files.end(), AlphabeticalFileOrder());
break;
}
default:
{
assert(0);
RCLogError("Using sort method to add to pack : undefined!");
break;
}
}
}
return entryCount;
}
//////////////////////////////////////////////////////////////////////////