From af91fcf1957c2e433c7d435deb49f29a94b34595 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Mon, 11 Jul 2022 19:45:31 +1000 Subject: [PATCH] Backport more common classes --- src/common/CMakeLists.txt | 2 + src/common/common.vcxproj | 2 + src/common/common.vcxproj.filters | 2 + src/common/http_downloader.cpp | 6 +- src/common/http_downloader_curl.cpp | 4 +- src/common/http_downloader_uwp.cpp | 4 +- src/common/http_downloader_winhttp.cpp | 4 +- src/common/image.cpp | 608 ++++++++++++++++-- src/common/image.h | 46 +- src/common/log.cpp | 4 +- src/common/lru_cache.h | 30 +- src/common/threading.cpp | 541 ++++++++++++++++ src/common/threading.h | 121 ++++ src/common/timer.cpp | 80 ++- src/common/timer.h | 11 +- src/core/gpu_backend.cpp | 4 +- src/core/gpu_hw.cpp | 4 +- src/core/host_display.cpp | 2 +- src/core/imgui_fullscreen.cpp | 4 +- src/core/namco_guncon.cpp | 2 +- src/core/system.cpp | 8 +- src/core/texture_replacements.cpp | 4 +- src/frontend-common/common_host_interface.cpp | 4 +- src/frontend-common/fullscreen_ui.cpp | 18 +- 24 files changed, 1381 insertions(+), 134 deletions(-) create mode 100644 src/common/threading.cpp create mode 100644 src/common/threading.h diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index eceb4e251..3785bade1 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -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 diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj index e9e5935ff..5a905689e 100644 --- a/src/common/common.vcxproj +++ b/src/common/common.vcxproj @@ -64,6 +64,7 @@ true + @@ -131,6 +132,7 @@ true + diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters index 22d0cf5a2..cec6b11db 100644 --- a/src/common/common.vcxproj.filters +++ b/src/common/common.vcxproj.filters @@ -137,6 +137,7 @@ + @@ -250,6 +251,7 @@ + diff --git a/src/common/http_downloader.cpp b/src/common/http_downloader.cpp index 8fe209f68..b44dd5db2 100644 --- a/src/common/http_downloader.cpp +++ b/src/common/http_downloader.cpp @@ -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 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 lock(m_pending_http_request_lock); if (LockedGetActiveRequestCount() < m_max_active_requests) @@ -76,7 +76,7 @@ void HTTPDownloader::LockedPollRequests(std::unique_lock& 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; diff --git a/src/common/http_downloader_curl.cpp b/src/common/http_downloader_curl.cpp index abcfbd774..3809a4750 100644 --- a/src/common/http_downloader_curl.cpp +++ b/src/common/http_downloader_curl.cpp @@ -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; } diff --git a/src/common/http_downloader_uwp.cpp b/src/common/http_downloader_uwp.cpp index 0f1ff98a7..3a3573008 100644 --- a/src/common/http_downloader_uwp.cpp +++ b/src/common/http_downloader_uwp.cpp @@ -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(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; } diff --git a/src/common/http_downloader_winhttp.cpp b/src/common/http_downloader_winhttp.cpp index b99dede86..1aa95fb15 100644 --- a/src/common/http_downloader_winhttp.cpp +++ b/src/common/http_downloader_winhttp.cpp @@ -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; } diff --git a/src/common/image.cpp b/src/common/image.cpp index bd5259b49..6e743f302 100644 --- a/src/common/image.cpp +++ b/src/common/image.cpp @@ -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* 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* 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* buffer, int quality); +static bool STBBufferSaverJPEG(const RGBA8Image& image, std::vector* 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*, 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::operator=(copy); + return *this; +} + +RGBA8Image& RGBA8Image::operator=(RGBA8Image&& move) +{ + Image::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(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(extension.size()), extension.data()); return false; } - image->SetPixels(static_cast(width), static_cast(height), reinterpret_cast(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(extension.size()), extension.data()); + return false; + } + + if (!handler->file_saver(*this, filename, fp, quality)) + return false; + + return (std::fflush(fp) == 0); +} + +std::optional> RGBA8Image::SaveToBuffer(const char* filename, int quality) const +{ + std::optional> 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(extension.size()), extension.data()); + return ret; + } + + ret = std::vector(); + 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& new_data, + std::vector& 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(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 new_data; + std::vector 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 new_data; + std::vector 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(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(png_get_io_ptr(png_ptr)); + const size_t read_size = std::min(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(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* 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* buffer = static_cast*>(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(buffer), static_cast(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(width), static_cast(height), reinterpret_cast(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(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(width), static_cast(height), reinterpret_cast(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 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(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* buffer, int quality) +{ + class BufferStream : public jpge::output_stream + { + std::vector* buffer; + + public: + explicit BufferStream(std::vector* 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(len)); + std::memcpy(buffer->data() + old_size, Pbuf, static_cast(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(buffer), static_cast(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(static_cast(user)->Read(data, static_cast(size))); - }; - iocb.skip = [](void* user, int n) { static_cast(user)->SeekRelative(n); }; - iocb.eof = [](void* user) { - ByteStream* stream = static_cast(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* 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* buffer = reinterpret_cast*>(data); + const u32 len = static_cast(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* buffer, int quality) +{ + const auto write_func = [](void* context, void* data, int size) { + std::vector* buffer = reinterpret_cast*>(data); + const u32 len = static_cast(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(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 \ No newline at end of file +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(context)); + }; + + return (stbi_write_jpg_to_func(write_func, fp, image.GetWidth(), image.GetHeight(), 4, image.GetPixels(), quality) == + 0); +} \ No newline at end of file diff --git a/src/common/image.h b/src/common/image.h index 922df6b6b..c8876a21e 100644 --- a/src/common/image.h +++ b/src/common/image.h @@ -2,12 +2,11 @@ #include "assert.h" #include "types.h" #include +#include #include #include #include -class ByteStream; - namespace Common { template 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 pixels) + { + m_width = width; + m_height = height; + m_pixels = std::move(pixels); + } + + std::vector TakePixels() + { + m_width = 0; + m_height = 0; + return std::move(m_pixels); + } + +protected: u32 m_width = 0; u32 m_height = 0; std::vector m_pixels; }; -using RGBA8Image = Image; +class RGBA8Image : public Image +{ +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 \ No newline at end of file + 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> SaveToBuffer(const char* filename, int quality = DEFAULT_SAVE_QUALITY) const; +}; + +} // namespace Common diff --git a/src/common/log.cpp b/src/common/log.cpp index cfbfea1d5..a28118257 100644 --- a/src/common/log.cpp +++ b/src/common/log.cpp @@ -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(Common::Timer::ConvertValueToSeconds(Common::Timer::GetValue() - s_startTimeStamp)); + static_cast(Common::Timer::ConvertValueToSeconds(Common::Timer::GetCurrentValue() - s_startTimeStamp)); if (level <= LOGLEVEL_PERF) { diff --git a/src/common/lru_cache.h b/src/common/lru_cache.h index e6f8bcc4b..6c76e66cb 100644 --- a/src/common/lru_cache.h +++ b/src/common/lru_cache.h @@ -16,7 +16,10 @@ class LRUCache using MapType = std::map; 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 + 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 + 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; }; \ No newline at end of file diff --git a/src/common/threading.cpp b/src/common/threading.cpp new file mode 100644 index 000000000..07428fffc --- /dev/null +++ b/src/common/threading.cpp @@ -0,0 +1,541 @@ +#include "threading.h" +#include "assert.h" +#include + +#if !defined(_WIN32) && !defined(__APPLE__) +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#endif + +#if defined(_WIN32) +#include "windows_headers.h" +#include +#else +#include +#include +#if defined(__linux__) +#include +#include +#include + +// glibc < v2.30 doesn't define gettid... +#if __GLIBC__ == 2 && __GLIBC_MINOR__ < 30 +#include +#define gettid() syscall(SYS_gettid) +#endif +#else +#include +#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(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 entry(static_cast(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 func_clone(std::make_unique(std::move(func))); + unsigned thread_id; + m_native_handle = + reinterpret_cast(_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 entry(static_cast(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 params(std::make_unique()); + 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 entry(static_cast(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 func_clone(std::make_unique(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 +} diff --git a/src/common/threading.h b/src/common/threading.h new file mode 100644 index 000000000..62094fe0d --- /dev/null +++ b/src/common/threading.h @@ -0,0 +1,121 @@ +#pragma once +#include "types.h" + +#if defined(__APPLE__) +#include +#elif !defined(_WIN32) +#include +#endif + +#include +#include + +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; + + 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 \ No newline at end of file diff --git a/src/common/timer.cpp b/src/common/timer.cpp index 79b49684d..679824f45 100644 --- a/src/common/timer.cpp +++ b/src/common/timer.cpp @@ -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(&ReturnValue)); return ReturnValue; @@ -52,44 +57,44 @@ Timer::Value Timer::GetValue() double Timer::ConvertValueToNanoseconds(Timer::Value value) { - return (static_cast(value) / s_counter_frequency); + return (static_cast(value) / GetFrequency()); } double Timer::ConvertValueToMilliseconds(Timer::Value value) { - return ((static_cast(value) / s_counter_frequency) / 1000000.0); + return ((static_cast(value) / GetFrequency()) / 1000000.0); } double Timer::ConvertValueToSeconds(Timer::Value value) { - return ((static_cast(value) / s_counter_frequency) / 1000000000.0); + return ((static_cast(value) / GetFrequency()) / 1000000000.0); } Timer::Value Timer::ConvertSecondsToValue(double s) { - return static_cast((s * 1000000000.0) * s_counter_frequency); + return static_cast((s * 1000000000.0) * GetFrequency()); } Timer::Value Timer::ConvertMillisecondsToValue(double ms) { - return static_cast((ms * 1000000.0) * s_counter_frequency); + return static_cast((ms * 1000000.0) * GetFrequency()); } Timer::Value Timer::ConvertNanosecondsToValue(double ns) { - return static_cast(ns * s_counter_frequency); + return static_cast(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(value - GetValue()); + const std::int64_t diff = static_cast(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(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(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(); } } diff --git a/src/common/timer.h b/src/common/timer.h index c105f3d50..45e31df1b 100644 --- a/src/common/timer.h +++ b/src/common/timer.h @@ -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; }; diff --git a/src/core/gpu_backend.cpp b/src/core/gpu_backend.cpp index e9e441ac6..64198c192 100644 --- a/src/core/gpu_backend.cpp +++ b/src/core/gpu_backend.cpp @@ -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); } } diff --git a/src/core/gpu_hw.cpp b/src/core/gpu_hw.cpp index 08be657c9..7601f425f 100644 --- a/src/core/gpu_hw.cpp +++ b/src/core/gpu_hw.cpp @@ -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(m_total), static_cast(m_progress)); diff --git a/src/core/host_display.cpp b/src/core/host_display.cpp index d0469fd3b..456c41cb8 100644 --- a/src/core/host_display.cpp +++ b/src/core/host_display.cpp @@ -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; diff --git a/src/core/imgui_fullscreen.cpp b/src/core/imgui_fullscreen.cpp index 1c3919740..46ce3049e 100644 --- a/src/core/imgui_fullscreen.cpp +++ b/src/core/imgui_fullscreen.cpp @@ -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); diff --git a/src/core/namco_guncon.cpp b/src/core/namco_guncon.cpp index 9c88a79d7..dbdb77886 100644 --- a/src/core/namco_guncon.cpp +++ b/src/core/namco_guncon.cpp @@ -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(); } diff --git a/src/core/system.cpp b/src/core/system.cpp index 38f193eff..69cbd9016 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -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) diff --git a/src/core/texture_replacements.cpp b/src/core/texture_replacements.cpp index 66a7d3f19..533c5978b 100644 --- a/src/core/texture_replacements.cpp +++ b/src/core/texture_replacements.cpp @@ -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; diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp index f6c2e8603..f19594bd9 100644 --- a/src/frontend-common/common_host_interface.cpp +++ b/src/frontend-common/common_host_interface.cpp @@ -1652,7 +1652,7 @@ void CommonHostInterface::AddControllerRumble(u32 controller_index, u32 num_moto rumble.num_motors = std::min(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) { diff --git a/src/frontend-common/fullscreen_ui.cpp b/src/frontend-common/fullscreen_ui.cpp index 8284adee3..2879c9481 100644 --- a/src/frontend-common/fullscreen_ui.cpp +++ b/src/frontend-common/fullscreen_ui.cpp @@ -481,19 +481,27 @@ void DestroyResources() static std::unique_ptr LoadTexture(const char* path, bool from_package) { - std::unique_ptr stream; + std::vector data; if (from_package) - stream = g_host_interface->OpenPackageFile(path, BYTESTREAM_OPEN_READ); + { + std::unique_ptr 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> 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 {};