Merge branch 'master' into windows-clang

This commit is contained in:
qurious-pixel 2025-11-16 05:27:10 -08:00 committed by Live session user
commit 24a94f2dbe
33 changed files with 530 additions and 115 deletions

View file

@ -387,8 +387,9 @@ jobs:
run: | run: |
export CCACHE_DIR=$(cygpath -u "$CCACHE_DIR") export CCACHE_DIR=$(cygpath -u "$CCACHE_DIR")
echo "CCACHE_DIR=$CCACHE_DIR" echo "CCACHE_DIR=$CCACHE_DIR"
.ci/setup-windows-ci-vars.sh ${{ matrix.arch }} ${{ matrix.compiler }}
.ci/build-windows-clang.sh .ci/build-windows-clang.sh
.ci/setup-windows-ci-vars.sh ${{ matrix.arch }} ${{ matrix.compiler }}
.ci/deploy-windows-${{ matrix.compiler }}.sh
- name: Save build Ccache - name: Save build Ccache
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master'

View file

@ -193,6 +193,8 @@ if(BUILD_RPCS3_TESTS)
PRIVATE PRIVATE
tests/test.cpp tests/test.cpp
tests/test_fmt.cpp tests/test_fmt.cpp
tests/test_pair.cpp
tests/test_tuple.cpp
tests/test_simple_array.cpp tests/test_simple_array.cpp
tests/test_address_range.cpp tests/test_address_range.cpp
) )

View file

@ -11,10 +11,10 @@ SELF_KEY::SELF_KEY(u64 ver_start, u64 ver_end, u16 rev, u32 type, const std::str
version_end = ver_end; version_end = ver_end;
revision = rev; revision = rev;
self_type = type; self_type = type;
hex_to_bytes(erk, e.c_str(), 0); hex_to_bytes(erk, e, 0);
hex_to_bytes(riv, r.c_str(), 0); hex_to_bytes(riv, r, 0);
hex_to_bytes(pub, pb.c_str(), 0); hex_to_bytes(pub, pb, 0);
hex_to_bytes(priv, pr.c_str(), 0); hex_to_bytes(priv, pr, 0);
curve_type = ct; curve_type = ct;
} }

View file

