Merge branch 'master' into windows-clang

This commit is contained in:
Megamouse 2026-02-28 11:36:06 +01:00 committed by GitHub
commit 3bf182734f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
95 changed files with 1240 additions and 712 deletions

View file

@ -33,23 +33,23 @@ jobs:
matrix:
include:
- os: ubuntu-24.04
docker_img: "rpcs3/rpcs3-ci-jammy:1.8"
docker_img: "rpcs3/rpcs3-ci-jammy:1.9"
build_sh: "/rpcs3/.ci/build-linux.sh"
compiler: clang
UPLOAD_COMMIT_HASH: d812f1254a1157c80fd402f94446310560f54e5f
UPLOAD_REPO_FULL_NAME: "rpcs3/rpcs3-binaries-linux"
- os: ubuntu-24.04
docker_img: "rpcs3/rpcs3-ci-jammy:1.8"
docker_img: "rpcs3/rpcs3-ci-jammy:1.9"
build_sh: "/rpcs3/.ci/build-linux.sh"
compiler: gcc
- os: ubuntu-24.04-arm
docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.8"
docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.9"
build_sh: "/rpcs3/.ci/build-linux-aarch64.sh"
compiler: clang
UPLOAD_COMMIT_HASH: a1d35836e8d45bfc6f63c26f0a3e5d46ef622fe1
UPLOAD_REPO_FULL_NAME: "rpcs3/rpcs3-binaries-linux-arm64"
- os: ubuntu-24.04-arm
docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.8"
docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.9"
build_sh: "/rpcs3/.ci/build-linux-aarch64.sh"
compiler: gcc
name: RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }}

@ -1 +1 @@
Subproject commit 75c00596307bf05ba7bbc8c7022836bf52f17477
Subproject commit c41d64c6a35f6174bf4a27010aeac52a8d3bb2c6

@ -1 +1 @@
Subproject commit a962f40bbba175e9716557a25d5d7965f134a3d3
Subproject commit 683181b47cfabd293e3ea409f838915b8297a4fd

2
3rdparty/zlib/zlib vendored

@ -1 +1 @@
Subproject commit 51b7f2abdade71cd9bb0e7a373ef2610ec6f9daf
Subproject commit da607da739fa6047df13e66a2af6b8bec7c2a498

View file

