mirror of
https://github.com/blackbearreloaded/ProsperoEden.git
synced 2026-10-19 05:09:50 -05:00
Cut game exit from 10-25 s to ~1 s and connect one controller per signed-in PS5 user
This commit is contained in:
1 parent
70b7d33062
commit
b91984737a
10 files changed
+620
-48
No files matched your search
+290
-8
@@ -959,6 +959,40 @@ list(REMOVE_ITEM core_sources hle/kernel/k_thread.cpp)
|
||||
list(APPEND core_sources "${PORT_BUILD_DIR}/k_thread.cpp")
|
||||
set_property(TARGET core PROPERTY SOURCES "${core_sources}")
|
||||
|
||||
# Game exit: the application's page table frees every physical page it maps (~1 s for a 4 GiB
|
||||
# game) although the kernel shutdown that finalizes it recreates the memory manager at the next
|
||||
# initialization. Skip those frees during a kernel shutdown; an in-game process exit (applets)
|
||||
# still frees its pages.
|
||||
file(READ "${PROJECT_SOURCE_DIR}/src/core/hle/kernel/k_page_table_base.cpp" page_table_source)
|
||||
set(page_free_old [=[ // Get physical pages.
|
||||
KPageGroup pg(m_system.Kernel(), m_block_info_manager);
|
||||
this->MakePageGroup(pg, addr, size / PageSize);
|
||||
|
||||
// Free the pages.
|
||||
pg.CloseAndReset(m_system.Kernel());
|
||||
};]=])
|
||||
string(FIND "${page_table_source}" "${page_free_old}" page_free_at)
|
||||
if(page_free_at LESS 0)
|
||||
message(FATAL_ERROR "Pinned page table finalization changed")
|
||||
endif()
|
||||
string(REPLACE "${page_free_old}" [=[ // The memory manager is recreated after a kernel shutdown.
|
||||
if (m_system.Kernel().IsShuttingDown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get physical pages.
|
||||
KPageGroup pg(m_system.Kernel(), m_block_info_manager);
|
||||
this->MakePageGroup(pg, addr, size / PageSize);
|
||||
|
||||
// Free the pages.
|
||||
pg.CloseAndReset(m_system.Kernel());
|
||||
};]=] page_table_source "${page_table_source}")
|
||||
write_derived("${PORT_BUILD_DIR}/k_page_table_base.cpp" "${page_table_source}")
|
||||
get_target_property(page_table_core_sources core SOURCES)
|
||||
list(REMOVE_ITEM page_table_core_sources hle/kernel/k_page_table_base.cpp)
|
||||
list(APPEND page_table_core_sources "${PORT_BUILD_DIR}/k_page_table_base.cpp")
|
||||
set_property(TARGET core PROPERTY SOURCES "${page_table_core_sources}")
|
||||
|
||||
# A server's stopped flag only covers its inner request loop; stopping the CPU
|
||||
# immediately after it fires can abandon the applet destructor mid-cleanup.
|
||||
file(READ "${PROJECT_SOURCE_DIR}/src/core/hle/kernel/kernel.cpp" kernel_source)
|
||||
@@ -970,6 +1004,218 @@ string(REPLACE " void Initialize(KernelCore& kernel) {"
|
||||
" void Initialize(KernelCore& kernel) {\n services_closing = false;" kernel_source "${kernel_source}")
|
||||
string(REPLACE " std::scoped_lock lk{server_lock};\n server_managers.clear();"
|
||||
" decltype(server_managers) closing_managers;\n {\n std::scoped_lock lk{server_lock};\n services_closing = true;\n closing_managers.swap(server_managers);\n }\n for (auto& manager : closing_managers) manager->RequestStop();\n closing_managers.clear();\n for (auto* thread : service_threads) {\n s32 index;\n KSynchronizationObject* objects[] = {thread};\n ASSERT(KSynchronizationObject::Wait(system.Kernel(), &index, objects, 1, -1).IsSuccess());\n thread->Close(system.Kernel());\n }\n service_threads.clear();" kernel_source "${kernel_source}")
|
||||
if(EDEN_DEV_PROFILE)
|
||||
# Development: time the service shutdown phases and every slow manager destruction, named by
|
||||
# the service process that ran it.
|
||||
string(PREPEND kernel_source "#include <algorithm>\n#include <chrono>\n#include <cstdio>\n#include <string>\n")
|
||||
string(REPLACE " bool services_closing = false;" " bool services_closing = false;\n std::vector<std::string> server_names;" kernel_source "${kernel_source}")
|
||||
string(REPLACE " impl->server_managers.emplace_back(std::move(server_manager));\n }"
|
||||
" impl->server_managers.emplace_back(std::move(server_manager));\n auto* eden_process = GetCurrentProcessPointer(*this);\n impl->server_names.emplace_back(eden_process ? eden_process->GetName() : \"?\");\n }" kernel_source "${kernel_source}")
|
||||
string(REPLACE [=[ decltype(server_managers) closing_managers;
|
||||
{
|
||||
std::scoped_lock lk{server_lock};
|
||||
services_closing = true;
|
||||
closing_managers.swap(server_managers);
|
||||
}
|
||||
for (auto& manager : closing_managers) manager->RequestStop();
|
||||
closing_managers.clear();
|
||||
for (auto* thread : service_threads) {
|
||||
s32 index;
|
||||
KSynchronizationObject* objects[] = {thread};
|
||||
ASSERT(KSynchronizationObject::Wait(system.Kernel(), &index, objects, 1, -1).IsSuccess());
|
||||
thread->Close(system.Kernel());
|
||||
}
|
||||
service_threads.clear();]=] [=[ using EdenClock = std::chrono::steady_clock;
|
||||
const auto eden_ms = [](EdenClock::time_point from) {
|
||||
return std::chrono::duration<double, std::milli>(EdenClock::now() - from).count();
|
||||
};
|
||||
const auto eden_start = EdenClock::now();
|
||||
decltype(server_managers) closing_managers;
|
||||
decltype(server_names) closing_names;
|
||||
{
|
||||
std::scoped_lock lk{server_lock};
|
||||
services_closing = true;
|
||||
closing_managers.swap(server_managers);
|
||||
closing_names.swap(server_names);
|
||||
}
|
||||
const double eden_lock_ms = eden_ms(eden_start);
|
||||
const size_t eden_count = closing_managers.size();
|
||||
auto eden_phase = EdenClock::now();
|
||||
double eden_stop_worst = 0;
|
||||
for (auto& manager : closing_managers) {
|
||||
const auto eden_one = EdenClock::now();
|
||||
manager->RequestStop();
|
||||
eden_stop_worst = std::max(eden_stop_worst, eden_ms(eden_one));
|
||||
}
|
||||
const double eden_stop_ms = eden_ms(eden_phase);
|
||||
eden_phase = EdenClock::now();
|
||||
double eden_worst = 0;
|
||||
for (size_t i = 0; i < closing_managers.size(); ++i) {
|
||||
const auto eden_one = EdenClock::now();
|
||||
closing_managers[i].reset();
|
||||
const double ms = eden_ms(eden_one);
|
||||
eden_worst = std::max(eden_worst, ms);
|
||||
if (ms >= 20.0) {
|
||||
std::fprintf(stderr, "EDEN_SHUTDOWN_MANAGER index=%zu name=%s ms=%.1f\n", i,
|
||||
i < closing_names.size() ? closing_names[i].c_str() : "?", ms);
|
||||
}
|
||||
}
|
||||
closing_managers.clear();
|
||||
const double eden_destroy_ms = eden_ms(eden_phase);
|
||||
eden_phase = EdenClock::now();
|
||||
double eden_wait_worst = 0;
|
||||
const size_t eden_threads = service_threads.size();
|
||||
for (auto* thread : service_threads) {
|
||||
const auto eden_one = EdenClock::now();
|
||||
s32 index;
|
||||
KSynchronizationObject* objects[] = {thread};
|
||||
ASSERT(KSynchronizationObject::Wait(system.Kernel(), &index, objects, 1, -1).IsSuccess());
|
||||
thread->Close(system.Kernel());
|
||||
eden_wait_worst = std::max(eden_wait_worst, eden_ms(eden_one));
|
||||
}
|
||||
service_threads.clear();
|
||||
std::fprintf(stderr, "EDEN_SHUTDOWN_SERVICES managers=%zu threads=%zu lock_ms=%.1f stop_ms=%.1f "
|
||||
"worst_stop_ms=%.1f destroy_ms=%.1f worst_manager_ms=%.1f wait_ms=%.1f "
|
||||
"worst_wait_ms=%.1f total_ms=%.1f\n", eden_count, eden_threads, eden_lock_ms,
|
||||
eden_stop_ms, eden_stop_worst, eden_destroy_ms, eden_worst, eden_ms(eden_phase),
|
||||
eden_wait_worst, eden_ms(eden_start));]=] kernel_source "${kernel_source}")
|
||||
# Development: time the kernel teardown steps after the services close (>= 20 ms each), and
|
||||
# the cost of an unbuffered stderr line on the console.
|
||||
function(eden_patch_kernel match replacement)
|
||||
string(FIND "${kernel_source}" "${match}" found_at)
|
||||
if(found_at LESS 0)
|
||||
message(FATAL_ERROR "Pinned kernel shutdown changed")
|
||||
endif()
|
||||
string(REPLACE "${match}" "${replacement}" patched "${kernel_source}")
|
||||
set(kernel_source "${patched}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
eden_patch_kernel([=[ void Shutdown(KernelCore& kernel) {
|
||||
worker_task_manager.WaitForRequests();]=] [=[ void Shutdown(KernelCore& kernel) {
|
||||
auto eden_last = std::chrono::steady_clock::now();
|
||||
const auto eden_mark = [&eden_last](const char* step) {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const double ms = std::chrono::duration<double, std::milli>(now - eden_last).count();
|
||||
if (ms >= 20.0) std::fprintf(stderr, "EDEN_SHUTDOWN_KERNEL step=%s ms=%.1f\n", step, ms);
|
||||
eden_last = std::chrono::steady_clock::now();
|
||||
};
|
||||
{
|
||||
const auto probe = std::chrono::steady_clock::now();
|
||||
for (int line = 0; line < 20; ++line) std::fprintf(stderr, "EDEN_SHUTDOWN_WRITE_PROBE line=%d\n", line);
|
||||
std::fprintf(stderr, "EDEN_SHUTDOWN_WRITE_PROBE lines=20 ms=%.1f\n",
|
||||
std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - probe).count());
|
||||
eden_last = std::chrono::steady_clock::now();
|
||||
}
|
||||
worker_task_manager.WaitForRequests();
|
||||
eden_mark("worker_tasks");]=])
|
||||
eden_patch_kernel([=[ CloseServices();
|
||||
|
||||
if (application_process) {
|
||||
application_process->Close(system.Kernel());
|
||||
application_process = nullptr;
|
||||
}
|
||||
]=] [=[ CloseServices();
|
||||
eden_mark("close_services");
|
||||
|
||||
if (application_process) {
|
||||
application_process->Close(system.Kernel());
|
||||
application_process = nullptr;
|
||||
}
|
||||
eden_mark("application_process");
|
||||
]=])
|
||||
eden_patch_kernel([=[ CleanupObject(system_resource_limit);
|
||||
]=] [=[ CleanupObject(system_resource_limit);
|
||||
eden_mark("persistent_objects");
|
||||
]=])
|
||||
eden_patch_kernel([=[ schedulers[core_id].reset();
|
||||
}
|
||||
]=] [=[ schedulers[core_id].reset();
|
||||
}
|
||||
eden_mark("schedulers");
|
||||
]=])
|
||||
eden_patch_kernel([=[ registered_in_use_objects.clear();
|
||||
}
|
||||
}
|
||||
]=] [=[ registered_in_use_objects.clear();
|
||||
}
|
||||
}
|
||||
eden_mark("in_use_objects");
|
||||
]=])
|
||||
eden_patch_kernel([=[ global_object_list_container.reset();
|
||||
]=] [=[ global_object_list_container.reset();
|
||||
eden_mark("object_container");
|
||||
]=])
|
||||
eden_patch_kernel([=[ hardware_timer.reset();
|
||||
]=] [=[ hardware_timer.reset();
|
||||
eden_mark("hardware_timer");
|
||||
]=])
|
||||
endif()
|
||||
if(EDEN_DEV_PROFILE)
|
||||
# Development: time the application process teardown steps (>= 20 ms each).
|
||||
file(READ "${PROJECT_SOURCE_DIR}/src/core/hle/kernel/k_process.cpp" process_source)
|
||||
string(PREPEND process_source "#include <chrono>\n#include <cstdio>\n")
|
||||
# First occurrence after KProcess::Finalize only: the comments recur in other functions.
|
||||
function(eden_patch_process match replacement)
|
||||
string(FIND "${process_source}" "void KProcess::Finalize(KernelCore& kernel) {" finalize_at)
|
||||
string(SUBSTRING "${process_source}" ${finalize_at} -1 tail)
|
||||
string(FIND "${tail}" "${match}" found_at)
|
||||
if(finalize_at LESS 0 OR found_at LESS 0)
|
||||
message(FATAL_ERROR "Pinned process finalization changed")
|
||||
endif()
|
||||
math(EXPR found_at "${finalize_at} + ${found_at}")
|
||||
string(LENGTH "${match}" match_length)
|
||||
math(EXPR after_at "${found_at} + ${match_length}")
|
||||
string(SUBSTRING "${process_source}" 0 ${found_at} head)
|
||||
string(SUBSTRING "${process_source}" ${after_at} -1 rest)
|
||||
set(process_source "${head}${replacement}${rest}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
eden_patch_process([=[void KProcess::Finalize(KernelCore& kernel) {
|
||||
// Delete the process local region.
|
||||
this->DeleteThreadLocalRegion(kernel, m_plr_address);
|
||||
]=] [=[void KProcess::Finalize(KernelCore& kernel) {
|
||||
auto eden_last = std::chrono::steady_clock::now();
|
||||
const auto eden_mark = [&eden_last](const char* step) {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const double ms = std::chrono::duration<double, std::milli>(now - eden_last).count();
|
||||
if (ms >= 20.0) std::fprintf(stderr, "EDEN_SHUTDOWN_PROCESS step=%s ms=%.1f\n", step, ms);
|
||||
eden_last = std::chrono::steady_clock::now();
|
||||
};
|
||||
// Delete the process local region.
|
||||
this->DeleteThreadLocalRegion(kernel, m_plr_address);
|
||||
eden_mark("local_region");
|
||||
]=])
|
||||
eden_patch_process([=[ m_page_table.Finalize();
|
||||
]=] [=[ eden_mark("used_memory_size");
|
||||
m_page_table.Finalize();
|
||||
eden_mark("page_table");
|
||||
]=])
|
||||
eden_patch_process([=[ // Free all shared memory infos.
|
||||
]=] [=[ eden_mark("system_resource");
|
||||
// Free all shared memory infos.
|
||||
]=])
|
||||
eden_patch_process([=[ // Our thread local page list must be empty at this point.
|
||||
]=] [=[ eden_mark("shared_memory");
|
||||
// Our thread local page list must be empty at this point.
|
||||
]=])
|
||||
eden_patch_process([=[ // Clear expensive resources, as the destructor is not called for guest objects.
|
||||
for (auto& interface : m_arm_interfaces) {
|
||||
interface.reset();
|
||||
}
|
||||
m_exclusive_monitor.reset();
|
||||
]=] [=[ eden_mark("resource_limit");
|
||||
// Clear expensive resources, as the destructor is not called for guest objects.
|
||||
for (auto& interface : m_arm_interfaces) {
|
||||
interface.reset();
|
||||
}
|
||||
eden_mark("arm_interfaces");
|
||||
m_exclusive_monitor.reset();
|
||||
eden_mark("exclusive_monitor");
|
||||
]=])
|
||||
write_derived("${PORT_BUILD_DIR}/k_process.cpp" "${process_source}")
|
||||
get_target_property(process_core_sources core SOURCES)
|
||||
list(REMOVE_ITEM process_core_sources hle/kernel/k_process.cpp)
|
||||
list(APPEND process_core_sources "${PORT_BUILD_DIR}/k_process.cpp")
|
||||
set_property(TARGET core PROPERTY SOURCES "${process_core_sources}")
|
||||
endif()
|
||||
# Never wait for service fibers while holding their registration mutex. A late
|
||||
# registration keeps caller ownership, stops, and runs its normal completion path.
|
||||
string(REPLACE " if (impl->is_shutting_down) {\n return;\n }\n\n impl->server_managers.emplace_back(std::move(server_manager));"
|
||||
@@ -982,14 +1228,50 @@ write_derived("${PORT_BUILD_DIR}/include/${server_header}" "${server_header_sour
|
||||
file(READ "${PROJECT_SOURCE_DIR}/src/core/hle/service/server_manager.cpp" server_source)
|
||||
string(REPLACE "ServerManager::~ServerManager() {\n // Signal stop.\n m_stop_source.request_stop();\n m_wakeup_event->Signal(m_system.Kernel());"
|
||||
"void ServerManager::RequestStop() {\n m_stop_source.request_stop();\n m_wakeup_event->Signal(m_system.Kernel());\n}\n\nServerManager::~ServerManager() {\n RequestStop();" server_source "${server_source}")
|
||||
if(PS5_NATIVE)
|
||||
string(PREPEND server_source "#include <cstdio>\n")
|
||||
foreach(stage "m_stopped.Wait();" "m_threads.clear();" "delete session;")
|
||||
string(REPLACE ";" "" label "${stage}")
|
||||
string(REPLACE "${stage}"
|
||||
"std::fprintf(stderr, \"[service-close] manager=%p begin ${label}\\n\", static_cast<void*>(this));\n ${stage}\n std::fprintf(stderr, \"[service-close] manager=%p end ${label}\\n\", static_cast<void*>(this));"
|
||||
server_source "${server_source}")
|
||||
endforeach()
|
||||
if(EDEN_DEV_PROFILE)
|
||||
# Development: split a slow manager destruction into its phases.
|
||||
string(PREPEND server_source "#include <chrono>\n#include <cstdio>\n")
|
||||
function(eden_patch_server match replacement)
|
||||
string(FIND "${server_source}" "${match}" found_at)
|
||||
if(found_at LESS 0)
|
||||
message(FATAL_ERROR "Pinned ServerManager destructor changed")
|
||||
endif()
|
||||
string(REPLACE "${match}" "${replacement}" patched "${server_source}")
|
||||
set(server_source "${patched}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
eden_patch_server([=[ServerManager::~ServerManager() {
|
||||
RequestStop();]=] [=[ServerManager::~ServerManager() {
|
||||
RequestStop();
|
||||
const auto eden_t0 = std::chrono::steady_clock::now();]=])
|
||||
eden_patch_server([=[ m_stopped.Wait();
|
||||
m_threads.clear();]=] [=[ m_stopped.Wait();
|
||||
const auto eden_t1 = std::chrono::steady_clock::now();
|
||||
m_threads.clear();
|
||||
const auto eden_t2 = std::chrono::steady_clock::now();]=])
|
||||
eden_patch_server([=[ // Clean up sessions.]=] [=[ const auto eden_t3 = std::chrono::steady_clock::now();
|
||||
size_t eden_sessions = 0;
|
||||
// Clean up sessions.]=])
|
||||
eden_patch_server([=[ delete session;
|
||||
}
|
||||
|
||||
// Close wakeup event.]=] [=[ delete session;
|
||||
++eden_sessions;
|
||||
}
|
||||
const auto eden_t4 = std::chrono::steady_clock::now();
|
||||
|
||||
// Close wakeup event.]=])
|
||||
eden_patch_server([=[ // Write event is owned by ServiceManager
|
||||
}
|
||||
}]=] [=[ // Write event is owned by ServiceManager
|
||||
}
|
||||
const auto eden_t5 = std::chrono::steady_clock::now();
|
||||
const auto eden_span = [](auto a, auto b) { return std::chrono::duration<double, std::milli>(b - a).count(); };
|
||||
if (eden_span(eden_t0, eden_t5) >= 10.0) {
|
||||
std::fprintf(stderr, "EDEN_SHUTDOWN_DTOR manager=%p stopped=%.1f threads=%.1f ports=%.1f sessions=%.1f n=%zu events=%.1f\n",
|
||||
static_cast<void*>(this), eden_span(eden_t0, eden_t1), eden_span(eden_t1, eden_t2),
|
||||
eden_span(eden_t2, eden_t3), eden_span(eden_t3, eden_t4), eden_sessions, eden_span(eden_t4, eden_t5));
|
||||
}
|
||||
}]=])
|
||||
endif()
|
||||
write_derived("${PORT_BUILD_DIR}/server_manager.cpp" "${server_source}")
|
||||
get_target_property(core_sources core SOURCES)
|
||||
|
||||
Executable
+56
@@ -0,0 +1,56 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// The guest controller applet (a game's "connect controllers" screen) without a UI. Eden's default
|
||||
// connects only the game's minimum player count, which disconnects a second player whenever a game
|
||||
// asks; this connects one player per PS5 controller in use, within the game's limits, with the
|
||||
// same controller style priority (Pro Controller, dual Joy-Con, single Joy-Con, handheld).
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include "common/settings.h"
|
||||
#include "core/frontend/applets/controller.h"
|
||||
#include "devices.h"
|
||||
#include "hid_core/frontend/emulated_controller.h"
|
||||
#include "hid_core/hid_core.h"
|
||||
|
||||
namespace Eden {
|
||||
class PadControllerApplet final : public Core::Frontend::ControllerApplet {
|
||||
public:
|
||||
PadControllerApplet(Core::HID::HIDCore& hid_core_, const Pad& pad_) : hid_core{hid_core_}, pad{pad_} {}
|
||||
|
||||
void Close() const override {}
|
||||
|
||||
void ReconfigureControllers(ReconfigureCallback callback,
|
||||
const Core::Frontend::ControllerParameters& parameters) const override {
|
||||
using Core::HID::NpadStyleIndex;
|
||||
const std::size_t min_players =
|
||||
parameters.enable_single_mode ? 1 : std::max<std::size_t>(std::max<int>(parameters.min_players, 1), 1);
|
||||
const std::size_t max_players =
|
||||
parameters.enable_single_mode ? 1 : std::max<std::size_t>(std::max<int>(parameters.max_players, 1), min_players);
|
||||
const std::size_t pads = std::popcount(pad.ConnectedPlayers() | 1u);
|
||||
const std::size_t players = std::clamp(pads, min_players, max_players);
|
||||
hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld)->Disconnect();
|
||||
for (std::size_t index = 0; index < Core::HID::HIDCore::available_controllers - 2; ++index) {
|
||||
auto* controller = hid_core.GetEmulatedControllerByIndex(index);
|
||||
controller->Disconnect();
|
||||
if (index >= players) continue;
|
||||
if (parameters.allow_pro_controller) {
|
||||
controller->SetNpadStyleIndex(NpadStyleIndex::Fullkey);
|
||||
} else if (parameters.allow_dual_joycons) {
|
||||
controller->SetNpadStyleIndex(NpadStyleIndex::JoyconDual);
|
||||
} else if (parameters.allow_left_joycon && parameters.allow_right_joycon) {
|
||||
controller->SetNpadStyleIndex(index % 2 == 0 ? NpadStyleIndex::JoyconLeft : NpadStyleIndex::JoyconRight);
|
||||
} else if (index == 0 && parameters.allow_handheld && !::Settings::IsDockedMode()) {
|
||||
controller->SetNpadStyleIndex(NpadStyleIndex::Handheld);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
controller->Connect(true);
|
||||
}
|
||||
callback(true);
|
||||
}
|
||||
|
||||
private:
|
||||
Core::HID::HIDCore& hid_core;
|
||||
const Pad& pad;
|
||||
};
|
||||
}
|
||||
+21
-3
@@ -1,5 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#pragma once
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
@@ -12,8 +13,11 @@
|
||||
#include "ps5_pad.hpp"
|
||||
|
||||
namespace Eden {
|
||||
// DualSense controllers as guest Pro Controllers. Player 1 is the controller of the user who
|
||||
// launched the game; controllers of other signed-in users become players 2-4 as they appear.
|
||||
class Pad final {
|
||||
public:
|
||||
static constexpr std::size_t kMaxPlayers = 4;
|
||||
explicit Pad(float deadzone = 0.08f, float trigger_threshold = 0.5f);
|
||||
~Pad();
|
||||
Pad(const Pad&) = delete;
|
||||
@@ -22,18 +26,32 @@ public:
|
||||
bool Poll();
|
||||
bool TakeReturnToMenu() { return return_to_menu.exchange(false); }
|
||||
bool TakeHudToggle() { return hud_toggle.exchange(false); }
|
||||
// Players with a controller (bit per player), and those whose controller came or went.
|
||||
unsigned ConnectedPlayers() const { return connected_players.load(); }
|
||||
unsigned TakeConnectionChanges() { return connection_changes.exchange(0); }
|
||||
void Close();
|
||||
void Consume(std::span<const ps5::pad::Data> samples);
|
||||
void Consume(std::span<const ps5::pad::Data> samples) { Consume(0, samples); }
|
||||
void Consume(std::size_t player, std::span<const ps5::pad::Data> samples);
|
||||
InputCommon::VirtualGamepad& Engine() { return *engine; }
|
||||
private:
|
||||
struct Slot {
|
||||
int user = -1;
|
||||
int handle = -1;
|
||||
u32 last_buttons = 0;
|
||||
};
|
||||
void Rescan();
|
||||
void OpenSlot(std::size_t player, int user);
|
||||
void CloseSlot(std::size_t player);
|
||||
std::shared_ptr<InputCommon::VirtualGamepad> engine;
|
||||
float deadzone;
|
||||
float trigger_threshold;
|
||||
int handle = -1;
|
||||
std::array<Slot, kMaxPlayers> slots{};
|
||||
bool owns_user_service = false;
|
||||
std::atomic<bool> return_to_menu = false;
|
||||
std::atomic<bool> hud_toggle = false;
|
||||
u32 last_buttons = 0;
|
||||
std::atomic<unsigned> connected_players = 0;
|
||||
std::atomic<unsigned> connection_changes = 0;
|
||||
unsigned polls_since_scan = 0;
|
||||
u64 polls = 0, samples_read = 0, usable_samples = 0, intercepted_samples = 0, circle_samples = 0, read_errors = 0;
|
||||
int last_result = 0;
|
||||
};
|
||||
|
||||
Executable
+87
@@ -0,0 +1,87 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Log streams without storage stalls. On the console a small write to a file under /data takes
|
||||
// ~25 ms or more (20 unbuffered lines: 474 ms), and the frontend writes stderr lines during game
|
||||
// shutdown and flushes stdout from the GPU thread. Attach() points the stream's descriptor at a
|
||||
// pipe; a background thread copies the pipe into the log file, so printing only waits for the
|
||||
// pipe. Data still in the pipe when the process is killed is lost (milliseconds' worth).
|
||||
#pragma once
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <fcntl.h>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace Eden {
|
||||
class LogPipe {
|
||||
public:
|
||||
LogPipe() = default;
|
||||
LogPipe(const LogPipe&) = delete;
|
||||
LogPipe& operator=(const LogPipe&) = delete;
|
||||
~LogPipe() { Detach(); }
|
||||
|
||||
// The stream must already write to its log file; on failure it keeps doing so.
|
||||
bool Attach(std::FILE* target) {
|
||||
if (stream) return false;
|
||||
std::fflush(target);
|
||||
const int stream_fd = fileno(target);
|
||||
int ends[2];
|
||||
// fcntl is a libkernel import; the SDK's static dup() is a direct syscall, which native
|
||||
// titles may not make.
|
||||
if (stream_fd < 0 || (file_fd = fcntl(stream_fd, F_DUPFD, 3)) < 0) return false;
|
||||
if (pipe(ends) != 0) {
|
||||
close(file_fd);
|
||||
file_fd = -1;
|
||||
return false;
|
||||
}
|
||||
if (dup2(ends[1], stream_fd) < 0) {
|
||||
close(ends[0]);
|
||||
close(ends[1]);
|
||||
close(file_fd);
|
||||
file_fd = -1;
|
||||
return false;
|
||||
}
|
||||
close(ends[1]);
|
||||
read_fd = ends[0];
|
||||
stream = target;
|
||||
worker = std::thread([this] { Drain(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
// Writes go straight to the file again once the pipe has been copied out.
|
||||
void Detach() {
|
||||
if (!stream) return;
|
||||
std::fflush(stream);
|
||||
// Replacing the pipe's only write end ends the copy after the remaining data.
|
||||
dup2(file_fd, fileno(stream));
|
||||
worker.join();
|
||||
close(read_fd);
|
||||
close(file_fd);
|
||||
read_fd = file_fd = -1;
|
||||
stream = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
void Drain() {
|
||||
char buffer[16384];
|
||||
for (;;) {
|
||||
const ssize_t count = read(read_fd, buffer, sizeof(buffer));
|
||||
if (count == 0) return;
|
||||
if (count < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
return;
|
||||
}
|
||||
for (ssize_t written = 0; written < count;) {
|
||||
const ssize_t result = write(file_fd, buffer + written, static_cast<size_t>(count - written));
|
||||
if (result < 0 && errno == EINTR) continue;
|
||||
if (result <= 0) break;
|
||||
written += result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::FILE* stream = nullptr;
|
||||
int read_fd = -1;
|
||||
int file_fd = -1;
|
||||
std::thread worker;
|
||||
};
|
||||
}
|
||||
+43
-3
@@ -27,6 +27,8 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "devices.h"
|
||||
#include "diagnostics.h"
|
||||
#include "log_pipe.h"
|
||||
#include "controller_applet.h"
|
||||
#include "preferences.h"
|
||||
#include "metadata_bridge.h"
|
||||
#ifdef EDEN_PS5_OPENGL
|
||||
@@ -56,7 +58,10 @@ extern "C" std::int64_t sceKernelGetDirectMemorySize();
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/frontend/graphics_context.h"
|
||||
#include "core/hle/service/am/applet_manager.h"
|
||||
#include "core/hle/service/am/frontend/applets.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "hid_core/frontend/emulated_controller.h"
|
||||
#include "hid_core/hid_core.h"
|
||||
#ifdef PS5_NATIVE
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunused-private-field"
|
||||
@@ -146,6 +151,10 @@ int main(int argc, char** argv) {
|
||||
// Batch SDK success traces; phase receipts still flush explicitly.
|
||||
static char stdout_buffer[64 * 1024];
|
||||
if (std::setvbuf(stdout, stdout_buffer, _IOFBF, sizeof(stdout_buffer)) != 0) return 2;
|
||||
// Console storage writes take ~25 ms each; background threads copy both streams to disk.
|
||||
static Eden::LogPipe stderr_pipe, stdout_pipe;
|
||||
if (!stderr_pipe.Attach(stderr) || !stdout_pipe.Attach(stdout))
|
||||
Eden::Report("logs", "Asynchronous log writing unavailable; writing directly");
|
||||
std::set_new_handler([] {
|
||||
ps5_opengl_heap_snapshot("allocation_failure", 0);
|
||||
std::fflush(stdout);
|
||||
@@ -462,6 +471,9 @@ int main(int argc, char** argv) {
|
||||
} else if (entry.starts_with("idle_spin_us=")) {
|
||||
// Guest cores spin this long on their interrupt flag before sleeping (W1).
|
||||
Eden::Performance::idle_spin_iterations = static_cast<unsigned>(std::stoul(entry.substr(13)) * 50);
|
||||
} else if (entry.starts_with("pc_core=") && entry.size() == 9 && entry[8] >= '0' && entry[8] <= '3') {
|
||||
// Host PC samples from this guest core instead of core 0 (with --pc-sample).
|
||||
Eden::Performance::pc_sample_core = static_cast<unsigned>(entry[8] - '0');
|
||||
} else if (entry == "replay=off") {
|
||||
// The profile title takes controller and runner input instead of the timed replay.
|
||||
} else if (entry == "large_pages=off") {
|
||||
@@ -605,9 +617,14 @@ int main(int argc, char** argv) {
|
||||
pad = std::make_unique<Eden::Pad>();
|
||||
if (!pad->Open()) throw std::runtime_error("PS5 controller initialization failed");
|
||||
Settings::values.audio_output_device_id = "ps5";
|
||||
auto& player = Settings::values.players.GetValue()[0];
|
||||
player.connected = true;
|
||||
player.controller_type = Settings::ControllerType::ProController;
|
||||
// One Pro Controller per signed-in user's DualSense; later changes apply mid-game.
|
||||
const unsigned connected = pad->ConnectedPlayers();
|
||||
(void)pad->TakeConnectionChanges();
|
||||
for (std::size_t index = 0; index < Eden::Pad::kMaxPlayers; ++index) {
|
||||
auto& player = Settings::values.players.GetValue()[index];
|
||||
player.connected = index == 0 || (connected & (1u << index)) != 0;
|
||||
player.controller_type = Settings::ControllerType::ProController;
|
||||
}
|
||||
}
|
||||
{
|
||||
#ifdef EDEN_PS5_OPENGL
|
||||
@@ -652,6 +669,12 @@ int main(int argc, char** argv) {
|
||||
// Like Eden's Qt/Android frontends, reset shutdown state for each load.
|
||||
system.SetShuttingDown(false);
|
||||
system.GetFileSystemController().CreateFactories(*system.GetFilesystem());
|
||||
if (pad) {
|
||||
// A game's "connect controllers" screen: one player per PS5 controller in use.
|
||||
Service::AM::Frontend::FrontendAppletSet applets;
|
||||
applets.controller = std::make_unique<Eden::PadControllerApplet>(system.HIDCore(), *pad);
|
||||
system.GetFrontendAppletHolder().SetFrontendAppletSet(std::move(applets));
|
||||
}
|
||||
Service::AM::FrontendAppletParameters params{
|
||||
.applet_id = Service::AM::AppletId::Application,
|
||||
};
|
||||
@@ -819,6 +842,23 @@ int main(int argc, char** argv) {
|
||||
(seconds >= 50 && seconds < 200 && seconds % 5 == 0) || seconds >= 200);
|
||||
}
|
||||
#endif
|
||||
if (const unsigned changed = pad->TakeConnectionChanges()) {
|
||||
// A controller that came or went connects or disconnects its player.
|
||||
const unsigned connected = pad->ConnectedPlayers();
|
||||
for (std::size_t index = 1; index < Eden::Pad::kMaxPlayers; ++index) {
|
||||
if (!(changed & (1u << index))) continue;
|
||||
const bool present = (connected & (1u << index)) != 0;
|
||||
Settings::values.players.GetValue()[index].connected = present;
|
||||
auto* controller = system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
if (present) {
|
||||
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey);
|
||||
controller->Connect();
|
||||
} else {
|
||||
controller->Disconnect();
|
||||
}
|
||||
LOG_INFO(Input, "EDEN_PLAYER player={} connected={}", index + 1, present);
|
||||
}
|
||||
}
|
||||
#ifdef EDEN_PS5_OPENGL
|
||||
if (pad->TakeHudToggle()) Eden::ToggleHud();
|
||||
#else
|
||||
|
||||
@@ -88,6 +88,12 @@ int sceUserServiceInitialize(void*) { return state.user_init_result; }
|
||||
int sceUserServiceGetInitialUser(int* user) { *user = 42; return 0; }
|
||||
int sceUserServiceGetForegroundUser(int* user) { *user = state.foreground_user; return state.user_result; }
|
||||
int sceUserServiceTerminate() { ++state.user_terminations; return 0; }
|
||||
// Only the foreground user is signed in on the host.
|
||||
int sceUserServiceGetLoginUserIdList(std::int32_t* users) {
|
||||
users[0] = state.foreground_user;
|
||||
users[1] = users[2] = users[3] = -1;
|
||||
return state.user_result;
|
||||
}
|
||||
int scePadInit() { return 0; }
|
||||
int scePadOpen(int user, int type, int index, const void*) {
|
||||
state.last_pad_user = user;
|
||||
@@ -95,6 +101,7 @@ int scePadOpen(int user, int type, int index, const void*) {
|
||||
++state.pad_opens;
|
||||
return state.pad_open_result;
|
||||
}
|
||||
int scePadGetHandle(int, int, int) { return -1; }
|
||||
int scePadClose(int) { ++state.pad_closes; return 0; }
|
||||
int scePadRead(int, ps5::pad::Data* data, int capacity) {
|
||||
std::scoped_lock lock(state.mutex);
|
||||
|
||||
+108
-32
@@ -7,9 +7,19 @@
|
||||
#include "common/logging.h"
|
||||
#include "input_common/input_poller.h"
|
||||
|
||||
namespace {
|
||||
// Signed-in users; unused entries are -1.
|
||||
struct LoginUserIdList {
|
||||
std::int32_t user_id[4];
|
||||
};
|
||||
}
|
||||
extern "C" int sceUserServiceGetForegroundUser(int* user);
|
||||
extern "C" int sceUserServiceGetLoginUserIdList(LoginUserIdList* list);
|
||||
|
||||
namespace Eden {
|
||||
// Look for newly signed-in (or signed-out) users about once a second at the 4 ms poll interval.
|
||||
constexpr unsigned kPollsPerScan = 250;
|
||||
|
||||
Pad::Pad(float deadzone_, float threshold)
|
||||
: engine{std::make_shared<InputCommon::VirtualGamepad>("virtual_gamepad")},
|
||||
deadzone{deadzone_}, trigger_threshold{threshold} {
|
||||
@@ -24,52 +34,116 @@ Pad::~Pad() {
|
||||
Common::Input::UnregisterInputFactory("virtual_gamepad");
|
||||
}
|
||||
bool Pad::Open() {
|
||||
if (handle >= 0) return true;
|
||||
if (slots[0].handle >= 0) return true;
|
||||
owns_user_service = sceUserServiceInitialize(nullptr) == 0;
|
||||
int user = -1;
|
||||
// Use the same application user as the title-aware launch controller.
|
||||
// Player 1 is the application user, as for the title-aware launch controller.
|
||||
if (sceUserServiceGetForegroundUser(&user) < 0 || user < 0 || scePadInit() < 0) {
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
handle = scePadOpen(user, ps5::pad::kPortTypeStandard, 0, nullptr);
|
||||
if (handle < 0) { Close(); return false; }
|
||||
OpenSlot(0, user);
|
||||
if (slots[0].handle < 0) { Close(); return false; }
|
||||
int initial_user = -1;
|
||||
const int initial_result = sceUserServiceGetInitialUser(&initial_user);
|
||||
LOG_INFO(Input, "EDEN_PAD_OPEN foreground={} initial={} initial_rc={} handle={}",
|
||||
user, initial_user, initial_result, handle);
|
||||
LOG_INFO(Input, "EDEN_PAD_USERS foreground={} initial={} initial_rc={}", user, initial_user, initial_result);
|
||||
Rescan();
|
||||
return true;
|
||||
}
|
||||
void Pad::OpenSlot(std::size_t player, int user) {
|
||||
int handle = scePadOpen(user, ps5::pad::kPortTypeStandard, 0, nullptr);
|
||||
// A handle the process already holds for this user (e.g. from the launcher) is reused.
|
||||
if (handle < 0) handle = scePadGetHandle(user, ps5::pad::kPortTypeStandard, 0);
|
||||
if (handle < 0) {
|
||||
LOG_WARNING(Input, "EDEN_PAD_OPEN_FAILED player={} user={} result={:#x}", player + 1, user,
|
||||
static_cast<unsigned>(handle));
|
||||
return;
|
||||
}
|
||||
slots[player] = {user, handle, 0};
|
||||
connected_players |= 1u << player;
|
||||
connection_changes |= 1u << player;
|
||||
LOG_INFO(Input, "EDEN_PAD_OPEN player={} user={} handle={}", player + 1, user, handle);
|
||||
}
|
||||
void Pad::CloseSlot(std::size_t player) {
|
||||
auto& slot = slots[player];
|
||||
if (slot.handle < 0) return;
|
||||
const auto neutral = ps5::pad::neutral_data();
|
||||
Consume(player, {&neutral, 1});
|
||||
const int closed = scePadClose(slot.handle);
|
||||
LOG_INFO(Input, "EDEN_PAD_CLOSE player={} user={} close={}", player + 1, slot.user, closed);
|
||||
slot = {};
|
||||
connected_players &= ~(1u << player);
|
||||
connection_changes |= 1u << player;
|
||||
}
|
||||
void Pad::Rescan() {
|
||||
LoginUserIdList list;
|
||||
std::fill(std::begin(list.user_id), std::end(list.user_id), -1);
|
||||
if (sceUserServiceGetLoginUserIdList(&list) < 0) return;
|
||||
const auto signed_in = [&](int user) {
|
||||
return std::find(std::begin(list.user_id), std::end(list.user_id), user) != std::end(list.user_id);
|
||||
};
|
||||
// Player 1 stays with the launching user; other players leave when their user signs out.
|
||||
for (std::size_t player = 1; player < kMaxPlayers; ++player) {
|
||||
if (slots[player].handle >= 0 && !signed_in(slots[player].user)) CloseSlot(player);
|
||||
}
|
||||
for (const int user : list.user_id) {
|
||||
if (user < 0 || std::any_of(slots.begin(), slots.end(), [user](const Slot& slot) {
|
||||
return slot.handle >= 0 && slot.user == user; }))
|
||||
continue;
|
||||
for (std::size_t player = 1; player < kMaxPlayers; ++player) {
|
||||
if (slots[player].handle < 0) {
|
||||
OpenSlot(player, user);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void Pad::Close() {
|
||||
engine->ResetControllers();
|
||||
if (handle >= 0) {
|
||||
const int closed = scePadClose(handle);
|
||||
LOG_INFO(Input, "EDEN_PAD_CLOSED polls={} samples={} usable={} intercepted={} circle={} errors={} last={} close={}",
|
||||
polls, samples_read, usable_samples, intercepted_samples, circle_samples, read_errors, last_result, closed);
|
||||
handle = -1;
|
||||
for (std::size_t player = 0; player < kMaxPlayers; ++player) {
|
||||
if (slots[player].handle < 0) continue;
|
||||
const int closed = scePadClose(slots[player].handle);
|
||||
if (player == 0) {
|
||||
LOG_INFO(Input, "EDEN_PAD_CLOSED polls={} samples={} usable={} intercepted={} circle={} errors={} last={} close={}",
|
||||
polls, samples_read, usable_samples, intercepted_samples, circle_samples, read_errors, last_result, closed);
|
||||
}
|
||||
slots[player] = {};
|
||||
}
|
||||
connected_players = 0;
|
||||
connection_changes = 0;
|
||||
if (owns_user_service) { sceUserServiceTerminate(); owns_user_service = false; }
|
||||
}
|
||||
bool Pad::Poll() {
|
||||
if (slots[0].handle >= 0 && ++polls_since_scan >= kPollsPerScan) {
|
||||
polls_since_scan = 0;
|
||||
Rescan();
|
||||
}
|
||||
++polls;
|
||||
bool primary = false;
|
||||
std::array<ps5::pad::Data, ps5::pad::kMaxSamples> samples{};
|
||||
const int count = handle < 0 ? -1 : scePadRead(handle, samples.data(), samples.size());
|
||||
++polls; last_result = count;
|
||||
if (count < 0 || count > static_cast<int>(samples.size())) {
|
||||
++read_errors;
|
||||
const auto neutral = ps5::pad::neutral_data();
|
||||
Consume({&neutral, 1});
|
||||
return false;
|
||||
for (std::size_t player = 0; player < kMaxPlayers; ++player) {
|
||||
const int handle = slots[player].handle;
|
||||
if (handle < 0) continue;
|
||||
const int count = scePadRead(handle, samples.data(), samples.size());
|
||||
if (player == 0) last_result = count;
|
||||
if (count < 0 || count > static_cast<int>(samples.size())) {
|
||||
++read_errors;
|
||||
const auto neutral = ps5::pad::neutral_data();
|
||||
Consume(player, {&neutral, 1});
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < count; ++i) {
|
||||
++samples_read;
|
||||
usable_samples += ps5::pad::is_usable(samples[i]);
|
||||
intercepted_samples += (samples[i].buttons & ps5::pad::kButtonIntercepted) != 0;
|
||||
circle_samples += ps5::pad::is_usable(samples[i]) && (samples[i].buttons & ps5::pad::kButtonCircle);
|
||||
}
|
||||
Consume(player, {samples.data(), static_cast<std::size_t>(count)});
|
||||
primary |= player == 0;
|
||||
}
|
||||
for (int i = 0; i < count; ++i) {
|
||||
++samples_read;
|
||||
usable_samples += ps5::pad::is_usable(samples[i]);
|
||||
intercepted_samples += (samples[i].buttons & ps5::pad::kButtonIntercepted) != 0;
|
||||
circle_samples += ps5::pad::is_usable(samples[i]) && (samples[i].buttons & ps5::pad::kButtonCircle);
|
||||
}
|
||||
Consume({samples.data(), static_cast<std::size_t>(count)});
|
||||
return true;
|
||||
return primary;
|
||||
}
|
||||
void Pad::Consume(std::span<const ps5::pad::Data> samples) {
|
||||
void Pad::Consume(std::size_t player, std::span<const ps5::pad::Data> samples) {
|
||||
using namespace ps5::pad;
|
||||
using Button = InputCommon::VirtualGamepad::VirtualButton;
|
||||
static constexpr std::pair<ButtonMask, Button> buttons[] = {
|
||||
@@ -87,8 +161,10 @@ void Pad::Consume(std::span<const ps5::pad::Data> samples) {
|
||||
return std::abs(x) <= deadzone ? 0.0f :
|
||||
std::copysign((std::abs(x) - deadzone) / (1.0f - deadzone), x);
|
||||
};
|
||||
auto& last_buttons = slots[player].last_buttons;
|
||||
for (const auto& raw : samples) {
|
||||
auto sample = is_usable(raw) ? raw : neutral_data(raw.timestamp_us);
|
||||
// The shortcuts work from every controller.
|
||||
constexpr ButtonMask menu_chord = kButtonTouchPad | kButtonL1;
|
||||
constexpr ButtonMask hud_chord = kButtonTouchPad | kButtonR1;
|
||||
const auto pressed = sample.buttons;
|
||||
@@ -102,14 +178,14 @@ void Pad::Consume(std::span<const ps5::pad::Data> samples) {
|
||||
sample.buttons &= ~(kButtonTouchPad | kButtonL1 | kButtonR1);
|
||||
last_buttons = pressed;
|
||||
for (const auto [mask, button] : buttons)
|
||||
engine->SetButtonState(0, button, (sample.buttons & mask) != 0);
|
||||
engine->SetButtonState(player, button, (sample.buttons & mask) != 0);
|
||||
// Guest ZL/ZR are digital; normalize the physical analog triggers first.
|
||||
const float left = sample.triggers.l2 / 255.0f;
|
||||
const float right = sample.triggers.r2 / 255.0f;
|
||||
engine->SetButtonState(0, Button::TriggerZL, (sample.buttons & kButtonL2) || left >= trigger_threshold);
|
||||
engine->SetButtonState(0, Button::TriggerZR, (sample.buttons & kButtonR2) || right >= trigger_threshold);
|
||||
engine->SetStickPosition(0, 0, axis(sample.left_stick.x), -axis(sample.left_stick.y));
|
||||
engine->SetStickPosition(0, 1, axis(sample.right_stick.x), -axis(sample.right_stick.y));
|
||||
engine->SetButtonState(player, Button::TriggerZL, (sample.buttons & kButtonL2) || left >= trigger_threshold);
|
||||
engine->SetButtonState(player, Button::TriggerZR, (sample.buttons & kButtonR2) || right >= trigger_threshold);
|
||||
engine->SetStickPosition(player, 0, axis(sample.left_stick.x), -axis(sample.left_stick.y));
|
||||
engine->SetStickPosition(player, 1, axis(sample.right_stick.x), -axis(sample.right_stick.y));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -403,13 +403,13 @@ void RegisterWorker(const char* name) {
|
||||
Worker worker;
|
||||
worker.thread = pthread_self();
|
||||
#ifdef EDEN_DEV_PROFILE
|
||||
if (pc_sampling && (i == 4 || i == 0)) {
|
||||
if (pc_sampling && (i == 4 || i == pc_sample_core.load())) {
|
||||
sigset_t mask;
|
||||
sigemptyset(&mask);
|
||||
sigaddset(&mask, SIGUSR2);
|
||||
if (pthread_sigmask(SIG_UNBLOCK, &mask, nullptr))
|
||||
throw std::runtime_error("Cannot enable development PC sampler");
|
||||
if (i == 0) {
|
||||
if (i == pc_sample_core.load()) {
|
||||
core_sample_thread = pthread_self();
|
||||
core_sample_ready.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ void SampleGpuFrame(unsigned frame);
|
||||
#ifdef EDEN_DEV_PROFILE
|
||||
void BeginPcSampling();
|
||||
void PollGpuPc();
|
||||
// Guest core whose host PCs the development sampler collects (dev-settings pc_core=N, default 0).
|
||||
inline std::atomic<unsigned> pc_sample_core{0};
|
||||
#endif
|
||||
// One writer per guest core. JIT state is read only by its owning worker after Run.
|
||||
enum class CpuPhase : unsigned { Kernel, Guest, Idle };
|
||||
|
||||
@@ -21,6 +21,10 @@ for name in ('__wrap_malloc', '__wrap_free', '__wrap_pthread_create', '__wrap_pt
|
||||
assert symbols[name][1] == 't', (name, symbols.get(name))
|
||||
assert symbols['vkGetInstanceProcAddr'] == symbols['radv_GetInstanceProcAddr']
|
||||
assert 'ps5vk_private_open_memstream' not in symbols
|
||||
# The SDK's static libc defines these as direct syscalls, which native titles may not make;
|
||||
# the libkernel imports (fcntl(F_DUPFD), pipe) must be used instead.
|
||||
for name in ('dup', 'pipe2'):
|
||||
assert name not in symbols, f'{name} links the SDK libc direct syscall'
|
||||
link = (build/'bin/eden-headless.map').read_text()
|
||||
assert 'heap_wrap' not in link, 'A second SDK heap wrapper owner was linked'
|
||||
assert 'libps5vk.a' not in link and 'libpsbc.a' not in link, 'Old Vulkan implementation linked'
|
||||
|
||||
Reference in new issue
Block a user