mirror of
https://github.com/microsoft/DirectXTex.git
synced 2025-07-09 11:40:14 +02:00
2124 lines
66 KiB
C++
2124 lines
66 KiB
C++
//--------------------------------------------------------------------------------------
|
|
// File: Texenvmap.cpp
|
|
//
|
|
// DirectX Texture environment map tool
|
|
//
|
|
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
//
|
|
// http://go.microsoft.com/fwlink/?LinkId=248926
|
|
//--------------------------------------------------------------------------------------
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 4005)
|
|
#endif
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#define NOMINMAX
|
|
#define NODRAWTEXT
|
|
#define NOGDI
|
|
#define NOMCX
|
|
#define NOSERVICE
|
|
#define NOHELP
|
|
#ifdef _MSC_VER
|
|
#pragma warning(pop)
|
|
#endif
|
|
|
|
#if __cplusplus < 201703L
|
|
#error Requires C++17 (and /Zc:__cplusplus with MSVC)
|
|
#endif
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <cwchar>
|
|
#include <cwctype>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iterator>
|
|
#include <list>
|
|
#include <locale>
|
|
#include <memory>
|
|
#include <new>
|
|
#include <set>
|
|
#include <string>
|
|
#include <tuple>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <wrl/client.h>
|
|
|
|
#include <d3d11_1.h>
|
|
#include <dxgi.h>
|
|
#include <dxgiformat.h>
|
|
|
|
#include <wincodec.h>
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma warning(disable : 4619 4616 26812)
|
|
#endif
|
|
|
|
#include "DirectXTex.h"
|
|
|
|
//Uncomment to add support for OpenEXR (.exr)
|
|
//#define USE_OPENEXR
|
|
|
|
#ifdef USE_OPENEXR
|
|
// See <https://github.com/Microsoft/DirectXTex/wiki/Adding-OpenEXR> for details
|
|
#include "DirectXTexEXR.h"
|
|
#endif
|
|
|
|
// See <https://github.com/Microsoft/DirectXTex/wiki/Using-JPEG-PNG-OSS> for details
|
|
#ifdef USE_LIBJPEG
|
|
#include "DirectXTexJPEG.h"
|
|
#endif
|
|
#ifdef USE_LIBPNG
|
|
#include "DirectXTexPNG.h"
|
|
#endif
|
|
|
|
using namespace DirectX;
|
|
using Microsoft::WRL::ComPtr;
|
|
|
|
namespace
|
|
{
|
|
enum COMMANDS : uint32_t
|
|
{
|
|
CMD_CUBIC = 1,
|
|
CMD_SPHERE,
|
|
CMD_DUAL_PARABOLA,
|
|
CMD_MAX
|
|
};
|
|
|
|
enum OPTIONS : uint32_t
|
|
{
|
|
OPT_RECURSIVE = 1,
|
|
OPT_FILELIST,
|
|
OPT_WIDTH,
|
|
OPT_HEIGHT,
|
|
OPT_FORMAT,
|
|
OPT_FILTER,
|
|
OPT_SRGBI,
|
|
OPT_SRGBO,
|
|
OPT_SRGB,
|
|
OPT_OUTPUTFILE,
|
|
OPT_TOLOWER,
|
|
OPT_OVERWRITE,
|
|
OPT_USE_DX10,
|
|
OPT_NOLOGO,
|
|
OPT_SEPALPHA,
|
|
OPT_NO_WIC,
|
|
OPT_DEMUL_ALPHA,
|
|
OPT_TA_WRAP,
|
|
OPT_TA_MIRROR,
|
|
OPT_GPU,
|
|
OPT_MAX
|
|
};
|
|
|
|
static_assert(OPT_MAX <= 32, "dwOptions is a unsigned int bitfield");
|
|
|
|
struct SConversion
|
|
{
|
|
std::wstring szSrc;
|
|
};
|
|
|
|
struct SValue
|
|
{
|
|
const wchar_t* name;
|
|
uint32_t value;
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
const SValue g_pCommands[] =
|
|
{
|
|
{ L"cubic", CMD_CUBIC },
|
|
{ L"sphere", CMD_SPHERE },
|
|
{ L"parabola", CMD_DUAL_PARABOLA },
|
|
{ nullptr, 0 }
|
|
};
|
|
|
|
const SValue g_pOptions[] =
|
|
{
|
|
{ L"r", OPT_RECURSIVE },
|
|
{ L"flist", OPT_FILELIST },
|
|
{ L"w", OPT_WIDTH },
|
|
{ L"h", OPT_HEIGHT },
|
|
{ L"f", OPT_FORMAT },
|
|
{ L"if", OPT_FILTER },
|
|
{ L"srgbi", OPT_SRGBI },
|
|
{ L"srgbo", OPT_SRGBO },
|
|
{ L"srgb", OPT_SRGB },
|
|
{ L"o", OPT_OUTPUTFILE },
|
|
{ L"l", OPT_TOLOWER },
|
|
{ L"y", OPT_OVERWRITE },
|
|
{ L"dx10", OPT_USE_DX10 },
|
|
{ L"nologo", OPT_NOLOGO },
|
|
{ L"sepalpha", OPT_SEPALPHA },
|
|
{ L"nowic", OPT_NO_WIC },
|
|
{ L"alpha", OPT_DEMUL_ALPHA },
|
|
{ L"wrap", OPT_TA_WRAP },
|
|
{ L"mirror", OPT_TA_MIRROR },
|
|
{ L"gpu", OPT_GPU },
|
|
{ nullptr, 0 }
|
|
};
|
|
|
|
#define DEFFMT(fmt) { L## #fmt, DXGI_FORMAT_ ## fmt }
|
|
|
|
const SValue g_pFormats[] =
|
|
{
|
|
// List only includes render target supported formats
|
|
DEFFMT(R32G32B32A32_FLOAT),
|
|
DEFFMT(R16G16B16A16_FLOAT),
|
|
DEFFMT(R16G16B16A16_UNORM),
|
|
DEFFMT(R32G32_FLOAT),
|
|
DEFFMT(R10G10B10A2_UNORM),
|
|
DEFFMT(R11G11B10_FLOAT),
|
|
DEFFMT(R8G8B8A8_UNORM),
|
|
DEFFMT(R8G8B8A8_UNORM_SRGB),
|
|
DEFFMT(R16G16_FLOAT),
|
|
DEFFMT(R16G16_UNORM),
|
|
DEFFMT(R32_FLOAT),
|
|
DEFFMT(R8G8_UNORM),
|
|
DEFFMT(R16_FLOAT),
|
|
DEFFMT(R16_UNORM),
|
|
DEFFMT(R8_UNORM),
|
|
DEFFMT(R8_UINT),
|
|
DEFFMT(A8_UNORM),
|
|
DEFFMT(B5G6R5_UNORM),
|
|
DEFFMT(B8G8R8A8_UNORM),
|
|
DEFFMT(B8G8R8A8_UNORM_SRGB),
|
|
|
|
// D3D11on12 format
|
|
{ L"A4B4G4R4_UNORM", DXGI_FORMAT(191) },
|
|
|
|
{ nullptr, DXGI_FORMAT_UNKNOWN }
|
|
};
|
|
|
|
const SValue g_pFormatAliases[] =
|
|
{
|
|
{ L"RGBA", DXGI_FORMAT_R8G8B8A8_UNORM },
|
|
{ L"BGRA", DXGI_FORMAT_B8G8R8A8_UNORM },
|
|
{ L"BGR", DXGI_FORMAT_B8G8R8X8_UNORM },
|
|
|
|
{ L"FP16", DXGI_FORMAT_R16G16B16A16_FLOAT },
|
|
{ L"FP32", DXGI_FORMAT_R32G32B32A32_FLOAT },
|
|
|
|
{ nullptr, DXGI_FORMAT_UNKNOWN }
|
|
};
|
|
|
|
const SValue g_pFilters[] =
|
|
{
|
|
{ L"POINT", TEX_FILTER_POINT },
|
|
{ L"LINEAR", TEX_FILTER_LINEAR },
|
|
{ L"CUBIC", TEX_FILTER_CUBIC },
|
|
{ L"FANT", TEX_FILTER_FANT },
|
|
{ L"BOX", TEX_FILTER_BOX },
|
|
{ L"TRIANGLE", TEX_FILTER_TRIANGLE },
|
|
{ L"POINT_DITHER", TEX_FILTER_POINT | TEX_FILTER_DITHER },
|
|
{ L"LINEAR_DITHER", TEX_FILTER_LINEAR | TEX_FILTER_DITHER },
|
|
{ L"CUBIC_DITHER", TEX_FILTER_CUBIC | TEX_FILTER_DITHER },
|
|
{ L"FANT_DITHER", TEX_FILTER_FANT | TEX_FILTER_DITHER },
|
|
{ L"BOX_DITHER", TEX_FILTER_BOX | TEX_FILTER_DITHER },
|
|
{ L"TRIANGLE_DITHER", TEX_FILTER_TRIANGLE | TEX_FILTER_DITHER },
|
|
{ L"POINT_DITHER_DIFFUSION", TEX_FILTER_POINT | TEX_FILTER_DITHER_DIFFUSION },
|
|
{ L"LINEAR_DITHER_DIFFUSION", TEX_FILTER_LINEAR | TEX_FILTER_DITHER_DIFFUSION },
|
|
{ L"CUBIC_DITHER_DIFFUSION", TEX_FILTER_CUBIC | TEX_FILTER_DITHER_DIFFUSION },
|
|
{ L"FANT_DITHER_DIFFUSION", TEX_FILTER_FANT | TEX_FILTER_DITHER_DIFFUSION },
|
|
{ L"BOX_DITHER_DIFFUSION", TEX_FILTER_BOX | TEX_FILTER_DITHER_DIFFUSION },
|
|
{ L"TRIANGLE_DITHER_DIFFUSION", TEX_FILTER_TRIANGLE | TEX_FILTER_DITHER_DIFFUSION },
|
|
{ nullptr, TEX_FILTER_DEFAULT }
|
|
};
|
|
|
|
#define CODEC_DDS 0xFFFF0001
|
|
#define CODEC_TGA 0xFFFF0002
|
|
#define CODEC_HDR 0xFFFF0005
|
|
|
|
#ifdef USE_OPENEXR
|
|
#define CODEC_EXR 0xFFFF0006
|
|
#endif
|
|
|
|
#ifdef USE_LIBJPEG
|
|
#define CODEC_JPEG 0xFFFF0007
|
|
#endif
|
|
#ifdef USE_LIBPNG
|
|
#define CODEC_PNG 0xFFFF0008
|
|
#endif
|
|
|
|
const SValue g_pExtFileTypes[] =
|
|
{
|
|
{ L".BMP", WIC_CODEC_BMP },
|
|
#ifdef USE_LIBJPEG
|
|
{ L".JPG", CODEC_JPEG },
|
|
{ L".JPEG", CODEC_JPEG },
|
|
#else
|
|
{ L".JPG", WIC_CODEC_JPEG },
|
|
{ L".JPEG", WIC_CODEC_JPEG },
|
|
#endif
|
|
#ifdef USE_LIBPNG
|
|
{ L".PNG", CODEC_PNG },
|
|
#else
|
|
{ L".PNG", WIC_CODEC_PNG },
|
|
#endif
|
|
{ L".DDS", CODEC_DDS },
|
|
{ L".TGA", CODEC_TGA },
|
|
{ L".HDR", CODEC_HDR },
|
|
{ L".TIF", WIC_CODEC_TIFF },
|
|
{ L".TIFF", WIC_CODEC_TIFF },
|
|
{ L".WDP", WIC_CODEC_WMP },
|
|
{ L".HDP", WIC_CODEC_WMP },
|
|
{ L".JXR", WIC_CODEC_WMP },
|
|
#ifdef USE_OPENEXR
|
|
{ L".EXR", CODEC_EXR },
|
|
#endif
|
|
{ nullptr, CODEC_DDS }
|
|
};
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace
|
|
{
|
|
#include "Texenvmap_VSBasic.inc"
|
|
#include "Texenvmap_PSBasic.inc"
|
|
#include "Texenvmap_PSEquiRect.inc"
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
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<void, find_closer>;
|
|
|
|
#ifdef _PREFAST_
|
|
#pragma prefast(disable : 26018, "Only used with static internal arrays")
|
|
#endif
|
|
|
|
uint32_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<SConversion>& 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<SConversion>& files)
|
|
{
|
|
std::list<SConversion> flist;
|
|
std::set<std::wstring> 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<SConversion> 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 PrintFormat(DXGI_FORMAT Format)
|
|
{
|
|
for (auto pFormat = g_pFormats; pFormat->name; pFormat++)
|
|
{
|
|
if (static_cast<DXGI_FORMAT>(pFormat->value) == Format)
|
|
{
|
|
wprintf(L"%ls", pFormat->name);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PrintInfo(const TexMetadata& info)
|
|
{
|
|
wprintf(L" (%zux%zu", info.width, info.height);
|
|
|
|
if (TEX_DIMENSION_TEXTURE3D == info.dimension)
|
|
wprintf(L"x%zu", info.depth);
|
|
|
|
if (info.mipLevels > 1)
|
|
wprintf(L",%zu", info.mipLevels);
|
|
|
|
if (info.arraySize > 1)
|
|
wprintf(L",%zu", info.arraySize);
|
|
|
|
wprintf(L" ");
|
|
PrintFormat(info.format);
|
|
|
|
switch (info.dimension)
|
|
{
|
|
case TEX_DIMENSION_TEXTURE1D:
|
|
wprintf(L"%ls", (info.arraySize > 1) ? L" 1DArray" : L" 1D");
|
|
break;
|
|
|
|
case TEX_DIMENSION_TEXTURE2D:
|
|
if (info.IsCubemap())
|
|
{
|
|
wprintf(L"%ls", (info.arraySize > 6) ? L" CubeArray" : L" Cube");
|
|
}
|
|
else
|
|
{
|
|
wprintf(L"%ls", (info.arraySize > 1) ? L" 2DArray" : L" 2D");
|
|
}
|
|
break;
|
|
|
|
case TEX_DIMENSION_TEXTURE3D:
|
|
wprintf(L" 3D");
|
|
break;
|
|
}
|
|
|
|
switch (info.GetAlphaMode())
|
|
{
|
|
case TEX_ALPHA_MODE_OPAQUE:
|
|
wprintf(L" \x0e0:Opaque");
|
|
break;
|
|
case TEX_ALPHA_MODE_PREMULTIPLIED:
|
|
wprintf(L" \x0e0:PM");
|
|
break;
|
|
case TEX_ALPHA_MODE_STRAIGHT:
|
|
wprintf(L" \x0e0:NonPM");
|
|
break;
|
|
case TEX_ALPHA_MODE_CUSTOM:
|
|
wprintf(L" \x0e0:Custom");
|
|
break;
|
|
case TEX_ALPHA_MODE_UNKNOWN:
|
|
break;
|
|
}
|
|
|
|
wprintf(L")");
|
|
}
|
|
|
|
void PrintList(size_t cch, const SValue *pValue)
|
|
{
|
|
while (pValue->name)
|
|
{
|
|
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<uint8_t[]>(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<const wchar_t*>(lpstr), strLen);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!*version || wcscmp(version, L"1.0.0.0") == 0)
|
|
{
|
|
swprintf_s(version, L"%03d (library)", DIRECTX_TEX_VERSION);
|
|
}
|
|
|
|
if (versionOnly)
|
|
{
|
|
wprintf(L"texenvmap version %ls\n", version);
|
|
}
|
|
else
|
|
{
|
|
wprintf(L"Microsoft (R) DirectX Environment Map Tool [DirectXTex] Version %ls\n", version);
|
|
wprintf(L"Copyright (C) Microsoft Corp.\n");
|
|
#ifdef _DEBUG
|
|
wprintf(L"*** Debug build ***\n");
|
|
#endif
|
|
wprintf(L"\n");
|
|
}
|
|
}
|
|
|
|
const wchar_t* GetErrorDesc(HRESULT hr)
|
|
{
|
|
static wchar_t desc[1024] = {};
|
|
|
|
LPWSTR errorText = nullptr;
|
|
|
|
DWORD result = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
|
|
nullptr, static_cast<DWORD>(hr),
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast<LPWSTR>(&errorText), 0, nullptr);
|
|
|
|
*desc = 0;
|
|
|
|
if (result > 0 && errorText)
|
|
{
|
|
swprintf_s(desc, L": %ls", errorText);
|
|
|
|
size_t len = wcslen(desc);
|
|
if (len >= 2)
|
|
{
|
|
desc[len - 2] = 0;
|
|
desc[len - 1] = 0;
|
|
}
|
|
|
|
if (errorText)
|
|
LocalFree(errorText);
|
|
}
|
|
|
|
return desc;
|
|
}
|
|
|
|
_Success_(return != false)
|
|
bool GetDXGIFactory(_Outptr_ IDXGIFactory1** pFactory) noexcept
|
|
{
|
|
if (!pFactory)
|
|
return false;
|
|
|
|
*pFactory = nullptr;
|
|
|
|
typedef HRESULT(WINAPI* pfn_CreateDXGIFactory1)(REFIID riid, _Out_ void** ppFactory);
|
|
|
|
static pfn_CreateDXGIFactory1 s_CreateDXGIFactory1 = nullptr;
|
|
|
|
if (!s_CreateDXGIFactory1)
|
|
{
|
|
HMODULE hModDXGI = LoadLibraryW(L"dxgi.dll");
|
|
if (!hModDXGI)
|
|
return false;
|
|
|
|
s_CreateDXGIFactory1 = reinterpret_cast<pfn_CreateDXGIFactory1>(reinterpret_cast<void*>(GetProcAddress(hModDXGI, "CreateDXGIFactory1")));
|
|
if (!s_CreateDXGIFactory1)
|
|
return false;
|
|
}
|
|
|
|
return SUCCEEDED(s_CreateDXGIFactory1(IID_PPV_ARGS(pFactory)));
|
|
}
|
|
|
|
|
|
void PrintUsage()
|
|
{
|
|
PrintLogo(false);
|
|
|
|
static const wchar_t* const s_usage =
|
|
L"Usage: texenvmap <command> <options> [--] <files>\n\n"
|
|
L" cubic create cubic environment map\n"
|
|
L" sphere create sphere environment map\n"
|
|
L" dualparabola create dual-parabolic environment map\n"
|
|
L" -r wildcard filename search is recursive\n"
|
|
L" -flist <filename> use text file with a list of input files (one per line)\n"
|
|
L" -w <n> width\n"
|
|
L" -h <n> height\n"
|
|
L" -f <format> format\n"
|
|
L" -if <filter> image filtering\n"
|
|
L" -srgb{i|o} sRGB {input, output}\n"
|
|
L" -o <filename> output filename\n"
|
|
L" -l force output filename to lower case\n"
|
|
L" -y overwrite existing output file (if any)\n"
|
|
L" -sepalpha resize alpha channel separately from color channels\n"
|
|
L" -nowic Force non-WIC filtering\n"
|
|
L" -wrap, -mirror texture addressing mode (wrap, mirror, or clamp)\n"
|
|
L" -alpha convert premultiplied alpha to straight alpha\n"
|
|
L" -dx10 Force use of 'DX10' extended header\n"
|
|
L" -nologo suppress copyright message\n"
|
|
L" -gpu <adapter> Select GPU for DirectCompute-based codecs (0 is default)\n"
|
|
L"\n"
|
|
L" '-- ' is needed if any input filepath starts with the '-' or '/' character\n";
|
|
|
|
wprintf(L"%ls", s_usage);
|
|
|
|
wprintf(L"\n <format>: ");
|
|
PrintList(13, g_pFormats);
|
|
wprintf(L" ");
|
|
PrintList(13, g_pFormatAliases);
|
|
|
|
wprintf(L"\n <filter>: ");
|
|
PrintList(13, g_pFilters);
|
|
|
|
ComPtr<IDXGIFactory1> dxgiFactory;
|
|
if (GetDXGIFactory(dxgiFactory.GetAddressOf()))
|
|
{
|
|
wprintf(L"\n <adapter>:\n");
|
|
|
|
ComPtr<IDXGIAdapter> adapter;
|
|
for (UINT adapterIndex = 0;
|
|
SUCCEEDED(dxgiFactory->EnumAdapters(adapterIndex, adapter.ReleaseAndGetAddressOf()));
|
|
++adapterIndex)
|
|
{
|
|
DXGI_ADAPTER_DESC desc;
|
|
if (SUCCEEDED(adapter->GetDesc(&desc)))
|
|
{
|
|
wprintf(L" %u: VID:%04X, PID:%04X - %ls\n", adapterIndex, desc.VendorId, desc.DeviceId, desc.Description);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
_Success_(return != false)
|
|
bool CreateDevice(int adapter, _Outptr_ ID3D11Device** pDevice) noexcept
|
|
{
|
|
if (!pDevice)
|
|
return false;
|
|
|
|
*pDevice = nullptr;
|
|
|
|
static PFN_D3D11_CREATE_DEVICE s_DynamicD3D11CreateDevice = nullptr;
|
|
|
|
if (!s_DynamicD3D11CreateDevice)
|
|
{
|
|
HMODULE hModD3D11 = LoadLibraryW(L"d3d11.dll");
|
|
if (!hModD3D11)
|
|
return false;
|
|
|
|
s_DynamicD3D11CreateDevice = reinterpret_cast<PFN_D3D11_CREATE_DEVICE>(reinterpret_cast<void*>(GetProcAddress(hModD3D11, "D3D11CreateDevice")));
|
|
if (!s_DynamicD3D11CreateDevice)
|
|
return false;
|
|
}
|
|
|
|
D3D_FEATURE_LEVEL featureLevels[] =
|
|
{
|
|
D3D_FEATURE_LEVEL_11_0,
|
|
D3D_FEATURE_LEVEL_10_1,
|
|
D3D_FEATURE_LEVEL_10_0,
|
|
};
|
|
|
|
UINT createDeviceFlags = 0;
|
|
#ifdef _DEBUG
|
|
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
|
|
#endif
|
|
|
|
ComPtr<IDXGIAdapter> pAdapter;
|
|
if (adapter >= 0)
|
|
{
|
|
ComPtr<IDXGIFactory1> dxgiFactory;
|
|
if (GetDXGIFactory(dxgiFactory.GetAddressOf()))
|
|
{
|
|
if (FAILED(dxgiFactory->EnumAdapters(static_cast<UINT>(adapter), pAdapter.GetAddressOf())))
|
|
{
|
|
wprintf(L"\nERROR: Invalid GPU adapter index (%d)!\n", adapter);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
D3D_FEATURE_LEVEL fl;
|
|
HRESULT hr = s_DynamicD3D11CreateDevice(pAdapter.Get(),
|
|
(pAdapter) ? D3D_DRIVER_TYPE_UNKNOWN : D3D_DRIVER_TYPE_HARDWARE,
|
|
nullptr, createDeviceFlags, featureLevels, static_cast<UINT>(std::size(featureLevels)),
|
|
D3D11_SDK_VERSION, pDevice, &fl, nullptr);
|
|
if (FAILED(hr))
|
|
{
|
|
hr = s_DynamicD3D11CreateDevice(nullptr,
|
|
D3D_DRIVER_TYPE_WARP,
|
|
nullptr, createDeviceFlags, featureLevels, static_cast<UINT>(std::size(featureLevels)),
|
|
D3D11_SDK_VERSION, pDevice, &fl, nullptr);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
ComPtr<IDXGIDevice> dxgiDevice;
|
|
hr = (*pDevice)->QueryInterface(IID_PPV_ARGS(dxgiDevice.GetAddressOf()));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = dxgiDevice->GetAdapter(pAdapter.ReleaseAndGetAddressOf());
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
DXGI_ADAPTER_DESC desc;
|
|
hr = pAdapter->GetDesc(&desc);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
wprintf(L"[Using Direct3D on \"%ls\"]\n\n", desc.Description);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
|
|
struct ConstantBuffer
|
|
{
|
|
XMFLOAT4X4 transform;
|
|
};
|
|
|
|
static_assert((sizeof(ConstantBuffer) % 16) == 0, "CB incorrect alignment");
|
|
|
|
class Shaders
|
|
{
|
|
public:
|
|
Shaders() = default;
|
|
|
|
HRESULT Create(ID3D11Device* device)
|
|
{
|
|
if (!device)
|
|
return E_INVALIDARG;
|
|
|
|
m_vertexShader.clear();
|
|
m_pixelShader.clear();
|
|
|
|
for (size_t j = 0; j < std::size(s_vs); ++j)
|
|
{
|
|
ComPtr<ID3D11VertexShader> shader;
|
|
HRESULT hr = device->CreateVertexShader(s_vs[j].code, s_vs[j].length, nullptr, shader.GetAddressOf());
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
m_vertexShader.emplace_back(shader);
|
|
}
|
|
|
|
for (size_t j = 0; j < std::size(s_ps); ++j)
|
|
{
|
|
ComPtr<ID3D11PixelShader> shader;
|
|
HRESULT hr = device->CreatePixelShader(s_ps[j].code, s_ps[j].length, nullptr, shader.GetAddressOf());
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
m_pixelShader.emplace_back(shader);
|
|
}
|
|
|
|
CD3D11_BUFFER_DESC desc(sizeof(ConstantBuffer), D3D11_BIND_CONSTANT_BUFFER, D3D11_USAGE_DYNAMIC, D3D11_CPU_ACCESS_WRITE);
|
|
HRESULT hr = device->CreateBuffer(&desc, nullptr, m_constantBuffer.ReleaseAndGetAddressOf());
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
enum VS_INDEX : unsigned int
|
|
{
|
|
VS_BASIC = 0,
|
|
};
|
|
|
|
enum PS_INDEX : unsigned int
|
|
{
|
|
PS_BASIC = 0,
|
|
PS_EQUIRECT,
|
|
};
|
|
|
|
void Apply(
|
|
unsigned int vsindex,
|
|
unsigned int psindex,
|
|
_In_ ID3D11DeviceContext* deviceContext,
|
|
_In_opt_ ConstantBuffer* cbuffer)
|
|
{
|
|
if ((vsindex >= std::size(s_vs))
|
|
|| (psindex >= std::size(s_ps))
|
|
|| !deviceContext)
|
|
return;
|
|
|
|
deviceContext->VSSetShader(m_vertexShader[vsindex].Get(), nullptr, 0);
|
|
deviceContext->PSSetShader(m_pixelShader[psindex].Get(), nullptr, 0);
|
|
|
|
if (cbuffer)
|
|
{
|
|
D3D11_MAPPED_SUBRESOURCE mapped = {};
|
|
if (SUCCEEDED(deviceContext->Map(m_constantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped)))
|
|
{
|
|
memcpy(mapped.pData, cbuffer, sizeof(ConstantBuffer));
|
|
deviceContext->Unmap(m_constantBuffer.Get(), 0);
|
|
}
|
|
auto cb = m_constantBuffer.Get();
|
|
deviceContext->VSSetConstantBuffers(0, 1, &cb);
|
|
}
|
|
}
|
|
|
|
void GetVertexShaderBytecode(
|
|
unsigned int vsindex,
|
|
_Out_ void const** pShaderByteCode,
|
|
_Out_ size_t* pByteCodeLength)
|
|
{
|
|
if (pShaderByteCode)
|
|
{
|
|
*pShaderByteCode = nullptr;
|
|
}
|
|
|
|
if (pByteCodeLength)
|
|
{
|
|
*pByteCodeLength = 0;
|
|
}
|
|
|
|
if (!pShaderByteCode
|
|
|| !pByteCodeLength
|
|
|| (vsindex >= std::size(s_vs)))
|
|
return;
|
|
|
|
*pShaderByteCode = s_vs[vsindex].code;
|
|
*pByteCodeLength = s_vs[vsindex].length;
|
|
}
|
|
|
|
private:
|
|
ComPtr<ID3D11Buffer> m_constantBuffer;
|
|
std::vector<ComPtr<ID3D11VertexShader>> m_vertexShader;
|
|
std::vector<ComPtr<ID3D11PixelShader>> m_pixelShader;
|
|
|
|
struct ShaderBytecode
|
|
{
|
|
void const* code;
|
|
size_t length;
|
|
};
|
|
|
|
const ShaderBytecode s_vs[1] =
|
|
{
|
|
{ Texenvmap_VSBasic, sizeof(Texenvmap_VSBasic) },
|
|
};
|
|
|
|
const ShaderBytecode s_ps[2] =
|
|
{
|
|
{ Texenvmap_PSBasic, sizeof(Texenvmap_PSBasic) },
|
|
{ Texenvmap_PSEquiRect, sizeof(Texenvmap_PSEquiRect) },
|
|
};
|
|
};
|
|
|
|
|
|
class StateObjects
|
|
{
|
|
public:
|
|
StateObjects() = default;
|
|
|
|
HRESULT Create(ID3D11Device* device) noexcept
|
|
{
|
|
if (!device)
|
|
return E_INVALIDARG;
|
|
|
|
{
|
|
D3D11_BLEND_DESC desc = {};
|
|
desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
|
|
|
|
desc.RenderTarget[0].BlendEnable = FALSE;
|
|
desc.RenderTarget[0].SrcBlend = desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
|
|
desc.RenderTarget[0].DestBlend = desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
|
|
desc.RenderTarget[0].BlendOp = desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
|
|
|
|
HRESULT hr = device->CreateBlendState(&desc, m_opaque.ReleaseAndGetAddressOf());
|
|
if (FAILED(hr))
|
|
return hr;
|
|
}
|
|
|
|
{
|
|
D3D11_DEPTH_STENCIL_DESC desc = {};
|
|
desc.DepthFunc = D3D11_COMPARISON_LESS_EQUAL;
|
|
desc.StencilReadMask = D3D11_DEFAULT_STENCIL_READ_MASK;
|
|
desc.StencilWriteMask = D3D11_DEFAULT_STENCIL_WRITE_MASK;
|
|
|
|
desc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
|
|
desc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
|
|
desc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
|
|
desc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
|
|
|
|
desc.BackFace = desc.FrontFace;
|
|
HRESULT hr = device->CreateDepthStencilState(&desc, m_depthNone.ReleaseAndGetAddressOf());
|
|
if (FAILED(hr))
|
|
return hr;
|
|
}
|
|
|
|
{
|
|
D3D11_RASTERIZER_DESC desc = {};
|
|
desc.CullMode = D3D11_CULL_NONE;
|
|
desc.FillMode = D3D11_FILL_SOLID;
|
|
desc.DepthClipEnable = TRUE;
|
|
desc.MultisampleEnable = TRUE;
|
|
|
|
HRESULT hr = device->CreateRasterizerState(&desc, m_cullNone.ReleaseAndGetAddressOf());
|
|
if (FAILED(hr))
|
|
return hr;
|
|
}
|
|
|
|
{
|
|
D3D11_SAMPLER_DESC desc = {};
|
|
desc.MaxAnisotropy = D3D11_MAX_MAXANISOTROPY;
|
|
desc.MaxLOD = FLT_MAX;
|
|
desc.ComparisonFunc = D3D11_COMPARISON_NEVER;
|
|
|
|
desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
|
|
desc.AddressU = desc.AddressV = desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
|
|
HRESULT hr = device->CreateSamplerState(&desc, m_linearClamp.ReleaseAndGetAddressOf());
|
|
if (FAILED(hr))
|
|
return hr;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
ID3D11BlendState* Opaque() const noexcept { return m_opaque.Get(); }
|
|
ID3D11DepthStencilState* DepthNone() const noexcept { return m_depthNone.Get(); }
|
|
ID3D11RasterizerState* CullNone() const noexcept { return m_cullNone.Get(); }
|
|
ID3D11SamplerState* LinearClamp() const noexcept { return m_linearClamp.Get(); }
|
|
|
|
private:
|
|
ComPtr<ID3D11BlendState> m_opaque;
|
|
ComPtr<ID3D11DepthStencilState> m_depthNone;
|
|
ComPtr<ID3D11RasterizerState> m_cullNone;
|
|
ComPtr<ID3D11SamplerState> m_linearClamp;
|
|
};
|
|
|
|
|
|
class RenderTarget
|
|
{
|
|
public:
|
|
RenderTarget() : viewPort{} {}
|
|
|
|
HRESULT Create(
|
|
ID3D11Device* device,
|
|
size_t width,
|
|
size_t height,
|
|
DXGI_FORMAT format) noexcept
|
|
{
|
|
texture.Reset();
|
|
srv.Reset();
|
|
rtv.Reset();
|
|
|
|
if (!device || !width || !height)
|
|
return E_INVALIDARG;
|
|
|
|
if ((width > D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION)
|
|
|| (height > D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION))
|
|
{
|
|
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
|
|
}
|
|
|
|
D3D11_TEXTURE2D_DESC desc = {};
|
|
desc.Width = static_cast<UINT>(width);
|
|
desc.Height = static_cast<UINT>(height);
|
|
desc.MipLevels = desc.ArraySize = 1;
|
|
desc.Format = format;
|
|
desc.SampleDesc.Count = 1;
|
|
desc.Usage = D3D11_USAGE_DEFAULT;
|
|
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
|
|
|
|
HRESULT hr = device->CreateTexture2D(&desc, nullptr, texture.GetAddressOf());
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = device->CreateShaderResourceView(texture.Get(), nullptr, srv.GetAddressOf());
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
hr = device->CreateRenderTargetView(texture.Get(), nullptr, rtv.GetAddressOf());
|
|
if (FAILED(hr))
|
|
return hr;
|
|
}
|
|
|
|
viewPort.TopLeftX = viewPort.TopLeftY = 0.f;
|
|
viewPort.Width = static_cast<float>(width);
|
|
viewPort.Height = static_cast<float>(height);
|
|
viewPort.MinDepth = D3D11_MIN_DEPTH;
|
|
viewPort.MaxDepth = D3D11_MAX_DEPTH;
|
|
|
|
return hr;
|
|
}
|
|
|
|
void Begin(ID3D11DeviceContext* context, bool clear = false)
|
|
{
|
|
if (!context)
|
|
return;
|
|
|
|
if (clear)
|
|
{
|
|
float black[4] = { 0.f, 0.f, 0.f, 1.f };
|
|
context->ClearRenderTargetView(rtv.Get(), black);
|
|
}
|
|
|
|
context->OMSetRenderTargets(1, rtv.GetAddressOf(), nullptr);
|
|
|
|
context->RSSetViewports(1, &viewPort);
|
|
}
|
|
|
|
void End(ID3D11DeviceContext* context)
|
|
{
|
|
if (!context)
|
|
return;
|
|
|
|
ID3D11RenderTargetView* nullrtv = nullptr;
|
|
context->OMSetRenderTargets(1, &nullrtv, nullptr);
|
|
}
|
|
|
|
ID3D11ShaderResourceView* GetSRV() const noexcept { return srv.Get(); }
|
|
ID3D11Texture2D* GetTexture() const noexcept { return texture.Get(); }
|
|
|
|
private:
|
|
D3D11_VIEWPORT viewPort;
|
|
ComPtr<ID3D11Texture2D> texture;
|
|
ComPtr<ID3D11ShaderResourceView> srv;
|
|
ComPtr<ID3D11RenderTargetView> rtv;
|
|
};
|
|
|
|
|
|
// Vertex types
|
|
struct VertexPositionTexture
|
|
{
|
|
XMFLOAT3 position;
|
|
XMFLOAT2 texcoord;
|
|
|
|
static constexpr unsigned int InputElementCount = 2;
|
|
static const D3D11_INPUT_ELEMENT_DESC InputElements[InputElementCount];
|
|
};
|
|
|
|
const D3D11_INPUT_ELEMENT_DESC VertexPositionTexture::InputElements[] =
|
|
{
|
|
{ "SV_Position", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
|
|
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
|
|
};
|
|
|
|
|
|
class UnitCube
|
|
{
|
|
public:
|
|
UnitCube() = default;
|
|
|
|
HRESULT Create(_In_ ID3D11Device* device) noexcept
|
|
{
|
|
if (!device)
|
|
return E_INVALIDARG;
|
|
|
|
D3D11_BUFFER_DESC desc = {};
|
|
desc.Usage = D3D11_USAGE_DEFAULT;
|
|
desc.ByteWidth = static_cast<UINT>(sizeof(VertexPositionTexture) * nVerts);
|
|
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
|
|
|
|
D3D11_SUBRESOURCE_DATA initData = {};
|
|
initData.pSysMem = c_cubeVertices;
|
|
|
|
HRESULT hr = device->CreateBuffer(&desc, &initData, vertexBuffer.ReleaseAndGetAddressOf());
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
desc.ByteWidth = static_cast<UINT>(sizeof(uint16_t) * nFaces * 3);
|
|
desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
|
|
initData.pSysMem = c_cubeIndices;
|
|
|
|
return device->CreateBuffer(&desc, &initData, indexBuffer.ReleaseAndGetAddressOf());
|
|
}
|
|
|
|
void Draw(_In_ ID3D11DeviceContext* context)
|
|
{
|
|
if (!context)
|
|
return;
|
|
|
|
auto vb = vertexBuffer.Get();
|
|
UINT stride = sizeof(VertexPositionTexture);
|
|
UINT offset = 0;
|
|
context->IASetVertexBuffers(0, 1, &vb, &stride, &offset);
|
|
|
|
context->IASetIndexBuffer(indexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0);
|
|
context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
|
|
|
context->DrawIndexed(nFaces * 3, 0, 0);
|
|
}
|
|
|
|
HRESULT CreateInputLayout(_In_ ID3D11Device* device, Shaders& shaders, _COM_Outptr_ ID3D11InputLayout** layout)
|
|
{
|
|
if (layout)
|
|
{
|
|
*layout = nullptr;
|
|
}
|
|
|
|
if (!device || !layout)
|
|
return E_INVALIDARG;
|
|
|
|
const void* code = nullptr;
|
|
size_t length = 0;
|
|
shaders.GetVertexShaderBytecode(Shaders::VS_BASIC, &code, &length);
|
|
|
|
return device->CreateInputLayout(
|
|
VertexPositionTexture::InputElements,
|
|
VertexPositionTexture::InputElementCount,
|
|
code,
|
|
length,
|
|
layout);
|
|
}
|
|
|
|
private:
|
|
static constexpr UINT nVerts = 24;
|
|
static const VertexPositionTexture c_cubeVertices[nVerts];
|
|
|
|
static constexpr UINT nFaces = 12;
|
|
static const uint16_t c_cubeIndices[nFaces * 3];
|
|
|
|
ComPtr<ID3D11Buffer> vertexBuffer;
|
|
ComPtr<ID3D11Buffer> indexBuffer;
|
|
};
|
|
|
|
const VertexPositionTexture UnitCube::c_cubeVertices[] =
|
|
{
|
|
{ XMFLOAT3(-1.0f, 1.0f, -1.0f), XMFLOAT2(1.0f, 0.0f) },
|
|
{ XMFLOAT3(1.0f, 1.0f, -1.0f), XMFLOAT2(0.0f, 0.0f) },
|
|
{ XMFLOAT3(1.0f, 1.0f, 1.0f), XMFLOAT2(0.0f, 1.0f) },
|
|
{ XMFLOAT3(-1.0f, 1.0f, 1.0f), XMFLOAT2(1.0f, 1.0f) },
|
|
|
|
{ XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT2(0.0f, 0.0f) },
|
|
{ XMFLOAT3(1.0f, -1.0f, -1.0f), XMFLOAT2(1.0f, 0.0f) },
|
|
{ XMFLOAT3(1.0f, -1.0f, 1.0f), XMFLOAT2(1.0f, 1.0f) },
|
|
{ XMFLOAT3(-1.0f, -1.0f, 1.0f), XMFLOAT2(0.0f, 1.0f) },
|
|
|
|
{ XMFLOAT3(-1.0f, -1.0f, 1.0f), XMFLOAT2(0.0f, 1.0f) },
|
|
{ XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT2(1.0f, 1.0f) },
|
|
{ XMFLOAT3(-1.0f, 1.0f, -1.0f), XMFLOAT2(1.0f, 0.0f) },
|
|
{ XMFLOAT3(-1.0f, 1.0f, 1.0f), XMFLOAT2(0.0f, 0.0f) },
|
|
|
|
{ XMFLOAT3(1.0f, -1.0f, 1.0f), XMFLOAT2(1.0f, 1.0f) },
|
|
{ XMFLOAT3(1.0f, -1.0f, -1.0f), XMFLOAT2(0.0f, 1.0f) },
|
|
{ XMFLOAT3(1.0f, 1.0f, -1.0f), XMFLOAT2(0.0f, 0.0f) },
|
|
{ XMFLOAT3(1.0f, 1.0f, 1.0f), XMFLOAT2(1.0f, 0.0f) },
|
|
|
|
{ XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT2(0.0f, 1.0f) },
|
|
{ XMFLOAT3(1.0f, -1.0f, -1.0f), XMFLOAT2(1.0f, 1.0f) },
|
|
{ XMFLOAT3(1.0f, 1.0f, -1.0f), XMFLOAT2(1.0f, 0.0f) },
|
|
{ XMFLOAT3(-1.0f, 1.0f, -1.0f), XMFLOAT2(0.0f, 0.0f) },
|
|
|
|
{ XMFLOAT3(-1.0f, -1.0f, 1.0f), XMFLOAT2(1.0f, 1.0f) },
|
|
{ XMFLOAT3(1.0f, -1.0f, 1.0f), XMFLOAT2(0.0f, 1.0f) },
|
|
{ XMFLOAT3(1.0f, 1.0f, 1.0f), XMFLOAT2(0.0f, 0.0f) },
|
|
{ XMFLOAT3(-1.0f, 1.0f, 1.0f), XMFLOAT2(1.0f, 0.0f) },
|
|
};
|
|
|
|
const uint16_t UnitCube::c_cubeIndices[] =
|
|
{
|
|
3,1,0,
|
|
2,1,3,
|
|
|
|
6,4,5,
|
|
7,4,6,
|
|
|
|
11,9,8,
|
|
10,9,11,
|
|
|
|
14,12,13,
|
|
15,12,14,
|
|
|
|
19,17,16,
|
|
18,17,19,
|
|
|
|
22,20,21,
|
|
23,20,22
|
|
};
|
|
|
|
|
|
size_t FitPowerOf2(size_t targetx, size_t maxsize)
|
|
{
|
|
size_t x;
|
|
for (x = maxsize; x > 1; x >>= 1) { if (x <= targetx) break; }
|
|
return x;
|
|
}
|
|
|
|
void FitPowerOf2(size_t& targetx, size_t& targety, size_t maxsize)
|
|
{
|
|
float origAR = float(targetx) / float(targety);
|
|
|
|
if (targetx > targety)
|
|
{
|
|
size_t x;
|
|
for (x = maxsize; x > 1; x >>= 1) { if (x <= targetx) break; }
|
|
targetx = x;
|
|
|
|
float bestScore = FLT_MAX;
|
|
for (size_t y = maxsize; y > 0; y >>= 1)
|
|
{
|
|
float score = fabsf((float(x) / float(y)) - origAR);
|
|
if (score < bestScore)
|
|
{
|
|
bestScore = score;
|
|
targety = y;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
size_t y;
|
|
for (y = maxsize; y > 1; y >>= 1) { if (y <= targety) break; }
|
|
targety = y;
|
|
|
|
float bestScore = FLT_MAX;
|
|
for (size_t x = maxsize; x > 0; x >>= 1)
|
|
{
|
|
float score = fabsf((float(x) / float(y)) - origAR);
|
|
if (score < bestScore)
|
|
{
|
|
bestScore = score;
|
|
targetx = x;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------
|
|
// Entry-point
|
|
//--------------------------------------------------------------------------------------
|
|
#ifdef _PREFAST_
|
|
#pragma prefast(disable : 28198, "Command-line tool, frees all memory on exit")
|
|
#endif
|
|
|
|
int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[])
|
|
{
|
|
// Parameters and defaults
|
|
size_t width = 0;
|
|
size_t height = 0;
|
|
|
|
DXGI_FORMAT format = DXGI_FORMAT_UNKNOWN;
|
|
TEX_FILTER_FLAGS dwFilter = TEX_FILTER_DEFAULT;
|
|
TEX_FILTER_FLAGS dwSRGB = TEX_FILTER_DEFAULT;
|
|
TEX_FILTER_FLAGS dwFilterOpts = TEX_FILTER_DEFAULT;
|
|
uint32_t fileType = WIC_CODEC_BMP;
|
|
int adapter = -1;
|
|
|
|
std::wstring outputFile;
|
|
|
|
// Set locale for output since GetErrorDesc can get localized strings.
|
|
std::locale::global(std::locale(""));
|
|
|
|
// Set locale for output since GetErrorDesc can get localized strings.
|
|
std::locale::global(std::locale(""));
|
|
|
|
// Initialize COM (needed for WIC)
|
|
HRESULT hr = hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Failed to initialize COM (%08X%ls)\n", static_cast<unsigned int>(hr), GetErrorDesc(hr));
|
|
return 1;
|
|
}
|
|
|
|
// Process command line
|
|
if (argc < 2)
|
|
{
|
|
PrintUsage();
|
|
return 0;
|
|
}
|
|
|
|
const uint32_t dwCommand = LookupByName(argv[1], g_pCommands);
|
|
switch (dwCommand)
|
|
{
|
|
case CMD_CUBIC:
|
|
case CMD_SPHERE:
|
|
case CMD_DUAL_PARABOLA:
|
|
break;
|
|
|
|
default:
|
|
wprintf(L"Must use one of: ");
|
|
PrintList(4, g_pCommands);
|
|
return 1;
|
|
}
|
|
|
|
uint32_t dwOptions = 0;
|
|
std::list<SConversion> conversion;
|
|
bool allowOpts = true;
|
|
|
|
for (int iArg = 2; iArg < argc; iArg++)
|
|
{
|
|
PWSTR pArg = argv[iArg];
|
|
|
|
if (allowOpts
|
|
&& ('-' == pArg[0]) && ('-' == pArg[1]))
|
|
{
|
|
if (pArg[2] == 0)
|
|
{
|
|
// "-- " is the POSIX standard for "end of options" marking to escape the '-' and '/' characters at the start of filepaths.
|
|
allowOpts = false;
|
|
}
|
|
else if (!_wcsicmp(pArg, L"--version"))
|
|
{
|
|
PrintLogo(true);
|
|
return 0;
|
|
}
|
|
else if (!_wcsicmp(pArg, L"--help"))
|
|
{
|
|
PrintUsage();
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
wprintf(L"Unknown option: %ls\n", pArg);
|
|
return 1;
|
|
}
|
|
}
|
|
else if (allowOpts
|
|
&& (('-' == pArg[0]) || ('/' == pArg[0])))
|
|
{
|
|
pArg++;
|
|
PWSTR pValue;
|
|
|
|
for (pValue = pArg; *pValue && (':' != *pValue); pValue++);
|
|
|
|
if (*pValue)
|
|
*pValue++ = 0;
|
|
|
|
const uint32_t dwOption = LookupByName(pArg, g_pOptions);
|
|
if (!dwOption || (dwOptions & (1 << dwOption)))
|
|
{
|
|
PrintUsage();
|
|
return 1;
|
|
}
|
|
|
|
dwOptions |= 1 << dwOption;
|
|
|
|
// Handle options with additional value parameter
|
|
switch (dwOption)
|
|
{
|
|
case OPT_FILELIST:
|
|
case OPT_WIDTH:
|
|
case OPT_HEIGHT:
|
|
case OPT_FORMAT:
|
|
case OPT_FILTER:
|
|
case OPT_OUTPUTFILE:
|
|
case OPT_GPU:
|
|
if (!*pValue)
|
|
{
|
|
if ((iArg + 1 >= argc))
|
|
{
|
|
PrintUsage();
|
|
return 1;
|
|
}
|
|
|
|
iArg++;
|
|
pValue = argv[iArg];
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (dwOption)
|
|
{
|
|
case OPT_WIDTH:
|
|
if (swscanf_s(pValue, L"%zu", &width) != 1)
|
|
{
|
|
wprintf(L"Invalid value specified with -w (%ls)\n", pValue);
|
|
return 1;
|
|
}
|
|
break;
|
|
|
|
case OPT_HEIGHT:
|
|
if (swscanf_s(pValue, L"%zu", &height) != 1)
|
|
{
|
|
wprintf(L"Invalid value specified with -h (%ls)\n", pValue);
|
|
return 1;
|
|
}
|
|
break;
|
|
|
|
case OPT_FORMAT:
|
|
format = static_cast<DXGI_FORMAT>(LookupByName(pValue, g_pFormats));
|
|
if (!format)
|
|
{
|
|
format = static_cast<DXGI_FORMAT>(LookupByName(pValue, g_pFormatAliases));
|
|
if (!format)
|
|
{
|
|
wprintf(L"Invalid value specified with -f (%ls)\n", pValue);
|
|
return 1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case OPT_FILTER:
|
|
dwFilter = static_cast<TEX_FILTER_FLAGS>(LookupByName(pValue, g_pFilters));
|
|
if (!dwFilter)
|
|
{
|
|
wprintf(L"Invalid value specified with -if (%ls)\n", pValue);
|
|
return 1;
|
|
}
|
|
break;
|
|
|
|
case OPT_SRGBI:
|
|
dwSRGB |= TEX_FILTER_SRGB_IN;
|
|
break;
|
|
|
|
case OPT_SRGBO:
|
|
dwSRGB |= TEX_FILTER_SRGB_OUT;
|
|
break;
|
|
|
|
case OPT_SRGB:
|
|
dwSRGB |= TEX_FILTER_SRGB;
|
|
break;
|
|
|
|
case OPT_SEPALPHA:
|
|
dwFilterOpts |= TEX_FILTER_SEPARATE_ALPHA;
|
|
break;
|
|
|
|
case OPT_NO_WIC:
|
|
dwFilterOpts |= TEX_FILTER_FORCE_NON_WIC;
|
|
break;
|
|
|
|
case OPT_OUTPUTFILE:
|
|
{
|
|
std::filesystem::path path(pValue);
|
|
outputFile = path.make_preferred().native();
|
|
|
|
fileType = LookupByName(path.extension().c_str(), g_pExtFileTypes);
|
|
|
|
if (fileType != CODEC_DDS)
|
|
{
|
|
wprintf(L"Environment map output file must be a dds\n");
|
|
return 1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OPT_TA_WRAP:
|
|
if (dwFilterOpts & TEX_FILTER_MIRROR)
|
|
{
|
|
wprintf(L"Can't use -wrap and -mirror at same time\n\n");
|
|
PrintUsage();
|
|
return 1;
|
|
}
|
|
dwFilterOpts |= TEX_FILTER_WRAP;
|
|
break;
|
|
|
|
case OPT_TA_MIRROR:
|
|
if (dwFilterOpts & TEX_FILTER_WRAP)
|
|
{
|
|
wprintf(L"Can't use -wrap and -mirror at same time\n\n");
|
|
PrintUsage();
|
|
return 1;
|
|
}
|
|
dwFilterOpts |= TEX_FILTER_MIRROR;
|
|
break;
|
|
|
|
case OPT_GPU:
|
|
if (swscanf_s(pValue, L"%d", &adapter) != 1)
|
|
{
|
|
wprintf(L"Invalid value specified with -gpu (%ls)\n\n", pValue);
|
|
PrintUsage();
|
|
return 1;
|
|
}
|
|
else if (adapter < 0)
|
|
{
|
|
wprintf(L"Adapter index (%ls)\n\n", pValue);
|
|
PrintUsage();
|
|
return 1;
|
|
}
|
|
break;
|
|
|
|
case OPT_FILELIST:
|
|
{
|
|
std::filesystem::path path(pValue);
|
|
std::wifstream inFile(path.make_preferred().c_str());
|
|
if (!inFile)
|
|
{
|
|
wprintf(L"Error opening -flist file %ls\n", pValue);
|
|
return 1;
|
|
}
|
|
|
|
inFile.imbue(std::locale::classic());
|
|
|
|
ProcessFileList(inFile, conversion);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else if (wcspbrk(pArg, L"?*") != nullptr)
|
|
{
|
|
const size_t count = conversion.size();
|
|
std::filesystem::path path(pArg);
|
|
SearchForFiles(path.make_preferred(), conversion, (dwOptions & (1 << OPT_RECURSIVE)) != 0);
|
|
if (conversion.size() <= count)
|
|
{
|
|
wprintf(L"No matching files found for %ls\n", pArg);
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SConversion conv = {};
|
|
std::filesystem::path path(pArg);
|
|
conv.szSrc = path.make_preferred().native();
|
|
conversion.push_back(conv);
|
|
}
|
|
}
|
|
|
|
if (conversion.empty())
|
|
{
|
|
PrintUsage();
|
|
return 0;
|
|
}
|
|
|
|
if (~dwOptions & (1 << OPT_NOLOGO))
|
|
PrintLogo(false);
|
|
|
|
ComPtr<ID3D11Device> pDevice;
|
|
if (!CreateDevice(adapter, pDevice.GetAddressOf()))
|
|
{
|
|
wprintf(L"\nERROR: Direct3D device not available\n");
|
|
return 1;
|
|
}
|
|
|
|
ComPtr<ID3D11DeviceContext> pContext;
|
|
pDevice->GetImmediateContext(pContext.GetAddressOf());
|
|
|
|
StateObjects stateObjects;
|
|
hr = stateObjects.Create(pDevice.Get());
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L" FAILED creating Direct3D state objects (%08X%ls)\n", static_cast<unsigned int>(hr), GetErrorDesc(hr));
|
|
return 1;
|
|
}
|
|
|
|
Shaders shaders;
|
|
hr = shaders.Create(pDevice.Get());
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L" FAILED creating Direct3D shaders (%08X%ls)\n", static_cast<unsigned int>(hr), GetErrorDesc(hr));
|
|
return 1;
|
|
}
|
|
|
|
UnitCube unitCube;
|
|
hr = unitCube.Create(pDevice.Get());
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L" FAILED creating Direct3D unit cube (%08X%ls)\n", static_cast<unsigned int>(hr), GetErrorDesc(hr));
|
|
return 1;
|
|
}
|
|
|
|
if (format != DXGI_FORMAT_UNKNOWN)
|
|
{
|
|
UINT support = 0;
|
|
hr = pDevice->CheckFormatSupport(format, &support);
|
|
UINT required = D3D11_FORMAT_SUPPORT_TEXTURE2D | D3D11_FORMAT_SUPPORT_RENDER_TARGET;
|
|
if (FAILED(hr) || ((support & required) != required))
|
|
{
|
|
wprintf(L"\nERROR: Direct3D device does not support format as a render target (DXGI_FORMAT_");
|
|
PrintFormat(format);
|
|
wprintf(L")\n");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (conversion.size() != 1 && conversion.size() != 6)
|
|
{
|
|
wprintf(L"ERROR: cubic/sphere/parabola requires 1 or 6 input images\n");
|
|
return 1;
|
|
}
|
|
|
|
// Load images
|
|
size_t images = 0;
|
|
|
|
std::vector<std::unique_ptr<ScratchImage>> loadedImages;
|
|
|
|
size_t maxWidth = 0;
|
|
size_t maxHeight = 0;
|
|
|
|
for (auto pConv = conversion.begin(); pConv != conversion.end(); ++pConv)
|
|
{
|
|
std::filesystem::path curpath(pConv->szSrc);
|
|
auto const ext = curpath.extension();
|
|
|
|
// Load source image
|
|
if (pConv != conversion.begin())
|
|
wprintf(L"\n");
|
|
else if (outputFile.empty())
|
|
{
|
|
if (_wcsicmp(curpath.extension().c_str(), L".dds") == 0)
|
|
{
|
|
wprintf(L"ERROR: Need to specify output file via -o\n");
|
|
return 1;
|
|
}
|
|
|
|
outputFile = curpath.stem().concat(L".dds").native();
|
|
break;
|
|
}
|
|
|
|
wprintf(L"reading %ls", curpath.c_str());
|
|
fflush(stdout);
|
|
|
|
TexMetadata info;
|
|
std::unique_ptr<ScratchImage> image(new (std::nothrow) ScratchImage);
|
|
if (!image)
|
|
{
|
|
wprintf(L"\nERROR: Memory allocation failed\n");
|
|
return 1;
|
|
}
|
|
|
|
if (_wcsicmp(ext.c_str(), L".dds") == 0)
|
|
{
|
|
hr = LoadFromDDSFile(curpath.c_str(), DDS_FLAGS_ALLOW_LARGE_FILES, &info, *image);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L" FAILED (%08X%ls)\n", static_cast<unsigned int>(hr), GetErrorDesc(hr));
|
|
return 1;
|
|
}
|
|
|
|
if (info.IsVolumemap())
|
|
{
|
|
wprintf(L"\nERROR: Can't use volume textures as input\n");
|
|
return 1;
|
|
}
|
|
|
|
if (info.arraySize > 1 && info.arraySize != 6)
|
|
{
|
|
wprintf(L"\nERROR: Can only use single cubemap or 6-entry array textures\n");
|
|
return 1;
|
|
}
|
|
}
|
|
else if (_wcsicmp(ext.c_str(), L".tga") == 0)
|
|
{
|
|
TGA_FLAGS tgaFlags = (IsBGR(format)) ? TGA_FLAGS_BGR : TGA_FLAGS_NONE;
|
|
|
|
hr = LoadFromTGAFile(curpath.c_str(), tgaFlags, &info, *image);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L" FAILED (%08X%ls)\n", static_cast<unsigned int>(hr), GetErrorDesc(hr));
|
|
return 1;
|
|
}
|
|
}
|
|
else if (_wcsicmp(ext.c_str(), L".hdr") == 0)
|
|
{
|
|
hr = LoadFromHDRFile(curpath.c_str(), &info, *image);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L" FAILED (%08X%ls)\n", static_cast<unsigned int>(hr), GetErrorDesc(hr));
|
|
return 1;
|
|
}
|
|
}
|
|
#ifdef USE_OPENEXR
|
|
else if (_wcsicmp(ext.c_str(), L".exr") == 0)
|
|
{
|
|
hr = LoadFromEXRFile(curpath.c_str(), &info, *image);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L" FAILED (%08X%ls)\n", static_cast<unsigned int>(hr), GetErrorDesc(hr));
|
|
return 1;
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef USE_LIBJPEG
|
|
else if (_wcsicmp(ext.c_str(), L".jpg") == 0 || _wcsicmp(ext.c_str(), L".jpeg") == 0)
|
|
{
|
|
hr = LoadFromJPEGFile(curpath.c_str(), &info, *image);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L" FAILED (%08X%ls)\n", static_cast<unsigned int>(hr), GetErrorDesc(hr));
|
|
return 1;
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef USE_LIBPNG
|
|
else if (_wcsicmp(ext.c_str(), L".png") == 0)
|
|
{
|
|
hr = LoadFromPNGFile(curpath.c_str(), &info, *image);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L" FAILED (%08X%ls)\n", static_cast<unsigned int>(hr), GetErrorDesc(hr));
|
|
return 1;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
else
|
|
{
|
|
// WIC shares the same filter values for mode and dither
|
|
static_assert(static_cast<int>(WIC_FLAGS_DITHER) == static_cast<int>(TEX_FILTER_DITHER), "WIC_FLAGS_* & TEX_FILTER_* should match");
|
|
static_assert(static_cast<int>(WIC_FLAGS_DITHER_DIFFUSION) == static_cast<int>(TEX_FILTER_DITHER_DIFFUSION), "WIC_FLAGS_* & TEX_FILTER_* should match");
|
|
static_assert(static_cast<int>(WIC_FLAGS_FILTER_POINT) == static_cast<int>(TEX_FILTER_POINT), "WIC_FLAGS_* & TEX_FILTER_* should match");
|
|
static_assert(static_cast<int>(WIC_FLAGS_FILTER_LINEAR) == static_cast<int>(TEX_FILTER_LINEAR), "WIC_FLAGS_* & TEX_FILTER_* should match");
|
|
static_assert(static_cast<int>(WIC_FLAGS_FILTER_CUBIC) == static_cast<int>(TEX_FILTER_CUBIC), "WIC_FLAGS_* & TEX_FILTER_* should match");
|
|
static_assert(static_cast<int>(WIC_FLAGS_FILTER_FANT) == static_cast<int>(TEX_FILTER_FANT), "WIC_FLAGS_* & TEX_FILTER_* should match");
|
|
|
|
hr = LoadFromWICFile(curpath.c_str(), WIC_FLAGS_NONE | dwFilter, &info, *image);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L" FAILED (%08X%ls)\n", static_cast<unsigned int>(hr), GetErrorDesc(hr));
|
|
if (hr == static_cast<HRESULT>(0xc00d5212) /* MF_E_TOPO_CODEC_NOT_FOUND */)
|
|
{
|
|
if (_wcsicmp(ext.c_str(), L".heic") == 0 || _wcsicmp(ext.c_str(), L".heif") == 0)
|
|
{
|
|
wprintf(L"INFO: This format requires installing the HEIF Image Extensions - https://aka.ms/heif\n");
|
|
}
|
|
else if (_wcsicmp(ext.c_str(), L".webp") == 0)
|
|
{
|
|
wprintf(L"INFO: This format requires installing the WEBP Image Extensions - https://www.microsoft.com/p/webp-image-extensions/9pg2dk419drg\n");
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
PrintInfo(info);
|
|
|
|
fflush(stdout);
|
|
|
|
// --- Planar ------------------------------------------------------------------
|
|
if (IsPlanar(info.format))
|
|
{
|
|
auto img = image->GetImage(0, 0, 0);
|
|
assert(img);
|
|
const size_t nimg = image->GetImageCount();
|
|
|
|
std::unique_ptr<ScratchImage> timage(new (std::nothrow) ScratchImage);
|
|
if (!timage)
|
|
{
|
|
wprintf(L"\nERROR: Memory allocation failed\n");
|
|
return 1;
|
|
}
|
|
|
|
hr = ConvertToSinglePlane(img, nimg, info, *timage);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L" FAILED [converttosingleplane] (%08X%ls)\n", static_cast<unsigned int>(hr), GetErrorDesc(hr));
|
|
continue;
|
|
}
|
|
|
|
auto& tinfo = timage->GetMetadata();
|
|
|
|
info.format = tinfo.format;
|
|
|
|
assert(info.width == tinfo.width);
|
|
assert(info.height == tinfo.height);
|
|
assert(info.depth == tinfo.depth);
|
|
assert(info.arraySize == tinfo.arraySize);
|
|
assert(info.mipLevels == tinfo.mipLevels);
|
|
assert(info.miscFlags == tinfo.miscFlags);
|
|
assert(info.dimension == tinfo.dimension);
|
|
|
|
image.swap(timage);
|
|
}
|
|
|
|
// --- Decompress --------------------------------------------------------------
|
|
if (IsCompressed(info.format))
|
|
{
|
|
const Image* img = image->GetImage(0, 0, 0);
|
|
assert(img);
|
|
const size_t nimg = image->GetImageCount();
|
|
|
|
std::unique_ptr<ScratchImage> timage(new (std::nothrow) ScratchImage);
|
|
if (!timage)
|
|
{
|
|
wprintf(L"\nERROR: Memory allocation failed\n");
|
|
return 1;
|
|
}
|
|
|
|
hr = Decompress(img, nimg, info, DXGI_FORMAT_UNKNOWN /* picks good default */, *timage.get());
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L" FAILED [decompress] (%08X%ls)\n", static_cast<unsigned int>(hr), GetErrorDesc(hr));
|
|
continue;
|
|
}
|
|
|
|
auto& tinfo = timage->GetMetadata();
|
|
|
|
info.format = tinfo.format;
|
|
|
|
assert(info.width == tinfo.width);
|
|
assert(info.height == tinfo.height);
|
|
assert(info.depth == tinfo.depth);
|
|
assert(info.arraySize == tinfo.arraySize);
|
|
assert(info.mipLevels == tinfo.mipLevels);
|
|
assert(info.miscFlags == tinfo.miscFlags);
|
|
assert(info.dimension == tinfo.dimension);
|
|
|
|
image.swap(timage);
|
|
}
|
|
|
|
// --- Undo Premultiplied Alpha (if requested) ---------------------------------
|
|
if ((dwOptions & (1 << OPT_DEMUL_ALPHA))
|
|
&& HasAlpha(info.format)
|
|
&& info.format != DXGI_FORMAT_A8_UNORM)
|
|
{
|
|
if (info.GetAlphaMode() == TEX_ALPHA_MODE_STRAIGHT)
|
|
{
|
|
printf("\nWARNING: Image is already using straight alpha\n");
|
|
}
|
|
else if (!info.IsPMAlpha())
|
|
{
|
|
printf("\nWARNING: Image is not using premultipled alpha\n");
|
|
}
|
|
else
|
|
{
|
|
auto img = image->GetImage(0, 0, 0);
|
|
assert(img);
|
|
const size_t nimg = image->GetImageCount();
|
|
|
|
std::unique_ptr<ScratchImage> timage(new (std::nothrow) ScratchImage);
|
|
if (!timage)
|
|
{
|
|
wprintf(L"\nERROR: Memory allocation failed\n");
|
|
return 1;
|
|
}
|
|
|
|
hr = PremultiplyAlpha(img, nimg, info, TEX_PMALPHA_REVERSE | dwSRGB, *timage);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L" FAILED [demultiply alpha] (%08X%ls)\n", static_cast<unsigned int>(hr), GetErrorDesc(hr));
|
|
continue;
|
|
}
|
|
|
|
auto& tinfo = timage->GetMetadata();
|
|
info.miscFlags2 = tinfo.miscFlags2;
|
|
|
|
assert(info.width == tinfo.width);
|
|
assert(info.height == tinfo.height);
|
|
assert(info.depth == tinfo.depth);
|
|
assert(info.arraySize == tinfo.arraySize);
|
|
assert(info.mipLevels == tinfo.mipLevels);
|
|
assert(info.miscFlags == tinfo.miscFlags);
|
|
assert(info.dimension == tinfo.dimension);
|
|
|
|
image.swap(timage);
|
|
}
|
|
}
|
|
|
|
if (format == DXGI_FORMAT_UNKNOWN)
|
|
{
|
|
format = (FormatDataType(info.format) == FORMAT_TYPE_FLOAT)
|
|
? DXGI_FORMAT_R32G32B32A32_FLOAT : DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
}
|
|
|
|
images += info.arraySize;
|
|
|
|
if (info.arraySize > 1)
|
|
{
|
|
for(size_t j = 0; j < info.arraySize; ++j)
|
|
{
|
|
auto img = image->GetImage(0, j, 0);
|
|
if (!img)
|
|
{
|
|
wprintf(L"\nERROR: Splitting array failed\n");
|
|
return 1;
|
|
}
|
|
|
|
std::unique_ptr<ScratchImage> timage(new (std::nothrow) ScratchImage);
|
|
if (!timage)
|
|
{
|
|
wprintf(L"\nERROR: Memory allocation failed\n");
|
|
return 1;
|
|
}
|
|
|
|
hr = timage->InitializeFromImage(*img);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L" FAILED [splitting array] (%08X%ls)\n", static_cast<unsigned int>(hr), GetErrorDesc(hr));
|
|
return 1;
|
|
}
|
|
|
|
loadedImages.emplace_back(std::move(timage));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
loadedImages.emplace_back(std::move(image));
|
|
}
|
|
|
|
if (info.width > maxWidth)
|
|
maxWidth = info.width;
|
|
if (info.height > maxHeight)
|
|
maxHeight = info.height;
|
|
}
|
|
|
|
if (images > 6)
|
|
{
|
|
wprintf(L"WARNING: Ignoring additional images, only using first 6 of %zu to form input cubemap\n", images);
|
|
}
|
|
|
|
// --- Convert input to cubemap ----------------------------------------------------
|
|
if (!width)
|
|
width = height;
|
|
|
|
if (!height)
|
|
height = width;
|
|
|
|
if (!width || !height)
|
|
{
|
|
// TODO - Make pow2?
|
|
if (images == 1)
|
|
{
|
|
width = height = FitPowerOf2(maxHeight, 16384);
|
|
}
|
|
else
|
|
{
|
|
width = maxWidth;
|
|
height = maxHeight;
|
|
FitPowerOf2(width, height, 16384);
|
|
}
|
|
}
|
|
|
|
size_t cubeWidth = (dwCommand == CMD_CUBIC) ? width : (images == 1) ? maxHeight : maxWidth;
|
|
size_t cubeHeight = (dwCommand == CMD_CUBIC) ? height : maxHeight;
|
|
|
|
RenderTarget cubemap[6];
|
|
for (auto& it : cubemap)
|
|
{
|
|
hr = it.Create(pDevice.Get(), cubeWidth, cubeHeight, format);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L" FAILED to initialize Direct3D cubemap (%08X%ls)\n", static_cast<unsigned int>(hr), GetErrorDesc(hr));
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
ComPtr<ID3D11InputLayout> inputLayout;
|
|
hr = unitCube.CreateInputLayout(pDevice.Get(), shaders, inputLayout.GetAddressOf());
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L" FAILED to initialize Direct3D input layout (%08X%ls)\n", static_cast<unsigned int>(hr), GetErrorDesc(hr));
|
|
return 1;
|
|
}
|
|
|
|
if (images == 1)
|
|
{
|
|
// TODO - perform equirectangular projection to cubemap
|
|
}
|
|
else
|
|
{
|
|
pContext->OMSetBlendState(stateObjects.Opaque(), nullptr, 0xFFFFFFFF);
|
|
pContext->OMSetDepthStencilState(stateObjects.DepthNone(), 0);
|
|
pContext->RSSetState(stateObjects.CullNone());
|
|
auto linear = stateObjects.LinearClamp();
|
|
|
|
for (size_t face = 0; face < 6; ++face)
|
|
{
|
|
ComPtr<ID3D11ShaderResourceView> srv;
|
|
auto& input = loadedImages[face];
|
|
|
|
hr = CreateShaderResourceView(pDevice.Get(), input->GetImage(0, 0, 0), 1, input->GetMetadata(), srv.GetAddressOf());
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L" FAILED to initialize Direct3D texture from image #%zu (%08X%ls)\n", face, static_cast<unsigned int>(hr), GetErrorDesc(hr));
|
|
return 1;
|
|
}
|
|
|
|
cubemap[face].Begin(pContext.Get(), false);
|
|
|
|
XMMATRIX mat = XMMatrixIdentity();
|
|
|
|
ConstantBuffer cbuffer;
|
|
XMStoreFloat4x4(&cbuffer.transform, mat);
|
|
|
|
shaders.Apply(Shaders::VS_BASIC, Shaders::PS_BASIC, pContext.Get(), &cbuffer);
|
|
pContext->IASetInputLayout(inputLayout.Get());
|
|
pContext->PSSetShaderResources(0, 1, srv.GetAddressOf());
|
|
pContext->PSSetSamplers(0, 1, &linear);
|
|
|
|
unitCube.Draw(pContext.Get());
|
|
|
|
cubemap[face].End(pContext.Get());
|
|
}
|
|
}
|
|
|
|
// --- Create result ---------------------------------------------------------------
|
|
|
|
// TODO - sphere / dual parabolic projection
|
|
|
|
// --- Write result ----------------------------------------------------------------
|
|
wprintf(L"\nWriting %ls ", outputFile.c_str());
|
|
fflush(stdout);
|
|
|
|
if (dwOptions & (1 << OPT_TOLOWER))
|
|
{
|
|
std::transform(outputFile.begin(), outputFile.end(), outputFile.begin(), towlower);
|
|
}
|
|
|
|
if (~dwOptions & (1 << OPT_OVERWRITE))
|
|
{
|
|
if (GetFileAttributesW(outputFile.c_str()) != INVALID_FILE_ATTRIBUTES)
|
|
{
|
|
wprintf(L"\nERROR: Output file already exists, use -y to overwrite\n");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
ScratchImage image[6];
|
|
Image imageArray[6] = {};
|
|
switch (dwCommand)
|
|
{
|
|
case CMD_CUBIC:
|
|
for (size_t face = 0; face < 6; ++face)
|
|
{
|
|
hr = CaptureTexture(pDevice.Get(), pContext.Get(), cubemap[face].GetTexture(), image[face]);
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L" FAILED to capture Direct3D texture from image #%zu (%08X%ls)\n", face, static_cast<unsigned int>(hr), GetErrorDesc(hr));
|
|
return 1;
|
|
}
|
|
|
|
imageArray[face] = *image[face].GetImage(0, 0, 0);
|
|
}
|
|
|
|
{
|
|
TexMetadata mdata = {};
|
|
mdata.width = imageArray[0].width;
|
|
mdata.height = imageArray[0].height;
|
|
mdata.format = imageArray[0].format;
|
|
mdata.arraySize = 6;
|
|
mdata.depth = mdata.mipLevels = 1;
|
|
mdata.miscFlags = TEX_MISC_TEXTURECUBE;
|
|
mdata.dimension = TEX_DIMENSION_TEXTURE2D;
|
|
|
|
hr = SaveToDDSFile(imageArray, 6, mdata,
|
|
(dwOptions & (1 << OPT_USE_DX10)) ? (DDS_FLAGS_FORCE_DX10_EXT | DDS_FLAGS_FORCE_DX10_EXT_MISC2) : DDS_FLAGS_NONE,
|
|
outputFile.c_str());
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"\nFAILED (%08X%ls)\n", static_cast<unsigned int>(hr), GetErrorDesc(hr));
|
|
return 1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
printf("ERROR: E_NOTIMPL\n");
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|