Files
WickedEngine/WickedEngine/wiHelper.cpp
T
2021-07-09 11:06:31 +02:00

863 lines
22 KiB
C++

#include "wiHelper.h"
#include "wiPlatform.h"
#include "wiRenderer.h"
#include "wiBackLog.h"
#include "wiEvent.h"
#include "Utility/stb_image_write.h"
#include <thread>
#include <locale>
#include <chrono>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <codecvt> // string conversion
#include <filesystem>
#ifdef _WIN32
#include <direct.h>
#ifdef PLATFORM_UWP
#include <winrt/Windows.UI.Popups.h>
#include <winrt/Windows.Storage.h>
#include <winrt/Windows.Storage.Pickers.h>
#include <winrt/Windows.Storage.AccessCache.h>
#include <winrt/Windows.Storage.Streams.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#else
#include <Commdlg.h> // openfile
#include <WinBase.h>
#endif // PLATFORM_UWP
#else
#include "Utility/portable-file-dialogs.h"
#endif // _WIN32
namespace wiHelper
{
std::string toUpper(const std::string& s)
{
std::string result;
std::locale loc;
for (unsigned int i = 0; i < s.length(); ++i)
{
result += std::toupper(s.at(i), loc);
}
return result;
}
void messageBox(const std::string& msg, const std::string& caption)
{
#ifdef _WIN32
#ifndef PLATFORM_UWP
MessageBoxA(GetActiveWindow(), msg.c_str(), caption.c_str(), 0);
#else
std::wstring wmessage, wcaption;
StringConvert(msg, wmessage);
StringConvert(caption, wcaption);
// UWP can only show message box on main thread:
wiEvent::Subscribe_Once(SYSTEM_EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) {
winrt::Windows::UI::Popups::MessageDialog(wmessage, wcaption).ShowAsync();
});
#endif // PLATFORM_UWP
#elif SDL2
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, caption.c_str(), msg.c_str(), NULL);
#endif // _WIN32
}
void screenshot(const wiGraphics::SwapChain& swapchain, const std::string& name)
{
std::string directory;
if (name.empty())
{
directory = std::filesystem::current_path().string() + "/screenshots";
}
else
{
directory = GetDirectoryFromPath(name);
}
DirectoryCreate(directory);
std::string filename = name;
if (filename.empty())
{
filename = directory + "/sc_" + getCurrentDateTimeAsString() + ".jpg";
}
bool result = saveTextureToFile(wiRenderer::GetDevice()->GetBackBuffer(&swapchain), filename);
assert(result);
if (result)
{
std::string msg = "Screenshot saved: " + filename;
wiBackLog::post(msg.c_str());
}
}
bool saveTextureToMemory(const wiGraphics::Texture& texture, std::vector<uint8_t>& texturedata)
{
using namespace wiGraphics;
GraphicsDevice* device = wiRenderer::GetDevice();
TextureDesc desc = texture.GetDesc();
uint32_t data_count = desc.Width * desc.Height;
uint32_t data_stride = device->GetFormatStride(desc.Format);
uint32_t data_size = data_count * data_stride;
texturedata.clear();
texturedata.resize(data_size);
Texture stagingTex;
TextureDesc staging_desc = desc;
staging_desc.Usage = USAGE_STAGING;
staging_desc.CPUAccessFlags = CPU_ACCESS_READ;
staging_desc.BindFlags = 0;
staging_desc.MiscFlags = 0;
staging_desc.MipLevels = 1;
staging_desc.layout = IMAGE_LAYOUT_COPY_DST;
bool success = device->CreateTexture(&staging_desc, nullptr, &stagingTex);
assert(success);
CommandList cmd = device->BeginCommandList();
{
GPUBarrier barriers[] = {
GPUBarrier::Image(&texture, texture.desc.layout, IMAGE_LAYOUT_COPY_SRC, 0)
};
device->Barrier(barriers, arraysize(barriers), cmd);
}
device->CopyResource(&stagingTex, &texture, cmd);
{
GPUBarrier barriers[] = {
GPUBarrier::Image(&texture, IMAGE_LAYOUT_COPY_SRC, texture.desc.layout, 0)
};
device->Barrier(barriers, arraysize(barriers), cmd);
}
device->SubmitCommandLists();
device->WaitForGPU();
Mapping mapping;
mapping._flags = Mapping::FLAG_READ;
mapping.size = data_size;
device->Map(&stagingTex, &mapping);
if (mapping.data != nullptr)
{
if (mapping.rowpitch / data_stride != desc.Width)
{
// Copy padded texture row by row:
const uint32_t cpysize = desc.Width * data_stride;
for (uint32_t i = 0; i < desc.Height; ++i)
{
void* src = (void*)((size_t)mapping.data + size_t(i * mapping.rowpitch));
void* dst = (void*)((size_t)texturedata.data() + size_t(i * cpysize));
memcpy(dst, src, cpysize);
}
}
else
{
// Copy whole
std::memcpy(texturedata.data(), mapping.data, texturedata.size());
}
device->Unmap(&stagingTex);
}
else
{
assert(0);
}
return mapping.data != nullptr;
}
bool saveTextureToMemoryFile(const wiGraphics::Texture& texture, const std::string& fileExtension, std::vector<uint8_t>& filedata)
{
using namespace wiGraphics;
TextureDesc desc = texture.GetDesc();
std::vector<uint8_t> texturedata;
if (saveTextureToMemory(texture, texturedata))
{
return saveTextureToMemoryFile(texturedata, desc, fileExtension, filedata);
}
return false;
}
bool saveTextureToMemoryFile(const std::vector<uint8_t>& texturedata, const wiGraphics::TextureDesc& desc, const std::string& fileExtension, std::vector<uint8_t>& filedata)
{
using namespace wiGraphics;
uint32_t data_count = desc.Width * desc.Height;
if (desc.Format == FORMAT_R10G10B10A2_UNORM)
{
// So this should be converted first to rgba8 before saving to common format...
uint32_t* data32 = (uint32_t*)texturedata.data();
for (uint32_t i = 0; i < data_count; ++i)
{
uint32_t pixel = data32[i];
float r = ((pixel >> 0) & 1023) / 1023.0f;
float g = ((pixel >> 10) & 1023) / 1023.0f;
float b = ((pixel >> 20) & 1023) / 1023.0f;
float a = ((pixel >> 30) & 3) / 3.0f;
uint32_t rgba8 = 0;
rgba8 |= (uint32_t)(r * 255.0f) << 0;
rgba8 |= (uint32_t)(g * 255.0f) << 8;
rgba8 |= (uint32_t)(b * 255.0f) << 16;
rgba8 |= (uint32_t)(a * 255.0f) << 24;
data32[i] = rgba8;
}
}
else if (desc.Format == FORMAT_R32G32B32A32_FLOAT)
{
// So this should be converted first to rgba8 before saving to common format...
XMFLOAT4* data128 = (XMFLOAT4*)texturedata.data();
uint32_t* data32 = (uint32_t*)texturedata.data();
for (uint32_t i = 0; i < data_count; ++i)
{
XMFLOAT4 pixel = data128[i];
float r = std::max(0.0f, std::min(pixel.x, 1.0f));
float g = std::max(0.0f, std::min(pixel.y, 1.0f));
float b = std::max(0.0f, std::min(pixel.z, 1.0f));
float a = std::max(0.0f, std::min(pixel.w, 1.0f));
uint32_t rgba8 = 0;
rgba8 |= (uint32_t)(r * 255.0f) << 0;
rgba8 |= (uint32_t)(g * 255.0f) << 8;
rgba8 |= (uint32_t)(b * 255.0f) << 16;
rgba8 |= (uint32_t)(a * 255.0f) << 24;
data32[i] = rgba8;
}
}
else
{
assert(desc.Format == FORMAT_R8G8B8A8_UNORM); // If you need to save other backbuffer format, convert the data here yourself...
}
int write_result = 0;
filedata.clear();
stbi_write_func* func = [](void* context, void* data, int size) {
std::vector<uint8_t>& filedata = *(std::vector<uint8_t>*)context;
for (int i = 0; i < size; ++i)
{
filedata.push_back(*((uint8_t*)data + i));
}
};
std::string extension = wiHelper::toUpper(fileExtension);
if (!extension.compare("JPG") || !extension.compare("JPEG"))
{
write_result = stbi_write_jpg_to_func(func, &filedata, (int)desc.Width, (int)desc.Height, 4, texturedata.data(), 100);
}
else if (!extension.compare("PNG"))
{
write_result = stbi_write_png_to_func(func, &filedata, (int)desc.Width, (int)desc.Height, 4, texturedata.data(), 0);
}
else if (!extension.compare("TGA"))
{
write_result = stbi_write_tga_to_func(func, &filedata, (int)desc.Width, (int)desc.Height, 4, texturedata.data());
}
else if (!extension.compare("BMP"))
{
write_result = stbi_write_bmp_to_func(func, &filedata, (int)desc.Width, (int)desc.Height, 4, texturedata.data());
}
else
{
assert(0 && "Unsupported extension");
}
return write_result != 0;
}
bool saveTextureToFile(const wiGraphics::Texture& texture, const std::string& fileName)
{
using namespace wiGraphics;
TextureDesc desc = texture.GetDesc();
std::vector<uint8_t> data;
if (saveTextureToMemory(texture, data))
{
return saveTextureToFile(data, desc, fileName);
}
return false;
}
bool saveTextureToFile(const std::vector<uint8_t>& texturedata, const wiGraphics::TextureDesc& desc, const std::string& fileName)
{
using namespace wiGraphics;
std::string ext = GetExtensionFromFileName(fileName);
std::vector<uint8_t> filedata;
if (saveTextureToMemoryFile(texturedata, desc, ext, filedata))
{
return FileWrite(fileName, filedata.data(), filedata.size());
}
return false;
}
std::string getCurrentDateTimeAsString()
{
time_t t = std::time(nullptr);
struct tm time_info;
#ifdef _WIN32
localtime_s(&time_info, &t);
#else
localtime(&t);
#endif
std::stringstream ss("");
ss << std::put_time(&time_info, "%d-%m-%Y %H-%M-%S");
return ss.str();
}
void SplitPath(const std::string& fullPath, std::string& dir, std::string& fileName)
{
size_t found;
found = fullPath.find_last_of("/\\");
dir = fullPath.substr(0, found + 1);
fileName = fullPath.substr(found + 1);
}
std::string GetFileNameFromPath(const std::string& fullPath)
{
if (fullPath.empty())
{
return fullPath;
}
std::string ret, empty;
SplitPath(fullPath, empty, ret);
return ret;
}
std::string GetDirectoryFromPath(const std::string& fullPath)
{
if (fullPath.empty())
{
return fullPath;
}
std::string ret, empty;
SplitPath(fullPath, ret, empty);
return ret;
}
std::string GetExtensionFromFileName(const std::string& filename)
{
size_t idx = filename.rfind('.');
if (idx != std::string::npos)
{
std::string extension = filename.substr(idx + 1);
return extension;
}
// No extension found
return "";
}
std::string ReplaceExtension(const std::string& filename, const std::string& extension)
{
size_t idx = filename.rfind('.');
if (idx != std::string::npos)
{
return filename.substr(0, idx + 1) + extension;
}
return filename;
}
void MakePathRelative(const std::string& rootdir, std::string& path)
{
if (rootdir.empty() || path.empty())
{
return;
}
std::filesystem::path filepath = path;
std::filesystem::path rootpath = rootdir;
std::filesystem::path relative = std::filesystem::relative(path, rootdir);
if (!relative.empty())
{
path = relative.string();
}
//size_t found = path.rfind(rootdir);
//if (found != std::string::npos)
//{
// path = path.substr(found + rootdir.length());
//}
}
void MakePathAbsolute(std::string& path)
{
std::filesystem::path filepath = path;
std::filesystem::path absolute = std::filesystem::absolute(path);
if (!absolute.empty())
{
path = absolute.string();
}
}
void DirectoryCreate(const std::string& path)
{
std::filesystem::create_directories(path);
}
bool FileRead(const std::string& fileName, std::vector<uint8_t>& data)
{
#ifndef PLATFORM_UWP
#ifdef SDL_FILESYSTEM_UNIX
std::string filepath = fileName;
std::replace(filepath.begin(), filepath.end(), '\\', '/');
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
#else
std::ifstream file(fileName, std::ios::binary | std::ios::ate);
#endif // SDL_FILESYSTEM_UNIX
if (file.is_open())
{
size_t dataSize = (size_t)file.tellg();
file.seekg(0, file.beg);
data.resize(dataSize);
file.read((char*)data.data(), dataSize);
file.close();
return true;
}
#else
using namespace winrt::Windows::Storage;
using namespace winrt::Windows::Storage::Streams;
using namespace winrt::Windows::Foundation;
std::wstring wstr;
std::filesystem::path filepath = fileName;
filepath = std::filesystem::absolute(filepath);
StringConvert(filepath.string(), wstr);
bool success = false;
auto async_helper = [&]() -> IAsyncAction {
try
{
auto file = co_await StorageFile::GetFileFromPathAsync(wstr);
auto buffer = co_await FileIO::ReadBufferAsync(file);
auto reader = DataReader::FromBuffer(buffer);
auto size = buffer.Length();
data.resize((size_t)size);
for (auto& x : data)
{
x = reader.ReadByte();
}
success = true;
}
catch (winrt::hresult_error const& ex)
{
switch (ex.code())
{
case E_ACCESSDENIED:
wiBackLog::post(("Opening file failed: " + fileName + " | Reason: Permission Denied!").c_str());
break;
default:
break;
}
}
};
if (winrt::impl::is_sta_thread())
{
std::thread([&] { async_helper().get(); }).join(); // can't block coroutine from ui thread
}
else
{
async_helper().get();
}
if (success)
{
return true;
}
#endif // PLATFORM_UWP
wiBackLog::post(("File not found: " + fileName).c_str());
return false;
}
bool FileWrite(const std::string& fileName, const uint8_t* data, size_t size)
{
if (size <= 0)
{
return false;
}
#ifndef PLATFORM_UWP
std::ofstream file(fileName, std::ios::binary | std::ios::trunc);
if (file.is_open())
{
file.write((const char*)data, (std::streamsize)size);
file.close();
return true;
}
#else
using namespace winrt::Windows::Storage;
using namespace winrt::Windows::Storage::Streams;
using namespace winrt::Windows::Foundation;
std::wstring wstr;
std::filesystem::path filepath = fileName;
filepath = std::filesystem::absolute(filepath);
StringConvert(filepath.string(), wstr);
CREATEFILE2_EXTENDED_PARAMETERS params = {};
params.dwSize = (DWORD)size;
params.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
HANDLE filehandle = CreateFile2FromAppW(wstr.c_str(), GENERIC_READ | GENERIC_WRITE, 0, CREATE_ALWAYS, &params);
assert(filehandle);
CloseHandle(filehandle);
bool success = false;
auto async_helper = [&]() -> IAsyncAction {
try
{
auto file = co_await StorageFile::GetFileFromPathAsync(wstr);
winrt::array_view<const uint8_t> dataarray(data, (winrt::array_view<const uint8_t>::size_type)size);
co_await FileIO::WriteBytesAsync(file, dataarray);
success = true;
}
catch (winrt::hresult_error const& ex)
{
switch (ex.code())
{
case E_ACCESSDENIED:
wiBackLog::post(("Opening file failed: " + fileName + " | Reason: Permission Denied!").c_str());
break;
default:
break;
}
}
};
if (winrt::impl::is_sta_thread())
{
std::thread([&] { async_helper().get(); }).join(); // can't block coroutine from ui thread
}
else
{
async_helper().get();
}
if (success)
{
return true;
}
#endif // PLATFORM_UWP
return false;
}
bool FileExists(const std::string& fileName)
{
bool exists = std::filesystem::exists(fileName);
return exists;
}
void FileDialog(const FileDialogParams& params, std::function<void(std::string fileName)> onSuccess)
{
#ifdef _WIN32
#ifndef PLATFORM_UWP
std::thread([=] {
char szFile[256];
OPENFILENAMEA ofn;
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = nullptr;
ofn.lpstrFile = szFile;
// Set lpstrFile[0] to '\0' so that GetOpenFileName does not
// use the contents of szFile to initialize itself.
ofn.lpstrFile[0] = '\0';
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.nFilterIndex = 1;
// Slightly convoluted way to create the filter.
// First string is description, ended by '\0'
// Second string is extensions, each separated by ';' and at the end of all, a '\0'
// Then the whole container string is closed with an other '\0'
// For example: "model files\0*.model;*.obj;\0" <-- this string literal has "model files" as description and two accepted extensions "model" and "obj"
std::vector<char> filter;
filter.reserve(256);
{
for (auto& x : params.description)
{
filter.push_back(x);
}
filter.push_back(0);
for (auto& x : params.extensions)
{
filter.push_back('*');
filter.push_back('.');
for (auto& y : x)
{
filter.push_back(y);
}
filter.push_back(';');
}
filter.push_back(0);
filter.push_back(0);
}
ofn.lpstrFilter = filter.data();
BOOL ok = FALSE;
switch (params.type)
{
case FileDialogParams::OPEN:
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
ofn.Flags |= OFN_NOCHANGEDIR;
ok = GetOpenFileNameA(&ofn) == TRUE;
break;
case FileDialogParams::SAVE:
ofn.Flags = OFN_OVERWRITEPROMPT;
ofn.Flags |= OFN_NOCHANGEDIR;
ok = GetSaveFileNameA(&ofn) == TRUE;
break;
}
if (ok)
{
onSuccess(ofn.lpstrFile);
}
}).detach();
#else
auto filedialoghelper = [](FileDialogParams params, std::function<void(std::string fileName)> onSuccess) -> winrt::fire_and_forget {
using namespace winrt::Windows::Storage;
using namespace winrt::Windows::Storage::Pickers;
using namespace winrt::Windows::Storage::AccessCache;
switch (params.type)
{
default:
case FileDialogParams::OPEN:
{
FileOpenPicker picker;
picker.ViewMode(PickerViewMode::List);
picker.SuggestedStartLocation(PickerLocationId::Objects3D);
for (auto& x : params.extensions)
{
std::wstring wstr;
StringConvert(x, wstr);
wstr = L"." + wstr;
picker.FileTypeFilter().Append(wstr);
}
auto file = co_await picker.PickSingleFileAsync();
if (file)
{
auto futureaccess = StorageApplicationPermissions::FutureAccessList();
futureaccess.Clear();
futureaccess.Add(file);
std::wstring wstr = file.Path().data();
std::string str;
StringConvert(wstr, str);
onSuccess(str);
}
}
break;
case FileDialogParams::SAVE:
{
FileSavePicker picker;
picker.SuggestedStartLocation(PickerLocationId::Objects3D);
std::wstring wdesc;
StringConvert(params.description, wdesc);
winrt::Windows::Foundation::Collections::IVector<winrt::hstring> extensions{ winrt::single_threaded_vector<winrt::hstring>() };
for (auto& x : params.extensions)
{
std::wstring wstr;
StringConvert(x, wstr);
wstr = L"." + wstr;
extensions.Append(wstr);
}
picker.FileTypeChoices().Insert(wdesc, extensions);
auto file = co_await picker.PickSaveFileAsync();
if (file)
{
auto futureaccess = StorageApplicationPermissions::FutureAccessList();
futureaccess.Clear();
futureaccess.Add(file);
std::wstring wstr = file.Path().data();
std::string str;
StringConvert(wstr, str);
onSuccess(str);
}
}
break;
}
};
filedialoghelper(params, onSuccess);
#endif // PLATFORM_UWP
#else
if (!pfd::settings::available()) {
const char *message = "No dialog backend available";
#ifdef SDL2
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
"File dialog error!",
message,
nullptr);
#endif
std::cerr << message << std::endl;
}
std::vector<std::string> extensions = {params.description, ""};
for (auto& x : params.extensions)
{
extensions[1] += "*." + x + " ";
}
switch (params.type) {
case FileDialogParams::OPEN: {
std::vector<std::string> selection = pfd::open_file(
"Open file",
std::filesystem::current_path().string(),
extensions
).result();
if (!selection.empty())
{
onSuccess(selection[0]);
}
break;
}
case FileDialogParams::SAVE: {
std::string destination = pfd::save_file(
"Save file",
std::filesystem::current_path().string(),
extensions
).result();
if (!destination.empty())
{
onSuccess(destination);
}
break;
}
}
#endif // _WIN32
}
bool Bin2H(const uint8_t* data, size_t size, const std::string& dst_filename, const char* dataName)
{
std::stringstream ss;
ss << "const uint8_t " << dataName << "[] = {";
for (size_t i = 0; i < size; ++i)
{
ss << (uint32_t)data[i] << ",";
}
ss << "};" << std::endl;
return FileWrite(dst_filename, (uint8_t*)ss.str().c_str(), ss.str().length());
}
void StringConvert(const std::string& from, std::wstring& to)
{
#ifdef _WIN32
int num = MultiByteToWideChar(CP_UTF8, 0, from.c_str(), -1, NULL, 0);
if (num > 0)
{
to.resize(size_t(num) - 1);
MultiByteToWideChar(CP_UTF8, 0, from.c_str(), -1, &to[0], num);
}
#else
to = std::wstring(from.begin(), from.end()); // TODO
#endif // _WIN32
}
void StringConvert(const std::wstring& from, std::string& to)
{
#ifdef _WIN32
int num = WideCharToMultiByte(CP_UTF8, 0, from.c_str(), -1, NULL, 0, NULL, NULL);
if (num > 0)
{
to.resize(size_t(num) - 1);
WideCharToMultiByte(CP_UTF8, 0, from.c_str(), -1, &to[0], num, NULL, NULL);
}
#else
to = std::string(from.begin(), from.end()); // TODO
#endif // _WIN32
}
int StringConvert(const char* from, wchar_t* to)
{
#ifdef _WIN32
int num = MultiByteToWideChar(CP_UTF8, 0, from, -1, NULL, 0);
if (num > 0)
{
MultiByteToWideChar(CP_UTF8, 0, from, -1, &to[0], num);
}
#else
std::string sfrom = from;
std::wstring wto = std::wstring(sfrom.begin(), sfrom.end());
std::wmemcpy(to, wto.c_str(), wto.length());
int num = wto.length();
#endif // _WIN32
return num;
}
int StringConvert(const wchar_t* from, char* to)
{
#ifdef _WIN32
int num = WideCharToMultiByte(CP_UTF8, 0, from, -1, NULL, 0, NULL, NULL);
if (num > 0)
{
WideCharToMultiByte(CP_UTF8, 0, from, -1, &to[0], num, NULL, NULL);
}
#else
std::wstring wfrom = from;
std::string sto = std::string(wfrom.begin(), wfrom.end());
std::strcpy(to, sto.c_str());
int num = sto.length();
#endif // _WIN32
return num;
}
void Sleep(float milliseconds)
{
std::this_thread::sleep_for(std::chrono::milliseconds((int)milliseconds));
}
void Spin(float milliseconds)
{
milliseconds /= 1000.0f;
std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
double ms = 0;
while (ms < milliseconds)
{
std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> time_span = std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1);
ms = time_span.count();
}
}
}