Backport more common classes

This commit is contained in:
Connor McLaughlin 2022-07-11 19:45:31 +10:00
parent f6b3652ae6
commit af91fcf195
24 changed files with 1381 additions and 134 deletions

View file

@ -55,6 +55,8 @@ add_library(common
string_util.h string_util.h
thirdparty/thread_pool.cpp thirdparty/thread_pool.cpp
thirdparty/thread_pool.h thirdparty/thread_pool.h
threading.cpp
threading.h
timer.cpp timer.cpp
timer.h timer.h
types.h types.h

View file

@ -64,6 +64,7 @@
<ClInclude Include="thirdparty\StackWalker.h"> <ClInclude Include="thirdparty\StackWalker.h">
<ExcludedFromBuild Condition="'$(BuildingForUWP)'=='true'">true</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(BuildingForUWP)'=='true'">true</ExcludedFromBuild>
</ClInclude> </ClInclude>
<ClInclude Include="threading.h" />
<ClInclude Include="timer.h" /> <ClInclude Include="timer.h" />
<ClInclude Include="types.h" /> <ClInclude Include="types.h" />
<ClInclude Include="minizip_helpers.h" /> <ClInclude Include="minizip_helpers.h" />
@ -131,6 +132,7 @@
<ClCompile Include="thirdparty\StackWalker.cpp"> <ClCompile Include="thirdparty\StackWalker.cpp">
<ExcludedFromBuild Condition="'$(BuildingForUWP)'=='true'">true</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(BuildingForUWP)'=='true'">true</ExcludedFromBuild>
</ClCompile> </ClCompile>
<ClCompile Include="threading.cpp" />
<ClCompile Include="timer.cpp" /> <ClCompile Include="timer.cpp" />
<ClCompile Include="vulkan\builders.cpp" /> <ClCompile Include="vulkan\builders.cpp" />
<ClCompile Include="vulkan\context.cpp" /> <ClCompile Include="vulkan\context.cpp" />

View file

@ -137,6 +137,7 @@
<ClInclude Include="layered_settings_interface.h" /> <ClInclude Include="layered_settings_interface.h" />
<ClInclude Include="heterogeneous_containers.h" /> <ClInclude Include="heterogeneous_containers.h" />
<ClInclude Include="memory_settings_interface.h" /> <ClInclude Include="memory_settings_interface.h" />
<ClInclude Include="threading.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="gl\program.cpp"> <ClCompile Include="gl\program.cpp">
@ -250,6 +251,7 @@
</ClCompile> </ClCompile>
<ClCompile Include="layered_settings_interface.cpp" /> <ClCompile Include="layered_settings_interface.cpp" />
<ClCompile Include="memory_settings_interface.cpp" /> <ClCompile Include="memory_settings_interface.cpp" />
<ClCompile Include="threading.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Natvis Include="bitfield.natvis" /> <Natvis Include="bitfield.natvis" />

View file

@ -37,7 +37,7 @@ void HTTPDownloader::CreateRequest(std::string url, Request::Callback callback)
req->type = Request::Type::Get; req->type = Request::Type::Get;
req->url = std::move(url); req->url = std::move(url);
req->callback = std::move(callback); 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); std::unique_lock<std::mutex> lock(m_pending_http_request_lock);
if (LockedGetActiveRequestCount() < m_max_active_requests) 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->url = std::move(url);
req->post_data = std::move(post_data); req->post_data = std::move(post_data);
req->callback = std::move(callback); 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); std::unique_lock<std::mutex> lock(m_pending_http_request_lock);
if (LockedGetActiveRequestCount() < m_max_active_requests) if (LockedGetActiveRequestCount() < m_max_active_requests)
@ -76,7 +76,7 @@ void HTTPDownloader::LockedPollRequests(std::unique_lock<std::mutex>& lock)
InternalPollRequests(); InternalPollRequests();
const Common::Timer::Value current_time = Common::Timer::GetValue(); const Common::Timer::Value current_time = Common::Timer::GetCurrentValue();
u32 active_requests = 0; u32 active_requests = 0;
u32 unstarted_requests = 0; u32 unstarted_requests = 0;

View file

@ -81,7 +81,7 @@ void HTTPDownloaderCurl::ProcessRequest(Request* req)
if (pthread_sigmask(SIG_BLOCK, &new_block_mask, &old_block_mask) != 0) if (pthread_sigmask(SIG_BLOCK, &new_block_mask, &old_block_mask) != 0)
Log_WarningPrint("Failed to block SIGPIPE"); 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); int ret = curl_easy_perform(req->handle);
if (ret == CURLE_OK) 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()); Log_DevPrintf("Started HTTP request for '%s'", req->url.c_str());
req->state = Request::State::Started; 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)); m_thread_pool->Schedule(std::bind(&HTTPDownloaderCurl::ProcessRequest, this, req));
return true; return true;
} }

