diff --git a/CMakeLists.txt b/CMakeLists.txt index 156f7e06..a6c8e24d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -239,6 +239,7 @@ if(BUILD_TOOLS AND WIN32) UVAtlasTool/UVAtlas.cpp UVAtlasTool/uvatlas.rc UVAtlasTool/settings.manifest + UVAtlasTool/CmdLineHelpers.h UVAtlasTool/Mesh.cpp UVAtlasTool/Mesh.h UVAtlasTool/MeshOBJ.cpp @@ -259,6 +260,10 @@ if(directxmath_FOUND) endforeach() endif() +if(TOOL_EXES) + message(STATUS "Building tools: ${TOOL_EXES}") +endif() + if(MSVC) foreach(t IN LISTS TOOL_EXES ITEMS ${PROJECT_NAME}) target_compile_options(${t} PRIVATE /Wall /EHsc /GR-) @@ -296,6 +301,7 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "Intel") set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 14) elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") if(ENABLE_CODE_ANALYSIS) + message(STATUS "Building with Code Analysis (PREFIX)") foreach(t IN LISTS TOOL_EXES ITEMS ${PROJECT_NAME}) target_compile_options(${t} PRIVATE /analyze /WX) endforeach() diff --git a/CMakePresets.json b/CMakePresets.json index 9efd759b..af639a67 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -255,7 +255,9 @@ { "name": "x64-Debug-Linux", "description": "WSL Linux x64 (Debug)", "inherits": [ "base", "x64", "Debug", "VCPKG" ] }, { "name": "x64-Release-Linux", "description": "WSL Linux x64 (Release)", "inherits": [ "base", "x64", "Release", "VCPKG" ] }, { "name": "arm64-Debug-Linux", "description": "WSL Linux ARM64 (Debug)", "inherits": [ "base", "ARM64", "Debug", "VCPKG" ] }, - { "name": "arm64-Release-Linux", "description": "WSL Linux ARM64 (Release)", "inherits": [ "base", "ARM64", "Release", "VCPKG" ] } + { "name": "arm64-Release-Linux", "description": "WSL Linux ARM64 (Release)", "inherits": [ "base", "ARM64", "Release", "VCPKG" ] }, + + { "name": "x64-Analyze" , "description": "MSVC for x64 (Debug) using /analyze", "inherits": [ "base", "x64", "Debug", "VCPKG", "MSVC", "Tools" ], "cacheVariables": { "ENABLE_CODE_ANALYSIS": true } } ], "testPresets": [ { "name": "x64-Debug" , "configurePreset": "x64-Debug" }, diff --git a/UVAtlasTool/CmdLineHelpers.h b/UVAtlasTool/CmdLineHelpers.h new file mode 100644 index 00000000..b3af3ee5 --- /dev/null +++ b/UVAtlasTool/CmdLineHelpers.h @@ -0,0 +1,375 @@ +//-------------------------------------------------------------------------------------- +// File: CmdLineHelpers.h +// +// Command-line tool shared functions +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +//-------------------------------------------------------------------------------------- + +#pragma once + +#if __cplusplus < 201703L +#error Requires C++17 (and /Zc:__cplusplus with MSVC) +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef TOOL_VERSION +#error Define TOOL_VERSION before including this header +#endif + + +namespace Helpers +{ + struct handle_closer { void operator()(HANDLE h) { if (h) CloseHandle(h); } }; + + using ScopedHandle = std::unique_ptr; + + inline HANDLE safe_handle(HANDLE h) noexcept { return (h == INVALID_HANDLE_VALUE) ? nullptr : h; } + + struct find_closer { void operator()(HANDLE h) noexcept { assert(h != INVALID_HANDLE_VALUE); if (h) FindClose(h); } }; + + using ScopedFindHandle = std::unique_ptr; + +#ifdef _PREFAST_ +#pragma prefast(disable : 26018, "Only used with static internal arrays") +#endif + + struct SConversion + { + std::wstring szSrc; + std::wstring szFolder; + }; + + template + struct SValue + { + const wchar_t* name; + T value; + }; + + template + T LookupByName(const wchar_t _In_z_ *pName, const SValue *pArray) + { + while (pArray->name) + { + if (!_wcsicmp(pName, pArray->name)) + return pArray->value; + + pArray++; + } + + return static_cast(0); + } + + template + const wchar_t* LookupByValue(T value, const SValue *pArray) + { + while (pArray->name) + { + if (value == pArray->value) + return pArray->name; + + pArray++; + } + + return L""; + } + + void PrintFormat(DXGI_FORMAT Format, const SValue* pFormatList) + { + for (auto pFormat = pFormatList; pFormat->name; pFormat++) + { + if (pFormat->value == Format) + { + wprintf(L"%ls", pFormat->name); + return; + } + } + + wprintf(L"*UNKNOWN*"); + } + + void PrintFormat(DXGI_FORMAT Format, const SValue* pFormatList1, const SValue* pFormatList2) + { + for (auto pFormat = pFormatList1; pFormat->name; pFormat++) + { + if (pFormat->value == Format) + { + wprintf(L"%ls", pFormat->name); + return; + } + } + + for (auto pFormat = pFormatList2; pFormat->name; pFormat++) + { + if (pFormat->value == Format) + { + wprintf(L"%ls", pFormat->name); + return; + } + } + + wprintf(L"*UNKNOWN*"); + } + + template + void PrintList(size_t cch, const SValue *pValue) + { + while (pValue->name) + { + const size_t cchName = wcslen(pValue->name); + + if (cch + cchName + 2 >= 80) + { + wprintf(L"\n "); + cch = 6; + } + + wprintf(L"%ls ", pValue->name); + cch += cchName + 2; + pValue++; + } + + wprintf(L"\n"); + } + + void PrintLogo(bool versionOnly, _In_z_ const wchar_t* name, _In_z_ const wchar_t* desc) + { + wchar_t version[32] = {}; + + wchar_t appName[_MAX_PATH] = {}; + if (GetModuleFileNameW(nullptr, appName, _MAX_PATH)) + { + const DWORD size = GetFileVersionInfoSizeW(appName, nullptr); + if (size > 0) + { + auto verInfo = std::make_unique(size); + if (GetFileVersionInfoW(appName, 0, size, verInfo.get())) + { + LPVOID lpstr = nullptr; + UINT strLen = 0; + if (VerQueryValueW(verInfo.get(), L"\\StringFileInfo\\040904B0\\ProductVersion", &lpstr, &strLen)) + { + wcsncpy_s(version, reinterpret_cast(lpstr), strLen); + } + } + } + } + + if (!*version || wcscmp(version, L"1.0.0.0") == 0) + { + swprintf_s(version, L"%03d (library)", TOOL_VERSION); + } + + if (versionOnly) + { + wprintf(L"%ls version %ls\n", name, version); + } + else + { + wprintf(L"%ls Version %ls\n", desc, version); + wprintf(L"Copyright (C) Microsoft Corp.\n"); + #ifdef _DEBUG + wprintf(L"*** Debug build ***\n"); + #endif + wprintf(L"\n"); + } + } + + void SearchForFiles(const std::filesystem::path& path, std::list& files, bool recursive, _In_opt_z_ const wchar_t* folder) + { + // Process files + WIN32_FIND_DATAW findData = {}; + ScopedFindHandle hFile(safe_handle(FindFirstFileExW(path.c_str(), + FindExInfoBasic, &findData, + FindExSearchNameMatch, nullptr, + FIND_FIRST_EX_LARGE_FETCH))); + if (hFile) + { + for (;;) + { + if (!(findData.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_DIRECTORY))) + { + SConversion conv = {}; + conv.szSrc = path.parent_path().append(findData.cFileName).native(); + if (folder) + { + conv.szFolder = folder; + } + files.push_back(conv); + } + + if (!FindNextFileW(hFile.get(), &findData)) + break; + } + } + + // Process directories + if (recursive) + { + auto searchDir = path.parent_path().append(L"*"); + + hFile.reset(safe_handle(FindFirstFileExW(searchDir.c_str(), + FindExInfoBasic, &findData, + FindExSearchLimitToDirectories, nullptr, + FIND_FIRST_EX_LARGE_FETCH))); + if (!hFile) + return; + + for (;;) + { + if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + if (findData.cFileName[0] != L'.') + { + auto subfolder = (folder) + ? (std::wstring(folder) + std::wstring(findData.cFileName) + std::filesystem::path::preferred_separator) + : (std::wstring(findData.cFileName) + std::filesystem::path::preferred_separator); + + auto subdir = path.parent_path().append(findData.cFileName).append(path.filename().c_str()); + + SearchForFiles(subdir, files, recursive, subfolder.c_str()); + } + } + + if (!FindNextFileW(hFile.get(), &findData)) + break; + } + } + } + + void ProcessFileList(std::wifstream& inFile, std::list& files) + { + std::list flist; + std::set excludes; + + for (;;) + { + std::wstring fname; + std::getline(inFile, fname); + if (!inFile) + break; + + if (fname[0] == L'#') + { + // Comment + } + else if (fname[0] == L'-') + { + if (flist.empty()) + { + wprintf(L"WARNING: Ignoring the line '%ls' in -flist\n", fname.c_str()); + } + else + { + std::filesystem::path path(fname.c_str() + 1); + auto& npath = path.make_preferred(); + if (wcspbrk(fname.c_str(), L"?*") != nullptr) + { + std::list removeFiles; + SearchForFiles(npath, removeFiles, false, nullptr); + + for (auto& it : removeFiles) + { + std::wstring name = it.szSrc; + std::transform(name.begin(), name.end(), name.begin(), towlower); + excludes.insert(name); + } + } + else + { + std::wstring name = npath.c_str(); + std::transform(name.begin(), name.end(), name.begin(), towlower); + excludes.insert(name); + } + } + } + else if (wcspbrk(fname.c_str(), L"?*") != nullptr) + { + std::filesystem::path path(fname.c_str()); + SearchForFiles(path.make_preferred(), flist, false, nullptr); + } + else + { + SConversion conv = {}; + std::filesystem::path path(fname.c_str()); + conv.szSrc = path.make_preferred().native(); + flist.push_back(conv); + } + } + + inFile.close(); + + if (!excludes.empty()) + { + // Remove any excluded files + for (auto it = flist.begin(); it != flist.end();) + { + std::wstring name = it->szSrc; + std::transform(name.begin(), name.end(), name.begin(), towlower); + auto item = it; + ++it; + if (excludes.find(name) != excludes.end()) + { + flist.erase(item); + } + } + } + + if (flist.empty()) + { + wprintf(L"WARNING: No file names found in -flist\n"); + } + else + { + files.splice(files.end(), flist); + } + } + + const wchar_t* GetErrorDesc(HRESULT hr) + { + static wchar_t desc[1024] = {}; + + LPWSTR errorText = nullptr; + + const DWORD result = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER, + nullptr, static_cast(hr), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&errorText), 0, nullptr); + + *desc = 0; + + if (result > 0 && errorText) + { + swprintf_s(desc, L": %ls", errorText); + + size_t len = wcslen(desc); + if (len >= 1) + { + desc[len - 1] = 0; + } + + if (errorText) + LocalFree(errorText); + + for(wchar_t* ptr = desc; *ptr != 0; ++ptr) + { + if (*ptr == L'\r' || *ptr == L'\n') + { + *ptr = L' '; + } + } + } + + return desc; + } +} diff --git a/UVAtlasTool/UVAtlas.cpp b/UVAtlasTool/UVAtlas.cpp index 060011db..06fb6625 100644 --- a/UVAtlasTool/UVAtlas.cpp +++ b/UVAtlasTool/UVAtlas.cpp @@ -57,6 +57,9 @@ #include "Mesh.h" +#define TOOL_VERSION UVATLAS_VERSION +#include "CmdLineHelpers.h" + //Uncomment to add support for OpenEXR (.exr) //#define USE_OPENEXR @@ -65,10 +68,14 @@ #include "DirectXTexEXR.h" #endif +using namespace Helpers; using namespace DirectX; namespace { + const wchar_t* g_ToolName = L"uvatlastool"; + const wchar_t* g_Description = L"Microsoft (R) UVAtlas Command-line Tool"; + enum OPTIONS : uint64_t { OPT_RECURSIVE = 1, @@ -127,18 +134,6 @@ namespace CHANNEL_TEXCOORD, }; - struct SConversion - { - std::wstring szSrc; - }; - - template - struct SValue - { - const wchar_t* name; - T value; - }; - const XMFLOAT3 g_ColorList[8] = { XMFLOAT3(1.0f, 0.5f, 0.5f), @@ -245,238 +240,9 @@ HRESULT LoadFromOBJ(const wchar_t* szFilename, namespace { - inline HANDLE safe_handle(HANDLE h) noexcept { return (h == INVALID_HANDLE_VALUE) ? nullptr : h; } - - struct find_closer { void operator()(HANDLE h) noexcept { assert(h != INVALID_HANDLE_VALUE); if (h) FindClose(h); } }; - - using ScopedFindHandle = std::unique_ptr; - -#ifdef __PREFAST__ -#pragma prefast(disable : 26018, "Only used with static internal arrays") -#endif - - template - T LookupByName(const wchar_t *pName, const SValue *pArray) - { - while (pArray->name) - { - if (!_wcsicmp(pName, pArray->name)) - return pArray->value; - - pArray++; - } - - return 0; - } - - void SearchForFiles(const std::filesystem::path& path, std::list& files, bool recursive) - { - // Process files - WIN32_FIND_DATAW findData = {}; - ScopedFindHandle hFile(safe_handle(FindFirstFileExW(path.c_str(), - FindExInfoBasic, &findData, - FindExSearchNameMatch, nullptr, - FIND_FIRST_EX_LARGE_FETCH))); - if (hFile) - { - for (;;) - { - if (!(findData.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_DIRECTORY))) - { - SConversion conv = {}; - conv.szSrc = path.parent_path().append(findData.cFileName).native(); - files.push_back(conv); - } - - if (!FindNextFileW(hFile.get(), &findData)) - break; - } - } - - // Process directories - if (recursive) - { - auto searchDir = path.parent_path().append(L"*"); - - hFile.reset(safe_handle(FindFirstFileExW(searchDir.c_str(), - FindExInfoBasic, &findData, - FindExSearchLimitToDirectories, nullptr, - FIND_FIRST_EX_LARGE_FETCH))); - if (!hFile) - return; - - for (;;) - { - if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - { - if (findData.cFileName[0] != L'.') - { - auto subdir = path.parent_path().append(findData.cFileName).append(path.filename().c_str()); - - SearchForFiles(subdir, files, recursive); - } - } - - if (!FindNextFileW(hFile.get(), &findData)) - break; - } - } - } - - void ProcessFileList(std::wifstream& inFile, std::list& files) - { - std::list flist; - std::set excludes; - - for (;;) - { - std::wstring fname; - std::getline(inFile, fname); - if (!inFile) - break; - - if (fname[0] == L'#') - { - // Comment - } - else if (fname[0] == L'-') - { - if (flist.empty()) - { - wprintf(L"WARNING: Ignoring the line '%ls' in -flist\n", fname.c_str()); - } - else - { - std::filesystem::path path(fname.c_str() + 1); - auto& npath = path.make_preferred(); - if (wcspbrk(fname.c_str(), L"?*") != nullptr) - { - std::list removeFiles; - SearchForFiles(npath, removeFiles, false); - - for (auto& it : removeFiles) - { - std::wstring name = it.szSrc; - std::transform(name.begin(), name.end(), name.begin(), towlower); - excludes.insert(name); - } - } - else - { - std::wstring name = npath.c_str(); - std::transform(name.begin(), name.end(), name.begin(), towlower); - excludes.insert(name); - } - } - } - else if (wcspbrk(fname.c_str(), L"?*") != nullptr) - { - std::filesystem::path path(fname.c_str()); - SearchForFiles(path.make_preferred(), flist, false); - } - else - { - SConversion conv = {}; - std::filesystem::path path(fname.c_str()); - conv.szSrc = path.make_preferred().native(); - flist.push_back(conv); - } - } - - inFile.close(); - - if (!excludes.empty()) - { - // Remove any excluded files - for (auto it = flist.begin(); it != flist.end();) - { - std::wstring name = it->szSrc; - std::transform(name.begin(), name.end(), name.begin(), towlower); - auto item = it; - ++it; - if (excludes.find(name) != excludes.end()) - { - flist.erase(item); - } - } - } - - if (flist.empty()) - { - wprintf(L"WARNING: No file names found in -flist\n"); - } - else - { - files.splice(files.end(), flist); - } - } - - void PrintList(size_t cch, const SValue* pValue) - { - while (pValue->name) - { - const size_t cchName = wcslen(pValue->name); - - if (cch + cchName + 2 >= 80) - { - wprintf(L"\n "); - cch = 6; - } - - wprintf(L"%ls ", pValue->name); - cch += cchName + 2; - pValue++; - } - - wprintf(L"\n"); - } - - void PrintLogo(bool versionOnly) - { - wchar_t version[32] = {}; - - wchar_t appName[_MAX_PATH] = {}; - if (GetModuleFileNameW(nullptr, appName, _MAX_PATH)) - { - const DWORD size = GetFileVersionInfoSizeW(appName, nullptr); - if (size > 0) - { - auto verInfo = std::make_unique(size); - if (GetFileVersionInfoW(appName, 0, size, verInfo.get())) - { - LPVOID lpstr = nullptr; - UINT strLen = 0; - if (VerQueryValueW(verInfo.get(), L"\\StringFileInfo\\040904B0\\ProductVersion", &lpstr, &strLen)) - { - wcsncpy_s(version, reinterpret_cast(lpstr), strLen); - } - } - } - } - - if (!*version || wcscmp(version, L"1.0.0.0") == 0) - { - swprintf_s(version, L"%03d (library)", UVATLAS_VERSION); - } - - if (versionOnly) - { - wprintf(L"uvatlastool version %ls\n", version); - } - else - { - wprintf(L"Microsoft (R) UVAtlas Command-line Tool Version %ls\n", version); - wprintf(L"Copyright (C) Microsoft Corp.\n"); - #ifdef _DEBUG - wprintf(L"*** Debug build ***\n"); - #endif - wprintf(L"\n"); - } - } - void PrintUsage() { - PrintLogo(false); + PrintLogo(false, g_ToolName, g_Description); static const wchar_t* const s_usage = L"Usage: uvatlas [--] \n" @@ -543,43 +309,6 @@ namespace PrintList(13, g_vertexColorFormats); } - const wchar_t* GetErrorDesc(HRESULT hr) - { - static wchar_t desc[1024] = {}; - - LPWSTR errorText = nullptr; - - const DWORD result = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER, - nullptr, static_cast(hr), - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&errorText), 0, nullptr); - - *desc = 0; - - if (result > 0 && errorText) - { - swprintf_s(desc, L": %ls", errorText); - - size_t len = wcslen(desc); - if (len >= 1) - { - desc[len - 1] = 0; - } - - if (errorText) - LocalFree(errorText); - - for (wchar_t* ptr = desc; *ptr != 0; ++ptr) - { - if (*ptr == L'\r' || *ptr == L'\n') - { - *ptr = L' '; - } - } - } - - return desc; - } - //-------------------------------------------------------------------------------------- HRESULT __cdecl UVAtlasCallback(float fPercentDone) { @@ -661,7 +390,7 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[]) } else if (!_wcsicmp(pArg, L"--version")) { - PrintLogo(true); + PrintLogo(true, g_ToolName, g_Description); return 0; } else if (!_wcsicmp(pArg, L"--help")) @@ -1001,7 +730,7 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[]) { const size_t count = conversion.size(); std::filesystem::path path(pArg); - SearchForFiles(path.make_preferred(), conversion, (dwOptions& (1 << OPT_RECURSIVE)) != 0); + SearchForFiles(path.make_preferred(), conversion, (dwOptions& (1 << OPT_RECURSIVE)) != 0, nullptr); if (conversion.size() <= count) { wprintf(L"No matching files found for %ls\n", pArg); @@ -1030,7 +759,7 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[]) } if (~dwOptions & (uint64_t(1) << OPT_NOLOGO)) - PrintLogo(false); + PrintLogo(false, g_ToolName, g_Description); // Process files for (auto pConv = conversion.begin(); pConv != conversion.end(); ++pConv) diff --git a/UVAtlasTool/UVAtlasTool_2019.vcxproj b/UVAtlasTool/UVAtlasTool_2019.vcxproj index ded5b9ab..8f0e4bd8 100644 --- a/UVAtlasTool/UVAtlasTool_2019.vcxproj +++ b/UVAtlasTool/UVAtlasTool_2019.vcxproj @@ -300,6 +300,7 @@ + diff --git a/UVAtlasTool/UVAtlasTool_2019.vcxproj.filters b/UVAtlasTool/UVAtlasTool_2019.vcxproj.filters index daba530e..22434bf5 100644 --- a/UVAtlasTool/UVAtlasTool_2019.vcxproj.filters +++ b/UVAtlasTool/UVAtlasTool_2019.vcxproj.filters @@ -19,6 +19,7 @@ + diff --git a/UVAtlasTool/UVAtlasTool_2022.vcxproj b/UVAtlasTool/UVAtlasTool_2022.vcxproj index 956d72d9..a5547d8b 100644 --- a/UVAtlasTool/UVAtlasTool_2022.vcxproj +++ b/UVAtlasTool/UVAtlasTool_2022.vcxproj @@ -300,6 +300,7 @@ + diff --git a/UVAtlasTool/UVAtlasTool_2022.vcxproj.filters b/UVAtlasTool/UVAtlasTool_2022.vcxproj.filters index daba530e..22434bf5 100644 --- a/UVAtlasTool/UVAtlasTool_2022.vcxproj.filters +++ b/UVAtlasTool/UVAtlasTool_2022.vcxproj.filters @@ -19,6 +19,7 @@ +