@ -117,6 +117,7 @@ static fs::error to_error(DWORD e)
case ERROR_NEGATIVE_SEEK: return fs::error::inval;
case ERROR_DIRECTORY: return fs::error::inval;
case ERROR_INVALID_NAME: return fs::error::inval;
case ERROR_INVALID_FUNCTION: return fs::error::inval;
case ERROR_SHARING_VIOLATION: return fs::error::acces;
case ERROR_DIR_NOT_EMPTY: return fs::error::notempty;
case ERROR_NOT_READY: return fs::error::noent;
@ -584,7 +585,7 @@ namespace fs
if (!GetFileInformationByHandleEx(m_handle, FileIdInfo, &info, sizeof(info)))
{
// Try GetFileInformationByHandle as a fallback
BY_HANDLE_FILE_INFORMATION info2;
BY_HANDLE_FILE_INFORMATION info2{};
ensure(GetFileInformationByHandle(m_handle, &info2));
info = {};
@ -1658,11 +1659,40 @@ fs::file::file(const std::string& path, bs_t<open_mode> mode)
// Check if the handle is actually valid.
// This can fail on empty mounted drives (e.g. with ERROR_NOT_READY or ERROR_INVALID_FUNCTION).
BY_HANDLE_FILE_INFORMATION info;
BY_HANDLE_FILE_INFORMATION info{};
if (!GetFileInformationByHandle(handle, &info))
{
const DWORD last_error = GetLastError();
CloseHandle(handle);
g_tls_error = to_error(GetLastError());
if (last_error == ERROR_INVALID_FUNCTION)
{
g_tls_error = fs::error::isdir;
return;
}
g_tls_error = to_error(last_error);
return;
}
if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
CloseHandle(handle);
g_tls_error = fs::error::isdir;
return;
}
if (info.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
{
CloseHandle(handle);
g_tls_error = fs::error::acces;
return;
}
if ((mode & fs::write) && (info.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
{
CloseHandle(handle);
g_tls_error = fs::error::readonly;
return;
}
@ -2605,7 +2635,7 @@ bool fs::pending_file::commit(bool overwrite)
while (file_handle != INVALID_HANDLE_VALUE)
{
// Get file ID (used to check for hardlinks)
BY_HANDLE_FILE_INFORMATION file_info;
BY_HANDLE_FILE_INFORMATION file_info{};
if (!GetFileInformationByHandle(file_handle, &file_info) || file_info.nNumberOfLinks == 1)
{

View file

@ -13,13 +13,13 @@ std::string wchar_to_utf8(std::wstring_view src);
std::string utf16_to_utf8(std::u16string_view src);
std::u16string utf8_to_utf16(std::string_view src);
// Copy null-terminated string from a std::string or a char array to a char array with truncation
template <typename D, typename T>
// Copy null-terminated string from a std::basic_string or a char array to a char array with truncation
template <typename D, typename T> requires requires (D& d, T& t) { std::declval<decltype(&t[0])&>() = &d[0]; }
inline void strcpy_trunc(D&& dst, const T& src)
{
const usz count = std::size(src) >= std::size(dst) ? std::max<usz>(std::size(dst), 1) - 1 : std::size(src);
std::memcpy(std::data(dst), std::data(src), count);
std::memset(std::data(dst) + count, 0, std::size(dst) - count);
std::copy_n(std::data(src), count, std::data(dst));
std::fill_n(std::data(dst) + count, std::size(dst) - count, std::remove_cvref_t<decltype(dst[0])>{});
}
// Convert string to signed integer

View file

@ -821,6 +821,14 @@ struct color4_base
a *= rhs;
}
void operator += (const color4_base<T>& rhs)
{
r += rhs.r;
g += rhs.g;
b += rhs.b;
a += rhs.a;
}
constexpr color4_base<T> operator * (const color4_base<T>& rhs) const
{
return { r * rhs.r, g * rhs.g, b * rhs.b, a * rhs.a };

View file

@ -206,11 +206,7 @@ struct cpu_prof
// Print only 7 hash characters out of 11 (which covers roughly 48 bits)
if (type_id == 2)
{
fmt::append(results, "\n\t[%s", fmt::base57(be_t<u64>{name}));
results.resize(results.size() - 4);
// Print chunk address from lowest 16 bits
fmt::append(results, "...chunk-0x%05x]: %.4f%% (%u)", (name & 0xffff) * 4, _frac * 100., count);
fmt::append(results, "\n\t[%s]: %.4f%% (%u)", spu_block_hash{name}, _frac * 100., count);
}
else
{

View file

@ -1397,9 +1397,9 @@ struct SceNpBasicMessageDetails
// Presence details of an user
struct SceNpBasicPresenceDetails
{
s8 title[SCE_NP_BASIC_PRESENCE_TITLE_SIZE_MAX];
s8 status[SCE_NP_BASIC_PRESENCE_STATUS_SIZE_MAX];
s8 comment[SCE_NP_BASIC_PRESENCE_COMMENT_SIZE_MAX];
char title[SCE_NP_BASIC_PRESENCE_TITLE_SIZE_MAX];
char status[SCE_NP_BASIC_PRESENCE_STATUS_SIZE_MAX];
char comment[SCE_NP_BASIC_PRESENCE_COMMENT_SIZE_MAX];
u8 data[SCE_NP_BASIC_MAX_PRESENCE_SIZE];
be_t<u32> size;
be_t<s32> state;
@ -1410,9 +1410,9 @@ struct SceNpBasicPresenceDetails2
{
be_t<u32> struct_size;
be_t<s32> state;
s8 title[SCE_NP_BASIC_PRESENCE_TITLE_SIZE_MAX];
s8 status[SCE_NP_BASIC_PRESENCE_EXTENDED_STATUS_SIZE_MAX];
s8 comment[SCE_NP_BASIC_PRESENCE_COMMENT_SIZE_MAX];
char title[SCE_NP_BASIC_PRESENCE_TITLE_SIZE_MAX];
char status[SCE_NP_BASIC_PRESENCE_EXTENDED_STATUS_SIZE_MAX];
char comment[SCE_NP_BASIC_PRESENCE_COMMENT_SIZE_MAX];
u8 data[SCE_NP_BASIC_MAX_PRESENCE_SIZE];
be_t<u32> size;
};
@ -1420,9 +1420,9 @@ struct SceNpBasicPresenceDetails2
// Country/region code
struct SceNpCountryCode
{
s8 data[2];
s8 term;
s8 padding[1];
char data[2];
char term;
char padding[1];
};
// Date information
@ -1451,8 +1451,8 @@ struct SceNpScoreGameInfo
// Ranking comment structure
struct SceNpScoreComment
{
s8 data[SCE_NP_SCORE_COMMENT_MAXLEN];
s8 term[1];
char data[SCE_NP_SCORE_COMMENT_MAXLEN];
char term[1];
};
// Ranking information structure
@ -1524,15 +1524,15 @@ struct SceNpScoreNpIdPcId
// Basic clan information to be used in raking
struct SceNpScoreClanBasicInfo
{
s8 clanName[SCE_NP_CLANS_CLAN_NAME_MAX_LENGTH + 1];
s8 clanTag[SCE_NP_CLANS_CLAN_TAG_MAX_LENGTH + 1];
char clanName[SCE_NP_CLANS_CLAN_NAME_MAX_LENGTH + 1];
char clanTag[SCE_NP_CLANS_CLAN_TAG_MAX_LENGTH + 1];
u8 reserved[10];
};
// Clan member information handled in ranking
struct SceNpScoreClansMemberDescription
{
s8 description[SCE_NP_CLANS_CLAN_DESCRIPTION_MAX_LENGTH + 1];
char description[SCE_NP_CLANS_CLAN_DESCRIPTION_MAX_LENGTH + 1];
};
// Clan ranking information

View file

@ -330,7 +330,7 @@ void comment_constant(std::string& last_opcode, u64 value, bool print_float = fa
// Comment constant formation
fmt::append(last_opcode, " #0x%xh", value);
if (print_float && ((value >> 31) <= 1u || (value >> 31) == 0x1'ffff'ffffu))
if (print_float && ((value >> 31) <= 1u || (value >> 31) == 0x1'ffff'ffffu) && (value > 0x3fffff && (value << 32 >> 32) < 0xffc00000))
{
const f32 float_val = std::bit_cast<f32>(static_cast<u32>(value));

View file

@ -51,22 +51,22 @@ struct spu_itype
RDCH,
RCHCNT,
BR, // branch_tag first
BR, // branch_tag first, zregmod_tag (2) first
BRA,
BRNZ,
BRZ,
BRHNZ,
BRHZ,
BRSL,
BRASL,
IRET,
BI,
BISLED,
BISL,
BIZ,
BINZ,
BIHZ,
BIHNZ, // branch_tag last
BIHNZ, // zregmod_tag (2) last
BRSL,
BRASL,
BISL, // branch_tag last
ILH, // constant_tag_first
ILHU,
@ -245,7 +245,7 @@ struct spu_itype
// Test for branch instruction
friend constexpr bool operator &(type value, branch_tag)
{
return value >= BR && value <= BIHNZ;
return value >= BR && value <= BISL;
}
// Test for floating point instruction
@ -299,7 +299,7 @@ struct spu_itype
// Test for non register-modifying instruction
friend constexpr bool operator &(type value, zregmod_tag)
{
return value >= HEQ && value <= STQR;
return (value >= HEQ && value <= STQR) || (value >= BR && value <= BIHNZ);
}
};

View file

@ -1176,108 +1176,6 @@ void spu_cache::initialize(bool build_existing_cache)
if ((g_cfg.core.spu_decoder == spu_decoder_type::asmjit || g_cfg.core.spu_decoder == spu_decoder_type::llvm) && !func_list.empty())
{
spu_log.success("SPU Runtime: Built %u functions.", func_list.size());
if (g_cfg.core.spu_debug)
{
std::string dump;
dump.reserve(10'000'000);
std::map<std::span<u8>, spu_program*, span_less<u8>> sorted;
for (auto&& f : func_list)
{
// Interpret as a byte string
std::span<u8> data = {reinterpret_cast<u8*>(f.data.data()), f.data.size() * sizeof(u32)};
sorted[data] = &f;
}
std::unordered_set<u32> depth_n;
u32 n_max = 0;
for (auto&& [bytes, f] : sorted)
{
{
sha1_context ctx;
u8 output[20];
sha1_starts(&ctx);
sha1_update(&ctx, bytes.data(), bytes.size());
sha1_finish(&ctx, output);
fmt::append(dump, "\n\t[%s] ", fmt::base57(output));
}
u32 depth_m = 0;
for (auto&& [data, f2] : sorted)
{
u32 depth = 0;
if (f2 == f)
{
continue;
}
for (u32 i = 0; i < bytes.size(); i++)
{
if (i < data.size() && data[i] == bytes[i])
{
depth++;
}
else
{
break;
}
}
depth_n.emplace(depth);
depth_m = std::max(depth, depth_m);
}
fmt::append(dump, "c=%06d,d=%06d ", depth_n.size(), depth_m);
bool sk = false;
for (u32 i = 0; i < std::min<usz>(bytes.size(), std::max<usz>(256, depth_m)); i++)
{
if (depth_m == i)
{
dump += '|';
sk = true;
}
fmt::append(dump, "%02x", bytes[i]);
if (i % 4 == 3)
{
if (sk)
{
sk = false;
}
else
{
dump += ' ';
}
dump += ' ';
}
}
fmt::append(dump, "\n\t%49s", "");
for (u32 i = 0; i < std::min<usz>(f->data.size(), std::max<usz>(64, utils::aligned_div<u32>(depth_m, 4))); i++)
{
fmt::append(dump, "%-10s", g_spu_iname.decode(std::bit_cast<be_t<u32>>(f->data[i])));
}
n_max = std::max(n_max, ::size32(depth_n));
depth_n.clear();
}
spu_log.notice("SPU Cache Dump (max_c=%d): %s", n_max, dump);
}
}
// Initialize global cache instance
@ -3705,6 +3603,11 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
default:
{
if (type & spu_itype::zregmod)
{
break;
}
// Unconst
const u32 op_rt = type & spu_itype::_quadrop ? +op.rt4 : +op.rt;
m_regmod[pos / 4] = op_rt;
@ -7488,6 +7391,7 @@ void spu_recompiler_base::dump(const spu_program& result, std::string& out)
SPUDisAsm dis_asm(cpu_disasm_mode::dump, reinterpret_cast<const u8*>(result.data.data()), result.lower_bound);
std::string hash;
be_t<u64> hash_start{};
if (!result.data.empty())
{
@ -7498,6 +7402,7 @@ void spu_recompiler_base::dump(const spu_program& result, std::string& out)
sha1_update(&ctx, reinterpret_cast<const u8*>(result.data.data()), result.data.size() * 4);
sha1_finish(&ctx, output);
fmt::append(hash, "%s", fmt::base57(output));
std::memcpy(&hash_start, output, sizeof(hash_start));
}
else
{
@ -7510,7 +7415,7 @@ void spu_recompiler_base::dump(const spu_program& result, std::string& out)
{
if (m_block_info[bb.first / 4])
{
fmt::append(out, "A: [0x%05x] %s\n", bb.first, m_entry_info[bb.first / 4] ? (m_ret_info[bb.first / 4] ? "Chunk" : "Entry") : "Block");
fmt::append(out, "A: [0x%05x] %s [%s]\n", bb.first, m_entry_info[bb.first / 4] ? (m_ret_info[bb.first / 4] ? "Chunk" : "Entry") : "Block", spu_block_hash{(hash_start & -65536) + bb.first / 4});
fmt::append(out, "\t F: 0x%05x\n", bb.second.func);

View file

@ -903,8 +903,14 @@ public:
if (auto [is_const, value] = try_get_const_equal_value_array<u32>(+op.ra); is_const)
{
if (value % 0x200 != 0)
{
// si10 is overwritten - likely an analysis mistake
return;
}
// Comment constant formation
comment_constant(last_opcode, value | static_cast<u32>(op.si10));
comment_constant(last_opcode, value | static_cast<u32>(op.si10), false);
}
}
void ORHI(spu_opcode_t op)
@ -941,8 +947,14 @@ public:
if (auto [is_const, value] = try_get_const_equal_value_array<u32>(op.ra); is_const)
{
if (value % 0x200 != 0)
{
// si10 is overwritten - likely an analysis mistake
return;
}
// Comment constant formation
comment_constant(last_opcode, value + static_cast<u32>(op.si10));
comment_constant(last_opcode, value + static_cast<u32>(op.si10), false);
}
}
void AHI(spu_opcode_t op)
@ -963,8 +975,14 @@ public:
if (auto [is_const, value] = try_get_const_equal_value_array<u32>(op.ra); is_const)
{
if (value % 0x200 != 0)
{
// si10 is overwritten - likely an analysis mistake
return;
}
// Comment constant formation
comment_constant(last_opcode, value ^ static_cast<u32>(op.si10));
comment_constant(last_opcode, value ^ static_cast<u32>(op.si10), false);
}
}
void XORHI(spu_opcode_t op)

View file

@ -2160,6 +2160,14 @@ public:
}
}
if (bb.preds.size() >= 2)
{
if (g_cfg.core.spu_prof || g_cfg.core.spu_debug)
{
m_ir->CreateStore(m_ir->getInt64((m_hash_start & -65536) | (baddr >> 2)), spu_ptr(&spu_thread::block_hash));
}
}
// State check at the beginning of the chunk
if (need_check || (bi == 0 && g_cfg.core.spu_block_size != spu_block_size_type::safe))
{
@ -2802,12 +2810,9 @@ public:
std::string& llvm_log = function_log;
raw_string_ostream out(llvm_log);
if (g_cfg.core.spu_debug)
{
fmt::append(llvm_log, "LLVM IR at 0x%x:\n", func.entry_point);
out << *_module; // print IR
out << "\n\n";
}
fmt::append(llvm_log, "LLVM IR at 0x%x:\n", func.entry_point);
out << *_module; // print IR
out << "\n\n";
if (verifyModule(*_module, &out))
{
@ -3274,12 +3279,9 @@ public:
std::string llvm_log;
raw_string_ostream out(llvm_log);
if (g_cfg.core.spu_debug)
{
fmt::append(llvm_log, "LLVM IR (interpreter):\n");
out << *_module; // print IR
out << "\n\n";
}
fmt::append(llvm_log, "LLVM IR (interpreter):\n");
out << *_module; // print IR
out << "\n\n";
if (verifyModule(*_module, &out))
{

View file

@ -164,7 +164,7 @@ void fmt_class_string<spu_block_hash>::format(std::string& out, u64 arg)
out.resize(out.size() - 4);
// Print chunk address from lowest 16 bits
fmt::append(out, "...chunk-0x%05x", (arg & 0xffff) * 4);
fmt::append(out, "-0x%05x", (arg & 0xffff) * 4);
}
enum class spu_block_hash_short : u64{};

View file

@ -59,6 +59,7 @@ CellError lv2_cond::on_id_create()
if (!mutex)
{
_mutex = static_cast<shared_ptr<lv2_obj>>(ensure(idm::get_unlocked<lv2_obj, lv2_mutex>(mtx_id)));
mutex = static_cast<lv2_mutex*>(_mutex.get());
}
// Defer function

View file

@ -174,20 +174,28 @@ bool lv2_config_service_listener::check_service(const lv2_config_service& servic
return true;
}
bool lv2_config_service_listener::notify(const shared_ptr<lv2_config_service_event>& event)
{
service_events.emplace_back(event);
return event->notify();
}
bool lv2_config_service_listener::notify(const shared_ptr<lv2_config_service>& service)
{
if (!check_service(*service))
return false;
{
std::lock_guard lock(mutex_service_events);
// Create service event and notify queue!
const auto event = lv2_config_service_event::create(handle, service, *this);
return notify(event);
if (!check_service(*service))
return false;
// Create service event and notify queue!
const auto event = lv2_config_service_event::create(handle, service, *this);
service_events.emplace_back(event);
if (!event->notify())
{
// If we fail to deliver the event to the queue just clean the event up or it'll hold the listener alive forever
g_fxo->get<lv2_config>().remove_service_event(event->id);
service_events.pop_back();
return false;
}
}
return true;
}
void lv2_config_service_listener::notify_all()
@ -267,7 +275,7 @@ void lv2_config_service_event::write(sys_config_service_event_t *dst) const
{
const auto registered = service->is_registered();
dst->service_listener_handle = listener.get_id();
dst->service_listener_handle = listener_id;
dst->registered = registered;
dst->service_id = service->id;
dst->user_id = service->user_id;
@ -346,7 +354,7 @@ error_code sys_config_get_service_event(u32 config_hdl, u32 event_id, vm::ptr<sy
// Find service_event object
const auto event = g_fxo->get<lv2_config>().find_event(event_id);
if (!event)
if (!event || event->handle != cfg)
{
return CELL_ESRCH;
}

View file

@ -296,11 +296,10 @@ private:
// The service listener owns the service events - service events will not be freed as long as their corresponding listener exists
// This has been confirmed to be the case in realhw
shared_mutex mutex_service_events;
std::vector<shared_ptr<lv2_config_service_event>> service_events;
shared_ptr<lv2_config_handle> handle;
bool notify(const shared_ptr<lv2_config_service_event>& event);
public:
const sys_config_service_id service_id;
const u64 min_verbosity;
@ -370,14 +369,14 @@ public:
// This has been confirmed to be the case in realhw
const shared_ptr<lv2_config_handle> handle;
const shared_ptr<lv2_config_service> service;
const lv2_config_service_listener& listener;
const u32 listener_id;
// Constructors (should not be used directly)
lv2_config_service_event(shared_ptr<lv2_config_handle> _handle, shared_ptr<lv2_config_service> _service, const lv2_config_service_listener& _listener) noexcept
: id(get_next_id())
, handle(std::move(_handle))
, service(std::move(_service))
, listener(_listener)
, listener_id(_listener.get_id())
{
}

View file

@ -105,7 +105,7 @@ error_code sys_dbg_write_process_memory(s32 pid, u32 address, u32 size, vm::cptr
i += op_size;
}
if (!is_exec || i >= end)
if ((!is_exec || i >= end) && exec_update_size > 0)
{
// Commit executable data update
// The read memory is also super ptr so memmove can work correctly on all implementations

View file

@ -170,8 +170,7 @@ CellError lv2_event_queue::send(lv2_event event, bool* notified_thread, lv2_even
{
if (auto cpu = get_current_cpu_thread())
{
cpu->state += cpu_flag::again;
cpu->state += cpu_flag::exit;
cpu->state += cpu_flag::again + cpu_flag::exit;
}
sys_event.warning("Ignored event!");
@ -309,6 +308,15 @@ error_code sys_event_queue_destroy(ppu_thread& ppu, u32 equeue_id, s32 mode)
return CELL_EBUSY;
}
for (auto cpu = head; cpu; cpu = cpu->get_next_cpu())
{
if (cpu->state & cpu_flag::again)
{
ppu.state += cpu_flag::again;
return CELL_EAGAIN;
}
}
if (!queue.events.empty())
{
// Copy events for logging, does not empty
@ -321,17 +329,6 @@ error_code sys_event_queue_destroy(ppu_thread& ppu, u32 equeue_id, s32 mode)
{
qlock.unlock();
}
else
{
for (auto cpu = head; cpu; cpu = cpu->get_next_cpu())
{
if (cpu->state & cpu_flag::again)
{
ppu.state += cpu_flag::again;
return CELL_EAGAIN;
}
}
}
return {};
});
@ -621,7 +618,7 @@ error_code sys_event_port_create(cpu_thread& cpu, vm::ptr<u32> eport_id, s32 por
sys_event.warning("sys_event_port_create(eport_id=*0x%x, port_type=%d, name=0x%llx)", eport_id, port_type, name);
if (port_type != SYS_EVENT_PORT_LOCAL && port_type != 3)
if (port_type != SYS_EVENT_PORT_LOCAL && port_type != SYS_EVENT_PORT_IPC)
{
sys_event.error("sys_event_port_create(): unknown port type (%d)", port_type);
return CELL_EINVAL;
@ -675,8 +672,9 @@ error_code sys_event_port_connect_local(cpu_thread& cpu, u32 eport_id, u32 equeu
std::lock_guard lock(id_manager::g_mutex);
const auto port = idm::check_unlocked<lv2_obj, lv2_event_port>(eport_id);
auto queue = idm::get_unlocked<lv2_obj, lv2_event_queue>(equeue_id);
if (!port || !idm::check_unlocked<lv2_obj, lv2_event_queue>(equeue_id))
if (!port || !queue)
{
return CELL_ESRCH;
}
@ -691,7 +689,7 @@ error_code sys_event_port_connect_local(cpu_thread& cpu, u32 eport_id, u32 equeu
return CELL_EISCONN;
}
port->queue = idm::get_unlocked<lv2_obj, lv2_event_queue>(equeue_id);
port->queue = std::move(queue);
return CELL_OK;
}

View file

@ -7,7 +7,6 @@
#include <deque>
class cpu_thread;
class spu_thrread;
// Event Queue Type
enum : u32

View file

@ -19,6 +19,22 @@ lv2_event_flag::lv2_event_flag(utils::serial& ar)
ar(pattern);
}
// Always set result
struct sys_event_store_result
{
vm::ptr<u64> ptr;
u64 val = 0;
~sys_event_store_result() noexcept
{
if (ptr)
{
cpu_thread::get_current()->check_state();
*ptr = val;
}
}
};
std::function<void(void*)> lv2_event_flag::load(utils::serial& ar)
{
return load_func(make_shared<lv2_event_flag>(stx::exact_t<utils::serial&>(ar)));
@ -120,21 +136,7 @@ error_code sys_event_flag_wait(ppu_thread& ppu, u32 id, u64 bitptn, u32 mode, vm
ppu.gpr[5] = mode;
ppu.gpr[6] = 0;
// Always set result
struct store_result
{
vm::ptr<u64> ptr;
u64 val = 0;
~store_result() noexcept
{
if (ptr)
{
cpu_thread::get_current()->check_state();
*ptr = val;
}
}
} store{result};
sys_event_store_result store{result};
if (!lv2_event_flag::check_mode(mode))
{
@ -273,21 +275,7 @@ error_code sys_event_flag_trywait(ppu_thread& ppu, u32 id, u64 bitptn, u32 mode,
sys_event_flag.trace("sys_event_flag_trywait(id=0x%x, bitptn=0x%llx, mode=0x%x, result=*0x%x)", id, bitptn, mode, result);
// Always set result
struct store_result
{
vm::ptr<u64> ptr;
u64 val = 0;
~store_result() noexcept
{
if (ptr)
{
cpu_thread::get_current()->check_state();
*ptr = val;
}
}
} store{result};
sys_event_store_result store{result};
if (!lv2_event_flag::check_mode(mode))
{
@ -556,8 +544,6 @@ error_code sys_event_flag_get(ppu_thread& ppu, u32 id, vm::ptr<u64> flags)
return +flag.pattern;
});
ppu.check_state();
if (!flag)
{
if (flags) *flags = 0;
@ -569,6 +555,8 @@ error_code sys_event_flag_get(ppu_thread& ppu, u32 id, vm::ptr<u64> flags)
return CELL_EFAULT;
}
ppu.check_state();
*flags = flag.ret;
return CELL_OK;
}

View file

@ -382,10 +382,12 @@ lv2_fs_object::lv2_fs_object(utils::serial& ar, bool)
u64 lv2_file::op_read(const fs::file& file, vm::ptr<void> buf, u64 size, u64 opt_pos)
{
if (u64 region = buf.addr() >> 28, region_end = (buf.addr() & 0xfff'ffff) + (size & 0xfff'ffff); region == region_end && ((region >> 28) == 0 || region >= 0xC))
if (u64 region = buf.addr() >> 28, region_end = (buf.addr() + size) >> 28;
size < u32{umax} && region == region_end && (region == 0 || region == 0xD) && vm::check_addr(buf.addr(), vm::page_writable, static_cast<u32>(size)))
{
// Optimize reads from safe memory
return (opt_pos == umax ? file.read(buf.get_ptr(), size) : file.read_at(opt_pos, buf.get_ptr(), size));
const auto buf_ptr = vm::get_super_ptr(buf.addr());
return (opt_pos == umax ? file.read(buf_ptr, size) : file.read_at(opt_pos, buf_ptr, size));
}
// Copy data from intermediate buffer (avoid passing vm pointer to a native API)
@ -412,6 +414,14 @@ u64 lv2_file::op_read(const fs::file& file, vm::ptr<void> buf, u64 size, u64 opt
u64 lv2_file::op_write(const fs::file& file, vm::cptr<void> buf, u64 size)
{
if (u64 region = buf.addr() >> 28, region_end = (buf.addr() + size) >> 28;
size < u32{umax} && region == region_end && (region == 0 || region == 0xD) && vm::check_addr(buf.addr(), vm::page_readable, static_cast<u32>(size)))
{
// Optimize writes from safe memory
const auto buf_ptr = vm::get_super_ptr(buf.addr());
return file.write(buf_ptr, size);
}
// Copy data to intermediate buffer (avoid passing vm pointer to a native API)
std::vector<uchar> local_buf(std::min<u64>(size, 65536));
@ -1391,7 +1401,8 @@ error_code sys_fs_opendir(ppu_thread& ppu, vm::cptr<char> path, vm::ptr<u32> fd)
// Add additional entries for split file candidates (while ends with .66600)
while (mp.mp != &g_mp_sys_dev_hdd1 && data.back().name.ends_with(".66600"))
{
data.emplace_back(data.back()).name.resize(data.back().name.size() - 6);
fs::dir_entry copy = data.back();
data.emplace_back(copy).name.resize(copy.name.size() - 6);
}
}
@ -2147,6 +2158,7 @@ error_code sys_fs_fcntl(ppu_thread& ppu, u32 fd, u32 op, vm::ptr<void> _arg, u32
sys_fs.notice("sys_fs_fcntl(0xc0000006): %s", vpath);
// Check only mountpoint
vpath = vpath.substr(0, vpath.find_first_of('\0'));
vpath = vpath.substr(0, vpath.find_first_of("/", 1));
// Some mountpoints seem to be handled specially
@ -2635,8 +2647,6 @@ error_code sys_fs_lseek(ppu_thread& ppu, u32 fd, s64 offset, s32 whence, vm::ptr
error_code sys_fs_fdatasync(ppu_thread& ppu, u32 fd)
{
lv2_obj::sleep(ppu);
sys_fs.trace("sys_fs_fdadasync(fd=%d)", fd);
const auto file = idm::get_unlocked<lv2_fs_object, lv2_file>(fd);
@ -2661,8 +2671,6 @@ error_code sys_fs_fdatasync(ppu_thread& ppu, u32 fd)
error_code sys_fs_fsync(ppu_thread& ppu, u32 fd)
{
lv2_obj::sleep(ppu);
sys_fs.trace("sys_fs_fsync(fd=%d)", fd);
const auto file = idm::get_unlocked<lv2_fs_object, lv2_file>(fd);
@ -2903,14 +2911,6 @@ error_code sys_fs_chmod(ppu_thread&, vm::cptr<char> path, s32 mode)
{
// Try to locate split files
for (u32 i = 66601; i <= 66699; i++)
{
if (mp != &g_mp_sys_dev_hdd1 && !fs::get_stat(fmt::format("%s.%u", local_path, i), info) && !info.is_directory)
{
break;
}
}
if (fs::get_stat(local_path + ".66600", info) && !info.is_directory)
{
break;

View file

@ -487,6 +487,8 @@ error_code _sys_lwcond_queue_wait(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id
{
ensure(cond.unqueue(cond.sq, &ppu));
ppu.state += cpu_flag::again;
cond.lwmutex_waiters--;
mutex->lwcond_waiters--;
return;
}

View file

@ -333,7 +333,7 @@ error_code sys_mmapper_allocate_shared_memory_ext(ppu_thread& ppu, u64 ipc_key,
}
}
if (flags & ~SYS_MEMORY_PAGE_SIZE_MASK)
if (flags & ~SYS_MEMORY_GRANULARITY_MASK)
{
return CELL_EINVAL;
}
@ -401,6 +401,11 @@ error_code sys_mmapper_allocate_shared_memory_from_container_ext(ppu_thread& ppu
sys_mmapper.todo("sys_mmapper_allocate_shared_memory_from_container_ext(ipc_key=0x%x, size=0x%x, flags=0x%x, cid=0x%x, entries=*0x%x, entry_count=0x%x, mem_id=*0x%x)", ipc_key, size, flags, cid, entries,
entry_count, mem_id);
if (size == 0)
{
return CELL_EALIGN;
}
switch (flags & SYS_MEMORY_PAGE_SIZE_MASK)
{
case SYS_MEMORY_PAGE_SIZE_1M:
@ -546,8 +551,7 @@ error_code sys_mmapper_free_address(ppu_thread& ppu, u32 addr)
// If a memory block is freed, remove it from page notification table.
auto& pf_entries = g_fxo->get<page_fault_notification_entries>();
std::lock_guard lock(pf_entries.mutex);
std::unique_lock lock(pf_entries.mutex);
auto ind_to_remove = pf_entries.entries.begin();
for (; ind_to_remove != pf_entries.entries.end(); ++ind_to_remove)
{
@ -558,7 +562,11 @@ error_code sys_mmapper_free_address(ppu_thread& ppu, u32 addr)
}
if (ind_to_remove != pf_entries.entries.end())
{
u32 port_id = ind_to_remove->port_id;
pf_entries.entries.erase(ind_to_remove);
lock.unlock();
sys_event_port_disconnect(ppu, port_id);
sys_event_port_destroy(ppu, port_id);
}
return CELL_OK;
@ -826,7 +834,6 @@ error_code sys_mmapper_enable_page_fault_notification(ppu_thread& ppu, u32 start
vm::var<u32> port_id(0);
error_code res = sys_event_port_create(ppu, port_id, SYS_EVENT_PORT_LOCAL, SYS_MEMORY_PAGE_FAULT_EVENT_KEY);
sys_event_port_connect_local(ppu, *port_id, event_queue_id);
if (res + 0u == CELL_EAGAIN)
{
@ -834,6 +841,8 @@ error_code sys_mmapper_enable_page_fault_notification(ppu_thread& ppu, u32 start
return CELL_EAGAIN;
}
sys_event_port_connect_local(ppu, *port_id, event_queue_id);
auto& pf_entries = g_fxo->get<page_fault_notification_entries>();
std::unique_lock lock(pf_entries.mutex);

View file

@ -85,7 +85,7 @@ error_code sys_mutex_create(ppu_thread& ppu, vm::ptr<u32> mutex_id, vm::ptr<sys_
sys_mutex.todo("sys_mutex_create(): unexpected adaptive (0x%x)", _attr.adaptive);
}
if (auto error = lv2_obj::create<lv2_mutex>(_attr.pshared, _attr.ipc_key, _attr.flags, [&]()
if (auto error = lv2_obj::create<lv2_mutex>(_attr.pshared, ipc_key, _attr.flags, [&]()
{
return make_shared<lv2_mutex>(
_attr.protocol,

View file

@ -173,7 +173,11 @@ struct lv2_mutex final : lv2_obj
if (sq == data.sq)
{
atomic_storage<u32>::release(control.raw().owner, res->id);
if (cpu_flag::again - res->state)
{
atomic_storage<u32>::release(control.raw().owner, res->id);
}
return false;
}

View file

@ -563,37 +563,34 @@ error_code sys_net_bnet_connect(ppu_thread& ppu, s32 s, vm::ptr<sys_net_sockaddr
return not_an_error(result);
}
if (!sock.ret)
while (auto state = ppu.state.fetch_sub(cpu_flag::signal))
{
while (auto state = ppu.state.fetch_sub(cpu_flag::signal))
if (is_stopped(state))
{
if (is_stopped(state))
{
return {};
}
if (state & cpu_flag::signal)
{
break;
}
ppu.state.wait(state);
return {};
}
if (ppu.gpr[3] == static_cast<u64>(-SYS_NET_EINTR))
if (state & cpu_flag::signal)
{
return -SYS_NET_EINTR;
break;
}
if (result)
{
if (result < 0)
{
return sys_net_error{result};
}
ppu.state.wait(state);
}
return not_an_error(result);
if (ppu.gpr[3] == static_cast<u64>(-SYS_NET_EINTR))
{
return -SYS_NET_EINTR;
}
if (result)
{
if (result < 0)
{
return sys_net_error{result};
}
return not_an_error(result);
}
return CELL_OK;
@ -1295,7 +1292,7 @@ error_code sys_net_bnet_poll(ppu_thread& ppu, vm::ptr<sys_net_pollfd> fds, s32 n
if (auto sock = idm::check_unlocked<lv2_socket>(fds_buf[i].fd))
{
signaled += sock->poll(fds_buf[i], _fds[i]);
sock->poll(fds_buf[i], _fds[i]);
#ifdef _WIN32
connecting[i] = sock->is_connecting();
#endif
@ -1303,7 +1300,6 @@ error_code sys_net_bnet_poll(ppu_thread& ppu, vm::ptr<sys_net_pollfd> fds, s32 n
else
{
fds_buf[i].revents |= SYS_NET_POLLNVAL;
signaled++;
}
}
@ -1536,9 +1532,9 @@ error_code sys_net_bnet_select(ppu_thread& ppu, s32 nfds, vm::ptr<sys_net_fd_set
for (s32 i = 0; i < nfds; i++)
{
bool sig = false;
if (_fds[i].revents & (POLLIN | POLLHUP | POLLERR))
if ((_fds[i].revents & (POLLIN | POLLHUP | POLLERR)) && _readfds.bit(i))
sig = true, rread.set(i);
if (_fds[i].revents & (POLLOUT | POLLERR))
if ((_fds[i].revents & (POLLOUT | POLLERR)) && _writefds.bit(i))
sig = true, rwrite.set(i);
if (sig)

View file

@ -104,7 +104,7 @@ public:
virtual void close() = 0;
virtual s32 shutdown(s32 how) = 0;
virtual s32 poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) = 0;
virtual void poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) = 0;
virtual std::tuple<bool, bool, bool> select(bs_t<poll_t> selected, pollfd& native_pfd) = 0;
error_code abort_socket(s32 flags);

View file

@ -1147,14 +1147,14 @@ s32 lv2_socket_native::shutdown(s32 how)
return -get_last_error(false);
}
s32 lv2_socket_native::poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd)
void lv2_socket_native::poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd)
{
// Check for fake packet for dns interceptions
auto& dnshook = g_fxo->get<np::dnshook>();
if (sn_pfd.events & SYS_NET_POLLIN && dnshook.is_dns(sn_pfd.fd) && dnshook.is_dns_queue(sn_pfd.fd))
{
sn_pfd.revents |= SYS_NET_POLLIN;
return 1;
return;
}
if (sn_pfd.events & ~(SYS_NET_POLLIN | SYS_NET_POLLOUT | SYS_NET_POLLERR))
{
@ -1171,8 +1171,6 @@ s32 lv2_socket_native::poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd)
{
native_pfd.events |= POLLOUT;
}
return 0;
}
std::tuple<bool, bool, bool> lv2_socket_native::select(bs_t<lv2_socket::poll_t> selected, pollfd& native_pfd)

View file

@ -50,7 +50,7 @@ public:
std::optional<s32> sendto(s32 flags, const std::vector<u8>& buf, std::optional<sys_net_sockaddr> opt_sn_addr, bool is_lock = true) override;
std::optional<s32> sendmsg(s32 flags, const sys_net_msghdr& msg, bool is_lock = true) override;
s32 poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override;
void poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override;
std::tuple<bool, bool, bool> select(bs_t<poll_t> selected, pollfd& native_pfd) override;
bool is_socket_connected();

View file

@ -364,7 +364,7 @@ s32 lv2_socket_p2p::shutdown([[maybe_unused]] s32 how)
return CELL_OK;
}
s32 lv2_socket_p2p::poll(sys_net_pollfd& sn_pfd, [[maybe_unused]] pollfd& native_pfd)
void lv2_socket_p2p::poll(sys_net_pollfd& sn_pfd, [[maybe_unused]] pollfd& native_pfd)
{
std::lock_guard lock(mutex);
ensure(vport);
@ -381,8 +381,6 @@ s32 lv2_socket_p2p::poll(sys_net_pollfd& sn_pfd, [[maybe_unused]] pollfd& native
{
sn_pfd.revents |= SYS_NET_POLLOUT;
}
return sn_pfd.revents ? 1 : 0;
}
std::tuple<bool, bool, bool> lv2_socket_p2p::select(bs_t<lv2_socket::poll_t> selected, [[maybe_unused]] pollfd& native_pfd)

View file

@ -30,7 +30,7 @@ public:
void close() override;
s32 shutdown(s32 how) override;
s32 poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override;
void poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override;
std::tuple<bool, bool, bool> select(bs_t<poll_t> selected, pollfd& native_pfd) override;
void handle_new_data(sys_net_sockaddr_in_p2p p2p_addr, std::vector<u8> p2p_data);

View file

@ -985,7 +985,7 @@ s32 lv2_socket_p2ps::shutdown([[maybe_unused]] s32 how)
return CELL_OK;
}
s32 lv2_socket_p2ps::poll(sys_net_pollfd& sn_pfd, [[maybe_unused]] pollfd& native_pfd)
void lv2_socket_p2ps::poll(sys_net_pollfd& sn_pfd, [[maybe_unused]] pollfd& native_pfd)
{
std::lock_guard lock(mutex);
sys_net.trace("[P2PS] poll checking for 0x%X", sn_pfd.events);
@ -1002,14 +1002,7 @@ s32 lv2_socket_p2ps::poll(sys_net_pollfd& sn_pfd, [[maybe_unused]] pollfd& nativ
{
sn_pfd.revents |= SYS_NET_POLLOUT;
}
if (sn_pfd.revents)
{
return 1;
}
}
return 0;
}
std::tuple<bool, bool, bool> lv2_socket_p2ps::select(bs_t<lv2_socket::poll_t> selected, [[maybe_unused]] pollfd& native_pfd)

View file

@ -89,7 +89,7 @@ public:
void close() override;
s32 shutdown(s32 how) override;
s32 poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override;
void poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override;
std::tuple<bool, bool, bool> select(bs_t<poll_t> selected, pollfd& native_pfd) override;
private:

View file

@ -134,10 +134,9 @@ s32 lv2_socket_raw::shutdown([[maybe_unused]] s32 how)
return {};
}
s32 lv2_socket_raw::poll([[maybe_unused]] sys_net_pollfd& sn_pfd, [[maybe_unused]] pollfd& native_pfd)
void lv2_socket_raw::poll([[maybe_unused]] sys_net_pollfd& sn_pfd, [[maybe_unused]] pollfd& native_pfd)
{
LOG_ONCE(raw_poll, "lv2_socket_raw::poll");
return {};
}
std::tuple<bool, bool, bool> lv2_socket_raw::select([[maybe_unused]] bs_t<lv2_socket::poll_t> selected, [[maybe_unused]] pollfd& native_pfd)

View file

@ -32,6 +32,6 @@ public:
void close() override;
s32 shutdown(s32 how) override;
s32 poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override;
void poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override;
std::tuple<bool, bool, bool> select(bs_t<poll_t> selected, pollfd& native_pfd) override;
};

View file

@ -305,6 +305,7 @@ error_code sys_ppu_thread_detach(ppu_thread& ppu, u32 thread_id)
{
// Join and notify thread (it is detached from IDM now so it must be done explicitly now)
*ptr = thread_state::finished;
return CELL_OK;
}
return result;

View file

@ -899,7 +899,7 @@ error_code _sys_prx_register_library(ppu_thread& ppu, vm::ptr<void> library)
{
for (u32 lib_addr = prx.exports_start, index = 0; lib_addr < prx.exports_end; index++, lib_addr += vm::read8(lib_addr) ? vm::read8(lib_addr) : sizeof_lib)
{
if (std::memcpy(vm::base(lib_addr), mem_copy.data(), sizeof_lib) == 0)
if (std::memcmp(vm::base(lib_addr), mem_copy.data(), sizeof_lib) == 0)
{
atomic_storage<char>::release(prx.m_external_loaded_flags[index], true);
return true;

View file

@ -46,14 +46,14 @@ namespace rsxaudio_ringbuf_reader
static void set_timestamp(rsxaudio_shmem::ringbuf_t& ring_buf, u64 timestamp)
{
const s32 entry_idx_raw = (ring_buf.read_idx + ring_buf.rw_max_idx - (ring_buf.rw_max_idx > 2) - 1) % ring_buf.rw_max_idx;
const s32 entry_idx = std::clamp<s32>(entry_idx_raw, 0, SYS_RSXAUDIO_RINGBUF_SZ);
const s32 entry_idx = std::clamp<s32>(entry_idx_raw, 0, SYS_RSXAUDIO_RINGBUF_SZ - 1);
ring_buf.entries[entry_idx].timestamp = convert_to_timebased_time(timestamp);
}
static std::tuple<bool /*notify*/, u64 /*blk_idx*/, u64 /*timestamp*/> update_status(rsxaudio_shmem::ringbuf_t& ring_buf)
{
const s32 read_idx = std::clamp<s32>(ring_buf.read_idx, 0, SYS_RSXAUDIO_RINGBUF_SZ);
const s32 read_idx = std::clamp<s32>(ring_buf.read_idx, 0, SYS_RSXAUDIO_RINGBUF_SZ - 1);
if ((ring_buf.entries[read_idx].valid & 1) == 0U)
{
@ -61,7 +61,7 @@ namespace rsxaudio_ringbuf_reader
}
const s32 entry_idx_raw = (ring_buf.read_idx + ring_buf.rw_max_idx - (ring_buf.rw_max_idx > 2)) % ring_buf.rw_max_idx;
const s32 entry_idx = std::clamp<s32>(entry_idx_raw, 0, SYS_RSXAUDIO_RINGBUF_SZ);
const s32 entry_idx = std::clamp<s32>(entry_idx_raw, 0, SYS_RSXAUDIO_RINGBUF_SZ - 1);
ring_buf.entries[read_idx].valid = 0;
ring_buf.queue_notify_idx = (ring_buf.queue_notify_idx + 1) % ring_buf.queue_notify_step;
@ -72,7 +72,7 @@ namespace rsxaudio_ringbuf_reader
static std::pair<bool /*entry_valid*/, u32 /*addr*/> get_addr(const rsxaudio_shmem::ringbuf_t& ring_buf)
{
const s32 read_idx = std::clamp<s32>(ring_buf.read_idx, 0, SYS_RSXAUDIO_RINGBUF_SZ);
const s32 read_idx = std::clamp<s32>(ring_buf.read_idx, 0, SYS_RSXAUDIO_RINGBUF_SZ - 1);
if (ring_buf.entries[read_idx].valid & 1)
{
@ -1392,9 +1392,9 @@ void rsxaudio_backend_thread::operator()()
return;
}
static rsxaudio_state ra_state{};
static emu_audio_cfg emu_cfg{};
static bool backend_failed = false;
rsxaudio_state ra_state{};
emu_audio_cfg emu_cfg{};
bool backend_failed = false;
for (;;)
{
@ -2018,7 +2018,7 @@ void rsxaudio_periodic_tmr::cancel_timer_unlocked()
{
const u64 flag = 1;
const auto wr_res = write(cancel_event, &flag, sizeof(flag));
ensure(wr_res == sizeof(flag) || wr_res == -EAGAIN);
ensure(wr_res == sizeof(flag) || errno == EAGAIN);
}
#elif defined(BSD) || defined(__APPLE__)
handle[TIMER_ID].flags = (handle[TIMER_ID].flags & ~EV_ENABLE) | EV_DISABLE;

View file

@ -441,6 +441,8 @@ error_code sys_rwlock_wlock(ppu_thread& ppu, u32 rw_lock_id, u64 timeout)
continue;
}
ppu.state += cpu_flag::wait;
std::lock_guard lock(rwlock->mutex);
if (!rwlock->unqueue(rwlock->wq, &ppu))

View file

@ -72,7 +72,7 @@ error_code sys_semaphore_create(ppu_thread& ppu, vm::ptr<u32> sem_id, vm::ptr<sy
return error;
}
static_cast<void>(ppu.test_stopped());
ppu.check_state();
*sem_id = idm::last_id();
return CELL_OK;
@ -358,7 +358,7 @@ error_code sys_semaphore_get_value(ppu_thread& ppu, u32 sem_id, vm::ptr<s32> cou
return CELL_EFAULT;
}
static_cast<void>(ppu.test_stopped());
ppu.check_state();
*count = sema.ret;
return CELL_OK;

View file

@ -437,7 +437,7 @@ struct spu_limits_t
raw_spu_count += spu_thread::g_raw_spu_ctr;
// physical_spus_count >= spu_limit returns EBUSY, not EINVAL!
if (spu_limit + raw_limit > 6 || raw_spu_count > raw_limit || physical_spus_count >= spu_limit || physical_spus_count > spu_limit || controllable_spu_count > spu_limit)
if (spu_limit + raw_limit > 6 || raw_spu_count > raw_limit || physical_spus_count >= spu_limit || controllable_spu_count > spu_limit)
{
return false;
}

View file

@ -24,7 +24,7 @@ struct lv2_update_manager
// For example, 4.90 should be converted to 0x4900000000000
std::erase(version_str, '.');
if (std::from_chars(version_str.data(), version_str.data() + version_str.size(), system_sw_version, 16).ec != std::errc{})
if (std::from_chars(version_str.data(), version_str.data() + version_str.size(), system_sw_version, 16).ec == std::errc{})
system_sw_version <<= 40;
else
system_sw_version = 0;
@ -79,6 +79,7 @@ struct lv2_update_manager
if (malloc_set.count(addr))
{
malloc_set.erase(addr);
return vm::dealloc(addr, vm::main);
}

View file

@ -1531,9 +1531,11 @@ private:
}
}();
if (sce_idx == umax)
return PS3AV_STATUS_INVALID_VIDEO_PARAM;
const video_sce_param &sce_param = sce_param_arr[sce_idx];
if (sce_idx == umax ||
video_head_cfg.video_head > PS3AV_HEAD_B_ANALOG ||
if (video_head_cfg.video_head > PS3AV_HEAD_B_ANALOG ||
video_head_cfg.video_order > 1 ||
video_head_cfg.video_format > 16 ||
video_head_cfg.video_out_format > 16 ||

View file

@ -1173,11 +1173,15 @@ error_code sys_usbd_get_device_list(ppu_thread& ppu, u32 handle, vm::ptr<UsbInte
return CELL_EINVAL;
// TODO: was std::min<s32>
u32 i_tocopy = std::min<u32>(max_devices, ::size32(usbh.handled_devices));
const u32 i_tocopy = std::min<u32>(max_devices, ::size32(usbh.handled_devices));
u32 index = 0;
for (u32 index = 0; index < i_tocopy; index++)
for (const auto& [_, device] : usbh.handled_devices)
{
device_list[index] = usbh.handled_devices[index].first;
if (index == i_tocopy)
break;
device_list[index++] = device.first;
}
return not_an_error(i_tocopy);
@ -1409,7 +1413,7 @@ error_code sys_usbd_receive_event(ppu_thread& ppu, u32 handle, vm::ptr<u64> arg1
if (is_stopped(state))
{
std::lock_guard lock(usbh.mutex);
std::lock_guard lock(usbh.mutex_sq);
for (auto cpu = +usbh.sq; cpu; cpu = cpu->next_cpu)
{
@ -1587,7 +1591,7 @@ error_code sys_usbd_get_transfer_status(ppu_thread& ppu, u32 handle, u32 id_tran
std::lock_guard lock(usbh.mutex);
if (!usbh.is_init)
if (!usbh.is_init || id_transfer >= MAX_SYS_USBD_TRANSFERS)
return CELL_EINVAL;
const auto status = usbh.get_transfer_status(id_transfer);
@ -1607,7 +1611,7 @@ error_code sys_usbd_get_isochronous_transfer_status(ppu_thread& ppu, u32 handle,
std::lock_guard lock(usbh.mutex);
if (!usbh.is_init)
if (!usbh.is_init || id_transfer >= MAX_SYS_USBD_TRANSFERS)
return CELL_EINVAL;
const auto status = usbh.get_isochronous_transfer_status(id_transfer);

View file

@ -97,7 +97,7 @@ error_code sys_vm_memory_map(ppu_thread& ppu, u64 vsize, u64 psize, u32 cid, u64
// Look for unmapped space
if (const auto area = vm::find_map(0x10000000, 0x10000000, 2 | (flag & SYS_MEMORY_PAGE_SIZE_MASK)))
{
sys_vm.warning("sys_vm_memory_map(): Found VM 0x%x area (vsize=0x%x)", addr, vsize);
sys_vm.warning("sys_vm_memory_map(): Found VM 0x%x area (vsize=0x%x)", area->addr, vsize);
// Alloc all memory (shall not fail)
ensure(area->alloc(static_cast<u32>(vsize)));

View file

@ -532,8 +532,8 @@ namespace np
if (!mi.msg().empty())
{
sce_mi->msgLen = ::narrow<u32>(mi.msg().size());
auto* ptr_msg_data = static_cast<u8*>(edata.allocate<void>(mi.msg().size(), sce_mi->msg));
sce_mi->msgLen = ::size32(mi.msg());
auto* ptr_msg_data = static_cast<u8*>(edata.allocate<void>(::size32(mi.msg()), sce_mi->msg));
memcpy(ptr_msg_data, mi.msg().data(), mi.msg().size());
}
}
@ -575,8 +575,8 @@ namespace np
if (!resp.opt().empty())
{
room_status->opt_len = ::narrow<u32>(resp.opt().size());
u8* opt_data = static_cast<u8*>(edata.allocate<void>(resp.opt().size(), room_status->opt));
room_status->opt_len = ::size32(resp.opt());
u8* opt_data = static_cast<u8*>(edata.allocate<void>(::size32(resp.opt()), room_status->opt));
memcpy(opt_data, resp.opt().data(), resp.opt().size());
}
@ -604,8 +604,8 @@ namespace np
cur_attr->id = attr.attr_id();
if (!attr.data().empty())
{
cur_attr->value.data.size = ::narrow<u32>(attr.data().size());
u8* data_ptr = static_cast<u8*>(edata.allocate<void>(attr.data().size(), cur_attr->value.data.ptr));
cur_attr->value.data.size = ::size32(attr.data());
u8* data_ptr = static_cast<u8*>(edata.allocate<void>(::size32(attr.data()), cur_attr->value.data.ptr));
memcpy(data_ptr, attr.data().data(), attr.data().size());
}
else

View file

@ -847,6 +847,17 @@ namespace rsx
}
}
bool texture_format_ex::hw_SNORM_possible() const
{
return (texel_remap_control & SEXT_MASK) == (get_host_format_snorm_mask(format()) << SEXT_OFFSET);
}
bool texture_format_ex::hw_SRGB_possible() const
{
return encoded_remap == RSX_TEXTURE_REMAP_IDENTITY &&
(texel_remap_control & GAMMA_CTRL_MASK) == GAMMA_RGB_MASK;
}
std::vector<rsx::subresource_layout> get_subresources_layout(const rsx::fragment_texture& texture)
{
return get_subresources_layout_impl(texture);
@ -1255,6 +1266,32 @@ namespace rsx
fmt::throw_exception("Unknown format 0x%x", texture_format);
}
/**
* Returns a channel mask in ARGB that can be SNORM-converted
* Some formats have a hardcoded constant in one lane which we cannot SNORM-interpret in hardware.
*/
u32 get_host_format_snorm_mask(u32 format)
{
switch (format)
{
case CELL_GCM_TEXTURE_B8:
case CELL_GCM_TEXTURE_R5G6B5:
case CELL_GCM_TEXTURE_R6G5B5:
case CELL_GCM_TEXTURE_D1R5G5B5:
case CELL_GCM_TEXTURE_D8R8G8B8:
case CELL_GCM_TEXTURE_COMPRESSED_B8R8_G8R8:
case CELL_GCM_TEXTURE_COMPRESSED_R8B8_R8G8:
// Hardcoded alpha formats
return 0b1110;
case CELL_GCM_TEXTURE_X16:
// This one is a mess. X and Z are hardcoded. Not supported.
// Fall through instead of throw
default:
return 0b1111;
}
}
/**
* A texture is stored as an array of blocks, where a block is a pixel for standard texture
* but is a structure containing several pixels for compressed format

View file

@ -135,6 +135,12 @@ namespace rsx
RSX_FORMAT_FEATURE_16BIT_CHANNELS = (1 << 3), // Complements RSX_FORMAT_FEATURE_SIGNED_COMPONENTS
};
enum host_format_features : u8
{
RSX_HOST_FORMAT_FEATURE_SNORM = (1 << 0),
RSX_HOST_FORMAT_FEATURE_SRGB = (1 << 1),
};
using enum format_features;
struct texture_format_ex
@ -147,10 +153,28 @@ namespace rsx
bool valid() const { return format_bits != 0; }
u32 format() const { return format_bits & ~(CELL_GCM_TEXTURE_LN | CELL_GCM_TEXTURE_UN); }
bool hw_SNORM_possible() const;
bool hw_SRGB_possible() const;
bool host_snorm_format_active() const { return host_features & RSX_HOST_FORMAT_FEATURE_SNORM; }
bool host_srgb_format_active() const { return host_features & RSX_HOST_FORMAT_FEATURE_SRGB; }
operator bool() const { return valid(); }
bool operator == (const texture_format_ex& that) const
{
return this->format_bits == that.format_bits &&
this->features == that.features &&
this->host_features == that.host_features &&
this->encoded_remap == that.encoded_remap;
}
//private:
u32 format_bits = 0;
u32 features = 0;
u32 encoded_remap = 0;
u32 texel_remap_control = 0;
u32 host_features = 0;
};
// Sampled image descriptor
@ -293,6 +317,12 @@ namespace rsx
*/
rsx::flags32_t get_format_features(u32 texture_format);
/**
* Returns a channel mask in ARGB that can be SNORM-converted
* Some formats have a hardcoded constant in one lane which we cannot SNORM-interpret in hardware.
*/
u32 get_host_format_snorm_mask(u32 format);
/**
* Returns number of texel rows encoded in one pitch-length line of bytes
*/

View file

@ -299,95 +299,155 @@ void GLGSRender::load_texture_env()
for (u32 textures_ref = current_fp_metadata.referenced_textures_mask, i = 0; textures_ref; textures_ref >>= 1, ++i)
{
if (!(textures_ref & 1))
{
continue;
}
if (!fs_sampler_state[i])
{
fs_sampler_state[i] = std::make_unique<gl::texture_cache::sampled_image_descriptor>();
}
auto sampler_state = static_cast<gl::texture_cache::sampled_image_descriptor*>(fs_sampler_state[i].get());
const auto& tex = rsx::method_registers.fragment_textures[i];
const auto previous_format_class = sampler_state->format_class;
if (m_samplers_dirty || m_textures_dirty[i] || m_gl_texture_cache.test_if_descriptor_expired(cmd, m_rtts, sampler_state, tex))
if (!m_samplers_dirty &&
!m_textures_dirty[i] &&
!m_gl_texture_cache.test_if_descriptor_expired(cmd, m_rtts, sampler_state, tex))
{
if (tex.enabled())
continue;
}
const bool is_sampler_dirty = m_textures_dirty[i];
m_textures_dirty[i] = false;
if (!tex.enabled())
{
*sampler_state = {};
continue;
}
*sampler_state = m_gl_texture_cache.upload_texture(cmd, tex, m_rtts);
if (!sampler_state->validate())
{
continue;
}
if (!is_sampler_dirty)
{
if (sampler_state->format_class != previous_format_class)
{
*sampler_state = m_gl_texture_cache.upload_texture(cmd, tex, m_rtts);
if (sampler_state->validate())
{
sampler_state->format_ex = tex.format_ex();
if (m_textures_dirty[i])
{
m_fs_sampler_states[i].apply(tex, fs_sampler_state[i].get());
}
else if (sampler_state->format_class != previous_format_class)
{
m_graphics_state |= rsx::fragment_program_state_dirty;
}
const auto texture_format = sampler_state->format_ex.format();
// Depth format redirected to BGRA8 resample stage. Do not filter to avoid bits leaking.
// If accurate graphics are desired, force a bitcast to COLOR as a workaround.
const bool is_depth_reconstructed = sampler_state->format_class != rsx::classify_format(texture_format) &&
(texture_format == CELL_GCM_TEXTURE_A8R8G8B8 || texture_format == CELL_GCM_TEXTURE_D8R8G8B8);
// SNORM conversion required in shader. Do not interpolate to avoid introducing discontinuities due to how negative numbers work
const bool is_snorm = (sampler_state->format_ex.texel_remap_control & rsx::texture_control_bits::SEXT_MASK) != 0;
if (is_depth_reconstructed || is_snorm)
{
// Depth format redirected to BGRA8 resample stage. Do not filter to avoid bits leaking.
m_fs_sampler_states[i].set_parameteri(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
m_fs_sampler_states[i].set_parameteri(GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
}
}
else
{
*sampler_state = {};
// Host details changed but RSX is not aware
m_graphics_state |= rsx::fragment_program_state_dirty;
}
m_textures_dirty[i] = false;
if (sampler_state->format_ex)
{
// Nothing to change, use cached sampler
continue;
}
}
sampler_state->format_ex = tex.format_ex();
if (sampler_state->format_ex.texel_remap_control &&
sampler_state->image_handle &&
sampler_state->upload_context == rsx::texture_upload_context::shader_read &&
(current_fp_metadata.bx2_texture_reads_mask & (1u << i)) == 0 &&
!g_cfg.video.disable_hardware_texel_remapping) [[ unlikely ]]
{
// Check if we need to override the view format
const auto gl_format = sampler_state->image_handle->view_format();
GLenum format_override = gl_format;
rsx::flags32_t flags_to_erase = 0u;
rsx::flags32_t host_flags_to_set = 0u;
if (sampler_state->format_ex.hw_SNORM_possible())
{
format_override = gl::get_compatible_snorm_format(gl_format);
flags_to_erase = rsx::texture_control_bits::SEXT_MASK;
host_flags_to_set = rsx::RSX_HOST_FORMAT_FEATURE_SNORM;
}
else if (sampler_state->format_ex.hw_SRGB_possible())
{
format_override = gl::get_compatible_srgb_format(gl_format);
flags_to_erase = rsx::texture_control_bits::GAMMA_CTRL_MASK;
host_flags_to_set = rsx::RSX_HOST_FORMAT_FEATURE_SRGB;
}
if (format_override != GL_NONE && format_override != gl_format)
{
sampler_state->image_handle = sampler_state->image_handle->as(format_override);
sampler_state->format_ex.texel_remap_control &= (~flags_to_erase);
sampler_state->format_ex.host_features |= host_flags_to_set;
}
}
m_fs_sampler_states[i].apply(tex, fs_sampler_state[i].get());
const auto texture_format = sampler_state->format_ex.format();
// Depth format redirected to BGRA8 resample stage. Do not filter to avoid bits leaking.
// If accurate graphics are desired, force a bitcast to COLOR as a workaround.
const bool is_depth_reconstructed = sampler_state->format_class != rsx::classify_format(texture_format) &&
(texture_format == CELL_GCM_TEXTURE_A8R8G8B8 || texture_format == CELL_GCM_TEXTURE_D8R8G8B8);
// SNORM conversion required in shader. Do not interpolate to avoid introducing discontinuities due to how negative numbers work
const bool is_snorm = (sampler_state->format_ex.texel_remap_control & rsx::texture_control_bits::SEXT_MASK) != 0;
if (is_depth_reconstructed || is_snorm)
{
// Depth format redirected to BGRA8 resample stage. Do not filter to avoid bits leaking.
m_fs_sampler_states[i].set_parameteri(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
m_fs_sampler_states[i].set_parameteri(GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
}
for (u32 textures_ref = current_vp_metadata.referenced_textures_mask, i = 0; textures_ref; textures_ref >>= 1, ++i)
{
if (!(textures_ref & 1))
{
continue;
}
if (!vs_sampler_state[i])
{
vs_sampler_state[i] = std::make_unique<gl::texture_cache::sampled_image_descriptor>();
}
auto sampler_state = static_cast<gl::texture_cache::sampled_image_descriptor*>(vs_sampler_state[i].get());
const auto& tex = rsx::method_registers.vertex_textures[i];
const auto previous_format_class = sampler_state->format_class;
if (m_samplers_dirty || m_vertex_textures_dirty[i] || m_gl_texture_cache.test_if_descriptor_expired(cmd, m_rtts, sampler_state, tex))
if (!m_samplers_dirty &&
!m_vertex_textures_dirty[i] &&
!m_gl_texture_cache.test_if_descriptor_expired(cmd, m_rtts, sampler_state, tex))
{
if (rsx::method_registers.vertex_textures[i].enabled())
{
*sampler_state = m_gl_texture_cache.upload_texture(cmd, rsx::method_registers.vertex_textures[i], m_rtts);
continue;
}
if (sampler_state->validate())
{
if (m_vertex_textures_dirty[i])
{
m_vs_sampler_states[i].apply(tex, vs_sampler_state[i].get());
}
else if (sampler_state->format_class != previous_format_class)
{
m_graphics_state |= rsx::vertex_program_state_dirty;
}
}
}
else
{
*sampler_state = {};
}
const bool is_sampler_dirty = m_vertex_textures_dirty[i];
m_vertex_textures_dirty[i] = false;
m_vertex_textures_dirty[i] = false;
if (!tex.enabled())
{
*sampler_state = {};
continue;
}
*sampler_state = m_gl_texture_cache.upload_texture(cmd, rsx::method_registers.vertex_textures[i], m_rtts);
if (!sampler_state->validate())
{
continue;
}
if (is_sampler_dirty)
{
m_vs_sampler_states[i].apply(tex, vs_sampler_state[i].get());
}
else if (sampler_state->format_class != previous_format_class)
{
m_graphics_state |= rsx::vertex_program_state_dirty;
}
}
@ -402,7 +462,9 @@ void GLGSRender::bind_texture_env()
for (u32 textures_ref = current_fp_metadata.referenced_textures_mask, i = 0; textures_ref; textures_ref >>= 1, ++i)
{
if (!(textures_ref & 1))
{
continue;
}
gl::texture_view* view = nullptr;
auto sampler_state = static_cast<gl::texture_cache::sampled_image_descriptor*>(fs_sampler_state[i].get());
@ -442,22 +504,26 @@ void GLGSRender::bind_texture_env()
for (u32 textures_ref = current_vp_metadata.referenced_textures_mask, i = 0; textures_ref; textures_ref >>= 1, ++i)
{
if (!(textures_ref & 1))
{
continue;
}
auto sampler_state = static_cast<gl::texture_cache::sampled_image_descriptor*>(vs_sampler_state[i].get());
gl::texture_view* view = nullptr;
if (rsx::method_registers.vertex_textures[i].enabled() &&
sampler_state->validate())
{
if (sampler_state->image_handle) [[likely]]
if (view = sampler_state->image_handle; !view)
{
sampler_state->image_handle->bind(cmd, GL_VERTEX_TEXTURES_START + i);
}
else
{
m_gl_texture_cache.create_temporary_subresource(cmd, sampler_state->external_subresource_desc)->bind(cmd, GL_VERTEX_TEXTURES_START + i);
view = m_gl_texture_cache.create_temporary_subresource(cmd, sampler_state->external_subresource_desc);
}
}
if (view) [[likely]]
{
view->bind(cmd, GL_VERTEX_TEXTURES_START + i);
}
else
{
cmd->bind_texture(GL_VERTEX_TEXTURES_START + i, GL_TEXTURE_2D, GL_NONE);

View file

@ -237,6 +237,7 @@ void GLFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS)
m_shader_props.require_tex3D_ops = properties.has_tex3D;
m_shader_props.require_shadowProj_ops = properties.shadow_sampler_mask != 0 && properties.has_texShadowProj;
m_shader_props.require_alpha_kill = !!(m_prog.ctrl & RSX_SHADER_CONTROL_TEXTURE_ALPHA_KILL);
m_shader_props.require_color_format_convert = !!(m_prog.ctrl & RSX_SHADER_CONTROL_TEXTURE_FORMAT_CONVERT);
glsl::insert_glsl_legacy_function(OS, m_shader_props);
}

View file

@ -266,6 +266,44 @@ namespace gl
fmt::throw_exception("Unknown format 0x%x", texture_format);
}
GLenum get_compatible_snorm_format(GLenum base_format)
{
switch (base_format)
{
case GL_R8:
return GL_R8_SNORM;
case GL_RG8:
return GL_RG8_SNORM;
case GL_RGBA8:
return GL_RGBA8_SNORM;
case GL_R16:
return GL_R16_SNORM;
case GL_RG16:
return GL_RG16_SNORM;
case GL_RGBA16:
return GL_RGBA16_SNORM;
default:
return GL_NONE;
}
}
GLenum get_compatible_srgb_format(GLenum base_format)
{
switch (base_format)
{
case GL_RGBA8:
return GL_SRGB8_ALPHA8_EXT;
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
default:
return GL_NONE;
}
}
cs_shuffle_base* get_trivial_transform_job(const pixel_buffer_layout& pack_info)
{
if (!pack_info.swap_bytes)

View file

@ -62,6 +62,8 @@ namespace gl
std::tuple<GLenum, GLenum> get_format_type(u32 texture_format);
pixel_buffer_layout get_format_type(texture::internal_format format);
std::array<GLenum, 4> get_swizzle_remap(u32 texture_format);
GLenum get_compatible_snorm_format(GLenum base_format);
GLenum get_compatible_srgb_format(GLenum base_format);
viewable_image* create_texture(u32 gcm_format, u16 width, u16 height, u16 depth, u16 mipmaps, rsx::texture_dimension_extended type);

View file

@ -318,6 +318,34 @@ namespace gl
}
}
texture_view* texture_view::as(GLenum format)
{
if (format == this->m_view_format)
{
return this;
}
auto self = m_root_view ? m_root_view : this;
if (auto found = self->m_subviews.find(format);
found != self->m_subviews.end())
{
return found->second.get();
}
GLenum swizzle_argb[4] =
{
component_swizzle[3],
component_swizzle[0],
component_swizzle[1],
component_swizzle[2],
};
auto view = std::make_unique<texture_view>(m_image_data, m_target, format, swizzle_argb, m_aspect_flags);
auto ret = view.get();
self->m_subviews.emplace(format, std::move(view));
return ret;
}
void texture_view::bind(gl::command_context& cmd, GLuint layer) const
{
cmd->bind_texture(layer, m_target, m_id);

View file

@ -353,7 +353,10 @@ namespace gl
GLenum m_aspect_flags = 0;
texture* m_image_data = nullptr;
GLenum component_swizzle[4] {};
GLenum component_swizzle[4]{};
std::unordered_map<GLenum, std::unique_ptr<texture_view>> m_subviews;
texture_view* m_root_view = nullptr;
texture_view() = default;
@ -395,6 +398,8 @@ namespace gl
virtual ~texture_view();
texture_view* as(GLenum format);
GLuint id() const
{
return m_id;

View file

@ -82,11 +82,34 @@ namespace gl
{
// NOTE: In OpenGL, the border texels are processed by the pipeline and will be swizzled by the texture view.
// Therefore, we pass the raw value here, and the texture view will handle the rest for us.
const auto encoded_color = tex.border_color();
if (get_parameteri(GL_TEXTURE_BORDER_COLOR) != encoded_color)
const bool sext_conv_required = (sampled_image->format_ex.texel_remap_control & rsx::SEXT_MASK) != 0;
const auto encoded_color = tex.border_color(sext_conv_required);
const auto host_features = sampled_image->format_ex.host_features;
if (get_parameteri(GL_TEXTURE_BORDER_COLOR) != encoded_color ||
get_parameteri(GL_TEXTURE_BORDER_VALUES_NV) != host_features)
{
m_propertiesi[GL_TEXTURE_BORDER_COLOR] = encoded_color;
const auto border_color = rsx::decode_border_color(encoded_color);
m_propertiesi[GL_TEXTURE_BORDER_VALUES_NV] = host_features;
auto border_color = rsx::decode_border_color(encoded_color);
if (sampled_image->format_ex.host_snorm_format_active()) [[ unlikely ]]
{
// Hardware SNORM is active
// Convert the border color in host space (2N - 1)
// HW does the conversion in integer space as (x - 128) / 127 which introduces a biasing error.
const float bias_v = 128.f / 255.f;
const float scale_v = 255.f / 127.f;
color4f scale{ 1.f }, bias{ 0.f };
const auto snorm_mask = tex.argb_signed();
if (snorm_mask & 1) { scale.a = scale_v; bias.a = -bias_v; }
if (snorm_mask & 2) { scale.r = scale_v; bias.r = -bias_v; }
if (snorm_mask & 4) { scale.g = scale_v; bias.g = -bias_v; }
if (snorm_mask & 8) { scale.b = scale_v; bias.b = -bias_v; }
border_color = (border_color + bias) * scale;
}
glSamplerParameterfv(sampler_handle, GL_TEXTURE_BORDER_COLOR, border_color.rgba);
}
}

View file

@ -12,6 +12,8 @@
extern atomic_t<bool> g_user_asked_for_recording;
atomic_t<bool> g_user_asked_for_fullscreen = false;
namespace rsx
{
namespace overlays
@ -80,7 +82,7 @@ namespace rsx
if (btn != pad_button::cross) return page_navigation::stay;
rsx_log.notice("User selected trophies in home menu");
Emu.CallFromMainThread([trop_name = std::move(trop_name)]()
Emu.CallFromMainThread([trop_name]()
{
if (auto manager = g_fxo->try_get<rsx::overlays::display_manager>())
{
@ -110,6 +112,17 @@ namespace rsx
return page_navigation::exit;
});
std::unique_ptr<overlay_element> fullscreen = std::make_unique<home_menu_entry>(get_localized_string(localized_string_id::HOME_MENU_TOGGLE_FULLSCREEN));
add_item(fullscreen, [](pad_button btn) -> page_navigation
{
if (btn != pad_button::cross)
return page_navigation::stay;
rsx_log.notice("User selected toggle fullscreen in home menu");
g_user_asked_for_fullscreen = true;
return page_navigation::stay; // No need to exit
});
add_page(std::make_shared<home_menu_savestate>(x, y, width, height, use_separators, this));
std::unique_ptr<overlay_element> restart = std::make_unique<home_menu_entry>(get_localized_string(localized_string_id::HOME_MENU_RESTART));

View file

@ -215,12 +215,24 @@ namespace rsx
case pad_button::dpad_up:
case pad_button::ls_up:
{
if (!is_auto_repeat && get_selected_index() <= 0)
{
select_entry(get_elements_count() - 1);
break;
}
select_previous();
break;
}
case pad_button::dpad_down:
case pad_button::ls_down:
{
if (!is_auto_repeat && get_selected_index() >= (get_elements_count() - 1))
{
select_entry(0);
break;
}
select_next();
break;
}

View file

@ -189,7 +189,12 @@ namespace rsx
update_selection();
}
int list_view::get_selected_index() const
u16 list_view::get_elements_count() const
{
return m_elements_count;
}
s32 list_view::get_selected_index() const
{
return m_selected_entry;
}

View file

@ -34,7 +34,8 @@ namespace rsx
void add_entry(std::unique_ptr<overlay_element>& entry);
int get_selected_index() const;
u16 get_elements_count() const;
s32 get_selected_index() const;
bool get_cancel_only() const;
const overlay_element* get_selected_entry() const;

View file

@ -408,6 +408,16 @@ namespace glsl
enabled_options.push_back("_ENABLE_TEXTURE_ALPHA_KILL");
}
if (props.require_color_format_convert)
{
enabled_options.push_back("_ENABLE_FORMAT_CONVERSION");
}
if (props.require_depth_conversion)
{
enabled_options.push_back("_ENABLE_DEPTH_FORMAT_RECONSTRUCTION");
}
program_common::define_glsl_switches(OS, enabled_options);
enabled_options.clear();

View file

@ -4,52 +4,12 @@
#include "GLSLTypes.h"
#include "ShaderParam.h"
#include "../color_utils.h"
struct RSXFragmentProgram;
namespace rsx
{
// TODO: Move this somewhere else once more compilers are supported other than glsl
enum texture_control_bits
{
GAMMA_A = 0,
GAMMA_R,
GAMMA_G,
GAMMA_B,
ALPHAKILL,
RENORMALIZE,
EXPAND_A,
EXPAND_R,
EXPAND_G,
EXPAND_B,
SEXT_A,
SEXT_R,
SEXT_G,
SEXT_B,
DEPTH_FLOAT,
DEPTH_COMPARE_OP,
DEPTH_COMPARE_1,
DEPTH_COMPARE_2,
FILTERED_MAG,
FILTERED_MIN,
UNNORMALIZED_COORDS,
CLAMP_TEXCOORDS_BIT,
WRAP_S,
WRAP_T,
WRAP_R,
FF_SIGNED_BIT,
FF_BIASED_RENORM_BIT,
FF_GAMMA_BIT,
FF_16BIT_CHANNELS_BIT,
GAMMA_CTRL_MASK = (1 << GAMMA_R) | (1 << GAMMA_G) | (1 << GAMMA_B) | (1 << GAMMA_A),
EXPAND_MASK = (1 << EXPAND_R) | (1 << EXPAND_G) | (1 << EXPAND_B) | (1 << EXPAND_A),
EXPAND_OFFSET = EXPAND_A,
SEXT_MASK = (1 << SEXT_R) | (1 << SEXT_G) | (1 << SEXT_B) | (1 << SEXT_A),
SEXT_OFFSET = SEXT_A,
FORMAT_FEATURES_OFFSET = FF_SIGNED_BIT,
};
enum ROP_control_bits : u32
{
// Commands. These trigger explicit action.

View file

@ -46,8 +46,10 @@ R"(
_texture_bx2_active = false; \
} while (false)
#define TEX_FLAGS(index) ((TEX_PARAM(index).flags & ~(_texture_flag_erase)) | _texture_flag_override)
#else
#elif defined(_ENABLE_TEXTURE_ALPHA_KILL) || defined(_ENABLE_FORMAT_CONVERSION) || defined(_ENABLE_DEPTH_FORMAT_RECONSTRUCTION)
#define TEX_FLAGS(index) (TEX_PARAM(index).flags)
#else
#define TEX_FLAGS(index) 0
#endif
#define TEX_NAME(index) tex##index
@ -193,6 +195,8 @@ vec4 _texcoord_xform_shadow(const in vec4 coord4, const in sampler_info params)
#endif // _EMULATE_SHADOW
#ifdef _ENABLE_FORMAT_CONVERSION
vec4 _sext_unorm8x4(const in vec4 x)
{
// TODO: Handle clamped sign-extension
@ -285,4 +289,27 @@ vec4 _process_texel(in vec4 rgba, const in uint control_bits)
return rgba;
}
#elif defined(_ENABLE_TEXTURE_ALPHA_KILL)
vec4 _process_texel(in vec4 rgba, const in uint control_bits)
{
if (_test_bit(control_bits, ALPHAKILL))
{
// Alphakill
if (rgba.a < 0.000001)
{
_kill();
return rgba;
}
}
return rgba;
}
#else
#define _process_texel(rgba, control) rgba
#endif // _ENABLE_FORMAT_CONVERSION
)"

View file

@ -60,5 +60,6 @@ namespace glsl
bool require_tex3D_ops : 1; // Include 3D texture stuff (including cubemap)
bool require_shadowProj_ops : 1; // Include shadow2DProj projection textures (1D is unsupported anyway)
bool require_alpha_kill : 1; // Include alpha kill checking code
bool require_color_format_convert : 1; // Include colorspace conversion code
};
};

View file

@ -651,7 +651,7 @@ fragment_program_utils::fragment_program_metadata fragment_program_utils::analys
case RSX_FP_OPCODE_TXB:
case RSX_FP_OPCODE_TXL:
result.referenced_textures_mask |= (1 << d0.tex_num);
result.has_tex_bx2_conv |= !!d0.exp_tex;
result.bx2_texture_reads_mask |= ((d0.exp_tex ? 1u : 0u) << d0.tex_num);
break;
case RSX_FP_OPCODE_PK4:
case RSX_FP_OPCODE_UP4:

View file

@ -56,10 +56,10 @@ namespace program_hash_util
u32 program_ucode_length;
u32 program_constants_buffer_length;
u16 referenced_textures_mask;
u16 bx2_texture_reads_mask;
bool has_pack_instructions;
bool has_branch_instructions;
bool has_tex_bx2_conv;
bool is_nop_shader; // Does this affect Z-pass testing???
};

View file

@ -121,6 +121,7 @@ namespace rsx
texture_format_ex result { format_bits };
result.features = format_features;
result.texel_remap_control = format_convert;
result.encoded_remap = remap();
return result;
}
@ -364,11 +365,15 @@ namespace rsx
return dimension() != rsx::texture_dimension::dimension1d ? ((registers[NV4097_SET_TEXTURE_IMAGE_RECT + (m_index * 8)]) & 0xffff) : 1;
}
u32 fragment_texture::border_color() const
u32 fragment_texture::border_color(bool apply_colorspace_remapping) const
{
const u32 raw = registers[NV4097_SET_TEXTURE_BORDER_COLOR + (m_index * 8)];
const u32 sext = argb_signed();
if (!apply_colorspace_remapping) [[ likely ]]
{
return raw;
}
const u32 sext = argb_signed();
if (!sext) [[ likely ]]
{
return raw;
@ -430,9 +435,9 @@ namespace rsx
return (conv & mask) | (raw & ~mask);
}
color4f fragment_texture::remapped_border_color() const
color4f fragment_texture::remapped_border_color(bool apply_colorspace_remapping) const
{
color4f base_color = rsx::decode_border_color(border_color());
color4f base_color = rsx::decode_border_color(border_color(apply_colorspace_remapping));
if (remap() == RSX_TEXTURE_REMAP_IDENTITY)
{
return base_color;
@ -571,14 +576,14 @@ namespace rsx
return dimension() != rsx::texture_dimension::dimension1d ? ((registers[NV4097_SET_VERTEX_TEXTURE_IMAGE_RECT + (m_index * 8)]) & 0xffff) : 1;
}
u32 vertex_texture::border_color() const
u32 vertex_texture::border_color(bool) const
{
return registers[NV4097_SET_VERTEX_TEXTURE_BORDER_COLOR + (m_index * 8)];
}
color4f vertex_texture::remapped_border_color() const
color4f vertex_texture::remapped_border_color(bool) const
{
return rsx::decode_border_color(border_color());
return rsx::decode_border_color(border_color(false));
}
u16 vertex_texture::depth() const

View file

@ -80,8 +80,8 @@ namespace rsx
u16 height() const;
// Border Color
u32 border_color() const;
color4f remapped_border_color() const;
u32 border_color(bool apply_colorspace_remapping = false) const;
color4f remapped_border_color(bool apply_colorspace_remapping = false) const;
u16 depth() const;
u32 pitch() const;
@ -136,8 +136,8 @@ namespace rsx
u16 height() const;
// Border Color
u32 border_color() const;
color4f remapped_border_color() const;
u32 border_color(bool = false) const;
color4f remapped_border_color(bool = false) const;
u16 depth() const;
u32 pitch() const;

View file

@ -2315,6 +2315,7 @@ namespace rsx
case CELL_GCM_TEXTURE_R5G6B5:
case CELL_GCM_TEXTURE_R6G5B5:
texture_control |= (1 << texture_control_bits::RENORMALIZE);
current_fragment_program.ctrl |= RSX_SHADER_CONTROL_TEXTURE_FORMAT_CONVERT;
break;
default:
break;
@ -2326,8 +2327,15 @@ namespace rsx
texture_control |= format_ex.texel_remap_control;
texture_control |= format_ex.features << texture_control_bits::FORMAT_FEATURES_OFFSET;
if (current_fp_metadata.has_tex_bx2_conv)
if (format_ex.texel_remap_control)
{
current_fragment_program.ctrl |= RSX_SHADER_CONTROL_TEXTURE_FORMAT_CONVERT;
}
if (current_fp_metadata.bx2_texture_reads_mask)
{
current_fragment_program.ctrl |= RSX_SHADER_CONTROL_TEXTURE_FORMAT_CONVERT;
const u32 remap_hi = tex.decoded_remap().shuffle_mask_bits(0xFu);
current_fragment_program.texture_params[i].remap &= ~(0xFu << 16u);
current_fragment_program.texture_params[i].remap |= (remap_hi << 16u);

View file

@ -263,7 +263,7 @@ void VKGSRender::load_texture_env()
{
// Load textures
bool check_for_cyclic_refs = false;
auto check_surface_cache_sampler = [&](auto descriptor, const auto& tex)
auto check_surface_cache_sampler_valid = [&](auto descriptor, const auto& tex)
{
if (!m_texture_cache.test_if_descriptor_expired(*m_current_command_buffer, m_rtts, descriptor, tex))
{
@ -274,11 +274,11 @@ void VKGSRender::load_texture_env()
return false;
};
auto get_border_color = [&](const rsx::Texture auto& tex)
auto get_border_color = [&](const rsx::Texture auto& tex, bool remap_colorspace)
{
return m_device->get_custom_border_color_support().require_border_color_remap
? tex.remapped_border_color()
: rsx::decode_border_color(tex.border_color());
? tex.remapped_border_color(remap_colorspace)
: rsx::decode_border_color(tex.border_color(remap_colorspace));
};
std::lock_guard lock(m_sampler_mutex);
@ -286,242 +286,319 @@ void VKGSRender::load_texture_env()
for (u32 textures_ref = current_fp_metadata.referenced_textures_mask, i = 0; textures_ref; textures_ref >>= 1, ++i)
{
if (!(textures_ref & 1))
{
continue;
}
if (!fs_sampler_state[i])
{
fs_sampler_state[i] = std::make_unique<vk::texture_cache::sampled_image_descriptor>();
}
auto sampler_state = static_cast<vk::texture_cache::sampled_image_descriptor*>(fs_sampler_state[i].get());
const auto& tex = rsx::method_registers.fragment_textures[i];
const auto previous_format_class = fs_sampler_state[i]->format_class;
if (m_samplers_dirty || m_textures_dirty[i] || !check_surface_cache_sampler(sampler_state, tex))
if (!m_samplers_dirty &&
!m_textures_dirty[i] &&
check_surface_cache_sampler_valid(sampler_state, tex))
{
if (tex.enabled())
continue;
}
const bool is_sampler_dirty = m_textures_dirty[i];
m_textures_dirty[i] = false;
if (!tex.enabled())
{
*sampler_state = {};
continue;
}
*sampler_state = m_texture_cache.upload_texture(*m_current_command_buffer, tex, m_rtts);
if (!sampler_state->validate())
{
continue;
}
if (sampler_state->is_cyclic_reference)
{
check_for_cyclic_refs |= true;
}
if (!is_sampler_dirty)
{
if (sampler_state->format_class != previous_format_class)
{
*sampler_state = m_texture_cache.upload_texture(*m_current_command_buffer, tex, m_rtts);
// Host details changed but RSX is not aware
m_graphics_state |= rsx::fragment_program_state_dirty;
}
if (sampler_state->format_ex)
{
// Nothing to change, use cached sampler
continue;
}
}
sampler_state->format_ex = tex.format_ex();
if (sampler_state->format_ex.texel_remap_control &&
sampler_state->image_handle &&
sampler_state->upload_context == rsx::texture_upload_context::shader_read &&
(current_fp_metadata.bx2_texture_reads_mask & (1u << i)) == 0 &&
!g_cfg.video.disable_hardware_texel_remapping) [[ unlikely ]]
{
// Check if we need to override the view format
const auto vk_format = sampler_state->image_handle->format();
VkFormat format_override = vk_format;;
rsx::flags32_t flags_to_erase = 0u;
rsx::flags32_t host_flags_to_set = 0u;
if (sampler_state->format_ex.hw_SNORM_possible())
{
format_override = vk::get_compatible_snorm_format(vk_format);
flags_to_erase = rsx::texture_control_bits::SEXT_MASK;
host_flags_to_set = rsx::RSX_HOST_FORMAT_FEATURE_SNORM;
}
else if (sampler_state->format_ex.hw_SRGB_possible())
{
format_override = vk::get_compatible_srgb_format(vk_format);
flags_to_erase = rsx::texture_control_bits::GAMMA_CTRL_MASK;
host_flags_to_set = rsx::RSX_HOST_FORMAT_FEATURE_SRGB;
}
if (format_override != VK_FORMAT_UNDEFINED && format_override != vk_format)
{
sampler_state->image_handle = sampler_state->image_handle->as(format_override);
sampler_state->format_ex.texel_remap_control &= (~flags_to_erase);
sampler_state->format_ex.host_features |= host_flags_to_set;
}
}
VkFilter mag_filter;
vk::minification_filter min_filter;
f32 min_lod = 0.f, max_lod = 0.f;
f32 lod_bias = 0.f;
const u32 texture_format = sampler_state->format_ex.format();
VkBool32 compare_enabled = VK_FALSE;
VkCompareOp depth_compare_mode = VK_COMPARE_OP_NEVER;
if (texture_format >= CELL_GCM_TEXTURE_DEPTH24_D8 && texture_format <= CELL_GCM_TEXTURE_DEPTH16_FLOAT)
{
compare_enabled = VK_TRUE;
depth_compare_mode = vk::get_compare_func(tex.zfunc(), true);
}
const f32 af_level = vk::max_aniso(tex.max_aniso());
const auto wrap_s = vk::vk_wrap_mode(tex.wrap_s());
const auto wrap_t = vk::vk_wrap_mode(tex.wrap_t());
const auto wrap_r = vk::vk_wrap_mode(tex.wrap_r());
// NOTE: In vulkan, the border color can bypass the sample swizzle stage.
// Check the device properties to determine whether to pre-swizzle the colors or not.
const bool sext_conv_required = (sampler_state->format_ex.texel_remap_control & rsx::texture_control_bits::SEXT_MASK) != 0;
vk::border_color_t border_color(VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK);
if (rsx::is_border_clamped_texture(tex))
{
auto color_value = get_border_color(tex, sext_conv_required);
if (sampler_state->format_ex.host_snorm_format_active())
{
// Convert the border color in host space (2N - 1)
// HW does the conversion in integer space as (x - 128) / 127 which introduces a biasing error.
const float bias_v = 128.f / 255.f;
const float scale_v = 255.f / 127.f;
color4f scale{ 1.f }, bias{ 0.f };
const auto snorm_mask = tex.argb_signed();
if (snorm_mask & 1) { scale.a = scale_v; bias.a = -bias_v; }
if (snorm_mask & 2) { scale.r = scale_v; bias.r = -bias_v; }
if (snorm_mask & 4) { scale.g = scale_v; bias.g = -bias_v; }
if (snorm_mask & 8) { scale.b = scale_v; bias.b = -bias_v; }
color_value = (color_value + bias) * scale;
}
border_color = color_value;
}
// Check if non-point filtering can even be used on this format
bool can_sample_linear;
if (sampler_state->format_class == RSX_FORMAT_CLASS_COLOR) [[likely]]
{
// Most PS3-like formats can be linearly filtered without problem
// Exclude textures that require SNORM conversion however
can_sample_linear = !sext_conv_required;
}
else if (sampler_state->format_class != rsx::classify_format(texture_format) &&
(texture_format == CELL_GCM_TEXTURE_A8R8G8B8 || texture_format == CELL_GCM_TEXTURE_D8R8G8B8))
{
// Depth format redirected to BGRA8 resample stage. Do not filter to avoid bits leaking
can_sample_linear = false;
}
else
{
// Not all GPUs support linear filtering of depth formats
const auto vk_format = sampler_state->image_handle ? sampler_state->image_handle->image()->format() :
vk::get_compatible_sampler_format(m_device->get_formats_support(), sampler_state->external_subresource_desc.gcm_format);
can_sample_linear = m_device->get_format_properties(vk_format).optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
}
const auto mipmap_count = tex.get_exact_mipmap_count();
min_filter = vk::get_min_filter(tex.min_filter());
if (can_sample_linear)
{
mag_filter = vk::get_mag_filter(tex.mag_filter());
}
else
{
mag_filter = VK_FILTER_NEAREST;
min_filter.filter = VK_FILTER_NEAREST;
}
if (min_filter.sample_mipmaps && mipmap_count > 1)
{
f32 actual_mipmaps;
if (sampler_state->upload_context == rsx::texture_upload_context::shader_read)
{
actual_mipmaps = static_cast<f32>(mipmap_count);
}
else if (sampler_state->external_subresource_desc.op == rsx::deferred_request_command::mipmap_gather)
{
// Clamp min and max lod
actual_mipmaps = static_cast<f32>(sampler_state->external_subresource_desc.sections_to_copy.size());
}
else
{
*sampler_state = {};
actual_mipmaps = 1.f;
}
if (sampler_state->validate())
if (actual_mipmaps > 1.f)
{
sampler_state->format_ex = tex.format_ex();
min_lod = tex.min_lod();
max_lod = tex.max_lod();
lod_bias = tex.bias();
if (sampler_state->is_cyclic_reference)
min_lod = std::min(min_lod, actual_mipmaps - 1.f);
max_lod = std::min(max_lod, actual_mipmaps - 1.f);
if (min_filter.mipmap_mode == VK_SAMPLER_MIPMAP_MODE_NEAREST)
{
check_for_cyclic_refs |= true;
}
if (!m_textures_dirty[i] && sampler_state->format_class != previous_format_class)
{
// Host details changed but RSX is not aware
m_graphics_state |= rsx::fragment_program_state_dirty;
}
bool replace = !fs_sampler_handles[i];
VkFilter mag_filter;
vk::minification_filter min_filter;
f32 min_lod = 0.f, max_lod = 0.f;
f32 lod_bias = 0.f;
const u32 texture_format = sampler_state->format_ex.format();
VkBool32 compare_enabled = VK_FALSE;
VkCompareOp depth_compare_mode = VK_COMPARE_OP_NEVER;
if (texture_format >= CELL_GCM_TEXTURE_DEPTH24_D8 && texture_format <= CELL_GCM_TEXTURE_DEPTH16_FLOAT)
{
compare_enabled = VK_TRUE;
depth_compare_mode = vk::get_compare_func(tex.zfunc(), true);
}
const f32 af_level = vk::max_aniso(tex.max_aniso());
const auto wrap_s = vk::vk_wrap_mode(tex.wrap_s());
const auto wrap_t = vk::vk_wrap_mode(tex.wrap_t());
const auto wrap_r = vk::vk_wrap_mode(tex.wrap_r());
// NOTE: In vulkan, the border color can bypass the sample swizzle stage.
// Check the device properties to determine whether to pre-swizzle the colors or not.
const auto border_color = rsx::is_border_clamped_texture(tex)
? vk::border_color_t(get_border_color(tex))
: vk::border_color_t(VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK);
// Check if non-point filtering can even be used on this format
bool can_sample_linear;
if (sampler_state->format_class == RSX_FORMAT_CLASS_COLOR) [[likely]]
{
// Most PS3-like formats can be linearly filtered without problem
// Exclude textures that require SNORM conversion however
can_sample_linear = (sampler_state->format_ex.texel_remap_control & rsx::texture_control_bits::SEXT_MASK) == 0;
}
else if (sampler_state->format_class != rsx::classify_format(texture_format) &&
(texture_format == CELL_GCM_TEXTURE_A8R8G8B8 || texture_format == CELL_GCM_TEXTURE_D8R8G8B8))
{
// Depth format redirected to BGRA8 resample stage. Do not filter to avoid bits leaking
can_sample_linear = false;
}
else
{
// Not all GPUs support linear filtering of depth formats
const auto vk_format = sampler_state->image_handle ? sampler_state->image_handle->image()->format() :
vk::get_compatible_sampler_format(m_device->get_formats_support(), sampler_state->external_subresource_desc.gcm_format);
can_sample_linear = m_device->get_format_properties(vk_format).optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
}
const auto mipmap_count = tex.get_exact_mipmap_count();
min_filter = vk::get_min_filter(tex.min_filter());
if (can_sample_linear)
{
mag_filter = vk::get_mag_filter(tex.mag_filter());
}
else
{
mag_filter = VK_FILTER_NEAREST;
min_filter.filter = VK_FILTER_NEAREST;
}
if (min_filter.sample_mipmaps && mipmap_count > 1)
{
f32 actual_mipmaps;
if (sampler_state->upload_context == rsx::texture_upload_context::shader_read)
{
actual_mipmaps = static_cast<f32>(mipmap_count);
}
else if (sampler_state->external_subresource_desc.op == rsx::deferred_request_command::mipmap_gather)
{
// Clamp min and max lod
actual_mipmaps = static_cast<f32>(sampler_state->external_subresource_desc.sections_to_copy.size());
}
else
{
actual_mipmaps = 1.f;
}
if (actual_mipmaps > 1.f)
{
min_lod = tex.min_lod();
max_lod = tex.max_lod();
lod_bias = tex.bias();
min_lod = std::min(min_lod, actual_mipmaps - 1.f);
max_lod = std::min(max_lod, actual_mipmaps - 1.f);
if (min_filter.mipmap_mode == VK_SAMPLER_MIPMAP_MODE_NEAREST)
{
// Round to nearest 0.5 to work around some broken games
// Unlike openGL, sampler parameters cannot be dynamically changed on vulkan, leading to many permutations
lod_bias = std::floor(lod_bias * 2.f + 0.5f) * 0.5f;
}
}
else
{
min_lod = max_lod = lod_bias = 0.f;
min_filter.mipmap_mode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
}
}
if (fs_sampler_handles[i] && m_textures_dirty[i])
{
if (!fs_sampler_handles[i]->matches(wrap_s, wrap_t, wrap_r, false, lod_bias, af_level, min_lod, max_lod,
min_filter.filter, mag_filter, min_filter.mipmap_mode, border_color, compare_enabled, depth_compare_mode))
{
replace = true;
}
}
if (replace)
{
fs_sampler_handles[i] = vk::get_resource_manager()->get_sampler(
*m_device,
fs_sampler_handles[i],
wrap_s, wrap_t, wrap_r,
false,
lod_bias, af_level, min_lod, max_lod,
min_filter.filter, mag_filter, min_filter.mipmap_mode,
border_color, compare_enabled, depth_compare_mode);
// Round to nearest 0.5 to work around some broken games
// Unlike openGL, sampler parameters cannot be dynamically changed on vulkan, leading to many permutations
lod_bias = std::floor(lod_bias * 2.f + 0.5f) * 0.5f;
}
}
m_textures_dirty[i] = false;
else
{
min_lod = max_lod = lod_bias = 0.f;
min_filter.mipmap_mode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
}
}
if (fs_sampler_handles[i] &&
fs_sampler_handles[i]->matches(wrap_s, wrap_t, wrap_r, false, lod_bias, af_level, min_lod, max_lod,
min_filter.filter, mag_filter, min_filter.mipmap_mode, border_color, compare_enabled, depth_compare_mode))
{
continue;
}
fs_sampler_handles[i] = vk::get_resource_manager()->get_sampler(
*m_device,
fs_sampler_handles[i],
wrap_s, wrap_t, wrap_r,
false,
lod_bias, af_level, min_lod, max_lod,
min_filter.filter, mag_filter, min_filter.mipmap_mode,
border_color, compare_enabled, depth_compare_mode);
}
for (u32 textures_ref = current_vp_metadata.referenced_textures_mask, i = 0; textures_ref; textures_ref >>= 1, ++i)
{
if (!(textures_ref & 1))
{
continue;
}
if (!vs_sampler_state[i])
{
vs_sampler_state[i] = std::make_unique<vk::texture_cache::sampled_image_descriptor>();
}
auto sampler_state = static_cast<vk::texture_cache::sampled_image_descriptor*>(vs_sampler_state[i].get());
const auto& tex = rsx::method_registers.vertex_textures[i];
const auto previous_format_class = sampler_state->format_class;
if (m_samplers_dirty || m_vertex_textures_dirty[i] || !check_surface_cache_sampler(sampler_state, tex))
if (!m_samplers_dirty &&
!m_vertex_textures_dirty[i] &&
check_surface_cache_sampler_valid(sampler_state, tex))
{
if (rsx::method_registers.vertex_textures[i].enabled())
{
*sampler_state = m_texture_cache.upload_texture(*m_current_command_buffer, tex, m_rtts);
}
else
{
*sampler_state = {};
}
if (sampler_state->validate())
{
if (sampler_state->is_cyclic_reference || sampler_state->external_subresource_desc.do_not_cache)
{
check_for_cyclic_refs |= true;
}
if (!m_vertex_textures_dirty[i] && sampler_state->format_class != previous_format_class)
{
// Host details changed but RSX is not aware
m_graphics_state |= rsx::vertex_program_state_dirty;
}
bool replace = !vs_sampler_handles[i];
const VkBool32 unnormalized_coords = !!(tex.format() & CELL_GCM_TEXTURE_UN);
const auto min_lod = tex.min_lod();
const auto max_lod = tex.max_lod();
const auto wrap_s = vk::vk_wrap_mode(tex.wrap_s());
const auto wrap_t = vk::vk_wrap_mode(tex.wrap_t());
// NOTE: In vulkan, the border color can bypass the sample swizzle stage.
// Check the device properties to determine whether to pre-swizzle the colors or not.
const auto border_color = is_border_clamped_texture(tex)
? vk::border_color_t(get_border_color(tex))
: vk::border_color_t(VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK);
if (vs_sampler_handles[i])
{
if (!vs_sampler_handles[i]->matches(wrap_s, wrap_t, VK_SAMPLER_ADDRESS_MODE_REPEAT,
unnormalized_coords, 0.f, 1.f, min_lod, max_lod, VK_FILTER_NEAREST, VK_FILTER_NEAREST, VK_SAMPLER_MIPMAP_MODE_NEAREST, border_color))
{
replace = true;
}
}
if (replace)
{
vs_sampler_handles[i] = vk::get_resource_manager()->get_sampler(
*m_device,
vs_sampler_handles[i],
wrap_s, wrap_t, VK_SAMPLER_ADDRESS_MODE_REPEAT,
unnormalized_coords,
0.f, 1.f, min_lod, max_lod,
VK_FILTER_NEAREST, VK_FILTER_NEAREST, VK_SAMPLER_MIPMAP_MODE_NEAREST, border_color);
}
}
m_vertex_textures_dirty[i] = false;
continue;
}
const bool is_sampler_dirty = m_vertex_textures_dirty[i];
m_vertex_textures_dirty[i] = false;
if (!rsx::method_registers.vertex_textures[i].enabled())
{
*sampler_state = {};
continue;
}
*sampler_state = m_texture_cache.upload_texture(*m_current_command_buffer, tex, m_rtts);
if (!sampler_state->validate())
{
continue;
}
if (sampler_state->is_cyclic_reference || sampler_state->external_subresource_desc.do_not_cache)
{
check_for_cyclic_refs |= true;
}
if (!is_sampler_dirty)
{
if (sampler_state->format_class != previous_format_class)
{
// Host details changed but RSX is not aware
m_graphics_state |= rsx::vertex_program_state_dirty;
}
if (vs_sampler_handles[i])
{
continue;
}
}
const VkBool32 unnormalized_coords = !!(tex.format() & CELL_GCM_TEXTURE_UN);
const auto min_lod = tex.min_lod();
const auto max_lod = tex.max_lod();
const auto wrap_s = vk::vk_wrap_mode(tex.wrap_s());
const auto wrap_t = vk::vk_wrap_mode(tex.wrap_t());
// NOTE: In vulkan, the border color can bypass the sample swizzle stage.
// Check the device properties to determine whether to pre-swizzle the colors or not.
const auto border_color = is_border_clamped_texture(tex)
? vk::border_color_t(get_border_color(tex, false))
: vk::border_color_t(VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK);
if (vs_sampler_handles[i] &&
vs_sampler_handles[i]->matches(wrap_s, wrap_t, VK_SAMPLER_ADDRESS_MODE_REPEAT,
unnormalized_coords, 0.f, 1.f, min_lod, max_lod, VK_FILTER_NEAREST, VK_FILTER_NEAREST, VK_SAMPLER_MIPMAP_MODE_NEAREST, border_color))
{
continue;
}
vs_sampler_handles[i] = vk::get_resource_manager()->get_sampler(
*m_device,
vs_sampler_handles[i],
wrap_s, wrap_t, VK_SAMPLER_ADDRESS_MODE_REPEAT,
unnormalized_coords,
0.f, 1.f, min_lod, max_lod,
VK_FILTER_NEAREST, VK_FILTER_NEAREST, VK_SAMPLER_MIPMAP_MODE_NEAREST, border_color);
}
m_samplers_dirty.store(false);
@ -557,7 +634,9 @@ bool VKGSRender::bind_texture_env()
for (u32 textures_ref = current_fp_metadata.referenced_textures_mask, i = 0; textures_ref; textures_ref >>= 1, ++i)
{
if (!(textures_ref & 1))
{
continue;
}
vk::image_view* view = nullptr;
auto sampler_state = static_cast<vk::texture_cache::sampled_image_descriptor*>(fs_sampler_state[i].get());
@ -627,7 +706,9 @@ bool VKGSRender::bind_texture_env()
for (u32 textures_ref = current_vp_metadata.referenced_textures_mask, i = 0; textures_ref; textures_ref >>= 1, ++i)
{
if (!(textures_ref & 1))
{
continue;
}
if (!rsx::method_registers.vertex_textures[i].enabled())
{

View file

@ -244,8 +244,16 @@ namespace vk
{
switch (rgb_format)
{
// 8-bit
case VK_FORMAT_R8_UNORM:
return VK_FORMAT_R8_SRGB;
case VK_FORMAT_R8G8_UNORM:
return VK_FORMAT_R8G8_SRGB;
case VK_FORMAT_R8G8B8A8_UNORM:
return VK_FORMAT_R8G8B8A8_SRGB;
case VK_FORMAT_B8G8R8A8_UNORM:
return VK_FORMAT_B8G8R8A8_SRGB;
// DXT
case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
return VK_FORMAT_BC1_RGBA_SRGB_BLOCK;
case VK_FORMAT_BC2_UNORM_BLOCK:
@ -253,7 +261,30 @@ namespace vk
case VK_FORMAT_BC3_UNORM_BLOCK:
return VK_FORMAT_BC3_SRGB_BLOCK;
default:
return rgb_format;
return VK_FORMAT_UNDEFINED;
}
}
VkFormat get_compatible_snorm_format(VkFormat rgb_format)
{
switch (rgb_format)
{
// 8-bit
case VK_FORMAT_R8_UNORM:
return VK_FORMAT_R8_SNORM;
case VK_FORMAT_R8G8_UNORM:
return VK_FORMAT_R8G8_SNORM;
case VK_FORMAT_R8G8B8A8_UNORM:
return VK_FORMAT_R8G8B8A8_SNORM;
case VK_FORMAT_B8G8R8A8_UNORM:
return VK_FORMAT_B8G8R8A8_SNORM;
// 16-bit
case VK_FORMAT_R16_UNORM:
return VK_FORMAT_R16_SNORM;
case VK_FORMAT_R16G16_UNORM:
return VK_FORMAT_R16G16_SNORM;
default:
return VK_FORMAT_UNDEFINED;
}
}

View file

@ -19,6 +19,7 @@ namespace vk
VkFormat get_compatible_depth_surface_format(const gpu_formats_support& support, rsx::surface_depth_format2 format);
VkFormat get_compatible_sampler_format(const gpu_formats_support& support, u32 format);
VkFormat get_compatible_srgb_format(VkFormat rgb_format);
VkFormat get_compatible_snorm_format(VkFormat rgb_format);
u8 get_format_texel_width(VkFormat format);
std::pair<u8, u8> get_format_element_size(VkFormat format);
std::pair<bool, u32> get_format_convert_flags(VkFormat format);

View file

@ -336,6 +336,7 @@ void VKFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS)
m_shader_props.require_tex3D_ops = properties.has_tex3D;
m_shader_props.require_shadowProj_ops = properties.shadow_sampler_mask != 0 && properties.has_texShadowProj;
m_shader_props.require_alpha_kill = !!(m_prog.ctrl & RSX_SHADER_CONTROL_TEXTURE_ALPHA_KILL);
m_shader_props.require_color_format_convert = !!(m_prog.ctrl & RSX_SHADER_CONTROL_TEXTURE_FORMAT_CONVERT);
// Declare global constants
if (m_shader_props.require_fog_read)

View file

@ -45,7 +45,7 @@ namespace vk
for (const auto& e : image_list)
{
const VkImageSubresourceRange subres = { e->aspect(), 0, 1, 0, 1 };
image_views.push_back(std::make_unique<vk::image_view>(dev, e, VK_IMAGE_VIEW_TYPE_2D, vk::default_component_map, subres));
image_views.push_back(std::make_unique<vk::image_view>(dev, e, e->format(), VK_IMAGE_VIEW_TYPE_2D, vk::default_component_map, subres));
}
auto value = std::make_unique<vk::framebuffer_holder>(dev, renderpass, width, height, std::move(image_views));

View file

@ -1025,6 +1025,29 @@ namespace vk
const VkFormat vk_format = get_compatible_sampler_format(m_formats_support, gcm_format);
VkImageCreateFlags create_flags = is_cubemap ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0;
VkSharingMode sharing_mode = (flags & texture_create_flags::shareable) ? VK_SHARING_MODE_CONCURRENT : VK_SHARING_MODE_EXCLUSIVE;
rsx::simple_array<VkFormat> mutable_format_list;
if (flags & texture_create_flags::mutable_format)
{
const VkFormat snorm_fmt = get_compatible_snorm_format(vk_format);
const VkFormat srgb_fmt = get_compatible_srgb_format(vk_format);
if (snorm_fmt != VK_FORMAT_UNDEFINED)
{
mutable_format_list.push_back(snorm_fmt);
}
if (srgb_fmt != VK_FORMAT_UNDEFINED)
{
mutable_format_list.push_back(srgb_fmt);
}
if (!mutable_format_list.empty())
{
create_flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
mutable_format_list.push_back(vk_format);
}
}
if (auto found = find_cached_image(vk_format, width, height, depth, mipmaps, image_type, create_flags, usage_flags, sharing_mode))
{
@ -1037,9 +1060,16 @@ namespace vk
create_flags |= VK_IMAGE_CREATE_SHAREABLE_RPCS3;
}
VkFormatEx create_format = vk_format;
if (!mutable_format_list.empty())
{
create_format.pViewFormats = mutable_format_list.data();
create_format.viewFormatCount = mutable_format_list.size();
}
image = new vk::viewable_image(*m_device,
m_memory_types.device_local, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
image_type, vk_format,
image_type, create_format,
width, height, depth, mipmaps, layer, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_TILING_OPTIMAL, usage_flags, create_flags,
VMM_ALLOCATION_POOL_TEXTURE_CACHE, rsx::classify_format(gcm_format));
@ -1123,6 +1153,12 @@ namespace vk
const bool upload_async = rsx::get_current_renderer()->get_backend_config().supports_asynchronous_compute;
rsx::flags32_t create_flags = 0;
if (context == rsx::texture_upload_context::shader_read &&
!g_cfg.video.disable_hardware_texel_remapping)
{
create_flags |= texture_create_flags::mutable_format;
}
if (upload_async && ensure(g_fxo->try_get<AsyncTaskScheduler>())->is_host_mode())
{
create_flags |= texture_create_flags::do_not_reuse;

View file

@ -418,7 +418,8 @@ namespace vk
{
initialize_image_contents = 1,
do_not_reuse = 2,
shareable = 4
shareable = 4,
mutable_format = 8
};
void on_section_destroyed(cached_texture_section& tex) override;

View file

@ -39,6 +39,22 @@ namespace vk
VkDescriptorBufferInfoEx() = default;
VkDescriptorBufferInfoEx(const vk::buffer& buffer, u64 offset, u64 range);
};
struct VkFormatEx
{
VkFormat baseFormat = VK_FORMAT_UNDEFINED;
VkFormat* pViewFormats = nullptr;
u32 viewFormatCount = 0u;
VkFormatEx() = default;
VkFormatEx(VkFormat format)
: baseFormat(format)
{}
operator VkFormat() const { return baseFormat; }
bool is_mutable() const { return viewFormatCount != 0; }
};
}
// Re-export

View file

@ -23,7 +23,7 @@ namespace vk
break;
case VK_IMAGE_TYPE_2D:
longest_dim = std::max(info.extent.width, info.extent.height);
dim_limit = (info.flags == VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT) ? gpu_limits.maxImageDimensionCube : gpu_limits.maxImageDimension2D;
dim_limit = (info.flags & VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT) ? gpu_limits.maxImageDimensionCube : gpu_limits.maxImageDimension2D;
break;
case VK_IMAGE_TYPE_3D:
longest_dim = std::max({ info.extent.width, info.extent.height, info.extent.depth });
@ -47,7 +47,7 @@ namespace vk
const memory_type_info& memory_type,
u32 access_flags,
VkImageType image_type,
VkFormat format,
const VkFormatEx& format,
u32 width, u32 height, u32 depth,
u32 mipmaps, u32 layers,
VkSampleCountFlagBits samples,
@ -59,7 +59,6 @@ namespace vk
rsx::format_class format_class)
: m_device(dev)
{
info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
info.imageType = image_type;
info.format = format;
info.extent = { width, height, depth };
@ -84,6 +83,16 @@ namespace vk
info.pQueueFamilyIndices = concurrency_queue_families.data();
}
VkImageFormatListCreateInfo format_list = { .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO };
if (format.is_mutable())
{
info.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
format_list.pViewFormats = format.pViewFormats;
format_list.viewFormatCount = format.viewFormatCount;
info.pNext = &format_list;
}
create_impl(dev, access_flags, memory_type, allocation_pool);
m_storage_aspect = get_aspect_flags(format);
@ -100,6 +109,7 @@ namespace vk
}
m_format_class = format_class;
info.pNext = nullptr;
}
// TODO: Ctor that uses a provided memory heap
@ -333,10 +343,10 @@ namespace vk
create_impl();
}
image_view::image_view(VkDevice dev, vk::image* resource, VkImageViewType view_type, const VkComponentMapping& mapping, const VkImageSubresourceRange& range)
image_view::image_view(VkDevice dev, vk::image* resource, VkFormat format, VkImageViewType view_type, const VkComponentMapping& mapping, const VkImageSubresourceRange& range)
: m_device(dev), m_resource(resource)
{
info.format = resource->info.format;
info.format = format == VK_FORMAT_UNDEFINED ? resource->format() : format;
info.image = resource->value;
info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
info.components = mapping;
@ -350,7 +360,7 @@ namespace vk
info.viewType = VK_IMAGE_VIEW_TYPE_1D;
break;
case VK_IMAGE_TYPE_2D:
if (resource->info.flags == VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT)
if (resource->info.flags & VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT)
info.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
else if (resource->info.arrayLayers == 1)
info.viewType = VK_IMAGE_VIEW_TYPE_2D;
@ -379,6 +389,33 @@ namespace vk
vkDestroyImageView(m_device, value, nullptr);
}
image_view* image_view::as(VkFormat format)
{
if (this->format() == format)
{
return this;
}
auto self = this->m_root_view
? this->m_root_view
: this;
if (auto found = self->m_subviews.find(format);
found != self->m_subviews.end())
{
return found->second.get();
}
// Create a derived
auto view = std::make_unique<image_view>(m_device, info.image, info.viewType, format, info.components, info.subresourceRange);
view->m_resource = self->m_resource;
view->m_root_view = self;
auto ret = view.get();
self->m_subviews.emplace(format, std::move(view));
return ret;
}
u32 image_view::encoded_component_map() const
{
#if (VK_DISABLE_COMPONENT_SWIZZLE)
@ -479,7 +516,7 @@ namespace vk
const VkImageSubresourceRange range = { aspect() & mask, 0, info.mipLevels, 0, info.arrayLayers };
ensure(range.aspectMask);
auto view = std::make_unique<vk::image_view>(*g_render_device, this, VK_IMAGE_VIEW_TYPE_MAX_ENUM, real_mapping, range);
auto view = std::make_unique<vk::image_view>(*g_render_device, this, format(), VK_IMAGE_VIEW_TYPE_MAX_ENUM, real_mapping, range);
auto result = view.get();
views.emplace(storage_key, std::move(view));
return result;

View file

@ -5,6 +5,7 @@
#include "commands.h"
#include "device.h"
#include "ex.h"
#include "memory.h"
#include "unique_resource.h"
@ -46,14 +47,14 @@ namespace vk
VkComponentMapping native_component_map = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A };
VkImageLayout current_layout = VK_IMAGE_LAYOUT_UNDEFINED;
u32 current_queue_family = VK_QUEUE_FAMILY_IGNORED;
VkImageCreateInfo info = {};
VkImageCreateInfo info = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
std::shared_ptr<vk::memory_block> memory;
image(const vk::render_device& dev,
const memory_type_info& memory_type,
u32 access_flags,
VkImageType image_type,
VkFormat format,
const VkFormatEx& format,
u32 width, u32 height, u32 depth,
u32 mipmaps, u32 layers,
VkSampleCountFlagBits samples,
@ -112,21 +113,29 @@ namespace vk
image_view(VkDevice dev, VkImageViewCreateInfo create_info);
image_view(VkDevice dev, vk::image* resource,
VkFormat format = VK_FORMAT_UNDEFINED,
VkImageViewType view_type = VK_IMAGE_VIEW_TYPE_MAX_ENUM,
const VkComponentMapping& mapping = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A },
const VkImageSubresourceRange& range = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 });
~image_view();
vk::image_view* as(VkFormat new_format);
u32 encoded_component_map() const;
vk::image* image() const;
image_view(const image_view&) = delete;
image_view(image_view&&) = delete;
VkFormat format() const { return info.format; }
private:
std::unordered_map<VkFormat, std::unique_ptr<vk::image_view>> m_subviews;
VkDevice m_device;
vk::image* m_resource = nullptr;
vk::image_view* m_root_view = nullptr;
void create_impl();
void set_debug_name(std::string_view name);
@ -141,7 +150,8 @@ namespace vk
public:
using image::image;
virtual image_view* get_view(const rsx::texture_channel_remap_t& remap,
virtual image_view* get_view(
const rsx::texture_channel_remap_t& remap,
VkImageAspectFlags mask = VK_IMAGE_ASPECT_COLOR_BIT | VK_IMAGE_ASPECT_DEPTH_BIT);
void set_native_component_layout(VkComponentMapping new_layout);

View file

@ -7,6 +7,52 @@
namespace rsx
{
enum texture_control_bits : u32
{
GAMMA_A = 0,
GAMMA_R,
GAMMA_G,
GAMMA_B,
ALPHAKILL,
RENORMALIZE,
EXPAND_A,
EXPAND_R,
EXPAND_G,
EXPAND_B,
SEXT_A,
SEXT_R,
SEXT_G,
SEXT_B,
DEPTH_FLOAT,
DEPTH_COMPARE_OP,
DEPTH_COMPARE_1,
DEPTH_COMPARE_2,
FILTERED_MAG,
FILTERED_MIN,
UNNORMALIZED_COORDS,
CLAMP_TEXCOORDS_BIT,
WRAP_S,
WRAP_T,
WRAP_R,
FF_SIGNED_BIT,
FF_BIASED_RENORM_BIT,
FF_GAMMA_BIT,
FF_16BIT_CHANNELS_BIT,
// Meta
GAMMA_CTRL_MASK = (1 << GAMMA_R) | (1 << GAMMA_G) | (1 << GAMMA_B) | (1 << GAMMA_A),
GAMMA_RGB_MASK = (1 << GAMMA_R) | (1 << GAMMA_G) | (1 << GAMMA_B),
GAMMA_OFFSET = GAMMA_A,
EXPAND_MASK = (1 << EXPAND_R) | (1 << EXPAND_G) | (1 << EXPAND_B) | (1 << EXPAND_A),
EXPAND_OFFSET = EXPAND_A,
SEXT_MASK = (1 << SEXT_R) | (1 << SEXT_G) | (1 << SEXT_B) | (1 << SEXT_A),
SEXT_OFFSET = SEXT_A,
FORMAT_FEATURES_OFFSET = FF_SIGNED_BIT,
};
struct texture_channel_remap_t
{
u32 encoded = 0xDEAD;

View file

@ -468,6 +468,8 @@ namespace gcm
RSX_SHADER_CONTROL_DISABLE_EARLY_Z = 0x2000000, // Do not allow early-Z optimizations on this shader
RSX_SHADER_CONTROL_TEXTURE_FORMAT_CONVERT = 0x4000000, // Allow format conversions (BX2, SNORM, SRGB, RENORM)
// Meta
RSX_SHADER_CONTROL_META_USES_DISCARD = (RSX_SHADER_CONTROL_USES_KIL | RSX_SHADER_CONTROL_TEXTURE_ALPHA_KILL | RSX_SHADER_CONTROL_ALPHA_TEST | RSX_SHADER_CONTROL_POLYGON_STIPPLE | RSX_SHADER_CONTROL_ALPHA_TO_COVERAGE)
};

View file

@ -277,6 +277,7 @@ enum class localized_string_id
HOME_MENU_RELOAD_SECOND_SAVESTATE,
HOME_MENU_RELOAD_THIRD_SAVESTATE,
HOME_MENU_RELOAD_FOURTH_SAVESTATE,
HOME_MENU_TOGGLE_FULLSCREEN,
HOME_MENU_RECORDING,
HOME_MENU_TROPHIES,
HOME_MENU_TROPHY_LIST_TITLE,

View file

@ -176,6 +176,7 @@ struct cfg_root : cfg::node
cfg::_bool disable_async_host_memory_manager{ this, "Disable Asynchronous Memory Manager", false, true };
cfg::_enum<output_scaling_mode> output_scaling{ this, "Output Scaling Mode", output_scaling_mode::bilinear, true };
cfg::_bool record_with_overlays{ this, "Record With Overlays", true, true };
cfg::_bool disable_hardware_texel_remapping{ this, "Disable Hardware ColorSpace Remapping", false, true };
struct node_vk : cfg::node
{

View file

@ -11,7 +11,7 @@ namespace rpcs3
std::string title_id;
std::string renderer;
std::string vulkan_adapter;
double fps = .0;
double fps = 0.0;
};
std::string get_formatted_title(const title_format_data& title_data);

View file

@ -1,3 +1,4 @@
#include "stdafx.h"
#include "mouse_gyro_handler.h"
#include <QEvent>
@ -7,53 +8,57 @@
#include <algorithm>
LOG_CHANNEL(gui_log, "GUI");
void mouse_gyro_handler::clear()
{
active = false;
reset = false;
gyro_x = DEFAULT_MOTION_X;
gyro_y = DEFAULT_MOTION_Y;
gyro_z = DEFAULT_MOTION_Z;
m_active = false;
m_reset = false;
m_gyro_x = DEFAULT_MOTION_X;
m_gyro_y = DEFAULT_MOTION_Y;
m_gyro_z = DEFAULT_MOTION_Z;
}
bool mouse_gyro_handler::toggle_enabled()
{
enabled = !enabled;
m_enabled = !m_enabled;
clear();
return enabled;
return m_enabled;
}
void mouse_gyro_handler::set_gyro_active()
{
active = true;
gui_log.notice("Mouse-based gyro activated");
m_active = true;
}
void mouse_gyro_handler::set_gyro_reset()
{
active = false;
reset = true;
gui_log.notice("Mouse-based gyro deactivated");
m_active = false;
m_reset = true;
}
void mouse_gyro_handler::set_gyro_xz(s32 off_x, s32 off_y)
{
if (!active)
if (!m_active)
return;
gyro_x = static_cast<u16>(std::clamp(off_x, 0, DEFAULT_MOTION_X * 2 - 1));
gyro_z = static_cast<u16>(std::clamp(off_y, 0, DEFAULT_MOTION_Z * 2 - 1));
m_gyro_x = static_cast<u16>(std::clamp(off_x, 0, DEFAULT_MOTION_X * 2 - 1));
m_gyro_z = static_cast<u16>(std::clamp(off_y, 0, DEFAULT_MOTION_Z * 2 - 1));
}
void mouse_gyro_handler::set_gyro_y(s32 steps)
{
if (!active)
if (!m_active)
return;
gyro_y = static_cast<u16>(std::clamp(gyro_y + steps, 0, DEFAULT_MOTION_Y * 2 - 1));
m_gyro_y = static_cast<u16>(std::clamp(m_gyro_y + steps, 0, DEFAULT_MOTION_Y * 2 - 1));
}
void mouse_gyro_handler::handle_event(QEvent* ev, const QWindow& win)
{
if (!enabled)
if (!m_enabled)
return;
// Mouse-based motion input.
@ -119,15 +124,12 @@ void mouse_gyro_handler::handle_event(QEvent* ev, const QWindow& win)
void mouse_gyro_handler::apply_gyro(const std::shared_ptr<Pad>& pad)
{
if (!enabled)
return;
if (!pad || !pad->is_connected())
if (!m_enabled || !pad || !pad->is_connected())
return;
// Inject mouse-based motion sensor values into pad sensors for gyro emulation.
// The Qt frontend maps cursor offset and wheel input to absolute motion values while RMB is held.
if (reset)
if (m_reset)
{
// RMB released → reset motion
pad->m_sensors[0].m_value = DEFAULT_MOTION_X;
@ -139,8 +141,8 @@ void mouse_gyro_handler::apply_gyro(const std::shared_ptr<Pad>& pad)
{
// RMB held → accumulate motion
// Axes have been chosen as tested in Sly 4 minigames. Top-down view motion uses X/Z axes.
pad->m_sensors[0].m_value = gyro_x; // Mouse X → Motion X
pad->m_sensors[1].m_value = gyro_y; // Mouse Wheel → Motion Y
pad->m_sensors[2].m_value = gyro_z; // Mouse Y → Motion Z
pad->m_sensors[0].m_value = m_gyro_x; // Mouse X → Motion X
pad->m_sensors[1].m_value = m_gyro_y; // Mouse Wheel → Motion Y
pad->m_sensors[2].m_value = m_gyro_z; // Mouse Y → Motion Z
}
}

View file

@ -10,24 +10,23 @@ class QWindow;
// Mouse-based motion sensor emulation state.
class mouse_gyro_handler
{
private:
atomic_t<bool> enabled = false; // Whether mouse-based gyro emulation mode has been enabled by using the associated hotkey
atomic_t<bool> active = false; // Whether right mouse button is currently held (gyro active)
atomic_t<bool> reset = false; // One-shot reset request on right mouse button release
atomic_t<s32> gyro_x = DEFAULT_MOTION_X; // Accumulated from mouse X position relative to center
atomic_t<s32> gyro_y = DEFAULT_MOTION_Y; // Accumulated from mouse wheel delta
atomic_t<s32> gyro_z = DEFAULT_MOTION_Z; // Accumulated from mouse Y position relative to center
void set_gyro_active();
void set_gyro_reset();
void set_gyro_xz(s32 off_x, s32 off_y);
void set_gyro_y(s32 steps);
public:
void clear();
bool toggle_enabled();
void handle_event(QEvent* ev, const QWindow& win);
void apply_gyro(const std::shared_ptr<Pad>& pad);
private:
atomic_t<bool> m_enabled = false; // Whether mouse-based gyro emulation mode has been enabled by using the associated hotkey
atomic_t<bool> m_active = false; // Whether right mouse button is currently held (gyro active)
atomic_t<bool> m_reset = false; // One-shot reset request on right mouse button release
atomic_t<s32> m_gyro_x = DEFAULT_MOTION_X; // Accumulated from mouse X position relative to center
atomic_t<s32> m_gyro_y = DEFAULT_MOTION_Y; // Accumulated from mouse wheel delta
atomic_t<s32> m_gyro_z = DEFAULT_MOTION_Z; // Accumulated from mouse Y position relative to center
void set_gyro_active();
void set_gyro_reset();
void set_gyro_xz(s32 off_x, s32 off_y);
void set_gyro_y(s32 steps);
};

View file

@ -108,6 +108,7 @@ enum class emu_settings_type
DisableAsyncHostMM,
UseReBAR,
RecordWithOverlays,
DisableHWTexelRemapping,
// Performance Overlay
PerfOverlayEnabled,
@ -312,6 +313,7 @@ inline static const std::map<emu_settings_type, cfg_location> settings_location
{ emu_settings_type::ForceHwMSAAResolve, { "Video", "Force Hardware MSAA Resolve"}},
{ emu_settings_type::DisableAsyncHostMM, { "Video", "Disable Asynchronous Memory Manager"}},
{ emu_settings_type::RecordWithOverlays, { "Video", "Record With Overlays"}},
{ emu_settings_type::DisableHWTexelRemapping, { "Video", "Disable Hardware ColorSpace Remapping"}},
// Vulkan
{ emu_settings_type::VulkanAsyncTextureUploads, { "Video", "Vulkan", "Asynchronous Texture Streaming 2"}},

View file

@ -51,6 +51,7 @@ LOG_CHANNEL(screenshot_log, "SCREENSHOT");
LOG_CHANNEL(mark_log, "MARK");
LOG_CHANNEL(gui_log, "GUI");
extern atomic_t<bool> g_user_asked_for_fullscreen;
extern atomic_t<bool> g_user_asked_for_recording;
extern atomic_t<bool> g_user_asked_for_screenshot;
extern atomic_t<bool> g_user_asked_for_frame_capture;
@ -150,6 +151,9 @@ gs_frame::gs_frame(QScreen* screen, const QRect& geometry, const QIcon& appIcon,
gs_frame::~gs_frame()
{
g_user_asked_for_screenshot = false;
g_user_asked_for_recording = false;
g_user_asked_for_frame_capture = false;
g_user_asked_for_fullscreen = false;
pad::g_home_menu_requested = false;
// Save active screen to gui settings
@ -201,12 +205,12 @@ void gs_frame::update_shortcuts()
}
}
void gs_frame::paintEvent(QPaintEvent *event)
void gs_frame::paintEvent(QPaintEvent* event)
{
Q_UNUSED(event)
}
void gs_frame::showEvent(QShowEvent *event)
void gs_frame::showEvent(QShowEvent* event)
{
// We have to calculate new window positions, since the frame is only known once the window was created.
// We will try to find the originally requested dimensions if possible by moving the frame.
@ -801,7 +805,7 @@ f64 gs_frame::client_display_rate()
return rate;
}
void gs_frame::flip(draw_context_t, bool /*skip_frame*/)
void gs_frame::flip(draw_context_t /*context*/, bool /*skip_frame*/)
{
static Timer fps_t;
@ -840,6 +844,14 @@ void gs_frame::flip(draw_context_t, bool /*skip_frame*/)
toggle_recording();
});
}
if (g_user_asked_for_fullscreen.exchange(false))
{
Emu.CallFromMainThread([this]()
{
toggle_fullscreen();
});
}
}
bool gs_frame::can_consume_frame() const

View file

@ -85,8 +85,8 @@ public:
protected:
video_renderer m_renderer;
void paintEvent(QPaintEvent *event) override;
void showEvent(QShowEvent *event) override;
void paintEvent(QPaintEvent* event) override;
void showEvent(QShowEvent* event) override;
void close() override;
void reset() override;

View file

@ -298,6 +298,7 @@ private:
case localized_string_id::HOME_MENU_RELOAD_SECOND_SAVESTATE: return tr("Reload Second-To-Last Emulation State");
case localized_string_id::HOME_MENU_RELOAD_THIRD_SAVESTATE: return tr("Reload Third-To-Last Emulation State");
case localized_string_id::HOME_MENU_RELOAD_FOURTH_SAVESTATE: return tr("Reload Fourth-To-Last Emulation State");
case localized_string_id::HOME_MENU_TOGGLE_FULLSCREEN: return tr("Toggle Fullscreen");
case localized_string_id::HOME_MENU_RECORDING: return tr("Start/Stop Recording");
case localized_string_id::HOME_MENU_TROPHIES: return tr("Trophies");
case localized_string_id::HOME_MENU_TROPHY_LIST_TITLE: return tr("Trophy Progress: %0").arg(std::forward<Args>(args)...);

View file

@ -1555,6 +1555,9 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
m_emu_settings->EnhanceCheckBox(ui->enableSpuEventsBusyLoop, emu_settings_type::EnabledSPUEventsBusyLoop);
SubscribeTooltip(ui->enableSpuEventsBusyLoop, tooltips.settings.enable_spu_events_busy_loop);
m_emu_settings->EnhanceCheckBox(ui->disableHardwareTexelRemapping, emu_settings_type::DisableHWTexelRemapping);
SubscribeTooltip(ui->disableHardwareTexelRemapping, tooltips.settings.disable_hw_texel_remapping);
// Comboboxes
m_emu_settings->EnhanceComboBox(ui->maxSPURSThreads, emu_settings_type::MaxSPURSThreads, true);

View file

@ -4316,6 +4316,13 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="disableHardwareTexelRemapping">
<property name="text">
<string>Disable Hardware ColorSpace Remapping</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacerDebugGPU">
<property name="orientation">

View file

@ -127,6 +127,7 @@ public:
const QString accurate_ppu_128_loop = tr("When enabled, PPU atomic operations will operate on entire cache line data, as opposed to a single 64bit block of memory when disabled.\nNumerical values control whether or not to enable the accurate version based on the atomic operation's length.");
const QString enable_performance_report = tr("Measure certain events and print a chart after the emulator is stopped. Don't enable if not asked to.");
const QString num_ppu_threads = tr("Affects maximum amount of PPU threads running concurrently, the value of 1 has very low compatibility with games.\n2 is the default, if unsure do not modify this setting.");
const QString disable_hw_texel_remapping = tr("Disables use of hardware-native color-space remapping formats such as _sRGB and _SNORM suffixes.\nDisabling this option increases accuracy compared to PS3 but can also introduce some noise due to how the software emulation works.");
// emulator

View file

@ -22,3 +22,7 @@
#include <unordered_map> // IWYU pragma: export
#include <algorithm> // IWYU pragma: export
#include <string_view> // IWYU pragma: export
#if defined(__INTELLISENSE__) && !defined(LLVM_AVAILABLE)
#define LLVM_AVAILABLE
#endif