View file

@ -74,7 +74,7 @@ bool HTTPDownloaderUWP::StartRequest(HTTPDownloader::Request* request)
try try
{ {
req->state.store(Request::State::Receiving); 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()); const HttpResponseMessage response(req->request_async.get());
req->status_code = static_cast<s32>(response.StatusCode()); 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()); Log_DevPrintf("Started HTTP request for '%s'", req->url.c_str());
req->state = Request::State::Started; req->state = Request::State::Started;
req->start_time = Common::Timer::GetValue(); req->start_time = Common::Timer::GetCurrentValue();
return true; return true;
} }

View file

@ -177,7 +177,7 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR
const u32 new_size = req->io_position + dwStatusInformationLength; const u32 new_size = req->io_position + dwStatusInformationLength;
Assert(new_size <= req->data.size()); Assert(new_size <= req->data.size());
req->data.resize(new_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) 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()); Log_DevPrintf("Started HTTP request for '%s'", req->url.c_str());
req->state = Request::State::Started; req->state = Request::State::Started;
req->start_time = Common::Timer::GetValue(); req->start_time = Common::Timer::GetCurrentValue();
return true; return true;
} }

View file

@ -2,35 +2,529 @@
#include "byte_stream.h" #include "byte_stream.h"
#include "file_system.h" #include "file_system.h"
#include "log.h" #include "log.h"
#include "path.h"
#include "scope_guard.h"
#include "stb_image.h" #include "stb_image.h"
#include "stb_image_write.h" #include "stb_image_write.h"
#include "string_util.h" #include "string_util.h"
Log_SetChannel(Common::Image); Log_SetChannel(Image);
namespace Common { using namespace Common;
bool LoadImageFromFile(Common::RGBA8Image* image, const char* filename)
#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"); auto fp = FileSystem::OpenManagedCFile(filename, "rb");
if (!fp) 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; return false;
} }
int width, height, file_channels; return handler->file_loader(this, filename, fp);
u8* pixel_data = stbi_load_from_file(fp.get(), &width, &height, &file_channels, 4); }
if (!pixel_data)
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("Unknown extension '%.*s'", static_cast<int>(extension.size()), extension.data());
Log_ErrorPrintf("Failed to load image from '%s': %s", filename, error_reason ? error_reason : "unknown error");
return false; return false;
} }
image->SetPixels(static_cast<u32>(width), static_cast<u32>(height), reinterpret_cast<const u32*>(pixel_data)); return handler->buffer_loader(this, buffer, buffer_size);
stbi_image_free(pixel_data); }
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; 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; int width, height, file_channels;
u8* pixel_data = stbi_load_from_memory(static_cast<const stbi_uc*>(buffer), static_cast<int>(buffer_size), &width, 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; 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; 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) if (!pixel_data)
{ {
const char* error_reason = stbi_failure_reason(); 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; return false;
} }
@ -73,52 +557,48 @@ bool LoadImageFromStream(RGBA8Image* image, ByteStream* stream)
return true; 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, '.'); const auto write_func = [](void* context, void* data, int size) {
if (!extension) std::vector<u8>* buffer = reinterpret_cast<std::vector<u8>*>(data);
{ const u32 len = static_cast<u32>(size);
Log_ErrorPrintf("Unable to determine file extension for '%s'", filename); buffer->resize(buffer->size() + len);
return false; std::memcpy(buffer->data(), data, len);
} };
auto fp = FileSystem::OpenManagedCFile(filename, "wb"); return (stbi_write_png_to_func(write_func, buffer, image.GetWidth(), image.GetHeight(), 4, image.GetPixels(),
if (!fp) image.GetByteStride()) == 0);
return {}; }
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) { const auto write_func = [](void* context, void* data, int size) {
std::fwrite(data, 1, size, static_cast<std::FILE*>(context)); std::fwrite(data, 1, size, static_cast<std::FILE*>(context));
}; };
bool result = false; return (stbi_write_png_to_func(write_func, fp, image.GetWidth(), image.GetHeight(), 4, image.GetPixels(),
if (StringUtil::Strcasecmp(extension, ".png") == 0) image.GetByteStride()) == 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;
} }
} // 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);
}

View file

