Compare commits

..

17 Commits

Author SHA1 Message Date
lizzie f307a0331d 2026-09-23 07:42:01
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 07:42:01 +00:00
lizzie cb85810ffd 2026-09-23 02:07:39
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 02:07:39 +00:00
lizzie 8ddd0f3cc4 2026-09-23 01:28:59
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 01:28:59 +00:00
lizzie c52a7be00f 2026-09-23 01:18:14
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 01:18:14 +00:00
lizzie 37dc82d6d7 2026-09-22 04:33:30
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 00:53:50 +00:00
lizzie b6a7c809eb 2026-09-22 04:25:09
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 00:53:50 +00:00
lizzie d2b43a2356 2026-09-21 02:08:29
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 00:53:50 +00:00
lizzie 6d508f3eaf 2026-09-21 02:07:30
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 00:53:50 +00:00
lizzie 0871e16706 Fix license headers 2026-09-23 00:53:50 +00:00
lizzie 38816c4f31 Fix license headers 2026-09-23 00:53:50 +00:00
lizzie 251dc590dd 2026-09-21 00:39:13
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 00:53:50 +00:00
lizzie 3eb20d4d45 Fix license headers 2026-09-23 00:53:50 +00:00
lizzie fdf816c1ee 2026-09-20 13:20:40
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 00:53:50 +00:00
lizzie 88bfc04887 2026-09-20 13:02:52
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 00:53:50 +00:00
lizzie 44e6f76c4c 2026-09-20 12:52:02
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 00:53:50 +00:00
lizzie b9ab300b97 Fix license headers 2026-09-23 00:53:49 +00:00
lizzie 376dc541ea 2026-09-20 07:03:36
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-23 00:53:49 +00:00
58 changed files with 998 additions and 867 deletions
+21 -33
View File
@@ -1,44 +1,32 @@
# 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.
- `--debug/-d`: Enter debug mode, allow gdb stub at port `1234`
- `--config/-c`: Specify alternate configuration file.
- `--fullscreen/-f`: Use fullscreen.
- `--help/-h`: Display help.
- `--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:
- `-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.
- `-f`: Use fullscreen.
- `-u <number>`: Select the index of the user to load as.
- `-input-profile <name>`: Specifies input profile name to use (for player #0 only).
- `-qlaunch`: Launch QLaunch.
- `-hlaunch`: Launch homebrew launcher `nx-hbloader`.
- Requires a copy of Atmosphere to be extracted onto `sdmc`.
- This is a shorthand for `<eden folder>/sdmc/atmosphere/hbl.nsp`.
- `-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.
- `-d/--room-description`: The room description.
- `-s/--bind-address`: The bind address for the room.
- `-p/--port`: The port used for the room.
- `-m/--max-members`: The maximum number of players for this room.
- `-w/--password`: The password for the room.
- `-g/--preferred-game`: The preferred game for this room.
- `-i/--preferred-game-id`: The preferred game-id for this room.
- `-u/--username`: The username used for announce.
- `-t/--token`: The token used for announce.
- `-a/--web-api-url`: yuzu Web API url.
- `-b/--ban-list-file`: The file for storing the room ban list.
- `-l/--log-file`: The file for storing the room log.
- `-h/--help`: Display this help and exit.
- `-v/--version`: Output version information and exit.
- `--debug/-d`: Enter debug mode, allow gdb stub at port `1234`
- `--config/-c`: Specify alternate configuration file.
- `--fullscreen/-f`: Set fullscreen.
- `--help/-h`: Display help.
- `--game/-g`: Specify the game to run.
- `--multiplayer/-m`: Specify multiplayer options.
- `--program/-p`: Specify the program arguments to pass (optional).
- `--user/-u`: Specify the user index.
- `--version/-v`: Display version and quit.
- `--input-profile/-i`: 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.
+6 -1
View File
@@ -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-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() {
return *audio_renderer;
}
+2 -1
View File
@@ -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-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -45,6 +45,7 @@ public:
explicit ADSP(Core::System& system, Sink::Sink& sink);
~ADSP() = default;
void Shutdown();
AudioRenderer::AudioRenderer& AudioRenderer();
OpusDecoder::OpusDecoder& OpusDecoder();
@@ -34,26 +34,19 @@ void AudioRenderer::Start() {
mailbox.Send(Direction::DSP, Message::InitializeOK);
if (mailbox.Receive(Direction::Host) != Message::InitializeOK) {
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
"message response from ADSP!");
return;
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown message response from ADSP!");
}
running = true;
}
void AudioRenderer::Stop() {
if (!running) {
return;
if (main_thread.joinable()) {
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) {
if (stream) {
stream->Stop();
@@ -61,7 +54,6 @@ void AudioRenderer::Stop() {
stream = nullptr;
}
}
running = false;
}
void AudioRenderer::Signal() {
@@ -69,13 +61,12 @@ void AudioRenderer::Signal() {
Send(Direction::DSP, Message::Render);
}
void AudioRenderer::Wait() {
auto msg = Receive(Direction::Host);
void AudioRenderer::Wait(std::stop_token stop_token) {
auto msg = mailbox.Receive(Direction::Host, stop_token);
if (msg != Message::RenderResponse) {
LOG_ERROR(Service_Audio,
"Did not receive the expected render response from the AudioRenderer! Expected "
"{}, got {}",
Message::RenderResponse, msg);
"Did not receive the expected render response from the AudioRenderer! Expected "
"{}, got {}", Message::RenderResponse, msg);
}
PostDSPClearCommandBuffer();
}
@@ -147,7 +138,9 @@ void AudioRenderer::Main(std::stop_token stop_token) {
constexpr u64 max_process_time{2'304'000ULL};
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) {
case Message::Shutdown:
mailbox.Send(Direction::Host, Message::Shutdown);
@@ -63,13 +63,15 @@ public:
*/
void Start();
void NotifyShutdown();
/**
* Stop the AudioRenderer.
*/
void Stop();
void Signal();
void Wait();
void Wait(std::stop_token stop_token);
void Send(Direction dir, u32 message);
u32 Receive(Direction dir);
@@ -102,8 +104,6 @@ private:
Mailbox mailbox;
/// 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
std::array<CommandBuffer, MaxRendererSessions> command_buffers{};
/// The command lists to process
+33 -23
View File
@@ -41,19 +41,16 @@ OpusDecoder::OpusDecoder(Core::System& system_) : system{system_} {
}
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();
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) {
@@ -73,7 +70,6 @@ void OpusDecoder::Init(std::stop_token stop_token) {
return;
}
main_thread = std::jthread([this](std::stop_token st) { Main(st); });
running = true;
Send(Direction::Host, Message::StartOK);
}
@@ -82,12 +78,15 @@ void OpusDecoder::Main(std::stop_token stop_token) {
while (!stop_token.stop_requested()) {
auto msg = Receive(Direction::DSP, stop_token);
if (stop_token.stop_requested())
break;
switch (msg) {
case Shutdown:
case Message::Shutdown:
Send(Direction::Host, Message::ShutdownOK);
return;
case GetWorkBufferSize: {
case Message::GetWorkBufferSize: {
auto channel_count = static_cast<s32>(shared_memory->host_send_data[0]);
ASSERT(IsValidChannelCount(channel_count));
@@ -96,7 +95,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
Send(Direction::Host, Message::GetWorkBufferSizeOK);
} break;
case InitializeDecodeObject: {
case Message::InitializeDecodeObject: {
auto buffer = shared_memory->host_send_data[0];
auto buffer_size = shared_memory->host_send_data[1];
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);
} break;
case ShutdownDecodeObject: {
case Message::ShutdownDecodeObject: {
auto buffer = shared_memory->host_send_data[0];
[[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);
} break;
case DecodeInterleaved: {
case Message::DecodeInterleaved: {
auto start_time = system.CoreTiming().GetGlobalTimeUs();
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);
} break;
case MapMemory: {
case Message::MapMemory: {
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
Send(Direction::Host, Message::MapMemoryOK);
} break;
case UnmapMemory: {
case Message::UnmapMemory: {
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
Send(Direction::Host, Message::UnmapMemoryOK);
} break;
case GetWorkBufferSizeForMultiStream: {
case Message::GetWorkBufferSizeForMultiStream: {
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]);
@@ -184,7 +183,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK);
} break;
case InitializeMultiStreamDecodeObject: {
case Message::InitializeMultiStreamDecodeObject: {
auto buffer = shared_memory->host_send_data[0];
auto buffer_size = shared_memory->host_send_data[1];
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);
} break;
case ShutdownMultiStreamDecodeObject: {
case Message::ShutdownMultiStreamDecodeObject: {
auto buffer = shared_memory->host_send_data[0];
[[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);
} break;
case DecodeInterleavedForMultiStream: {
case Message::DecodeInterleavedForMultiStream: {
auto start_time = system.CoreTiming().GetGlobalTimeUs();
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
+3 -3
View File
@@ -57,7 +57,7 @@ public:
~OpusDecoder();
bool IsRunning() const noexcept {
return running;
return main_thread.joinable();
}
void Send(Direction dir, u32 message);
@@ -67,6 +67,8 @@ public:
shared_memory = &shared_memory_;
}
void Shutdown();
private:
/**
* Initializing thread, launched at audio_core boot to avoid blocking the main emu boot thread.
@@ -85,8 +87,6 @@ private:
std::jthread init_thread{};
/// 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,
/// and the responses are written back by the OpusDecoder.
SharedMemory* shared_memory{};
+1
View File
@@ -33,6 +33,7 @@ void AudioCore::CreateSinks() {
void AudioCore::Shutdown() {
audio_manager->Shutdown();
adsp->Shutdown();
}
AudioManager& AudioCore::GetAudioManager() {
+2 -2
View File
@@ -17,7 +17,7 @@ AudioManager::AudioManager() {
std::unique_lock l{events.GetAudioEventLock()};
events.ClearEvents();
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)) {
break;
}
@@ -35,9 +35,9 @@ AudioManager::AudioManager() {
}
void AudioManager::Shutdown() {
events.SetAudioEvent(Event::Type::Max, true);
if (thread.joinable()) {
thread.request_stop();
events.SetAudioEvent(Event::Type::Max, true);
thread.join();
}
}
+1 -3
View File
@@ -39,9 +39,7 @@ class AudioManager {
public:
explicit AudioManager();
/**
* Shutdown the audio manager.
*/
/// @brief Shutdown the audio manager.
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-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_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.address = CpuAddr(0);
out_status.writeOffset = current_state->last_pos2;
+19 -20
View File
@@ -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-FileCopyrightText: Copyright 2022 yuzu Emulator Project
@@ -31,10 +31,10 @@ public:
};
struct DeviceInParameter {
/* 0x000 */ char name[0x100];
/* 0x000 */ u8 name[0x100];
/* 0x100 */ u32 input_count;
/* 0x104 */ std::array<s8, MaxChannels> inputs;
/* 0x10A */ char unk10A[0x1];
/* 0x10A */ u8 unk10A[0x1];
/* 0x10B */ bool downmix_enabled;
/* 0x10C */ std::array<f32, 4> downmix_coeff;
};
@@ -43,7 +43,7 @@ public:
struct DeviceState {
/* 0x00 */ UpsamplerInfo* upsampler_info;
/* 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!");
@@ -55,8 +55,8 @@ public:
/* 0x14 */ u32 previous_pos;
/* 0x18 */ SampleFormat format;
/* 0x1C */ std::array<s8, MaxChannels> inputs;
/* 0x22 */ bool in_use;
/* 0x23 */ char unk23[0x5];
/* 0x22 */ u8 in_use;
/* 0x23 */ u8 unk23[0x5];
};
static_assert(sizeof(CircularBufferInParameter) == 0x28,
"CircularBufferInParameter has the wrong size!");
@@ -65,16 +65,16 @@ public:
/* 0x00 */ u32 last_pos2;
/* 0x04 */ s32 current_pos;
/* 0x08 */ u32 last_pos;
/* 0x0C */ char unk0C[0x4];
/* 0x0C */ u8 unk0C[0x4];
/* 0x10 */ AddressInfo address_info;
};
static_assert(sizeof(CircularBufferState) == 0x30, "CircularBufferState has the wrong size!");
struct InParameter {
/* 0x000 */ Type type;
/* 0x001 */ bool in_use;
/* 0x001 */ u8 in_use;
/* 0x004 */ u32 node_id;
/* 0x008 */ char unk08[0x18];
/* 0x008 */ u8 unk08[0x18];
union {
/* 0x020 */ DeviceInParameter device;
/* 0x020 */ CircularBufferInParameter circular_buffer;
@@ -84,7 +84,7 @@ public:
struct OutStatus {
/* 0x00 */ u32 writeOffset;
/* 0x04 */ char unk04[0x1C];
/* 0x04 */ u8 unk04[0x1C];
}; // size == 0x20
static_assert(sizeof(OutStatus) == 0x20, "SinkInfoBase::OutStatus has the wrong size!");
@@ -162,19 +162,18 @@ public:
u8* GetParameter();
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
std::array<u8, (std::max)(sizeof(DeviceState), sizeof(CircularBufferState))> state{};
/// Parameter buffer for this sink
std::array<u8, (std::max)(sizeof(DeviceInParameter), sizeof(CircularBufferInParameter))>
parameter{};
std::array<u8, (std::max)(sizeof(DeviceInParameter), sizeof(CircularBufferInParameter))> 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
+14 -14
View File
@@ -100,8 +100,9 @@ u64 System::GetWorkBufferSize(const AudioRendererParameterInternal& params) {
}
System::System(Core::System& core_, Kernel::KEvent* adsp_rendered_event_)
: core{core_}, audio_renderer{core.AudioCore().ADSP().AudioRenderer()},
adsp_rendered_event{adsp_rendered_event_} {}
: core{core_}
, adsp_rendered_event{adsp_rendered_event_}
{}
Result System::Initialize(const AudioRendererParameterInternal& params,
Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size,
@@ -406,7 +407,7 @@ void System::Finalize() {
return;
}
if (active) {
if (IsActive()) {
Stop();
}
@@ -433,14 +434,12 @@ void System::Start() {
std::scoped_lock l{lock};
frames_elapsed = 0;
state = State::Started;
active = true;
}
void System::Stop() {
{
std::scoped_lock l{lock};
state = State::Stopped;
active = false;
}
if (execution_mode == ExecutionMode::Auto) {
@@ -480,7 +479,7 @@ Result System::Update(std::span<const u8> input, std::span<u8> performance, std:
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);
if (result.IsError()) {
LOG_ERROR(Service_Audio, "Failed to update Effects!");
@@ -582,16 +581,16 @@ u32 System::GetRenderingDevice() const {
}
bool System::IsActive() const {
return active;
return state == State::Started;
}
void System::SendCommandToDsp() {
std::scoped_lock l{lock};
if (initialized) {
if (active) {
if (IsActive()) {
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};
if (remaining_command_count) {
@@ -618,16 +617,17 @@ void System::SendCommandToDsp() {
auto time_limit{
static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 *
(static_cast<f32>(render_time_limit_percent) / 100.0f))};
audio_renderer.SetCommandBuffer(session_id, translated_addr, command_size, time_limit,
applet_resource_user_id, process_handle,
reset_command_buffers);
core.AudioCore().ADSP().AudioRenderer().SetCommandBuffer(session_id,
translated_addr, command_size,
time_limit, applet_resource_user_id,
process_handle, reset_command_buffers);
reset_command_buffers = false;
command_buffer_size = command_size;
if (remaining_command_count == 0) {
adsp_rendered_event->Signal(core.Kernel());
}
} else {
audio_renderer.ClearRemainCommandCount(session_id);
core.AudioCore().ADSP().AudioRenderer().ClearRemainCommandCount(session_id);
terminate_event.Set();
}
}
@@ -738,7 +738,7 @@ u64 System::GenerateCommand(std::span<u8> in_command_buffer,
const auto end_time{core.CoreTiming().GetGlobalTimeNs().count()};
total_ticks_elapsed += end_time - start_time;
num_command_lists_generated++;
render_start_tick = audio_renderer.GetRenderingStartTick(session_id);
render_start_tick = core.AudioCore().ADSP().AudioRenderer().GetRenderingStartTick(session_id);
frames_elapsed++;
return command_buffer.size;
-2
View File
@@ -219,8 +219,6 @@ public:
private:
/// Core system
Core::System& core;
/// Reference to the ADSP's AudioRenderer for communication
::AudioCore::ADSP::AudioRenderer::AudioRenderer& audio_renderer;
/// Is this system initialized?
bool initialized{};
/// Is this system currently active?
+26 -29
View File
@@ -11,73 +11,70 @@
#include "audio_core/renderer/system_manager.h"
#include "common/thread.h"
#include "core/core.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/core_timing.h"
namespace AudioCore::Renderer {
SystemManager::SystemManager(Core::System& core_)
: core{core_}, audio_renderer{core.AudioCore().ADSP().AudioRenderer()} {}
: core{core_}
{
}
SystemManager::~SystemManager() {
Stop();
}
void SystemManager::InitializeUnsafe() {
if (!active) {
active = true;
audio_renderer.Start();
thread = std::jthread([this](std::stop_token stop_token) {
Common::SetCurrentThreadName("AudioRenderSystemManager");
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
while (active && !stop_token.stop_requested()) {
if (!thread.joinable()) {
core.AudioCore().ADSP().AudioRenderer().Start();
thread = core.Kernel().RunOnHostCoreThread("AudioRenderSystemManager", [this]() {
auto& audio_renderer = core.AudioCore().ADSP().AudioRenderer();
auto const stop_token = thread.get_stop_token();
while (!stop_token.stop_requested()) {
{
std::scoped_lock l{mutex1};
std::scoped_lock lk{mutex};
for (auto system : systems)
system->SendCommandToDsp();
}
audio_renderer.Signal();
audio_renderer.Wait();
audio_renderer.Wait(stop_token);
}
});
}
}
void SystemManager::Stop() {
if (active) {
active = false;
core.AudioCore().ADSP().AudioRenderer().Stop();
if (thread.joinable()) {
thread.request_stop();
thread.join();
audio_renderer.Stop();
}
}
bool SystemManager::Add(System& system_) {
std::scoped_lock l2{mutex2};
if (systems.size() + 1 > MaxRendererSessions) {
std::scoped_lock lk{mutex};
if (systems.size() >= MaxRendererSessions) {
LOG_ERROR(Service_Audio, "Maximum AudioRenderer Systems active, cannot add more!");
return false;
}
{
std::scoped_lock l{mutex1};
if (systems.empty())
InitializeUnsafe();
}
systems.push_back(&system_);
if (!systems.empty()) {
InitializeUnsafe();
}
return true;
}
bool SystemManager::Remove(System& system_) {
std::scoped_lock l2{mutex2};
{
std::scoped_lock l{mutex1};
if (systems.remove(&system_) == 0) {
LOG_ERROR(Service_Audio, "Failed to remove a render system, it was not found in the list!");
return false;
}
std::scoped_lock lk{mutex};
if (systems.remove(&system_) == 0) {
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();
}
return true;
}
+2 -8
View File
@@ -75,14 +75,8 @@ private:
std::list<System*> systems{};
/// Main worker thread for generating command lists
std::jthread thread;
/// Mutex for the systems
std::mutex mutex1{};
/// 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;
/// Mutex for the reading and adding/removing systems
std::mutex mutex{};
};
} // namespace AudioCore::Renderer
+6 -6
View File
@@ -109,12 +109,6 @@ private:
if (read_index == producer.index.load(std::memory_order::acquire)) {
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) {
// Wait until the queue is not empty.
std::unique_lock lock{consumer.cv_mutex};
@@ -124,6 +118,12 @@ private:
if (stop_token.stop_requested()) {
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 {
static_assert(Mode < PopMode::Count, "Invalid PopMode.");
}
-15
View File
@@ -96,21 +96,6 @@ bool CommitVectorPage(uintptr_t addr, bool write) noexcept {
#ifndef MAP_NOCORE
#define MAP_NOCORE 0
#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 {
if (auto page = HostPageSize; size % page != 0) {
+3 -17
View File
@@ -35,7 +35,6 @@ const u64 HostPageMask = ~(HostPageSize - 1);
void* AllocateMemoryPages(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.
template <typename T>
@@ -115,7 +114,7 @@ public:
const u64 end_page = AlignUp(base, HostPageSize);
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);
}
@@ -125,16 +124,11 @@ public:
base = end_page;
for (u64 page = base; page < end; page += HostPageSize) {
auto index = (page - reinterpret_cast<u64>(base_ptr)) / sizeof(T);
if (!IsCommittedPage(index)) {
if (!IsCommittedPage((page - reinterpret_cast<u64>(base_ptr)) / sizeof(T))) {
continue;
}
if (end - page >= HostPageSize) {
DecommitPage(index);
} else {
std::memset(reinterpret_cast<void*>(page), 0, end - page);
}
std::memset(reinterpret_cast<void*>(page), 0, (std::min)( HostPageSize, end - page));
}
}
@@ -189,14 +183,6 @@ private:
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{};
T* base_ptr{};
-5
View File
@@ -23,8 +23,6 @@ add_library(core STATIC
core_timing.h
cpu_manager.cpp
cpu_manager.h
launch_params.cpp
launch_params.h
crypto/aes_util.cpp
crypto/aes_util.h
crypto/ctr_encryption_layer.cpp
@@ -1205,9 +1203,6 @@ endif()
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)
if (MSVC)
target_link_libraries(core PRIVATE getopt)
endif()
if (BOOST_NO_HEADERS)
target_link_libraries(core PUBLIC Boost::container Boost::heap Boost::asio Boost::process Boost::crc)
+45 -23
View File
@@ -110,8 +110,13 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
struct System::Impl {
explicit Impl(System& system)
: kernel{system}, fs_controller{system}, hid_core{kernel}, cpu_manager{system},
reporter{system}, applet_manager{system}, frontend_applets{system}, profile_manager{} {}
: kernel{system}
, fs_controller{system}
, hid_core{kernel}
, cpu_manager{system}
, reporter{system}
, profile_manager{}
{}
u64 program_id;
@@ -124,6 +129,12 @@ struct System::Impl {
core_timing.SetMulticore(is_multicore);
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.
if (virtual_filesystem == nullptr) {
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
@@ -133,7 +144,7 @@ struct System::Impl {
}
// 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();
@@ -376,7 +387,7 @@ struct System::Impl {
// Register with applet manager
// 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_current_game) {
@@ -428,13 +439,27 @@ struct System::Impl {
core_timing.SyncPause(false);
Network::CancelPendingSocketOperations();
kernel.SuspendEmulation(true);
kernel.CloseServices();
kernel.ShutdownCores();
// Notify services helpers of shutdown
audio_core->Shutdown();
// Wait for threads/services to join
kernel.CloseServices();
// service shutdown
services.reset();
service_manager.reset();
frontend_applets.reset();
applet_manager.reset();
apm_controller.reset();
arp_manager.reset();
profile_manager.reset();
fs_controller.Reset();
cheat_engine.reset();
core_timing.ClearPendingEvents();
core_timing.Reset();
app_loader.reset();
audio_core.reset();
gpu_core.reset();
@@ -451,9 +476,6 @@ struct System::Impl {
room_member->SendGameInfo(game_info);
}
// Reset all glue registrations
arp_manager.ResetAll();
LOG_DEBUG(Core, "Shutdown OK");
}
@@ -482,13 +504,13 @@ struct System::Impl {
CpuManager cpu_manager;
Reporter reporter;
/// Applets
Service::AM::AppletManager applet_manager;
Service::AM::Frontend::FrontendAppletHolder frontend_applets;
std::optional<Service::AM::AppletManager> applet_manager;
std::optional<Service::AM::Frontend::FrontendAppletHolder> frontend_applets;
/// APM (Performance) services
Service::APM::Controller apm_controller{core_timing};
std::optional<Service::APM::Controller> apm_controller;
/// Service State
Service::Glue::ARPManager arp_manager;
Service::Account::ProfileManager profile_manager;
std::optional<Service::Glue::ARPManager> arp_manager;
std::optional<Service::Account::ProfileManager> profile_manager;
/// Network instance
Network::NetworkInstance network_instance;
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) {
impl->frontend_applets.SetFrontendAppletSet(std::move(set));
impl->frontend_applets->SetFrontendAppletSet(std::move(set));
}
Service::AM::Frontend::FrontendAppletHolder& System::GetFrontendAppletHolder() {
return impl->frontend_applets;
return *impl->frontend_applets;
}
const Service::AM::Frontend::FrontendAppletHolder& System::GetFrontendAppletHolder() const {
return impl->frontend_applets;
return *impl->frontend_applets;
}
Service::AM::AppletManager& System::GetAppletManager() {
return impl->applet_manager;
return *impl->applet_manager;
}
void System::SetContentProvider(std::unique_ptr<FileSys::ContentProviderUnion> provider) {
@@ -868,27 +890,27 @@ const Reporter& System::GetReporter() const {
}
Service::Glue::ARPManager& System::GetARPManager() {
return impl->arp_manager;
return *impl->arp_manager;
}
const Service::Glue::ARPManager& System::GetARPManager() const {
return impl->arp_manager;
return *impl->arp_manager;
}
Service::APM::Controller& System::GetAPMController() {
return impl->apm_controller;
return *impl->apm_controller;
}
const Service::APM::Controller& System::GetAPMController() const {
return impl->apm_controller;
return *impl->apm_controller;
}
Service::Account::ProfileManager& System::GetProfileManager() {
return impl->profile_manager;
return *impl->profile_manager;
}
const Service::Account::ProfileManager& System::GetProfileManager() const {
return impl->profile_manager;
return *impl->profile_manager;
}
void System::SetExitLocked(bool locked) {
+2 -2
View File
@@ -304,10 +304,10 @@ std::optional<s64> CoreTiming::Advance() {
void CoreTiming::Reset() {
paused = true;
pause_event.Set();
event.Set();
if (timer_thread.joinable()) {
timer_thread.request_stop();
pause_event.Set();
event.Set();
timer_thread.join();
}
has_started = false;
@@ -72,11 +72,9 @@ void GlobalSchedulerContext::UnregisterDummyThreadForWakeup(KThread* thread) noe
void GlobalSchedulerContext::WakeupWaitingDummyThreads(KernelCore& kernel) noexcept {
ASSERT(this->IsLocked());
if (m_woken_dummy_threads.size() > 0) {
for (auto* thread : m_woken_dummy_threads)
thread->DummyThreadEndWait(kernel);
m_woken_dummy_threads.clear();
}
for (auto* thread : m_woken_dummy_threads)
thread->DummyThreadEndWait(kernel);
m_woken_dummy_threads.clear();
}
} // namespace Kernel
-1
View File
@@ -59,7 +59,6 @@ Result TerminateChildren(KernelCore& kernel, KProcess* process, const KThread* t
KThread* cur_child = nullptr;
{
KScopedLightLock proc_lk(process->GetListLock());
auto& thread_list = process->GetThreadList();
for (auto it = thread_list.begin(); it != thread_list.end(); ++it) {
if (KThread* thread = std::addressof(*it); thread != thread_to_not_terminate) {
+4 -10
View File
@@ -1236,18 +1236,13 @@ namespace Kernel {
KScopedSchedulerLock sl{kernel};
// Determine if this is the first termination request.
const bool first_request = [&]() -> bool {
// Perform an atomic compare-and-swap from false to true.
bool expected = false;
return m_termination_requested.compare_exchange_strong(expected, true);
}();
// Perform an atomic compare-and-swap from false to true.
bool expected = false;
// 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 (this->GetState() == ThreadState::Initialized) {
m_thread_state = ThreadState::Terminated;
return ThreadState::Terminated;
return m_thread_state = ThreadState::Terminated;
}
// Register the terminating dpc.
@@ -1281,7 +1276,6 @@ namespace Kernel {
m_wait_queue->CancelWait(kernel, this, ResultTerminationRequested, true);
}
}
return this->GetState();
}
+34 -5
View File
@@ -4,7 +4,9 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <thread>
#include "common/assert.h"
#include "common/thread.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_thread.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) {
ASSERT(type <= WorkerType::Count);
@@ -35,10 +63,11 @@ void KWorkerTaskManager::AddTask(KernelCore& kernel, WorkerType type, KWorkerTas
void KWorkerTaskManager::AddTask(KernelCore& kernel, KWorkerTask* task) {
KScopedSchedulerLock sl(kernel);
m_waiting_thread.QueueWork([&kernel, task]() {
// Do the task.
task->DoWorkerTask(kernel);
});
{
std::scoped_lock lk{m_task_mutex};
m_task_queue.emplace_back(task);
}
m_task_cv.notify_one();
}
} // namespace Kernel
+11 -4
View File
@@ -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-License-Identifier: GPL-2.0-or-later
#pragma once
#include <condition_variable>
#include <thread>
#include "common/common_types.h"
#include "common/thread_worker.h"
@@ -18,15 +23,17 @@ public:
Count,
};
KWorkerTaskManager();
KWorkerTaskManager(KernelCore& kernel);
~KWorkerTaskManager();
static void AddTask(KernelCore& kernel, WorkerType type, KWorkerTask* task);
private:
void AddTask(KernelCore& kernel, KWorkerTask* task);
std::jthread m_waiting_thread;
private:
Common::ThreadWorker m_waiting_thread;
std::mutex m_task_mutex;
std::condition_variable_any m_task_cv;
std::vector<KWorkerTask*> m_task_queue;
};
} // namespace Kernel
+17 -12
View File
@@ -80,7 +80,10 @@ struct KernelCore::Impl {
// so it will be statically given a TLS slot anyways.
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;
}
@@ -967,12 +970,16 @@ Kernel::KHardwareTimer& KernelCore::HardwareTimer() {
return *impl->hardware_timer;
}
KAutoObjectWithListContainer& KernelCore::ObjectListContainer() {
return *impl->global_object_list_container;
KAutoObjectWithListContainer* KernelCore::ObjectListContainer() {
if (!impl->global_object_list_container)
return nullptr;
return std::addressof(*impl->global_object_list_container);
}
const KAutoObjectWithListContainer& KernelCore::ObjectListContainer() const {
return *impl->global_object_list_container;
const KAutoObjectWithListContainer* KernelCore::ObjectListContainer() const {
if (!impl->global_object_list_container)
return nullptr;
return std::addressof(*impl->global_object_list_container);
}
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) {
auto* manager = server_manager.get();
{
if (!impl->is_shutting_down) {
std::scoped_lock lk{impl->server_lock};
if (impl->is_shutting_down) {
return;
}
impl->server_managers.emplace_back(std::move(server_manager));
}
manager->LoopProcess();
}
@@ -1255,6 +1256,10 @@ void KernelCore::SuspendEmulation(bool suspended) {
}
void KernelCore::ShutdownCores() {
// Notify shutdown (pre-emptively)
for (auto& sm : impl->server_managers) {
sm->NotifyShutdown();
}
impl->TerminateAllProcesses();
KScopedSchedulerLock lk{*this};
for (auto* thread : impl->shutdown_threads)
+2 -3
View File
@@ -175,9 +175,8 @@ public:
/// Stops execution of 'id' core, in order to reschedule a new thread.
void PrepareReschedule(std::size_t id);
KAutoObjectWithListContainer& ObjectListContainer();
const KAutoObjectWithListContainer& ObjectListContainer() const;
KAutoObjectWithListContainer* ObjectListContainer();
const KAutoObjectWithListContainer* ObjectListContainer() const;
/// Registers all kernel objects with the global emulation state, this is purely for tracking
/// leaks after emulation has been shutdown.
+4 -3
View File
@@ -151,7 +151,8 @@ public:
const bool is_initialized = this->IsInitialized();
uintptr_t arg = 0;
if (is_initialized) {
kernel.ObjectListContainer().Unregister(this);
if (auto const olc = kernel.ObjectListContainer(); olc)
olc->Unregister(this);
arg = this->GetPostDestroyArgument();
this->Finalize(kernel);
}
@@ -175,7 +176,7 @@ public:
public:
static void InitializeSlabHeap(KernelCore& kernel, void* memory, size_t memory_size) {
kernel.SlabHeap<Derived>().Initialize(memory, memory_size);
kernel.ObjectListContainer().Initialize();
kernel.ObjectListContainer()->Initialize();
}
static Derived* Create(KernelCore& kernel) {
@@ -186,7 +187,7 @@ public:
}
static void Register(KernelCore& kernel, Derived* obj) {
return kernel.ObjectListContainer().Register(obj);
return kernel.ObjectListContainer()->Register(obj);
}
static size_t GetObjectSize(KernelCore& kernel) {
+2 -6
View File
@@ -15,13 +15,9 @@ void LoopProcess(Core::System& system) {
WindowSystem window_system(system);
ButtonPoller button_poller(system, window_system);
EventObserver event_observer(system, window_system);
auto server_manager = std::make_unique<ServerManager>(system);
server_manager->RegisterNamedService(
"appletAE", std::make_shared<IAllSystemAppletProxiesService>(system, window_system));
server_manager->RegisterNamedService(
"appletOE", std::make_shared<IApplicationProxyService>(system, window_system));
server_manager->RegisterNamedService("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));
}
+9 -8
View File
@@ -26,24 +26,25 @@ EventObserver::EventObserver(Core::System& system, WindowSystem& window_system)
m_window_system.SetEventObserver(this);
m_wakeup_holder.SetUserData(static_cast<uintptr_t>(UserDataTag::WakeupEvent));
m_wakeup_holder.LinkToMultiWait(std::addressof(m_multi_wait));
m_thread = std::jthread([this](std::stop_token stop_token) {
Common::SetCurrentThreadName("am:EventObserver");
system.Kernel().RunOnGuestCoreProcess("am:EventObserver", [this]() {
auto const stop_token = m_stop_source.get_token();
while (!stop_token.stop_requested()) {
auto* signaled_holder = this->WaitSignaled(stop_token);
if (!signaled_holder)
if (stop_token.stop_requested() || !signaled_holder)
break;
this->Process(signaled_holder);
}
m_process_cv.notify_one();
});
}
EventObserver::~EventObserver() {
// Signal thread and wait for processing to finish.
if (m_thread.joinable()) {
// Signal thread and wait for processing to finish.
m_thread.request_stop();
m_wakeup_event.Signal(m_system.Kernel());
m_thread.join();
m_stop_source.request_stop();
m_wakeup_event.Signal(m_system.Kernel());
{
std::unique_lock lk{m_process_mutex};
m_process_cv.wait(lk);
}
// Free remaining owned sessions.
+5 -1
View File
@@ -6,6 +6,8 @@
#pragma once
#include <condition_variable>
#include <stop_token>
#include "common/polyfill_thread.h"
#include "common/thread.h"
#include "core/hle/service/kernel_helpers.h"
@@ -62,7 +64,9 @@ private:
MultiWait m_deferred_wait_list;
// 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
+4 -5
View File
@@ -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-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -90,10 +90,9 @@ static Service::PSC::Time::LocationName GetTimeZoneString(
}
TimeManager::TimeManager(Core::System& system)
: m_steady_clock_resource{system}, m_time_zone_binary{system}, m_worker{
system,
m_steady_clock_resource,
m_file_timestamp_worker} {
: m_steady_clock_resource{system}, m_time_zone_binary{system}
, m_worker{system, m_steady_clock_resource, m_file_timestamp_worker}
{
m_time_m =
system.ServiceManager().GetService<Service::PSC::Time::ServiceManager>("time:m", true);
+151 -151
View File
@@ -20,15 +20,17 @@
namespace Service::Glue::Time {
TimeWorker::TimeWorker(Core::System& system, StandardSteadyClockResource& steady_clock_resource,
FileTimestampWorker& file_timestamp_worker)
: m_system{system}, m_ctx{m_system, "Glue:TimeWorker"}, m_event{m_ctx.CreateEvent(
"Glue:TimeWorker:Event")},
m_steady_clock_resource{steady_clock_resource},
m_file_timestamp_worker{file_timestamp_worker}, m_timer_steady_clock{m_ctx.CreateEvent(
"Glue:TimeWorker:SteadyClockTimerEvent")},
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} {
TimeWorker::TimeWorker(Core::System& system, StandardSteadyClockResource& steady_clock_resource, FileTimestampWorker& file_timestamp_worker)
: m_system{system}
, m_ctx{m_system, "Glue:TimeWorker"}
, m_event{m_ctx.CreateEvent("Glue:TimeWorker:Event")}
, m_steady_clock_resource{steady_clock_resource}
, m_file_timestamp_worker{file_timestamp_worker}
, m_timer_steady_clock{m_ctx.CreateEvent("Glue:TimeWorker:SteadyClockTimerEvent")}
, 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_timer_steady_clock_timing_event = Core::Timing::CreateEvent(
"Time::SteadyClockEvent",
[this](s64 time,
@@ -47,14 +49,16 @@ TimeWorker::TimeWorker(Core::System& system, StandardSteadyClockResource& steady
}
TimeWorker::~TimeWorker() {
// Wait for processing to stop
m_stop_source.request_stop();
m_local_clock_event->Signal(m_system.Kernel());
m_network_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_thread.join();
{
std::unique_lock lk{m_process_mutex};
m_process_cv.wait(lk);
}
m_ctx.CloseEvent(m_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() {
m_thread = std::jthread(std::bind_front(&TimeWorker::ThreadFunc, this));
}
void TimeWorker::ThreadFunc(std::stop_token stop_token) {
Common::SetCurrentThreadName("TimeWorker");
Common::SetCurrentThreadPriority(Common::ThreadPriority::Low);
while (!stop_token.stop_requested()) {
enum class EventType : s32 {
Exit = 0,
PowerStateChange = 1,
SignalAlarms = 2,
UpdateLocalSystemClock = 3,
UpdateNetworkSystemClock = 4,
UpdateEphemeralSystemClock = 5,
UpdateSteadyClock = 6,
UpdateFileTimestamp = 7,
AutoCorrect = 8,
};
s32 index{};
if (m_pm_state_change_handler.m_priority != 0) {
// TODO: gIPmModuleService::GetEvent() 1
index = WaitAny(m_system.Kernel(),
&m_event->GetReadableEvent(), // 0
&m_alarm_worker.GetEvent() // 1
);
} else {
// TODO: gIPmModuleService::GetEvent() 1
index = WaitAny(m_system.Kernel(),
&m_event->GetReadableEvent(), // 0
&m_alarm_worker.GetEvent(), // 1
&m_alarm_worker.GetTimerEvent().GetReadableEvent(), // 2
m_local_clock_event, // 3
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();
m_system.Kernel().RunOnGuestCoreProcess("TimeWorker", [this]() {
auto const stop_token = m_stop_source.get_token();
while (!stop_token.stop_requested()) {
enum class EventType : s32 {
Exit = 0,
PowerStateChange = 1,
SignalAlarms = 2,
UpdateLocalSystemClock = 3,
UpdateNetworkSystemClock = 4,
UpdateEphemeralSystemClock = 5,
UpdateSteadyClock = 6,
UpdateFileTimestamp = 7,
AutoCorrect = 8,
};
s32 index{};
if (m_pm_state_change_handler.m_priority != 0) {
// TODO: gIPmModuleService::GetEvent() 1
index = WaitAny(
m_system.Kernel(),
&m_event->GetReadableEvent(), // 0
&m_alarm_worker.GetEvent() // 1
);
} else {
// TODO: gIPmModuleService::GetEvent() 1
index = WaitAny(
m_system.Kernel(),
&m_event->GetReadableEvent(), // 0
&m_alarm_worker.GetEvent(), // 1
&m_alarm_worker.GetTimerEvent().GetReadableEvent(), // 2
m_local_clock_event, // 3
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
);
}
break;
case EventType::SignalAlarms:
m_alarm_worker.GetTimerEvent().Clear(m_system.Kernel());
m_time_m->CheckAndSignalAlarms();
break;
if (stop_token.stop_requested())
break;
case EventType::UpdateLocalSystemClock: {
m_local_clock_event->Clear(m_system.Kernel());
switch (EventType(index)) {
case EventType::Exit:
return;
Service::PSC::Time::SystemClockContext context{};
R_ASSERT(m_local_clock->GetSystemClockContext(&context));
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;
m_set_sys->SetUserSystemClockContext(context);
m_file_timestamp_worker.SetFilesystemPosixTime();
break;
}
case EventType::SignalAlarms:
m_alarm_worker.GetTimerEvent().Clear(m_system.Kernel());
m_time_m->CheckAndSignalAlarms();
break;
case EventType::UpdateNetworkSystemClock: {
m_network_clock_event->Clear(m_system.Kernel());
case EventType::UpdateLocalSystemClock: {
m_local_clock_event->Clear(m_system.Kernel());
Service::PSC::Time::SystemClockContext context{};
R_ASSERT(m_network_clock->GetSystemClockContext(&context));
Service::PSC::Time::SystemClockContext context{};
R_ASSERT(m_local_clock->GetSystemClockContext(&context));
m_set_sys->SetNetworkSystemClockContext(context);
s64 time{};
if (m_network_clock->GetCurrentTime(&time) != ResultSuccess) {
m_set_sys->SetUserSystemClockContext(context);
m_file_timestamp_worker.SetFilesystemPosixTime();
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;
}
case EventType::UpdateNetworkSystemClock: {
m_network_clock_event->Clear(m_system.Kernel());
m_file_timestamp_worker.SetFilesystemPosixTime();
break;
}
Service::PSC::Time::SystemClockContext context{};
R_ASSERT(m_network_clock->GetSystemClockContext(&context));
case EventType::UpdateEphemeralSystemClock: {
m_ephemeral_clock_event->Clear(m_system.Kernel());
m_set_sys->SetNetworkSystemClockContext(context);
Service::PSC::Time::SystemClockContext context{};
auto res = m_ephemeral_clock->GetSystemClockContext(&context);
if (res != ResultSuccess) {
s64 time{};
if (m_network_clock->GetCurrentTime(&time) != 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;
}
s64 time{};
res = m_ephemeral_clock->GetCurrentTime(&time);
if (res != ResultSuccess) {
case EventType::UpdateEphemeralSystemClock: {
m_ephemeral_clock_event->Clear(m_system.Kernel());
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;
}
[[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;
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();
}
break;
}
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();
}
}
m_process_cv.notify_one();
});
}
} // namespace Service::Glue::Time
+8 -4
View File
@@ -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-License-Identifier: GPL-2.0-or-later
#pragma once
#include <condition_variable>
#include "common/common_types.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/glue/time/alarm_worker.h"
@@ -33,17 +37,17 @@ public:
void StartThread();
private:
template <typename T>
T GetSettingsItemValue(const std::string& category, const std::string& name);
void ThreadFunc(std::stop_token stop_token);
Core::System& m_system;
KernelHelpers::ServiceContext m_ctx;
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{};
std::shared_ptr<Service::PSC::Time::ServiceManager> m_time_m;
std::shared_ptr<Service::PSC::Time::StaticService> m_time_sm;
+12 -3
View File
@@ -98,8 +98,10 @@ ServerManager::~ServerManager() {
// Signal stop.
m_stop_source.request_stop();
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_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() {
SCOPE_EXIT {
m_stopped.Set();
@@ -285,9 +295,8 @@ MultiWaitHolder* ServerManager::WaitSignaled() {
this->LinkDeferred();
// If we're done, return before we start waiting.
if (m_stop_source.stop_requested()) {
if (m_stop_source.stop_requested())
return nullptr;
}
auto* selected = m_multi_wait.WaitAny(m_system.Kernel());
if (selected == std::addressof(*m_wakeup_holder)) {
+4
View File
@@ -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-License-Identifier: GPL-2.0-or-later
@@ -49,6 +52,7 @@ public:
Result LoopProcess();
void StartAdditionalHostThreads(const char* name, size_t num_threads);
void NotifyShutdown();
static void RunServer(std::unique_ptr<ServerManager>&& server);
+1 -1
View File
@@ -117,7 +117,6 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
{"friends", &Friend::LoopProcess},
{"settings", &Set::LoopProcess},
{"psc", &PSC::LoopProcess},
{"glue", &Glue::LoopProcess},
{"grc", &GRC::LoopProcess},
{"hid", &HID::LoopProcess},
{"jit", &JIT::LoopProcess},
@@ -153,6 +152,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
{"usb", &USB::LoopProcess},
{"i2c", &I2C::LoopProcess},
{"gpio", &GPIO::LoopProcess},
{"glue", &Glue::LoopProcess},
})
kernel.RunOnGuestCoreProcess(std::string(e.first), [&system, f = e.second] { f(system); });
}
+4 -6
View File
@@ -305,16 +305,14 @@ SM::SM(ServiceManager& service_manager_, Core::System& system_)
SM::~SM() = default;
void LoopProcess(Core::System& system) {
auto& service_manager = system.ServiceManager();
auto server_manager = std::make_unique<ServerManager>(system);
Kernel::KEvent* 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);
server_manager->ManageNamedPort("sm:", [sm_service] { return sm_service; });
server_manager->ManageNamedPort("sm:", [sm_service] {
return sm_service;
});
ServerManager::RunServer(std::move(server_manager));
}
+23 -33
View File
@@ -11,6 +11,7 @@
#include "common/thread.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/vi/conductor.h"
#include "core/hle/service/vi/container.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()) {
m_event = Core::Timing::CreateEvent(
"ScreenComposition",
[this](s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
m_signal.Set();
return std::chrono::nanoseconds(this->GetNextTicks());
});
m_event = Core::Timing::CreateEvent("ScreenComposition", [this](s64 time, 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);
m_thread = std::jthread([this](std::stop_token token) { this->VsyncThread(token); });
} else {
m_event = Core::Timing::CreateEvent(
"ScreenComposition",
[this](s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
m_thread = system.Kernel().RunOnHostCoreThread("VSyncThread", [this]() {
auto const stop_token = m_thread.get_stop_token();
Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
Common::SetCurrentThreadToPerformanceCores();
while (!stop_token.stop_requested()) {
m_signal.Wait();
if (stop_token.stop_requested() || m_system.IsShuttingDown()) {
break;
}
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);
}
@@ -54,10 +60,10 @@ Conductor::Conductor(Core::System& system, Container& container, DisplayList& di
Conductor::~Conductor() {
m_system.CoreTiming().UnscheduleEvent(m_event);
if (m_system.IsMulticore()) {
if (m_thread.joinable()) {
m_thread.request_stop();
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 {
const auto& settings = Settings::values;
auto speed_scale = 1.f;
-205
View File
@@ -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
);
}
}
}
-43
View File
@@ -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;
}
+14
View File
@@ -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-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/scope_exit.h"
#include "core/memory/dmnt_cheat_types.h"
@@ -1266,3 +1275,8 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
}
} // namespace Core::Memory
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
+11 -9
View File
@@ -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-FileCopyrightText: Copyright 2019 yuzu Emulator Project
@@ -254,14 +254,16 @@ struct UnrecognizedInstruction {
struct CheatVmOpcode {
bool begin_conditional_block{};
std::variant<StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode,
LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode,
PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, PauseProcessOpcode,
ResumeProcessOpcode, DebugLogOpcode, UnrecognizedInstruction>
opcode{};
std::variant<
std::monostate,
StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode,
LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode,
PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, PauseProcessOpcode,
ResumeProcessOpcode, DebugLogOpcode, UnrecognizedInstruction
> opcode{};
};
class DmntCheatVm {
@@ -170,13 +170,19 @@ template<>
// check for marked bit, use as unmapped if marked
if (ctx.conf.page_table_marked_bit) {
code.bt(page, *ctx.conf.page_table_marked_bit);
code.jc(abort, code.T_NEAR);
auto const marked_bit = *ctx.conf.page_table_marked_bit;
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
if (ctx.conf.page_table_pointer_mask == 0) {
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);
} else {
code.mov(tmp, ctx.conf.page_table_pointer_mask);
@@ -79,8 +79,10 @@ void NpadAbstractButtonHandler::UpdateAllButtonLifo() {
Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
for (std::size_t i = 0; i < AruidIndexMax; i++) {
auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i);
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
UpdateButtonLifo(npad_entry, data->aruid);
if (auto const shfmt = data->shared_memory_format; shfmt) {
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();
for (std::size_t i = 0; i < AruidIndexMax; i++) {
auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i);
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
UpdateButtonLifo(npad_entry, data->aruid);
if (auto const shfmt = data->shared_memory_format; shfmt) {
auto& npad_entry = shfmt->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
UpdateButtonLifo(npad_entry, data->aruid);
}
}
}
void NpadAbstractButtonHandler::UpdateButtonState(u64 aruid) {
Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
auto* data = applet_resource_holder->applet_resource->GetAruidData(aruid);
if (data == nullptr) {
return;
if (auto data = applet_resource_holder->applet_resource->GetAruidData(aruid); data) {
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
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) {
+5 -2
View File
@@ -66,14 +66,17 @@ void EmuThread::run() {
} else {
QtCommon::system->Pause();
m_stopped.Set();
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);
}
}
// 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->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-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...>>;
};
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>
auto Arg(EmitContext& ctx, const IR::Value& arg) {
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>
void Invoke(EmitContext& ctx, IR::Inst* inst, std::index_sequence<I...>) {
using Traits = FuncTraits<decltype(func)>;
if constexpr (is_first_arg_inst) {
func(ctx, *inst, Arg<typename Traits::template ArgType<I + 2>>(ctx, inst->Arg(I))...);
if constexpr (std::is_same_v<typename Traits::ReturnType, Id>) {
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 {
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) {
// Flatten all leading PHIs from each block into a vector
std::vector<IR::Inst*> phi_instructions;
for (IR::Block* block : program.blocks) {
for (auto it = block->begin(); it != block->end(); ++it) {
if (it->GetOpcode() != IR::Opcode::Phi)
break;
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 };
});
// Flatten all leading PHIs from each block into a vector
std::vector<IR::Inst*> phi_instructions;
for (IR::Block* block : program.blocks) {
for (auto it = block->begin(); it != block->end(); ++it) {
if (it->GetOpcode() != IR::Opcode::Phi)
break;
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 = 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
std::vector<u32> EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_info, IR::Program& program, Bindings& bindings) {
+8 -15
View File
@@ -121,7 +121,7 @@ void BufferCache<P>::UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t size
template <class P>
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);
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);
break;
case ObtainBufferOperation::DiscardWrite: {
ClearDownload(device_addr, size);
gpu_modified_ranges.Subtract(device_addr, size);
const DAddr device_addr_start = Common::AlignDown(device_addr, 64);
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;
}
default:
@@ -1739,24 +1742,14 @@ bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, DAddr device_addr, u32 si
u64 total_size_bytes = 0;
u64 largest_copy = 0;
const DAddr buffer_start = buffer.cpu_addr_cached;
const auto add_upload = [&](DAddr start, DAddr end) {
if (start == end) return;
const u64 range_size = end - start;
memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) {
upload_copies.push_back(BufferCopy{
.src_offset = total_size_bytes,
.dst_offset = start - buffer_start,
.dst_offset = device_addr_out - buffer_start,
.size = range_size,
});
total_size_bytes += 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) {
return true;
+5 -10
View File
@@ -16,7 +16,7 @@
namespace Tegra {
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_)
: system{system_}
@@ -73,16 +73,11 @@ bool DmaPusher::Step() {
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 (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();
if (use_safe) {
Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader, Tegra::Memory::GuestMemoryFlags::SafeRead>headers(memory_manager, dma_state.dma_get, header.size, &command_headers);
+4 -11
View File
@@ -49,16 +49,13 @@ void KeplerCompute::CallMethod(Core::System& system, u32 method, u32 method_argu
case KEPLER_COMPUTE_REG_INDEX(exec_upload): {
UploadInfo info{.upload_address = upload_address,
.exec_address = upload_state.ExecTargetAddress(),
.copy_size = upload_state.GetUploadSize(),
.was_dirty = upload_dirty};
.copy_size = upload_state.GetUploadSize()};
uploads.push_back(info);
upload_state.ProcessExec(regs.exec_upload.linear != 0);
break;
}
case KEPLER_COMPUTE_REG_INDEX(data_upload): {
upload_address = current_dma_segment;
upload_dirty = current_dirty;
current_dirty = false;
upload_state.ProcessData(method_argument, is_last_call);
break;
}
@@ -67,11 +64,9 @@ void KeplerCompute::CallMethod(Core::System& system, u32 method, u32 method_argu
for (auto& data : uploads) {
const GPUVAddr offset = data.exec_address - launch_desc_loc;
if (offset / sizeof(u32) == LAUNCH_REG_INDEX(grid_dim_x)) {
const bool source_dirty = memory_manager.IsMemoryDirty(data.upload_address, data.copy_size);
if (data.was_dirty || source_dirty) {
indirect_compute = {data.upload_address};
}
if (offset / sizeof(u32) == LAUNCH_REG_INDEX(grid_dim_x) &&
memory_manager.IsMemoryDirty(data.upload_address, data.copy_size)) {
indirect_compute = {data.upload_address};
}
}
uploads.clear();
@@ -88,8 +83,6 @@ void KeplerCompute::CallMultiMethod(Core::System& system, u32 method, const u32*
switch (method) {
case KEPLER_COMPUTE_REG_INDEX(data_upload):
upload_address = current_dma_segment;
upload_dirty = current_dirty;
current_dirty = false;
upload_state.ProcessData(base_start, amount);
return;
default:
-2
View File
@@ -226,13 +226,11 @@ private:
VideoCore::RasterizerInterface* rasterizer = nullptr;
Upload::State upload_state;
GPUVAddr upload_address;
bool upload_dirty{};
struct UploadInfo {
GPUVAddr upload_address;
GPUVAddr exec_address;
u32 copy_size;
bool was_dirty;
};
std::vector<UploadInfo> uploads;
std::optional<GPUVAddr> indirect_compute{};
+68 -18
View File
@@ -3,7 +3,6 @@
// Static Qt on macOS doesn't use Vulkan
// Other platforms do, and thus have conflicting VulkanMemoryAllocator symbols
#include "core/launch_params.h"
#if defined(QT_STATICPLUGIN) && !defined(__APPLE__)
#undef VMA_IMPLEMENTATION
#endif
@@ -516,28 +515,79 @@ MainWindow::MainWindow(bool has_broken_vulkan)
// to prevent the UI from blowing up.
UpdateUITheme();
if (QStringList args = QApplication::arguments(); args.size() >= 2) {
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());
QStringList args = QApplication::arguments();
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
if (!lp.filepath.empty() || lp.fullscreen) {
ui->action_Fullscreen->setChecked(lp.fullscreen);
QString game_path;
bool should_launch_qlaunch = false;
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()) {
BootGame(QString::fromStdString(lp.filepath), ApplicationAppletParameters());
} else if (lp.launch_setup) {
LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Starter), std::nullopt);
} else if (lp.launch_qlaunch) {
// Override fullscreen setting if gamepath or argument is provided
if (has_gamepath || is_fullscreen) {
ui->action_Fullscreen->setChecked(is_fullscreen);
}
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);
} else if (lp.launch_hlaunch) {
} else if (should_launch_hlaunch) {
std::filesystem::path const sd_dir =
Common::FS::GetEdenPathString(Common::FS::EdenPath::SDMCDir);
auto const hbl_path = (sd_dir / "atmosphere" / "hbl.nsp").string();
+3
View File
@@ -42,6 +42,9 @@ target_link_libraries(yuzu-cmd PRIVATE common core input_common frontend_common
if (ENABLE_OPENGL)
target_link_libraries(yuzu-cmd PRIVATE glad)
endif()
if (MSVC)
target_link_libraries(yuzu-cmd PRIVATE getopt)
endif()
target_link_libraries(yuzu-cmd PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
create_resource("../../dist/eden.bmp" "yuzu_cmd/yuzu_icon.h" "yuzu_icon")
+190 -16
View File
@@ -10,7 +10,6 @@
#include <regex>
#include <string>
#include "common/settings_enums.h"
#include "core/launch_params.h"
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
@@ -53,6 +52,12 @@
#include "common/windows/timer_resolution.h"
#endif
#undef _UNICODE
#include <getopt.h>
#ifndef _MSC_VER
#include <unistd.h>
#endif
#ifdef _WIN32
extern "C" {
// 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
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) {
switch (state) {
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");
return SDL_APP_FAILURE;
}
#else
wchar_t **argv_w = nullptr;
#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
LocalFree(argv_w);
#endif
SdlConfig config{lp.config_path};
Core::ApplyLaunchParams(lp);
if (lp.filepath.empty()) {
if (filepath.empty()) {
LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified");
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_GLASM:
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;
#endif
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;
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;
default:
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{
.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) {
case Core::SystemResultStatus::Success:
break; // Expected case
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;
case Core::SystemResultStatus::ErrorLoader:
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;
}
if (lp.use_multiplayer) {
if (use_multiplayer) {
if (auto member = Network::GetRoomMember().lock()) {
member->BindOnChatMessageReceived(OnMessageReceived);
member->BindOnStatusMessageReceived(OnStatusMessageReceived);
member->BindOnStateChanged(OnStateChanged);
member->BindOnError(OnNetworkError);
LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", lp.address, lp.port, lp.nickname);
member->Join(lp.nickname, lp.address.c_str(), lp.port, 0, Network::NoPreferredIP, lp.password);
LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port, nickname);
member->Join(nickname, address.c_str(), port, 0, Network::NoPreferredIP, password);
} else {
LOG_ERROR(Network, "Could not access RoomMember");
return SDL_APP_FAILURE;
+122
View File
@@ -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"