@ -7,9 +7,11 @@
#include "sha1.h" #include "sha1.h"
#include "sha256.h" #include "sha256.h"
#include "key_vault.h" #include "key_vault.h"
#include <charconv>
#include <cstdlib>
#include <cstring> #include <cstring>
#include <stdio.h> #include <cstdio>
#include <time.h> #include <ctime>
#include "Utilities/StrUtil.h" #include "Utilities/StrUtil.h"
#include "Utilities/File.h" #include "Utilities/File.h"
@ -21,50 +23,24 @@
// Auxiliary functions (endian swap, xor). // Auxiliary functions (endian swap, xor).
// Hex string conversion auxiliary functions. // Hex string conversion auxiliary functions.
u64 hex_to_u64(const char* hex_str) void hex_to_bytes(unsigned char* data, std::string_view hex_str, unsigned int str_length)
{ {
auto length = std::strlen(hex_str); const auto strn_length = (str_length > 0) ? str_length : hex_str.size();
u64 tmp = 0;
u64 result = 0;
char c;
while (length--)
{
c = *hex_str++;
if((c >= '0') && (c <= '9'))
tmp = c - '0';
else if((c >= 'a') && (c <= 'f'))
tmp = c - 'a' + 10;
else if((c >= 'A') && (c <= 'F'))
tmp = c - 'A' + 10;
else
tmp = 0;
result |= (tmp << (length * 4));
}
return result;
}
void hex_to_bytes(unsigned char* data, const char* hex_str, unsigned int str_length)
{
const auto strn_length = (str_length > 0) ? str_length : std::strlen(hex_str);
auto data_length = strn_length / 2;
char tmp_buf[3] = {0, 0, 0};
// Don't convert if the string length is odd. // Don't convert if the string length is odd.
if ((strn_length % 2) == 0) if ((strn_length % 2) == 0)
{ {
while (data_length--) for (size_t i = 0; i < strn_length; i += 2)
{ {
tmp_buf[0] = *hex_str++; const auto [ptr, err] = std::from_chars(hex_str.data() + i, hex_str.data() + i + 2, *data++, 16);
tmp_buf[1] = *hex_str++; if (err != std::errc())
{
*data++ = static_cast<u8>(hex_to_u64(tmp_buf) & 0xFF); fmt::throw_exception("Failed to read hex string: %s", std::make_error_code(err).message());
}
} }
} }
} }
// Crypto functions (AES128-CBC, AES128-ECB, SHA1-HMAC and AES-CMAC). // Crypto functions (AES128-CBC, AES128-ECB, SHA1-HMAC and AES-CMAC).
void aescbc128_decrypt(unsigned char *key, unsigned char *iv, unsigned char *in, unsigned char *out, usz len) void aescbc128_decrypt(unsigned char *key, unsigned char *iv, unsigned char *in, unsigned char *out, usz len)
{ {

View file

@ -6,7 +6,8 @@
#include "util/types.hpp" #include "util/types.hpp"
#include <stdlib.h> #include <cstdlib>
#include <string_view>
enum { CRYPTO_MAX_PATH = 4096 }; enum { CRYPTO_MAX_PATH = 4096 };
@ -15,8 +16,7 @@ char* extract_file_name(const char* file_path, char real_file_name[CRYPTO_MAX_PA
std::string sha256_get_hash(const char* data, usz size, bool lower_case); std::string sha256_get_hash(const char* data, usz size, bool lower_case);
// Hex string conversion auxiliary functions. // Hex string conversion auxiliary functions.
u64 hex_to_u64(const char* hex_str); void hex_to_bytes(unsigned char* data, std::string_view hex_str, unsigned int str_length);
void hex_to_bytes(unsigned char *data, const char *hex_str, unsigned int str_length);
// Crypto functions (AES128-CBC, AES128-ECB, SHA1-HMAC and AES-CMAC). // Crypto functions (AES128-CBC, AES128-ECB, SHA1-HMAC and AES-CMAC).
void aescbc128_decrypt(unsigned char *key, unsigned char *iv, unsigned char *in, unsigned char *out, usz len); void aescbc128_decrypt(unsigned char *key, unsigned char *iv, unsigned char *in, unsigned char *out, usz len);

View file

@ -1230,7 +1230,7 @@ s32 _spurs::initialize(ppu_thread& ppu, vm::ptr<CellSpurs> spurs, u32 revision,
if (flags & SAF_UNKNOWN_FLAG_9) spuTgAttr->type |= 0x0800; if (flags & SAF_UNKNOWN_FLAG_9) spuTgAttr->type |= 0x0800;
if (flags & SAF_SYSTEM_WORKLOAD_ENABLED) spuTgAttr->type |= SYS_SPU_THREAD_GROUP_TYPE_COOPERATE_WITH_SYSTEM; if (flags & SAF_SYSTEM_WORKLOAD_ENABLED) spuTgAttr->type |= SYS_SPU_THREAD_GROUP_TYPE_COOPERATE_WITH_SYSTEM;
if (s32 rc = sys_spu_thread_group_create(ppu, spurs.ptr(&CellSpurs::spuTG), nSpus, spuPriority, spuTgAttr)) if (s32 rc = sys_spu_thread_group_create(ppu, spurs.ptr(&CellSpurs::spuTG), nSpus, spuPriority, vm::unsafe_ptr_cast<reduced_sys_spu_thread_group_attribute>(spuTgAttr)))
{ {
ppu_execute<&sys_spu_image_close>(ppu, spurs.ptr(&CellSpurs::spuImg)); ppu_execute<&sys_spu_image_close>(ppu, spurs.ptr(&CellSpurs::spuImg));
return rollback(), rc; return rollback(), rc;

View file

@ -303,6 +303,33 @@ static FORCE_INLINE void mov_rdata_avx(__m256i* dst, const __m256i* src)
} }
#endif #endif
// Check if only a single 16-bytes block has changed
// Returning its position, or -1 if that is not the situation
static inline usz scan16_rdata(const decltype(spu_thread::rdata)& _lhs, const decltype(spu_thread::rdata)& _rhs)
{
const auto lhs = reinterpret_cast<const v128*>(_lhs);
const auto rhs = reinterpret_cast<const v128*>(_rhs);
u32 mask = 0;
for (usz i = 0; i < 8; i += 4)
{
const u32 a = (lhs[i + 0] != rhs[i + 0]) ? 1 : 0;
const u32 b = (lhs[i + 1] != rhs[i + 1]) ? 1 : 0;
const u32 c = (lhs[i + 2] != rhs[i + 2]) ? 1 : 0;
const u32 d = (lhs[i + 3] != rhs[i + 3]) ? 1 : 0;
mask |= ((a << 0) + (b << 1) + (c << 2) + (d << 3)) << i;
}
if (mask && (mask & (mask - 1)) == 0)
{
return std::countr_zero(mask);
}
return umax;
}
#ifdef _MSC_VER #ifdef _MSC_VER
__forceinline __forceinline
#endif #endif
@ -3854,6 +3881,11 @@ bool spu_thread::do_putllc(const spu_mfc_cmd& args)
return false; return false;
} }
static const auto cast_as = [](void* ptr, usz pos){ return reinterpret_cast<u128*>(ptr) + pos; };
static const auto cast_as_const = [](const void* ptr, usz pos){ return reinterpret_cast<const u128*>(ptr) + pos; };
const usz diff16_pos = scan16_rdata(to_write, rdata);
auto [_oldd, _ok] = res.fetch_op([&](u64& r) auto [_oldd, _ok] = res.fetch_op([&](u64& r)
{ {
if ((r & -128) != rtime || (r & 127)) if ((r & -128) != rtime || (r & 127))
@ -3975,8 +4007,19 @@ bool spu_thread::do_putllc(const spu_mfc_cmd& args)
if (cmp_rdata(rdata, super_data)) if (cmp_rdata(rdata, super_data))
{ {
mov_rdata(super_data, to_write); if (diff16_pos != umax)
return true; {
// Do it with CMPXCHG16B if possible, this allows to improve accuracy whenever "RSX Accurate Reservations" is off
if (atomic_storage<u128>::compare_exchange(*cast_as(super_data, diff16_pos), *cast_as(rdata, diff16_pos), *cast_as_const(to_write, diff16_pos)))
{
return true;
}
}
else
{
mov_rdata(super_data, to_write);
return true;
}
} }
return false; return false;

View file

@ -468,11 +468,11 @@ error_code sys_ppu_thread_restart(ppu_thread& ppu)
return CELL_OK; return CELL_OK;
} }
error_code _sys_ppu_thread_create(ppu_thread& ppu, vm::ptr<u64> thread_id, vm::ptr<ppu_thread_param_t> param, u64 arg, u64 unk, s32 prio, u32 _stacksz, u64 flags, vm::cptr<char> threadname) error_code _sys_ppu_thread_create(ppu_thread& ppu, vm::ptr<u64> thread_id, vm::ptr<ppu_thread_param_t> param, u64 arg, u64 unk, s32 prio, u64 _stacksz, u64 flags, vm::cptr<char> threadname)
{ {
ppu.state += cpu_flag::wait; ppu.state += cpu_flag::wait;
sys_ppu_thread.warning("_sys_ppu_thread_create(thread_id=*0x%x, param=*0x%x, arg=0x%llx, unk=0x%llx, prio=%d, stacksize=0x%x, flags=0x%llx, threadname=*0x%x)", sys_ppu_thread.warning("_sys_ppu_thread_create(thread_id=*0x%x, param=*0x%x, arg=0x%llx, unk=0x%llx, prio=%d, stacksize=0x%llx, flags=0x%llx, threadname=*0x%x)",
thread_id, param, arg, unk, prio, _stacksz, flags, threadname); thread_id, param, arg, unk, prio, _stacksz, flags, threadname);
// thread_id is checked for null in stub -> CELL_ENOMEM // thread_id is checked for null in stub -> CELL_ENOMEM
@ -497,7 +497,8 @@ error_code _sys_ppu_thread_create(ppu_thread& ppu, vm::ptr<u64> thread_id, vm::p
const u32 tls = param->tls; const u32 tls = param->tls;
// Compute actual stack size and allocate // Compute actual stack size and allocate
const u32 stack_size = utils::align<u32>(std::max<u32>(_stacksz, 4096), 4096); // 0 and UINT64_MAX both convert to 4096
const u64 stack_size = FN(x ? x : 4096)(utils::align<u64>(_stacksz, 4096));
auto& dct = g_fxo->get<lv2_memory_container>(); auto& dct = g_fxo->get<lv2_memory_container>();
@ -507,7 +508,7 @@ error_code _sys_ppu_thread_create(ppu_thread& ppu, vm::ptr<u64> thread_id, vm::p
return {CELL_ENOMEM, dct.size - dct.used}; return {CELL_ENOMEM, dct.size - dct.used};
} }
const vm::addr_t stack_base{vm::alloc(stack_size, vm::stack, 4096)}; const vm::addr_t stack_base{vm::alloc(static_cast<u32>(stack_size), vm::stack, 4096)};
if (!stack_base) if (!stack_base)
{ {
@ -532,7 +533,7 @@ error_code _sys_ppu_thread_create(ppu_thread& ppu, vm::ptr<u64> thread_id, vm::p
{ {
ppu_thread_params p; ppu_thread_params p;
p.stack_addr = stack_base; p.stack_addr = stack_base;
p.stack_size = stack_size; p.stack_size = static_cast<u32>(stack_size);
p.tls_addr = tls; p.tls_addr = tls;
p.entry = entry; p.entry = entry;
p.arg0 = arg; p.arg0 = arg;

View file

@ -53,7 +53,7 @@ error_code sys_ppu_thread_get_priority(ppu_thread& ppu, u32 thread_id, vm::ptr<s
error_code sys_ppu_thread_get_stack_information(ppu_thread& ppu, vm::ptr<sys_ppu_thread_stack_t> sp); error_code sys_ppu_thread_get_stack_information(ppu_thread& ppu, vm::ptr<sys_ppu_thread_stack_t> sp);
error_code sys_ppu_thread_stop(ppu_thread& ppu, u32 thread_id); error_code sys_ppu_thread_stop(ppu_thread& ppu, u32 thread_id);
error_code sys_ppu_thread_restart(ppu_thread& ppu); error_code sys_ppu_thread_restart(ppu_thread& ppu);
error_code _sys_ppu_thread_create(ppu_thread& ppu, vm::ptr<u64> thread_id, vm::ptr<ppu_thread_param_t> param, u64 arg, u64 arg4, s32 prio, u32 stacksize, u64 flags, vm::cptr<char> threadname); error_code _sys_ppu_thread_create(ppu_thread& ppu, vm::ptr<u64> thread_id, vm::ptr<ppu_thread_param_t> param, u64 arg, u64 arg4, s32 prio, u64 stacksize, u64 flags, vm::cptr<char> threadname);
error_code sys_ppu_thread_start(ppu_thread& ppu, u32 thread_id); error_code sys_ppu_thread_start(ppu_thread& ppu, u32 thread_id);
error_code sys_ppu_thread_rename(ppu_thread& ppu, u32 thread_id, vm::cptr<char> name); error_code sys_ppu_thread_rename(ppu_thread& ppu, u32 thread_id, vm::cptr<char> name);
error_code sys_ppu_thread_recover_page_fault(ppu_thread& ppu, u32 thread_id); error_code sys_ppu_thread_recover_page_fault(ppu_thread& ppu, u32 thread_id);

View file

@ -767,7 +767,12 @@ error_code sys_spu_thread_initialize(ppu_thread& ppu, vm::ptr<u32> thread, u32 g
} }
// Read thread name // Read thread name
const std::string thread_name(attr_data.name.get_ptr(), std::max<u32>(attr_data.name_len, 1) - 1); std::string thread_name;
if (attr_data.name_len && !vm::read_string(attr_data.name.addr(), attr_data.name_len - 1, thread_name, true))
{
return { CELL_EFAULT, attr_data.name.addr() };
}
const auto group = idm::get_unlocked<lv2_spu_group>(group_id); const auto group = idm::get_unlocked<lv2_spu_group>(group_id);
@ -906,7 +911,7 @@ error_code sys_spu_thread_get_exit_status(ppu_thread& ppu, u32 id, vm::ptr<s32>
return CELL_ESTAT; return CELL_ESTAT;
} }
error_code sys_spu_thread_group_create(ppu_thread& ppu, vm::ptr<u32> id, u32 num, s32 prio, vm::ptr<sys_spu_thread_group_attribute> attr) error_code sys_spu_thread_group_create(ppu_thread& ppu, vm::ptr<u32> id, u32 num, s32 prio, vm::ptr<reduced_sys_spu_thread_group_attribute> attr)
{ {
ppu.state += cpu_flag::wait; ppu.state += cpu_flag::wait;
@ -914,13 +919,32 @@ error_code sys_spu_thread_group_create(ppu_thread& ppu, vm::ptr<u32> id, u32 num
const s32 min_prio = g_ps3_process_info.has_root_perm() ? 0 : 16; const s32 min_prio = g_ps3_process_info.has_root_perm() ? 0 : 16;
const sys_spu_thread_group_attribute attr_data = *attr; sys_spu_thread_group_attribute attr_data{};
{
const reduced_sys_spu_thread_group_attribute attr_reduced = *attr;
attr_data.name = attr_reduced.name;
attr_data.nsize = attr_reduced.nsize;
attr_data.type = attr_reduced.type;
// Read container-id member at offset 12 bytes conditionally (that's what LV2 does)
if (attr_data.type & SYS_SPU_THREAD_GROUP_TYPE_MEMORY_FROM_CONTAINER)
{
attr_data.ct = vm::unsafe_ptr_cast<sys_spu_thread_group_attribute>(attr)->ct;
}
}
if (attr_data.nsize > 0x80 || !num) if (attr_data.nsize > 0x80 || !num)
{ {
return CELL_EINVAL; return CELL_EINVAL;
} }
std::string group_name;
if (attr_data.nsize && !vm::read_string(attr_data.name.addr(), attr_data.nsize - 1, group_name, true))
{
return { CELL_EFAULT, attr_data.name.addr() };
}
const s32 type = attr_data.type; const s32 type = attr_data.type;
bool use_scheduler = true; bool use_scheduler = true;
@ -1075,7 +1099,7 @@ error_code sys_spu_thread_group_create(ppu_thread& ppu, vm::ptr<u32> id, u32 num
return CELL_EBUSY; return CELL_EBUSY;
} }
const auto group = idm::make_ptr<lv2_spu_group>(std::string(attr_data.name.get_ptr(), std::max<u32>(attr_data.nsize, 1) - 1), num, prio, type, ct, use_scheduler, mem_size); const auto group = idm::make_ptr<lv2_spu_group>(std::move(group_name), num, prio, type, ct, use_scheduler, mem_size);
if (!group) if (!group)
{ {

View file

@ -82,11 +82,18 @@ enum spu_stop_syscall : u32
SYS_SPU_THREAD_STOP_SWITCH_SYSTEM_MODULE = 0x0120, SYS_SPU_THREAD_STOP_SWITCH_SYSTEM_MODULE = 0x0120,
}; };
struct sys_spu_thread_group_attribute struct reduced_sys_spu_thread_group_attribute
{ {
be_t<u32> nsize; // name length including NULL terminator be_t<u32> nsize; // name length including NULL terminator
vm::bcptr<char> name; vm::bcptr<char> name;
be_t<s32> type; be_t<s32> type;
};
struct sys_spu_thread_group_attribute
{
be_t<u32> nsize;
vm::bcptr<char> name;
be_t<s32> type;
be_t<u32> ct; // memory container id be_t<u32> ct; // memory container id
}; };
@ -360,7 +367,7 @@ error_code _sys_spu_image_close(ppu_thread&, vm::ptr<sys_spu_image> img);
error_code _sys_spu_image_get_segments(ppu_thread&, vm::ptr<sys_spu_image> img, vm::ptr<sys_spu_segment> segments, s32 nseg); error_code _sys_spu_image_get_segments(ppu_thread&, vm::ptr<sys_spu_image> img, vm::ptr<sys_spu_segment> segments, s32 nseg);
error_code sys_spu_thread_initialize(ppu_thread&, vm::ptr<u32> thread, u32 group, u32 spu_num, vm::ptr<sys_spu_image>, vm::ptr<sys_spu_thread_attribute>, vm::ptr<sys_spu_thread_argument>); error_code sys_spu_thread_initialize(ppu_thread&, vm::ptr<u32> thread, u32 group, u32 spu_num, vm::ptr<sys_spu_image>, vm::ptr<sys_spu_thread_attribute>, vm::ptr<sys_spu_thread_argument>);
error_code sys_spu_thread_set_argument(ppu_thread&, u32 id, vm::ptr<sys_spu_thread_argument> arg); error_code sys_spu_thread_set_argument(ppu_thread&, u32 id, vm::ptr<sys_spu_thread_argument> arg);
error_code sys_spu_thread_group_create(ppu_thread&, vm::ptr<u32> id, u32 num, s32 prio, vm::ptr<sys_spu_thread_group_attribute> attr); error_code sys_spu_thread_group_create(ppu_thread&, vm::ptr<u32> id, u32 num, s32 prio, vm::ptr<reduced_sys_spu_thread_group_attribute> attr);
error_code sys_spu_thread_group_destroy(ppu_thread&, u32 id); error_code sys_spu_thread_group_destroy(ppu_thread&, u32 id);
error_code sys_spu_thread_group_start(ppu_thread&, u32 id); error_code sys_spu_thread_group_start(ppu_thread&, u32 id);
error_code sys_spu_thread_group_suspend(ppu_thread&, u32 id); error_code sys_spu_thread_group_suspend(ppu_thread&, u32 id);

View file

@ -16,7 +16,7 @@ namespace rsx
}; };
template <typename Ty> template <typename Ty>
requires std::is_trivially_destructible_v<Ty> requires std::is_trivially_destructible_v<Ty> && std::is_trivially_copyable_v<Ty>
struct simple_array struct simple_array
{ {
public: public:
@ -196,7 +196,7 @@ namespace rsx
if (is_local_storage()) if (is_local_storage())
{ {
// Switch to heap storage // Switch to heap storage
_data = static_cast<Ty*>(std::malloc(sizeof(Ty) * size)); ensure(_data = static_cast<Ty*>(std::malloc(sizeof(Ty) * size)));
std::memcpy(static_cast<void*>(_data), _local_storage, size_bytes()); std::memcpy(static_cast<void*>(_data), _local_storage, size_bytes());
} }
else else

View file

@ -9,6 +9,7 @@
#include <list> #include <list>
#include "util/asm.hpp" #include "util/asm.hpp"
#include "util/pair.hpp"
namespace rsx namespace rsx
{ {
@ -244,10 +245,9 @@ namespace rsx
template <bool is_depth_surface> template <bool is_depth_surface>
void intersect_surface_region(command_list_type cmd, u32 address, surface_type new_surface, surface_type prev_surface) void intersect_surface_region(command_list_type cmd, u32 address, surface_type new_surface, surface_type prev_surface)
{ {
auto scan_list = [&new_surface, address](const rsx::address_range32& mem_range, auto scan_list = [&new_surface, address](const rsx::address_range32& mem_range, surface_ranged_map& data)
surface_ranged_map& data) -> rsx::simple_array<std::pair<u32, surface_type>>
{ {
rsx::simple_array<std::pair<u32, surface_type>> result; rsx::simple_array<utils::pair<u32, surface_type>> result;
for (auto it = data.begin_range(mem_range); it != data.end(); ++it) for (auto it = data.begin_range(mem_range); it != data.end(); ++it)
{ {
auto surface = Traits::get(it->second); auto surface = Traits::get(it->second);
@ -314,7 +314,7 @@ namespace rsx
} }
} }
rsx::simple_array<std::pair<u32, surface_type>> surface_info; rsx::simple_array<utils::pair<u32, surface_type>> surface_info;
if (list1.empty()) if (list1.empty())
{ {
surface_info = std::move(list2); surface_info = std::move(list2);
@ -1091,7 +1091,7 @@ namespace rsx
rsx::simple_array<surface_overlap_info> get_merged_texture_memory_region(commandbuffer_type& cmd, u32 texaddr, u32 required_width, u32 required_height, u32 required_pitch, u8 required_bpp, rsx::surface_access access) rsx::simple_array<surface_overlap_info> get_merged_texture_memory_region(commandbuffer_type& cmd, u32 texaddr, u32 required_width, u32 required_height, u32 required_pitch, u8 required_bpp, rsx::surface_access access)
{ {
rsx::simple_array<surface_overlap_info> result; rsx::simple_array<surface_overlap_info> result;
rsx::simple_array<std::pair<u32, bool>> dirty; rsx::simple_array<utils::pair<u32, bool>> dirty;
const auto surface_internal_pitch = (required_width * required_bpp); const auto surface_internal_pitch = (required_width * required_bpp);

View file

@ -323,7 +323,9 @@ void GLGSRender::flip(const rsx::display_flip_info_t& info)
if (image_to_flip) if (image_to_flip)
{ {
if (g_user_asked_for_screenshot || (g_recording_mode != recording_mode::stopped && m_frame->can_consume_frame())) const bool user_asked_for_screenshot = g_user_asked_for_screenshot.exchange(false);
if (user_asked_for_screenshot || (g_recording_mode != recording_mode::stopped && m_frame->can_consume_frame()))
{ {
static const gl::pixel_pack_settings pack_settings{}; static const gl::pixel_pack_settings pack_settings{};
@ -374,7 +376,7 @@ void GLGSRender::flip(const rsx::display_flip_info_t& info)
{ {
screenshot_log.error("Failed to capture image: 0x%x", err); screenshot_log.error("Failed to capture image: 0x%x", err);
} }
else if (g_user_asked_for_screenshot.exchange(false)) else if (user_asked_for_screenshot)
{ {
m_frame->take_screenshot(std::move(sshot_frame), buffer_width, buffer_height, false); m_frame->take_screenshot(std::move(sshot_frame), buffer_width, buffer_height, false);
} }

View file

@ -387,7 +387,7 @@ namespace gl
allocator.pools[i].flags = 0; allocator.pools[i].flags = 0;
} }
rsx::simple_array<std::pair<int, int>> replacement_map; rsx::simple_array<utils::pair<int, int>> replacement_map;
for (int i = 0; i < rsx::limits::fragment_textures_count; ++i) for (int i = 0; i < rsx::limits::fragment_textures_count; ++i)
{ {
if (reference_mask & (1 << i)) if (reference_mask & (1 << i))

View file

@ -4,6 +4,8 @@
#include "Emu/system_config.h" #include "Emu/system_config.h"
#include "Utilities/date_time.h" #include "Utilities/date_time.h"
extern atomic_t<bool> g_user_asked_for_screenshot;
namespace rsx namespace rsx
{ {
namespace overlays namespace overlays
@ -94,12 +96,13 @@ namespace rsx
break; break;
} }
case page_navigation::exit: case page_navigation::exit:
case page_navigation::exit_for_screenshot:
{ {
fade_animation.current = color4f(1.f); fade_animation.current = color4f(1.f);
fade_animation.end = color4f(0.f); fade_animation.end = color4f(0.f);
fade_animation.active = true; fade_animation.active = true;
fade_animation.on_finish = [this] fade_animation.on_finish = [this, navigation]
{ {
close(true, true); close(true, true);
@ -110,6 +113,12 @@ namespace rsx
Emu.Resume(); Emu.Resume();
}); });
} }
if (navigation == page_navigation::exit_for_screenshot)
{
rsx_log.notice("Taking screenshot after exiting home menu");
g_user_asked_for_screenshot = true;
}
}; };
break; break;
} }

View file

@ -18,7 +18,8 @@ namespace rsx
stay, stay,
back, back,
next, next,
exit exit,
exit_for_screenshot
}; };
struct home_menu_entry : horizontal_layout struct home_menu_entry : horizontal_layout

View file

@ -11,7 +11,6 @@
#include "Emu/Cell/Modules/sceNpTrophy.h" #include "Emu/Cell/Modules/sceNpTrophy.h"
extern atomic_t<bool> g_user_asked_for_recording; extern atomic_t<bool> g_user_asked_for_recording;
extern atomic_t<bool> g_user_asked_for_screenshot;
namespace rsx namespace rsx
{ {
@ -98,8 +97,7 @@ namespace rsx
if (btn != pad_button::cross) return page_navigation::stay; if (btn != pad_button::cross) return page_navigation::stay;
rsx_log.notice("User selected screenshot in home menu"); rsx_log.notice("User selected screenshot in home menu");
g_user_asked_for_screenshot = true; return page_navigation::exit_for_screenshot;
return page_navigation::exit;
}); });
std::unique_ptr<overlay_element> recording = std::make_unique<home_menu_entry>(get_localized_string(localized_string_id::HOME_MENU_RECORDING)); std::unique_ptr<overlay_element> recording = std::make_unique<home_menu_entry>(get_localized_string(localized_string_id::HOME_MENU_RECORDING));

View file

@ -201,7 +201,7 @@ std::u32string utf16_to_u32string(const std::u16string& utf16_string)
for (const auto& code : utf16_string) for (const auto& code : utf16_string)
{ {
result.push_back(code); result.push_back(static_cast<char32_t>(code));
} }
return result; return result;

View file

@ -694,7 +694,9 @@ void VKGSRender::flip(const rsx::display_flip_info_t& info)
m_upscaler->scale_output(*m_current_command_buffer, image_to_flip, target_image, target_layout, rgn, UPSCALE_AND_COMMIT | UPSCALE_DEFAULT_VIEW); m_upscaler->scale_output(*m_current_command_buffer, image_to_flip, target_image, target_layout, rgn, UPSCALE_AND_COMMIT | UPSCALE_DEFAULT_VIEW);
} }
if (g_user_asked_for_screenshot || (g_recording_mode != recording_mode::stopped && m_frame->can_consume_frame())) const bool user_asked_for_screenshot = g_user_asked_for_screenshot.exchange(false);
if (user_asked_for_screenshot || (g_recording_mode != recording_mode::stopped && m_frame->can_consume_frame()))
{ {
const usz sshot_size = buffer_height * buffer_width * 4; const usz sshot_size = buffer_height * buffer_width * 4;
@ -766,7 +768,7 @@ void VKGSRender::flip(const rsx::display_flip_info_t& info)
const bool is_bgra = image_to_copy->format() == VK_FORMAT_B8G8R8A8_UNORM; const bool is_bgra = image_to_copy->format() == VK_FORMAT_B8G8R8A8_UNORM;
if (g_user_asked_for_screenshot.exchange(false)) if (user_asked_for_screenshot)
{ {
m_frame->take_screenshot(std::move(sshot_frame), buffer_width, buffer_height, is_bgra); m_frame->take_screenshot(std::move(sshot_frame), buffer_width, buffer_height, is_bgra);
} }

View file

@ -453,7 +453,7 @@ namespace vk
} }
m_descriptor_slots.resize(bind_slots_count); m_descriptor_slots.resize(bind_slots_count);
std::memset(m_descriptor_slots.data(), 0, sizeof(descriptor_slot_t) * bind_slots_count); std::fill(m_descriptor_slots.begin(), m_descriptor_slots.end(), descriptor_slot_t{});
m_descriptors_dirty.resize(bind_slots_count); m_descriptors_dirty.resize(bind_slots_count);
std::fill(m_descriptors_dirty.begin(), m_descriptors_dirty.end(), false); std::fill(m_descriptors_dirty.begin(), m_descriptors_dirty.end(), false);
@ -534,7 +534,7 @@ namespace vk
auto update_descriptor_slot = [this](unsigned idx) auto update_descriptor_slot = [this](unsigned idx)
{ {
const auto& slot = m_descriptor_slots[idx]; const auto& slot = m_descriptor_slots[idx];
const VkDescriptorType type = m_descriptor_types[idx];
if (auto ptr = std::get_if<VkDescriptorImageInfoEx>(&slot)) if (auto ptr = std::get_if<VkDescriptorImageInfoEx>(&slot))
{ {
m_descriptor_template[idx].pImageInfo = m_descriptor_set.store(*ptr); m_descriptor_template[idx].pImageInfo = m_descriptor_set.store(*ptr);

View file

@ -736,6 +736,8 @@
<ClInclude Include="Loader\mself.hpp" /> <ClInclude Include="Loader\mself.hpp" />
<ClInclude Include="util\atomic.hpp" /> <ClInclude Include="util\atomic.hpp" />
<ClInclude Include="util\bless.hpp" /> <ClInclude Include="util\bless.hpp" />
<ClInclude Include="util\pair.hpp" />
<ClInclude Include="util\tuple.hpp" />
<ClInclude Include="util\video_sink.h" /> <ClInclude Include="util\video_sink.h" />
<ClInclude Include="util\video_provider.h" /> <ClInclude Include="util\video_provider.h" />
<ClInclude Include="util\media_utils.h" /> <ClInclude Include="util\media_utils.h" />

View file

@ -2758,6 +2758,12 @@
<ClInclude Include="..\Utilities\deferred_op.hpp"> <ClInclude Include="..\Utilities\deferred_op.hpp">
<Filter>Utilities</Filter> <Filter>Utilities</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="util\tuple.hpp">
<Filter>Utilities</Filter>
</ClInclude>
<ClInclude Include="util\pair.hpp">
<Filter>Utilities</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="Emu\RSX\Program\GLSLSnippets\GPUDeswizzle.glsl"> <None Include="Emu\RSX\Program\GLSLSnippets\GPUDeswizzle.glsl">

View file

@ -769,7 +769,7 @@ cheat_manager_dialog::cheat_manager_dialog(QWidget* parent)
} }
// TODO: better way to do this? // TODO: better way to do this?
switch (static_cast<cheat_type>(cbx_cheat_search_type->currentIndex())) switch (cheat->type)
{ {
case cheat_type::unsigned_8_cheat: results = convert_and_set<u8>(final_offset); break; case cheat_type::unsigned_8_cheat: results = convert_and_set<u8>(final_offset); break;
case cheat_type::unsigned_16_cheat: results = convert_and_set<u16>(final_offset); break; case cheat_type::unsigned_16_cheat: results = convert_and_set<u16>(final_offset); break;
@ -1062,7 +1062,5 @@ QString cheat_manager_dialog::get_localized_cheat_type(cheat_type type)
case cheat_type::float_32_cheat: return tr("Float 32 bits"); case cheat_type::float_32_cheat: return tr("Float 32 bits");
case cheat_type::max: break; case cheat_type::max: break;
} }
std::string type_formatted; return QString::fromStdString(fmt::format("%s", type));
fmt::append(type_formatted, "%s", type);
return QString::fromStdString(type_formatted);
} }

View file

@ -16,6 +16,7 @@
#include "Emu/System.h" #include "Emu/System.h"
#include "Emu/system_utils.hpp" #include "Emu/system_utils.hpp"
#include "Utilities/File.h"
#include "Input/pad_thread.h" #include "Input/pad_thread.h"
#include "Input/gui_pad_thread.h" #include "Input/gui_pad_thread.h"
@ -86,21 +87,7 @@ pad_settings_dialog::pad_settings_dialog(std::shared_ptr<gui_settings> gui_setti
if (m_title_id.empty()) if (m_title_id.empty())
{ {
const QString input_config_dir = QString::fromStdString(rpcs3::utils::get_input_config_dir(m_title_id)); const auto [config_files, active_config_file] = get_config_files();
QStringList config_files = gui::utils::get_dir_entries(QDir(input_config_dir), QStringList() << "*.yml");
QString active_config_file = QString::fromStdString(g_cfg_input_configs.active_configs.get_value(g_cfg_input_configs.global_key));
if (!config_files.contains(active_config_file))
{
const QString default_config_file = QString::fromStdString(g_cfg_input_configs.default_config);
if (!config_files.contains(default_config_file) && CreateConfigFile(input_config_dir, default_config_file))
{
config_files.prepend(default_config_file);
}
active_config_file = default_config_file;
}
for (const QString& profile : config_files) for (const QString& profile : config_files)
{ {
@ -145,6 +132,9 @@ pad_settings_dialog::pad_settings_dialog(std::shared_ptr<gui_settings> gui_setti
// Pushbutton: Add config file // Pushbutton: Add config file
connect(ui->b_addConfig, &QAbstractButton::clicked, this, &pad_settings_dialog::AddConfigFile); connect(ui->b_addConfig, &QAbstractButton::clicked, this, &pad_settings_dialog::AddConfigFile);
// Pushbutton: Remove config file
connect(ui->b_remConfig, &QAbstractButton::clicked, this, &pad_settings_dialog::RemoveConfigFile);
ui->buttonBox->button(QDialogButtonBox::Reset)->setText(tr("Filter Noise")); ui->buttonBox->button(QDialogButtonBox::Reset)->setText(tr("Filter Noise"));
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, [this](QAbstractButton* button) connect(ui->buttonBox, &QDialogButtonBox::clicked, this, [this](QAbstractButton* button)
@ -272,6 +262,27 @@ void pad_settings_dialog::showEvent(QShowEvent* event)
QDialog::showEvent(event); QDialog::showEvent(event);
} }
std::pair<QStringList, QString> pad_settings_dialog::get_config_files()
{
const QString input_config_dir = QString::fromStdString(rpcs3::utils::get_input_config_dir(m_title_id));
QStringList config_files = gui::utils::get_dir_entries(QDir(input_config_dir), QStringList() << "*.yml");
QString active_config_file = QString::fromStdString(g_cfg_input_configs.active_configs.get_value(g_cfg_input_configs.global_key));
if (!config_files.contains(active_config_file))
{
const QString default_config_file = QString::fromStdString(g_cfg_input_configs.default_config);
if (!config_files.contains(default_config_file) && CreateConfigFile(input_config_dir, default_config_file))
{
config_files.prepend(default_config_file);
}
active_config_file = default_config_file;
}
return std::make_pair<QStringList, QString>(std::move(config_files), std::move(active_config_file));
}
void pad_settings_dialog::InitButtons() void pad_settings_dialog::InitButtons()
{ {
m_pad_buttons = new QButtonGroup(this); m_pad_buttons = new QButtonGroup(this);
@ -321,6 +332,7 @@ void pad_settings_dialog::InitButtons()
m_pad_buttons->addButton(ui->b_refresh, button_ids::id_refresh); m_pad_buttons->addButton(ui->b_refresh, button_ids::id_refresh);
m_pad_buttons->addButton(ui->b_addConfig, button_ids::id_add_config_file); m_pad_buttons->addButton(ui->b_addConfig, button_ids::id_add_config_file);
m_pad_buttons->addButton(ui->b_remConfig, button_ids::id_remove_config_file);
connect(m_pad_buttons, &QButtonGroup::idClicked, this, &pad_settings_dialog::OnPadButtonClicked); connect(m_pad_buttons, &QButtonGroup::idClicked, this, &pad_settings_dialog::OnPadButtonClicked);
@ -1320,6 +1332,7 @@ void pad_settings_dialog::OnPadButtonClicked(int id)
case button_ids::id_pad_begin: case button_ids::id_pad_begin:
case button_ids::id_pad_end: case button_ids::id_pad_end:
case button_ids::id_add_config_file: case button_ids::id_add_config_file:
case button_ids::id_remove_config_file:
case button_ids::id_refresh: case button_ids::id_refresh:
return; return;
case button_ids::id_reset_parameters: case button_ids::id_reset_parameters:
@ -1636,6 +1649,8 @@ void pad_settings_dialog::ChangeConfig(const QString& config_file)
m_config_file = config_file.toStdString(); m_config_file = config_file.toStdString();
ui->b_remConfig->setEnabled(m_title_id.empty() && m_config_file != g_cfg_input_configs.default_config);
// Load in order to get the pad handlers // Load in order to get the pad handlers
if (!g_cfg_input.load(m_title_id, m_config_file, true)) if (!g_cfg_input.load(m_title_id, m_config_file, true))
{ {
@ -1810,6 +1825,44 @@ void pad_settings_dialog::AddConfigFile()
} }
} }
void pad_settings_dialog::RemoveConfigFile()
{
const std::string config_to_remove = m_config_file;
const QString q_config_to_remove = QString::fromStdString(config_to_remove);
if (config_to_remove == g_cfg_input_configs.default_config)
{
QMessageBox::warning(this, tr("Warning!"), tr("Can't remove default configuration '%0'.").arg(q_config_to_remove));
return;
}
if (QMessageBox::question(this, tr("Remove Configuration?"), tr("Do you really want to remove the configuration '%0'?").arg(q_config_to_remove)) != QMessageBox::StandardButton::Yes)
{
return;
}
const std::string filepath = fmt::format("%s%s.yml", rpcs3::utils::get_input_config_dir(m_title_id), config_to_remove);
if (!fs::remove_file(filepath))
{
QMessageBox::warning(this, tr("Warning!"), tr("Failed to remove '%0'.").arg(QString::fromStdString(filepath)));
return;
}
const auto [config_files, active_config_file] = get_config_files();
ui->chooseConfig->setCurrentText(active_config_file);
ui->chooseConfig->removeItem(ui->chooseConfig->findText(q_config_to_remove));
// Save new config if we removed the currently saved config
if (active_config_file == q_config_to_remove)
{
save(false);
}
QMessageBox::information(this, tr("Removed Configuration"), tr("Removed configuration '%0'.\nThe selected configuration is now '%1'.").arg(q_config_to_remove).arg(active_config_file));
}
void pad_settings_dialog::RefreshHandlers() void pad_settings_dialog::RefreshHandlers()
{ {
const u32 player_id = GetPlayerIndex(); const u32 player_id = GetPlayerIndex();
@ -1949,27 +2002,30 @@ void pad_settings_dialog::ApplyCurrentPlayerConfig(int new_player_id)
cfg.product_id.set(info.product_id); cfg.product_id.set(info.product_id);
} }
void pad_settings_dialog::SaveExit() void pad_settings_dialog::save(bool check_duplicates)
{ {
ApplyCurrentPlayerConfig(m_last_player_id); ApplyCurrentPlayerConfig(m_last_player_id);
for (const auto& [player_id, key] : m_duplicate_buttons) if (check_duplicates)
{ {
if (!key.empty()) for (const auto& [player_id, key] : m_duplicate_buttons)
{ {
int result = QMessageBox::Yes; if (!key.empty())
m_gui_settings->ShowConfirmationBox( {
tr("Warning!"), int result = QMessageBox::Yes;
tr("The %0 button <b>%1</b> of <b>Player %2</b> was assigned at least twice.<br>Please consider adjusting the configuration.<br><br>Continue anyway?<br>") m_gui_settings->ShowConfirmationBox(
.arg(QString::fromStdString(g_cfg_input.player[player_id]->handler.to_string())) tr("Warning!"),
.arg(QString::fromStdString(key)) tr("The %0 button <b>%1</b> of <b>Player %2</b> was assigned at least twice.<br>Please consider adjusting the configuration.<br><br>Continue anyway?<br>")
.arg(player_id + 1), .arg(QString::fromStdString(g_cfg_input.player[player_id]->handler.to_string()))
gui::ib_same_buttons, &result, this); .arg(QString::fromStdString(key))
.arg(player_id + 1),
gui::ib_same_buttons, &result, this);
if (result == QMessageBox::No) if (result == QMessageBox::No)
return; return;
break; break;
}
} }
} }
@ -1979,6 +2035,11 @@ void pad_settings_dialog::SaveExit()
g_cfg_input_configs.save(); g_cfg_input_configs.save();
g_cfg_input.save(m_title_id, m_config_file); g_cfg_input.save(m_title_id, m_config_file);
}
void pad_settings_dialog::SaveExit()
{
save(true);
QDialog::accept(); QDialog::accept();
} }

View file

@ -74,7 +74,8 @@ class pad_settings_dialog : public QDialog
id_reset_parameters, id_reset_parameters,
id_blacklist, id_blacklist,
id_refresh, id_refresh,
id_add_config_file id_add_config_file,
id_remove_config_file
}; };
struct pad_button struct pad_button
@ -101,6 +102,7 @@ private Q_SLOTS:
void ChangeDevice(int index); void ChangeDevice(int index);
void HandleDeviceClassChange(u32 class_id) const; void HandleDeviceClassChange(u32 class_id) const;
void AddConfigFile(); void AddConfigFile();
void RemoveConfigFile();
/** Update the current player config with the GUI values. */ /** Update the current player config with the GUI values. */
void ApplyCurrentPlayerConfig(int new_player_id); void ApplyCurrentPlayerConfig(int new_player_id);
void RefreshPads(); void RefreshPads();
@ -192,6 +194,9 @@ private:
void start_input_thread(); void start_input_thread();
void pause_input_thread(); void pause_input_thread();
std::pair<QStringList, QString> get_config_files();
void save(bool check_duplicates);
void SaveExit(); void SaveExit();
void CancelExit(); void CancelExit();

View file

@ -170,7 +170,17 @@
<item> <item>
<widget class="QPushButton" name="b_addConfig"> <widget class="QPushButton" name="b_addConfig">
<property name="text"> <property name="text">
<string>Add Configuration</string> <string>Add New</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="b_remConfig">
<property name="text">
<string>Remove</string>
</property> </property>
<property name="autoDefault"> <property name="autoDefault">
<bool>false</bool> <bool>false</bool>

View file

@ -90,6 +90,8 @@
<ClCompile Include="test_fmt.cpp" /> <ClCompile Include="test_fmt.cpp" />
<ClCompile Include="test_simple_array.cpp" /> <ClCompile Include="test_simple_array.cpp" />
<ClCompile Include="test_address_range.cpp" /> <ClCompile Include="test_address_range.cpp" />
<ClCompile Include="test_tuple.cpp" />
<ClCompile Include="test_pair.cpp" />
</ItemGroup> </ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets" Condition="'$(GTestInstalled)' == 'true'"> <ImportGroup Label="ExtensionTargets" Condition="'$(GTestInstalled)' == 'true'">

46
rpcs3/tests/test_pair.cpp Normal file
View file

@ -0,0 +1,46 @@
#include <gtest/gtest.h>
#include "util/types.hpp"
#include "util/pair.hpp"
struct some_struct
{
u64 v {};
char s[12] = "Hello World";
bool operator == (const some_struct& r) const
{
return v == r.v && std::memcmp(s, r.s, sizeof(s)) == 0;
}
};
TEST(Utils, Pair)
{
some_struct s {};
s.v = 1234;
utils::pair<int, some_struct> p;
EXPECT_EQ(sizeof(p), 32);
EXPECT_EQ(p.first, 0);
EXPECT_EQ(p.second, some_struct{});
p = { 666, s };
EXPECT_EQ(p.first, 666);
EXPECT_EQ(p.second, s);
const utils::pair<int, some_struct> p1 = p;
EXPECT_EQ(p.first, 666);
EXPECT_EQ(p.second, s);
EXPECT_EQ(p1.first, 666);
EXPECT_EQ(p1.second, s);
utils::pair<int, some_struct> p2 = p1;
EXPECT_EQ(p1.first, 666);
EXPECT_EQ(p1.second, s);
EXPECT_EQ(p2.first, 666);
EXPECT_EQ(p2.second, s);
utils::pair<int, some_struct> p3 = std::move(p);
EXPECT_EQ(p3.first, 666);
EXPECT_EQ(p3.second, s);
}

View file

@ -1,5 +1,7 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include "util/pair.hpp"
#define private public #define private public
#include "Emu/RSX/Common/simple_array.hpp" #include "Emu/RSX/Common/simple_array.hpp"
#undef private #undef private
@ -240,4 +242,29 @@ namespace rsx
EXPECT_EQ(sum, 15); EXPECT_EQ(sum, 15);
} }
TEST(SimpleArray, SimplePair)
{
struct some_struct
{
u64 v {};
char s[12] = "Hello World";
};
some_struct s {};
rsx::simple_array<utils::pair<int, some_struct>> arr;
for (int i = 0; i < 5; ++i)
{
s.v = i;
arr.push_back(utils::pair(i, s));
}
EXPECT_EQ(arr.size(), 5);
for (int i = 0; i < 5; ++i)
{
EXPECT_EQ(arr[i].first, i);
EXPECT_EQ(arr[i].second.v, i);
EXPECT_EQ(std::memcmp(arr[i].second.s, "Hello World", sizeof(arr[i].second.s)), 0);
}
}
} }

114
rpcs3/tests/test_tuple.cpp Normal file
View file

@ -0,0 +1,114 @@
#include <gtest/gtest.h>
#include "util/tuple.hpp"
struct some_struct
{
u64 v {};
char s[12] = "Hello World";
bool operator == (const some_struct& r) const
{
return v == r.v && std::memcmp(s, r.s, sizeof(s)) == 0;
}
};
TEST(Utils, Tuple)
{
some_struct s {};
s.v = 1234;
utils::tuple t0 = {};
EXPECT_EQ(t0.size(), 0);
utils::tuple<int> t;
EXPECT_EQ(sizeof(t), sizeof(int));
EXPECT_TRUE((std::is_same_v<decltype(t.get<0>()), int&>));
EXPECT_EQ(t.size(), 1);
EXPECT_EQ(t.get<0>(), 0);
utils::tuple<int> t1 = 2;
EXPECT_EQ(sizeof(t1), sizeof(int));
EXPECT_TRUE((std::is_same_v<decltype(t1.get<0>()), int&>));
EXPECT_EQ(t1.size(), 1);
EXPECT_EQ(t1.get<0>(), 2);
t1 = {};
EXPECT_EQ(t1.size(), 1);
EXPECT_EQ(t1.get<0>(), 0);
utils::tuple<int, some_struct> t2 = { 2, s };
EXPECT_EQ(sizeof(t2), 32);
EXPECT_EQ(t2.size(), 2);
EXPECT_TRUE((std::is_same_v<decltype(t2.get<0>()), int&>));
EXPECT_TRUE((std::is_same_v<decltype(t2.get<1>()), some_struct&>));
EXPECT_EQ(t2.get<0>(), 2);
EXPECT_EQ(t2.get<1>(), s);
t2 = {};
EXPECT_EQ(t2.size(), 2);
EXPECT_EQ(t2.get<0>(), 0);
EXPECT_EQ(t2.get<1>(), some_struct{});
t2.get<0>() = 666;
t2.get<1>() = s;
EXPECT_EQ(t2.get<0>(), 666);
EXPECT_EQ(t2.get<1>(), s);
utils::tuple<int, some_struct, double> t3 = { 2, s, 1234.0 };
EXPECT_EQ(sizeof(t3), 40);
EXPECT_EQ(t3.size(), 3);
EXPECT_TRUE((std::is_same_v<decltype(t3.get<0>()), int&>));
EXPECT_TRUE((std::is_same_v<decltype(t3.get<1>()), some_struct&>));
EXPECT_TRUE((std::is_same_v<decltype(t3.get<2>()), double&>));
EXPECT_EQ(t3.get<0>(), 2);
EXPECT_EQ(t3.get<1>(), s);
EXPECT_EQ(t3.get<2>(), 1234.0);
t3 = {};
EXPECT_EQ(t3.size(), 3);
EXPECT_EQ(t3.get<0>(), 0);
EXPECT_EQ(t3.get<1>(), some_struct{});
EXPECT_EQ(t3.get<2>(), 0.0);
t3.get<0>() = 666;
t3.get<1>() = s;
t3.get<2>() = 7.0;
EXPECT_EQ(t3.get<0>(), 666);
EXPECT_EQ(t3.get<1>(), s);
EXPECT_EQ(t3.get<2>(), 7.0);
// const
const utils::tuple<int, some_struct> tc = { 2, s };
EXPECT_EQ(tc.size(), 2);
EXPECT_TRUE((std::is_same_v<decltype(tc.get<0>()), const int&>));
EXPECT_TRUE((std::is_same_v<decltype(tc.get<1>()), const some_struct&>));
EXPECT_EQ(tc.get<0>(), 2);
EXPECT_EQ(tc.get<1>(), s);
// assignment
const utils::tuple<int, some_struct> ta1 = { 2, s };
utils::tuple<int, some_struct> ta = ta1;
EXPECT_EQ(ta.size(), 2);
EXPECT_TRUE((std::is_same_v<decltype(ta.get<0>()), int&>));
EXPECT_TRUE((std::is_same_v<decltype(ta.get<1>()), some_struct&>));
EXPECT_EQ(ta.get<0>(), 2);
EXPECT_EQ(ta.get<1>(), s);
utils::tuple<int, some_struct> ta2 = { 2, s };
ta = ta2;
EXPECT_EQ(ta.size(), 2);
EXPECT_TRUE((std::is_same_v<decltype(ta.get<0>()), int&>));
EXPECT_TRUE((std::is_same_v<decltype(ta.get<1>()), some_struct&>));
EXPECT_EQ(ta.get<0>(), 2);
EXPECT_EQ(ta.get<1>(), s);
EXPECT_EQ(ta2.size(), 2);
EXPECT_TRUE((std::is_same_v<decltype(ta2.get<0>()), int&>));
EXPECT_TRUE((std::is_same_v<decltype(ta2.get<1>()), some_struct&>));
EXPECT_EQ(ta2.get<0>(), 2);
EXPECT_EQ(ta2.get<1>(), s);
ta = std::move(ta2);
EXPECT_EQ(ta.size(), 2);
EXPECT_TRUE((std::is_same_v<decltype(ta.get<0>()), int&>));
EXPECT_TRUE((std::is_same_v<decltype(ta.get<1>()), some_struct&>));
EXPECT_EQ(ta.get<0>(), 2);
EXPECT_EQ(ta.get<1>(), s);
}

15
rpcs3/util/pair.hpp Normal file
View file

@ -0,0 +1,15 @@
#pragma once
#include <type_traits>
namespace utils
{
template <typename T1, typename T2>
requires std::is_trivially_copyable_v<T1> && std::is_trivially_destructible_v<T1> &&
std::is_trivially_copyable_v<T2> && std::is_trivially_destructible_v<T2>
struct pair
{
T1 first {};
T2 second {};
};
}

63
rpcs3/util/tuple.hpp Normal file
View file

@ -0,0 +1,63 @@
#pragma once
#include "types.hpp"
#include <type_traits>
#include <utility>
namespace utils
{
template <typename... Ts>
requires ((std::is_trivially_copyable_v<Ts> && std::is_trivially_destructible_v<Ts>) && ...)
struct tuple;
template <>
struct tuple<>
{
constexpr tuple() = default;
static constexpr usz size() noexcept { return 0; }
};
template <typename Head, typename... Tail>
struct tuple<Head, Tail...> : tuple<Tail...>
{
private:
Head head;
public:
constexpr tuple()
: tuple<Tail...>()
, head{}
{}
constexpr tuple(Head h, Tail... t)
: tuple<Tail...>(std::forward<Tail>(t)...)
, head(std::move(h))
{}
static constexpr usz size() noexcept
{
return 1 + sizeof...(Tail);
}
template <usz N>
requires (N < size())
constexpr auto& get()
{
if constexpr (N == 0)
return head;
else
return tuple<Tail...>::template get<N - 1>();
}
template <usz N>
requires (N < size())
constexpr const auto& get() const
{
if constexpr (N == 0)
return head;
else
return tuple<Tail...>::template get<N - 1>();
}
};
}