@ -2,12 +2,11 @@
#include "assert.h" #include "assert.h"
#include "types.h" #include "types.h"
#include <algorithm> #include <algorithm>
#include <cstdio>
#include <cstring> #include <cstring>
#include <optional> #include <optional>
#include <vector> #include <vector>
class ByteStream;
namespace Common { namespace Common {
template<typename PixelType> template<typename PixelType>
class Image class Image
@ -86,17 +85,46 @@ public:
std::memcpy(m_pixels.data(), pixels, width * height * sizeof(PixelType)); 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_width = 0;
u32 m_height = 0; u32 m_height = 0;
std::vector<PixelType> m_pixels; 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); RGBA8Image();
bool LoadImageFromBuffer(Common::RGBA8Image* image, const void* buffer, std::size_t buffer_size); RGBA8Image(u32 width, u32 height, const u32* pixels);
bool LoadImageFromStream(Common::RGBA8Image* image, ByteStream* stream); RGBA8Image(const RGBA8Image& copy);
bool WriteImageToFile(const Common::RGBA8Image& image, const char* filename); 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

View file

@ -30,7 +30,7 @@ static std::mutex s_callback_mutex;
static LOGLEVEL s_filter_level = LOGLEVEL_TRACE; 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 bool s_console_output_enabled = false;
static String s_console_output_channel_filter; 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 // find time since start of process
const float message_time = 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) if (level <= LOGLEVEL_PERF)
{ {

View file

@ -16,7 +16,10 @@ class LRUCache
using MapType = std::map<K, Item>; using MapType = std::map<K, Item>;
public: 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; ~LRUCache() = default;
std::size_t GetSize() const { return m_items.size(); } std::size_t GetSize() const { return m_items.size(); }
@ -31,7 +34,8 @@ public:
Evict(m_items.size() - m_max_capacity); 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); auto iter = m_items.find(key);
if (iter == m_items.end()) if (iter == m_items.end())
@ -41,7 +45,7 @@ public:
return &iter->second.value; return &iter->second.value;
} }
V* Insert(const K& key, V value) V* Insert(K key, V value)
{ {
ShrinkForNewItem(); ShrinkForNewItem();
@ -57,7 +61,7 @@ public:
Item it; Item it;
it.last_access = ++m_last_counter; it.last_access = ++m_last_counter;
it.value = std::move(value); 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; 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); auto iter = m_items.find(key);
if (iter == m_items.end()) if (iter == m_items.end())
@ -86,6 +91,20 @@ public:
return true; 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: private:
void ShrinkForNewItem() void ShrinkForNewItem()
{ {
@ -98,4 +117,5 @@ private:
MapType m_items; MapType m_items;
CounterType m_last_counter = 0; CounterType m_last_counter = 0;
std::size_t m_max_capacity = 0; std::size_t m_max_capacity = 0;
bool m_manual_evict = false;
}; };

541
src/common/threading.cpp Normal file
View 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
View 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

View file

@ -34,7 +34,7 @@ static HANDLE GetSleepTimer()
return s_sleep_timer; return s_sleep_timer;
} }
Timer::Value Timer::GetValue() double Timer::GetFrequency()
{ {
// even if this races, it should still result in the same value.. // even if this races, it should still result in the same value..
if (!s_counter_initialized) if (!s_counter_initialized)
@ -45,6 +45,11 @@ Timer::Value Timer::GetValue()
s_counter_initialized = true; s_counter_initialized = true;
} }
return s_counter_frequency;
}
Timer::Value Timer::GetCurrentValue()
{
Timer::Value ReturnValue; Timer::Value ReturnValue;
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&ReturnValue)); QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&ReturnValue));
return ReturnValue; return ReturnValue;
@ -52,44 +57,44 @@ Timer::Value Timer::GetValue()
double Timer::ConvertValueToNanoseconds(Timer::Value value) 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) 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) 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) 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) 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) 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) void Timer::SleepUntil(Value value, bool exact)
{ {
if (exact) if (exact)
{ {
while (GetValue() < value) while (GetCurrentValue() < value)
SleepUntil(value, false); SleepUntil(value, false);
} }
else 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) if (diff <= 0)
return; return;
@ -120,7 +125,12 @@ void Timer::SleepUntil(Value value, bool exact)
#else #else
Timer::Value Timer::GetValue() double Timer::GetFrequency()
{
return 1.0;
}
Timer::Value Timer::GetCurrentValue()
{ {
struct timespec tv; struct timespec tv;
clock_gettime(CLOCK_MONOTONIC, &tv); clock_gettime(CLOCK_MONOTONIC, &tv);
@ -161,14 +171,14 @@ void Timer::SleepUntil(Value value, bool exact)
{ {
if (exact) if (exact)
{ {
while (GetValue() < value) while (GetCurrentValue() < value)
SleepUntil(value, false); SleepUntil(value, false);
} }
else else
{ {
// Apple doesn't have TIMER_ABSTIME, so fall back to nanosleep in such a case. // Apple doesn't have TIMER_ABSTIME, so fall back to nanosleep in such a case.
#ifdef __APPLE__ #ifdef __APPLE__
const Value current_time = GetValue(); const Value current_time = GetCurrentValue();
if (value <= current_time) if (value <= current_time)
return; return;
@ -195,58 +205,82 @@ Timer::Timer()
void Timer::Reset() void Timer::Reset()
{ {
m_tvStartValue = GetValue(); m_tvStartValue = GetCurrentValue();
} }
double Timer::GetTimeSeconds() const double Timer::GetTimeSeconds() const
{ {
return ConvertValueToSeconds(GetValue() - m_tvStartValue); return ConvertValueToSeconds(GetCurrentValue() - m_tvStartValue);
} }
double Timer::GetTimeMilliseconds() const double Timer::GetTimeMilliseconds() const
{ {
return ConvertValueToMilliseconds(GetValue() - m_tvStartValue); return ConvertValueToMilliseconds(GetCurrentValue() - m_tvStartValue);
} }
double Timer::GetTimeNanoseconds() const 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) void Timer::BusyWait(std::uint64_t ns)
{ {
const Value start = GetValue(); const Value start = GetCurrentValue();
const Value end = start + ConvertNanosecondsToValue(static_cast<double>(ns)); const Value end = start + ConvertNanosecondsToValue(static_cast<double>(ns));
if (end < start) if (end < start)
{ {
// overflow, unlikely // 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) 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)); const std::uint64_t end = start + ConvertNanosecondsToValue(static_cast<double>(ns));
if (end < start) if (end < start)
{ {
// overflow, unlikely // overflow, unlikely
while (GetValue() > end) while (GetCurrentValue() > end)
; ;
} }
std::uint64_t current = GetValue(); std::uint64_t current = GetCurrentValue();
while (current < end) while (current < end)
{ {
const std::uint64_t remaining = end - current; const std::uint64_t remaining = end - current;
if (remaining >= min_sleep_time) if (remaining >= min_sleep_time)
NanoSleep(min_sleep_time); NanoSleep(min_sleep_time);
current = GetValue(); current = GetCurrentValue();
} }
} }

View file

@ -10,7 +10,9 @@ public:
Timer(); Timer();
static Value GetValue(); static double GetFrequency();
static Value GetCurrentValue();
static double ConvertValueToSeconds(Value value); static double ConvertValueToSeconds(Value value);
static double ConvertValueToMilliseconds(Value value); static double ConvertValueToMilliseconds(Value value);
static double ConvertValueToNanoseconds(Value value); static double ConvertValueToNanoseconds(Value value);
@ -23,11 +25,18 @@ public:
static void SleepUntil(Value value, bool exact); static void SleepUntil(Value value, bool exact);
void Reset(); void Reset();
void ResetTo(Value value) { m_tvStartValue = value; }
Value GetStartValue() const { return m_tvStartValue; }
double GetTimeSeconds() const; double GetTimeSeconds() const;
double GetTimeMilliseconds() const; double GetTimeMilliseconds() const;
double GetTimeNanoseconds() const; double GetTimeNanoseconds() const;
double GetTimeSecondsAndReset();
double GetTimeMillisecondsAndReset();
double GetTimeNanosecondsAndReset();
private: private:
Value m_tvStartValue; Value m_tvStartValue;
}; };

View file

@ -215,7 +215,7 @@ void GPUBackend::RunGPULoop()
u32 read_ptr = m_command_fifo_read_ptr.load(); u32 read_ptr = m_command_fifo_read_ptr.load();
if (read_ptr == write_ptr) 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) if (Common::Timer::ConvertValueToNanoseconds(current_time - last_command_time) < SPIN_TIME_NS)
continue; 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); m_command_fifo_read_ptr.store(read_ptr);
} }
} }

