mirror of
https://github.com/ARMSX2/ARMSX3.git
synced 2026-08-24 16:58:52 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82f21b16d2 | ||
|
|
2ed8442c11 | ||
|
|
ab58fb087b | ||
|
|
9829bc5402 | ||
|
|
0fb1757a84 | ||
|
|
53e225cf96 | ||
|
|
d9a0481dcb | ||
|
|
a2f0059551 | ||
|
|
cbee3cd44b | ||
|
|
5d71742da9 | ||
|
|
21a64b9eb7 | ||
|
|
39571dc7c9 | ||
|
|
bab81aa23e | ||
|
|
8a6c96745a | ||
|
|
358101c47b | ||
|
|
c4eff69711 | ||
|
|
5d2601599e | ||
|
|
e5e280cd3f | ||
|
|
c122edd638 | ||
|
|
d12d8782af | ||
|
|
64da425e81 | ||
|
|
b78bae0b9f | ||
|
|
ca25fbaa5d | ||
|
|
323e2d35a2 | ||
|
|
57ecb42433 | ||
|
|
84ceef9717 | ||
|
|
0dd3d6528c | ||
|
|
ddd82ecada | ||
|
|
7973b8ac6d |
@@ -34,8 +34,8 @@ android {
|
||||
// agree -- an APK that installs below its core's target is a dlopen failure at boot.
|
||||
minSdk = (project.findProperty("armsx3.minSdk") as String?)?.toInt() ?: 33
|
||||
targetSdk = 37
|
||||
versionCode = 19
|
||||
versionName = "0.9.3.1"
|
||||
versionCode = 20
|
||||
versionName = "0.9.4"
|
||||
|
||||
// ARMSX2's UI reads these. STORAGE_ALL_FILES gates the all-files storage path in
|
||||
// onboarding; IN_APP_UPDATER gates the in-app GitHub-release updater.
|
||||
|
||||
@@ -572,6 +572,10 @@ val EN: Map<String, String> = mapOf(
|
||||
"rpcn.hosts.removed" to "Server removed.",
|
||||
"rpcn.hosts.resetDone" to "Back to the official server (np.rpcs3.net).",
|
||||
"rpcn.hosts.selected" to "Switched server. Test sign-in to check it.",
|
||||
"rpcn.need.username" to "Enter your username first.",
|
||||
"rpcn.need.password" to "Enter your password first \u2014 Save clears the box, so type it again.",
|
||||
"rpcn.need.newPassword" to "Enter the new password you want to set.",
|
||||
"rpcn.need.email" to "Enter the email address for this account.",
|
||||
"rpcn.account.saved" to "Account saved:",
|
||||
"rpcn.account.saved.note" to "It stays saved between restarts. RPCN keeps no permanent session, so games sign in with this account when they go online \u2014 there is nothing to log into again.",
|
||||
"rpcn.account.connected" to "Signed in as",
|
||||
|
||||
@@ -85,6 +85,10 @@ fun RpcnAccountSection() {
|
||||
val msgCreateHint = str("rpcn.create.hint")
|
||||
val msgTokenSent = str("rpcn.token.sent")
|
||||
val msgResetSent = str("rpcn.reset.sent")
|
||||
val msgNeedUsername = str("rpcn.need.username")
|
||||
val msgNeedPassword = str("rpcn.need.password")
|
||||
val msgNeedNewPassword = str("rpcn.need.newPassword")
|
||||
val msgNeedEmail = str("rpcn.need.email")
|
||||
val msgHostAdded = str("rpcn.hosts.added")
|
||||
val msgHostRemoved = str("rpcn.hosts.removed")
|
||||
val msgHostsReset = str("rpcn.hosts.resetDone")
|
||||
@@ -329,7 +333,12 @@ fun RpcnAccountSection() {
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 6.dp),
|
||||
)
|
||||
|
||||
if (creating) {
|
||||
// Always shown, not only while creating an account.
|
||||
//
|
||||
// Password reset sends this address to the server, and hiding the box outside creation
|
||||
// mode meant Reset password posted an EMPTY email. The server rejects that whole query
|
||||
// as Malformed, which reached the user as the meaningless "Server error 1".
|
||||
run {
|
||||
OutlinedTextField(
|
||||
value = email,
|
||||
onValueChange = { email = it },
|
||||
@@ -385,6 +394,12 @@ fun RpcnAccountSection() {
|
||||
if (!creating) {
|
||||
creating = true
|
||||
status = msgCreateHint
|
||||
} else if (npid.isBlank()) {
|
||||
status = msgNeedUsername
|
||||
} else if (password.isBlank()) {
|
||||
status = msgNeedPassword
|
||||
} else if (email.isBlank()) {
|
||||
status = msgNeedEmail
|
||||
} else {
|
||||
Rpcs3Bridge.rpcnSetConfig(host.trim(), "", "", "")
|
||||
run(msgCreated) {
|
||||
@@ -395,20 +410,39 @@ fun RpcnAccountSection() {
|
||||
}
|
||||
}) { Text(if (creating) str("rpcn.create.go") else str("rpcn.create")) }
|
||||
|
||||
// Checked here rather than sent and rejected. A required field left empty makes the
|
||||
// whole query Malformed, and the server's answer to that is an error number with no
|
||||
// way for the user to know which box to fill in. Note the password box is CLEARED by
|
||||
// Save, so "I typed it a moment ago" is not the same as "it is in the field now".
|
||||
TextButton(enabled = !busy, onClick = {
|
||||
Rpcs3Bridge.rpcnSetConfig(host.trim(), "", "", "")
|
||||
run(msgTokenSent) {
|
||||
Rpcs3Bridge.rpcnResendToken(npid.trim(), password)
|
||||
when {
|
||||
npid.isBlank() -> status = msgNeedUsername
|
||||
password.isBlank() -> status = msgNeedPassword
|
||||
else -> {
|
||||
Rpcs3Bridge.rpcnSetConfig(host.trim(), "", "", "")
|
||||
run(msgTokenSent) {
|
||||
Rpcs3Bridge.rpcnResendToken(npid.trim(), password)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) { Text(str("rpcn.token.resend")) }
|
||||
|
||||
TextButton(enabled = !busy, onClick = {
|
||||
Rpcs3Bridge.rpcnSetConfig(host.trim(), "", "", "")
|
||||
run(msgResetSent) {
|
||||
if (token.isBlank()) {
|
||||
Rpcs3Bridge.rpcnSendResetToken(npid.trim(), email.trim())
|
||||
} else {
|
||||
Rpcs3Bridge.rpcnResetPassword(npid.trim(), token.trim(), password)
|
||||
when {
|
||||
npid.isBlank() -> status = msgNeedUsername
|
||||
// No token yet: ask the server to email one, which needs the address.
|
||||
token.isBlank() && email.isBlank() -> status = msgNeedEmail
|
||||
// Token in hand: this is the actual reset, so a new password is required.
|
||||
token.isNotBlank() && password.isBlank() -> status = msgNeedNewPassword
|
||||
else -> {
|
||||
Rpcs3Bridge.rpcnSetConfig(host.trim(), "", "", "")
|
||||
run(msgResetSent) {
|
||||
if (token.isBlank()) {
|
||||
Rpcs3Bridge.rpcnSendResetToken(npid.trim(), email.trim())
|
||||
} else {
|
||||
Rpcs3Bridge.rpcnResetPassword(npid.trim(), token.trim(), password)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}) { Text(str("rpcn.reset")) }
|
||||
|
||||
@@ -2784,7 +2784,17 @@ static std::string rpcn_describe(rpcn::ErrorType error) {
|
||||
case rpcn::ErrorType::LoginAlreadyLoggedIn:
|
||||
return "That account is already logged in somewhere else.";
|
||||
case rpcn::ErrorType::LoginError: return "The server refused the login.";
|
||||
default: return fmt::format("Server error %d.", static_cast<int>(error));
|
||||
|
||||
// These three were reaching users as "Server error 1" and the like, which says nothing about
|
||||
// what to do. Malformed in particular is OUR fault, not the server's: it means a required
|
||||
// field was sent empty, which is exactly what Reset password did when the email box was
|
||||
// hidden outside account creation.
|
||||
case rpcn::ErrorType::Malformed:
|
||||
return "The request was incomplete -- a required field was empty. This is a bug; please "
|
||||
"report which button you pressed.";
|
||||
case rpcn::ErrorType::Invalid:
|
||||
return "The server rejected that request as out of order. Try Test sign-in first.";
|
||||
default: return fmt::format("Unexpected server error %d.", static_cast<int>(error));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3354,15 +3364,29 @@ extern "C" int _rpcsx_boot(std::string_view path_) {
|
||||
// Kill() is idempotent and returns quickly when already stopped. The bound is generous
|
||||
// because a real teardown joins the RSX and SPU threads and flushes caches; past it we
|
||||
// boot anyway and let BootGame report a normal error rather than hanging the UI.
|
||||
if (!Emu.IsStopped()) {
|
||||
// IsStopped(true), not IsStopped().
|
||||
//
|
||||
// The default overload is `m_state <= system_state::stopping`, so it answers TRUE while the
|
||||
// previous VM is still stopping -- and loading. This whole block was therefore skipped exactly
|
||||
// when it was needed: Kill() signals the threads and hands the actual joining to a detached
|
||||
// "Emulation Join Thread", the state reaches stopping immediately, and the guard read that as
|
||||
// stopped. Observed on device as a boot starting three seconds into a teardown that never
|
||||
// finished, with the join thread still waiting on an SPU interrupt thread seventeen seconds
|
||||
// later, seven SPUs parked in EXIT|w|G-PAUSE, and the app frozen on the last frame of the
|
||||
// previous game. There was no "previous VM still running" line in the log, because the check
|
||||
// passed.
|
||||
//
|
||||
// The `true` overload requires system_state::stopped, which is only reached once that join
|
||||
// thread has run to completion.
|
||||
if (!Emu.IsStopped(true)) {
|
||||
rpcsx_android.notice("boot: previous VM still running, stopping it first");
|
||||
Emu.Kill();
|
||||
|
||||
for (int waited = 0; !Emu.IsStopped() && waited < 10000; waited += 20) {
|
||||
for (int waited = 0; !Emu.IsStopped(true) && waited < 10000; waited += 20) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
}
|
||||
|
||||
if (!Emu.IsStopped()) {
|
||||
if (!Emu.IsStopped(true)) {
|
||||
rpcsx_android.error("boot: previous VM did not stop in time, booting anyway");
|
||||
}
|
||||
}
|
||||
|
||||
+29
-7
@@ -1269,10 +1269,14 @@ void package_reader::extract_worker()
|
||||
while (read_size < size)
|
||||
{
|
||||
const u64 block_size = std::min<u64>(BUF_SIZE, size - read_size);
|
||||
const u64 available_buffer_size = buffer.size() - read_size;
|
||||
u64 available_buffer_size = original_size - read_size;
|
||||
|
||||
if (buffer.data() == ptr)
|
||||
{
|
||||
available_buffer_size = buffer.size() - read_size;
|
||||
ensure(buffer.size() == original_size + BUF_PADDING);
|
||||
}
|
||||
|
||||
ensure(buffer.data() == ptr);
|
||||
ensure(buffer.size() == original_size + BUF_PADDING);
|
||||
ensure(available_buffer_size >= block_size);
|
||||
|
||||
const usz advance_size = decrypt(entry.file_offset + pos, block_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), std::span<u8>{static_cast<u8*>(ptr) + read_size, available_buffer_size});
|
||||
@@ -1431,14 +1435,32 @@ package_install_result package_reader::extract_data(std::deque<package_reader>&
|
||||
if (reader.m_num_failures == 0)
|
||||
{
|
||||
const usz thread_count = std::min<usz>(utils::get_thread_count(), reader.m_install_entries.size());
|
||||
atomic_t<u32> num_threads_succeeded {0}; // Check if any thread didn't finish. For example when hitting an exception.
|
||||
|
||||
named_thread_group workers("PKG Installer "sv, std::max<u32>(::narrow<u32>(thread_count), 1) - 1, [&]()
|
||||
if (thread_count > 1)
|
||||
{
|
||||
named_thread_group workers("PKG Installer "sv, ::narrow<u32>(thread_count) - 1, [&]()
|
||||
{
|
||||
reader.extract_worker();
|
||||
num_threads_succeeded++;
|
||||
});
|
||||
|
||||
reader.extract_worker();
|
||||
num_threads_succeeded++;
|
||||
|
||||
workers.join();
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.extract_worker();
|
||||
});
|
||||
num_threads_succeeded++;
|
||||
}
|
||||
|
||||
reader.extract_worker();
|
||||
workers.join();
|
||||
if (thread_count != num_threads_succeeded)
|
||||
{
|
||||
pkg_log.error("%d thread(s) failed with an exception!", thread_count - num_threads_succeeded);
|
||||
reader.m_num_failures++;
|
||||
}
|
||||
}
|
||||
|
||||
num_failures += reader.m_num_failures;
|
||||
|
||||
@@ -408,7 +408,7 @@ std::pair<vm::addr_t, u32> vm::hle_malloc_allocator::alloc(u32 size, u32 align)
|
||||
return { vm::cast(addr), addr ? size : 0 };
|
||||
}
|
||||
|
||||
void vm::hle_malloc_allocator::dealloc(u32 addr, u32 size) noexcept
|
||||
void vm::hle_malloc_allocator::dealloc(u32 addr, u32 /*size*/) noexcept
|
||||
{
|
||||
const auto ppu = ensure(cpu_thread::get_current<ppu_thread>());
|
||||
ppu_execute<&_sys_free>(*ppu, addr);
|
||||
|
||||
@@ -2339,8 +2339,14 @@ void ppu_thread::cpu_on_stop()
|
||||
ppu_log.notice("thread context: %s", ret);
|
||||
}
|
||||
|
||||
if (is_stopped())
|
||||
// Report once. Nothing guarantees this hook runs a single time, and a PPU thread that
|
||||
// re-enters the stop path without exiting reports on every pass -- measured at ~100,000
|
||||
// lines per second from one thread, which saturates the log writer and stalls the whole
|
||||
// emulator during shutdown on Android, where the log goes to external storage.
|
||||
if (is_stopped() && !perf_stats_reported)
|
||||
{
|
||||
perf_stats_reported = true;
|
||||
|
||||
if (last_succ == 0 && last_fail == 0 && exec_bytes == 0)
|
||||
{
|
||||
perf_log.notice("PPU thread perf stats are not available.");
|
||||
|
||||
@@ -317,6 +317,11 @@ public:
|
||||
u64 last_succ = 0;
|
||||
u64 exec_bytes = 0; // Amount of "bytes" executed (4 for each instruction)
|
||||
|
||||
// cpu_on_stop() is a teardown hook with nothing enforcing that it runs once, and a thread
|
||||
// that re-enters the stop path reports its perf stats again every time. Deliberately not
|
||||
// serialized: it describes this run's reporting, not guest state.
|
||||
bool perf_stats_reported = false;
|
||||
|
||||
u32 dbg_step_pc = 0;
|
||||
atomic_t<ppu_debugger_mode> debugger_mode{};
|
||||
|
||||
|
||||
@@ -40,9 +40,6 @@ bool spu_thread::read_reg(const u32 addr, u32& value)
|
||||
{
|
||||
const u32 offset = addr - (RAW_SPU_BASE_ADDR + RAW_SPU_OFFSET * index) - RAW_SPU_PROB_OFFSET;
|
||||
|
||||
raw_spu_log_stats_t stats{};
|
||||
stats.mmio_offset = offset;
|
||||
|
||||
const auto [old_stats, is_changed] = mmio_stats.fetch_op([&](raw_spu_log_stats_t& old)
|
||||
{
|
||||
if (old.mmio_offset == offset)
|
||||
|
||||
@@ -1466,7 +1466,7 @@ void spu_cache::initialize(bool build_existing_cache)
|
||||
{
|
||||
if (ls[start_new / 4] && g_spu_itype.decode(ls[start_new / 4]) != spu_itype::UNK)
|
||||
{
|
||||
spu_log.notice("Precompiling fallthrough to 0x%05x", start_new);
|
||||
spu_log.trace("Precompiling fallthrough to 0x%05x", start_new);
|
||||
func2 = compiler->analyse(ls.data(), start_new, &targets);
|
||||
block_addr = start_new;
|
||||
continue;
|
||||
@@ -1530,7 +1530,7 @@ void spu_cache::initialize(bool build_existing_cache)
|
||||
}
|
||||
|
||||
|
||||
spu_log.notice("Precompiling filler space at 0x%05x (next=0x%05x)", new_entry, next_func);
|
||||
spu_log.trace("Precompiling filler space at 0x%05x (next=0x%05x)", new_entry, next_func);
|
||||
func2 = compiler->analyse(ls.data(), new_entry, &targets);
|
||||
block_addr = new_entry;
|
||||
}
|
||||
@@ -2139,7 +2139,12 @@ spu_function_t spu_runtime::rebuild_ubertrampoline(u32 id_inst)
|
||||
if (w.level >= w.beg->first.size() || w.level >= it->first.size())
|
||||
{
|
||||
// If functions cannot be compared, assume smallest function
|
||||
spu_log.error("Trampoline simplified at ??? (level=%u)", w.level);
|
||||
// Routine control-flow simplification, not a failure -- it was at error level
|
||||
// and fired ~1500 times in a 15 minute session. Every diagnostic in this
|
||||
// recompiler is per-block or per-instruction, which upstream can afford and a
|
||||
// phone writing to /sdcard cannot: the burst lands exactly while a game is
|
||||
// already stalling to compile. Still reachable by raising the SPU channel.
|
||||
spu_log.trace("Trampoline simplified at ??? (level=%u)", w.level);
|
||||
#if defined(ARCH_X64)
|
||||
make_jump(0xe9, w.beg->second); // jmp rel32
|
||||
#elif defined(ARCH_ARM64)
|
||||
@@ -2178,7 +2183,7 @@ spu_function_t spu_runtime::rebuild_ubertrampoline(u32 id_inst)
|
||||
|
||||
if (it == m_flat_list.end())
|
||||
{
|
||||
spu_log.error("Trampoline simplified (II) at ??? (level=%u)", w.level);
|
||||
spu_log.trace("Trampoline simplified (II) at ??? (level=%u)", w.level);
|
||||
#if defined(ARCH_X64)
|
||||
make_jump(0xe9, w.beg->second); // jmp rel32
|
||||
#elif defined(ARCH_ARM64)
|
||||
@@ -6069,7 +6074,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
|
||||
getllar_starts[previous.lsa_pc] = true;
|
||||
g_fxo->get<putllc16_statistics_t>().breaking_reason[cause]++;
|
||||
|
||||
if (!spu_log.notice)
|
||||
if (!spu_log.trace)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -6109,7 +6114,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
|
||||
}
|
||||
|
||||
fmt::append(tracing, " of %d failures", fail_count);
|
||||
spu_log.notice("%s\n%s", break_error, tracing);
|
||||
spu_log.trace("%s\n%s", break_error, tracing);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6126,7 +6131,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
|
||||
|
||||
g_fxo->get<rchcnt_statistics_t>().breaking_reason[cause]++;
|
||||
|
||||
if (!spu_log.notice)
|
||||
if (!spu_log.trace)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -6166,7 +6171,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
|
||||
}
|
||||
|
||||
fmt::append(tracing, " of %d failures", fail_count);
|
||||
spu_log.notice("%s\n%s", break_error, tracing);
|
||||
spu_log.trace("%s\n%s", break_error, tracing);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6176,7 +6181,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
|
||||
{
|
||||
g_fxo->get<reduced_statistics_t>().breaking_reason[cause]++;
|
||||
|
||||
if (!spu_log.notice)
|
||||
if (!spu_log.trace)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -6221,12 +6226,12 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
|
||||
}
|
||||
|
||||
fmt::append(tracing, " of %d failures", fail_count);
|
||||
spu_log.notice("%s\n%s", break_error, tracing);
|
||||
spu_log.trace("%s\n%s", break_error, tracing);
|
||||
|
||||
std::string block_dump;
|
||||
this->dump(result, block_dump, previous.loop_pc, previous.loop_end + 1);
|
||||
|
||||
spu_log.notice("SPU Block Dump:\n%s", block_dump);
|
||||
spu_log.trace("SPU Block Dump:\n%s", block_dump);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6437,7 +6442,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
|
||||
{
|
||||
if (!std::exchange(logged_block[target_pc / 4], true))
|
||||
{
|
||||
spu_log.notice("SPU block is a loop at [0x%05x -> 0x%05x]", state_it->pc, target_pc);
|
||||
spu_log.trace("SPU block is a loop at [0x%05x -> 0x%05x]", state_it->pc, target_pc);
|
||||
}
|
||||
|
||||
state_it->parent_target_index++;
|
||||
@@ -7959,7 +7964,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
|
||||
if (getllar_starts.emplace(atomic16->lsa_pc, false).second)
|
||||
{
|
||||
g_fxo->get<putllc16_statistics_t>().all++;
|
||||
spu_log.notice("[0x%05x] GETLLAR pattern entry point", pos);
|
||||
spu_log.trace("[0x%05x] GETLLAR pattern entry point", pos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9287,7 +9292,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
|
||||
add_pattern(inst_attr::putllc16, pattern.put_pc - result.entry_point, value.data);
|
||||
}
|
||||
|
||||
spu_log.success("PUTLLC16 Pattern Detected! (mem_count=%d, put_pc=0x%x, pc_rel=%d, offset=0x%x, const=%u, two_regs=%d, reg=%u, runtime=%d, 0x%x-%s, pattern-hash=%s) (putllc0=%d, putllc16+0=%d, all=%d)"
|
||||
spu_log.trace("PUTLLC16 Pattern Detected! (mem_count=%d, put_pc=0x%x, pc_rel=%d, offset=0x%x, const=%u, two_regs=%d, reg=%u, runtime=%d, 0x%x-%s, pattern-hash=%s) (putllc0=%d, putllc16+0=%d, all=%d)"
|
||||
, pattern.mem_count, pattern.put_pc, value.type == v_relative, value.off18, value.type == v_const, value.type == v_reg2, value.reg, value.runtime16_select, entry_point, func_hash, pattern_hash, +stats.nowrite, ++stats.single, +stats.all);
|
||||
}
|
||||
|
||||
@@ -9393,7 +9398,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
|
||||
|
||||
if (likely_putllc_loop && !had_putllc_evaluation)
|
||||
{
|
||||
spu_log.notice("Likely missed PUTLLC16 patterns. (entry=0x%x)", entry_point);
|
||||
spu_log.trace("Likely missed PUTLLC16 patterns. (entry=0x%x)", entry_point);
|
||||
}
|
||||
|
||||
if (result.data.empty())
|
||||
|
||||
@@ -733,6 +733,17 @@ class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator
|
||||
ensure(val && val->getType() == get_type<u32[4]>());
|
||||
|
||||
const auto x = m_ir->CreateZExt(val, get_type<u64[4]>());
|
||||
|
||||
// Use integer operations here so LLVM can fold the masks into VPTERNLOG
|
||||
if (m_use_avx512)
|
||||
{
|
||||
const auto s = m_ir->CreateAnd(m_ir->CreateShl(x, 32), 0x8000000000000000);
|
||||
const auto m = m_ir->CreateAnd(m_ir->CreateShl(x, 29), 0x0fffffffe0000000);
|
||||
const auto f = m_ir->CreateAdd(m_ir->CreateOr(s, m), splat<u64[4]>(0x3800000000000000).eval(m_ir));
|
||||
const auto e = m_ir->CreateAnd(val, 0x7f800000);
|
||||
return uint64_as_double(m_ir->CreateSelect(m_ir->CreateIsNotNull(e), f, s));
|
||||
}
|
||||
|
||||
const auto s = m_ir->CreateShl(m_ir->CreateAnd(x, 0x80000000), 32);
|
||||
const auto a = m_ir->CreateAnd(x, 0x7fffffff);
|
||||
const auto m = m_ir->CreateShl(m_ir->CreateAdd(a, splat<u64[4]>(0x1c0000000).eval(m_ir)), 29);
|
||||
@@ -746,6 +757,19 @@ class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator
|
||||
{
|
||||
ensure(val && val->getType() == get_type<f64[4]>());
|
||||
|
||||
// Use integer operations here so LLVM can fold the masks into VPTERNLOG
|
||||
if (m_use_avx512)
|
||||
{
|
||||
const auto d = double_as_uint64(val);
|
||||
const auto smax = splat<u64[4]>(0x47ffffffe0000000).eval(m_ir);
|
||||
const auto smin = splat<u64[4]>(0x3810000000000000).eval(m_ir);
|
||||
const auto a = m_ir->CreateAnd(d, 0x7fffffffe0000000);
|
||||
const auto n = m_ir->CreateICmpUGE(a, smin);
|
||||
const auto c = m_ir->CreateSelect(m_ir->CreateICmpULT(a, smax), a, smax);
|
||||
const auto r = m_ir->CreateOr(c, m_ir->CreateAnd(d, 0x8000000000000000));
|
||||
return uint64_as_double(m_ir->CreateSelect(n, r, splat<u64[4]>(0).eval(m_ir)));
|
||||
}
|
||||
|
||||
const auto smax = uint64_as_double(splat<u64[4]>(0x47ffffffe0000000).eval(m_ir));
|
||||
const auto smin = uint64_as_double(splat<u64[4]>(0x3810000000000000).eval(m_ir));
|
||||
|
||||
@@ -4131,7 +4155,7 @@ public:
|
||||
cache.add(func);
|
||||
}
|
||||
|
||||
spu_log.success("New SPU block compiled successfully (size=%u)", func_size);
|
||||
spu_log.trace("New SPU block compiled successfully (size=%u)", func_size);
|
||||
}
|
||||
|
||||
return fn;
|
||||
@@ -5268,7 +5292,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
spu_log.warning("[0x%x] MFC_EAH: $%u is not a zero constant", m_pos, +op.rt);
|
||||
spu_log.trace("[0x%x] MFC_EAH: $%u is not a zero constant", m_pos, +op.rt);
|
||||
//m_ir->CreateStore(val.value, spu_ptr(&spu_thread::ch_mfc_cmd, &spu_mfc_cmd::eah));
|
||||
return;
|
||||
}
|
||||
@@ -5626,7 +5650,7 @@ public:
|
||||
}
|
||||
|
||||
// Fallback to unoptimized WRCH implementation (TODO)
|
||||
spu_log.warning("[0x%x] MFC_Cmd: $%u is not a constant", m_pos, +op.rt);
|
||||
spu_log.trace("[0x%x] MFC_Cmd: $%u is not a constant", m_pos, +op.rt);
|
||||
break;
|
||||
}
|
||||
case MFC_WrListStallAck:
|
||||
|
||||
@@ -1053,7 +1053,7 @@ void fmt_class_string<CellError>::format(std::string& out, u64 arg)
|
||||
if (upper == s_error_codes_formatting_by_type.begin())
|
||||
{
|
||||
// Format as unknown
|
||||
format_enum(out, arg, [](auto error)
|
||||
format_enum(out, arg, [](auto /*error*/)
|
||||
{
|
||||
return unknown;
|
||||
});
|
||||
|
||||
@@ -447,7 +447,16 @@ error_code sys_memory_container_get_size(cpu_thread& cpu, vm::ptr<sys_memory_inf
|
||||
{
|
||||
cpu.state += cpu_flag::wait;
|
||||
|
||||
sys_memory.warning("sys_memory_container_get_size(mem_info=*0x%x, cid=0x%x)", mem_info, cid);
|
||||
// A pure query with no side effects, and a game is free to poll it in a loop: Tales of
|
||||
// Xillia 2 (BLUS31397) called it 20,664 times in a 19 minute session, 5,123 of them inside
|
||||
// a single second. At warning level that is thousands of lines a second onto external
|
||||
// storage, which is the same way the SPU recompiler diagnostics stalled the emulator.
|
||||
//
|
||||
// Nothing is lost: a container's size is fixed at creation, and both
|
||||
// sys_memory_container_create and _destroy already log at warning, so the interesting
|
||||
// events are still on the record. Compare sys_memory_get_user_memory_size above, which
|
||||
// upstream already rate-limits by only logging when the reported values change.
|
||||
sys_memory.trace("sys_memory_container_get_size(mem_info=*0x%x, cid=0x%x)", mem_info, cid);
|
||||
|
||||
const auto ct = idm::get_unlocked<lv2_memory_container>(cid);
|
||||
|
||||
|
||||
@@ -614,6 +614,23 @@ namespace rpcn
|
||||
{
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Connected and authentified, but no game is running.
|
||||
//
|
||||
// Every other exit from this loop breaks out to the outer sem_rpcn.acquire(),
|
||||
// and the only blocking wait lives inside the branch above -- so this case
|
||||
// fell through to `while (true)` with nothing to wait on and span a full core
|
||||
// for as long as the user stayed signed in at the menu. Breaking out instead
|
||||
// would park until something released the semaphore, which nothing does on
|
||||
// game start, so wait here and re-check.
|
||||
//
|
||||
// Only reachable once RPCN is actually authentified, which is why it went
|
||||
// unnoticed upstream.
|
||||
if (!sem_rpcn.try_acquire_for(500ms))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -726,7 +743,7 @@ namespace rpcn
|
||||
if (data.size() != 4)
|
||||
return error_and_disconnect("Invalid size of ServerInfo packet");
|
||||
|
||||
received_version = reinterpret_cast<le_t<u32>&>(data[0]);
|
||||
received_version = read_from_ptr<le_t<u32>>(data, 0);
|
||||
server_info_received = true;
|
||||
break;
|
||||
}
|
||||
@@ -1662,7 +1679,7 @@ namespace rpcn
|
||||
{
|
||||
std::vector<u8> data(COMMUNICATION_ID_SIZE + sizeof(u16));
|
||||
rpcn_client::write_communication_id(communication_id, data);
|
||||
reinterpret_cast<le_t<u16>&>(data[COMMUNICATION_ID_SIZE]) = server_id;
|
||||
write_to_ptr<le_t<u16>>(data, COMMUNICATION_ID_SIZE, server_id);
|
||||
|
||||
return forge_send(CommandType::GetWorldList, req_id, data);
|
||||
}
|
||||
@@ -2215,7 +2232,7 @@ namespace rpcn
|
||||
pb_req.SerializeToString(&serialized);
|
||||
|
||||
std::vector<u8> data(serialized.size() + sizeof(u32));
|
||||
reinterpret_cast<le_t<u32>&>(data[0]) = static_cast<u32>(serialized.size());
|
||||
write_to_ptr<le_t<u32>>(data, 0, static_cast<u32>(serialized.size()));
|
||||
memcpy(data.data() + sizeof(u32), serialized.data(), serialized.size());
|
||||
|
||||
return forge_send(CommandType::SendMessage, rpcn_request_counter.fetch_add(1), data);
|
||||
@@ -2315,9 +2332,9 @@ namespace rpcn
|
||||
std::vector<u8> data(COMMUNICATION_ID_SIZE + sizeof(u32) + bufsize + sizeof(u32) + score_data.size());
|
||||
|
||||
rpcn_client::write_communication_id(communication_id, data);
|
||||
reinterpret_cast<le_t<u32>&>(data[COMMUNICATION_ID_SIZE]) = static_cast<u32>(bufsize);
|
||||
write_to_ptr<le_t<u32>>(data, COMMUNICATION_ID_SIZE, static_cast<u32>(bufsize));
|
||||
memcpy(data.data() + COMMUNICATION_ID_SIZE + sizeof(u32), serialized.data(), bufsize);
|
||||
reinterpret_cast<le_t<u32>&>(data[COMMUNICATION_ID_SIZE + sizeof(u32) + bufsize]) = static_cast<u32>(score_data.size());
|
||||
write_to_ptr<le_t<u32>>(data, COMMUNICATION_ID_SIZE + sizeof(u32) + bufsize, static_cast<u32>(score_data.size()));
|
||||
memcpy(data.data() + COMMUNICATION_ID_SIZE + sizeof(u32) + bufsize + sizeof(u32), score_data.data(), score_data.size());
|
||||
|
||||
return forge_send(CommandType::RecordScoreData, req_id, data);
|
||||
@@ -2618,8 +2635,8 @@ namespace rpcn
|
||||
{
|
||||
std::vector<u8> data(COMMUNICATION_ID_SIZE + sizeof(s32) + sizeof(s64));
|
||||
rpcn_client::write_communication_id(communication_id, data);
|
||||
reinterpret_cast<le_t<s32>&>(data[COMMUNICATION_ID_SIZE]) = trophy_id;
|
||||
reinterpret_cast<le_t<s64>&>(data[COMMUNICATION_ID_SIZE + sizeof(s32)]) = timestamp;
|
||||
write_to_ptr<le_t<s32>>(data, COMMUNICATION_ID_SIZE, trophy_id);
|
||||
write_to_ptr<le_t<s64>>(data, COMMUNICATION_ID_SIZE + sizeof(s32), timestamp);
|
||||
return forge_send(CommandType::UnlockTrophy, rpcn_request_counter.fetch_add(1), data);
|
||||
}
|
||||
|
||||
@@ -2632,14 +2649,14 @@ namespace rpcn
|
||||
std::vector<u8> data(COMMUNICATION_ID_SIZE + sizeof(u32) + count * (sizeof(s32) + sizeof(s64))), reply_data;
|
||||
|
||||
rpcn_client::write_communication_id(communication_id, data);
|
||||
reinterpret_cast<le_t<u32>&>(data[COMMUNICATION_ID_SIZE]) = count;
|
||||
write_to_ptr<le_t<u32>>(data, COMMUNICATION_ID_SIZE, count);
|
||||
|
||||
usz offset = COMMUNICATION_ID_SIZE + sizeof(u32);
|
||||
for (const auto& [tid, ts] : local_unlocked)
|
||||
{
|
||||
reinterpret_cast<le_t<s32>&>(data[offset]) = tid;
|
||||
write_to_ptr<le_t<s32>>(data, offset, tid);
|
||||
offset += sizeof(s32);
|
||||
reinterpret_cast<le_t<s64>&>(data[offset]) = ts;
|
||||
write_to_ptr<le_t<s64>>(data, offset, ts);
|
||||
offset += sizeof(s64);
|
||||
}
|
||||
|
||||
@@ -2933,7 +2950,7 @@ namespace rpcn
|
||||
|
||||
rpcn_client::write_communication_id(com_id, data);
|
||||
|
||||
reinterpret_cast<le_t<u32>&>(data[COMMUNICATION_ID_SIZE]) = static_cast<u32>(bufsize);
|
||||
write_to_ptr<le_t<u32>>(data, COMMUNICATION_ID_SIZE, static_cast<u32>(bufsize));
|
||||
memcpy(data.data() + COMMUNICATION_ID_SIZE + sizeof(u32), serialized_data.data(), bufsize);
|
||||
|
||||
return forge_send(command, packet_id, data);
|
||||
@@ -2944,7 +2961,7 @@ namespace rpcn
|
||||
const usz bufsize = serialized_data.size();
|
||||
std::vector<u8> data(sizeof(u32) + bufsize);
|
||||
|
||||
reinterpret_cast<le_t<u32>&>(data[0]) = static_cast<u32>(bufsize);
|
||||
write_to_ptr<le_t<u32>>(data, 0, static_cast<u32>(bufsize));
|
||||
memcpy(data.data() + sizeof(u32), serialized_data.data(), bufsize);
|
||||
|
||||
return forge_send(command, packet_id, data);
|
||||
@@ -2956,9 +2973,9 @@ namespace rpcn
|
||||
|
||||
std::vector<u8> packet(packet_size);
|
||||
packet[0] = static_cast<u8>(PacketType::Request);
|
||||
reinterpret_cast<le_t<u16>&>(packet[1]) = static_cast<u16>(command);
|
||||
reinterpret_cast<le_t<u32>&>(packet[3]) = ::narrow<u32>(packet_size);
|
||||
reinterpret_cast<le_t<u64>&>(packet[7]) = packet_id;
|
||||
write_to_ptr<le_t<u16>>(packet, 1, static_cast<u16>(command));
|
||||
write_to_ptr<le_t<u32>>(packet, 3, ::narrow<u32>(packet_size));
|
||||
write_to_ptr<le_t<u64>>(packet, 7, packet_id);
|
||||
|
||||
memcpy(packet.data() + RPCN_HEADER_SIZE, data.data(), data.size());
|
||||
return packet;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "TextureUtils.h"
|
||||
#include "../RSXThread.h"
|
||||
#include "../rsx_utils.h"
|
||||
#include "../color_utils.h"
|
||||
|
||||
#include "3rdparty/bcdec/bcdec.hpp"
|
||||
|
||||
#include "util/asm.hpp"
|
||||
@@ -1870,4 +1872,25 @@ namespace rsx
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
u32 get_ROP_output_shuffle_index(rsx::surface_color_format format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case surface_color_format::b8:
|
||||
return static_cast<u32>(ROP_channel_remap::BBBB);
|
||||
case surface_color_format::g8b8:
|
||||
return static_cast<u32>(ROP_channel_remap::GBGB);
|
||||
case surface_color_format::x1r5g5b5_z1r5g5b5:
|
||||
case surface_color_format::x8r8g8b8_z8r8g8b8:
|
||||
case surface_color_format::x8b8g8r8_z8b8g8r8:
|
||||
return static_cast<u32>(ROP_channel_remap::RGB0);
|
||||
case surface_color_format::x1r5g5b5_o1r5g5b5:
|
||||
case surface_color_format::x8r8g8b8_o8r8g8b8:
|
||||
case surface_color_format::x8b8g8r8_o8b8g8r8:
|
||||
return static_cast<u32>(ROP_channel_remap::RGB1);
|
||||
default:
|
||||
return static_cast<u32>(ROP_channel_remap::RGBA);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,4 +422,6 @@ namespace rsx
|
||||
{
|
||||
return is_border_clamped_texture(tex.wrap_s(), tex.wrap_t(), tex.wrap_r(), tex.dimension());
|
||||
}
|
||||
|
||||
u32 get_ROP_output_shuffle_index(rsx::surface_color_format format);
|
||||
}
|
||||
|
||||
@@ -662,7 +662,7 @@ namespace rsx
|
||||
}
|
||||
}
|
||||
|
||||
void draw_command_processor::fill_fragment_state_buffer(void* buffer, const RSXFragmentProgram& /*fragment_program*/) const
|
||||
void draw_command_processor::fill_fragment_state_buffer(void* buffer, const RSXFragmentProgram& fragment_program) const
|
||||
{
|
||||
#pragma pack(push, 1)
|
||||
struct fragment_context_t
|
||||
@@ -684,6 +684,12 @@ namespace rsx
|
||||
const u32 alpha_func = static_cast<u32>(REGS(m_ctx)->alpha_func());
|
||||
rop_control.set_alpha_test_func(alpha_func);
|
||||
|
||||
if (fragment_program.ctrl & RSX_SHADER_CONTROL_ROP_OUTPUT_REMAP)
|
||||
{
|
||||
const u32 remap_index = get_ROP_output_shuffle_index(REGS(m_ctx)->surface_color());
|
||||
rop_control.set_output_remap(remap_index);
|
||||
}
|
||||
|
||||
// Generate wpos coefficients
|
||||
// wpos equation is now as follows (ignoring pixel center offset):
|
||||
// wpos.y = (frag_coord / resolution_scale) * ((window_origin!=top)?-1.: 1.) + ((window_origin!=top)? window_height : 0)
|
||||
|
||||
@@ -240,6 +240,7 @@ void GLFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS)
|
||||
m_shader_props.ROP_alpha_to_coverage_test = !!(m_prog.ctrl & RSX_SHADER_CONTROL_ALPHA_TO_COVERAGE);
|
||||
m_shader_props.ROP_polygon_stipple_test = !!(m_prog.ctrl & RSX_SHADER_CONTROL_POLYGON_STIPPLE);
|
||||
m_shader_props.ROP_discard = !!(m_prog.ctrl & RSX_SHADER_CONTROL_USES_KIL);
|
||||
m_shader_props.ROP_channel_remap = !!(m_prog.ctrl & RSX_SHADER_CONTROL_ROP_OUTPUT_REMAP);
|
||||
|
||||
m_shader_props.require_tex1D_ops = properties.has_tex1D;
|
||||
m_shader_props.require_tex2D_ops = properties.has_tex2D;
|
||||
|
||||
@@ -145,6 +145,7 @@ namespace gl
|
||||
if (fp_ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT) opt |= COMPILER_OPT_ENABLE_DEPTH_EXPORT;
|
||||
if (fp_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS) opt |= COMPILER_OPT_ENABLE_F32_EXPORT;
|
||||
if (fp_ctrl & RSX_SHADER_CONTROL_USES_KIL) opt |= COMPILER_OPT_ENABLE_KIL;
|
||||
if (fp_ctrl & RSX_SHADER_CONTROL_ROP_OUTPUT_REMAP) opt |= COMPILER_OPT_ENABLE_ROP_REMAP;
|
||||
if (metadata.referenced_textures_mask) opt |= COMPILER_OPT_ENABLE_TEXTURES;
|
||||
if (metadata.has_branch_instructions) opt |= COMPILER_OPT_ENABLE_FLOW_CTRL;
|
||||
if (metadata.has_pack_instructions) opt |= COMPILER_OPT_ENABLE_PACKING;
|
||||
@@ -412,6 +413,7 @@ namespace gl
|
||||
{
|
||||
.domain = ::glsl::program_domain::glsl_fragment_program,
|
||||
.require_lit_emulation = true,
|
||||
.ROP_channel_remap = !!(compiler_options & COMPILER_OPT_ENABLE_ROP_REMAP),
|
||||
};
|
||||
|
||||
::glsl::insert_glsl_legacy_function(builder, properties);
|
||||
@@ -576,7 +578,7 @@ namespace gl
|
||||
}
|
||||
}
|
||||
|
||||
void shader_interpreter::flush_vertex_texture_bindings(glsl::program* program)
|
||||
void shader_interpreter::flush_vertex_texture_bindings(glsl::program* /*program*/)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
@@ -578,8 +578,13 @@ namespace gl
|
||||
{
|
||||
ensure(desc.sections_to_copy.size() == 1);
|
||||
const auto& section = desc.sections_to_copy.front();
|
||||
return create_temporary_subresource_impl(cmd, section.src, static_cast<GLenum>(section.src->get_internal_format()),
|
||||
GL_TEXTURE_2D, desc.gcm_format, desc.width, desc.height, 1, 1, desc.remap, §ion);
|
||||
return create_temporary_subresource_impl(
|
||||
cmd, section.src,
|
||||
GL_NONE, // NOTE: Do not force this to section.get_sized_internal_fmt(). Leave it as GL_NONE, let the callee find the right type in case of bitcast.
|
||||
GL_TEXTURE_2D, desc.gcm_format,
|
||||
desc.width, desc.height, 1, 1,
|
||||
desc.remap,
|
||||
§ion);
|
||||
}
|
||||
|
||||
gl::texture_view* generate_cubemap_from_images(gl::command_context& cmd, const deferred_subresource& desc) override
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user