mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2025-01-17 22:25:37 +00:00
Backport more common classes
This commit is contained in:
parent
f6b3652ae6
commit
af91fcf195
|
@ -55,6 +55,8 @@ add_library(common
|
|||
string_util.h
|
||||
thirdparty/thread_pool.cpp
|
||||
thirdparty/thread_pool.h
|
||||
threading.cpp
|
||||
threading.h
|
||||
timer.cpp
|
||||
timer.h
|
||||
types.h
|
||||
|
|
|
@ -64,6 +64,7 @@
|
|||
<ClInclude Include="thirdparty\StackWalker.h">
|
||||
<ExcludedFromBuild Condition="'$(BuildingForUWP)'=='true'">true</ExcludedFromBuild>
|
||||
</ClInclude>
|
||||
<ClInclude Include="threading.h" />
|
||||
<ClInclude Include="timer.h" />
|
||||
<ClInclude Include="types.h" />
|
||||
<ClInclude Include="minizip_helpers.h" />
|
||||
|
@ -131,6 +132,7 @@
|
|||
<ClCompile Include="thirdparty\StackWalker.cpp">
|
||||
<ExcludedFromBuild Condition="'$(BuildingForUWP)'=='true'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="threading.cpp" />
|
||||
<ClCompile Include="timer.cpp" />
|
||||
<ClCompile Include="vulkan\builders.cpp" />
|
||||
<ClCompile Include="vulkan\context.cpp" />
|
||||
|
|
|
@ -137,6 +137,7 @@
|
|||
<ClInclude Include="layered_settings_interface.h" />
|
||||
<ClInclude Include="heterogeneous_containers.h" />
|
||||
<ClInclude Include="memory_settings_interface.h" />
|
||||
<ClInclude Include="threading.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="gl\program.cpp">
|
||||
|
@ -250,6 +251,7 @@
|
|||
</ClCompile>
|
||||
<ClCompile Include="layered_settings_interface.cpp" />
|
||||
<ClCompile Include="memory_settings_interface.cpp" />
|
||||
<ClCompile Include="threading.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="bitfield.natvis" />
|
||||
|
|
|
@ -37,7 +37,7 @@ void HTTPDownloader::CreateRequest(std::string url, Request::Callback callback)
|
|||
req->type = Request::Type::Get;
|
||||
req->url = std::move(url);
|
||||
req->callback = std::move(callback);
|
||||
req->start_time = Common::Timer::GetValue();
|
||||
req->start_time = Common::Timer::GetCurrentValue();
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_pending_http_request_lock);
|
||||
if (LockedGetActiveRequestCount() < m_max_active_requests)
|
||||
|
@ -57,7 +57,7 @@ void HTTPDownloader::CreatePostRequest(std::string url, std::string post_data, R
|
|||
req->url = std::move(url);
|
||||
req->post_data = std::move(post_data);
|
||||
req->callback = std::move(callback);
|
||||
req->start_time = Common::Timer::GetValue();
|
||||
req->start_time = Common::Timer::GetCurrentValue();
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_pending_http_request_lock);
|
||||
if (LockedGetActiveRequestCount() < m_max_active_requests)
|
||||
|
@ -76,7 +76,7 @@ void HTTPDownloader::LockedPollRequests(std::unique_lock<std::mutex>& lock)
|
|||
|
||||
InternalPollRequests();
|
||||
|
||||
const Common::Timer::Value current_time = Common::Timer::GetValue();
|
||||
const Common::Timer::Value current_time = Common::Timer::GetCurrentValue();
|
||||
u32 active_requests = 0;
|
||||
u32 unstarted_requests = 0;
|
||||
|
||||
|
|
|
@ -81,7 +81,7 @@ void HTTPDownloaderCurl::ProcessRequest(Request* req)
|
|||
if (pthread_sigmask(SIG_BLOCK, &new_block_mask, &old_block_mask) != 0)
|
||||
Log_WarningPrint("Failed to block SIGPIPE");
|
||||
|
||||
req->start_time = Common::Timer::GetValue();
|
||||
req->start_time = Common::Timer::GetCurrentValue();
|
||||
int ret = curl_easy_perform(req->handle);
|
||||
if (ret == CURLE_OK)
|
||||
{
|
||||
|
@ -144,7 +144,7 @@ bool HTTPDownloaderCurl::StartRequest(HTTPDownloader::Request* request)
|
|||
|
||||
Log_DevPrintf("Started HTTP request for '%s'", req->url.c_str());
|
||||
req->state = Request::State::Started;
|
||||
req->start_time = Common::Timer::GetValue();
|
||||
req->start_time = Common::Timer::GetCurrentValue();
|
||||
m_thread_pool->Schedule(std::bind(&HTTPDownloaderCurl::ProcessRequest, this, req));
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ bool HTTPDownloaderUWP::StartRequest(HTTPDownloader::Request* request)
|
|||
try
|
||||
{
|
||||
req->state.store(Request::State::Receiving);
|
||||
req->start_time = Common::Timer::GetValue();
|
||||
req->start_time = Common::Timer::GetCurrentValue();
|
||||
|
||||
const HttpResponseMessage response(req->request_async.get());
|
||||
req->status_code = static_cast<s32>(response.StatusCode());
|
||||
|
@ -146,7 +146,7 @@ bool HTTPDownloaderUWP::StartRequest(HTTPDownloader::Request* request)
|
|||
|
||||
Log_DevPrintf("Started HTTP request for '%s'", req->url.c_str());
|
||||
req->state = Request::State::Started;
|
||||
req->start_time = Common::Timer::GetValue();
|
||||
req->start_time = Common::Timer::GetCurrentValue();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -177,7 +177,7 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR
|
|||
const u32 new_size = req->io_position + dwStatusInformationLength;
|
||||
Assert(new_size <= req->data.size());
|
||||
req->data.resize(new_size);
|
||||
req->start_time = Common::Timer::GetValue();
|
||||
req->start_time = Common::Timer::GetCurrentValue();
|
||||
|
||||
if (!WinHttpQueryDataAvailable(hRequest, nullptr) && GetLastError() != ERROR_IO_PENDING)
|
||||
{
|
||||
|
@ -275,7 +275,7 @@ bool HTTPDownloaderWinHttp::StartRequest(HTTPDownloader::Request* request)
|
|||
|
||||
Log_DevPrintf("Started HTTP request for '%s'", req->url.c_str());
|
||||
req->state = Request::State::Started;
|
||||
req->start_time = Common::Timer::GetValue();
|
||||
req->start_time = Common::Timer::GetCurrentValue();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,35 +2,529 @@
|
|||
#include "byte_stream.h"
|
||||
#include "file_system.h"
|
||||
#include "log.h"
|
||||
#include "path.h"
|
||||
#include "scope_guard.h"
|
||||
#include "stb_image.h"
|
||||
#include "stb_image_write.h"
|
||||
#include "string_util.h"
|
||||
Log_SetChannel(Common::Image);
|
||||
Log_SetChannel(Image);
|
||||
|
||||
namespace Common {
|
||||
bool LoadImageFromFile(Common::RGBA8Image* image, const char* filename)
|
||||
using namespace Common;
|
||||
|
||||
#if 0
|
||||
static bool PNGBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size);
|
||||
static bool PNGBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, int quality);
|
||||
static bool PNGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp);
|
||||
static bool PNGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, int quality);
|
||||
|
||||
static bool JPEGBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size);
|
||||
static bool JPEGBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, int quality);
|
||||
static bool JPEGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp);
|
||||
static bool JPEGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, int quality);
|
||||
#endif
|
||||
|
||||
static bool STBBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size);
|
||||
static bool STBFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp);
|
||||
static bool STBBufferSaverPNG(const RGBA8Image& image, std::vector<u8>* buffer, int quality);
|
||||
static bool STBBufferSaverJPEG(const RGBA8Image& image, std::vector<u8>* buffer, int quality);
|
||||
static bool STBFileSaverPNG(const RGBA8Image& image, const char* filename, std::FILE* fp, int quality);
|
||||
static bool STBFileSaverJPEG(const RGBA8Image& image, const char* filename, std::FILE* fp, int quality);
|
||||
|
||||
struct FormatHandler
|
||||
{
|
||||
const char* extension;
|
||||
bool (*buffer_loader)(RGBA8Image*, const void*, size_t);
|
||||
bool (*buffer_saver)(const RGBA8Image&, std::vector<u8>*, int);
|
||||
bool (*file_loader)(RGBA8Image*, const char*, std::FILE*);
|
||||
bool (*file_saver)(const RGBA8Image&, const char*, std::FILE*, int);
|
||||
};
|
||||
|
||||
static constexpr FormatHandler s_format_handlers[] = {
|
||||
#if 0
|
||||
{"png", PNGBufferLoader, PNGBufferSaver, PNGFileLoader, PNGFileSaver},
|
||||
{"jpg", JPEGBufferLoader, JPEGBufferSaver, JPEGFileLoader, JPEGFileSaver},
|
||||
{"jpeg", JPEGBufferLoader, JPEGBufferSaver, JPEGFileLoader, JPEGFileSaver},
|
||||
#else
|
||||
{"png", STBBufferLoader, STBBufferSaverPNG, STBFileLoader, STBFileSaverPNG},
|
||||
{"jpg", STBBufferLoader, STBBufferSaverJPEG, STBFileLoader, STBFileSaverJPEG},
|
||||
{"jpeg", STBBufferLoader, STBBufferSaverJPEG, STBFileLoader, STBFileSaverJPEG},
|
||||
#endif
|
||||
};
|
||||
|
||||
static const FormatHandler* GetFormatHandler(const std::string_view& extension)
|
||||
{
|
||||
for (const FormatHandler& handler : s_format_handlers)
|
||||
{
|
||||
if (StringUtil::Strncasecmp(extension.data(), handler.extension, extension.size()))
|
||||
return &handler;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RGBA8Image::RGBA8Image() = default;
|
||||
|
||||
RGBA8Image::RGBA8Image(const RGBA8Image& copy) : Image(copy) {}
|
||||
|
||||
RGBA8Image::RGBA8Image(u32 width, u32 height, const u32* pixels) : Image(width, height, pixels) {}
|
||||
|
||||
RGBA8Image::RGBA8Image(RGBA8Image&& move) : Image(move) {}
|
||||
|
||||
RGBA8Image& RGBA8Image::operator=(const RGBA8Image& copy)
|
||||
{
|
||||
Image<u32>::operator=(copy);
|
||||
return *this;
|
||||
}
|
||||
|
||||
RGBA8Image& RGBA8Image::operator=(RGBA8Image&& move)
|
||||
{
|
||||
Image<u32>::operator=(move);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool RGBA8Image::LoadFromFile(const char* filename)
|
||||
{
|
||||
auto fp = FileSystem::OpenManagedCFile(filename, "rb");
|
||||
if (!fp)
|
||||
return false;
|
||||
|
||||
return LoadFromFile(filename, fp.get());
|
||||
}
|
||||
|
||||
bool RGBA8Image::SaveToFile(const char* filename, int quality) const
|
||||
{
|
||||
auto fp = FileSystem::OpenManagedCFile(filename, "wb");
|
||||
if (!fp)
|
||||
return false;
|
||||
|
||||
if (SaveToFile(filename, fp.get(), quality))
|
||||
return true;
|
||||
|
||||
// save failed
|
||||
fp.reset();
|
||||
FileSystem::DeleteFile(filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RGBA8Image::LoadFromFile(const char* filename, std::FILE* fp)
|
||||
{
|
||||
const std::string_view extension(Path::GetExtension(filename));
|
||||
const FormatHandler* handler = GetFormatHandler(extension);
|
||||
if (!handler || !handler->file_loader)
|
||||
{
|
||||
Log_ErrorPrintf("Unknown extension '%.*s'", static_cast<int>(extension.size()), extension.data());
|
||||
return false;
|
||||
}
|
||||
|
||||
int width, height, file_channels;
|
||||
u8* pixel_data = stbi_load_from_file(fp.get(), &width, &height, &file_channels, 4);
|
||||
if (!pixel_data)
|
||||
return handler->file_loader(this, filename, fp);
|
||||
}
|
||||
|
||||
bool RGBA8Image::LoadFromBuffer(const char* filename, const void* buffer, size_t buffer_size)
|
||||
{
|
||||
const std::string_view extension(Path::GetExtension(filename));
|
||||
const FormatHandler* handler = GetFormatHandler(extension);
|
||||
if (!handler || !handler->buffer_loader)
|
||||
{
|
||||
const char* error_reason = stbi_failure_reason();
|
||||
Log_ErrorPrintf("Failed to load image from '%s': %s", filename, error_reason ? error_reason : "unknown error");
|
||||
Log_ErrorPrintf("Unknown extension '%.*s'", static_cast<int>(extension.size()), extension.data());
|
||||
return false;
|
||||
}
|
||||
|
||||
image->SetPixels(static_cast<u32>(width), static_cast<u32>(height), reinterpret_cast<const u32*>(pixel_data));
|
||||
stbi_image_free(pixel_data);
|
||||
return handler->buffer_loader(this, buffer, buffer_size);
|
||||
}
|
||||
|
||||
bool RGBA8Image::SaveToFile(const char* filename, std::FILE* fp, int quality) const
|
||||
{
|
||||
const std::string_view extension(Path::GetExtension(filename));
|
||||
const FormatHandler* handler = GetFormatHandler(extension);
|
||||
if (!handler || !handler->file_saver)
|
||||
{
|
||||
Log_ErrorPrintf("Unknown extension '%.*s'", static_cast<int>(extension.size()), extension.data());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!handler->file_saver(*this, filename, fp, quality))
|
||||
return false;
|
||||
|
||||
return (std::fflush(fp) == 0);
|
||||
}
|
||||
|
||||
std::optional<std::vector<u8>> RGBA8Image::SaveToBuffer(const char* filename, int quality) const
|
||||
{
|
||||
std::optional<std::vector<u8>> ret;
|
||||
|
||||
const std::string_view extension(Path::GetExtension(filename));
|
||||
const FormatHandler* handler = GetFormatHandler(extension);
|
||||
if (!handler || !handler->file_saver)
|
||||
{
|
||||
Log_ErrorPrintf("Unknown extension '%.*s'", static_cast<int>(extension.size()), extension.data());
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = std::vector<u8>();
|
||||
if (!handler->buffer_saver(*this, &ret.value(), quality))
|
||||
ret.reset();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
static bool PNGCommonLoader(RGBA8Image* image, png_structp png_ptr, png_infop info_ptr, std::vector<u32>& new_data,
|
||||
std::vector<png_bytep>& row_pointers)
|
||||
{
|
||||
png_read_info(png_ptr, info_ptr);
|
||||
|
||||
const u32 width = png_get_image_width(png_ptr, info_ptr);
|
||||
const u32 height = png_get_image_height(png_ptr, info_ptr);
|
||||
const png_byte color_type = png_get_color_type(png_ptr, info_ptr);
|
||||
const png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr);
|
||||
|
||||
// Read any color_type into 8bit depth, RGBA format.
|
||||
// See http://www.libpng.org/pub/png/libpng-manual.txt
|
||||
|
||||
if (bit_depth == 16)
|
||||
png_set_strip_16(png_ptr);
|
||||
|
||||
if (color_type == PNG_COLOR_TYPE_PALETTE)
|
||||
png_set_palette_to_rgb(png_ptr);
|
||||
|
||||
// PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth.
|
||||
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
|
||||
png_set_expand_gray_1_2_4_to_8(png_ptr);
|
||||
|
||||
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
|
||||
png_set_tRNS_to_alpha(png_ptr);
|
||||
|
||||
// These color_type don't have an alpha channel then fill it with 0xff.
|
||||
if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)
|
||||
png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);
|
||||
|
||||
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
|
||||
png_set_gray_to_rgb(png_ptr);
|
||||
|
||||
png_read_update_info(png_ptr, info_ptr);
|
||||
|
||||
new_data.resize(width * height);
|
||||
row_pointers.reserve(height);
|
||||
for (u32 y = 0; y < height; y++)
|
||||
row_pointers.push_back(reinterpret_cast<png_bytep>(new_data.data() + y * width));
|
||||
|
||||
png_read_image(png_ptr, row_pointers.data());
|
||||
image->SetPixels(width, height, std::move(new_data));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadImageFromBuffer(Common::RGBA8Image* image, const void* buffer, std::size_t buffer_size)
|
||||
bool PNGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp)
|
||||
{
|
||||
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
if (!png_ptr)
|
||||
return false;
|
||||
|
||||
png_infop info_ptr = png_create_info_struct(png_ptr);
|
||||
if (!info_ptr)
|
||||
{
|
||||
png_destroy_read_struct(&png_ptr, nullptr, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedGuard cleanup([&png_ptr, &info_ptr]() { png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); });
|
||||
|
||||
std::vector<u32> new_data;
|
||||
std::vector<png_bytep> row_pointers;
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr)))
|
||||
return false;
|
||||
|
||||
png_init_io(png_ptr, fp);
|
||||
return PNGCommonLoader(image, png_ptr, info_ptr, new_data, row_pointers);
|
||||
}
|
||||
|
||||
bool PNGBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size)
|
||||
{
|
||||
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
if (!png_ptr)
|
||||
return false;
|
||||
|
||||
png_infop info_ptr = png_create_info_struct(png_ptr);
|
||||
if (!info_ptr)
|
||||
{
|
||||
png_destroy_read_struct(&png_ptr, nullptr, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedGuard cleanup([&png_ptr, &info_ptr]() { png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); });
|
||||
|
||||
std::vector<u32> new_data;
|
||||
std::vector<png_bytep> row_pointers;
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr)))
|
||||
return false;
|
||||
|
||||
struct IOData
|
||||
{
|
||||
const u8* buffer;
|
||||
size_t buffer_size;
|
||||
size_t buffer_pos;
|
||||
};
|
||||
IOData data = {static_cast<const u8*>(buffer), buffer_size, 0};
|
||||
|
||||
png_set_read_fn(png_ptr, &data, [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) {
|
||||
IOData* data = static_cast<IOData*>(png_get_io_ptr(png_ptr));
|
||||
const size_t read_size = std::min<size_t>(data->buffer_size - data->buffer_pos, size);
|
||||
if (read_size > 0)
|
||||
{
|
||||
std::memcpy(data_ptr, data->buffer + data->buffer_pos, read_size);
|
||||
data->buffer_pos += read_size;
|
||||
}
|
||||
});
|
||||
|
||||
return PNGCommonLoader(image, png_ptr, info_ptr, new_data, row_pointers);
|
||||
}
|
||||
|
||||
static void PNGSaveCommon(const RGBA8Image& image, png_structp png_ptr, png_infop info_ptr, int quality)
|
||||
{
|
||||
png_set_compression_level(png_ptr, std::clamp(quality / 10, 0, 9));
|
||||
png_set_IHDR(png_ptr, info_ptr, image.GetWidth(), image.GetHeight(), 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE,
|
||||
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||
png_write_info(png_ptr, info_ptr);
|
||||
|
||||
for (u32 y = 0; y < image.GetHeight(); ++y)
|
||||
png_write_row(png_ptr, (png_bytep)image.GetRowPixels(y));
|
||||
|
||||
png_write_end(png_ptr, nullptr);
|
||||
}
|
||||
|
||||
bool PNGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, int quality)
|
||||
{
|
||||
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
png_infop info_ptr = nullptr;
|
||||
if (!png_ptr)
|
||||
return false;
|
||||
|
||||
ScopedGuard cleanup([&png_ptr, &info_ptr]() {
|
||||
if (png_ptr)
|
||||
png_destroy_write_struct(&png_ptr, info_ptr ? &info_ptr : nullptr);
|
||||
});
|
||||
|
||||
info_ptr = png_create_info_struct(png_ptr);
|
||||
if (!info_ptr)
|
||||
return false;
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr)))
|
||||
return false;
|
||||
|
||||
png_set_write_fn(
|
||||
png_ptr, fp,
|
||||
[](png_structp png_ptr, png_bytep data_ptr, png_size_t size) {
|
||||
if (std::fwrite(data_ptr, size, 1, static_cast<std::FILE*>(png_get_io_ptr(png_ptr))) != 1)
|
||||
png_error(png_ptr, "file write error");
|
||||
},
|
||||
[](png_structp png_ptr) {});
|
||||
|
||||
PNGSaveCommon(image, png_ptr, info_ptr, quality);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PNGBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, int quality)
|
||||
{
|
||||
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
png_infop info_ptr = nullptr;
|
||||
if (!png_ptr)
|
||||
return false;
|
||||
|
||||
ScopedGuard cleanup([&png_ptr, &info_ptr]() {
|
||||
if (png_ptr)
|
||||
png_destroy_write_struct(&png_ptr, info_ptr ? &info_ptr : nullptr);
|
||||
});
|
||||
|
||||
info_ptr = png_create_info_struct(png_ptr);
|
||||
if (!info_ptr)
|
||||
return false;
|
||||
|
||||
buffer->reserve(image.GetWidth() * image.GetHeight() * 2);
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr)))
|
||||
return false;
|
||||
|
||||
png_set_write_fn(
|
||||
png_ptr, buffer,
|
||||
[](png_structp png_ptr, png_bytep data_ptr, png_size_t size) {
|
||||
std::vector<u8>* buffer = static_cast<std::vector<u8>*>(png_get_io_ptr(png_ptr));
|
||||
const size_t old_pos = buffer->size();
|
||||
buffer->resize(old_pos + size);
|
||||
std::memcpy(buffer->data() + old_pos, data_ptr, size);
|
||||
},
|
||||
[](png_structp png_ptr) {});
|
||||
|
||||
PNGSaveCommon(image, png_ptr, info_ptr, quality);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool JPEGBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size)
|
||||
{
|
||||
int width, height, file_comps;
|
||||
u8* data = jpgd::decompress_jpeg_image_from_memory(static_cast<const u8*>(buffer), static_cast<int>(buffer_size),
|
||||
&width, &height, &file_comps, 4, 0);
|
||||
if (!data)
|
||||
{
|
||||
Console.Error("jpgd::decompress_jpeg_image_from_memory() failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
image->SetPixels(static_cast<u32>(width), static_cast<u32>(height), reinterpret_cast<const u32*>(data));
|
||||
std::free(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool JPEGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp)
|
||||
{
|
||||
class FileStream : public jpgd::jpeg_decoder_stream
|
||||
{
|
||||
std::FILE* m_fp;
|
||||
bool m_error_flag = false;
|
||||
bool m_eof_flag = false;
|
||||
|
||||
public:
|
||||
explicit FileStream(std::FILE* fp_) : m_fp(fp_) {}
|
||||
|
||||
int read(jpgd::uint8* pBuf, int max_bytes_to_read, bool* pEOF_flag) override
|
||||
{
|
||||
if (m_eof_flag)
|
||||
{
|
||||
*pEOF_flag = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (m_error_flag)
|
||||
return -1;
|
||||
|
||||
int bytes_read = static_cast<int>(std::fread(pBuf, 1, max_bytes_to_read, m_fp));
|
||||
if (bytes_read < max_bytes_to_read)
|
||||
{
|
||||
if (std::ferror(m_fp))
|
||||
{
|
||||
m_error_flag = true;
|
||||
return -1;
|
||||
}
|
||||
|
||||
m_eof_flag = true;
|
||||
*pEOF_flag = true;
|
||||
}
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
};
|
||||
|
||||
FileStream stream(fp);
|
||||
int width, height, file_comps;
|
||||
u8* data = jpgd::decompress_jpeg_image_from_stream(&stream, &width, &height, &file_comps, 4, 0);
|
||||
if (!data)
|
||||
{
|
||||
Console.Error("jpgd::decompress_jpeg_image_from_stream() failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
image->SetPixels(static_cast<u32>(width), static_cast<u32>(height), reinterpret_cast<const u32*>(data));
|
||||
std::free(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool JPEGCommonSaver(const RGBA8Image& image, jpge::output_stream& stream, int quality)
|
||||
{
|
||||
jpge::params params;
|
||||
params.m_quality = quality;
|
||||
|
||||
jpge::jpeg_encoder dst_image;
|
||||
if (!dst_image.init(&stream, image.GetWidth(), image.GetHeight(), 3, params))
|
||||
return false;
|
||||
|
||||
// for RGBA->RGB
|
||||
std::vector<u8> row;
|
||||
row.resize(image.GetWidth() * 3);
|
||||
|
||||
for (uint pass_index = 0; pass_index < dst_image.get_total_passes(); pass_index++)
|
||||
{
|
||||
for (u32 i = 0; i < image.GetHeight(); i++)
|
||||
{
|
||||
const u8* row_in = reinterpret_cast<const u8*>(image.GetRowPixels(i));
|
||||
u8* row_out = row.data();
|
||||
for (u32 j = 0; j < image.GetWidth(); j++)
|
||||
{
|
||||
*(row_out++) = *(row_in++);
|
||||
*(row_out++) = *(row_in++);
|
||||
*(row_out++) = *(row_in++);
|
||||
row_in++;
|
||||
}
|
||||
|
||||
if (!dst_image.process_scanline(row.data()))
|
||||
return false;
|
||||
}
|
||||
if (!dst_image.process_scanline(NULL))
|
||||
return false;
|
||||
}
|
||||
|
||||
dst_image.deinit();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool JPEGBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, int quality)
|
||||
{
|
||||
class BufferStream : public jpge::output_stream
|
||||
{
|
||||
std::vector<u8>* buffer;
|
||||
|
||||
public:
|
||||
explicit BufferStream(std::vector<u8>* buffer_) : buffer(buffer_) {}
|
||||
|
||||
bool put_buf(const void* Pbuf, int len) override
|
||||
{
|
||||
const size_t old_size = buffer->size();
|
||||
buffer->resize(buffer->size() + static_cast<size_t>(len));
|
||||
std::memcpy(buffer->data() + old_size, Pbuf, static_cast<size_t>(len));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// give enough space to avoid reallocs
|
||||
buffer->reserve(image.GetWidth() * image.GetHeight() * 2);
|
||||
|
||||
BufferStream stream(buffer);
|
||||
return JPEGCommonSaver(image, stream, quality);
|
||||
}
|
||||
|
||||
bool JPEGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, int quality)
|
||||
{
|
||||
class FileStream : public jpge::output_stream
|
||||
{
|
||||
std::FILE* m_fp;
|
||||
bool m_error_flag = false;
|
||||
|
||||
public:
|
||||
explicit FileStream(std::FILE* fp_) : m_fp(fp_) {}
|
||||
|
||||
bool put_buf(const void* Pbuf, int len) override
|
||||
{
|
||||
if (m_error_flag)
|
||||
return false;
|
||||
|
||||
if (std::fwrite(Pbuf, len, 1, m_fp) != 1)
|
||||
{
|
||||
m_error_flag = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
FileStream stream(fp);
|
||||
return JPEGCommonSaver(image, stream, quality);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool STBBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size)
|
||||
{
|
||||
int width, height, file_channels;
|
||||
u8* pixel_data = stbi_load_from_memory(static_cast<const stbi_uc*>(buffer), static_cast<int>(buffer_size), &width,
|
||||
|
@ -47,24 +541,14 @@ bool LoadImageFromBuffer(Common::RGBA8Image* image, const void* buffer, std::siz
|
|||
return true;
|
||||
}
|
||||
|
||||
bool LoadImageFromStream(RGBA8Image* image, ByteStream* stream)
|
||||
bool STBFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp)
|
||||
{
|
||||
stbi_io_callbacks iocb;
|
||||
iocb.read = [](void* user, char* data, int size) {
|
||||
return static_cast<int>(static_cast<ByteStream*>(user)->Read(data, static_cast<u32>(size)));
|
||||
};
|
||||
iocb.skip = [](void* user, int n) { static_cast<ByteStream*>(user)->SeekRelative(n); };
|
||||
iocb.eof = [](void* user) {
|
||||
ByteStream* stream = static_cast<ByteStream*>(user);
|
||||
return (stream->InErrorState() || stream->GetPosition() == stream->GetSize()) ? 1 : 0;
|
||||
};
|
||||
|
||||
int width, height, file_channels;
|
||||
u8* pixel_data = stbi_load_from_callbacks(&iocb, stream, &width, &height, &file_channels, 4);
|
||||
u8* pixel_data = stbi_load_from_file(fp, &width, &height, &file_channels, 4);
|
||||
if (!pixel_data)
|
||||
{
|
||||
const char* error_reason = stbi_failure_reason();
|
||||
Log_ErrorPrintf("Failed to load image from stream: %s", error_reason ? error_reason : "unknown error");
|
||||
Log_ErrorPrintf("Failed to load image from memory: %s", error_reason ? error_reason : "unknown error");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -73,52 +557,48 @@ bool LoadImageFromStream(RGBA8Image* image, ByteStream* stream)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool WriteImageToFile(const RGBA8Image& image, const char* filename)
|
||||
bool STBBufferSaverPNG(const RGBA8Image& image, std::vector<u8>* buffer, int quality)
|
||||
{
|
||||
const char* extension = std::strrchr(filename, '.');
|
||||
if (!extension)
|
||||
{
|
||||
Log_ErrorPrintf("Unable to determine file extension for '%s'", filename);
|
||||
return false;
|
||||
}
|
||||
const auto write_func = [](void* context, void* data, int size) {
|
||||
std::vector<u8>* buffer = reinterpret_cast<std::vector<u8>*>(data);
|
||||
const u32 len = static_cast<u32>(size);
|
||||
buffer->resize(buffer->size() + len);
|
||||
std::memcpy(buffer->data(), data, len);
|
||||
};
|
||||
|
||||
auto fp = FileSystem::OpenManagedCFile(filename, "wb");
|
||||
if (!fp)
|
||||
return {};
|
||||
return (stbi_write_png_to_func(write_func, buffer, image.GetWidth(), image.GetHeight(), 4, image.GetPixels(),
|
||||
image.GetByteStride()) == 0);
|
||||
}
|
||||
|
||||
bool STBBufferSaverJPEG(const RGBA8Image& image, std::vector<u8>* buffer, int quality)
|
||||
{
|
||||
const auto write_func = [](void* context, void* data, int size) {
|
||||
std::vector<u8>* buffer = reinterpret_cast<std::vector<u8>*>(data);
|
||||
const u32 len = static_cast<u32>(size);
|
||||
buffer->resize(buffer->size() + len);
|
||||
std::memcpy(buffer->data(), data, len);
|
||||
};
|
||||
|
||||
return (stbi_write_jpg_to_func(write_func, buffer, image.GetWidth(), image.GetHeight(), 4, image.GetPixels(),
|
||||
quality) == 0);
|
||||
}
|
||||
|
||||
bool STBFileSaverPNG(const RGBA8Image& image, const char* filename, std::FILE* fp, int quality)
|
||||
{
|
||||
const auto write_func = [](void* context, void* data, int size) {
|
||||
std::fwrite(data, 1, size, static_cast<std::FILE*>(context));
|
||||
};
|
||||
|
||||
bool result = false;
|
||||
if (StringUtil::Strcasecmp(extension, ".png") == 0)
|
||||
{
|
||||
result = (stbi_write_png_to_func(write_func, fp.get(), image.GetWidth(), image.GetHeight(), 4, image.GetPixels(),
|
||||
image.GetByteStride()) != 0);
|
||||
}
|
||||
else if (StringUtil::Strcasecmp(extension, ".jpg") == 0)
|
||||
{
|
||||
result = (stbi_write_jpg_to_func(write_func, fp.get(), image.GetWidth(), image.GetHeight(), 4, image.GetPixels(),
|
||||
95) != 0);
|
||||
}
|
||||
else if (StringUtil::Strcasecmp(extension, ".tga") == 0)
|
||||
{
|
||||
result =
|
||||
(stbi_write_tga_to_func(write_func, fp.get(), image.GetWidth(), image.GetHeight(), 4, image.GetPixels()) != 0);
|
||||
}
|
||||
else if (StringUtil::Strcasecmp(extension, ".bmp") == 0)
|
||||
{
|
||||
result =
|
||||
(stbi_write_bmp_to_func(write_func, fp.get(), image.GetWidth(), image.GetHeight(), 4, image.GetPixels()) != 0);
|
||||
}
|
||||
|
||||
if (!result)
|
||||
{
|
||||
Log_ErrorPrintf("Unknown extension in filename '%s' or save error: '%s'", filename, extension);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return (stbi_write_png_to_func(write_func, fp, image.GetWidth(), image.GetHeight(), 4, image.GetPixels(),
|
||||
image.GetByteStride()) == 0);
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
bool STBFileSaverJPEG(const RGBA8Image& image, const char* filename, std::FILE* fp, int quality)
|
||||
{
|
||||
const auto write_func = [](void* context, void* data, int size) {
|
||||
std::fwrite(data, 1, size, static_cast<std::FILE*>(context));
|
||||
};
|
||||
|
||||
return (stbi_write_jpg_to_func(write_func, fp, image.GetWidth(), image.GetHeight(), 4, image.GetPixels(), quality) ==
|
||||
0);
|
||||
}
|
|
@ -2,12 +2,11 @@
|
|||
#include "assert.h"
|
||||
#include "types.h"
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
class ByteStream;
|
||||
|
||||
namespace Common {
|
||||
template<typename PixelType>
|
||||
class Image
|
||||
|
@ -86,17 +85,46 @@ public:
|
|||
std::memcpy(m_pixels.data(), pixels, width * height * sizeof(PixelType));
|
||||
}
|
||||
|
||||
private:
|
||||
void SetPixels(u32 width, u32 height, std::vector<PixelType> pixels)
|
||||
{
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
m_pixels = std::move(pixels);
|
||||
}
|
||||
|
||||
std::vector<PixelType> TakePixels()
|
||||
{
|
||||
m_width = 0;
|
||||
m_height = 0;
|
||||
return std::move(m_pixels);
|
||||
}
|
||||
|
||||
protected:
|
||||
u32 m_width = 0;
|
||||
u32 m_height = 0;
|
||||
std::vector<PixelType> m_pixels;
|
||||
};
|
||||
|
||||
using RGBA8Image = Image<u32>;
|
||||
class RGBA8Image : public Image<u32>
|
||||
{
|
||||
public:
|
||||
static constexpr int DEFAULT_SAVE_QUALITY = 85;
|
||||
|
||||
bool LoadImageFromFile(Common::RGBA8Image* image, const char* filename);
|
||||
bool LoadImageFromBuffer(Common::RGBA8Image* image, const void* buffer, std::size_t buffer_size);
|
||||
bool LoadImageFromStream(Common::RGBA8Image* image, ByteStream* stream);
|
||||
bool WriteImageToFile(const Common::RGBA8Image& image, const char* filename);
|
||||
RGBA8Image();
|
||||
RGBA8Image(u32 width, u32 height, const u32* pixels);
|
||||
RGBA8Image(const RGBA8Image& copy);
|
||||
RGBA8Image(RGBA8Image&& move);
|
||||
|
||||
} // namespace Common
|
||||
RGBA8Image& operator=(const RGBA8Image& copy);
|
||||
RGBA8Image& operator=(RGBA8Image&& move);
|
||||
|
||||
bool LoadFromFile(const char* filename);
|
||||
bool LoadFromFile(const char* filename, std::FILE* fp);
|
||||
bool LoadFromBuffer(const char* filename, const void* buffer, size_t buffer_size);
|
||||
|
||||
bool SaveToFile(const char* filename, int quality = DEFAULT_SAVE_QUALITY) const;
|
||||
bool SaveToFile(const char* filename, std::FILE* fp, int quality = DEFAULT_SAVE_QUALITY) const;
|
||||
std::optional<std::vector<u8>> SaveToBuffer(const char* filename, int quality = DEFAULT_SAVE_QUALITY) const;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
|
|
@ -30,7 +30,7 @@ static std::mutex s_callback_mutex;
|
|||
|
||||
static LOGLEVEL s_filter_level = LOGLEVEL_TRACE;
|
||||
|
||||
static Common::Timer::Value s_startTimeStamp = Common::Timer::GetValue();
|
||||
static Common::Timer::Value s_startTimeStamp = Common::Timer::GetCurrentValue();
|
||||
|
||||
static bool s_console_output_enabled = false;
|
||||
static String s_console_output_channel_filter;
|
||||
|
@ -123,7 +123,7 @@ static int FormatLogMessageForDisplay(char* buffer, size_t buffer_size, const ch
|
|||
{
|
||||
// find time since start of process
|
||||
const float message_time =
|
||||
static_cast<float>(Common::Timer::ConvertValueToSeconds(Common::Timer::GetValue() - s_startTimeStamp));
|
||||
static_cast<float>(Common::Timer::ConvertValueToSeconds(Common::Timer::GetCurrentValue() - s_startTimeStamp));
|
||||
|
||||
if (level <= LOGLEVEL_PERF)
|
||||
{
|
||||
|
|
|
@ -16,7 +16,10 @@ class LRUCache
|
|||
using MapType = std::map<K, Item>;
|
||||
|
||||
public:
|
||||
LRUCache(std::size_t max_capacity = 16) : m_max_capacity(max_capacity) {}
|
||||
LRUCache(std::size_t max_capacity = 16, bool manual_evict = false)
|
||||
: m_max_capacity(max_capacity), m_manual_evict(manual_evict)
|
||||
{
|
||||
}
|
||||
~LRUCache() = default;
|
||||
|
||||
std::size_t GetSize() const { return m_items.size(); }
|
||||
|
@ -31,7 +34,8 @@ public:
|
|||
Evict(m_items.size() - m_max_capacity);
|
||||
}
|
||||
|
||||
V* Lookup(const K& key)
|
||||
template<typename KeyT>
|
||||
V* Lookup(const KeyT& key)
|
||||
{
|
||||
auto iter = m_items.find(key);
|
||||
if (iter == m_items.end())
|
||||
|
@ -41,7 +45,7 @@ public:
|
|||
return &iter->second.value;
|
||||
}
|
||||
|
||||
V* Insert(const K& key, V value)
|
||||
V* Insert(K key, V value)
|
||||
{
|
||||
ShrinkForNewItem();
|
||||
|
||||
|
@ -57,7 +61,7 @@ public:
|
|||
Item it;
|
||||
it.last_access = ++m_last_counter;
|
||||
it.value = std::move(value);
|
||||
auto ip = m_items.emplace(key, std::move(it));
|
||||
auto ip = m_items.emplace(std::move(key), std::move(it));
|
||||
return &ip.first->second.value;
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +80,8 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
bool Remove(const K& key)
|
||||
template<typename KeyT>
|
||||
bool Remove(const KeyT& key)
|
||||
{
|
||||
auto iter = m_items.find(key);
|
||||
if (iter == m_items.end())
|
||||
|
@ -86,6 +91,20 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
void SetManualEvict(bool block)
|
||||
{
|
||||
m_manual_evict = block;
|
||||
if (!m_manual_evict)
|
||||
ManualEvict();
|
||||
}
|
||||
|
||||
void ManualEvict()
|
||||
{
|
||||
// evict if we went over
|
||||
while (m_items.size() > m_max_capacity)
|
||||
Evict(m_items.size() - (m_max_capacity - 1));
|
||||
}
|
||||
|
||||
private:
|
||||
void ShrinkForNewItem()
|
||||
{
|
||||
|
@ -98,4 +117,5 @@ private:
|
|||
MapType m_items;
|
||||
CounterType m_last_counter = 0;
|
||||
std::size_t m_max_capacity = 0;
|
||||
bool m_manual_evict = false;
|
||||
};
|
541
src/common/threading.cpp
Normal file
541
src/common/threading.cpp
Normal file
|
@ -0,0 +1,541 @@
|
|||
#include "threading.h"
|
||||
#include "assert.h"
|
||||
#include <memory>
|
||||
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include "windows_headers.h"
|
||||
#include <process.h>
|
||||
#else
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#if defined(__linux__)
|
||||
#include <sched.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
// glibc < v2.30 doesn't define gettid...
|
||||
#if __GLIBC__ == 2 && __GLIBC_MINOR__ < 30
|
||||
#include <sys/syscall.h>
|
||||
#define gettid() syscall(SYS_gettid)
|
||||
#endif
|
||||
#else
|
||||
#include <pthread_np.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
// This hacky union would probably fail on some cpu platforms if the contents of FILETIME aren't
|
||||
// packed (but for any x86 CPU and microsoft compiler, they will be).
|
||||
union FileTimeSucks
|
||||
{
|
||||
FILETIME filetime;
|
||||
u64 u64time;
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
// gets the CPU time used by the current thread (both system and user), in
|
||||
// microseconds, returns 0 on failure
|
||||
static u64 getthreadtime(thread_port_t thread)
|
||||
{
|
||||
mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
|
||||
thread_basic_info_data_t info;
|
||||
|
||||
kern_return_t kr = thread_info(thread, THREAD_BASIC_INFO, (thread_info_t)&info, &count);
|
||||
if (kr != KERN_SUCCESS)
|
||||
return 0;
|
||||
|
||||
// add system and user time
|
||||
return (u64)info.user_time.seconds * (u64)1e6 + (u64)info.user_time.microseconds +
|
||||
(u64)info.system_time.seconds * (u64)1e6 + (u64)info.system_time.microseconds;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
// Helper function to get either either the current cpu usage
|
||||
// in called thread or in id thread
|
||||
static u64 get_thread_time(void* id = 0)
|
||||
{
|
||||
clockid_t cid;
|
||||
if (id)
|
||||
{
|
||||
int err = pthread_getcpuclockid((pthread_t)id, &cid);
|
||||
if (err)
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
cid = CLOCK_THREAD_CPUTIME_ID;
|
||||
}
|
||||
|
||||
struct timespec ts;
|
||||
int err = clock_gettime(cid, &ts);
|
||||
if (err)
|
||||
return 0;
|
||||
|
||||
return (u64)ts.tv_sec * (u64)1e6 + (u64)ts.tv_nsec / (u64)1e3;
|
||||
}
|
||||
#endif
|
||||
|
||||
void Threading::Timeslice()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
::Sleep(0);
|
||||
#elif defined(__APPLE__)
|
||||
sched_yield();
|
||||
#else
|
||||
sched_yield();
|
||||
#endif
|
||||
}
|
||||
|
||||
Threading::ThreadHandle::ThreadHandle() = default;
|
||||
|
||||
#ifdef _WIN32
|
||||
Threading::ThreadHandle::ThreadHandle(const ThreadHandle& handle)
|
||||
{
|
||||
if (handle.m_native_handle)
|
||||
{
|
||||
HANDLE new_handle;
|
||||
if (DuplicateHandle(GetCurrentProcess(), (HANDLE)handle.m_native_handle, GetCurrentProcess(), &new_handle,
|
||||
THREAD_QUERY_INFORMATION | THREAD_SET_LIMITED_INFORMATION, FALSE, 0))
|
||||
{
|
||||
m_native_handle = (void*)new_handle;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
Threading::ThreadHandle::ThreadHandle(const ThreadHandle& handle)
|
||||
: m_native_handle(handle.m_native_handle)
|
||||
#ifdef __linux__
|
||||
,
|
||||
m_native_id(handle.m_native_id)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
Threading::ThreadHandle::ThreadHandle(ThreadHandle&& handle) : m_native_handle(handle.m_native_handle)
|
||||
{
|
||||
handle.m_native_handle = nullptr;
|
||||
}
|
||||
#else
|
||||
Threading::ThreadHandle::ThreadHandle(ThreadHandle&& handle)
|
||||
: m_native_handle(handle.m_native_handle)
|
||||
#ifdef __linux__
|
||||
,
|
||||
m_native_id(handle.m_native_id)
|
||||
#endif
|
||||
{
|
||||
handle.m_native_handle = nullptr;
|
||||
#ifdef __linux__
|
||||
handle.m_native_id = 0;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
Threading::ThreadHandle::~ThreadHandle()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (m_native_handle)
|
||||
CloseHandle(m_native_handle);
|
||||
#endif
|
||||
}
|
||||
|
||||
Threading::ThreadHandle Threading::ThreadHandle::GetForCallingThread()
|
||||
{
|
||||
ThreadHandle ret;
|
||||
#ifdef _WIN32
|
||||
ret.m_native_handle =
|
||||
(void*)OpenThread(THREAD_QUERY_INFORMATION | THREAD_SET_LIMITED_INFORMATION, FALSE, GetCurrentThreadId());
|
||||
#else
|
||||
ret.m_native_handle = (void*)pthread_self();
|
||||
#ifdef __linux__
|
||||
ret.m_native_id = gettid();
|
||||
#endif
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
Threading::ThreadHandle& Threading::ThreadHandle::operator=(ThreadHandle&& handle)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (m_native_handle)
|
||||
CloseHandle((HANDLE)m_native_handle);
|
||||
m_native_handle = handle.m_native_handle;
|
||||
handle.m_native_handle = nullptr;
|
||||
#else
|
||||
m_native_handle = handle.m_native_handle;
|
||||
handle.m_native_handle = nullptr;
|
||||
#ifdef __linux__
|
||||
m_native_id = handle.m_native_id;
|
||||
handle.m_native_id = 0;
|
||||
#endif
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
Threading::ThreadHandle& Threading::ThreadHandle::operator=(const ThreadHandle& handle)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (m_native_handle)
|
||||
{
|
||||
CloseHandle((HANDLE)m_native_handle);
|
||||
m_native_handle = nullptr;
|
||||
}
|
||||
|
||||
HANDLE new_handle;
|
||||
if (DuplicateHandle(GetCurrentProcess(), (HANDLE)handle.m_native_handle, GetCurrentProcess(), &new_handle,
|
||||
THREAD_QUERY_INFORMATION | THREAD_SET_LIMITED_INFORMATION, FALSE, 0))
|
||||
{
|
||||
m_native_handle = (void*)new_handle;
|
||||
}
|
||||
#else
|
||||
m_native_handle = handle.m_native_handle;
|
||||
#ifdef __linux__
|
||||
m_native_id = handle.m_native_id;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
u64 Threading::ThreadHandle::GetCPUTime() const
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
#if 0
|
||||
u64 ret = 0;
|
||||
if (m_native_handle)
|
||||
QueryThreadCycleTime((HANDLE)m_native_handle, &ret);
|
||||
return ret;
|
||||
#else
|
||||
FileTimeSucks user = {}, kernel = {};
|
||||
FILETIME dummy;
|
||||
GetThreadTimes((HANDLE)m_native_handle, &dummy, &dummy, &kernel.filetime, &user.filetime);
|
||||
return user.u64time + kernel.u64time;
|
||||
#endif
|
||||
#elif defined(__APPLE__)
|
||||
return getthreadtime(pthread_mach_thread_np((pthread_t)m_native_handle));
|
||||
#elif defined(__linux__)
|
||||
return get_thread_time(m_native_handle);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Threading::ThreadHandle::SetAffinity(u64 processor_mask) const
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
if (processor_mask == 0)
|
||||
processor_mask = ~processor_mask;
|
||||
|
||||
return (SetThreadAffinityMask(GetCurrentThread(), (DWORD_PTR)processor_mask) != 0 || GetLastError() != ERROR_SUCCESS);
|
||||
#elif defined(__linux__)
|
||||
cpu_set_t set;
|
||||
CPU_ZERO(&set);
|
||||
|
||||
if (processor_mask != 0)
|
||||
{
|
||||
for (u32 i = 0; i < 64; i++)
|
||||
{
|
||||
if (processor_mask & (static_cast<u64>(1) << i))
|
||||
{
|
||||
CPU_SET(i, &set);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
long num_processors = sysconf(_SC_NPROCESSORS_CONF);
|
||||
for (long i = 0; i < num_processors; i++)
|
||||
{
|
||||
CPU_SET(i, &set);
|
||||
}
|
||||
}
|
||||
|
||||
return sched_setaffinity((pid_t)m_native_id, sizeof(set), &set) >= 0;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
Threading::Thread::Thread() = default;
|
||||
|
||||
Threading::Thread::Thread(Thread&& thread) : ThreadHandle(thread), m_stack_size(thread.m_stack_size)
|
||||
{
|
||||
thread.m_stack_size = 0;
|
||||
}
|
||||
|
||||
Threading::Thread::Thread(EntryPoint func) : ThreadHandle()
|
||||
{
|
||||
if (!Start(std::move(func)))
|
||||
Panic("Failed to start implicitly started thread.");
|
||||
}
|
||||
|
||||
Threading::Thread::~Thread()
|
||||
{
|
||||
AssertMsg(!m_native_handle, "Thread should be detached or joined at destruction");
|
||||
}
|
||||
|
||||
void Threading::Thread::SetStackSize(u32 size)
|
||||
{
|
||||
AssertMsg(!m_native_handle, "Can't change the stack size on a started thread");
|
||||
m_stack_size = size;
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
unsigned Threading::Thread::ThreadProc(void* param)
|
||||
{
|
||||
std::unique_ptr<EntryPoint> entry(static_cast<EntryPoint*>(param));
|
||||
(*entry.get())();
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Threading::Thread::Start(EntryPoint func)
|
||||
{
|
||||
AssertMsg(!m_native_handle, "Can't start an already-started thread");
|
||||
|
||||
std::unique_ptr<EntryPoint> func_clone(std::make_unique<EntryPoint>(std::move(func)));
|
||||
unsigned thread_id;
|
||||
m_native_handle =
|
||||
reinterpret_cast<void*>(_beginthreadex(nullptr, m_stack_size, ThreadProc, func_clone.get(), 0, &thread_id));
|
||||
if (!m_native_handle)
|
||||
return false;
|
||||
|
||||
// thread started, it'll release the memory
|
||||
func_clone.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
#elif defined(__linux__)
|
||||
|
||||
// For Linux, we have to do a bit of trickery here to get the thread's ID back from
|
||||
// the thread itself, because it's not part of pthreads. We use a semaphore to signal
|
||||
// when the thread has started, and filled in thread_id_ptr.
|
||||
struct ThreadProcParameters
|
||||
{
|
||||
Threading::Thread::EntryPoint func;
|
||||
Threading::KernelSemaphore* start_semaphore;
|
||||
unsigned int* thread_id_ptr;
|
||||
};
|
||||
|
||||
void* Threading::Thread::ThreadProc(void* param)
|
||||
{
|
||||
std::unique_ptr<ThreadProcParameters> entry(static_cast<ThreadProcParameters*>(param));
|
||||
*entry->thread_id_ptr = gettid();
|
||||
entry->start_semaphore->Post();
|
||||
entry->func();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Threading::Thread::Start(EntryPoint func)
|
||||
{
|
||||
AssertMsg(!m_native_handle, "Can't start an already-started thread");
|
||||
|
||||
KernelSemaphore start_semaphore;
|
||||
std::unique_ptr<ThreadProcParameters> params(std::make_unique<ThreadProcParameters>());
|
||||
params->func = std::move(func);
|
||||
params->start_semaphore = &start_semaphore;
|
||||
params->thread_id_ptr = &m_native_id;
|
||||
|
||||
pthread_attr_t attrs;
|
||||
bool has_attributes = false;
|
||||
|
||||
if (m_stack_size != 0)
|
||||
{
|
||||
has_attributes = true;
|
||||
pthread_attr_init(&attrs);
|
||||
}
|
||||
if (m_stack_size != 0)
|
||||
pthread_attr_setstacksize(&attrs, m_stack_size);
|
||||
|
||||
pthread_t handle;
|
||||
const int res = pthread_create(&handle, has_attributes ? &attrs : nullptr, ThreadProc, params.get());
|
||||
if (res != 0)
|
||||
return false;
|
||||
|
||||
// wait until it sets our native id
|
||||
start_semaphore.Wait();
|
||||
|
||||
// thread started, it'll release the memory
|
||||
m_native_handle = (void*)handle;
|
||||
params.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void* Threading::Thread::ThreadProc(void* param)
|
||||
{
|
||||
std::unique_ptr<EntryPoint> entry(static_cast<EntryPoint*>(param));
|
||||
(*entry.get())();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Threading::Thread::Start(EntryPoint func)
|
||||
{
|
||||
pxAssertRel(!m_native_handle, "Can't start an already-started thread");
|
||||
|
||||
std::unique_ptr<EntryPoint> func_clone(std::make_unique<EntryPoint>(std::move(func)));
|
||||
|
||||
pthread_attr_t attrs;
|
||||
bool has_attributes = false;
|
||||
|
||||
if (m_stack_size != 0)
|
||||
{
|
||||
has_attributes = true;
|
||||
pthread_attr_init(&attrs);
|
||||
}
|
||||
if (m_stack_size != 0)
|
||||
pthread_attr_setstacksize(&attrs, m_stack_size);
|
||||
|
||||
pthread_t handle;
|
||||
const int res = pthread_create(&handle, has_attributes ? &attrs : nullptr, ThreadProc, func_clone.get());
|
||||
if (res != 0)
|
||||
return false;
|
||||
|
||||
// thread started, it'll release the memory
|
||||
m_native_handle = (void*)handle;
|
||||
func_clone.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void Threading::Thread::Detach()
|
||||
{
|
||||
AssertMsg(m_native_handle, "Can't detach without a thread");
|
||||
#ifdef _WIN32
|
||||
CloseHandle((HANDLE)m_native_handle);
|
||||
m_native_handle = nullptr;
|
||||
#else
|
||||
pthread_detach((pthread_t)m_native_handle);
|
||||
m_native_handle = nullptr;
|
||||
#ifdef __linux__
|
||||
m_native_id = 0;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void Threading::Thread::Join()
|
||||
{
|
||||
AssertMsg(m_native_handle, "Can't join without a thread");
|
||||
#ifdef _WIN32
|
||||
const DWORD res = WaitForSingleObject((HANDLE)m_native_handle, INFINITE);
|
||||
if (res != WAIT_OBJECT_0)
|
||||
Panic("WaitForSingleObject() for thread join failed");
|
||||
|
||||
CloseHandle((HANDLE)m_native_handle);
|
||||
m_native_handle = nullptr;
|
||||
#else
|
||||
void* retval;
|
||||
const int res = pthread_join((pthread_t)m_native_handle, &retval);
|
||||
if (res != 0)
|
||||
Panic("pthread_join() for thread join failed");
|
||||
|
||||
m_native_handle = nullptr;
|
||||
#ifdef __linux__
|
||||
m_native_id = 0;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
Threading::ThreadHandle& Threading::Thread::operator=(Thread&& thread)
|
||||
{
|
||||
ThreadHandle::operator=(thread);
|
||||
m_stack_size = thread.m_stack_size;
|
||||
thread.m_stack_size = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
u64 Threading::GetThreadCpuTime()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
#if 0
|
||||
u64 ret = 0;
|
||||
QueryThreadCycleTime(GetCurrentThread(), &ret);
|
||||
return ret;
|
||||
#else
|
||||
FileTimeSucks user = {}, kernel = {};
|
||||
FILETIME dummy;
|
||||
GetThreadTimes(GetCurrentThread(), &dummy, &dummy, &kernel.filetime, &user.filetime);
|
||||
return user.u64time + kernel.u64time;
|
||||
#endif
|
||||
#elif defined(__APPLE__)
|
||||
return getthreadtime(pthread_mach_thread_np(pthread_self()));
|
||||
#else
|
||||
return get_thread_time(nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
u64 Threading::GetThreadTicksPerSecond()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
#if 0
|
||||
// On x86, despite what the MS documentation says, this basically appears to be rdtsc.
|
||||
// So, the frequency is our base clock speed (and stable regardless of power management).
|
||||
static u64 frequency = 0;
|
||||
if (unlikely(frequency == 0))
|
||||
frequency = x86caps.CachedMHz() * u64(1000000);
|
||||
return frequency;
|
||||
#else
|
||||
return 10000000;
|
||||
#endif
|
||||
#elif defined(__APPLE__)
|
||||
return 1000000;
|
||||
|
||||
#else
|
||||
return 1000000;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Threading::SetNameOfCurrentThread(const char* name)
|
||||
{
|
||||
// This feature needs Windows headers and MSVC's SEH support:
|
||||
|
||||
#if defined(_WIN32) && defined(_MSC_VER)
|
||||
|
||||
// This code sample was borrowed form some obscure MSDN article.
|
||||
// In a rare bout of sanity, it's an actual Microsoft-published hack
|
||||
// that actually works!
|
||||
|
||||
static const int MS_VC_EXCEPTION = 0x406D1388;
|
||||
|
||||
#pragma pack(push, 8)
|
||||
struct THREADNAME_INFO
|
||||
{
|
||||
DWORD dwType; // Must be 0x1000.
|
||||
LPCSTR szName; // Pointer to name (in user addr space).
|
||||
DWORD dwThreadID; // Thread ID (-1=caller thread).
|
||||
DWORD dwFlags; // Reserved for future use, must be zero.
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
THREADNAME_INFO info;
|
||||
info.dwType = 0x1000;
|
||||
info.szName = name;
|
||||
info.dwThreadID = GetCurrentThreadId();
|
||||
info.dwFlags = 0;
|
||||
|
||||
__try
|
||||
{
|
||||
RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info);
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
// Extract of manpage: "The name can be up to 16 bytes long, and should be
|
||||
// null-terminated if it contains fewer bytes."
|
||||
prctl(PR_SET_NAME, name, 0, 0, 0);
|
||||
#else
|
||||
pthread_set_name_np(pthread_self(), name);
|
||||
#endif
|
||||
}
|
121
src/common/threading.h
Normal file
121
src/common/threading.h
Normal file
|
@ -0,0 +1,121 @@
|
|||
#pragma once
|
||||
#include "types.h"
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <mach/semaphore.h>
|
||||
#elif !defined(_WIN32)
|
||||
#include <semaphore.h>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
|
||||
namespace Threading {
|
||||
extern u64 GetThreadCpuTime();
|
||||
extern u64 GetThreadTicksPerSecond();
|
||||
|
||||
/// Set the name of the current thread
|
||||
extern void SetNameOfCurrentThread(const char* name);
|
||||
|
||||
// Releases a timeslice to other threads.
|
||||
extern void Timeslice();
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// ThreadHandle
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Abstracts an OS's handle to a thread, closing the handle when necessary. Currently,
|
||||
// only used for getting the CPU time for a thread.
|
||||
//
|
||||
class ThreadHandle
|
||||
{
|
||||
public:
|
||||
ThreadHandle();
|
||||
ThreadHandle(ThreadHandle&& handle);
|
||||
ThreadHandle(const ThreadHandle& handle);
|
||||
~ThreadHandle();
|
||||
|
||||
/// Returns a new handle for the calling thread.
|
||||
static ThreadHandle GetForCallingThread();
|
||||
|
||||
ThreadHandle& operator=(ThreadHandle&& handle);
|
||||
ThreadHandle& operator=(const ThreadHandle& handle);
|
||||
|
||||
operator void*() const { return m_native_handle; }
|
||||
operator bool() const { return (m_native_handle != nullptr); }
|
||||
|
||||
/// Returns the amount of CPU time consumed by the thread, at the GetThreadTicksPerSecond() frequency.
|
||||
u64 GetCPUTime() const;
|
||||
|
||||
/// Sets the affinity for a thread to the specified processors.
|
||||
/// Obviously, only works up to 64 processors.
|
||||
bool SetAffinity(u64 processor_mask) const;
|
||||
|
||||
protected:
|
||||
void* m_native_handle = nullptr;
|
||||
|
||||
// We need the thread ID for affinity adjustments on Linux.
|
||||
#if defined(__linux__)
|
||||
unsigned int m_native_id = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Thread
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Abstracts a native thread in a lightweight manner. Provides more functionality than
|
||||
// std::thread (allowing stack size adjustments).
|
||||
//
|
||||
class Thread : public ThreadHandle
|
||||
{
|
||||
public:
|
||||
using EntryPoint = std::function<void()>;
|
||||
|
||||
Thread();
|
||||
Thread(Thread&& thread);
|
||||
Thread(const Thread&) = delete;
|
||||
Thread(EntryPoint func);
|
||||
~Thread();
|
||||
|
||||
ThreadHandle& operator=(Thread&& thread);
|
||||
ThreadHandle& operator=(const Thread& handle) = delete;
|
||||
|
||||
ALWAYS_INLINE bool Joinable() const { return (m_native_handle != nullptr); }
|
||||
ALWAYS_INLINE u32 GetStackSize() const { return m_stack_size; }
|
||||
|
||||
/// Sets the stack size for the thread. Do not call if the thread has already been started.
|
||||
void SetStackSize(u32 size);
|
||||
|
||||
bool Start(EntryPoint func);
|
||||
void Detach();
|
||||
void Join();
|
||||
|
||||
protected:
|
||||
#ifdef _WIN32
|
||||
static unsigned __stdcall ThreadProc(void* param);
|
||||
#else
|
||||
static void* ThreadProc(void* param);
|
||||
#endif
|
||||
|
||||
u32 m_stack_size = 0;
|
||||
};
|
||||
|
||||
/// A semaphore that may not have a fast userspace path
|
||||
/// (Used in other semaphore-based algorithms where the semaphore is just used for its thread sleep/wake ability)
|
||||
class KernelSemaphore
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
void* m_sema;
|
||||
#elif defined(__APPLE__)
|
||||
semaphore_t m_sema;
|
||||
#else
|
||||
sem_t m_sema;
|
||||
#endif
|
||||
public:
|
||||
KernelSemaphore();
|
||||
~KernelSemaphore();
|
||||
void Post();
|
||||
void Wait();
|
||||
bool TryWait();
|
||||
};
|
||||
|
||||
} // namespace Threading
|
|
@ -34,7 +34,7 @@ static HANDLE GetSleepTimer()
|
|||
return s_sleep_timer;
|
||||
}
|
||||
|
||||
Timer::Value Timer::GetValue()
|
||||
double Timer::GetFrequency()
|
||||
{
|
||||
// even if this races, it should still result in the same value..
|
||||
if (!s_counter_initialized)
|
||||
|
@ -45,6 +45,11 @@ Timer::Value Timer::GetValue()
|
|||
s_counter_initialized = true;
|
||||
}
|
||||
|
||||
return s_counter_frequency;
|
||||
}
|
||||
|
||||
Timer::Value Timer::GetCurrentValue()
|
||||
{
|
||||
Timer::Value ReturnValue;
|
||||
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&ReturnValue));
|
||||
return ReturnValue;
|
||||
|
@ -52,44 +57,44 @@ Timer::Value Timer::GetValue()
|
|||
|
||||
double Timer::ConvertValueToNanoseconds(Timer::Value value)
|
||||
{
|
||||
return (static_cast<double>(value) / s_counter_frequency);
|
||||
return (static_cast<double>(value) / GetFrequency());
|
||||
}
|
||||
|
||||
double Timer::ConvertValueToMilliseconds(Timer::Value value)
|
||||
{
|
||||
return ((static_cast<double>(value) / s_counter_frequency) / 1000000.0);
|
||||
return ((static_cast<double>(value) / GetFrequency()) / 1000000.0);
|
||||
}
|
||||
|
||||
double Timer::ConvertValueToSeconds(Timer::Value value)
|
||||
{
|
||||
return ((static_cast<double>(value) / s_counter_frequency) / 1000000000.0);
|
||||
return ((static_cast<double>(value) / GetFrequency()) / 1000000000.0);
|
||||
}
|
||||
|
||||
Timer::Value Timer::ConvertSecondsToValue(double s)
|
||||
{
|
||||
return static_cast<Value>((s * 1000000000.0) * s_counter_frequency);
|
||||
return static_cast<Value>((s * 1000000000.0) * GetFrequency());
|
||||
}
|
||||
|
||||
Timer::Value Timer::ConvertMillisecondsToValue(double ms)
|
||||
{
|
||||
return static_cast<Value>((ms * 1000000.0) * s_counter_frequency);
|
||||
return static_cast<Value>((ms * 1000000.0) * GetFrequency());
|
||||
}
|
||||
|
||||
Timer::Value Timer::ConvertNanosecondsToValue(double ns)
|
||||
{
|
||||
return static_cast<Value>(ns * s_counter_frequency);
|
||||
return static_cast<Value>(ns * GetFrequency());
|
||||
}
|
||||
|
||||
void Timer::SleepUntil(Value value, bool exact)
|
||||
{
|
||||
if (exact)
|
||||
{
|
||||
while (GetValue() < value)
|
||||
while (GetCurrentValue() < value)
|
||||
SleepUntil(value, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::int64_t diff = static_cast<std::int64_t>(value - GetValue());
|
||||
const std::int64_t diff = static_cast<std::int64_t>(value - GetCurrentValue());
|
||||
if (diff <= 0)
|
||||
return;
|
||||
|
||||
|
@ -120,7 +125,12 @@ void Timer::SleepUntil(Value value, bool exact)
|
|||
|
||||
#else
|
||||
|
||||
Timer::Value Timer::GetValue()
|
||||
double Timer::GetFrequency()
|
||||
{
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
Timer::Value Timer::GetCurrentValue()
|
||||
{
|
||||
struct timespec tv;
|
||||
clock_gettime(CLOCK_MONOTONIC, &tv);
|
||||
|
@ -161,14 +171,14 @@ void Timer::SleepUntil(Value value, bool exact)
|
|||
{
|
||||
if (exact)
|
||||
{
|
||||
while (GetValue() < value)
|
||||
while (GetCurrentValue() < value)
|
||||
SleepUntil(value, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Apple doesn't have TIMER_ABSTIME, so fall back to nanosleep in such a case.
|
||||
#ifdef __APPLE__
|
||||
const Value current_time = GetValue();
|
||||
const Value current_time = GetCurrentValue();
|
||||
if (value <= current_time)
|
||||
return;
|
||||
|
||||
|
@ -195,58 +205,82 @@ Timer::Timer()
|
|||
|
||||
void Timer::Reset()
|
||||
{
|
||||
m_tvStartValue = GetValue();
|
||||
m_tvStartValue = GetCurrentValue();
|
||||
}
|
||||
|
||||
double Timer::GetTimeSeconds() const
|
||||
{
|
||||
return ConvertValueToSeconds(GetValue() - m_tvStartValue);
|
||||
return ConvertValueToSeconds(GetCurrentValue() - m_tvStartValue);
|
||||
}
|
||||
|
||||
double Timer::GetTimeMilliseconds() const
|
||||
{
|
||||
return ConvertValueToMilliseconds(GetValue() - m_tvStartValue);
|
||||
return ConvertValueToMilliseconds(GetCurrentValue() - m_tvStartValue);
|
||||
}
|
||||
|
||||
double Timer::GetTimeNanoseconds() const
|
||||
{
|
||||
return ConvertValueToNanoseconds(GetValue() - m_tvStartValue);
|
||||
return ConvertValueToNanoseconds(GetCurrentValue() - m_tvStartValue);
|
||||
}
|
||||
|
||||
double Timer::GetTimeSecondsAndReset()
|
||||
{
|
||||
const Value value = GetCurrentValue();
|
||||
const double ret = ConvertValueToSeconds(value - m_tvStartValue);
|
||||
m_tvStartValue = value;
|
||||
return ret;
|
||||
}
|
||||
|
||||
double Timer::GetTimeMillisecondsAndReset()
|
||||
{
|
||||
const Value value = GetCurrentValue();
|
||||
const double ret = ConvertValueToMilliseconds(value - m_tvStartValue);
|
||||
m_tvStartValue = value;
|
||||
return ret;
|
||||
}
|
||||
|
||||
double Timer::GetTimeNanosecondsAndReset()
|
||||
{
|
||||
const Value value = GetCurrentValue();
|
||||
const double ret = ConvertValueToNanoseconds(value - m_tvStartValue);
|
||||
m_tvStartValue = value;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Timer::BusyWait(std::uint64_t ns)
|
||||
{
|
||||
const Value start = GetValue();
|
||||
const Value start = GetCurrentValue();
|
||||
const Value end = start + ConvertNanosecondsToValue(static_cast<double>(ns));
|
||||
if (end < start)
|
||||
{
|
||||
// overflow, unlikely
|
||||
while (GetValue() > end)
|
||||
while (GetCurrentValue() > end)
|
||||
;
|
||||
}
|
||||
|
||||
while (GetValue() < end)
|
||||
while (GetCurrentValue() < end)
|
||||
;
|
||||
}
|
||||
|
||||
void Timer::HybridSleep(std::uint64_t ns, std::uint64_t min_sleep_time)
|
||||
{
|
||||
const std::uint64_t start = GetValue();
|
||||
const std::uint64_t start = GetCurrentValue();
|
||||
const std::uint64_t end = start + ConvertNanosecondsToValue(static_cast<double>(ns));
|
||||
if (end < start)
|
||||
{
|
||||
// overflow, unlikely
|
||||
while (GetValue() > end)
|
||||
while (GetCurrentValue() > end)
|
||||
;
|
||||
}
|
||||
|
||||
std::uint64_t current = GetValue();
|
||||
std::uint64_t current = GetCurrentValue();
|
||||
while (current < end)
|
||||
{
|
||||
const std::uint64_t remaining = end - current;
|
||||
if (remaining >= min_sleep_time)
|
||||
NanoSleep(min_sleep_time);
|
||||
|
||||
current = GetValue();
|
||||
current = GetCurrentValue();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,9 @@ public:
|
|||
|
||||
Timer();
|
||||
|
||||
static Value GetValue();
|
||||
static double GetFrequency();
|
||||
static Value GetCurrentValue();
|
||||
|
||||
static double ConvertValueToSeconds(Value value);
|
||||
static double ConvertValueToMilliseconds(Value value);
|
||||
static double ConvertValueToNanoseconds(Value value);
|
||||
|
@ -23,11 +25,18 @@ public:
|
|||
static void SleepUntil(Value value, bool exact);
|
||||
|
||||
void Reset();
|
||||
void ResetTo(Value value) { m_tvStartValue = value; }
|
||||
|
||||
Value GetStartValue() const { return m_tvStartValue; }
|
||||
|
||||
double GetTimeSeconds() const;
|
||||
double GetTimeMilliseconds() const;
|
||||
double GetTimeNanoseconds() const;
|
||||
|
||||
double GetTimeSecondsAndReset();
|
||||
double GetTimeMillisecondsAndReset();
|
||||
double GetTimeNanosecondsAndReset();
|
||||
|
||||
private:
|
||||
Value m_tvStartValue;
|
||||
};
|
||||
|
|
|
@ -215,7 +215,7 @@ void GPUBackend::RunGPULoop()
|
|||
u32 read_ptr = m_command_fifo_read_ptr.load();
|
||||
if (read_ptr == write_ptr)
|
||||
{
|
||||
const Common::Timer::Value current_time = Common::Timer::GetValue();
|
||||
const Common::Timer::Value current_time = Common::Timer::GetCurrentValue();
|
||||
if (Common::Timer::ConvertValueToNanoseconds(current_time - last_command_time) < SPIN_TIME_NS)
|
||||
continue;
|
||||
|
||||
|
@ -263,7 +263,7 @@ void GPUBackend::RunGPULoop()
|
|||
}
|
||||
}
|
||||
|
||||
last_command_time = allow_sleep ? 0 : Common::Timer::GetValue();
|
||||
last_command_time = allow_sleep ? 0 : Common::Timer::GetCurrentValue();
|
||||
m_command_fifo_read_ptr.store(read_ptr);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1475,7 +1475,7 @@ void GPU_HW::DrawRendererStats(bool is_idle_frame)
|
|||
|
||||
GPU_HW::ShaderCompileProgressTracker::ShaderCompileProgressTracker(std::string title, u32 total)
|
||||
: m_title(std::move(title)), m_min_time(Common::Timer::ConvertSecondsToValue(1.0)),
|
||||
m_update_interval(Common::Timer::ConvertSecondsToValue(0.1)), m_start_time(Common::Timer::GetValue()),
|
||||
m_update_interval(Common::Timer::ConvertSecondsToValue(0.1)), m_start_time(Common::Timer::GetCurrentValue()),
|
||||
m_last_update_time(0), m_progress(0), m_total(total)
|
||||
{
|
||||
}
|
||||
|
@ -1484,7 +1484,7 @@ void GPU_HW::ShaderCompileProgressTracker::Increment()
|
|||
{
|
||||
m_progress++;
|
||||
|
||||
const u64 tv = Common::Timer::GetValue();
|
||||
const u64 tv = Common::Timer::GetCurrentValue();
|
||||
if ((tv - m_start_time) >= m_min_time && (tv - m_last_update_time) >= m_update_interval)
|
||||
{
|
||||
g_host_interface->DisplayLoadingScreen(m_title.c_str(), 0, static_cast<int>(m_total), static_cast<int>(m_progress));
|
||||
|
|
|
@ -36,7 +36,7 @@ bool HostDisplay::ShouldSkipDisplayingFrame()
|
|||
if (m_display_frame_interval == 0.0f)
|
||||
return false;
|
||||
|
||||
const u64 now = Common::Timer::GetValue();
|
||||
const u64 now = Common::Timer::GetCurrentValue();
|
||||
const double diff = Common::Timer::ConvertValueToSeconds(now - m_last_frame_displayed_time);
|
||||
if (diff < m_display_frame_interval)
|
||||
return true;
|
||||
|
|
|
@ -1678,7 +1678,7 @@ void AddNotification(float duration, std::string title, std::string text, std::s
|
|||
notif.title = std::move(title);
|
||||
notif.text = std::move(text);
|
||||
notif.badge_path = std::move(image_path);
|
||||
notif.start_time = Common::Timer::GetValue();
|
||||
notif.start_time = Common::Timer::GetCurrentValue();
|
||||
s_notifications.push_back(std::move(notif));
|
||||
}
|
||||
|
||||
|
@ -1694,7 +1694,7 @@ void DrawNotifications(ImVec2& position, float spacing)
|
|||
|
||||
static constexpr float EASE_IN_TIME = 0.6f;
|
||||
static constexpr float EASE_OUT_TIME = 0.6f;
|
||||
const Common::Timer::Value current_time = Common::Timer::GetValue();
|
||||
const Common::Timer::Value current_time = Common::Timer::GetCurrentValue();
|
||||
|
||||
const float horizontal_padding = ImGuiFullscreen::LayoutScale(20.0f);
|
||||
const float vertical_padding = ImGuiFullscreen::LayoutScale(10.0f);
|
||||
|
|
|
@ -283,7 +283,7 @@ void NamcoGunCon::LoadSettings(const char* section)
|
|||
{
|
||||
m_crosshair_image_path = std::move(path);
|
||||
if (m_crosshair_image_path.empty() ||
|
||||
!Common::LoadImageFromFile(&m_crosshair_image, m_crosshair_image_path.c_str()))
|
||||
!m_crosshair_image.LoadFromFile(m_crosshair_image_path.c_str()))
|
||||
{
|
||||
m_crosshair_image.Invalidate();
|
||||
}
|
||||
|
|
|
@ -1585,7 +1585,7 @@ void UpdateThrottlePeriod()
|
|||
|
||||
void ResetThrottler()
|
||||
{
|
||||
s_next_frame_time = Common::Timer::GetValue();
|
||||
s_next_frame_time = Common::Timer::GetCurrentValue();
|
||||
}
|
||||
|
||||
void Throttle()
|
||||
|
@ -1606,7 +1606,7 @@ void Throttle()
|
|||
#endif
|
||||
|
||||
// Use unsigned for defined overflow/wrap-around.
|
||||
const Common::Timer::Value time = Common::Timer::GetValue();
|
||||
const Common::Timer::Value time = Common::Timer::GetCurrentValue();
|
||||
const double sleep_time = (s_next_frame_time >= time) ?
|
||||
Common::Timer::ConvertValueToNanoseconds(s_next_frame_time - time) :
|
||||
-Common::Timer::ConvertValueToNanoseconds(time - s_next_frame_time);
|
||||
|
@ -1630,7 +1630,7 @@ void RunFrames()
|
|||
const u32 max_frames_to_run = 2;
|
||||
u32 frames_run = 0;
|
||||
|
||||
Common::Timer::Value value = Common::Timer::GetValue();
|
||||
Common::Timer::Value value = Common::Timer::GetCurrentValue();
|
||||
while (frames_run < max_frames_to_run)
|
||||
{
|
||||
if (value < s_next_frame_time)
|
||||
|
@ -1639,7 +1639,7 @@ void RunFrames()
|
|||
RunFrame();
|
||||
frames_run++;
|
||||
|
||||
value = Common::Timer::GetValue();
|
||||
value = Common::Timer::GetCurrentValue();
|
||||
}
|
||||
|
||||
if (frames_run != 1)
|
||||
|
|
|
@ -105,7 +105,7 @@ void TextureReplacements::DumpVRAMWrite(u32 width, u32 height, const void* pixel
|
|||
}
|
||||
|
||||
Log_InfoPrintf("Dumping %ux%u VRAM write to '%s'", width, height, filename.c_str());
|
||||
if (!Common::WriteImageToFile(image, filename.c_str()))
|
||||
if (!image.SaveToFile(filename.c_str()))
|
||||
Log_ErrorPrintf("Failed to dump %ux%u VRAM write to '%s'", width, height, filename.c_str());
|
||||
}
|
||||
|
||||
|
@ -266,7 +266,7 @@ const TextureReplacementTexture* TextureReplacements::LoadTexture(const std::str
|
|||
return &it->second;
|
||||
|
||||
Common::RGBA8Image image;
|
||||
if (!Common::LoadImageFromFile(&image, filename.c_str()))
|
||||
if (!image.LoadFromFile(filename.c_str()))
|
||||
{
|
||||
Log_ErrorPrintf("Failed to load '%s'", filename.c_str());
|
||||
return nullptr;
|
||||
|
|
|
@ -1652,7 +1652,7 @@ void CommonHostInterface::AddControllerRumble(u32 controller_index, u32 num_moto
|
|||
rumble.num_motors = std::min<u32>(num_motors, ControllerRumbleState::MAX_MOTORS);
|
||||
rumble.last_strength.fill(0.0f);
|
||||
rumble.update_callback = std::move(callback);
|
||||
rumble.last_update_time = Common::Timer::GetValue();
|
||||
rumble.last_update_time = Common::Timer::GetCurrentValue();
|
||||
m_controller_vibration_motors.push_back(std::move(rumble));
|
||||
}
|
||||
|
||||
|
@ -1666,7 +1666,7 @@ void CommonHostInterface::UpdateControllerRumble()
|
|||
// This is because the rumble update is synchronous, and with bluetooth latency can severely impact fast forward
|
||||
// performance.
|
||||
static constexpr float UPDATE_FREQUENCY = 1000.0f;
|
||||
const u64 time = Common::Timer::GetValue();
|
||||
const u64 time = Common::Timer::GetCurrentValue();
|
||||
|
||||
for (ControllerRumbleState& rumble : m_controller_vibration_motors)
|
||||
{
|
||||
|
|
|
@ -481,19 +481,27 @@ void DestroyResources()
|
|||
|
||||
static std::unique_ptr<HostDisplayTexture> LoadTexture(const char* path, bool from_package)
|
||||
{
|
||||
std::unique_ptr<ByteStream> stream;
|
||||
std::vector<u8> data;
|
||||
if (from_package)
|
||||
stream = g_host_interface->OpenPackageFile(path, BYTESTREAM_OPEN_READ);
|
||||
{
|
||||
std::unique_ptr<ByteStream> stream = g_host_interface->OpenPackageFile(path, BYTESTREAM_OPEN_READ);
|
||||
if (stream)
|
||||
data = ByteStream::ReadBinaryStream(stream.get(), false);
|
||||
}
|
||||
else
|
||||
stream = ByteStream::OpenFile(path, BYTESTREAM_OPEN_READ);
|
||||
if (!stream)
|
||||
{
|
||||
std::optional<std::vector<u8>> odata(FileSystem::ReadBinaryFile(path));
|
||||
if (!odata.has_value())
|
||||
data = std::move(odata.value());
|
||||
}
|
||||
if (data.empty())
|
||||
{
|
||||
Log_ErrorPrintf("Failed to open texture resource '%s'", path);
|
||||
return {};
|
||||
}
|
||||
|
||||
Common::RGBA8Image image;
|
||||
if (!Common::LoadImageFromStream(&image, stream.get()) && image.IsValid())
|
||||
if (!image.LoadFromBuffer(path, data.data(), data.size()) && image.IsValid())
|
||||
{
|
||||
Log_ErrorPrintf("Failed to read texture resource '%s'", path);
|
||||
return {};
|
||||
|
|
Loading…
Reference in a new issue