View file

@ -1475,7 +1475,7 @@ void GPU_HW::DrawRendererStats(bool is_idle_frame)
GPU_HW::ShaderCompileProgressTracker::ShaderCompileProgressTracker(std::string title, u32 total) GPU_HW::ShaderCompileProgressTracker::ShaderCompileProgressTracker(std::string title, u32 total)
: m_title(std::move(title)), m_min_time(Common::Timer::ConvertSecondsToValue(1.0)), : 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) m_last_update_time(0), m_progress(0), m_total(total)
{ {
} }
@ -1484,7 +1484,7 @@ void GPU_HW::ShaderCompileProgressTracker::Increment()
{ {
m_progress++; 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) 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)); g_host_interface->DisplayLoadingScreen(m_title.c_str(), 0, static_cast<int>(m_total), static_cast<int>(m_progress));

View file

@ -36,7 +36,7 @@ bool HostDisplay::ShouldSkipDisplayingFrame()
if (m_display_frame_interval == 0.0f) if (m_display_frame_interval == 0.0f)
return false; 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); const double diff = Common::Timer::ConvertValueToSeconds(now - m_last_frame_displayed_time);
if (diff < m_display_frame_interval) if (diff < m_display_frame_interval)
return true; return true;

View file

@ -1678,7 +1678,7 @@ void AddNotification(float duration, std::string title, std::string text, std::s
notif.title = std::move(title); notif.title = std::move(title);
notif.text = std::move(text); notif.text = std::move(text);
notif.badge_path = std::move(image_path); 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)); 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_IN_TIME = 0.6f;
static constexpr float EASE_OUT_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 horizontal_padding = ImGuiFullscreen::LayoutScale(20.0f);
const float vertical_padding = ImGuiFullscreen::LayoutScale(10.0f); const float vertical_padding = ImGuiFullscreen::LayoutScale(10.0f);

