mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-26 19:11:00 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f307a0331d | |||
| cb85810ffd | |||
| 8ddd0f3cc4 | |||
| c52a7be00f | |||
| 37dc82d6d7 | |||
| b6a7c809eb | |||
| d2b43a2356 | |||
| 6d508f3eaf | |||
| 0871e16706 | |||
| 38816c4f31 | |||
| 251dc590dd | |||
| 3eb20d4d45 | |||
| fdf816c1ee | |||
| 88bfc04887 | |||
| 44e6f76c4c | |||
| b9ab300b97 | |||
| 376dc541ea |
@@ -5,11 +5,6 @@
|
|||||||
"repo": "lioncash/biscuit",
|
"repo": "lioncash/biscuit",
|
||||||
"version": "v0.19.0"
|
"version": "v0.19.0"
|
||||||
},
|
},
|
||||||
"atmosphere_zstd_bic": {
|
|
||||||
"hash": "204f7573ea40de2878759c7ff5b2c016111fc29ae8574df50a3a0db9018c3b012f9040ea7466559130424d4335f2dfd6d7abfdc4d4d67e8a34531b8284f0cc6a",
|
|
||||||
"repo": "Atmosphere-NX/Atmosphere",
|
|
||||||
"version": "3ace80cc7b87e1837fccca1522801bbf6fddc6aa"
|
|
||||||
},
|
|
||||||
"boost": {
|
"boost": {
|
||||||
"artifact": "boost-%VERSION%.tar.zst",
|
"artifact": "boost-%VERSION%.tar.zst",
|
||||||
"find_args": "CONFIG OPTIONAL_COMPONENTS headers context system fiber filesystem",
|
"find_args": "CONFIG OPTIONAL_COMPONENTS headers context system fiber filesystem",
|
||||||
@@ -338,11 +333,6 @@
|
|||||||
"repo": "herumi/xbyak",
|
"repo": "herumi/xbyak",
|
||||||
"version": "v7.40.1"
|
"version": "v7.40.1"
|
||||||
},
|
},
|
||||||
"zbic": {
|
|
||||||
"hash": "fbe2f37986377d7f0d96ae3224c80b5971df6e8b6961f68061f1975ebb2e8cb78f07b90f014b007be4023dbf5513581504e7f7d61a5237cb2a8a7a61ae11e482",
|
|
||||||
"repo": "kinnay/zbic",
|
|
||||||
"version": "11b08f2712264bbed731545085cbd9702096ceb7"
|
|
||||||
},
|
|
||||||
"zlib": {
|
"zlib": {
|
||||||
"hash": "16fea4df307a68cf0035858abe2fd550250618a97590e202037acd18a666f57afc10f8836cbbd472d54a0e76539d0e558cb26f059d53de52ff90634bbf4f47d4",
|
"hash": "16fea4df307a68cf0035858abe2fd550250618a97590e202037acd18a666f57afc10f8836cbbd472d54a0e76539d0e558cb26f059d53de52ff90634bbf4f47d4",
|
||||||
"min_version": "1.2",
|
"min_version": "1.2",
|
||||||
|
|||||||
+21
-33
@@ -1,44 +1,32 @@
|
|||||||
# User Handbook - Command Line
|
# User Handbook - Command Line
|
||||||
|
|
||||||
There are two main applications, an SDL-based app (`eden-cli`) and a Qt based app (`eden`); both accept the same command line arguments.
|
There are two main applications, an SDL-based app (`eden-cli`) and a Qt based app (`eden`); both accept command line arguments.
|
||||||
|
|
||||||
|
## eden
|
||||||
|
|
||||||
- `./eden <path>`: Running with a single argument and nothing else, will make the emulator look for the given file and load it, this behavior is similar to `eden-cli`; allows dragging and dropping games into the application.
|
- `./eden <path>`: Running with a single argument and nothing else, will make the emulator look for the given file and load it, this behavior is similar to `eden-cli`; allows dragging and dropping games into the application.
|
||||||
- `--debug/-d`: Enter debug mode, allow gdb stub at port `1234`
|
- `-g <path>`: Alternate way to specify what to load, overrides. However let it be noted that arguments that use `-` will be treated as options/ignored, if your game, for some reason, starts with `-`, in order to safely handle it you may need to specify it as an argument.
|
||||||
- `--config/-c`: Specify alternate configuration file.
|
- `-f`: Use fullscreen.
|
||||||
- `--fullscreen/-f`: Use fullscreen.
|
- `-u <number>`: Select the index of the user to load as.
|
||||||
- `--help/-h`: Display help.
|
- `-input-profile <name>`: Specifies input profile name to use (for player #0 only).
|
||||||
- `--game/-g <path>`: Alternate way to specify what to load, overrides. However let it be noted that arguments that use `-` will be treated as options/ignored, if your game, for some reason, starts with `-`, in order to safely handle it you may need to specify it as an argument.
|
|
||||||
- `--multiplayer/-m`: Specify multiplayer options.
|
|
||||||
- `--program/-p`: Specify the program arguments to pass (optional).
|
|
||||||
- `--user/-u <number>`: Specify the user index.
|
|
||||||
- `--version/-v`: Display version and quit.
|
|
||||||
- `--input-profile/-i <name>`: Specifies input profile name to use (for player #0 only).
|
|
||||||
- `--null-render/-n`: Forces the usage of the "Null" render backend irrespective of settings.
|
|
||||||
- `--filter/-x`: Sets the debug log filter irrespective of settings.
|
|
||||||
- `--singlecore/-s`: Forces single-core regardless of settings.
|
|
||||||
|
|
||||||
Only the Qt frontend supports the following arguments:
|
|
||||||
|
|
||||||
- `-qlaunch`: Launch QLaunch.
|
- `-qlaunch`: Launch QLaunch.
|
||||||
- `-hlaunch`: Launch homebrew launcher `nx-hbloader`.
|
- `-hlaunch`: Launch homebrew launcher `nx-hbloader`.
|
||||||
- Requires a copy of Atmosphere to be extracted onto `sdmc`.
|
- Requires a copy of Atmosphere to be extracted onto `sdmc`.
|
||||||
- This is a shorthand for `<eden folder>/sdmc/atmosphere/hbl.nsp`.
|
- This is a shorthand for `<eden folder>/sdmc/atmosphere/hbl.nsp`.
|
||||||
- `-setup`: Launch setup applet.
|
- `-setup`: Launch setup applet.
|
||||||
|
|
||||||
`eden` (provided with `--room`), and `eden-room` supports the following options as well:
|
## eden-cli
|
||||||
|
|
||||||
- `-n/--room-name`: The name of the room.
|
- `--debug/-d`: Enter debug mode, allow gdb stub at port `1234`
|
||||||
- `-d/--room-description`: The room description.
|
- `--config/-c`: Specify alternate configuration file.
|
||||||
- `-s/--bind-address`: The bind address for the room.
|
- `--fullscreen/-f`: Set fullscreen.
|
||||||
- `-p/--port`: The port used for the room.
|
- `--help/-h`: Display help.
|
||||||
- `-m/--max-members`: The maximum number of players for this room.
|
- `--game/-g`: Specify the game to run.
|
||||||
- `-w/--password`: The password for the room.
|
- `--multiplayer/-m`: Specify multiplayer options.
|
||||||
- `-g/--preferred-game`: The preferred game for this room.
|
- `--program/-p`: Specify the program arguments to pass (optional).
|
||||||
- `-i/--preferred-game-id`: The preferred game-id for this room.
|
- `--user/-u`: Specify the user index.
|
||||||
- `-u/--username`: The username used for announce.
|
- `--version/-v`: Display version and quit.
|
||||||
- `-t/--token`: The token used for announce.
|
- `--input-profile/-i`: Specifies input profile name to use (for player #0 only).
|
||||||
- `-a/--web-api-url`: yuzu Web API url.
|
- `--null-render/-n`: Forces the usage of the "Null" render backend irrespective of settings.
|
||||||
- `-b/--ban-list-file`: The file for storing the room ban list.
|
- `--filter/-x`: Sets the debug log filter irrespective of settings.
|
||||||
- `-l/--log-file`: The file for storing the room log.
|
- `--singlecore/-s`: Forces single-core regardless of settings.
|
||||||
- `-h/--help`: Display this help and exit.
|
|
||||||
- `-v/--version`: Output version information and exit.
|
|
||||||
|
|||||||
Vendored
-5
@@ -48,11 +48,6 @@ if (NOT TARGET stb::headers)
|
|||||||
add_library(stb::headers ALIAS stb)
|
add_library(stb::headers ALIAS stb)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
AddJsonPackage(NAME zbic DOWNLOAD_ONLY)
|
|
||||||
set(ZBIC_INCLUDE_DIR
|
|
||||||
"${zbic_SOURCE_DIR}/src"
|
|
||||||
PARENT_SCOPE)
|
|
||||||
|
|
||||||
# ItaniumDemangle (Windows only)
|
# ItaniumDemangle (Windows only)
|
||||||
if (WIN32 AND NOT TARGET LLVM::Demangle)
|
if (WIN32 AND NOT TARGET LLVM::Demangle)
|
||||||
add_library(demangle demangle/ItaniumDemangle.cpp)
|
add_library(demangle demangle/ItaniumDemangle.cpp)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
@@ -19,6 +19,11 @@ ADSP::ADSP(Core::System& system, Sink::Sink& sink) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ADSP::Shutdown() {
|
||||||
|
audio_renderer->Stop();
|
||||||
|
opus_decoder->Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
AudioRenderer::AudioRenderer& ADSP::AudioRenderer() {
|
AudioRenderer::AudioRenderer& ADSP::AudioRenderer() {
|
||||||
return *audio_renderer;
|
return *audio_renderer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
@@ -45,6 +45,7 @@ public:
|
|||||||
explicit ADSP(Core::System& system, Sink::Sink& sink);
|
explicit ADSP(Core::System& system, Sink::Sink& sink);
|
||||||
~ADSP() = default;
|
~ADSP() = default;
|
||||||
|
|
||||||
|
void Shutdown();
|
||||||
AudioRenderer::AudioRenderer& AudioRenderer();
|
AudioRenderer::AudioRenderer& AudioRenderer();
|
||||||
OpusDecoder::OpusDecoder& OpusDecoder();
|
OpusDecoder::OpusDecoder& OpusDecoder();
|
||||||
|
|
||||||
|
|||||||
@@ -34,26 +34,19 @@ void AudioRenderer::Start() {
|
|||||||
|
|
||||||
mailbox.Send(Direction::DSP, Message::InitializeOK);
|
mailbox.Send(Direction::DSP, Message::InitializeOK);
|
||||||
if (mailbox.Receive(Direction::Host) != Message::InitializeOK) {
|
if (mailbox.Receive(Direction::Host) != Message::InitializeOK) {
|
||||||
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
|
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown message response from ADSP!");
|
||||||
"message response from ADSP!");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
running = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioRenderer::Stop() {
|
void AudioRenderer::Stop() {
|
||||||
if (!running) {
|
if (main_thread.joinable()) {
|
||||||
return;
|
main_thread.request_stop();
|
||||||
|
mailbox.Send(Direction::DSP, Message::Shutdown);
|
||||||
|
if (mailbox.Receive(Direction::Host, main_thread.get_stop_token()) != Message::Shutdown) {
|
||||||
|
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown message response from ADSP!");
|
||||||
|
}
|
||||||
|
main_thread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
mailbox.Send(Direction::DSP, Message::Shutdown);
|
|
||||||
if (mailbox.Receive(Direction::Host) != Message::Shutdown) {
|
|
||||||
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
|
|
||||||
"message response from ADSP!");
|
|
||||||
}
|
|
||||||
main_thread.request_stop();
|
|
||||||
main_thread.join();
|
|
||||||
|
|
||||||
for (auto& stream : streams) {
|
for (auto& stream : streams) {
|
||||||
if (stream) {
|
if (stream) {
|
||||||
stream->Stop();
|
stream->Stop();
|
||||||
@@ -61,7 +54,6 @@ void AudioRenderer::Stop() {
|
|||||||
stream = nullptr;
|
stream = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
running = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioRenderer::Signal() {
|
void AudioRenderer::Signal() {
|
||||||
@@ -69,13 +61,12 @@ void AudioRenderer::Signal() {
|
|||||||
Send(Direction::DSP, Message::Render);
|
Send(Direction::DSP, Message::Render);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioRenderer::Wait() {
|
void AudioRenderer::Wait(std::stop_token stop_token) {
|
||||||
auto msg = Receive(Direction::Host);
|
auto msg = mailbox.Receive(Direction::Host, stop_token);
|
||||||
if (msg != Message::RenderResponse) {
|
if (msg != Message::RenderResponse) {
|
||||||
LOG_ERROR(Service_Audio,
|
LOG_ERROR(Service_Audio,
|
||||||
"Did not receive the expected render response from the AudioRenderer! Expected "
|
"Did not receive the expected render response from the AudioRenderer! Expected "
|
||||||
"{}, got {}",
|
"{}, got {}", Message::RenderResponse, msg);
|
||||||
Message::RenderResponse, msg);
|
|
||||||
}
|
}
|
||||||
PostDSPClearCommandBuffer();
|
PostDSPClearCommandBuffer();
|
||||||
}
|
}
|
||||||
@@ -147,7 +138,9 @@ void AudioRenderer::Main(std::stop_token stop_token) {
|
|||||||
constexpr u64 max_process_time{2'304'000ULL};
|
constexpr u64 max_process_time{2'304'000ULL};
|
||||||
|
|
||||||
while (!stop_token.stop_requested()) {
|
while (!stop_token.stop_requested()) {
|
||||||
auto msg{mailbox.Receive(Direction::DSP)};
|
auto msg = mailbox.Receive(Direction::DSP, stop_token);
|
||||||
|
if (stop_token.stop_requested())
|
||||||
|
break;
|
||||||
switch (msg) {
|
switch (msg) {
|
||||||
case Message::Shutdown:
|
case Message::Shutdown:
|
||||||
mailbox.Send(Direction::Host, Message::Shutdown);
|
mailbox.Send(Direction::Host, Message::Shutdown);
|
||||||
|
|||||||
@@ -63,13 +63,15 @@ public:
|
|||||||
*/
|
*/
|
||||||
void Start();
|
void Start();
|
||||||
|
|
||||||
|
void NotifyShutdown();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop the AudioRenderer.
|
* Stop the AudioRenderer.
|
||||||
*/
|
*/
|
||||||
void Stop();
|
void Stop();
|
||||||
|
|
||||||
void Signal();
|
void Signal();
|
||||||
void Wait();
|
void Wait(std::stop_token stop_token);
|
||||||
|
|
||||||
void Send(Direction dir, u32 message);
|
void Send(Direction dir, u32 message);
|
||||||
u32 Receive(Direction dir);
|
u32 Receive(Direction dir);
|
||||||
@@ -102,8 +104,6 @@ private:
|
|||||||
Mailbox mailbox;
|
Mailbox mailbox;
|
||||||
/// Main thread
|
/// Main thread
|
||||||
std::jthread main_thread{};
|
std::jthread main_thread{};
|
||||||
/// The current state
|
|
||||||
std::atomic<bool> running{};
|
|
||||||
/// Shared memory of input command buffers, set by host, read by DSP
|
/// Shared memory of input command buffers, set by host, read by DSP
|
||||||
std::array<CommandBuffer, MaxRendererSessions> command_buffers{};
|
std::array<CommandBuffer, MaxRendererSessions> command_buffers{};
|
||||||
/// The command lists to process
|
/// The command lists to process
|
||||||
|
|||||||
@@ -41,19 +41,16 @@ OpusDecoder::OpusDecoder(Core::System& system_) : system{system_} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
OpusDecoder::~OpusDecoder() {
|
OpusDecoder::~OpusDecoder() {
|
||||||
if (!running) {
|
if (main_thread.joinable()) {
|
||||||
|
// Shutdown the thread
|
||||||
|
Send(Direction::DSP, Message::Shutdown);
|
||||||
|
main_thread.request_stop();
|
||||||
|
auto msg = Receive(Direction::Host, main_thread.get_stop_token());
|
||||||
|
ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}", Message::ShutdownOK, msg);
|
||||||
|
main_thread.join();
|
||||||
|
} else {
|
||||||
init_thread.request_stop();
|
init_thread.request_stop();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown the thread
|
|
||||||
Send(Direction::DSP, Message::Shutdown);
|
|
||||||
auto msg = Receive(Direction::Host);
|
|
||||||
ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}",
|
|
||||||
Message::ShutdownOK, msg);
|
|
||||||
main_thread.request_stop();
|
|
||||||
main_thread.join();
|
|
||||||
running = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpusDecoder::Send(Direction dir, u32 message) {
|
void OpusDecoder::Send(Direction dir, u32 message) {
|
||||||
@@ -73,7 +70,6 @@ void OpusDecoder::Init(std::stop_token stop_token) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
main_thread = std::jthread([this](std::stop_token st) { Main(st); });
|
main_thread = std::jthread([this](std::stop_token st) { Main(st); });
|
||||||
running = true;
|
|
||||||
Send(Direction::Host, Message::StartOK);
|
Send(Direction::Host, Message::StartOK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,12 +78,15 @@ void OpusDecoder::Main(std::stop_token stop_token) {
|
|||||||
|
|
||||||
while (!stop_token.stop_requested()) {
|
while (!stop_token.stop_requested()) {
|
||||||
auto msg = Receive(Direction::DSP, stop_token);
|
auto msg = Receive(Direction::DSP, stop_token);
|
||||||
|
if (stop_token.stop_requested())
|
||||||
|
break;
|
||||||
|
|
||||||
switch (msg) {
|
switch (msg) {
|
||||||
case Shutdown:
|
case Message::Shutdown:
|
||||||
Send(Direction::Host, Message::ShutdownOK);
|
Send(Direction::Host, Message::ShutdownOK);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case GetWorkBufferSize: {
|
case Message::GetWorkBufferSize: {
|
||||||
auto channel_count = static_cast<s32>(shared_memory->host_send_data[0]);
|
auto channel_count = static_cast<s32>(shared_memory->host_send_data[0]);
|
||||||
|
|
||||||
ASSERT(IsValidChannelCount(channel_count));
|
ASSERT(IsValidChannelCount(channel_count));
|
||||||
@@ -96,7 +95,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
|
|||||||
Send(Direction::Host, Message::GetWorkBufferSizeOK);
|
Send(Direction::Host, Message::GetWorkBufferSizeOK);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case InitializeDecodeObject: {
|
case Message::InitializeDecodeObject: {
|
||||||
auto buffer = shared_memory->host_send_data[0];
|
auto buffer = shared_memory->host_send_data[0];
|
||||||
auto buffer_size = shared_memory->host_send_data[1];
|
auto buffer_size = shared_memory->host_send_data[1];
|
||||||
auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
|
auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
|
||||||
@@ -113,7 +112,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
|
|||||||
Send(Direction::Host, Message::InitializeDecodeObjectOK);
|
Send(Direction::Host, Message::InitializeDecodeObjectOK);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case ShutdownDecodeObject: {
|
case Message::ShutdownDecodeObject: {
|
||||||
auto buffer = shared_memory->host_send_data[0];
|
auto buffer = shared_memory->host_send_data[0];
|
||||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||||
|
|
||||||
@@ -123,7 +122,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
|
|||||||
Send(Direction::Host, Message::ShutdownDecodeObjectOK);
|
Send(Direction::Host, Message::ShutdownDecodeObjectOK);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case DecodeInterleaved: {
|
case Message::DecodeInterleaved: {
|
||||||
auto start_time = system.CoreTiming().GetGlobalTimeUs();
|
auto start_time = system.CoreTiming().GetGlobalTimeUs();
|
||||||
|
|
||||||
auto buffer = shared_memory->host_send_data[0];
|
auto buffer = shared_memory->host_send_data[0];
|
||||||
@@ -161,19 +160,19 @@ void OpusDecoder::Main(std::stop_token stop_token) {
|
|||||||
Send(Direction::Host, Message::DecodeInterleavedOK);
|
Send(Direction::Host, Message::DecodeInterleavedOK);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case MapMemory: {
|
case Message::MapMemory: {
|
||||||
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
||||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||||
Send(Direction::Host, Message::MapMemoryOK);
|
Send(Direction::Host, Message::MapMemoryOK);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case UnmapMemory: {
|
case Message::UnmapMemory: {
|
||||||
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
||||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||||
Send(Direction::Host, Message::UnmapMemoryOK);
|
Send(Direction::Host, Message::UnmapMemoryOK);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case GetWorkBufferSizeForMultiStream: {
|
case Message::GetWorkBufferSizeForMultiStream: {
|
||||||
auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[0]);
|
auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[0]);
|
||||||
auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[1]);
|
auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[1]);
|
||||||
|
|
||||||
@@ -184,7 +183,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
|
|||||||
Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK);
|
Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case InitializeMultiStreamDecodeObject: {
|
case Message::InitializeMultiStreamDecodeObject: {
|
||||||
auto buffer = shared_memory->host_send_data[0];
|
auto buffer = shared_memory->host_send_data[0];
|
||||||
auto buffer_size = shared_memory->host_send_data[1];
|
auto buffer_size = shared_memory->host_send_data[1];
|
||||||
auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
|
auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
|
||||||
@@ -211,7 +210,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
|
|||||||
Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK);
|
Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case ShutdownMultiStreamDecodeObject: {
|
case Message::ShutdownMultiStreamDecodeObject: {
|
||||||
auto buffer = shared_memory->host_send_data[0];
|
auto buffer = shared_memory->host_send_data[0];
|
||||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||||
|
|
||||||
@@ -221,7 +220,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
|
|||||||
Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK);
|
Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case DecodeInterleavedForMultiStream: {
|
case Message::DecodeInterleavedForMultiStream: {
|
||||||
auto start_time = system.CoreTiming().GetGlobalTimeUs();
|
auto start_time = system.CoreTiming().GetGlobalTimeUs();
|
||||||
|
|
||||||
auto buffer = shared_memory->host_send_data[0];
|
auto buffer = shared_memory->host_send_data[0];
|
||||||
@@ -266,4 +265,15 @@ void OpusDecoder::Main(std::stop_token stop_token) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OpusDecoder::Shutdown() {
|
||||||
|
if (init_thread.joinable()) {
|
||||||
|
init_thread.request_stop();
|
||||||
|
init_thread.join(); //must explicitly wait for init to join
|
||||||
|
}
|
||||||
|
if (main_thread.joinable()) {
|
||||||
|
main_thread.request_stop();
|
||||||
|
Send(Direction::DSP, Message::Shutdown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace AudioCore::ADSP::OpusDecoder
|
} // namespace AudioCore::ADSP::OpusDecoder
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ public:
|
|||||||
~OpusDecoder();
|
~OpusDecoder();
|
||||||
|
|
||||||
bool IsRunning() const noexcept {
|
bool IsRunning() const noexcept {
|
||||||
return running;
|
return main_thread.joinable();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Send(Direction dir, u32 message);
|
void Send(Direction dir, u32 message);
|
||||||
@@ -67,6 +67,8 @@ public:
|
|||||||
shared_memory = &shared_memory_;
|
shared_memory = &shared_memory_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Shutdown();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Initializing thread, launched at audio_core boot to avoid blocking the main emu boot thread.
|
* Initializing thread, launched at audio_core boot to avoid blocking the main emu boot thread.
|
||||||
@@ -85,8 +87,6 @@ private:
|
|||||||
std::jthread init_thread{};
|
std::jthread init_thread{};
|
||||||
/// Main thread
|
/// Main thread
|
||||||
std::jthread main_thread{};
|
std::jthread main_thread{};
|
||||||
/// The current state
|
|
||||||
bool running{};
|
|
||||||
/// Structure shared with the host, input data set by the host before sending a mailbox message,
|
/// Structure shared with the host, input data set by the host before sending a mailbox message,
|
||||||
/// and the responses are written back by the OpusDecoder.
|
/// and the responses are written back by the OpusDecoder.
|
||||||
SharedMemory* shared_memory{};
|
SharedMemory* shared_memory{};
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ void AudioCore::CreateSinks() {
|
|||||||
|
|
||||||
void AudioCore::Shutdown() {
|
void AudioCore::Shutdown() {
|
||||||
audio_manager->Shutdown();
|
audio_manager->Shutdown();
|
||||||
|
adsp->Shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioManager& AudioCore::GetAudioManager() {
|
AudioManager& AudioCore::GetAudioManager() {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ AudioManager::AudioManager() {
|
|||||||
std::unique_lock l{events.GetAudioEventLock()};
|
std::unique_lock l{events.GetAudioEventLock()};
|
||||||
events.ClearEvents();
|
events.ClearEvents();
|
||||||
while (!stop_token.stop_requested()) {
|
while (!stop_token.stop_requested()) {
|
||||||
const auto timed_out{events.Wait(l, std::chrono::seconds(2))};
|
const auto timed_out = events.Wait(l, std::chrono::seconds{2});
|
||||||
if (events.CheckAudioEventSet(Event::Type::Max)) {
|
if (events.CheckAudioEventSet(Event::Type::Max)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -35,9 +35,9 @@ AudioManager::AudioManager() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AudioManager::Shutdown() {
|
void AudioManager::Shutdown() {
|
||||||
events.SetAudioEvent(Event::Type::Max, true);
|
|
||||||
if (thread.joinable()) {
|
if (thread.joinable()) {
|
||||||
thread.request_stop();
|
thread.request_stop();
|
||||||
|
events.SetAudioEvent(Event::Type::Max, true);
|
||||||
thread.join();
|
thread.join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,9 +39,7 @@ class AudioManager {
|
|||||||
public:
|
public:
|
||||||
explicit AudioManager();
|
explicit AudioManager();
|
||||||
|
|
||||||
/**
|
/// @brief Shutdown the audio manager.
|
||||||
* Shutdown the audio manager.
|
|
||||||
*/
|
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -35,7 +38,7 @@ void CircularBufferSinkInfo::Update(BehaviorInfo::ErrorInfo& error_info, OutStat
|
|||||||
auto current_params{reinterpret_cast<CircularBufferInParameter*>(parameter.data())};
|
auto current_params{reinterpret_cast<CircularBufferInParameter*>(parameter.data())};
|
||||||
auto current_state{reinterpret_cast<CircularBufferState*>(state.data())};
|
auto current_state{reinterpret_cast<CircularBufferState*>(state.data())};
|
||||||
|
|
||||||
if (in_use == buffer_params->in_use && !buffer_unmapped) {
|
if (in_use == bool(buffer_params->in_use) && !buffer_unmapped) {
|
||||||
error_info.error_code = ResultSuccess;
|
error_info.error_code = ResultSuccess;
|
||||||
error_info.address = CpuAddr(0);
|
error_info.address = CpuAddr(0);
|
||||||
out_status.writeOffset = current_state->last_pos2;
|
out_status.writeOffset = current_state->last_pos2;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
@@ -31,10 +31,10 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct DeviceInParameter {
|
struct DeviceInParameter {
|
||||||
/* 0x000 */ char name[0x100];
|
/* 0x000 */ u8 name[0x100];
|
||||||
/* 0x100 */ u32 input_count;
|
/* 0x100 */ u32 input_count;
|
||||||
/* 0x104 */ std::array<s8, MaxChannels> inputs;
|
/* 0x104 */ std::array<s8, MaxChannels> inputs;
|
||||||
/* 0x10A */ char unk10A[0x1];
|
/* 0x10A */ u8 unk10A[0x1];
|
||||||
/* 0x10B */ bool downmix_enabled;
|
/* 0x10B */ bool downmix_enabled;
|
||||||
/* 0x10C */ std::array<f32, 4> downmix_coeff;
|
/* 0x10C */ std::array<f32, 4> downmix_coeff;
|
||||||
};
|
};
|
||||||
@@ -43,7 +43,7 @@ public:
|
|||||||
struct DeviceState {
|
struct DeviceState {
|
||||||
/* 0x00 */ UpsamplerInfo* upsampler_info;
|
/* 0x00 */ UpsamplerInfo* upsampler_info;
|
||||||
/* 0x08 */ std::array<Common::FixedPoint<16, 16>, 4> downmix_coeff;
|
/* 0x08 */ std::array<Common::FixedPoint<16, 16>, 4> downmix_coeff;
|
||||||
/* 0x18 */ char unk18[0x18];
|
/* 0x18 */ u8 unk18[0x18];
|
||||||
};
|
};
|
||||||
static_assert(sizeof(DeviceState) == 0x30, "DeviceState has the wrong size!");
|
static_assert(sizeof(DeviceState) == 0x30, "DeviceState has the wrong size!");
|
||||||
|
|
||||||
@@ -55,8 +55,8 @@ public:
|
|||||||
/* 0x14 */ u32 previous_pos;
|
/* 0x14 */ u32 previous_pos;
|
||||||
/* 0x18 */ SampleFormat format;
|
/* 0x18 */ SampleFormat format;
|
||||||
/* 0x1C */ std::array<s8, MaxChannels> inputs;
|
/* 0x1C */ std::array<s8, MaxChannels> inputs;
|
||||||
/* 0x22 */ bool in_use;
|
/* 0x22 */ u8 in_use;
|
||||||
/* 0x23 */ char unk23[0x5];
|
/* 0x23 */ u8 unk23[0x5];
|
||||||
};
|
};
|
||||||
static_assert(sizeof(CircularBufferInParameter) == 0x28,
|
static_assert(sizeof(CircularBufferInParameter) == 0x28,
|
||||||
"CircularBufferInParameter has the wrong size!");
|
"CircularBufferInParameter has the wrong size!");
|
||||||
@@ -65,16 +65,16 @@ public:
|
|||||||
/* 0x00 */ u32 last_pos2;
|
/* 0x00 */ u32 last_pos2;
|
||||||
/* 0x04 */ s32 current_pos;
|
/* 0x04 */ s32 current_pos;
|
||||||
/* 0x08 */ u32 last_pos;
|
/* 0x08 */ u32 last_pos;
|
||||||
/* 0x0C */ char unk0C[0x4];
|
/* 0x0C */ u8 unk0C[0x4];
|
||||||
/* 0x10 */ AddressInfo address_info;
|
/* 0x10 */ AddressInfo address_info;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(CircularBufferState) == 0x30, "CircularBufferState has the wrong size!");
|
static_assert(sizeof(CircularBufferState) == 0x30, "CircularBufferState has the wrong size!");
|
||||||
|
|
||||||
struct InParameter {
|
struct InParameter {
|
||||||
/* 0x000 */ Type type;
|
/* 0x000 */ Type type;
|
||||||
/* 0x001 */ bool in_use;
|
/* 0x001 */ u8 in_use;
|
||||||
/* 0x004 */ u32 node_id;
|
/* 0x004 */ u32 node_id;
|
||||||
/* 0x008 */ char unk08[0x18];
|
/* 0x008 */ u8 unk08[0x18];
|
||||||
union {
|
union {
|
||||||
/* 0x020 */ DeviceInParameter device;
|
/* 0x020 */ DeviceInParameter device;
|
||||||
/* 0x020 */ CircularBufferInParameter circular_buffer;
|
/* 0x020 */ CircularBufferInParameter circular_buffer;
|
||||||
@@ -84,7 +84,7 @@ public:
|
|||||||
|
|
||||||
struct OutStatus {
|
struct OutStatus {
|
||||||
/* 0x00 */ u32 writeOffset;
|
/* 0x00 */ u32 writeOffset;
|
||||||
/* 0x04 */ char unk04[0x1C];
|
/* 0x04 */ u8 unk04[0x1C];
|
||||||
}; // size == 0x20
|
}; // size == 0x20
|
||||||
static_assert(sizeof(OutStatus) == 0x20, "SinkInfoBase::OutStatus has the wrong size!");
|
static_assert(sizeof(OutStatus) == 0x20, "SinkInfoBase::OutStatus has the wrong size!");
|
||||||
|
|
||||||
@@ -162,19 +162,18 @@ public:
|
|||||||
u8* GetParameter();
|
u8* GetParameter();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Type of this sink
|
|
||||||
Type type{Type::Invalid};
|
|
||||||
/// Is this sink in use?
|
|
||||||
bool in_use{};
|
|
||||||
/// Is this sink's buffer unmapped? Circular only
|
|
||||||
bool buffer_unmapped{};
|
|
||||||
/// Node id for this sink
|
|
||||||
u32 node_id{};
|
|
||||||
/// State buffer for this sink
|
/// State buffer for this sink
|
||||||
std::array<u8, (std::max)(sizeof(DeviceState), sizeof(CircularBufferState))> state{};
|
std::array<u8, (std::max)(sizeof(DeviceState), sizeof(CircularBufferState))> state{};
|
||||||
/// Parameter buffer for this sink
|
/// Parameter buffer for this sink
|
||||||
std::array<u8, (std::max)(sizeof(DeviceInParameter), sizeof(CircularBufferInParameter))>
|
std::array<u8, (std::max)(sizeof(DeviceInParameter), sizeof(CircularBufferInParameter))> parameter{};
|
||||||
parameter{};
|
/// Type of this sink
|
||||||
|
Type type{Type::Invalid};
|
||||||
|
/// Node id for this sink
|
||||||
|
u32 node_id{};
|
||||||
|
/// Is this sink in use?
|
||||||
|
bool in_use : 1 = false;
|
||||||
|
/// Is this sink's buffer unmapped? Circular only
|
||||||
|
bool buffer_unmapped : 1 = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace AudioCore::Renderer
|
} // namespace AudioCore::Renderer
|
||||||
|
|||||||
@@ -100,8 +100,9 @@ u64 System::GetWorkBufferSize(const AudioRendererParameterInternal& params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
System::System(Core::System& core_, Kernel::KEvent* adsp_rendered_event_)
|
System::System(Core::System& core_, Kernel::KEvent* adsp_rendered_event_)
|
||||||
: core{core_}, audio_renderer{core.AudioCore().ADSP().AudioRenderer()},
|
: core{core_}
|
||||||
adsp_rendered_event{adsp_rendered_event_} {}
|
, adsp_rendered_event{adsp_rendered_event_}
|
||||||
|
{}
|
||||||
|
|
||||||
Result System::Initialize(const AudioRendererParameterInternal& params,
|
Result System::Initialize(const AudioRendererParameterInternal& params,
|
||||||
Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size,
|
Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size,
|
||||||
@@ -406,7 +407,7 @@ void System::Finalize() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (active) {
|
if (IsActive()) {
|
||||||
Stop();
|
Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,14 +434,12 @@ void System::Start() {
|
|||||||
std::scoped_lock l{lock};
|
std::scoped_lock l{lock};
|
||||||
frames_elapsed = 0;
|
frames_elapsed = 0;
|
||||||
state = State::Started;
|
state = State::Started;
|
||||||
active = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::Stop() {
|
void System::Stop() {
|
||||||
{
|
{
|
||||||
std::scoped_lock l{lock};
|
std::scoped_lock l{lock};
|
||||||
state = State::Stopped;
|
state = State::Stopped;
|
||||||
active = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (execution_mode == ExecutionMode::Auto) {
|
if (execution_mode == ExecutionMode::Auto) {
|
||||||
@@ -480,7 +479,7 @@ Result System::Update(std::span<const u8> input, std::span<u8> performance, std:
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = info_updater.UpdateEffects(effect_context, active, memory_pool_workbuffer,
|
result = info_updater.UpdateEffects(effect_context, IsActive(), memory_pool_workbuffer,
|
||||||
memory_pool_count);
|
memory_pool_count);
|
||||||
if (result.IsError()) {
|
if (result.IsError()) {
|
||||||
LOG_ERROR(Service_Audio, "Failed to update Effects!");
|
LOG_ERROR(Service_Audio, "Failed to update Effects!");
|
||||||
@@ -582,16 +581,16 @@ u32 System::GetRenderingDevice() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool System::IsActive() const {
|
bool System::IsActive() const {
|
||||||
return active;
|
return state == State::Started;
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::SendCommandToDsp() {
|
void System::SendCommandToDsp() {
|
||||||
std::scoped_lock l{lock};
|
std::scoped_lock l{lock};
|
||||||
|
|
||||||
if (initialized) {
|
if (initialized) {
|
||||||
if (active) {
|
if (IsActive()) {
|
||||||
terminate_event.Reset();
|
terminate_event.Reset();
|
||||||
const auto remaining_command_count{audio_renderer.GetRemainCommandCount(session_id)};
|
const auto remaining_command_count = core.AudioCore().ADSP().AudioRenderer().GetRemainCommandCount(session_id);
|
||||||
u64 command_size{0};
|
u64 command_size{0};
|
||||||
|
|
||||||
if (remaining_command_count) {
|
if (remaining_command_count) {
|
||||||
@@ -618,16 +617,17 @@ void System::SendCommandToDsp() {
|
|||||||
auto time_limit{
|
auto time_limit{
|
||||||
static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 *
|
static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 *
|
||||||
(static_cast<f32>(render_time_limit_percent) / 100.0f))};
|
(static_cast<f32>(render_time_limit_percent) / 100.0f))};
|
||||||
audio_renderer.SetCommandBuffer(session_id, translated_addr, command_size, time_limit,
|
core.AudioCore().ADSP().AudioRenderer().SetCommandBuffer(session_id,
|
||||||
applet_resource_user_id, process_handle,
|
translated_addr, command_size,
|
||||||
reset_command_buffers);
|
time_limit, applet_resource_user_id,
|
||||||
|
process_handle, reset_command_buffers);
|
||||||
reset_command_buffers = false;
|
reset_command_buffers = false;
|
||||||
command_buffer_size = command_size;
|
command_buffer_size = command_size;
|
||||||
if (remaining_command_count == 0) {
|
if (remaining_command_count == 0) {
|
||||||
adsp_rendered_event->Signal(core.Kernel());
|
adsp_rendered_event->Signal(core.Kernel());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
audio_renderer.ClearRemainCommandCount(session_id);
|
core.AudioCore().ADSP().AudioRenderer().ClearRemainCommandCount(session_id);
|
||||||
terminate_event.Set();
|
terminate_event.Set();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -738,7 +738,7 @@ u64 System::GenerateCommand(std::span<u8> in_command_buffer,
|
|||||||
const auto end_time{core.CoreTiming().GetGlobalTimeNs().count()};
|
const auto end_time{core.CoreTiming().GetGlobalTimeNs().count()};
|
||||||
total_ticks_elapsed += end_time - start_time;
|
total_ticks_elapsed += end_time - start_time;
|
||||||
num_command_lists_generated++;
|
num_command_lists_generated++;
|
||||||
render_start_tick = audio_renderer.GetRenderingStartTick(session_id);
|
render_start_tick = core.AudioCore().ADSP().AudioRenderer().GetRenderingStartTick(session_id);
|
||||||
frames_elapsed++;
|
frames_elapsed++;
|
||||||
|
|
||||||
return command_buffer.size;
|
return command_buffer.size;
|
||||||
|
|||||||
@@ -219,8 +219,6 @@ public:
|
|||||||
private:
|
private:
|
||||||
/// Core system
|
/// Core system
|
||||||
Core::System& core;
|
Core::System& core;
|
||||||
/// Reference to the ADSP's AudioRenderer for communication
|
|
||||||
::AudioCore::ADSP::AudioRenderer::AudioRenderer& audio_renderer;
|
|
||||||
/// Is this system initialized?
|
/// Is this system initialized?
|
||||||
bool initialized{};
|
bool initialized{};
|
||||||
/// Is this system currently active?
|
/// Is this system currently active?
|
||||||
|
|||||||
@@ -11,73 +11,70 @@
|
|||||||
#include "audio_core/renderer/system_manager.h"
|
#include "audio_core/renderer/system_manager.h"
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
#include "core/hle/kernel/kernel.h"
|
||||||
|
#include "core/hle/kernel/k_scheduler.h"
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
|
|
||||||
namespace AudioCore::Renderer {
|
namespace AudioCore::Renderer {
|
||||||
|
|
||||||
SystemManager::SystemManager(Core::System& core_)
|
SystemManager::SystemManager(Core::System& core_)
|
||||||
: core{core_}, audio_renderer{core.AudioCore().ADSP().AudioRenderer()} {}
|
: core{core_}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
SystemManager::~SystemManager() {
|
SystemManager::~SystemManager() {
|
||||||
Stop();
|
Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SystemManager::InitializeUnsafe() {
|
void SystemManager::InitializeUnsafe() {
|
||||||
if (!active) {
|
if (!thread.joinable()) {
|
||||||
active = true;
|
core.AudioCore().ADSP().AudioRenderer().Start();
|
||||||
audio_renderer.Start();
|
thread = core.Kernel().RunOnHostCoreThread("AudioRenderSystemManager", [this]() {
|
||||||
thread = std::jthread([this](std::stop_token stop_token) {
|
auto& audio_renderer = core.AudioCore().ADSP().AudioRenderer();
|
||||||
Common::SetCurrentThreadName("AudioRenderSystemManager");
|
auto const stop_token = thread.get_stop_token();
|
||||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
while (!stop_token.stop_requested()) {
|
||||||
while (active && !stop_token.stop_requested()) {
|
|
||||||
{
|
{
|
||||||
std::scoped_lock l{mutex1};
|
std::scoped_lock lk{mutex};
|
||||||
for (auto system : systems)
|
for (auto system : systems)
|
||||||
system->SendCommandToDsp();
|
system->SendCommandToDsp();
|
||||||
}
|
}
|
||||||
audio_renderer.Signal();
|
audio_renderer.Signal();
|
||||||
audio_renderer.Wait();
|
audio_renderer.Wait(stop_token);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SystemManager::Stop() {
|
void SystemManager::Stop() {
|
||||||
if (active) {
|
core.AudioCore().ADSP().AudioRenderer().Stop();
|
||||||
active = false;
|
if (thread.joinable()) {
|
||||||
thread.request_stop();
|
thread.request_stop();
|
||||||
thread.join();
|
thread.join();
|
||||||
audio_renderer.Stop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SystemManager::Add(System& system_) {
|
bool SystemManager::Add(System& system_) {
|
||||||
std::scoped_lock l2{mutex2};
|
std::scoped_lock lk{mutex};
|
||||||
if (systems.size() + 1 > MaxRendererSessions) {
|
if (systems.size() >= MaxRendererSessions) {
|
||||||
LOG_ERROR(Service_Audio, "Maximum AudioRenderer Systems active, cannot add more!");
|
LOG_ERROR(Service_Audio, "Maximum AudioRenderer Systems active, cannot add more!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
{
|
|
||||||
std::scoped_lock l{mutex1};
|
|
||||||
if (systems.empty())
|
|
||||||
InitializeUnsafe();
|
|
||||||
}
|
|
||||||
systems.push_back(&system_);
|
systems.push_back(&system_);
|
||||||
|
if (!systems.empty()) {
|
||||||
|
InitializeUnsafe();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SystemManager::Remove(System& system_) {
|
bool SystemManager::Remove(System& system_) {
|
||||||
std::scoped_lock l2{mutex2};
|
std::scoped_lock lk{mutex};
|
||||||
{
|
if (systems.remove(&system_) == 0) {
|
||||||
std::scoped_lock l{mutex1};
|
LOG_ERROR(Service_Audio, "Failed to remove a render system, it was not found in the list!");
|
||||||
if (systems.remove(&system_) == 0) {
|
return false;
|
||||||
LOG_ERROR(Service_Audio, "Failed to remove a render system, it was not found in the list!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (systems.empty()) {
|
||||||
if (systems.empty())
|
|
||||||
Stop();
|
Stop();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,14 +75,8 @@ private:
|
|||||||
std::list<System*> systems{};
|
std::list<System*> systems{};
|
||||||
/// Main worker thread for generating command lists
|
/// Main worker thread for generating command lists
|
||||||
std::jthread thread;
|
std::jthread thread;
|
||||||
/// Mutex for the systems
|
/// Mutex for the reading and adding/removing systems
|
||||||
std::mutex mutex1{};
|
std::mutex mutex{};
|
||||||
/// Mutex for adding/removing systems
|
|
||||||
std::mutex mutex2{};
|
|
||||||
/// Is the system manager thread active?
|
|
||||||
std::atomic<bool> active{};
|
|
||||||
/// Reference to the ADSP's AudioRenderer for communication
|
|
||||||
::AudioCore::ADSP::AudioRenderer::AudioRenderer& audio_renderer;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace AudioCore::Renderer
|
} // namespace AudioCore::Renderer
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ add_library(
|
|||||||
logging.h
|
logging.h
|
||||||
lz4_compression.cpp
|
lz4_compression.cpp
|
||||||
lz4_compression.h
|
lz4_compression.h
|
||||||
|
make_unique_for_overwrite.h
|
||||||
math_util.h
|
math_util.h
|
||||||
memory_detect.cpp
|
memory_detect.cpp
|
||||||
memory_detect.h
|
memory_detect.h
|
||||||
@@ -137,8 +138,6 @@ add_library(
|
|||||||
uuid.cpp
|
uuid.cpp
|
||||||
uuid.h
|
uuid.h
|
||||||
vector_math.h
|
vector_math.h
|
||||||
zbic_compression.cpp
|
|
||||||
zbic_compression.h
|
|
||||||
zstd_compression.cpp
|
zstd_compression.cpp
|
||||||
zstd_compression.h
|
zstd_compression.h
|
||||||
fs/ryujinx_compat.h fs/ryujinx_compat.cpp
|
fs/ryujinx_compat.h fs/ryujinx_compat.cpp
|
||||||
@@ -149,10 +148,6 @@ add_library(
|
|||||||
net/net.h net/net.cpp
|
net/net.h net/net.cpp
|
||||||
container/unordered_map.h container/unordered_set.h)
|
container/unordered_map.h container/unordered_set.h)
|
||||||
|
|
||||||
set_source_files_properties(zbic_compression.cpp PROPERTIES
|
|
||||||
INCLUDE_DIRECTORIES "${ZBIC_INCLUDE_DIR}"
|
|
||||||
COMPILE_OPTIONS "$<$<CXX_COMPILER_ID:Clang,GNU>:-Wno-unused-function>")
|
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_sources(common PRIVATE windows/timer_resolution.cpp
|
target_sources(common PRIVATE windows/timer_resolution.cpp
|
||||||
windows/timer_resolution.h)
|
windows/timer_resolution.h)
|
||||||
|
|||||||
@@ -109,12 +109,6 @@ private:
|
|||||||
if (read_index == producer.index.load(std::memory_order::acquire)) {
|
if (read_index == producer.index.load(std::memory_order::acquire)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if constexpr (Mode == PopMode::Wait) {
|
|
||||||
// Wait until the queue is not empty.
|
|
||||||
std::unique_lock lock{consumer.cv_mutex};
|
|
||||||
consumer.cv.wait(lock, [this, read_index] {
|
|
||||||
return read_index != producer.index.load(std::memory_order::acquire);
|
|
||||||
});
|
|
||||||
} else if constexpr (Mode == PopMode::WaitWithStopToken) {
|
} else if constexpr (Mode == PopMode::WaitWithStopToken) {
|
||||||
// Wait until the queue is not empty.
|
// Wait until the queue is not empty.
|
||||||
std::unique_lock lock{consumer.cv_mutex};
|
std::unique_lock lock{consumer.cv_mutex};
|
||||||
@@ -124,6 +118,12 @@ private:
|
|||||||
if (stop_token.stop_requested()) {
|
if (stop_token.stop_requested()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if constexpr (Mode == PopMode::Wait) {
|
||||||
|
// Wait until the queue is not empty.
|
||||||
|
std::unique_lock lock{consumer.cv_mutex};
|
||||||
|
consumer.cv.wait(lock, [this, read_index] {
|
||||||
|
return read_index != producer.index.load(std::memory_order::acquire);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
static_assert(Mode < PopMode::Count, "Invalid PopMode.");
|
static_assert(Mode < PopMode::Count, "Invalid PopMode.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
requires(!std::is_array_v<T>)
|
||||||
|
std::unique_ptr<T> make_unique_for_overwrite() {
|
||||||
|
return std::unique_ptr<T>(new T);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
requires std::is_unbounded_array_v<T>
|
||||||
|
std::unique_ptr<T> make_unique_for_overwrite(std::size_t n) {
|
||||||
|
return std::unique_ptr<T>(new std::remove_extent_t<T>[n]);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class... Args>
|
||||||
|
requires std::is_bounded_array_v<T>
|
||||||
|
void make_unique_for_overwrite(Args&&...) = delete;
|
||||||
|
|
||||||
|
} // namespace Common
|
||||||
@@ -8,7 +8,8 @@
|
|||||||
|
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <memory>
|
|
||||||
|
#include "common/make_unique_for_overwrite.h"
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
@@ -37,9 +38,8 @@ public:
|
|||||||
ScratchBuffer() = default;
|
ScratchBuffer() = default;
|
||||||
|
|
||||||
explicit ScratchBuffer(size_type initial_capacity)
|
explicit ScratchBuffer(size_type initial_capacity)
|
||||||
: last_requested_size{initial_capacity}
|
: last_requested_size{initial_capacity}, buffer_capacity{initial_capacity},
|
||||||
, buffer_capacity{initial_capacity}
|
buffer{Common::make_unique_for_overwrite<T[]>(initial_capacity)} {}
|
||||||
, buffer{std::make_unique_for_overwrite<T[]>(initial_capacity)} {}
|
|
||||||
|
|
||||||
~ScratchBuffer() = default;
|
~ScratchBuffer() = default;
|
||||||
ScratchBuffer(const ScratchBuffer&) = delete;
|
ScratchBuffer(const ScratchBuffer&) = delete;
|
||||||
@@ -64,7 +64,7 @@ public:
|
|||||||
/// The previously held data will remain intact.
|
/// The previously held data will remain intact.
|
||||||
void resize(size_type size) {
|
void resize(size_type size) {
|
||||||
if (size > buffer_capacity) {
|
if (size > buffer_capacity) {
|
||||||
auto new_buffer = std::make_unique_for_overwrite<T[]>(size);
|
auto new_buffer = Common::make_unique_for_overwrite<T[]>(size);
|
||||||
std::memcpy(new_buffer.get(), buffer.get(), buffer_capacity * sizeof(T));
|
std::memcpy(new_buffer.get(), buffer.get(), buffer_capacity * sizeof(T));
|
||||||
buffer = std::move(new_buffer);
|
buffer = std::move(new_buffer);
|
||||||
buffer_capacity = size;
|
buffer_capacity = size;
|
||||||
@@ -77,7 +77,7 @@ public:
|
|||||||
void resize_destructive(size_type size) {
|
void resize_destructive(size_type size) {
|
||||||
if (size > buffer_capacity) {
|
if (size > buffer_capacity) {
|
||||||
buffer_capacity = size;
|
buffer_capacity = size;
|
||||||
buffer = std::make_unique_for_overwrite<T[]>(buffer_capacity);
|
buffer = Common::make_unique_for_overwrite<T[]>(buffer_capacity);
|
||||||
}
|
}
|
||||||
last_requested_size = size;
|
last_requested_size = size;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,21 +96,6 @@ bool CommitVectorPage(uintptr_t addr, bool write) noexcept {
|
|||||||
#ifndef MAP_NOCORE
|
#ifndef MAP_NOCORE
|
||||||
#define MAP_NOCORE 0
|
#define MAP_NOCORE 0
|
||||||
#endif
|
#endif
|
||||||
#ifndef MADV_FREE
|
|
||||||
#define MADV_FREE MADV_DONTNEED
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void DecommitVectorPage(uintptr_t base) noexcept {
|
|
||||||
#if defined(_WIN32)
|
|
||||||
VirtualFree(reinterpret_cast<LPVOID>(base), HostPageSize, MEM_DECOMMIT);
|
|
||||||
#elif defined(__linux__)
|
|
||||||
// Linux's MADV_DONTNEED zeros out pages for us
|
|
||||||
madvise(reinterpret_cast<void*>(base), HostPageSize, MADV_DONTNEED);
|
|
||||||
#else
|
|
||||||
madvise(reinterpret_cast<void*>(base), HostPageSize, MADV_FREE);
|
|
||||||
std::memset(reinterpret_cast<void*>(base), 0, HostPageSize);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void* AllocateMemoryPages(std::size_t size) noexcept {
|
void* AllocateMemoryPages(std::size_t size) noexcept {
|
||||||
if (auto page = HostPageSize; size % page != 0) {
|
if (auto page = HostPageSize; size % page != 0) {
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ const u64 HostPageMask = ~(HostPageSize - 1);
|
|||||||
|
|
||||||
void* AllocateMemoryPages(std::size_t size) noexcept;
|
void* AllocateMemoryPages(std::size_t size) noexcept;
|
||||||
void FreeMemoryPages(void* base, std::size_t size) noexcept;
|
void FreeMemoryPages(void* base, std::size_t size) noexcept;
|
||||||
void DecommitVectorPage(uintptr_t base) noexcept;
|
|
||||||
|
|
||||||
/// A large page-aligned buffer that has optimized memory usage for zero-writes.
|
/// A large page-aligned buffer that has optimized memory usage for zero-writes.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@@ -115,7 +114,7 @@ public:
|
|||||||
const u64 end_page = AlignUp(base, HostPageSize);
|
const u64 end_page = AlignUp(base, HostPageSize);
|
||||||
const u64 first_size = (std::min)(end_page, end) - base;
|
const u64 first_size = (std::min)(end_page, end) - base;
|
||||||
|
|
||||||
if (IsCommittedPage(start)) {
|
if (IsCommittedPage(start / sizeof(T))) {
|
||||||
std::memset(reinterpret_cast<void*>(base), 0, first_size);
|
std::memset(reinterpret_cast<void*>(base), 0, first_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,16 +124,11 @@ public:
|
|||||||
base = end_page;
|
base = end_page;
|
||||||
|
|
||||||
for (u64 page = base; page < end; page += HostPageSize) {
|
for (u64 page = base; page < end; page += HostPageSize) {
|
||||||
auto index = (page - reinterpret_cast<u64>(base_ptr)) / sizeof(T);
|
if (!IsCommittedPage((page - reinterpret_cast<u64>(base_ptr)) / sizeof(T))) {
|
||||||
if (!IsCommittedPage(index)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (end - page >= HostPageSize) {
|
std::memset(reinterpret_cast<void*>(page), 0, (std::min)( HostPageSize, end - page));
|
||||||
DecommitPage(index);
|
|
||||||
} else {
|
|
||||||
std::memset(reinterpret_cast<void*>(page), 0, end - page);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,14 +183,6 @@ private:
|
|||||||
committed_pages[page_index >> 6].fetch_or(1ULL << (page_index & 63), std::memory_order_release);
|
committed_pages[page_index >> 6].fetch_or(1ULL << (page_index & 63), std::memory_order_release);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr void DecommitPage(std::size_t index) noexcept {
|
|
||||||
auto page_index = (index * sizeof(T)) >> HostPageBits;
|
|
||||||
auto page = reinterpret_cast<uintptr_t>(base_ptr + index) & HostPageMask;
|
|
||||||
|
|
||||||
committed_pages[page_index >> 6].fetch_and(~(1ULL << (page_index & 63)), std::memory_order_release);
|
|
||||||
DecommitVectorPage(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t alloc_size{};
|
std::size_t alloc_size{};
|
||||||
T* base_ptr{};
|
T* base_ptr{};
|
||||||
|
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
#include "common/zbic_compression.h"
|
|
||||||
|
|
||||||
#define ZSTD_ZBIC_SUPPORT 1
|
|
||||||
#define ZSTDLIB_VISIBLE static
|
|
||||||
#define ZSTDLIB_HIDDEN static
|
|
||||||
#define ZSTDERRORLIB_VISIBLE static
|
|
||||||
#define ZSTDERRORLIB_HIDDEN static
|
|
||||||
#undef ZSTD_MULTITHREAD
|
|
||||||
|
|
||||||
#include "zstd.h"
|
|
||||||
#include "zstd.c"
|
|
||||||
|
|
||||||
namespace Common::Compression {
|
|
||||||
|
|
||||||
bool IsZBIC(std::span<const u8> src) {
|
|
||||||
if (src.size() < sizeof(u32)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
u32 magic = 0;
|
|
||||||
std::memcpy(&magic, src.data(), sizeof(u32));
|
|
||||||
return magic == ZSTD_MAGICNUMBER; // 0x4349425A ("ZBIC")
|
|
||||||
}
|
|
||||||
|
|
||||||
int DecompressDataZBIC(std::span<u8> dst, std::span<const u8> src) {
|
|
||||||
if (dst.empty() || src.empty()) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
const size_t res = ZSTD_decompress(dst.data(), dst.size(), src.data(), src.size());
|
|
||||||
if (ZSTD_isError(res)) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return static_cast<int>(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Common::Compression
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <span>
|
|
||||||
#include "common/common_types.h"
|
|
||||||
|
|
||||||
namespace Common::Compression {
|
|
||||||
|
|
||||||
[[nodiscard]] bool IsZBIC(std::span<const u8> src);
|
|
||||||
|
|
||||||
[[nodiscard]] int DecompressDataZBIC(std::span<u8> dst, std::span<const u8> src);
|
|
||||||
|
|
||||||
} // namespace Common::Compression
|
|
||||||
@@ -23,8 +23,6 @@ add_library(core STATIC
|
|||||||
core_timing.h
|
core_timing.h
|
||||||
cpu_manager.cpp
|
cpu_manager.cpp
|
||||||
cpu_manager.h
|
cpu_manager.h
|
||||||
launch_params.cpp
|
|
||||||
launch_params.h
|
|
||||||
crypto/aes_util.cpp
|
crypto/aes_util.cpp
|
||||||
crypto/aes_util.h
|
crypto/aes_util.h
|
||||||
crypto/ctr_encryption_layer.cpp
|
crypto/ctr_encryption_layer.cpp
|
||||||
@@ -1205,9 +1203,6 @@ endif()
|
|||||||
|
|
||||||
target_include_directories(core PRIVATE ${OPUS_INCLUDE_DIRS})
|
target_include_directories(core PRIVATE ${OPUS_INCLUDE_DIRS})
|
||||||
target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb tz)
|
target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb tz)
|
||||||
if (MSVC)
|
|
||||||
target_link_libraries(core PRIVATE getopt)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (BOOST_NO_HEADERS)
|
if (BOOST_NO_HEADERS)
|
||||||
target_link_libraries(core PUBLIC Boost::container Boost::heap Boost::asio Boost::process Boost::crc)
|
target_link_libraries(core PUBLIC Boost::container Boost::heap Boost::asio Boost::process Boost::crc)
|
||||||
|
|||||||
+45
-23
@@ -110,8 +110,13 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
|
|||||||
|
|
||||||
struct System::Impl {
|
struct System::Impl {
|
||||||
explicit Impl(System& system)
|
explicit Impl(System& system)
|
||||||
: kernel{system}, fs_controller{system}, hid_core{kernel}, cpu_manager{system},
|
: kernel{system}
|
||||||
reporter{system}, applet_manager{system}, frontend_applets{system}, profile_manager{} {}
|
, fs_controller{system}
|
||||||
|
, hid_core{kernel}
|
||||||
|
, cpu_manager{system}
|
||||||
|
, reporter{system}
|
||||||
|
, profile_manager{}
|
||||||
|
{}
|
||||||
|
|
||||||
u64 program_id;
|
u64 program_id;
|
||||||
|
|
||||||
@@ -124,6 +129,12 @@ struct System::Impl {
|
|||||||
core_timing.SetMulticore(is_multicore);
|
core_timing.SetMulticore(is_multicore);
|
||||||
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
|
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
|
||||||
|
|
||||||
|
applet_manager.emplace(system);
|
||||||
|
frontend_applets.emplace(system);
|
||||||
|
apm_controller.emplace(core_timing);
|
||||||
|
arp_manager.emplace();
|
||||||
|
profile_manager.emplace();
|
||||||
|
|
||||||
// Create a default fs if one doesn't already exist.
|
// Create a default fs if one doesn't already exist.
|
||||||
if (virtual_filesystem == nullptr) {
|
if (virtual_filesystem == nullptr) {
|
||||||
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
|
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
|
||||||
@@ -133,7 +144,7 @@ struct System::Impl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create default implementations of applets if one is not provided.
|
// Create default implementations of applets if one is not provided.
|
||||||
frontend_applets.SetDefaultAppletsIfMissing();
|
frontend_applets->SetDefaultAppletsIfMissing();
|
||||||
|
|
||||||
auto const is_async_gpu = Settings::values.use_asynchronous_gpu_emulation.GetValue();
|
auto const is_async_gpu = Settings::values.use_asynchronous_gpu_emulation.GetValue();
|
||||||
|
|
||||||
@@ -376,7 +387,7 @@ struct System::Impl {
|
|||||||
|
|
||||||
// Register with applet manager
|
// Register with applet manager
|
||||||
// All threads are started, begin main process execution, now that we're in the clear
|
// All threads are started, begin main process execution, now that we're in the clear
|
||||||
applet_manager.CreateAndInsertByFrontendAppletParameters(std::move(process), params);
|
applet_manager->CreateAndInsertByFrontendAppletParameters(std::move(process), params);
|
||||||
|
|
||||||
if (Settings::values.gamecard_inserted) {
|
if (Settings::values.gamecard_inserted) {
|
||||||
if (Settings::values.gamecard_current_game) {
|
if (Settings::values.gamecard_current_game) {
|
||||||
@@ -428,13 +439,27 @@ struct System::Impl {
|
|||||||
core_timing.SyncPause(false);
|
core_timing.SyncPause(false);
|
||||||
Network::CancelPendingSocketOperations();
|
Network::CancelPendingSocketOperations();
|
||||||
kernel.SuspendEmulation(true);
|
kernel.SuspendEmulation(true);
|
||||||
kernel.CloseServices();
|
|
||||||
kernel.ShutdownCores();
|
kernel.ShutdownCores();
|
||||||
|
|
||||||
|
// Notify services helpers of shutdown
|
||||||
|
audio_core->Shutdown();
|
||||||
|
|
||||||
|
// Wait for threads/services to join
|
||||||
|
kernel.CloseServices();
|
||||||
|
|
||||||
|
// service shutdown
|
||||||
services.reset();
|
services.reset();
|
||||||
service_manager.reset();
|
service_manager.reset();
|
||||||
|
frontend_applets.reset();
|
||||||
|
applet_manager.reset();
|
||||||
|
apm_controller.reset();
|
||||||
|
arp_manager.reset();
|
||||||
|
profile_manager.reset();
|
||||||
|
|
||||||
fs_controller.Reset();
|
fs_controller.Reset();
|
||||||
cheat_engine.reset();
|
cheat_engine.reset();
|
||||||
core_timing.ClearPendingEvents();
|
core_timing.ClearPendingEvents();
|
||||||
|
core_timing.Reset();
|
||||||
app_loader.reset();
|
app_loader.reset();
|
||||||
audio_core.reset();
|
audio_core.reset();
|
||||||
gpu_core.reset();
|
gpu_core.reset();
|
||||||
@@ -451,9 +476,6 @@ struct System::Impl {
|
|||||||
room_member->SendGameInfo(game_info);
|
room_member->SendGameInfo(game_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset all glue registrations
|
|
||||||
arp_manager.ResetAll();
|
|
||||||
|
|
||||||
LOG_DEBUG(Core, "Shutdown OK");
|
LOG_DEBUG(Core, "Shutdown OK");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,13 +504,13 @@ struct System::Impl {
|
|||||||
CpuManager cpu_manager;
|
CpuManager cpu_manager;
|
||||||
Reporter reporter;
|
Reporter reporter;
|
||||||
/// Applets
|
/// Applets
|
||||||
Service::AM::AppletManager applet_manager;
|
std::optional<Service::AM::AppletManager> applet_manager;
|
||||||
Service::AM::Frontend::FrontendAppletHolder frontend_applets;
|
std::optional<Service::AM::Frontend::FrontendAppletHolder> frontend_applets;
|
||||||
/// APM (Performance) services
|
/// APM (Performance) services
|
||||||
Service::APM::Controller apm_controller{core_timing};
|
std::optional<Service::APM::Controller> apm_controller;
|
||||||
/// Service State
|
/// Service State
|
||||||
Service::Glue::ARPManager arp_manager;
|
std::optional<Service::Glue::ARPManager> arp_manager;
|
||||||
Service::Account::ProfileManager profile_manager;
|
std::optional<Service::Account::ProfileManager> profile_manager;
|
||||||
/// Network instance
|
/// Network instance
|
||||||
Network::NetworkInstance network_instance;
|
Network::NetworkInstance network_instance;
|
||||||
Core::SpeedLimiter speed_limiter;
|
Core::SpeedLimiter speed_limiter;
|
||||||
@@ -815,19 +837,19 @@ void System::RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void System::SetFrontendAppletSet(Service::AM::Frontend::FrontendAppletSet&& set) {
|
void System::SetFrontendAppletSet(Service::AM::Frontend::FrontendAppletSet&& set) {
|
||||||
impl->frontend_applets.SetFrontendAppletSet(std::move(set));
|
impl->frontend_applets->SetFrontendAppletSet(std::move(set));
|
||||||
}
|
}
|
||||||
|
|
||||||
Service::AM::Frontend::FrontendAppletHolder& System::GetFrontendAppletHolder() {
|
Service::AM::Frontend::FrontendAppletHolder& System::GetFrontendAppletHolder() {
|
||||||
return impl->frontend_applets;
|
return *impl->frontend_applets;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Service::AM::Frontend::FrontendAppletHolder& System::GetFrontendAppletHolder() const {
|
const Service::AM::Frontend::FrontendAppletHolder& System::GetFrontendAppletHolder() const {
|
||||||
return impl->frontend_applets;
|
return *impl->frontend_applets;
|
||||||
}
|
}
|
||||||
|
|
||||||
Service::AM::AppletManager& System::GetAppletManager() {
|
Service::AM::AppletManager& System::GetAppletManager() {
|
||||||
return impl->applet_manager;
|
return *impl->applet_manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::SetContentProvider(std::unique_ptr<FileSys::ContentProviderUnion> provider) {
|
void System::SetContentProvider(std::unique_ptr<FileSys::ContentProviderUnion> provider) {
|
||||||
@@ -868,27 +890,27 @@ const Reporter& System::GetReporter() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Service::Glue::ARPManager& System::GetARPManager() {
|
Service::Glue::ARPManager& System::GetARPManager() {
|
||||||
return impl->arp_manager;
|
return *impl->arp_manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Service::Glue::ARPManager& System::GetARPManager() const {
|
const Service::Glue::ARPManager& System::GetARPManager() const {
|
||||||
return impl->arp_manager;
|
return *impl->arp_manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
Service::APM::Controller& System::GetAPMController() {
|
Service::APM::Controller& System::GetAPMController() {
|
||||||
return impl->apm_controller;
|
return *impl->apm_controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Service::APM::Controller& System::GetAPMController() const {
|
const Service::APM::Controller& System::GetAPMController() const {
|
||||||
return impl->apm_controller;
|
return *impl->apm_controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
Service::Account::ProfileManager& System::GetProfileManager() {
|
Service::Account::ProfileManager& System::GetProfileManager() {
|
||||||
return impl->profile_manager;
|
return *impl->profile_manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Service::Account::ProfileManager& System::GetProfileManager() const {
|
const Service::Account::ProfileManager& System::GetProfileManager() const {
|
||||||
return impl->profile_manager;
|
return *impl->profile_manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::SetExitLocked(bool locked) {
|
void System::SetExitLocked(bool locked) {
|
||||||
|
|||||||
@@ -304,10 +304,10 @@ std::optional<s64> CoreTiming::Advance() {
|
|||||||
|
|
||||||
void CoreTiming::Reset() {
|
void CoreTiming::Reset() {
|
||||||
paused = true;
|
paused = true;
|
||||||
pause_event.Set();
|
|
||||||
event.Set();
|
|
||||||
if (timer_thread.joinable()) {
|
if (timer_thread.joinable()) {
|
||||||
timer_thread.request_stop();
|
timer_thread.request_stop();
|
||||||
|
pause_event.Set();
|
||||||
|
event.Set();
|
||||||
timer_thread.join();
|
timer_thread.join();
|
||||||
}
|
}
|
||||||
has_started = false;
|
has_started = false;
|
||||||
|
|||||||
@@ -109,7 +109,8 @@ VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::op
|
|||||||
auto reference = std::make_unique<FileReference>();
|
auto reference = std::make_unique<FileReference>();
|
||||||
this->InsertReferenceIntoListLocked(*reference);
|
this->InsertReferenceIntoListLocked(*reference);
|
||||||
|
|
||||||
auto file = std::make_shared<RealVfsFile>(*this, std::move(reference), path, perms, size, std::move(parent_path));
|
auto file = std::shared_ptr<RealVfsFile>(
|
||||||
|
new RealVfsFile(*this, std::move(reference), path, perms, size, std::move(parent_path)));
|
||||||
cache[path] = file;
|
cache[path] = file;
|
||||||
|
|
||||||
return file;
|
return file;
|
||||||
@@ -176,7 +177,7 @@ bool RealVfsFilesystem::DeleteFile(std::string_view path_) {
|
|||||||
|
|
||||||
VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, OpenMode perms) {
|
VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, OpenMode perms) {
|
||||||
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
|
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
|
||||||
return std::make_shared<RealVfsDirectory>(*this, path, perms);
|
return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
|
||||||
}
|
}
|
||||||
|
|
||||||
VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, OpenMode perms) {
|
VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, OpenMode perms) {
|
||||||
@@ -184,7 +185,7 @@ VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, OpenMode p
|
|||||||
if (!FS::CreateDirs(path)) {
|
if (!FS::CreateDirs(path)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return std::make_shared<RealVfsDirectory>(*this, path, perms);
|
return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
|
||||||
}
|
}
|
||||||
|
|
||||||
VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_,
|
VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_,
|
||||||
|
|||||||
@@ -82,9 +82,6 @@ class RealVfsFile : public VfsFile {
|
|||||||
friend class RealVfsFilesystem;
|
friend class RealVfsFilesystem;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RealVfsFile(RealVfsFilesystem& base, std::unique_ptr<FileReference> reference,
|
|
||||||
const std::string& path, OpenMode perms = OpenMode::Read,
|
|
||||||
std::optional<u64> size = {}, std::optional<std::string> parent_path = {});
|
|
||||||
~RealVfsFile() override;
|
~RealVfsFile() override;
|
||||||
|
|
||||||
std::string GetName() const override;
|
std::string GetName() const override;
|
||||||
@@ -98,6 +95,9 @@ public:
|
|||||||
bool Rename(std::string_view name) override;
|
bool Rename(std::string_view name) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
RealVfsFile(RealVfsFilesystem& base, std::unique_ptr<FileReference> reference,
|
||||||
|
const std::string& path, OpenMode perms = OpenMode::Read,
|
||||||
|
std::optional<u64> size = {}, std::optional<std::string> parent_path = {});
|
||||||
|
|
||||||
RealVfsFilesystem& base;
|
RealVfsFilesystem& base;
|
||||||
std::unique_ptr<FileReference> reference;
|
std::unique_ptr<FileReference> reference;
|
||||||
@@ -113,8 +113,6 @@ class RealVfsDirectory : public VfsDirectory {
|
|||||||
friend class RealVfsFilesystem;
|
friend class RealVfsFilesystem;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RealVfsDirectory(RealVfsFilesystem& base, const std::string& path,
|
|
||||||
OpenMode perms = OpenMode::Read);
|
|
||||||
~RealVfsDirectory() override;
|
~RealVfsDirectory() override;
|
||||||
|
|
||||||
VirtualFile GetFileRelative(std::string_view relative_path) const override;
|
VirtualFile GetFileRelative(std::string_view relative_path) const override;
|
||||||
@@ -140,6 +138,9 @@ public:
|
|||||||
std::map<std::string, VfsEntryType, std::less<>> GetEntries() const override;
|
std::map<std::string, VfsEntryType, std::less<>> GetEntries() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
RealVfsDirectory(RealVfsFilesystem& base, const std::string& path,
|
||||||
|
OpenMode perms = OpenMode::Read);
|
||||||
|
|
||||||
template <typename T, typename R>
|
template <typename T, typename R>
|
||||||
std::vector<std::shared_ptr<R>> IterateEntries() const;
|
std::vector<std::shared_ptr<R>> IterateEntries() const;
|
||||||
|
|
||||||
|
|||||||
@@ -72,11 +72,9 @@ void GlobalSchedulerContext::UnregisterDummyThreadForWakeup(KThread* thread) noe
|
|||||||
|
|
||||||
void GlobalSchedulerContext::WakeupWaitingDummyThreads(KernelCore& kernel) noexcept {
|
void GlobalSchedulerContext::WakeupWaitingDummyThreads(KernelCore& kernel) noexcept {
|
||||||
ASSERT(this->IsLocked());
|
ASSERT(this->IsLocked());
|
||||||
if (m_woken_dummy_threads.size() > 0) {
|
for (auto* thread : m_woken_dummy_threads)
|
||||||
for (auto* thread : m_woken_dummy_threads)
|
thread->DummyThreadEndWait(kernel);
|
||||||
thread->DummyThreadEndWait(kernel);
|
m_woken_dummy_threads.clear();
|
||||||
m_woken_dummy_threads.clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Kernel
|
} // namespace Kernel
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ Result TerminateChildren(KernelCore& kernel, KProcess* process, const KThread* t
|
|||||||
KThread* cur_child = nullptr;
|
KThread* cur_child = nullptr;
|
||||||
{
|
{
|
||||||
KScopedLightLock proc_lk(process->GetListLock());
|
KScopedLightLock proc_lk(process->GetListLock());
|
||||||
|
|
||||||
auto& thread_list = process->GetThreadList();
|
auto& thread_list = process->GetThreadList();
|
||||||
for (auto it = thread_list.begin(); it != thread_list.end(); ++it) {
|
for (auto it = thread_list.begin(); it != thread_list.end(); ++it) {
|
||||||
if (KThread* thread = std::addressof(*it); thread != thread_to_not_terminate) {
|
if (KThread* thread = std::addressof(*it); thread != thread_to_not_terminate) {
|
||||||
|
|||||||
@@ -1236,18 +1236,13 @@ namespace Kernel {
|
|||||||
KScopedSchedulerLock sl{kernel};
|
KScopedSchedulerLock sl{kernel};
|
||||||
|
|
||||||
// Determine if this is the first termination request.
|
// Determine if this is the first termination request.
|
||||||
const bool first_request = [&]() -> bool {
|
// Perform an atomic compare-and-swap from false to true.
|
||||||
// Perform an atomic compare-and-swap from false to true.
|
bool expected = false;
|
||||||
bool expected = false;
|
|
||||||
return m_termination_requested.compare_exchange_strong(expected, true);
|
|
||||||
}();
|
|
||||||
|
|
||||||
// If this is the first request, start termination procedure.
|
// If this is the first request, start termination procedure.
|
||||||
if (first_request) {
|
if (m_termination_requested.compare_exchange_strong(expected, true)) {
|
||||||
// If the thread is in initialized state, just change state to terminated.
|
// If the thread is in initialized state, just change state to terminated.
|
||||||
if (this->GetState() == ThreadState::Initialized) {
|
if (this->GetState() == ThreadState::Initialized) {
|
||||||
m_thread_state = ThreadState::Terminated;
|
return m_thread_state = ThreadState::Terminated;
|
||||||
return ThreadState::Terminated;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the terminating dpc.
|
// Register the terminating dpc.
|
||||||
@@ -1281,7 +1276,6 @@ namespace Kernel {
|
|||||||
m_wait_queue->CancelWait(kernel, this, ResultTerminationRequested, true);
|
m_wait_queue->CancelWait(kernel, this, ResultTerminationRequested, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this->GetState();
|
return this->GetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
#include "common/thread.h"
|
||||||
#include "core/hle/kernel/k_process.h"
|
#include "core/hle/kernel/k_process.h"
|
||||||
#include "core/hle/kernel/k_thread.h"
|
#include "core/hle/kernel/k_thread.h"
|
||||||
#include "core/hle/kernel/k_worker_task.h"
|
#include "core/hle/kernel/k_worker_task.h"
|
||||||
@@ -26,7 +28,33 @@ void KWorkerTask::DoWorkerTask(KernelCore& kernel) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
KWorkerTaskManager::KWorkerTaskManager() : m_waiting_thread(1, "KWorkerTaskManager") {}
|
KWorkerTaskManager::KWorkerTaskManager(KernelCore& kernel) {
|
||||||
|
m_waiting_thread = std::jthread([&kernel, this](std::stop_token stop_token) {
|
||||||
|
Common::SetCurrentThreadName("KWorkerManager");
|
||||||
|
while (!stop_token.stop_requested()) {
|
||||||
|
KWorkerTask* t;
|
||||||
|
{
|
||||||
|
std::unique_lock lk{m_task_mutex};
|
||||||
|
m_task_cv.wait(lk, m_waiting_thread.get_stop_token(), [&]() {
|
||||||
|
return !m_task_queue.empty();
|
||||||
|
});
|
||||||
|
if (stop_token.stop_requested())
|
||||||
|
break;
|
||||||
|
t = m_task_queue.back();
|
||||||
|
m_task_queue.pop_back();
|
||||||
|
}
|
||||||
|
t->DoWorkerTask(kernel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
KWorkerTaskManager::~KWorkerTaskManager() {
|
||||||
|
if (m_waiting_thread.joinable()) {
|
||||||
|
m_waiting_thread.request_stop();
|
||||||
|
m_task_cv.notify_one();
|
||||||
|
m_waiting_thread.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void KWorkerTaskManager::AddTask(KernelCore& kernel, WorkerType type, KWorkerTask* task) {
|
void KWorkerTaskManager::AddTask(KernelCore& kernel, WorkerType type, KWorkerTask* task) {
|
||||||
ASSERT(type <= WorkerType::Count);
|
ASSERT(type <= WorkerType::Count);
|
||||||
@@ -35,10 +63,11 @@ void KWorkerTaskManager::AddTask(KernelCore& kernel, WorkerType type, KWorkerTas
|
|||||||
|
|
||||||
void KWorkerTaskManager::AddTask(KernelCore& kernel, KWorkerTask* task) {
|
void KWorkerTaskManager::AddTask(KernelCore& kernel, KWorkerTask* task) {
|
||||||
KScopedSchedulerLock sl(kernel);
|
KScopedSchedulerLock sl(kernel);
|
||||||
m_waiting_thread.QueueWork([&kernel, task]() {
|
{
|
||||||
// Do the task.
|
std::scoped_lock lk{m_task_mutex};
|
||||||
task->DoWorkerTask(kernel);
|
m_task_queue.emplace_back(task);
|
||||||
});
|
}
|
||||||
|
m_task_cv.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Kernel
|
} // namespace Kernel
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <thread>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/thread_worker.h"
|
#include "common/thread_worker.h"
|
||||||
|
|
||||||
@@ -18,15 +23,17 @@ public:
|
|||||||
Count,
|
Count,
|
||||||
};
|
};
|
||||||
|
|
||||||
KWorkerTaskManager();
|
KWorkerTaskManager(KernelCore& kernel);
|
||||||
|
~KWorkerTaskManager();
|
||||||
|
|
||||||
static void AddTask(KernelCore& kernel, WorkerType type, KWorkerTask* task);
|
static void AddTask(KernelCore& kernel, WorkerType type, KWorkerTask* task);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void AddTask(KernelCore& kernel, KWorkerTask* task);
|
void AddTask(KernelCore& kernel, KWorkerTask* task);
|
||||||
|
std::jthread m_waiting_thread;
|
||||||
|
|
||||||
private:
|
std::mutex m_task_mutex;
|
||||||
Common::ThreadWorker m_waiting_thread;
|
std::condition_variable_any m_task_cv;
|
||||||
|
std::vector<KWorkerTask*> m_task_queue;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Kernel
|
} // namespace Kernel
|
||||||
|
|||||||
@@ -80,7 +80,10 @@ struct KernelCore::Impl {
|
|||||||
// so it will be statically given a TLS slot anyways.
|
// so it will be statically given a TLS slot anyways.
|
||||||
static inline thread_local ThreadLocalData tls_data = {};
|
static inline thread_local ThreadLocalData tls_data = {};
|
||||||
|
|
||||||
explicit Impl(Core::System& system_, KernelCore& kernel_) : system{system_} {
|
explicit Impl(Core::System& system_, KernelCore& kernel_)
|
||||||
|
: worker_task_manager{kernel_}
|
||||||
|
, system{system_}
|
||||||
|
{
|
||||||
tls_data.lock = true;
|
tls_data.lock = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -967,12 +970,16 @@ Kernel::KHardwareTimer& KernelCore::HardwareTimer() {
|
|||||||
return *impl->hardware_timer;
|
return *impl->hardware_timer;
|
||||||
}
|
}
|
||||||
|
|
||||||
KAutoObjectWithListContainer& KernelCore::ObjectListContainer() {
|
KAutoObjectWithListContainer* KernelCore::ObjectListContainer() {
|
||||||
return *impl->global_object_list_container;
|
if (!impl->global_object_list_container)
|
||||||
|
return nullptr;
|
||||||
|
return std::addressof(*impl->global_object_list_container);
|
||||||
}
|
}
|
||||||
|
|
||||||
const KAutoObjectWithListContainer& KernelCore::ObjectListContainer() const {
|
const KAutoObjectWithListContainer* KernelCore::ObjectListContainer() const {
|
||||||
return *impl->global_object_list_container;
|
if (!impl->global_object_list_container)
|
||||||
|
return nullptr;
|
||||||
|
return std::addressof(*impl->global_object_list_container);
|
||||||
}
|
}
|
||||||
|
|
||||||
void KernelCore::PrepareReschedule(std::size_t id) {
|
void KernelCore::PrepareReschedule(std::size_t id) {
|
||||||
@@ -1001,16 +1008,10 @@ void KernelCore::UnregisterInUseObject(KAutoObject* object) {
|
|||||||
|
|
||||||
void KernelCore::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) {
|
void KernelCore::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) {
|
||||||
auto* manager = server_manager.get();
|
auto* manager = server_manager.get();
|
||||||
|
if (!impl->is_shutting_down) {
|
||||||
{
|
|
||||||
std::scoped_lock lk{impl->server_lock};
|
std::scoped_lock lk{impl->server_lock};
|
||||||
if (impl->is_shutting_down) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl->server_managers.emplace_back(std::move(server_manager));
|
impl->server_managers.emplace_back(std::move(server_manager));
|
||||||
}
|
}
|
||||||
|
|
||||||
manager->LoopProcess();
|
manager->LoopProcess();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1255,6 +1256,10 @@ void KernelCore::SuspendEmulation(bool suspended) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void KernelCore::ShutdownCores() {
|
void KernelCore::ShutdownCores() {
|
||||||
|
// Notify shutdown (pre-emptively)
|
||||||
|
for (auto& sm : impl->server_managers) {
|
||||||
|
sm->NotifyShutdown();
|
||||||
|
}
|
||||||
impl->TerminateAllProcesses();
|
impl->TerminateAllProcesses();
|
||||||
KScopedSchedulerLock lk{*this};
|
KScopedSchedulerLock lk{*this};
|
||||||
for (auto* thread : impl->shutdown_threads)
|
for (auto* thread : impl->shutdown_threads)
|
||||||
|
|||||||
@@ -175,9 +175,8 @@ public:
|
|||||||
/// Stops execution of 'id' core, in order to reschedule a new thread.
|
/// Stops execution of 'id' core, in order to reschedule a new thread.
|
||||||
void PrepareReschedule(std::size_t id);
|
void PrepareReschedule(std::size_t id);
|
||||||
|
|
||||||
KAutoObjectWithListContainer& ObjectListContainer();
|
KAutoObjectWithListContainer* ObjectListContainer();
|
||||||
|
const KAutoObjectWithListContainer* ObjectListContainer() const;
|
||||||
const KAutoObjectWithListContainer& ObjectListContainer() const;
|
|
||||||
|
|
||||||
/// Registers all kernel objects with the global emulation state, this is purely for tracking
|
/// Registers all kernel objects with the global emulation state, this is purely for tracking
|
||||||
/// leaks after emulation has been shutdown.
|
/// leaks after emulation has been shutdown.
|
||||||
|
|||||||
@@ -151,7 +151,8 @@ public:
|
|||||||
const bool is_initialized = this->IsInitialized();
|
const bool is_initialized = this->IsInitialized();
|
||||||
uintptr_t arg = 0;
|
uintptr_t arg = 0;
|
||||||
if (is_initialized) {
|
if (is_initialized) {
|
||||||
kernel.ObjectListContainer().Unregister(this);
|
if (auto const olc = kernel.ObjectListContainer(); olc)
|
||||||
|
olc->Unregister(this);
|
||||||
arg = this->GetPostDestroyArgument();
|
arg = this->GetPostDestroyArgument();
|
||||||
this->Finalize(kernel);
|
this->Finalize(kernel);
|
||||||
}
|
}
|
||||||
@@ -175,7 +176,7 @@ public:
|
|||||||
public:
|
public:
|
||||||
static void InitializeSlabHeap(KernelCore& kernel, void* memory, size_t memory_size) {
|
static void InitializeSlabHeap(KernelCore& kernel, void* memory, size_t memory_size) {
|
||||||
kernel.SlabHeap<Derived>().Initialize(memory, memory_size);
|
kernel.SlabHeap<Derived>().Initialize(memory, memory_size);
|
||||||
kernel.ObjectListContainer().Initialize();
|
kernel.ObjectListContainer()->Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
static Derived* Create(KernelCore& kernel) {
|
static Derived* Create(KernelCore& kernel) {
|
||||||
@@ -186,7 +187,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void Register(KernelCore& kernel, Derived* obj) {
|
static void Register(KernelCore& kernel, Derived* obj) {
|
||||||
return kernel.ObjectListContainer().Register(obj);
|
return kernel.ObjectListContainer()->Register(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t GetObjectSize(KernelCore& kernel) {
|
static size_t GetObjectSize(KernelCore& kernel) {
|
||||||
|
|||||||
@@ -15,13 +15,9 @@ void LoopProcess(Core::System& system) {
|
|||||||
WindowSystem window_system(system);
|
WindowSystem window_system(system);
|
||||||
ButtonPoller button_poller(system, window_system);
|
ButtonPoller button_poller(system, window_system);
|
||||||
EventObserver event_observer(system, window_system);
|
EventObserver event_observer(system, window_system);
|
||||||
|
|
||||||
auto server_manager = std::make_unique<ServerManager>(system);
|
auto server_manager = std::make_unique<ServerManager>(system);
|
||||||
|
server_manager->RegisterNamedService("appletAE", std::make_shared<IAllSystemAppletProxiesService>(system, window_system));
|
||||||
server_manager->RegisterNamedService(
|
server_manager->RegisterNamedService("appletOE", std::make_shared<IApplicationProxyService>(system, window_system));
|
||||||
"appletAE", std::make_shared<IAllSystemAppletProxiesService>(system, window_system));
|
|
||||||
server_manager->RegisterNamedService(
|
|
||||||
"appletOE", std::make_shared<IApplicationProxyService>(system, window_system));
|
|
||||||
ServerManager::RunServer(std::move(server_manager));
|
ServerManager::RunServer(std::move(server_manager));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,24 +26,25 @@ EventObserver::EventObserver(Core::System& system, WindowSystem& window_system)
|
|||||||
m_window_system.SetEventObserver(this);
|
m_window_system.SetEventObserver(this);
|
||||||
m_wakeup_holder.SetUserData(static_cast<uintptr_t>(UserDataTag::WakeupEvent));
|
m_wakeup_holder.SetUserData(static_cast<uintptr_t>(UserDataTag::WakeupEvent));
|
||||||
m_wakeup_holder.LinkToMultiWait(std::addressof(m_multi_wait));
|
m_wakeup_holder.LinkToMultiWait(std::addressof(m_multi_wait));
|
||||||
m_thread = std::jthread([this](std::stop_token stop_token) {
|
system.Kernel().RunOnGuestCoreProcess("am:EventObserver", [this]() {
|
||||||
Common::SetCurrentThreadName("am:EventObserver");
|
auto const stop_token = m_stop_source.get_token();
|
||||||
while (!stop_token.stop_requested()) {
|
while (!stop_token.stop_requested()) {
|
||||||
auto* signaled_holder = this->WaitSignaled(stop_token);
|
auto* signaled_holder = this->WaitSignaled(stop_token);
|
||||||
if (!signaled_holder)
|
if (stop_token.stop_requested() || !signaled_holder)
|
||||||
break;
|
break;
|
||||||
this->Process(signaled_holder);
|
this->Process(signaled_holder);
|
||||||
}
|
}
|
||||||
|
m_process_cv.notify_one();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
EventObserver::~EventObserver() {
|
EventObserver::~EventObserver() {
|
||||||
// Signal thread and wait for processing to finish.
|
// Signal thread and wait for processing to finish.
|
||||||
if (m_thread.joinable()) {
|
m_stop_source.request_stop();
|
||||||
// Signal thread and wait for processing to finish.
|
m_wakeup_event.Signal(m_system.Kernel());
|
||||||
m_thread.request_stop();
|
{
|
||||||
m_wakeup_event.Signal(m_system.Kernel());
|
std::unique_lock lk{m_process_mutex};
|
||||||
m_thread.join();
|
m_process_cv.wait(lk);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Free remaining owned sessions.
|
// Free remaining owned sessions.
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <stop_token>
|
||||||
#include "common/polyfill_thread.h"
|
#include "common/polyfill_thread.h"
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
#include "core/hle/service/kernel_helpers.h"
|
#include "core/hle/service/kernel_helpers.h"
|
||||||
@@ -62,7 +64,9 @@ private:
|
|||||||
MultiWait m_deferred_wait_list;
|
MultiWait m_deferred_wait_list;
|
||||||
|
|
||||||
// Processing thread.
|
// Processing thread.
|
||||||
std::jthread m_thread{};
|
std::stop_source m_stop_source{};
|
||||||
|
std::mutex m_process_mutex;
|
||||||
|
std::condition_variable m_process_cv{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Service::AM
|
} // namespace Service::AM
|
||||||
|
|||||||
@@ -517,7 +517,7 @@ void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground, b
|
|||||||
// Layer ordering. Composition sorts back-to-front. Now with enums for calrity.
|
// Layer ordering. Composition sorts back-to-front. Now with enums for calrity.
|
||||||
s32 z_index = Background;
|
s32 z_index = Background;
|
||||||
if (is_overlay) {
|
if (is_overlay) {
|
||||||
z_index = this->IsOverlayOpenLocked(*applet) ? Overlay : Background;
|
z_index = Overlay;
|
||||||
} else if (inherited_foreground) {
|
} else if (inherited_foreground) {
|
||||||
z_index = is_obscured ? Foreground : ForegroundVisible;
|
z_index = is_obscured ? Foreground : ForegroundVisible;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
@@ -90,10 +90,9 @@ static Service::PSC::Time::LocationName GetTimeZoneString(
|
|||||||
}
|
}
|
||||||
|
|
||||||
TimeManager::TimeManager(Core::System& system)
|
TimeManager::TimeManager(Core::System& system)
|
||||||
: m_steady_clock_resource{system}, m_time_zone_binary{system}, m_worker{
|
: m_steady_clock_resource{system}, m_time_zone_binary{system}
|
||||||
system,
|
, m_worker{system, m_steady_clock_resource, m_file_timestamp_worker}
|
||||||
m_steady_clock_resource,
|
{
|
||||||
m_file_timestamp_worker} {
|
|
||||||
m_time_m =
|
m_time_m =
|
||||||
system.ServiceManager().GetService<Service::PSC::Time::ServiceManager>("time:m", true);
|
system.ServiceManager().GetService<Service::PSC::Time::ServiceManager>("time:m", true);
|
||||||
|
|
||||||
|
|||||||
@@ -20,15 +20,17 @@
|
|||||||
|
|
||||||
namespace Service::Glue::Time {
|
namespace Service::Glue::Time {
|
||||||
|
|
||||||
TimeWorker::TimeWorker(Core::System& system, StandardSteadyClockResource& steady_clock_resource,
|
TimeWorker::TimeWorker(Core::System& system, StandardSteadyClockResource& steady_clock_resource, FileTimestampWorker& file_timestamp_worker)
|
||||||
FileTimestampWorker& file_timestamp_worker)
|
: m_system{system}
|
||||||
: m_system{system}, m_ctx{m_system, "Glue:TimeWorker"}, m_event{m_ctx.CreateEvent(
|
, m_ctx{m_system, "Glue:TimeWorker"}
|
||||||
"Glue:TimeWorker:Event")},
|
, m_event{m_ctx.CreateEvent("Glue:TimeWorker:Event")}
|
||||||
m_steady_clock_resource{steady_clock_resource},
|
, m_steady_clock_resource{steady_clock_resource}
|
||||||
m_file_timestamp_worker{file_timestamp_worker}, m_timer_steady_clock{m_ctx.CreateEvent(
|
, m_file_timestamp_worker{file_timestamp_worker}
|
||||||
"Glue:TimeWorker:SteadyClockTimerEvent")},
|
, m_timer_steady_clock{m_ctx.CreateEvent("Glue:TimeWorker:SteadyClockTimerEvent")}
|
||||||
m_timer_file_system{m_ctx.CreateEvent("Glue:TimeWorker:FileTimeTimerEvent")},
|
, m_timer_file_system{m_ctx.CreateEvent("Glue:TimeWorker:FileTimeTimerEvent")}
|
||||||
m_alarm_worker{m_system, m_steady_clock_resource}, m_pm_state_change_handler{m_alarm_worker} {
|
, m_alarm_worker{m_system, m_steady_clock_resource}
|
||||||
|
, m_pm_state_change_handler{m_alarm_worker}
|
||||||
|
{
|
||||||
m_timer_steady_clock_timing_event = Core::Timing::CreateEvent(
|
m_timer_steady_clock_timing_event = Core::Timing::CreateEvent(
|
||||||
"Time::SteadyClockEvent",
|
"Time::SteadyClockEvent",
|
||||||
[this](s64 time,
|
[this](s64 time,
|
||||||
@@ -47,14 +49,16 @@ TimeWorker::TimeWorker(Core::System& system, StandardSteadyClockResource& steady
|
|||||||
}
|
}
|
||||||
|
|
||||||
TimeWorker::~TimeWorker() {
|
TimeWorker::~TimeWorker() {
|
||||||
|
// Wait for processing to stop
|
||||||
|
m_stop_source.request_stop();
|
||||||
m_local_clock_event->Signal(m_system.Kernel());
|
m_local_clock_event->Signal(m_system.Kernel());
|
||||||
m_network_clock_event->Signal(m_system.Kernel());
|
m_network_clock_event->Signal(m_system.Kernel());
|
||||||
m_ephemeral_clock_event->Signal(m_system.Kernel());
|
m_ephemeral_clock_event->Signal(m_system.Kernel());
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(16));
|
|
||||||
|
|
||||||
m_thread.request_stop();
|
|
||||||
m_event->Signal(m_system.Kernel());
|
m_event->Signal(m_system.Kernel());
|
||||||
m_thread.join();
|
{
|
||||||
|
std::unique_lock lk{m_process_mutex};
|
||||||
|
m_process_cv.wait(lk);
|
||||||
|
}
|
||||||
|
|
||||||
m_ctx.CloseEvent(m_event);
|
m_ctx.CloseEvent(m_event);
|
||||||
m_system.CoreTiming().UnscheduleEvent(m_timer_steady_clock_timing_event);
|
m_system.CoreTiming().UnscheduleEvent(m_timer_steady_clock_timing_event);
|
||||||
@@ -122,166 +126,162 @@ void TimeWorker::Initialize(std::shared_ptr<Service::PSC::Time::StaticService> t
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TimeWorker::StartThread() {
|
void TimeWorker::StartThread() {
|
||||||
m_thread = std::jthread(std::bind_front(&TimeWorker::ThreadFunc, this));
|
m_system.Kernel().RunOnGuestCoreProcess("TimeWorker", [this]() {
|
||||||
}
|
auto const stop_token = m_stop_source.get_token();
|
||||||
|
while (!stop_token.stop_requested()) {
|
||||||
void TimeWorker::ThreadFunc(std::stop_token stop_token) {
|
enum class EventType : s32 {
|
||||||
Common::SetCurrentThreadName("TimeWorker");
|
Exit = 0,
|
||||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Low);
|
PowerStateChange = 1,
|
||||||
|
SignalAlarms = 2,
|
||||||
while (!stop_token.stop_requested()) {
|
UpdateLocalSystemClock = 3,
|
||||||
enum class EventType : s32 {
|
UpdateNetworkSystemClock = 4,
|
||||||
Exit = 0,
|
UpdateEphemeralSystemClock = 5,
|
||||||
PowerStateChange = 1,
|
UpdateSteadyClock = 6,
|
||||||
SignalAlarms = 2,
|
UpdateFileTimestamp = 7,
|
||||||
UpdateLocalSystemClock = 3,
|
AutoCorrect = 8,
|
||||||
UpdateNetworkSystemClock = 4,
|
};
|
||||||
UpdateEphemeralSystemClock = 5,
|
s32 index{};
|
||||||
UpdateSteadyClock = 6,
|
if (m_pm_state_change_handler.m_priority != 0) {
|
||||||
UpdateFileTimestamp = 7,
|
// TODO: gIPmModuleService::GetEvent() 1
|
||||||
AutoCorrect = 8,
|
index = WaitAny(
|
||||||
};
|
m_system.Kernel(),
|
||||||
|
&m_event->GetReadableEvent(), // 0
|
||||||
s32 index{};
|
&m_alarm_worker.GetEvent() // 1
|
||||||
|
);
|
||||||
if (m_pm_state_change_handler.m_priority != 0) {
|
} else {
|
||||||
// TODO: gIPmModuleService::GetEvent() 1
|
// TODO: gIPmModuleService::GetEvent() 1
|
||||||
index = WaitAny(m_system.Kernel(),
|
index = WaitAny(
|
||||||
&m_event->GetReadableEvent(), // 0
|
m_system.Kernel(),
|
||||||
&m_alarm_worker.GetEvent() // 1
|
&m_event->GetReadableEvent(), // 0
|
||||||
);
|
&m_alarm_worker.GetEvent(), // 1
|
||||||
} else {
|
&m_alarm_worker.GetTimerEvent().GetReadableEvent(), // 2
|
||||||
// TODO: gIPmModuleService::GetEvent() 1
|
m_local_clock_event, // 3
|
||||||
index = WaitAny(m_system.Kernel(),
|
m_network_clock_event, // 4
|
||||||
&m_event->GetReadableEvent(), // 0
|
m_ephemeral_clock_event, // 5
|
||||||
&m_alarm_worker.GetEvent(), // 1
|
&m_timer_steady_clock->GetReadableEvent(), // 6
|
||||||
&m_alarm_worker.GetTimerEvent().GetReadableEvent(), // 2
|
&m_timer_file_system->GetReadableEvent(), // 7
|
||||||
m_local_clock_event, // 3
|
m_standard_user_auto_correct_clock_event // 8
|
||||||
m_network_clock_event, // 4
|
);
|
||||||
m_ephemeral_clock_event, // 5
|
|
||||||
&m_timer_steady_clock->GetReadableEvent(), // 6
|
|
||||||
&m_timer_file_system->GetReadableEvent(), // 7
|
|
||||||
m_standard_user_auto_correct_clock_event // 8
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (static_cast<EventType>(index)) {
|
|
||||||
case EventType::Exit:
|
|
||||||
return;
|
|
||||||
|
|
||||||
case EventType::PowerStateChange:
|
|
||||||
m_alarm_worker.GetEvent().Clear(m_system.Kernel());
|
|
||||||
if (m_pm_state_change_handler.m_priority <= 1) {
|
|
||||||
m_alarm_worker.OnPowerStateChanged();
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
case EventType::SignalAlarms:
|
if (stop_token.stop_requested())
|
||||||
m_alarm_worker.GetTimerEvent().Clear(m_system.Kernel());
|
break;
|
||||||
m_time_m->CheckAndSignalAlarms();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EventType::UpdateLocalSystemClock: {
|
switch (EventType(index)) {
|
||||||
m_local_clock_event->Clear(m_system.Kernel());
|
case EventType::Exit:
|
||||||
|
return;
|
||||||
|
|
||||||
Service::PSC::Time::SystemClockContext context{};
|
case EventType::PowerStateChange:
|
||||||
R_ASSERT(m_local_clock->GetSystemClockContext(&context));
|
m_alarm_worker.GetEvent().Clear(m_system.Kernel());
|
||||||
|
if (m_pm_state_change_handler.m_priority <= 1) {
|
||||||
|
m_alarm_worker.OnPowerStateChanged();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
m_set_sys->SetUserSystemClockContext(context);
|
case EventType::SignalAlarms:
|
||||||
m_file_timestamp_worker.SetFilesystemPosixTime();
|
m_alarm_worker.GetTimerEvent().Clear(m_system.Kernel());
|
||||||
break;
|
m_time_m->CheckAndSignalAlarms();
|
||||||
}
|
break;
|
||||||
|
|
||||||
case EventType::UpdateNetworkSystemClock: {
|
case EventType::UpdateLocalSystemClock: {
|
||||||
m_network_clock_event->Clear(m_system.Kernel());
|
m_local_clock_event->Clear(m_system.Kernel());
|
||||||
|
|
||||||
Service::PSC::Time::SystemClockContext context{};
|
Service::PSC::Time::SystemClockContext context{};
|
||||||
R_ASSERT(m_network_clock->GetSystemClockContext(&context));
|
R_ASSERT(m_local_clock->GetSystemClockContext(&context));
|
||||||
|
|
||||||
m_set_sys->SetNetworkSystemClockContext(context);
|
m_set_sys->SetUserSystemClockContext(context);
|
||||||
|
m_file_timestamp_worker.SetFilesystemPosixTime();
|
||||||
s64 time{};
|
|
||||||
if (m_network_clock->GetCurrentTime(&time) != ResultSuccess) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[maybe_unused]] auto offset_before{
|
case EventType::UpdateNetworkSystemClock: {
|
||||||
m_ig_report_network_clock_context_set ? m_report_network_clock_context.offset : 0};
|
m_network_clock_event->Clear(m_system.Kernel());
|
||||||
// TODO system report "standard_netclock_operation"
|
|
||||||
// "clock_time" = time
|
|
||||||
// "context_offset_before" = offset_before
|
|
||||||
// "context_offset_after" = context.offset
|
|
||||||
m_report_network_clock_context = context;
|
|
||||||
if (!m_ig_report_network_clock_context_set) {
|
|
||||||
m_ig_report_network_clock_context_set = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_file_timestamp_worker.SetFilesystemPosixTime();
|
Service::PSC::Time::SystemClockContext context{};
|
||||||
break;
|
R_ASSERT(m_network_clock->GetSystemClockContext(&context));
|
||||||
}
|
|
||||||
|
|
||||||
case EventType::UpdateEphemeralSystemClock: {
|
m_set_sys->SetNetworkSystemClockContext(context);
|
||||||
m_ephemeral_clock_event->Clear(m_system.Kernel());
|
|
||||||
|
|
||||||
Service::PSC::Time::SystemClockContext context{};
|
s64 time{};
|
||||||
auto res = m_ephemeral_clock->GetSystemClockContext(&context);
|
if (m_network_clock->GetCurrentTime(&time) != ResultSuccess) {
|
||||||
if (res != ResultSuccess) {
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]] auto offset_before{
|
||||||
|
m_ig_report_network_clock_context_set ? m_report_network_clock_context.offset : 0};
|
||||||
|
// TODO system report "standard_netclock_operation"
|
||||||
|
// "clock_time" = time
|
||||||
|
// "context_offset_before" = offset_before
|
||||||
|
// "context_offset_after" = context.offset
|
||||||
|
m_report_network_clock_context = context;
|
||||||
|
if (!m_ig_report_network_clock_context_set) {
|
||||||
|
m_ig_report_network_clock_context_set = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_file_timestamp_worker.SetFilesystemPosixTime();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
s64 time{};
|
case EventType::UpdateEphemeralSystemClock: {
|
||||||
res = m_ephemeral_clock->GetCurrentTime(&time);
|
m_ephemeral_clock_event->Clear(m_system.Kernel());
|
||||||
if (res != ResultSuccess) {
|
|
||||||
|
Service::PSC::Time::SystemClockContext context{};
|
||||||
|
auto res = m_ephemeral_clock->GetSystemClockContext(&context);
|
||||||
|
if (res != ResultSuccess) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
s64 time{};
|
||||||
|
res = m_ephemeral_clock->GetCurrentTime(&time);
|
||||||
|
if (res != ResultSuccess) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]] auto offset_before{m_ig_report_ephemeral_clock_context_set
|
||||||
|
? m_report_ephemeral_clock_context.offset
|
||||||
|
: 0};
|
||||||
|
// TODO system report "ephemeral_netclock_operation"
|
||||||
|
// "clock_time" = time
|
||||||
|
// "context_offset_before" = offset_before
|
||||||
|
// "context_offset_after" = context.offset
|
||||||
|
m_report_ephemeral_clock_context = context;
|
||||||
|
if (!m_ig_report_ephemeral_clock_context_set) {
|
||||||
|
m_ig_report_ephemeral_clock_context_set = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[maybe_unused]] auto offset_before{m_ig_report_ephemeral_clock_context_set
|
case EventType::UpdateSteadyClock:
|
||||||
? m_report_ephemeral_clock_context.offset
|
m_timer_steady_clock->Clear(m_system.Kernel());
|
||||||
: 0};
|
|
||||||
// TODO system report "ephemeral_netclock_operation"
|
m_steady_clock_resource.UpdateTime();
|
||||||
// "clock_time" = time
|
m_time_m->SetStandardSteadyClockBaseTime(m_steady_clock_resource.GetTime());
|
||||||
// "context_offset_before" = offset_before
|
break;
|
||||||
// "context_offset_after" = context.offset
|
|
||||||
m_report_ephemeral_clock_context = context;
|
case EventType::UpdateFileTimestamp:
|
||||||
if (!m_ig_report_ephemeral_clock_context_set) {
|
m_timer_file_system->Clear(m_system.Kernel());
|
||||||
m_ig_report_ephemeral_clock_context_set = true;
|
m_file_timestamp_worker.SetFilesystemPosixTime();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EventType::AutoCorrect: {
|
||||||
|
m_standard_user_auto_correct_clock_event->Clear(m_system.Kernel());
|
||||||
|
|
||||||
|
bool automatic_correction{};
|
||||||
|
R_ASSERT(m_time_sm->IsStandardUserSystemClockAutomaticCorrectionEnabled(&automatic_correction));
|
||||||
|
|
||||||
|
Service::PSC::Time::SteadyClockTimePoint time_point{};
|
||||||
|
R_ASSERT(m_time_sm->GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(&time_point));
|
||||||
|
|
||||||
|
m_set_sys->SetUserSystemClockAutomaticCorrectionEnabled(automatic_correction);
|
||||||
|
m_set_sys->SetUserSystemClockAutomaticCorrectionUpdatedTime(time_point);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
m_process_cv.notify_one();
|
||||||
case EventType::UpdateSteadyClock:
|
});
|
||||||
m_timer_steady_clock->Clear(m_system.Kernel());
|
|
||||||
|
|
||||||
m_steady_clock_resource.UpdateTime();
|
|
||||||
m_time_m->SetStandardSteadyClockBaseTime(m_steady_clock_resource.GetTime());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EventType::UpdateFileTimestamp:
|
|
||||||
m_timer_file_system->Clear(m_system.Kernel());
|
|
||||||
|
|
||||||
m_file_timestamp_worker.SetFilesystemPosixTime();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EventType::AutoCorrect: {
|
|
||||||
m_standard_user_auto_correct_clock_event->Clear(m_system.Kernel());
|
|
||||||
|
|
||||||
bool automatic_correction{};
|
|
||||||
R_ASSERT(m_time_sm->IsStandardUserSystemClockAutomaticCorrectionEnabled(
|
|
||||||
&automatic_correction));
|
|
||||||
|
|
||||||
Service::PSC::Time::SteadyClockTimePoint time_point{};
|
|
||||||
R_ASSERT(
|
|
||||||
m_time_sm->GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(&time_point));
|
|
||||||
|
|
||||||
m_set_sys->SetUserSystemClockAutomaticCorrectionEnabled(automatic_correction);
|
|
||||||
m_set_sys->SetUserSystemClockAutomaticCorrectionUpdatedTime(time_point);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
UNREACHABLE();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Service::Glue::Time
|
} // namespace Service::Glue::Time
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <condition_variable>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "core/hle/kernel/k_event.h"
|
#include "core/hle/kernel/k_event.h"
|
||||||
#include "core/hle/service/glue/time/alarm_worker.h"
|
#include "core/hle/service/glue/time/alarm_worker.h"
|
||||||
@@ -33,17 +37,17 @@ public:
|
|||||||
|
|
||||||
void StartThread();
|
void StartThread();
|
||||||
|
|
||||||
private:
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T GetSettingsItemValue(const std::string& category, const std::string& name);
|
T GetSettingsItemValue(const std::string& category, const std::string& name);
|
||||||
|
|
||||||
void ThreadFunc(std::stop_token stop_token);
|
|
||||||
|
|
||||||
Core::System& m_system;
|
Core::System& m_system;
|
||||||
KernelHelpers::ServiceContext m_ctx;
|
KernelHelpers::ServiceContext m_ctx;
|
||||||
std::shared_ptr<Service::Set::ISystemSettingsServer> m_set_sys;
|
std::shared_ptr<Service::Set::ISystemSettingsServer> m_set_sys;
|
||||||
|
|
||||||
std::jthread m_thread;
|
std::mutex m_process_mutex;
|
||||||
|
std::condition_variable m_process_cv;
|
||||||
|
std::stop_source m_stop_source;
|
||||||
|
|
||||||
Kernel::KEvent* m_event{};
|
Kernel::KEvent* m_event{};
|
||||||
std::shared_ptr<Service::PSC::Time::ServiceManager> m_time_m;
|
std::shared_ptr<Service::PSC::Time::ServiceManager> m_time_m;
|
||||||
std::shared_ptr<Service::PSC::Time::StaticService> m_time_sm;
|
std::shared_ptr<Service::PSC::Time::StaticService> m_time_sm;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||||
@@ -24,7 +24,7 @@ IReceiverService::~IReceiverService() = default;
|
|||||||
|
|
||||||
Result IReceiverService::OpenReceiver(Out<SharedPointer<IReceiver>> out_receiver) {
|
Result IReceiverService::OpenReceiver(Out<SharedPointer<IReceiver>> out_receiver) {
|
||||||
LOG_DEBUG(Service_PSC, "called");
|
LOG_DEBUG(Service_PSC, "called");
|
||||||
*out_receiver = std::make_shared<IReceiver>(system);
|
*out_receiver = std::shared_ptr<IReceiver>(new IReceiver(system));
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,8 +98,10 @@ ServerManager::~ServerManager() {
|
|||||||
// Signal stop.
|
// Signal stop.
|
||||||
m_stop_source.request_stop();
|
m_stop_source.request_stop();
|
||||||
m_wakeup_event->Signal(m_system.Kernel());
|
m_wakeup_event->Signal(m_system.Kernel());
|
||||||
|
if (m_deferral_event) m_deferral_event->Signal(m_system.Kernel()); //for sm: and ports
|
||||||
|
|
||||||
// Wait for processing to stop.
|
|
||||||
|
// Wait for processing to stop. Schedule
|
||||||
m_stopped.Wait();
|
m_stopped.Wait();
|
||||||
m_threads.clear();
|
m_threads.clear();
|
||||||
|
|
||||||
@@ -253,6 +255,14 @@ void ServerManager::StartAdditionalHostThreads(const char* name, size_t num_thre
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief Notifies that the system is shutting down (pre-emptively terminate threads)
|
||||||
|
void ServerManager::NotifyShutdown() {
|
||||||
|
m_stop_source.request_stop();
|
||||||
|
// Wake them up regardless
|
||||||
|
m_wakeup_event->Signal(m_system.Kernel());
|
||||||
|
if (m_deferral_event) m_deferral_event->Signal(m_system.Kernel()); //for sm: and ports
|
||||||
|
}
|
||||||
|
|
||||||
Result ServerManager::LoopProcess() {
|
Result ServerManager::LoopProcess() {
|
||||||
SCOPE_EXIT {
|
SCOPE_EXIT {
|
||||||
m_stopped.Set();
|
m_stopped.Set();
|
||||||
@@ -285,9 +295,8 @@ MultiWaitHolder* ServerManager::WaitSignaled() {
|
|||||||
this->LinkDeferred();
|
this->LinkDeferred();
|
||||||
|
|
||||||
// If we're done, return before we start waiting.
|
// If we're done, return before we start waiting.
|
||||||
if (m_stop_source.stop_requested()) {
|
if (m_stop_source.stop_requested())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
|
||||||
|
|
||||||
auto* selected = m_multi_wait.WaitAny(m_system.Kernel());
|
auto* selected = m_multi_wait.WaitAny(m_system.Kernel());
|
||||||
if (selected == std::addressof(*m_wakeup_holder)) {
|
if (selected == std::addressof(*m_wakeup_holder)) {
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -49,6 +52,7 @@ public:
|
|||||||
|
|
||||||
Result LoopProcess();
|
Result LoopProcess();
|
||||||
void StartAdditionalHostThreads(const char* name, size_t num_threads);
|
void StartAdditionalHostThreads(const char* name, size_t num_threads);
|
||||||
|
void NotifyShutdown();
|
||||||
|
|
||||||
static void RunServer(std::unique_ptr<ServerManager>&& server);
|
static void RunServer(std::unique_ptr<ServerManager>&& server);
|
||||||
|
|
||||||
|
|||||||
@@ -117,7 +117,6 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
|
|||||||
{"friends", &Friend::LoopProcess},
|
{"friends", &Friend::LoopProcess},
|
||||||
{"settings", &Set::LoopProcess},
|
{"settings", &Set::LoopProcess},
|
||||||
{"psc", &PSC::LoopProcess},
|
{"psc", &PSC::LoopProcess},
|
||||||
{"glue", &Glue::LoopProcess},
|
|
||||||
{"grc", &GRC::LoopProcess},
|
{"grc", &GRC::LoopProcess},
|
||||||
{"hid", &HID::LoopProcess},
|
{"hid", &HID::LoopProcess},
|
||||||
{"jit", &JIT::LoopProcess},
|
{"jit", &JIT::LoopProcess},
|
||||||
@@ -153,6 +152,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
|
|||||||
{"usb", &USB::LoopProcess},
|
{"usb", &USB::LoopProcess},
|
||||||
{"i2c", &I2C::LoopProcess},
|
{"i2c", &I2C::LoopProcess},
|
||||||
{"gpio", &GPIO::LoopProcess},
|
{"gpio", &GPIO::LoopProcess},
|
||||||
|
{"glue", &Glue::LoopProcess},
|
||||||
})
|
})
|
||||||
kernel.RunOnGuestCoreProcess(std::string(e.first), [&system, f = e.second] { f(system); });
|
kernel.RunOnGuestCoreProcess(std::string(e.first), [&system, f = e.second] { f(system); });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -305,16 +305,14 @@ SM::SM(ServiceManager& service_manager_, Core::System& system_)
|
|||||||
SM::~SM() = default;
|
SM::~SM() = default;
|
||||||
|
|
||||||
void LoopProcess(Core::System& system) {
|
void LoopProcess(Core::System& system) {
|
||||||
auto& service_manager = system.ServiceManager();
|
|
||||||
auto server_manager = std::make_unique<ServerManager>(system);
|
auto server_manager = std::make_unique<ServerManager>(system);
|
||||||
|
|
||||||
Kernel::KEvent* deferral_event{};
|
Kernel::KEvent* deferral_event{};
|
||||||
server_manager->ManageDeferral(&deferral_event);
|
server_manager->ManageDeferral(&deferral_event);
|
||||||
service_manager.SetDeferralEvent(deferral_event);
|
system.ServiceManager().SetDeferralEvent(deferral_event);
|
||||||
|
|
||||||
auto sm_service = std::make_shared<SM>(system.ServiceManager(), system);
|
auto sm_service = std::make_shared<SM>(system.ServiceManager(), system);
|
||||||
server_manager->ManageNamedPort("sm:", [sm_service] { return sm_service; });
|
server_manager->ManageNamedPort("sm:", [sm_service] {
|
||||||
|
return sm_service;
|
||||||
|
});
|
||||||
ServerManager::RunServer(std::move(server_manager));
|
ServerManager::RunServer(std::move(server_manager));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
|
#include "core/hle/kernel/kernel.h"
|
||||||
#include "core/hle/service/vi/conductor.h"
|
#include "core/hle/service/vi/conductor.h"
|
||||||
#include "core/hle/service/vi/container.h"
|
#include "core/hle/service/vi/container.h"
|
||||||
#include "core/hle/service/vi/display_list.h"
|
#include "core/hle/service/vi/display_list.h"
|
||||||
@@ -29,24 +30,29 @@ Conductor::Conductor(Core::System& system, Container& container, DisplayList& di
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (system.IsMulticore()) {
|
if (system.IsMulticore()) {
|
||||||
m_event = Core::Timing::CreateEvent(
|
m_event = Core::Timing::CreateEvent("ScreenComposition", [this](s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
|
||||||
"ScreenComposition",
|
m_signal.Set();
|
||||||
[this](s64 time,
|
return std::chrono::nanoseconds(this->GetNextTicks());
|
||||||
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
|
});
|
||||||
m_signal.Set();
|
|
||||||
return std::chrono::nanoseconds(this->GetNextTicks());
|
|
||||||
});
|
|
||||||
|
|
||||||
system.CoreTiming().ScheduleLoopingEvent(FrameNs, FrameNs, m_event);
|
system.CoreTiming().ScheduleLoopingEvent(FrameNs, FrameNs, m_event);
|
||||||
m_thread = std::jthread([this](std::stop_token token) { this->VsyncThread(token); });
|
m_thread = system.Kernel().RunOnHostCoreThread("VSyncThread", [this]() {
|
||||||
} else {
|
auto const stop_token = m_thread.get_stop_token();
|
||||||
m_event = Core::Timing::CreateEvent(
|
Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
|
||||||
"ScreenComposition",
|
Common::SetCurrentThreadToPerformanceCores();
|
||||||
[this](s64 time,
|
while (!stop_token.stop_requested()) {
|
||||||
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
|
m_signal.Wait();
|
||||||
|
if (stop_token.stop_requested() || m_system.IsShuttingDown()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
this->ProcessVsync();
|
this->ProcessVsync();
|
||||||
return std::chrono::nanoseconds(this->GetNextTicks());
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
m_event = Core::Timing::CreateEvent("ScreenComposition", [this](s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
|
||||||
|
this->ProcessVsync();
|
||||||
|
return std::chrono::nanoseconds(this->GetNextTicks());
|
||||||
|
});
|
||||||
|
|
||||||
system.CoreTiming().ScheduleLoopingEvent(FrameNs, FrameNs, m_event);
|
system.CoreTiming().ScheduleLoopingEvent(FrameNs, FrameNs, m_event);
|
||||||
}
|
}
|
||||||
@@ -54,10 +60,10 @@ Conductor::Conductor(Core::System& system, Container& container, DisplayList& di
|
|||||||
|
|
||||||
Conductor::~Conductor() {
|
Conductor::~Conductor() {
|
||||||
m_system.CoreTiming().UnscheduleEvent(m_event);
|
m_system.CoreTiming().UnscheduleEvent(m_event);
|
||||||
|
if (m_thread.joinable()) {
|
||||||
if (m_system.IsMulticore()) {
|
|
||||||
m_thread.request_stop();
|
m_thread.request_stop();
|
||||||
m_signal.Set();
|
m_signal.Set();
|
||||||
|
m_thread.join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,22 +89,6 @@ void Conductor::ProcessVsync() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Conductor::VsyncThread(std::stop_token token) {
|
|
||||||
Common::SetCurrentThreadName("VSyncThread");
|
|
||||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
|
|
||||||
Common::SetCurrentThreadToPerformanceCores();
|
|
||||||
|
|
||||||
while (!token.stop_requested()) {
|
|
||||||
m_signal.Wait();
|
|
||||||
|
|
||||||
if (m_system.IsShuttingDown()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->ProcessVsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s64 Conductor::GetNextTicks() const {
|
s64 Conductor::GetNextTicks() const {
|
||||||
const auto& settings = Settings::values;
|
const auto& settings = Settings::values;
|
||||||
auto speed_scale = 1.f;
|
auto speed_scale = 1.f;
|
||||||
|
|||||||
@@ -1,205 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#include <regex>
|
|
||||||
#include <cctype>
|
|
||||||
|
|
||||||
#include "common/assert.h"
|
|
||||||
#include "common/logging.h"
|
|
||||||
#include "common/settings.h"
|
|
||||||
#include "common/string_util.h"
|
|
||||||
#include "common/scm_rev.h"
|
|
||||||
#include "core/core.h"
|
|
||||||
#include "core/hle/service/acc/profile_manager.h"
|
|
||||||
#include "core/launch_params.h"
|
|
||||||
|
|
||||||
#undef _UNICODE
|
|
||||||
#include <getopt.h>
|
|
||||||
#ifndef _MSC_VER
|
|
||||||
#include <unistd.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace Core {
|
|
||||||
LaunchParams ParseLaunchParams(Core::System& system, int argc, char *argv[], wchar_t *argv_w[]) noexcept {
|
|
||||||
LaunchParams p{};
|
|
||||||
|
|
||||||
int option_index = 0;
|
|
||||||
static struct option long_options[] = {
|
|
||||||
// clang-format off
|
|
||||||
{"debug", no_argument, 0, 'd'},
|
|
||||||
{"config", required_argument, 0, 'c'},
|
|
||||||
{"fullscreen", no_argument, 0, 'f'},
|
|
||||||
{"help", no_argument, 0, 'h'},
|
|
||||||
{"game", required_argument, 0, 'g'},
|
|
||||||
{"multiplayer", required_argument, 0, 'm'},
|
|
||||||
{"program", optional_argument, 0, 'p'},
|
|
||||||
{"user", required_argument, 0, 'u'},
|
|
||||||
{"version", no_argument, 0, 'v'},
|
|
||||||
{"input-profile", no_argument, 0, 'i'},
|
|
||||||
{"null-render", no_argument, 0, 'n'},
|
|
||||||
{"singlecore", no_argument, 0, 's'},
|
|
||||||
{"filter", no_argument, 0, 'x'},
|
|
||||||
{0, 0, 0, 0},
|
|
||||||
// clang-format on
|
|
||||||
};
|
|
||||||
|
|
||||||
while (optind < argc) {
|
|
||||||
if (int arg = getopt_long(argc, argv, "dc:fhg:m:p:u:vinsx", long_options, &option_index); arg != -1) {
|
|
||||||
switch (char(arg)) {
|
|
||||||
case 'd':
|
|
||||||
p.override_gdb_port = uint16_t(atoi(optarg));
|
|
||||||
break;
|
|
||||||
case 'c':
|
|
||||||
p.config_path = optarg;
|
|
||||||
break;
|
|
||||||
case 'f':
|
|
||||||
p.fullscreen = true;
|
|
||||||
LOG_INFO(Frontend, "Starting in fullscreen mode...");
|
|
||||||
break;
|
|
||||||
case 'h':
|
|
||||||
p.print_help = true;
|
|
||||||
break;
|
|
||||||
case 'g':
|
|
||||||
p.filepath = std::string(optarg);
|
|
||||||
break;
|
|
||||||
case 'i': {
|
|
||||||
p.input_profile = std::string(optarg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'm': {
|
|
||||||
p.use_multiplayer = true;
|
|
||||||
const std::string str_arg(optarg);
|
|
||||||
// regex to check if the format is nickname:password@ip:port
|
|
||||||
// with optional :password
|
|
||||||
const std::regex re("^([^:]+)(?::(.+))?@([^:]+)(?::([0-9]+))?$");
|
|
||||||
if (std::regex_match(str_arg, re)) {
|
|
||||||
std::smatch match;
|
|
||||||
std::regex_search(str_arg, match, re);
|
|
||||||
ASSERT(match.size() == 5);
|
|
||||||
p.nickname = match[1];
|
|
||||||
p.password = match[2];
|
|
||||||
p.address = match[3];
|
|
||||||
if (!match[4].str().empty()) {
|
|
||||||
p.port = u16(std::strtoul(match[4].str().c_str(), nullptr, 0));
|
|
||||||
}
|
|
||||||
std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$");
|
|
||||||
ASSERT(std::regex_match(p.nickname, nickname_re) && "Nickname is not valid. Must be 4 to 20 alphanumeric characters");
|
|
||||||
ASSERT(!p.address.empty() && "Address is empty");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'p':
|
|
||||||
p.program_args = argv[optind];
|
|
||||||
++optind;
|
|
||||||
break;
|
|
||||||
case 'u': {
|
|
||||||
// Launch game with a specific user
|
|
||||||
bool argument_ok = isdigit(optarg[0]);
|
|
||||||
p.selected_user = atoi(optarg);
|
|
||||||
if (!argument_ok) {
|
|
||||||
// try to look it up by username, only finds the first username that matches.
|
|
||||||
auto const user_idx = system.GetProfileManager().GetUserIndex(optarg);
|
|
||||||
if (user_idx != std::nullopt) {
|
|
||||||
p.selected_user = user_idx.value();
|
|
||||||
} else {
|
|
||||||
LOG_ERROR(Frontend, "Invalid user argument '{}'", optarg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (system.GetProfileManager().UserExistsIndex(*p.selected_user)) {
|
|
||||||
Settings::values.current_user = s32(*p.selected_user);
|
|
||||||
} else {
|
|
||||||
LOG_ERROR(Frontend, "Selected user {} doesn't exist", *p.selected_user);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'v':
|
|
||||||
p.print_version = true;
|
|
||||||
break;
|
|
||||||
case 'n':
|
|
||||||
p.force_null_render = true;
|
|
||||||
break;
|
|
||||||
case 's':
|
|
||||||
p.force_single_core = true;
|
|
||||||
break;
|
|
||||||
case 'x':
|
|
||||||
p.log_filter = argv[optind];
|
|
||||||
++optind;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// only kept due to shortcuts made by Qt frontend which use "-qlaunch"
|
|
||||||
if (strcmp(argv[optind], "-hlaunch") == 0) {
|
|
||||||
p.launch_hlaunch = true;
|
|
||||||
} else if (strcmp(argv[optind], "-qlaunch") == 0) {
|
|
||||||
p.launch_qlaunch = true;
|
|
||||||
} else if (strcmp(argv[optind], "-setup") == 0) {
|
|
||||||
p.launch_setup = true;
|
|
||||||
} else {
|
|
||||||
// qt feeds utf8 data, sdl frontend feeds raw utf16 data
|
|
||||||
#ifdef _WIN32
|
|
||||||
p.filepath = argv_w != nullptr
|
|
||||||
? Common::UTF16ToUTF8(argv_w[optind])
|
|
||||||
: argv[optind];
|
|
||||||
#else
|
|
||||||
p.filepath = argv[optind];
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
optind++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.argv0 = argv[0];
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApplyLaunchParams(LaunchParams const& lp) noexcept {
|
|
||||||
// apply the log_filter setting
|
|
||||||
// the logger was initialized before and doesn't pick up the filter on its own
|
|
||||||
Common::Log::Filter filter{};
|
|
||||||
filter.ParseFilterString(lp.log_filter.value_or(Settings::values.log_filter.GetValue()));
|
|
||||||
Common::Log::SetGlobalFilter(filter);
|
|
||||||
|
|
||||||
if (!lp.program_args.empty()) {
|
|
||||||
Settings::values.program_args = lp.program_args;
|
|
||||||
}
|
|
||||||
if (!lp.input_profile.empty()) {
|
|
||||||
auto& players = Settings::values.players.GetValue();
|
|
||||||
players[0].profile_name = lp.input_profile;
|
|
||||||
}
|
|
||||||
if (lp.selected_user.has_value()) {
|
|
||||||
Settings::values.current_user = std::clamp(*lp.selected_user, 0, 7);
|
|
||||||
}
|
|
||||||
if (lp.override_gdb_port.has_value()) {
|
|
||||||
Settings::values.use_gdbstub = true;
|
|
||||||
Settings::values.gdbstub_port = *lp.override_gdb_port;
|
|
||||||
}
|
|
||||||
if (lp.force_single_core) {
|
|
||||||
Settings::values.use_multi_core = false;
|
|
||||||
}
|
|
||||||
if (lp.force_null_render) {
|
|
||||||
Settings::values.renderer_backend = Settings::RendererBackend::Null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lp.print_version) {
|
|
||||||
LOG_INFO(Frontend, "Eden {} {}", Common::g_scm_branch, Common::g_scm_desc);
|
|
||||||
}
|
|
||||||
if (lp.print_help) {
|
|
||||||
LOG_INFO(Frontend,
|
|
||||||
"Usage: {}"
|
|
||||||
" [options] <filename>\n"
|
|
||||||
"-c, --config Load the specified configuration file\n"
|
|
||||||
"-f, --fullscreen Start in fullscreen mode\n"
|
|
||||||
"-g, --game File path of the game to load\n"
|
|
||||||
"-h, --help Display this help and exit\n"
|
|
||||||
"-m, --multiplayer=nick:password@address:port"
|
|
||||||
" Nickname, password, address and port for multiplayer\n"
|
|
||||||
"-p, --program Pass following string as arguments to executable\n"
|
|
||||||
"-u, --user Select a specific user profile from 0 to 7\n"
|
|
||||||
"-d, --debug Run the GDB stub on a port from 1 to 65535\n"
|
|
||||||
"-v, --version Output version information and exit\n",
|
|
||||||
lp.argv0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <optional>
|
|
||||||
#include "common/common_types.h"
|
|
||||||
#include "network/room.h"
|
|
||||||
|
|
||||||
namespace Core {
|
|
||||||
|
|
||||||
class System;
|
|
||||||
struct LaunchParams {
|
|
||||||
std::string argv0{};
|
|
||||||
std::string filepath{};
|
|
||||||
std::string nickname{};
|
|
||||||
std::string password{};
|
|
||||||
std::string address{};
|
|
||||||
std::string input_profile{};
|
|
||||||
std::string program_args{};
|
|
||||||
std::optional<std::string> config_path{};
|
|
||||||
std::optional<int> selected_user{};
|
|
||||||
std::optional<u16> override_gdb_port{};
|
|
||||||
std::optional<std::string> log_filter{};
|
|
||||||
u16 port = Network::DefaultRoomPort;
|
|
||||||
bool use_multiplayer = false;
|
|
||||||
bool fullscreen = false;
|
|
||||||
bool force_null_render = false;
|
|
||||||
bool force_single_core = false;
|
|
||||||
// print
|
|
||||||
bool print_version = false;
|
|
||||||
bool print_help = false;
|
|
||||||
// qt
|
|
||||||
bool launch_hlaunch = false;
|
|
||||||
bool launch_qlaunch = false;
|
|
||||||
bool launch_setup = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
LaunchParams ParseLaunchParams(Core::System& system, int argc, char *argv[], wchar_t *argv_w[]) noexcept;
|
|
||||||
void ApplyLaunchParams(LaunchParams const& lp) noexcept;
|
|
||||||
|
|
||||||
}
|
|
||||||
+4
-31
@@ -7,14 +7,12 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <span>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "common/common_funcs.h"
|
#include "common/common_funcs.h"
|
||||||
#include "common/hex_util.h"
|
#include "common/hex_util.h"
|
||||||
#include "common/logging.h"
|
#include "common/logging.h"
|
||||||
#include "common/lz4_compression.h"
|
#include "common/lz4_compression.h"
|
||||||
#include "common/zbic_compression.h"
|
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "common/swap.h"
|
#include "common/swap.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
@@ -106,36 +104,11 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
|
|||||||
for (std::size_t i = 0; i < nso_header.segments.size(); ++i) {
|
for (std::size_t i = 0; i < nso_header.segments.size(); ++i) {
|
||||||
nso_file.Read(compressed_data.data(), nso_header.segments_compressed_size[i], nso_header.segments[i].offset);
|
nso_file.Read(compressed_data.data(), nso_header.segments_compressed_size[i], nso_header.segments[i].offset);
|
||||||
if (nso_header.IsSegmentCompressed(i)) {
|
if (nso_header.IsSegmentCompressed(i)) {
|
||||||
if (nso_header.IsZBICCompressed()) {
|
int r = Common::Compression::DecompressDataLZ4(decompressed_size.data(), nso_header.segments[i].size, compressed_data.data(), nso_header.segments_compressed_size[i]);
|
||||||
// ZBIC compression
|
ASSERT(r == int(nso_header.segments[i].size));
|
||||||
const int r = Common::Compression::DecompressDataZBIC(
|
std::memcpy(codeset.memory.data() + module_start + nso_header.segments[i].location, decompressed_size.data(), nso_header.segments[i].size);
|
||||||
std::span<u8>{decompressed_size}.first(nso_header.segments[i].size),
|
|
||||||
std::span<const u8>{compressed_data}.first(nso_header.segments_compressed_size[i])
|
|
||||||
);
|
|
||||||
ASSERT(r > 0);
|
|
||||||
} else {
|
|
||||||
// LZ4 compression
|
|
||||||
int r = Common::Compression::DecompressDataLZ4(
|
|
||||||
decompressed_size.data(),
|
|
||||||
nso_header.segments[i].size,
|
|
||||||
compressed_data.data(),
|
|
||||||
nso_header.segments_compressed_size[i]
|
|
||||||
);
|
|
||||||
ASSERT(r == int(nso_header.segments[i].size));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::memcpy(
|
|
||||||
codeset.memory.data() + module_start + nso_header.segments[i].location,
|
|
||||||
decompressed_size.data(),
|
|
||||||
nso_header.segments[i].size
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// Not compressed
|
std::memcpy(codeset.memory.data() + module_start + nso_header.segments[i].location, compressed_data.data(), nso_header.segments[i].size);
|
||||||
std::memcpy(
|
|
||||||
codeset.memory.data() + module_start + nso_header.segments[i].location,
|
|
||||||
compressed_data.data(),
|
|
||||||
nso_header.segments[i].size
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
codeset.segments[i].addr = module_start + nso_header.segments[i].location;
|
codeset.segments[i].addr = module_start + nso_header.segments[i].location;
|
||||||
codeset.segments[i].offset = module_start + nso_header.segments[i].location;
|
codeset.segments[i].offset = module_start + nso_header.segments[i].location;
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -61,9 +58,6 @@ struct NSOHeader {
|
|||||||
std::array<SHA256Hash, 3> segment_hashes;
|
std::array<SHA256Hash, 3> segment_hashes;
|
||||||
|
|
||||||
bool IsSegmentCompressed(size_t segment_num) const;
|
bool IsSegmentCompressed(size_t segment_num) const;
|
||||||
bool IsZBICCompressed() const {
|
|
||||||
return ((flags >> 7) & 1) != 0;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
static_assert(sizeof(NSOHeader) == 0x100, "NSOHeader has incorrect size.");
|
static_assert(sizeof(NSOHeader) == 0x100, "NSOHeader has incorrect size.");
|
||||||
static_assert(std::is_trivially_copyable_v<NSOHeader>, "NSOHeader must be trivially copyable.");
|
static_assert(std::is_trivially_copyable_v<NSOHeader>, "NSOHeader must be trivially copyable.");
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// gcc14 bug
|
||||||
|
#ifdef __GNUC__
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/scope_exit.h"
|
#include "common/scope_exit.h"
|
||||||
#include "core/memory/dmnt_cheat_types.h"
|
#include "core/memory/dmnt_cheat_types.h"
|
||||||
@@ -1266,3 +1275,8 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Core::Memory
|
} // namespace Core::Memory
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||||
@@ -254,14 +254,16 @@ struct UnrecognizedInstruction {
|
|||||||
|
|
||||||
struct CheatVmOpcode {
|
struct CheatVmOpcode {
|
||||||
bool begin_conditional_block{};
|
bool begin_conditional_block{};
|
||||||
std::variant<StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode,
|
std::variant<
|
||||||
LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode,
|
std::monostate,
|
||||||
PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
|
StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode,
|
||||||
PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
|
LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode,
|
||||||
BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
|
PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
|
||||||
SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, PauseProcessOpcode,
|
PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
|
||||||
ResumeProcessOpcode, DebugLogOpcode, UnrecognizedInstruction>
|
BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
|
||||||
opcode{};
|
SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, PauseProcessOpcode,
|
||||||
|
ResumeProcessOpcode, DebugLogOpcode, UnrecognizedInstruction
|
||||||
|
> opcode{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class DmntCheatVm {
|
class DmntCheatVm {
|
||||||
|
|||||||
@@ -170,13 +170,19 @@ template<>
|
|||||||
|
|
||||||
// check for marked bit, use as unmapped if marked
|
// check for marked bit, use as unmapped if marked
|
||||||
if (ctx.conf.page_table_marked_bit) {
|
if (ctx.conf.page_table_marked_bit) {
|
||||||
code.bt(page, *ctx.conf.page_table_marked_bit);
|
auto const marked_bit = *ctx.conf.page_table_marked_bit;
|
||||||
code.jc(abort, code.T_NEAR);
|
if (s64(s32(1 << marked_bit)) == s64(1 << marked_bit)) {
|
||||||
|
code.test(page, s32(1 << marked_bit));
|
||||||
|
code.jnz(abort, code.T_NEAR);
|
||||||
|
} else {
|
||||||
|
code.bt(page, marked_bit);
|
||||||
|
code.jc(abort, code.T_NEAR);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// mask away attributes
|
// mask away attributes
|
||||||
if (ctx.conf.page_table_pointer_mask == 0) {
|
if (ctx.conf.page_table_pointer_mask == 0) {
|
||||||
code.test(page, page);
|
code.test(page, page);
|
||||||
} else if (std::in_range<s32>(ctx.conf.page_table_pointer_mask)) {
|
} else if (s64(s32(ctx.conf.page_table_pointer_mask)) == s64(ctx.conf.page_table_pointer_mask)) {
|
||||||
code.and_(page, ctx.conf.page_table_pointer_mask);
|
code.and_(page, ctx.conf.page_table_pointer_mask);
|
||||||
} else {
|
} else {
|
||||||
code.mov(tmp, ctx.conf.page_table_pointer_mask);
|
code.mov(tmp, ctx.conf.page_table_pointer_mask);
|
||||||
|
|||||||
@@ -79,8 +79,10 @@ void NpadAbstractButtonHandler::UpdateAllButtonLifo() {
|
|||||||
Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
|
Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
|
||||||
for (std::size_t i = 0; i < AruidIndexMax; i++) {
|
for (std::size_t i = 0; i < AruidIndexMax; i++) {
|
||||||
auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i);
|
auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i);
|
||||||
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
|
if (auto const shfmt = data->shared_memory_format; shfmt) {
|
||||||
UpdateButtonLifo(npad_entry, data->aruid);
|
auto& npad_entry = shfmt->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
|
||||||
|
UpdateButtonLifo(npad_entry, data->aruid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,19 +90,19 @@ void NpadAbstractButtonHandler::UpdateCoreBatteryState() {
|
|||||||
Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
|
Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
|
||||||
for (std::size_t i = 0; i < AruidIndexMax; i++) {
|
for (std::size_t i = 0; i < AruidIndexMax; i++) {
|
||||||
auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i);
|
auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i);
|
||||||
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
|
if (auto const shfmt = data->shared_memory_format; shfmt) {
|
||||||
UpdateButtonLifo(npad_entry, data->aruid);
|
auto& npad_entry = shfmt->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
|
||||||
|
UpdateButtonLifo(npad_entry, data->aruid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NpadAbstractButtonHandler::UpdateButtonState(u64 aruid) {
|
void NpadAbstractButtonHandler::UpdateButtonState(u64 aruid) {
|
||||||
Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
|
Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
|
||||||
auto* data = applet_resource_holder->applet_resource->GetAruidData(aruid);
|
if (auto data = applet_resource_holder->applet_resource->GetAruidData(aruid); data) {
|
||||||
if (data == nullptr) {
|
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
|
||||||
return;
|
UpdateButtonLifo(npad_entry, aruid);
|
||||||
}
|
}
|
||||||
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
|
|
||||||
UpdateButtonLifo(npad_entry, aruid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Result NpadAbstractButtonHandler::SetHomeProtection(bool is_enabled, u64 aruid) {
|
Result NpadAbstractButtonHandler::SetHomeProtection(bool is_enabled, u64 aruid) {
|
||||||
|
|||||||
@@ -45,19 +45,6 @@ NPad::NPad(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service
|
|||||||
AbstractPad{hid_core_.kernel},
|
AbstractPad{hid_core_.kernel},
|
||||||
}}
|
}}
|
||||||
{
|
{
|
||||||
for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; ++aruid_index) {
|
|
||||||
for (std::size_t i = 0; i < controller_data[aruid_index].size(); ++i) {
|
|
||||||
auto& controller = controller_data[aruid_index][i];
|
|
||||||
controller.device = hid_core.GetEmulatedControllerByIndex(i);
|
|
||||||
Core::HID::ControllerUpdateCallback engine_callback{
|
|
||||||
.on_change = [this, i, kernel = &hid_core.kernel](Core::HID::ControllerTriggerType type) {
|
|
||||||
ControllerUpdate(*kernel, type, i);
|
|
||||||
},
|
|
||||||
.is_npad_service = true,
|
|
||||||
};
|
|
||||||
controller.callback_key = controller.device->SetCallback(engine_callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (std::size_t i = 0; i < abstracted_pads.size(); ++i) {
|
for (std::size_t i = 0; i < abstracted_pads.size(); ++i) {
|
||||||
abstracted_pads[i].SetNpadId(IndexToNpadIdType(i));
|
abstracted_pads[i].SetNpadId(IndexToNpadIdType(i));
|
||||||
}
|
}
|
||||||
@@ -106,6 +93,16 @@ Result NPad::Activate(u64 aruid) {
|
|||||||
for (std::size_t i = 0; i < controller_data[aruid_index].size(); ++i) {
|
for (std::size_t i = 0; i < controller_data[aruid_index].size(); ++i) {
|
||||||
auto& controller = controller_data[aruid_index][i];
|
auto& controller = controller_data[aruid_index][i];
|
||||||
controller.shared_memory = &data->shared_memory_format->npad.npad_entry[i].internal_state;
|
controller.shared_memory = &data->shared_memory_format->npad.npad_entry[i].internal_state;
|
||||||
|
controller.device = hid_core.GetEmulatedControllerByIndex(i);
|
||||||
|
if (!controller.callback_key) {
|
||||||
|
Core::HID::ControllerUpdateCallback engine_callback{
|
||||||
|
.on_change = [this, i](Core::HID::ControllerTriggerType type) {
|
||||||
|
ControllerUpdate(hid_core.kernel, type, i);
|
||||||
|
},
|
||||||
|
.is_npad_service = true,
|
||||||
|
};
|
||||||
|
controller.callback_key = controller.device->SetCallback(engine_callback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefill controller buffers
|
// Prefill controller buffers
|
||||||
|
|||||||
@@ -66,14 +66,17 @@ void EmuThread::run() {
|
|||||||
} else {
|
} else {
|
||||||
QtCommon::system->Pause();
|
QtCommon::system->Pause();
|
||||||
m_stopped.Set();
|
m_stopped.Set();
|
||||||
|
|
||||||
EmulationPaused(lk);
|
EmulationPaused(lk);
|
||||||
m_should_run_cv.wait(lk, stop_token, [&] { return m_should_run; });
|
m_should_run_cv.wait(lk, stop_token, [&] {
|
||||||
|
return m_should_run;
|
||||||
|
});
|
||||||
EmulationResumed(lk);
|
EmulationResumed(lk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown the main emulated process
|
// Shutdown the main emulated process
|
||||||
|
// Needs to pause first, we may not have paused before shutting down.
|
||||||
|
QtCommon::system->Pause();
|
||||||
QtCommon::system->DetachDebugger();
|
QtCommon::system->DetachDebugger();
|
||||||
QtCommon::system->ShutdownMainProcess();
|
QtCommon::system->ShutdownMainProcess();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -31,6 +28,11 @@ struct FuncTraits<ReturnType_ (*)(Args...)> {
|
|||||||
using ArgType = std::tuple_element_t<I, std::tuple<Args...>>;
|
using ArgType = std::tuple_element_t<I, std::tuple<Args...>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <auto func, typename... Args>
|
||||||
|
void SetDefinition(EmitContext& ctx, IR::Inst* inst, Args... args) {
|
||||||
|
inst->SetDefinition<Id>(func(ctx, std::forward<Args>(args)...));
|
||||||
|
}
|
||||||
|
|
||||||
template <typename ArgType>
|
template <typename ArgType>
|
||||||
auto Arg(EmitContext& ctx, const IR::Value& arg) {
|
auto Arg(EmitContext& ctx, const IR::Value& arg) {
|
||||||
if constexpr (std::is_same_v<ArgType, std::string_view>) {
|
if constexpr (std::is_same_v<ArgType, std::string_view>) {
|
||||||
@@ -51,10 +53,21 @@ auto Arg(EmitContext& ctx, const IR::Value& arg) {
|
|||||||
template <auto func, bool is_first_arg_inst, size_t... I>
|
template <auto func, bool is_first_arg_inst, size_t... I>
|
||||||
void Invoke(EmitContext& ctx, IR::Inst* inst, std::index_sequence<I...>) {
|
void Invoke(EmitContext& ctx, IR::Inst* inst, std::index_sequence<I...>) {
|
||||||
using Traits = FuncTraits<decltype(func)>;
|
using Traits = FuncTraits<decltype(func)>;
|
||||||
if constexpr (is_first_arg_inst) {
|
if constexpr (std::is_same_v<typename Traits::ReturnType, Id>) {
|
||||||
func(ctx, *inst, Arg<typename Traits::template ArgType<I + 2>>(ctx, inst->Arg(I))...);
|
if constexpr (is_first_arg_inst) {
|
||||||
|
SetDefinition<func>(
|
||||||
|
ctx, inst, *inst,
|
||||||
|
Arg<typename Traits::template ArgType<I + 2>>(ctx, inst->Arg(I))...);
|
||||||
|
} else {
|
||||||
|
SetDefinition<func>(
|
||||||
|
ctx, inst, Arg<typename Traits::template ArgType<I + 1>>(ctx, inst->Arg(I))...);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
func(ctx, Arg<typename Traits::template ArgType<I + 1>>(ctx, inst->Arg(I))...);
|
if constexpr (is_first_arg_inst) {
|
||||||
|
func(ctx, *inst, Arg<typename Traits::template ArgType<I + 2>>(ctx, inst->Arg(I))...);
|
||||||
|
} else {
|
||||||
|
func(ctx, Arg<typename Traits::template ArgType<I + 1>>(ctx, inst->Arg(I))...);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -496,31 +496,30 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PatchPhiNodes(IR::Program& program, EmitContext& ctx) {
|
void PatchPhiNodes(IR::Program& program, EmitContext& ctx) {
|
||||||
// Flatten all leading PHIs from each block into a vector
|
// Flatten all leading PHIs from each block into a vector
|
||||||
std::vector<IR::Inst*> phi_instructions;
|
std::vector<IR::Inst*> phi_instructions;
|
||||||
for (IR::Block* block : program.blocks) {
|
for (IR::Block* block : program.blocks) {
|
||||||
for (auto it = block->begin(); it != block->end(); ++it) {
|
for (auto it = block->begin(); it != block->end(); ++it) {
|
||||||
if (it->GetOpcode() != IR::Opcode::Phi)
|
if (it->GetOpcode() != IR::Opcode::Phi)
|
||||||
break;
|
break;
|
||||||
phi_instructions.push_back(&*it);
|
phi_instructions.push_back(&*it);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (phi_instructions.empty()) {
|
|
||||||
return; // nothing to patch
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start "before" first PHI; advance on phi_arg == 0
|
|
||||||
size_t phi_index = static_cast<size_t>(-1);
|
|
||||||
|
|
||||||
ctx.PatchDeferredPhi([&](size_t phi_arg, Id parent) -> std::pair<Id, Id> {
|
|
||||||
if (phi_arg == 0) {
|
|
||||||
++phi_index;
|
|
||||||
}
|
|
||||||
IR::Inst* phi = phi_instructions[phi_index];
|
|
||||||
return { ctx.Def(phi->Arg(phi_arg)), parent };
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (phi_instructions.empty()) {
|
||||||
|
return; // nothing to patch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start "before" first PHI; advance on phi_arg == 0
|
||||||
|
size_t phi_index = size_t(-1);
|
||||||
|
ctx.PatchDeferredPhi([&ctx, &phi_index, phi_insts = std::move(phi_instructions)](size_t phi_arg, Id parent) -> std::pair<Id, Id> {
|
||||||
|
if (phi_arg == 0) {
|
||||||
|
++phi_index;
|
||||||
|
}
|
||||||
|
IR::Inst* phi = phi_insts[phi_index];
|
||||||
|
return { ctx.Def(phi->Arg(phi_arg)), parent };
|
||||||
|
});
|
||||||
|
}
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
||||||
std::vector<u32> EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_info, IR::Program& program, Bindings& bindings) {
|
std::vector<u32> EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_info, IR::Program& program, Bindings& bindings) {
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ void BufferCache<P>::UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t size
|
|||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void BufferCache<P>::WriteMemory(DAddr device_addr, u64 size) {
|
void BufferCache<P>::WriteMemory(DAddr device_addr, u64 size) {
|
||||||
if (IsRegionGpuModified(device_addr, size)) {
|
if (memory_tracker.IsRegionGpuModified(device_addr, size)) {
|
||||||
ClearDownload(device_addr, size);
|
ClearDownload(device_addr, size);
|
||||||
gpu_modified_ranges.Subtract(device_addr, size);
|
gpu_modified_ranges.Subtract(device_addr, size);
|
||||||
}
|
}
|
||||||
@@ -311,8 +311,11 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainCPUBuffer(
|
|||||||
MarkWrittenBuffer(buffer_id, device_addr, size);
|
MarkWrittenBuffer(buffer_id, device_addr, size);
|
||||||
break;
|
break;
|
||||||
case ObtainBufferOperation::DiscardWrite: {
|
case ObtainBufferOperation::DiscardWrite: {
|
||||||
ClearDownload(device_addr, size);
|
const DAddr device_addr_start = Common::AlignDown(device_addr, 64);
|
||||||
gpu_modified_ranges.Subtract(device_addr, size);
|
const DAddr device_addr_end = Common::AlignUp(device_addr + size, 64);
|
||||||
|
const size_t new_size = device_addr_end - device_addr_start;
|
||||||
|
ClearDownload(device_addr_start, new_size);
|
||||||
|
gpu_modified_ranges.Subtract(device_addr_start, new_size);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -1739,24 +1742,14 @@ bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, DAddr device_addr, u32 si
|
|||||||
u64 total_size_bytes = 0;
|
u64 total_size_bytes = 0;
|
||||||
u64 largest_copy = 0;
|
u64 largest_copy = 0;
|
||||||
const DAddr buffer_start = buffer.cpu_addr_cached;
|
const DAddr buffer_start = buffer.cpu_addr_cached;
|
||||||
const auto add_upload = [&](DAddr start, DAddr end) {
|
memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) {
|
||||||
if (start == end) return;
|
|
||||||
const u64 range_size = end - start;
|
|
||||||
upload_copies.push_back(BufferCopy{
|
upload_copies.push_back(BufferCopy{
|
||||||
.src_offset = total_size_bytes,
|
.src_offset = total_size_bytes,
|
||||||
.dst_offset = start - buffer_start,
|
.dst_offset = device_addr_out - buffer_start,
|
||||||
.size = range_size,
|
.size = range_size,
|
||||||
});
|
});
|
||||||
total_size_bytes += range_size;
|
total_size_bytes += range_size;
|
||||||
largest_copy = (std::max)(largest_copy, range_size);
|
largest_copy = (std::max)(largest_copy, range_size);
|
||||||
};
|
|
||||||
memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) {
|
|
||||||
DAddr upload_start = device_addr_out;
|
|
||||||
gpu_modified_ranges.ForEachInRange(device_addr_out, range_size, [&](DAddr gpu_start, DAddr gpu_end) {
|
|
||||||
add_upload(upload_start, gpu_start);
|
|
||||||
upload_start = gpu_end;
|
|
||||||
});
|
|
||||||
add_upload(upload_start, device_addr_out + range_size);
|
|
||||||
});
|
});
|
||||||
if (total_size_bytes == 0) {
|
if (total_size_bytes == 0) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
namespace Tegra {
|
namespace Tegra {
|
||||||
|
|
||||||
constexpr u32 MacroRegistersStart = 0xE00;
|
constexpr u32 MacroRegistersStart = 0xE00;
|
||||||
constexpr u32 ComputeInline = 0x6D;
|
[[maybe_unused]] constexpr u32 ComputeInline = 0x6D;
|
||||||
|
|
||||||
DmaPusher::DmaPusher(Core::System& system_, MemoryManager& memory_manager_, Control::ChannelState& channel_state_)
|
DmaPusher::DmaPusher(Core::System& system_, MemoryManager& memory_manager_, Control::ChannelState& channel_state_)
|
||||||
: system{system_}
|
: system{system_}
|
||||||
@@ -73,16 +73,11 @@ bool DmaPusher::Step() {
|
|||||||
synced = false;
|
synced = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (header.size > 0 && dma_state.method >= MacroRegistersStart && subchannels[dma_state.subchannel]) {
|
||||||
|
subchannels[dma_state.subchannel]->current_dirty = memory_manager.IsMemoryDirty(dma_state.dma_get, header.size * sizeof(u32));
|
||||||
|
}
|
||||||
|
|
||||||
if (header.size > 0) {
|
if (header.size > 0) {
|
||||||
if (subchannels[dma_state.subchannel] && dma_state.method_count) {
|
|
||||||
const auto engine = subchannel_type[dma_state.subchannel];
|
|
||||||
const bool kepler_payload = engine == Engines::EngineTypes::KeplerCompute && dma_state.method == ComputeInline && dma_state.non_incrementing;
|
|
||||||
const bool macro_payload = engine == Engines::EngineTypes::Maxwell3D && dma_state.method >= MacroRegistersStart;
|
|
||||||
if (kepler_payload || macro_payload) {
|
|
||||||
const size_t words = std::min<size_t>(dma_state.method_count, header.size);
|
|
||||||
subchannels[dma_state.subchannel]->current_dirty = memory_manager.IsMemoryDirty(dma_state.dma_get, words * sizeof(u32));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const bool use_safe = Settings::IsDMALevelDefault() ? Settings::IsGPULevelHigh() : Settings::IsDMALevelSafe();
|
const bool use_safe = Settings::IsDMALevelDefault() ? Settings::IsGPULevelHigh() : Settings::IsDMALevelSafe();
|
||||||
if (use_safe) {
|
if (use_safe) {
|
||||||
Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader, Tegra::Memory::GuestMemoryFlags::SafeRead>headers(memory_manager, dma_state.dma_get, header.size, &command_headers);
|
Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader, Tegra::Memory::GuestMemoryFlags::SafeRead>headers(memory_manager, dma_state.dma_get, header.size, &command_headers);
|
||||||
|
|||||||
@@ -49,16 +49,13 @@ void KeplerCompute::CallMethod(Core::System& system, u32 method, u32 method_argu
|
|||||||
case KEPLER_COMPUTE_REG_INDEX(exec_upload): {
|
case KEPLER_COMPUTE_REG_INDEX(exec_upload): {
|
||||||
UploadInfo info{.upload_address = upload_address,
|
UploadInfo info{.upload_address = upload_address,
|
||||||
.exec_address = upload_state.ExecTargetAddress(),
|
.exec_address = upload_state.ExecTargetAddress(),
|
||||||
.copy_size = upload_state.GetUploadSize(),
|
.copy_size = upload_state.GetUploadSize()};
|
||||||
.was_dirty = upload_dirty};
|
|
||||||
uploads.push_back(info);
|
uploads.push_back(info);
|
||||||
upload_state.ProcessExec(regs.exec_upload.linear != 0);
|
upload_state.ProcessExec(regs.exec_upload.linear != 0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case KEPLER_COMPUTE_REG_INDEX(data_upload): {
|
case KEPLER_COMPUTE_REG_INDEX(data_upload): {
|
||||||
upload_address = current_dma_segment;
|
upload_address = current_dma_segment;
|
||||||
upload_dirty = current_dirty;
|
|
||||||
current_dirty = false;
|
|
||||||
upload_state.ProcessData(method_argument, is_last_call);
|
upload_state.ProcessData(method_argument, is_last_call);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -67,11 +64,9 @@ void KeplerCompute::CallMethod(Core::System& system, u32 method, u32 method_argu
|
|||||||
|
|
||||||
for (auto& data : uploads) {
|
for (auto& data : uploads) {
|
||||||
const GPUVAddr offset = data.exec_address - launch_desc_loc;
|
const GPUVAddr offset = data.exec_address - launch_desc_loc;
|
||||||
if (offset / sizeof(u32) == LAUNCH_REG_INDEX(grid_dim_x)) {
|
if (offset / sizeof(u32) == LAUNCH_REG_INDEX(grid_dim_x) &&
|
||||||
const bool source_dirty = memory_manager.IsMemoryDirty(data.upload_address, data.copy_size);
|
memory_manager.IsMemoryDirty(data.upload_address, data.copy_size)) {
|
||||||
if (data.was_dirty || source_dirty) {
|
indirect_compute = {data.upload_address};
|
||||||
indirect_compute = {data.upload_address};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
uploads.clear();
|
uploads.clear();
|
||||||
@@ -88,8 +83,6 @@ void KeplerCompute::CallMultiMethod(Core::System& system, u32 method, const u32*
|
|||||||
switch (method) {
|
switch (method) {
|
||||||
case KEPLER_COMPUTE_REG_INDEX(data_upload):
|
case KEPLER_COMPUTE_REG_INDEX(data_upload):
|
||||||
upload_address = current_dma_segment;
|
upload_address = current_dma_segment;
|
||||||
upload_dirty = current_dirty;
|
|
||||||
current_dirty = false;
|
|
||||||
upload_state.ProcessData(base_start, amount);
|
upload_state.ProcessData(base_start, amount);
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -226,13 +226,11 @@ private:
|
|||||||
VideoCore::RasterizerInterface* rasterizer = nullptr;
|
VideoCore::RasterizerInterface* rasterizer = nullptr;
|
||||||
Upload::State upload_state;
|
Upload::State upload_state;
|
||||||
GPUVAddr upload_address;
|
GPUVAddr upload_address;
|
||||||
bool upload_dirty{};
|
|
||||||
|
|
||||||
struct UploadInfo {
|
struct UploadInfo {
|
||||||
GPUVAddr upload_address;
|
GPUVAddr upload_address;
|
||||||
GPUVAddr exec_address;
|
GPUVAddr exec_address;
|
||||||
u32 copy_size;
|
u32 copy_size;
|
||||||
bool was_dirty;
|
|
||||||
};
|
};
|
||||||
std::vector<UploadInfo> uploads;
|
std::vector<UploadInfo> uploads;
|
||||||
std::optional<GPUVAddr> indirect_compute{};
|
std::optional<GPUVAddr> indirect_compute{};
|
||||||
|
|||||||
@@ -94,14 +94,11 @@ void Layer::ConfigureDraw(const Device& device, PresentPushConstants* out_push_c
|
|||||||
const u32 scaled_width = texture_info ? texture_info->scaled_width : texture_width;
|
const u32 scaled_width = texture_info ? texture_info->scaled_width : texture_width;
|
||||||
const u32 scaled_height = texture_info ? texture_info->scaled_height : texture_height;
|
const u32 scaled_height = texture_info ? texture_info->scaled_height : texture_height;
|
||||||
const bool use_accelerated = texture_info.has_value();
|
const bool use_accelerated = texture_info.has_value();
|
||||||
const bool is_applet =
|
|
||||||
(framebuffer.layer_stack_mask & Service::Nvnflinger::LayerStackBit(
|
|
||||||
Service::Nvnflinger::LayerStackId::Recording)) == 0;
|
|
||||||
|
|
||||||
RefreshResources(device, framebuffer);
|
RefreshResources(device, framebuffer);
|
||||||
SetAntiAliasPass(device);
|
SetAntiAliasPass(device);
|
||||||
#ifdef HAS_RESHADE
|
#ifdef HAS_RESHADE
|
||||||
SetPostProcessPass(device, is_applet);
|
SetPostProcessPass(device);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Finish any pending renderpass
|
// Finish any pending renderpass
|
||||||
@@ -141,11 +138,8 @@ void Layer::ConfigureDraw(const Device& device, PresentPushConstants* out_push_c
|
|||||||
source_image_view = fsr->Draw(device, scheduler, image_index, source_image, source_image_view, render_extent, crop_rect);
|
source_image_view = fsr->Draw(device, scheduler, image_index, source_image, source_image_view, render_extent, crop_rect);
|
||||||
crop_rect = {0, 0, 1, 1};
|
crop_rect = {0, 0, 1, 1};
|
||||||
} else if (auto* sgsr = std::get_if<SGSR>(&sr_filter)) {
|
} else if (auto* sgsr = std::get_if<SGSR>(&sr_filter)) {
|
||||||
if (!is_applet) {
|
source_image_view = sgsr->Draw(device, scheduler, image_index, source_image, source_image_view, render_extent, crop_rect);
|
||||||
source_image_view = sgsr->Draw(device, scheduler, image_index, source_image,
|
crop_rect = {0, 0, 1, 1};
|
||||||
source_image_view, render_extent, crop_rect);
|
|
||||||
crop_rect = {0, 0, 1, 1};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SetMatrixData(device, *out_push_constants, layout);
|
SetMatrixData(device, *out_push_constants, layout);
|
||||||
@@ -234,11 +228,7 @@ void Layer::SetAntiAliasPass(const Device& device) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAS_RESHADE
|
#ifdef HAS_RESHADE
|
||||||
void Layer::SetPostProcessPass(const Device& device, bool is_applet) {
|
void Layer::SetPostProcessPass(const Device& device) {
|
||||||
if (is_applet) {
|
|
||||||
post_process.reset();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const VkExtent2D render_area{
|
const VkExtent2D render_area{
|
||||||
.width = Settings::values.resolution_info.ScaleUp(raw_width),
|
.width = Settings::values.resolution_info.ScaleUp(raw_width),
|
||||||
.height = Settings::values.resolution_info.ScaleUp(raw_height),
|
.height = Settings::values.resolution_info.ScaleUp(raw_height),
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ private:
|
|||||||
void RefreshResources(const Device& device, const Tegra::FramebufferConfig& framebuffer);
|
void RefreshResources(const Device& device, const Tegra::FramebufferConfig& framebuffer);
|
||||||
void SetAntiAliasPass(const Device& device);
|
void SetAntiAliasPass(const Device& device);
|
||||||
#ifdef HAS_RESHADE
|
#ifdef HAS_RESHADE
|
||||||
void SetPostProcessPass(const Device& device, bool is_applet);
|
void SetPostProcessPass(const Device& device);
|
||||||
#endif
|
#endif
|
||||||
void ReleaseRawImages();
|
void ReleaseRawImages();
|
||||||
|
|
||||||
|
|||||||
+68
-18
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
// Static Qt on macOS doesn't use Vulkan
|
// Static Qt on macOS doesn't use Vulkan
|
||||||
// Other platforms do, and thus have conflicting VulkanMemoryAllocator symbols
|
// Other platforms do, and thus have conflicting VulkanMemoryAllocator symbols
|
||||||
#include "core/launch_params.h"
|
|
||||||
#if defined(QT_STATICPLUGIN) && !defined(__APPLE__)
|
#if defined(QT_STATICPLUGIN) && !defined(__APPLE__)
|
||||||
#undef VMA_IMPLEMENTATION
|
#undef VMA_IMPLEMENTATION
|
||||||
#endif
|
#endif
|
||||||
@@ -516,28 +515,79 @@ MainWindow::MainWindow(bool has_broken_vulkan)
|
|||||||
// to prevent the UI from blowing up.
|
// to prevent the UI from blowing up.
|
||||||
UpdateUITheme();
|
UpdateUITheme();
|
||||||
|
|
||||||
if (QStringList args = QApplication::arguments(); args.size() >= 2) {
|
QStringList args = QApplication::arguments();
|
||||||
QList<QByteArray> qba{};
|
|
||||||
for (auto const& e : args)
|
|
||||||
qba.push_back(e.toUtf8());
|
|
||||||
std::vector<char*> args_cstr{};
|
|
||||||
for (auto& e : qba)
|
|
||||||
args_cstr.push_back(e.data());
|
|
||||||
|
|
||||||
auto const lp = Core::ParseLaunchParams(*QtCommon::system, args_cstr.size(), args_cstr.data(), nullptr);
|
if (args.size() < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Override fullscreen setting if gamepath or argument is provided
|
QString game_path;
|
||||||
if (!lp.filepath.empty() || lp.fullscreen) {
|
bool should_launch_qlaunch = false;
|
||||||
ui->action_Fullscreen->setChecked(lp.fullscreen);
|
bool should_launch_hlaunch = false;
|
||||||
|
bool should_launch_setup = false;
|
||||||
|
bool has_gamepath = false;
|
||||||
|
bool is_fullscreen = false;
|
||||||
|
|
||||||
|
// Preserves drag/drop functionality
|
||||||
|
for (int i = 1; i < args.size(); ++i) {
|
||||||
|
if (args[i] == QStringLiteral("-f")) {
|
||||||
|
// Launch game in fullscreen mode
|
||||||
|
is_fullscreen = true;
|
||||||
|
} else if (args[i] == QStringLiteral("-u") && i < args.size() - 1) {
|
||||||
|
// Launch game with a specific user
|
||||||
|
int user_arg_idx = ++i;
|
||||||
|
bool argument_ok;
|
||||||
|
std::size_t selected_user = args[user_arg_idx].toUInt(&argument_ok);
|
||||||
|
if (!argument_ok) {
|
||||||
|
// try to look it up by username, only finds the first username that matches.
|
||||||
|
std::string const user_arg_str = args[user_arg_idx].toStdString();
|
||||||
|
auto const user_idx =
|
||||||
|
QtCommon::system->GetProfileManager().GetUserIndex(user_arg_str);
|
||||||
|
if (user_idx != std::nullopt) {
|
||||||
|
selected_user = user_idx.value();
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Frontend, "Invalid user argument '{}'", user_arg_str);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (QtCommon::system->GetProfileManager().UserExistsIndex(selected_user)) {
|
||||||
|
Settings::values.current_user = s32(selected_user);
|
||||||
|
user_flag_cmd_line = true;
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Frontend, "Selected user {} doesn't exist", selected_user);
|
||||||
|
}
|
||||||
|
} else if (args[i] == QStringLiteral("-g") && i < args.size() - 1) {
|
||||||
|
// Launch game at path
|
||||||
|
game_path = args[++i];
|
||||||
|
has_gamepath = true;
|
||||||
|
} else if (args[i] == QStringLiteral("-input-profile") && i < args.size() - 1) {
|
||||||
|
auto& players = Settings::values.players.GetValue();
|
||||||
|
players[0].profile_name = args[++i].toStdString();
|
||||||
|
} else if (args[i] == QStringLiteral("-qlaunch")) {
|
||||||
|
should_launch_qlaunch = true;
|
||||||
|
} else if (args[i] == QStringLiteral("-hlaunch")) {
|
||||||
|
should_launch_hlaunch = true;
|
||||||
|
} else if (args[i] == QStringLiteral("-setup")) {
|
||||||
|
should_launch_setup = true;
|
||||||
|
} else {
|
||||||
|
game_path = args[i];
|
||||||
|
has_gamepath = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!lp.filepath.empty()) {
|
// Override fullscreen setting if gamepath or argument is provided
|
||||||
BootGame(QString::fromStdString(lp.filepath), ApplicationAppletParameters());
|
if (has_gamepath || is_fullscreen) {
|
||||||
} else if (lp.launch_setup) {
|
ui->action_Fullscreen->setChecked(is_fullscreen);
|
||||||
LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Starter), std::nullopt);
|
}
|
||||||
} else if (lp.launch_qlaunch) {
|
|
||||||
|
if (should_launch_setup) {
|
||||||
|
LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Starter), std::nullopt);
|
||||||
|
} else {
|
||||||
|
if (!game_path.isEmpty()) {
|
||||||
|
BootGame(game_path, ApplicationAppletParameters());
|
||||||
|
} else if (should_launch_qlaunch) {
|
||||||
LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::QLaunch), std::nullopt);
|
LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::QLaunch), std::nullopt);
|
||||||
} else if (lp.launch_hlaunch) {
|
} else if (should_launch_hlaunch) {
|
||||||
std::filesystem::path const sd_dir =
|
std::filesystem::path const sd_dir =
|
||||||
Common::FS::GetEdenPathString(Common::FS::EdenPath::SDMCDir);
|
Common::FS::GetEdenPathString(Common::FS::EdenPath::SDMCDir);
|
||||||
auto const hbl_path = (sd_dir / "atmosphere" / "hbl.nsp").string();
|
auto const hbl_path = (sd_dir / "atmosphere" / "hbl.nsp").string();
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ target_link_libraries(yuzu-cmd PRIVATE common core input_common frontend_common
|
|||||||
if (ENABLE_OPENGL)
|
if (ENABLE_OPENGL)
|
||||||
target_link_libraries(yuzu-cmd PRIVATE glad)
|
target_link_libraries(yuzu-cmd PRIVATE glad)
|
||||||
endif()
|
endif()
|
||||||
|
if (MSVC)
|
||||||
|
target_link_libraries(yuzu-cmd PRIVATE getopt)
|
||||||
|
endif()
|
||||||
target_link_libraries(yuzu-cmd PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
|
target_link_libraries(yuzu-cmd PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
|
||||||
|
|
||||||
create_resource("../../dist/eden.bmp" "yuzu_cmd/yuzu_icon.h" "yuzu_icon")
|
create_resource("../../dist/eden.bmp" "yuzu_cmd/yuzu_icon.h" "yuzu_icon")
|
||||||
|
|||||||
+190
-16
@@ -10,7 +10,6 @@
|
|||||||
#include <regex>
|
#include <regex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "common/settings_enums.h"
|
#include "common/settings_enums.h"
|
||||||
#include "core/launch_params.h"
|
|
||||||
#ifdef __EMSCRIPTEN__
|
#ifdef __EMSCRIPTEN__
|
||||||
#include <emscripten.h>
|
#include <emscripten.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -53,6 +52,12 @@
|
|||||||
#include "common/windows/timer_resolution.h"
|
#include "common/windows/timer_resolution.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#undef _UNICODE
|
||||||
|
#include <getopt.h>
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
extern "C" {
|
extern "C" {
|
||||||
// tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable
|
// tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable
|
||||||
@@ -62,6 +67,25 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static void PrintHelp(const char* argv0) {
|
||||||
|
std::cout << "Usage: " << argv0
|
||||||
|
<< " [options] <filename>\n"
|
||||||
|
"-c, --config Load the specified configuration file\n"
|
||||||
|
"-f, --fullscreen Start in fullscreen mode\n"
|
||||||
|
"-g, --game File path of the game to load\n"
|
||||||
|
"-h, --help Display this help and exit\n"
|
||||||
|
"-m, --multiplayer=nick:password@address:port"
|
||||||
|
" Nickname, password, address and port for multiplayer\n"
|
||||||
|
"-p, --program Pass following string as arguments to executable\n"
|
||||||
|
"-u, --user Select a specific user profile from 0 to 7\n"
|
||||||
|
"-d, --debug Run the GDB stub on a port from 1 to 65535\n"
|
||||||
|
"-v, --version Output version information and exit\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PrintVersion() {
|
||||||
|
std::cout << "Eden " << Common::g_scm_branch << " " << Common::g_scm_desc << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
static void OnStateChanged(const Network::RoomMember::State& state) {
|
static void OnStateChanged(const Network::RoomMember::State& state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case Network::RoomMember::State::Idle:
|
case Network::RoomMember::State::Idle:
|
||||||
@@ -184,18 +208,168 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
|
|||||||
LOG_CRITICAL(Frontend, "Failed to get command line arguments");
|
LOG_CRITICAL(Frontend, "Failed to get command line arguments");
|
||||||
return SDL_APP_FAILURE;
|
return SDL_APP_FAILURE;
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
wchar_t **argv_w = nullptr;
|
|
||||||
#endif
|
#endif
|
||||||
Core::LaunchParams lp = Core::ParseLaunchParams(state->system, argc, argv, argv_w);
|
std::string filepath;
|
||||||
|
std::optional<std::string> config_path{};
|
||||||
|
std::string program_args;
|
||||||
|
std::optional<int> selected_user{};
|
||||||
|
std::optional<u16> override_gdb_port{};
|
||||||
|
bool use_multiplayer = false;
|
||||||
|
bool fullscreen = false;
|
||||||
|
bool force_null_render = false;
|
||||||
|
bool force_single_core = false;
|
||||||
|
std::string nickname{};
|
||||||
|
std::string password{};
|
||||||
|
std::string address{};
|
||||||
|
std::string input_profile{};
|
||||||
|
std::optional<std::string> log_filter{};
|
||||||
|
u16 port = Network::DefaultRoomPort;
|
||||||
|
|
||||||
|
static struct option long_options[] = {
|
||||||
|
// clang-format off
|
||||||
|
{"debug", no_argument, 0, 'd'},
|
||||||
|
{"config", required_argument, 0, 'c'},
|
||||||
|
{"fullscreen", no_argument, 0, 'f'},
|
||||||
|
{"help", no_argument, 0, 'h'},
|
||||||
|
{"game", required_argument, 0, 'g'},
|
||||||
|
{"multiplayer", required_argument, 0, 'm'},
|
||||||
|
{"program", optional_argument, 0, 'p'},
|
||||||
|
{"user", required_argument, 0, 'u'},
|
||||||
|
{"version", no_argument, 0, 'v'},
|
||||||
|
{"input-profile", no_argument, 0, 'i'},
|
||||||
|
{"null-render", no_argument, 0, 'n'},
|
||||||
|
{"singlecore", no_argument, 0, 's'},
|
||||||
|
{"filter", no_argument, 0, 'x'},
|
||||||
|
{0, 0, 0, 0},
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
|
||||||
|
while (optind < argc) {
|
||||||
|
int arg = getopt_long(argc, argv, "g:fhvcip::c:u:d:", long_options, &option_index);
|
||||||
|
if (arg != -1) {
|
||||||
|
switch (char(arg)) {
|
||||||
|
case 'd':
|
||||||
|
override_gdb_port = uint16_t(atoi(optarg));
|
||||||
|
break;
|
||||||
|
case 'c':
|
||||||
|
config_path = optarg;
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
fullscreen = true;
|
||||||
|
LOG_INFO(Frontend, "Starting in fullscreen mode...");
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
PrintHelp(argv[0]);
|
||||||
|
return SDL_APP_FAILURE;
|
||||||
|
case 'g':
|
||||||
|
filepath = std::string(optarg);
|
||||||
|
break;
|
||||||
|
case 'i': {
|
||||||
|
input_profile = std::string(optarg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'm': {
|
||||||
|
use_multiplayer = true;
|
||||||
|
const std::string str_arg(optarg);
|
||||||
|
// regex to check if the format is nickname:password@ip:port
|
||||||
|
// with optional :password
|
||||||
|
const std::regex re("^([^:]+)(?::(.+))?@([^:]+)(?::([0-9]+))?$");
|
||||||
|
if (!std::regex_match(str_arg, re)) {
|
||||||
|
std::cout << "Wrong format for option --multiplayer\n";
|
||||||
|
PrintHelp(argv[0]);
|
||||||
|
return SDL_APP_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::smatch match;
|
||||||
|
std::regex_search(str_arg, match, re);
|
||||||
|
ASSERT(match.size() == 5);
|
||||||
|
nickname = match[1];
|
||||||
|
password = match[2];
|
||||||
|
address = match[3];
|
||||||
|
if (!match[4].str().empty()) {
|
||||||
|
port = u16(std::strtoul(match[4].str().c_str(), nullptr, 0));
|
||||||
|
}
|
||||||
|
std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$");
|
||||||
|
if (!std::regex_match(nickname, nickname_re)) {
|
||||||
|
LOG_ERROR(Frontend, "Nickname is not valid. Must be 4 to 20 alphanumeric characters");
|
||||||
|
return SDL_APP_FAILURE;
|
||||||
|
}
|
||||||
|
if (address.empty()) {
|
||||||
|
LOG_ERROR(Frontend, "Address to room must not be empty");
|
||||||
|
return SDL_APP_FAILURE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'p':
|
||||||
|
program_args = argv[optind];
|
||||||
|
++optind;
|
||||||
|
break;
|
||||||
|
case 'u':
|
||||||
|
selected_user = atoi(optarg);
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
PrintVersion();
|
||||||
|
return SDL_APP_FAILURE;
|
||||||
|
case 'n':
|
||||||
|
force_null_render = true;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
force_single_core = true;
|
||||||
|
break;
|
||||||
|
case 'x':
|
||||||
|
log_filter = argv[optind];
|
||||||
|
++optind;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
#ifdef _WIN32
|
||||||
|
filepath = Common::UTF16ToUTF8(argv_w[optind]);
|
||||||
|
#else
|
||||||
|
filepath = argv[optind];
|
||||||
|
#endif
|
||||||
|
optind++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SdlConfig config{config_path};
|
||||||
|
|
||||||
|
// apply the log_filter setting
|
||||||
|
// the logger was initialized before and doesn't pick up the filter on its own
|
||||||
|
Common::Log::Filter filter;
|
||||||
|
filter.ParseFilterString(log_filter.value_or(Settings::values.log_filter.GetValue()));
|
||||||
|
Common::Log::SetGlobalFilter(filter);
|
||||||
|
|
||||||
|
if (!program_args.empty()) {
|
||||||
|
Settings::values.program_args = program_args;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!input_profile.empty()) {
|
||||||
|
auto& players = Settings::values.players.GetValue();
|
||||||
|
players[0].profile_name = input_profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected_user.has_value()) {
|
||||||
|
Settings::values.current_user = std::clamp(*selected_user, 0, 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (override_gdb_port.has_value()) {
|
||||||
|
Settings::values.use_gdbstub = true;
|
||||||
|
Settings::values.gdbstub_port = *override_gdb_port;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (force_single_core) {
|
||||||
|
Settings::values.use_multi_core = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (force_null_render) {
|
||||||
|
Settings::values.renderer_backend = Settings::RendererBackend::Null;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
LocalFree(argv_w);
|
LocalFree(argv_w);
|
||||||
#endif
|
#endif
|
||||||
SdlConfig config{lp.config_path};
|
|
||||||
|
|
||||||
Core::ApplyLaunchParams(lp);
|
if (filepath.empty()) {
|
||||||
|
|
||||||
if (lp.filepath.empty()) {
|
|
||||||
LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified");
|
LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified");
|
||||||
return SDL_APP_FAILURE;
|
return SDL_APP_FAILURE;
|
||||||
}
|
}
|
||||||
@@ -212,14 +386,14 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
|
|||||||
case Settings::RendererBackend::OpenGL_GLSL:
|
case Settings::RendererBackend::OpenGL_GLSL:
|
||||||
case Settings::RendererBackend::OpenGL_GLASM:
|
case Settings::RendererBackend::OpenGL_GLASM:
|
||||||
case Settings::RendererBackend::OpenGL_SPIRV:
|
case Settings::RendererBackend::OpenGL_SPIRV:
|
||||||
state->emu_window = std::make_unique<EmuWindow_SDL3_GL>(&input_subsystem, state->system, lp.fullscreen);
|
state->emu_window = std::make_unique<EmuWindow_SDL3_GL>(&input_subsystem, state->system, fullscreen);
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
case Settings::RendererBackend::Vulkan:
|
case Settings::RendererBackend::Vulkan:
|
||||||
state->emu_window = std::make_unique<EmuWindow_SDL3_VK>(&input_subsystem, state->system, lp.fullscreen);
|
state->emu_window = std::make_unique<EmuWindow_SDL3_VK>(&input_subsystem, state->system, fullscreen);
|
||||||
break;
|
break;
|
||||||
case Settings::RendererBackend::Null:
|
case Settings::RendererBackend::Null:
|
||||||
state->emu_window = std::make_unique<EmuWindow_SDL3_Null>(&input_subsystem, state->system, lp.fullscreen);
|
state->emu_window = std::make_unique<EmuWindow_SDL3_Null>(&input_subsystem, state->system, fullscreen);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
LOG_CRITICAL(Frontend, "Invalid renderer backend");
|
LOG_CRITICAL(Frontend, "Invalid renderer backend");
|
||||||
@@ -239,12 +413,12 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
|
|||||||
Service::AM::FrontendAppletParameters load_parameters{
|
Service::AM::FrontendAppletParameters load_parameters{
|
||||||
.applet_id = Service::AM::AppletId::Application,
|
.applet_id = Service::AM::AppletId::Application,
|
||||||
};
|
};
|
||||||
const Core::SystemResultStatus load_result = state->system.Load(*state->emu_window, lp.filepath, load_parameters);
|
const Core::SystemResultStatus load_result = state->system.Load(*state->emu_window, filepath, load_parameters);
|
||||||
switch (load_result) {
|
switch (load_result) {
|
||||||
case Core::SystemResultStatus::Success:
|
case Core::SystemResultStatus::Success:
|
||||||
break; // Expected case
|
break; // Expected case
|
||||||
case Core::SystemResultStatus::ErrorGetLoader:
|
case Core::SystemResultStatus::ErrorGetLoader:
|
||||||
LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", lp.filepath);
|
LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filepath);
|
||||||
return SDL_APP_FAILURE;
|
return SDL_APP_FAILURE;
|
||||||
case Core::SystemResultStatus::ErrorLoader:
|
case Core::SystemResultStatus::ErrorLoader:
|
||||||
LOG_CRITICAL(Frontend, "Failed to load ROM!");
|
LOG_CRITICAL(Frontend, "Failed to load ROM!");
|
||||||
@@ -266,14 +440,14 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
|
|||||||
return SDL_APP_FAILURE;
|
return SDL_APP_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lp.use_multiplayer) {
|
if (use_multiplayer) {
|
||||||
if (auto member = Network::GetRoomMember().lock()) {
|
if (auto member = Network::GetRoomMember().lock()) {
|
||||||
member->BindOnChatMessageReceived(OnMessageReceived);
|
member->BindOnChatMessageReceived(OnMessageReceived);
|
||||||
member->BindOnStatusMessageReceived(OnStatusMessageReceived);
|
member->BindOnStatusMessageReceived(OnStatusMessageReceived);
|
||||||
member->BindOnStateChanged(OnStateChanged);
|
member->BindOnStateChanged(OnStateChanged);
|
||||||
member->BindOnError(OnNetworkError);
|
member->BindOnError(OnNetworkError);
|
||||||
LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", lp.address, lp.port, lp.nickname);
|
LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port, nickname);
|
||||||
member->Join(lp.nickname, lp.address.c_str(), lp.port, 0, Network::NoPreferredIP, lp.password);
|
member->Join(nickname, address.c_str(), port, 0, Network::NoPreferredIP, password);
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR(Network, "Could not access RoomMember");
|
LOG_ERROR(Network, "Could not access RoomMember");
|
||||||
return SDL_APP_FAILURE;
|
return SDL_APP_FAILURE;
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <regex>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "common/logging.h"
|
||||||
|
#include "common/settings.h"
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "core/cpu_manager.h"
|
||||||
|
#include "core/file_sys/registered_cache.h"
|
||||||
|
#include "core/file_sys/vfs/vfs_real.h"
|
||||||
|
#include "core/hle/service/am/applet_manager.h"
|
||||||
|
#include "core/hle/service/filesystem/filesystem.h"
|
||||||
|
#include "core/loader/loader.h"
|
||||||
|
#include "frontend_common/config.h"
|
||||||
|
#include "input_common/main.h"
|
||||||
|
#include "video_core/gpu.h"
|
||||||
|
#include "video_core/renderer_base.h"
|
||||||
|
#include "yuzu_cmd/emu_window/emu_window_sdl3.h"
|
||||||
|
#include "yuzu_cmd/emu_window/emu_window_sdl3_null.h"
|
||||||
|
#include "yuzu_cmd/emu_window/emu_window_sdl3_vk.h"
|
||||||
|
|
||||||
|
class EmuWindow_Headless : public Core::Frontend::EmuWindow {
|
||||||
|
public:
|
||||||
|
explicit EmuWindow_Headless() = default;
|
||||||
|
~EmuWindow_Headless() = default;
|
||||||
|
|
||||||
|
bool IsShown() const override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override {
|
||||||
|
|
||||||
|
}
|
||||||
|
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override {
|
||||||
|
return std::make_unique<DummyContext>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
struct {
|
||||||
|
Core::System system{};
|
||||||
|
EmuWindow_Headless emu_window{};
|
||||||
|
} state = {};
|
||||||
|
|
||||||
|
Common::Log::Initialize();
|
||||||
|
Common::Log::SetColorConsoleBackendEnabled(true);
|
||||||
|
Common::Log::Start();
|
||||||
|
|
||||||
|
if (argc < 2) {
|
||||||
|
LOG_CRITICAL(Frontend, "Usage: {} [ms-to-run] [file]", argv[0]);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::chrono::milliseconds time_quanta = std::chrono::milliseconds{atoi(argv[1])};
|
||||||
|
auto const time_end = std::chrono::steady_clock::now() + time_quanta;
|
||||||
|
std::string filepath = argv[2];
|
||||||
|
|
||||||
|
// apply the log_filter setting
|
||||||
|
// the logger was initialized before and doesn't pick up the filter on its own
|
||||||
|
Common::Log::Filter filter;
|
||||||
|
filter.ParseFilterString("*:Info");
|
||||||
|
Common::Log::SetGlobalFilter(filter);
|
||||||
|
|
||||||
|
if (filepath.empty()) {
|
||||||
|
LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.system.Initialize();
|
||||||
|
|
||||||
|
InputCommon::InputSubsystem input_subsystem{};
|
||||||
|
|
||||||
|
// Apply the command line arguments
|
||||||
|
state.system.ApplySettings();
|
||||||
|
Settings::values.renderer_backend.SetValue(Settings::RendererBackend::Null);
|
||||||
|
|
||||||
|
state.system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
||||||
|
state.system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
|
||||||
|
state.system.GetFileSystemController().CreateFactories(*state.system.GetFilesystem());
|
||||||
|
state.system.GetUserChannel().clear();
|
||||||
|
|
||||||
|
Service::AM::FrontendAppletParameters load_parameters{
|
||||||
|
.applet_id = Service::AM::AppletId::Application,
|
||||||
|
};
|
||||||
|
if (auto const load_result = state.system.Load(state.emu_window, filepath, load_parameters); load_result != Core::SystemResultStatus::Success) {
|
||||||
|
LOG_CRITICAL(Frontend, "load result = {}", u32(load_result));
|
||||||
|
// shutdown
|
||||||
|
void(state.system.Pause());
|
||||||
|
state.system.DetachDebugger();
|
||||||
|
state.system.ShutdownMainProcess();
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
// Core is loaded, start the GPU (makes the GPU contexts current to this thread)
|
||||||
|
state.system.GPU().Start();
|
||||||
|
state.system.GetCpuManager().OnGpuReady();
|
||||||
|
// don't do anything, SDL3 already exists for us :D
|
||||||
|
state.system.RegisterExitCallback([] {});
|
||||||
|
void(state.system.Run());
|
||||||
|
if (state.system.DebuggerEnabled())
|
||||||
|
state.system.InitializeDebugger();
|
||||||
|
while (state.system.IsPoweredOn() && !state.system.GetExitRequested()) {
|
||||||
|
auto const time_now = std::chrono::steady_clock::now();
|
||||||
|
if (time_now > time_end)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// shutdown
|
||||||
|
void(state.system.Pause());
|
||||||
|
state.system.DetachDebugger();
|
||||||
|
state.system.ShutdownMainProcess();
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define VMA_IMPLEMENTATION
|
||||||
|
#include "video_core/vulkan_common/vma.h"
|
||||||
Reference in New Issue
Block a user