View file

@ -283,7 +283,7 @@ void NamcoGunCon::LoadSettings(const char* section)
{ {
m_crosshair_image_path = std::move(path); m_crosshair_image_path = std::move(path);
if (m_crosshair_image_path.empty() || 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(); m_crosshair_image.Invalidate();
} }

View file

@ -1585,7 +1585,7 @@ void UpdateThrottlePeriod()
void ResetThrottler() void ResetThrottler()
{ {
s_next_frame_time = Common::Timer::GetValue(); s_next_frame_time = Common::Timer::GetCurrentValue();
} }
void Throttle() void Throttle()
@ -1606,7 +1606,7 @@ void Throttle()
#endif #endif
// Use unsigned for defined overflow/wrap-around. // 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) ? const double sleep_time = (s_next_frame_time >= time) ?
Common::Timer::ConvertValueToNanoseconds(s_next_frame_time - time) : Common::Timer::ConvertValueToNanoseconds(s_next_frame_time - time) :
-Common::Timer::ConvertValueToNanoseconds(time - s_next_frame_time); -Common::Timer::ConvertValueToNanoseconds(time - s_next_frame_time);
@ -1630,7 +1630,7 @@ void RunFrames()
const u32 max_frames_to_run = 2; const u32 max_frames_to_run = 2;
u32 frames_run = 0; 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) while (frames_run < max_frames_to_run)
{ {
if (value < s_next_frame_time) if (value < s_next_frame_time)
@ -1639,7 +1639,7 @@ void RunFrames()
RunFrame(); RunFrame();
frames_run++; frames_run++;
value = Common::Timer::GetValue(); value = Common::Timer::GetCurrentValue();
} }
if (frames_run != 1) if (frames_run != 1)

View file

@ -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()); 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()); 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; return &it->second;
Common::RGBA8Image image; 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()); Log_ErrorPrintf("Failed to load '%s'", filename.c_str());
return nullptr; return nullptr;

View file

@ -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.num_motors = std::min<u32>(num_motors, ControllerRumbleState::MAX_MOTORS);
rumble.last_strength.fill(0.0f); rumble.last_strength.fill(0.0f);
rumble.update_callback = std::move(callback); 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)); 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 // This is because the rumble update is synchronous, and with bluetooth latency can severely impact fast forward
// performance. // performance.
static constexpr float UPDATE_FREQUENCY = 1000.0f; 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) for (ControllerRumbleState& rumble : m_controller_vibration_motors)
{ {

View file

@ -481,19 +481,27 @@ void DestroyResources()
static std::unique_ptr<HostDisplayTexture> LoadTexture(const char* path, bool from_package) static std::unique_ptr<HostDisplayTexture> LoadTexture(const char* path, bool from_package)
{ {
std::unique_ptr<ByteStream> stream; std::vector<u8> data;
if (from_package) 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 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); Log_ErrorPrintf("Failed to open texture resource '%s'", path);
return {}; return {};
} }
Common::RGBA8Image image; 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); Log_ErrorPrintf("Failed to read texture resource '%s'", path);
return {}; return {};