Compare commits

..

8 commits

Author SHA1 Message Date
91493fa39b
[vk] Fast UBO: fix tracking (#2712)
Fixes or mitigates memory errors in TOTK and possibly other games as well.

Credit: Ribbit
Reviewed-on: #2712
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2025-10-11 06:34:21 +02:00
973a65c4c5
[qt_common] fix typo (#2715)
Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: #2715
2025-10-11 04:31:14 +02:00
8a017951aa
[qt_common] fix building with Qt 6.10 (#2713)
Qt old style include variables are deprecated in Qt, see <https://github.com/qt/qtbase/blob/v6.10.0/cmake/QtModuleConfig.cmake.in#L84>, and Qt 6.10 stopped exporting them after <ad7b94e163>.

Signed-off-by: Marcin Serwin <marcin@serwin.dev>

Co-authored-by: crueter <crueter@eden-emu.dev>
Reviewed-on: #2713
Co-authored-by: Marcin Serwin <marcin@serwin.dev>
Co-committed-by: Marcin Serwin <marcin@serwin.dev>
2025-10-10 22:33:15 +02:00
776958c79d
[vk] Introduce Ring Buffers for Uniform Buffer (#2698)
Create 3 ring buffers which rotates between buffers each frame to avoid GPU/CPU conflicts
BindMappedUniformBuffer first tries to allocate from the ring buffer and falls back to staging pool only if allocation is too large.
Note to testers:- please test the performance since it is primarily a performance optimization and also look for visual bugs.

Co-authored-by: wildcard <wildcard@eden-emu.dev>
Co-authored-by: MaranBr <maranbr@outlook.com>
Reviewed-on: #2698
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: Shinmegumi <shinmegumi@eden-emu.dev>
Co-committed-by: Shinmegumi <shinmegumi@eden-emu.dev>
2025-10-10 19:24:20 +02:00
3656253262
[acc] do not consider system profile as orphaned (#2708)
Profile 00000000000000000000000000000000 is apparently needed for acnh,
etc

Signed-off-by: crueter <crueter@eden-emu.dev>

Reviewed-on: #2708
2025-10-10 05:59:31 +02:00
b6241e4148
revert [vk] StreamBuffer Changes (#2684) (#2707)
revert [vk] StreamBuffer Changes (#2684)

Streambuffer changes did broke stuff in other games that got out of our scope of testing, we're going to study this changes in the future for better graphic stability.

Co-authored-by: Ribbit <ribbit@placeholder.com>
Reviewed-on: #2684
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: Ribbit <ribbit@eden-emu.dev>
Co-committed-by: Ribbit <ribbit@eden-emu.dev>
Reviewed-on: #2707
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: CamilleLaVey <camillelavey99@gmail.com>
Co-committed-by: CamilleLaVey <camillelavey99@gmail.com>
2025-10-10 01:55:43 +02:00
bfffafe68b
[common] Change web offline applet default setting to HLE (#2705)
This prevents some games from ignoring the disable web applet setting.

Reviewed-on: #2705
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2025-10-10 01:36:55 +02:00
3c6ef765af
revert [vk] Fast UBO: fix tracking, resize heuristics, add debug guard (#2695) (#2706)
revert [vk] Fast UBO: fix tracking, resize heuristics, add debug guard (#2695)

Well, stuff showed up after testing phase, that showed us this change break SMO and some mods after being merged directly into master, we will keep stuying why happens this and add a better handling later.

Co-authored-by: Ribbit <ribbit@placeholder.com>
Reviewed-on: #2695
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: Ribbit <ribbit@eden-emu.dev>
Co-committed-by: Ribbit <ribbit@eden-emu.dev>

Reviewed-on: #2706
Co-authored-by: CamilleLaVey <camillelavey99@gmail.com>
Co-committed-by: CamilleLaVey <camillelavey99@gmail.com>
2025-10-09 21:37:27 +02:00
24 changed files with 499 additions and 432 deletions

View file

@ -161,7 +161,7 @@ struct Values {
Category::LibraryApplet};
Setting<AppletMode> photo_viewer_applet_mode{
linkage, AppletMode::LLE, "photo_viewer_applet_mode", Category::LibraryApplet};
Setting<AppletMode> offline_web_applet_mode{linkage, AppletMode::LLE, "offline_web_applet_mode",
Setting<AppletMode> offline_web_applet_mode{linkage, AppletMode::HLE, "offline_web_applet_mode",
Category::LibraryApplet};
Setting<AppletMode> login_share_applet_mode{linkage, AppletMode::HLE, "login_share_applet_mode",
Category::LibraryApplet};

View file

@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 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
@ -112,9 +109,6 @@ public:
void ReadBlock(DAddr address, void* dest_pointer, size_t size);
void ReadBlockUnsafe(DAddr address, void* dest_pointer, size_t size);
#ifdef YUZU_DEBUG
bool ReadBlockFastChecked(DAddr address, void* dest_pointer, size_t size);
#endif
void WriteBlock(DAddr address, const void* src_pointer, size_t size);
void WriteBlockUnsafe(DAddr address, const void* src_pointer, size_t size);

View file

@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 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
@ -470,29 +467,6 @@ void DeviceMemoryManager<Traits>::ReadBlockUnsafe(DAddr address, void* dest_poin
});
}
#ifdef YUZU_DEBUG
template <typename Traits>
bool DeviceMemoryManager<Traits>::ReadBlockFastChecked(DAddr address, void* dest_pointer,
size_t size) {
bool success = true;
WalkBlock(
address, size,
[&](size_t copy_amount, DAddr current_vaddr) {
LOG_CRITICAL(Render, "DeviceMemory OOB/unmapped: addr=0x{:x} size={}", current_vaddr,
size);
std::memset(dest_pointer, 0, copy_amount);
success = false;
},
[&](size_t copy_amount, const u8* const src_ptr) {
std::memcpy(dest_pointer, src_ptr, copy_amount);
},
[&](const std::size_t copy_amount) {
dest_pointer = static_cast<u8*>(dest_pointer) + copy_amount;
});
return success;
}
#endif
template <typename Traits>
void DeviceMemoryManager<Traits>::WriteBlockUnsafe(DAddr address, const void* src_pointer,
size_t size) {

View file

@ -509,6 +509,9 @@ std::vector<std::string> ProfileManager::FindOrphanedProfiles()
good_uuids.emplace_back(uuid_string);
}
// used for acnh, etc
good_uuids.emplace_back("00000000000000000000000000000000");
// TODO: fetch save_id programmatically
const auto path = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir)
/ "user/save/0000000000000000";

View file

@ -19,6 +19,9 @@
namespace Core {
// Time between room is announced to web_service
static constexpr std::chrono::seconds announce_time_interval(15);
AnnounceMultiplayerSession::AnnounceMultiplayerSession() {
#ifdef ENABLE_WEB_SERVICE
backend = std::make_unique<WebService::RoomJson>(Settings::values.web_api_url.GetValue(),
@ -50,58 +53,18 @@ WebService::WebResult AnnounceMultiplayerSession::Register() {
}
void AnnounceMultiplayerSession::Start() {
if (announce_multiplayer_thread.has_value()) {
if (announce_multiplayer_thread) {
Stop();
}
announce_multiplayer_thread.emplace([&](std::stop_token stoken) {
// Invokes all current bound error callbacks.
const auto ErrorCallback = [this](WebService::WebResult result) {
std::lock_guard lock(callback_mutex);
for (auto callback : error_callbacks)
(*callback)(result);
};
if (!registered) {
WebService::WebResult result = Register();
if (result.result_code != WebService::WebResult::Code::Success) {
ErrorCallback(result);
return;
}
}
// Time between room is announced to web_service
std::chrono::seconds const announce_timeslice(15);
auto update_time = std::chrono::steady_clock::now();
std::future<WebService::WebResult> future;
while (!shutdown_event.WaitUntil(update_time)) {
update_time = std::chrono::steady_clock::now() + announce_timeslice;
auto room = Network::GetRoom().lock();
if (!room) {
break;
}
if (room->GetState() != Network::Room::State::Open) {
break;
}
UpdateBackendData(room);
WebService::WebResult result = backend->Update();
if (result.result_code != WebService::WebResult::Code::Success) {
ErrorCallback(result);
}
if (result.result_string == "404") {
registered = false;
// Needs to register the room again
WebService::WebResult register_result = Register();
if (register_result.result_code != WebService::WebResult::Code::Success) {
ErrorCallback(register_result);
}
}
}
});
shutdown_event.Reset();
announce_multiplayer_thread =
std::make_unique<std::thread>(&AnnounceMultiplayerSession::AnnounceMultiplayerLoop, this);
}
void AnnounceMultiplayerSession::Stop() {
if (announce_multiplayer_thread.has_value()) {
if (announce_multiplayer_thread) {
shutdown_event.Set();
announce_multiplayer_thread->join();
announce_multiplayer_thread.reset();
backend->Delete();
registered = false;
@ -138,10 +101,58 @@ void AnnounceMultiplayerSession::UpdateBackendData(std::shared_ptr<Network::Room
}
}
void AnnounceMultiplayerSession::AnnounceMultiplayerLoop() {
// Invokes all current bound error callbacks.
const auto ErrorCallback = [this](WebService::WebResult result) {
std::lock_guard lock(callback_mutex);
for (auto callback : error_callbacks) {
(*callback)(result);
}
};
if (!registered) {
WebService::WebResult result = Register();
if (result.result_code != WebService::WebResult::Code::Success) {
ErrorCallback(result);
return;
}
}
auto update_time = std::chrono::steady_clock::now();
std::future<WebService::WebResult> future;
while (!shutdown_event.WaitUntil(update_time)) {
update_time += announce_time_interval;
auto room = Network::GetRoom().lock();
if (!room) {
break;
}
if (room->GetState() != Network::Room::State::Open) {
break;
}
UpdateBackendData(room);
WebService::WebResult result = backend->Update();
if (result.result_code != WebService::WebResult::Code::Success) {
ErrorCallback(result);
}
if (result.result_string == "404") {
registered = false;
// Needs to register the room again
WebService::WebResult register_result = Register();
if (register_result.result_code != WebService::WebResult::Code::Success) {
ErrorCallback(register_result);
}
}
}
}
AnnounceMultiplayerRoom::RoomList AnnounceMultiplayerSession::GetRoomList() {
return backend->GetRoomList();
}
bool AnnounceMultiplayerSession::IsRunning() const {
return announce_multiplayer_thread != nullptr;
}
void AnnounceMultiplayerSession::UpdateCredentials() {
ASSERT_MSG(!IsRunning(), "Credentials can only be updated when session is not running");
#ifdef ENABLE_WEB_SERVICE

View file

@ -72,9 +72,7 @@ public:
/**
* Whether the announce session is still running
*/
[[nodiscard]] bool IsRunning() const {
return announce_multiplayer_thread.has_value();
}
bool IsRunning() const;
/**
* Recreates the backend, updating the credentials.
@ -84,13 +82,16 @@ public:
private:
void UpdateBackendData(std::shared_ptr<Network::Room> room);
void AnnounceMultiplayerLoop();
Common::Event shutdown_event;
std::mutex callback_mutex;
std::set<CallbackHandle> error_callbacks;
std::optional<std::jthread> announce_multiplayer_thread;
std::unique_ptr<std::thread> announce_multiplayer_thread;
/// Backend interface that logs fields
std::unique_ptr<AnnounceMultiplayerRoom::Backend> backend;
std::mutex callback_mutex;
std::atomic_bool registered = false; ///< Whether the room has been registered
};

View file

@ -1,5 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -12,7 +13,6 @@
#include <shared_mutex>
#include <sstream>
#include <thread>
#include "common/polyfill_thread.h"
#include "common/logging/log.h"
#include "enet/enet.h"
#include "network/packet.h"
@ -54,11 +54,13 @@ public:
RoomImpl() : random_gen(std::random_device()()) {}
/// Thread that receives and dispatches network packets
std::optional<std::jthread> room_thread;
std::unique_ptr<std::thread> room_thread;
/// Verification backend of the room
std::unique_ptr<VerifyUser::Backend> verify_backend;
/// Thread function that will receive and dispatch messages until the room is destroyed.
void ServerLoop();
void StartLoop();
/**
@ -238,57 +240,59 @@ public:
};
// RoomImpl
void Room::RoomImpl::StartLoop() {
room_thread.emplace([&](std::stop_token stoken) {
while (state != State::Closed) {
ENetEvent event;
if (enet_host_service(server, &event, 5) > 0) {
switch (event.type) {
case ENET_EVENT_TYPE_RECEIVE:
switch (event.packet->data[0]) {
case IdJoinRequest:
HandleJoinRequest(&event);
break;
case IdSetGameInfo:
HandleGameInfoPacket(&event);
break;
case IdProxyPacket:
HandleProxyPacket(&event);
break;
case IdLdnPacket:
HandleLdnPacket(&event);
break;
case IdChatMessage:
HandleChatPacket(&event);
break;
// Moderation
case IdModKick:
HandleModKickPacket(&event);
break;
case IdModBan:
HandleModBanPacket(&event);
break;
case IdModUnban:
HandleModUnbanPacket(&event);
break;
case IdModGetBanList:
HandleModGetBanListPacket(&event);
break;
}
enet_packet_destroy(event.packet);
void Room::RoomImpl::ServerLoop() {
while (state != State::Closed) {
ENetEvent event;
if (enet_host_service(server, &event, 5) > 0) {
switch (event.type) {
case ENET_EVENT_TYPE_RECEIVE:
switch (event.packet->data[0]) {
case IdJoinRequest:
HandleJoinRequest(&event);
break;
case ENET_EVENT_TYPE_DISCONNECT:
HandleClientDisconnection(event.peer);
case IdSetGameInfo:
HandleGameInfoPacket(&event);
break;
case ENET_EVENT_TYPE_NONE:
case ENET_EVENT_TYPE_CONNECT:
case IdProxyPacket:
HandleProxyPacket(&event);
break;
case IdLdnPacket:
HandleLdnPacket(&event);
break;
case IdChatMessage:
HandleChatPacket(&event);
break;
// Moderation
case IdModKick:
HandleModKickPacket(&event);
break;
case IdModBan:
HandleModBanPacket(&event);
break;
case IdModUnban:
HandleModUnbanPacket(&event);
break;
case IdModGetBanList:
HandleModGetBanListPacket(&event);
break;
}
enet_packet_destroy(event.packet);
break;
case ENET_EVENT_TYPE_DISCONNECT:
HandleClientDisconnection(event.peer);
break;
case ENET_EVENT_TYPE_NONE:
case ENET_EVENT_TYPE_CONNECT:
break;
}
}
// Close the connection to all members:
SendCloseMessage();
});
}
// Close the connection to all members:
SendCloseMessage();
}
void Room::RoomImpl::StartLoop() {
room_thread = std::make_unique<std::thread>(&Room::RoomImpl::ServerLoop, this);
}
void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
@ -1128,6 +1132,7 @@ void Room::SetVerifyUID(const std::string& uid) {
void Room::Destroy() {
room_impl->state = State::Closed;
room_impl->room_thread->join();
room_impl->room_thread.reset();
if (room_impl->server) {

View file

@ -1,5 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -9,7 +7,6 @@
#include <set>
#include <thread>
#include "common/assert.h"
#include "common/polyfill_thread.h"
#include "common/socket_types.h"
#include "enet/enet.h"
#include "network/packet.h"
@ -21,21 +18,6 @@ constexpr u32 ConnectionTimeoutMs = 5000;
class RoomMember::RoomMemberImpl {
public:
void SetState(const State new_state) noexcept {
if (state != new_state) {
state = new_state;
Invoke<State>(state);
}
}
void SetError(const Error new_error) noexcept {
Invoke<Error>(new_error);
}
[[nodiscard]] bool IsConnected() const noexcept {
return state == State::Joining || state == State::Joined || state == State::Moderator;
}
ENetHost* client = nullptr; ///< ENet network interface.
ENetPeer* server = nullptr; ///< The server peer the client is connected to
@ -48,6 +30,9 @@ public:
GameInfo current_game_info;
std::atomic<State> state{State::Idle}; ///< Current state of the RoomMember.
void SetState(const State new_state);
void SetError(const Error new_error);
bool IsConnected() const;
std::string nickname; ///< The nickname of this member.
@ -58,9 +43,9 @@ public:
std::mutex network_mutex; ///< Mutex that controls access to the `client` variable.
/// Thread that receives and dispatches network packets
std::optional<std::jthread> loop_thread;
std::unique_ptr<std::thread> loop_thread;
std::mutex send_list_mutex; ///< Mutex that controls access to the `send_list` variable.
std::vector<Packet> send_list; ///< A list that stores all packets to send the async
std::list<Packet> send_list; ///< A list that stores all packets to send the async
template <typename T>
using CallbackSet = std::set<CallbackHandle<T>>;
@ -83,6 +68,8 @@ public:
};
Callbacks callbacks; ///< All CallbackSets to all events
void MemberLoop();
void StartLoop();
/**
@ -159,117 +146,134 @@ public:
};
// RoomMemberImpl
void RoomMember::RoomMemberImpl::StartLoop() {
loop_thread.emplace([&](std::stop_token stoken) {
// Receive packets while the connection is open
while (IsConnected()) {
std::lock_guard lock(network_mutex);
ENetEvent event;
if (enet_host_service(client, &event, 5) > 0) {
switch (event.type) {
case ENET_EVENT_TYPE_RECEIVE:
switch (event.packet->data[0]) {
case IdProxyPacket:
HandleProxyPackets(&event);
break;
case IdLdnPacket:
HandleLdnPackets(&event);
break;
case IdChatMessage:
HandleChatPacket(&event);
break;
case IdStatusMessage:
HandleStatusMessagePacket(&event);
break;
case IdRoomInformation:
HandleRoomInformationPacket(&event);
break;
case IdJoinSuccess:
case IdJoinSuccessAsMod:
// The join request was successful, we are now in the room.
// If we joined successfully, there must be at least one client in the room: us.
ASSERT_MSG(member_information.size() > 0,
"We have not yet received member information.");
HandleJoinPacket(&event); // Get the MAC Address for the client
if (event.packet->data[0] == IdJoinSuccessAsMod) {
SetState(State::Moderator);
} else {
SetState(State::Joined);
}
break;
case IdModBanListResponse:
HandleModBanListResponsePacket(&event);
break;
case IdRoomIsFull:
SetState(State::Idle);
SetError(Error::RoomIsFull);
break;
case IdNameCollision:
SetState(State::Idle);
SetError(Error::NameCollision);
break;
case IdIpCollision:
SetState(State::Idle);
SetError(Error::IpCollision);
break;
case IdVersionMismatch:
SetState(State::Idle);
SetError(Error::WrongVersion);
break;
case IdWrongPassword:
SetState(State::Idle);
SetError(Error::WrongPassword);
break;
case IdCloseRoom:
SetState(State::Idle);
SetError(Error::LostConnection);
break;
case IdHostKicked:
SetState(State::Idle);
SetError(Error::HostKicked);
break;
case IdHostBanned:
SetState(State::Idle);
SetError(Error::HostBanned);
break;
case IdModPermissionDenied:
SetError(Error::PermissionDenied);
break;
case IdModNoSuchUser:
SetError(Error::NoSuchUser);
break;
}
enet_packet_destroy(event.packet);
void RoomMember::RoomMemberImpl::SetState(const State new_state) {
if (state != new_state) {
state = new_state;
Invoke<State>(state);
}
}
void RoomMember::RoomMemberImpl::SetError(const Error new_error) {
Invoke<Error>(new_error);
}
bool RoomMember::RoomMemberImpl::IsConnected() const {
return state == State::Joining || state == State::Joined || state == State::Moderator;
}
void RoomMember::RoomMemberImpl::MemberLoop() {
// Receive packets while the connection is open
while (IsConnected()) {
std::lock_guard lock(network_mutex);
ENetEvent event;
if (enet_host_service(client, &event, 5) > 0) {
switch (event.type) {
case ENET_EVENT_TYPE_RECEIVE:
switch (event.packet->data[0]) {
case IdProxyPacket:
HandleProxyPackets(&event);
break;
case ENET_EVENT_TYPE_DISCONNECT:
if (state == State::Joined || state == State::Moderator) {
SetState(State::Idle);
SetError(Error::LostConnection);
case IdLdnPacket:
HandleLdnPackets(&event);
break;
case IdChatMessage:
HandleChatPacket(&event);
break;
case IdStatusMessage:
HandleStatusMessagePacket(&event);
break;
case IdRoomInformation:
HandleRoomInformationPacket(&event);
break;
case IdJoinSuccess:
case IdJoinSuccessAsMod:
// The join request was successful, we are now in the room.
// If we joined successfully, there must be at least one client in the room: us.
ASSERT_MSG(member_information.size() > 0,
"We have not yet received member information.");
HandleJoinPacket(&event); // Get the MAC Address for the client
if (event.packet->data[0] == IdJoinSuccessAsMod) {
SetState(State::Moderator);
} else {
SetState(State::Joined);
}
break;
case ENET_EVENT_TYPE_NONE:
case IdModBanListResponse:
HandleModBanListResponsePacket(&event);
break;
case ENET_EVENT_TYPE_CONNECT:
// The ENET_EVENT_TYPE_CONNECT event can not possibly happen here because we're
// already connected
ASSERT_MSG(false, "Received unexpected connect event while already connected");
case IdRoomIsFull:
SetState(State::Idle);
SetError(Error::RoomIsFull);
break;
case IdNameCollision:
SetState(State::Idle);
SetError(Error::NameCollision);
break;
case IdIpCollision:
SetState(State::Idle);
SetError(Error::IpCollision);
break;
case IdVersionMismatch:
SetState(State::Idle);
SetError(Error::WrongVersion);
break;
case IdWrongPassword:
SetState(State::Idle);
SetError(Error::WrongPassword);
break;
case IdCloseRoom:
SetState(State::Idle);
SetError(Error::LostConnection);
break;
case IdHostKicked:
SetState(State::Idle);
SetError(Error::HostKicked);
break;
case IdHostBanned:
SetState(State::Idle);
SetError(Error::HostBanned);
break;
case IdModPermissionDenied:
SetError(Error::PermissionDenied);
break;
case IdModNoSuchUser:
SetError(Error::NoSuchUser);
break;
}
enet_packet_destroy(event.packet);
break;
case ENET_EVENT_TYPE_DISCONNECT:
if (state == State::Joined || state == State::Moderator) {
SetState(State::Idle);
SetError(Error::LostConnection);
}
break;
case ENET_EVENT_TYPE_NONE:
break;
case ENET_EVENT_TYPE_CONNECT:
// The ENET_EVENT_TYPE_CONNECT event can not possibly happen here because we're
// already connected
ASSERT_MSG(false, "Received unexpected connect event while already connected");
break;
}
std::vector<Packet> packets;
{
std::lock_guard send_lock(send_list_mutex);
packets.swap(send_list);
}
for (auto const& packet : packets) {
ENetPacket* enetPacket = enet_packet_create(packet.GetData(), packet.GetDataSize(),
ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(server, 0, enetPacket);
}
enet_host_flush(client);
}
Disconnect();
});
std::list<Packet> packets;
{
std::lock_guard send_lock(send_list_mutex);
packets.swap(send_list);
}
for (const auto& packet : packets) {
ENetPacket* enetPacket = enet_packet_create(packet.GetData(), packet.GetDataSize(),
ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(server, 0, enetPacket);
}
enet_host_flush(client);
}
Disconnect();
};
void RoomMember::RoomMemberImpl::StartLoop() {
loop_thread = std::make_unique<std::thread>(&RoomMember::RoomMemberImpl::MemberLoop, this);
}
void RoomMember::RoomMemberImpl::Send(Packet&& packet) {
@ -743,7 +747,9 @@ void RoomMember::Unbind(CallbackHandle<T> handle) {
void RoomMember::Leave() {
room_member_impl->SetState(State::Idle);
room_member_impl->loop_thread->join();
room_member_impl->loop_thread.reset();
enet_host_destroy(room_member_impl->client);
room_member_impl->client = nullptr;
}

View file

@ -45,6 +45,10 @@ if (NOT APPLE AND ENABLE_OPENGL)
target_compile_definitions(qt_common PUBLIC HAS_OPENGL)
endif()
if (NOT WIN32)
target_include_directories(qt_common PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
if (UNIX AND NOT APPLE)
if (TARGET Qt6::GuiPrivate)
target_link_libraries(qt_common PRIVATE Qt6::GuiPrivate)
else()
target_include_directories(qt_common PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
endif()
endif()

View file

@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -21,9 +18,13 @@ public:
: socket(io_context, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 0)) {}
~FakeCemuhookServer() {
is_running = false;
boost::system::error_code error_code;
socket.shutdown(boost::asio::socket_base::shutdown_both, error_code);
socket.close();
if (handler.joinable()) {
handler.join();
}
}
u16 GetPort() {
@ -40,9 +41,10 @@ public:
sizeof(InputCommon::CemuhookUDP::Message<InputCommon::CemuhookUDP::Response::PadData>);
REQUIRE(touch_movement_path.size() > 0);
handler = std::jthread([touch_movement_path, this](std::stop_token stoken) {
is_running = true;
handler = std::thread([touch_movement_path, this]() {
auto current_touch_position = touch_movement_path.begin();
while (!stoken.stop_requested()) {
while (is_running) {
boost::asio::ip::udp::endpoint sender_endpoint;
boost::system::error_code error_code;
auto received_size = socket.receive_from(boost::asio::buffer(receive_buffer),
@ -85,7 +87,8 @@ private:
boost::asio::ip::udp::socket socket;
std::array<u8, InputCommon::CemuhookUDP::MAX_PACKET_SIZE> send_buffer;
std::array<u8, InputCommon::CemuhookUDP::MAX_PACKET_SIZE> receive_buffer;
std::jthread handler;
bool is_running = false;
std::thread handler;
};
TEST_CASE("CalibrationConfigurationJob completed", "[input_common]") {

View file

@ -386,8 +386,7 @@ void BufferCache<P>::BindHostComputeBuffers() {
template <class P>
void BufferCache<P>::SetUniformBuffersState(const std::array<u32, NUM_STAGES>& mask,
const UniformBufferSizes* sizes) {
const bool mask_changed = channel_state->enabled_uniform_buffer_masks != mask;
if (mask_changed) {
if (channel_state->enabled_uniform_buffer_masks != mask) {
channel_state->fast_bound_uniform_buffers.fill(0);
if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) {
channel_state->dirty_uniform_buffers.fill(~u32{0});
@ -818,18 +817,7 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size;
// Stream buffer path to avoid stalling on non-Nvidia drivers or Vulkan
const std::span<u8> span = runtime.BindMappedUniformBuffer(stage, binding_index, size);
#ifdef YUZU_DEBUG
ASSERT(binding_index < NUM_GRAPHICS_UNIFORM_BUFFERS);
ASSERT(span.size() >= size && "UBO stream span too small");
if (!device_memory.ReadBlockFastChecked(device_addr, span.data(), size)) {
LOG_CRITICAL(Render, "DeviceMemory OOB/unmapped: addr=0x{:x} size={}", device_addr, size);
channel_state->fast_bound_uniform_buffers[stage] &= ~(1u << binding_index);
ASSERT(false);
return;
}
#else
device_memory.ReadBlockUnsafe(device_addr, span.data(), size);
#endif
return;
}
// Classic cached path
@ -838,8 +826,7 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
}
// Skip binding if it's not needed and if the bound buffer is not the fast version
// This exists to avoid instances where the fast buffer is bound and a GPU write happens
const bool was_fast_bound = HasFastUniformBufferBound(stage, binding_index);
needs_bind |= was_fast_bound;
needs_bind |= HasFastUniformBufferBound(stage, binding_index);
if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) {
needs_bind |= channel_state->uniform_buffer_binding_sizes[stage][binding_index] != size;
}

View file

@ -53,6 +53,7 @@ constexpr u32 NUM_COMPUTE_UNIFORM_BUFFERS = 8;
constexpr u32 NUM_STORAGE_BUFFERS = 16;
constexpr u32 NUM_TEXTURE_BUFFERS = 32;
constexpr u32 NUM_STAGES = 5;
static_assert(NUM_GRAPHICS_UNIFORM_BUFFERS <= 32, "fast bitmask must fit u32");
using UniformBufferSizes = std::array<std::array<u32, NUM_GRAPHICS_UNIFORM_BUFFERS>, NUM_STAGES>;

View file

@ -337,6 +337,11 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m
uint8_pass = std::make_unique<Uint8Pass>(device, scheduler, descriptor_pool, staging_pool,
compute_pass_descriptor_queue);
}
const u32 ubo_align = static_cast<u32>(
device.GetUniformBufferAlignment() //check if the device has it
);
// add the ability to change the size in settings in future
uniform_ring.Init(device, memory_allocator, 8 * 1024 * 1024 /* 8 MiB */, ubo_align ? ubo_align : 256);
quad_array_index_buffer = std::make_shared<QuadArrayIndexBuffer>(device_, memory_allocator_,
scheduler_, staging_pool_);
quad_strip_index_buffer = std::make_shared<QuadStripIndexBuffer>(device_, memory_allocator_,
@ -355,6 +360,42 @@ void BufferCacheRuntime::FreeDeferredStagingBuffer(StagingBufferRef& ref) {
staging_pool.FreeDeferred(ref);
}
void BufferCacheRuntime::UniformRing::Init(const Device& device,
MemoryAllocator& alloc,
u64 bytes, u32 alignment) {
for (size_t i = 0; i < NUM_FRAMES; ++i) {
VkBufferCreateInfo ci{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.size = bytes,
.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
buffers[i] = alloc.CreateBuffer(ci, MemoryUsage::Upload);
mapped[i] = buffers[i].Mapped().data();
}
size = bytes;
align = alignment ? alignment : 256;
head = 0;
current_frame = 0;
}
std::span<u8> BufferCacheRuntime::UniformRing::Alloc(u32 bytes, u32& out_offset) {
const u64 aligned = Common::AlignUp(head, static_cast<u64>(align));
u64 end = aligned + bytes;
if (end > size) {
return {}; // Fallback to staging pool
}
out_offset = static_cast<u32>(aligned);
head = end;
return {mapped[current_frame] + out_offset, bytes};
}
u64 BufferCacheRuntime::GetDeviceLocalMemory() const {
return device.GetDeviceLocalMemory();
}
@ -375,6 +416,7 @@ void BufferCacheRuntime::TickFrame(Common::SlotVector<Buffer>& slot_buffers) noe
for (auto it = slot_buffers.begin(); it != slot_buffers.end(); it++) {
it->ResetUsageTracking();
}
uniform_ring.BeginFrame();
}
void BufferCacheRuntime::Finish() {

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 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
@ -124,8 +127,15 @@ public:
void BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings);
std::span<u8> BindMappedUniformBuffer([[maybe_unused]] size_t stage,
[[maybe_unused]] u32 binding_index, u32 size) {
std::span<u8> BindMappedUniformBuffer([[maybe_unused]] size_t /*stage*/,
[[maybe_unused]] u32 /*binding_index*/,
u32 size) {
u32 offset = 0;
if (auto span = uniform_ring.Alloc(size, offset); !span.empty()) {
BindBuffer(*uniform_ring.buffers[uniform_ring.current_frame], offset, size);
return span;
}
// Fallback for giant requests
const StagingBufferRef ref = staging_pool.Request(size, MemoryUsage::Upload);
BindBuffer(ref.buffer, static_cast<u32>(ref.offset), size);
return ref.mapped_span;
@ -153,6 +163,24 @@ private:
void ReserveNullBuffer();
vk::Buffer CreateNullBuffer();
struct UniformRing {
static constexpr size_t NUM_FRAMES = 3;
std::array<vk::Buffer, NUM_FRAMES> buffers{};
std::array<u8*, NUM_FRAMES> mapped{};
u64 size = 0;
u64 head = 0;
u32 align = 256;
size_t current_frame = 0;
void Init(const Device& device, MemoryAllocator& alloc, u64 bytes, u32 alignment);
void BeginFrame() {
current_frame = (current_frame + 1) % NUM_FRAMES;
head = 0;
}
std::span<u8> Alloc(u32 bytes, u32& out_offset);
};
UniformRing uniform_ring;
const Device& device;
MemoryAllocator& memory_allocator;
Scheduler& scheduler;

View file

@ -25,48 +25,35 @@ namespace {
using namespace Common::Literals;
// Minimum alignment we want to enforce for the streaming ring
constexpr VkDeviceSize MIN_STREAM_ALIGNMENT = 256;
// Maximum potential alignment of a Vulkan buffer
constexpr VkDeviceSize MAX_ALIGNMENT = 256;
// Stream buffer size in bytes
constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 128_MiB;
size_t GetStreamBufferSize(const Device& device, VkDeviceSize alignment) {
size_t GetStreamBufferSize(const Device& device) {
VkDeviceSize size{0};
if (device.HasDebuggingToolAttached()) {
bool found_heap = false;
ForEachDeviceLocalHostVisibleHeap(device, [&size, &found_heap](size_t /*index*/, VkMemoryHeap& heap) {
ForEachDeviceLocalHostVisibleHeap(device, [&size](size_t index, VkMemoryHeap& heap) {
size = (std::max)(size, heap.size);
found_heap = true;
});
// If no suitable heap was found fall back to the default cap to avoid creating a zero-sized stream buffer.
if (!found_heap) {
size = MAX_STREAM_BUFFER_SIZE;
} else if (size <= 256_MiB) {
// If rebar is not supported, cut the max heap size to 40%. This will allow 2 captures to be
// loaded at the same time in RenderDoc. If rebar is supported, this shouldn't be an issue
// as the heap will be much larger.
// If rebar is not supported, cut the max heap size to 40%. This will allow 2 captures to be
// loaded at the same time in RenderDoc. If rebar is supported, this shouldn't be an issue
// as the heap will be much larger.
if (size <= 256_MiB) {
size = size * 40 / 100;
}
} else {
size = MAX_STREAM_BUFFER_SIZE;
}
// Clamp to the configured maximum, align up for safety, and ensure a sane minimum so
// region_size (stream_buffer_size / NUM_SYNCS) never becomes zero.
const VkDeviceSize aligned =
(std::min)(Common::AlignUp(size, alignment), MAX_STREAM_BUFFER_SIZE);
const VkDeviceSize min_size = alignment * StagingBufferPool::NUM_SYNCS;
return static_cast<size_t>((std::max)(aligned, min_size));
return (std::min)(Common::AlignUp(size, MAX_ALIGNMENT), MAX_STREAM_BUFFER_SIZE);
}
} // Anonymous namespace
StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_,
Scheduler& scheduler_)
: device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_},
stream_alignment{std::max<VkDeviceSize>(device_.GetUniformBufferAlignment(),
MIN_STREAM_ALIGNMENT)},
stream_buffer_size{GetStreamBufferSize(device_, stream_alignment)},
region_size{stream_buffer_size / StagingBufferPool::NUM_SYNCS} {
stream_buffer_size{GetStreamBufferSize(device)}, region_size{stream_buffer_size /
StagingBufferPool::NUM_SYNCS} {
VkBufferCreateInfo stream_ci = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
@ -119,54 +106,31 @@ void StagingBufferPool::TickFrame() {
}
StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) {
const size_t alignment = static_cast<size_t>(stream_alignment);
const size_t aligned_size = Common::AlignUp(size, alignment);
const bool wraps = iterator + size >= stream_buffer_size;
const size_t new_iterator =
wraps ? aligned_size : Common::AlignUp(iterator + size, alignment);
const size_t begin_region = wraps ? 0 : Region(iterator);
const size_t last_byte = new_iterator == 0 ? 0 : new_iterator - 1;
const size_t end_region = (std::min)(Region(last_byte) + 1, NUM_SYNCS);
const size_t guard_begin = (std::min)(Region(free_iterator) + 1, NUM_SYNCS);
if (!wraps) {
if (guard_begin < end_region && AreRegionsActive(guard_begin, end_region)) {
// Avoid waiting for the previous usages to be free
return GetStagingBuffer(size, MemoryUsage::Upload);
}
} else if (guard_begin < NUM_SYNCS && AreRegionsActive(guard_begin, NUM_SYNCS)) {
if (AreRegionsActive(Region(free_iterator) + 1,
(std::min)(Region(iterator + size) + 1, NUM_SYNCS))) {
// Avoid waiting for the previous usages to be free
return GetStagingBuffer(size, MemoryUsage::Upload);
}
const u64 current_tick = scheduler.CurrentTick();
std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + Region(iterator),
current_tick);
used_iterator = iterator;
free_iterator = (std::max)(free_iterator, iterator + size);
if (wraps) {
if (iterator + size >= stream_buffer_size) {
std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS,
current_tick);
used_iterator = 0;
iterator = 0;
free_iterator = aligned_size;
const size_t head_last_byte = aligned_size == 0 ? 0 : aligned_size - 1;
const size_t head_end_region = (std::min)(Region(head_last_byte) + 1, NUM_SYNCS);
if (AreRegionsActive(0, head_end_region)) {
free_iterator = size;
if (AreRegionsActive(0, Region(size) + 1)) {
// Avoid waiting for the previous usages to be free
return GetStagingBuffer(size, MemoryUsage::Upload);
}
}
std::fill(sync_ticks.begin() + begin_region, sync_ticks.begin() + end_region, current_tick);
const size_t offset = wraps ? 0 : iterator;
iterator = new_iterator;
if (!wraps) {
free_iterator = (std::max)(free_iterator, offset + aligned_size);
}
const size_t offset = iterator;
iterator = Common::AlignUp(iterator + size, MAX_ALIGNMENT);
return StagingBufferRef{
.buffer = *stream_buffer,
.offset = static_cast<VkDeviceSize>(offset),

View file

@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
@ -105,7 +102,6 @@ private:
MemoryAllocator& memory_allocator;
Scheduler& scheduler;
VkDeviceSize stream_alignment;
vk::Buffer stream_buffer;
std::span<u8> stream_pointer;
VkDeviceSize stream_buffer_size;

View file

@ -393,16 +393,8 @@ target_link_libraries(yuzu PRIVATE common core input_common frontend_common netw
target_link_libraries(yuzu PRIVATE Boost::headers glad Qt6::Widgets)
target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
if (NOT WIN32)
target_include_directories(yuzu PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
endif()
if (UNIX AND NOT APPLE)
target_link_libraries(yuzu PRIVATE Qt6::DBus)
if (TARGET Qt6::GuiPrivate)
target_link_libraries(yuzu PRIVATE Qt6::GuiPrivate)
endif()
endif()
target_compile_definitions(yuzu PRIVATE

View file

@ -1,5 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 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
@ -1494,36 +1492,53 @@ void QtSoftwareKeyboardDialog::MoveTextCursorDirection(Direction direction) {
}
void QtSoftwareKeyboardDialog::StartInputThread() {
input_thread = std::jthread([&](std::stop_token stoken) {
while (!stoken.stop_requested()) {
input_interpreter->PollInput();
HandleButtonPressedOnce<
Core::HID::NpadButton::A, Core::HID::NpadButton::B, Core::HID::NpadButton::X,
Core::HID::NpadButton::Y, Core::HID::NpadButton::StickL, Core::HID::NpadButton::StickR,
Core::HID::NpadButton::L, Core::HID::NpadButton::R, Core::HID::NpadButton::Plus,
Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
Core::HID::NpadButton::StickLDown, Core::HID::NpadButton::StickRLeft,
Core::HID::NpadButton::StickRUp, Core::HID::NpadButton::StickRRight,
Core::HID::NpadButton::StickRDown>();
HandleButtonHold<Core::HID::NpadButton::B, Core::HID::NpadButton::L,
Core::HID::NpadButton::R, Core::HID::NpadButton::Left,
Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
Core::HID::NpadButton::StickLDown, Core::HID::NpadButton::StickRLeft,
Core::HID::NpadButton::StickRUp, Core::HID::NpadButton::StickRRight,
Core::HID::NpadButton::StickRDown>();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
});
if (input_thread_running) {
return;
}
input_thread_running = true;
input_thread = std::thread(&QtSoftwareKeyboardDialog::InputThread, this);
}
void QtSoftwareKeyboardDialog::StopInputThread() {
input_thread.request_stop();
if (input_interpreter)
input_thread_running = false;
if (input_thread.joinable()) {
input_thread.join();
}
if (input_interpreter) {
input_interpreter->ResetButtonStates();
}
}
void QtSoftwareKeyboardDialog::InputThread() {
while (input_thread_running) {
input_interpreter->PollInput();
HandleButtonPressedOnce<
Core::HID::NpadButton::A, Core::HID::NpadButton::B, Core::HID::NpadButton::X,
Core::HID::NpadButton::Y, Core::HID::NpadButton::StickL, Core::HID::NpadButton::StickR,
Core::HID::NpadButton::L, Core::HID::NpadButton::R, Core::HID::NpadButton::Plus,
Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
Core::HID::NpadButton::StickLDown, Core::HID::NpadButton::StickRLeft,
Core::HID::NpadButton::StickRUp, Core::HID::NpadButton::StickRRight,
Core::HID::NpadButton::StickRDown>();
HandleButtonHold<Core::HID::NpadButton::B, Core::HID::NpadButton::L,
Core::HID::NpadButton::R, Core::HID::NpadButton::Left,
Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
Core::HID::NpadButton::StickLDown, Core::HID::NpadButton::StickRLeft,
Core::HID::NpadButton::StickRUp, Core::HID::NpadButton::StickRRight,
Core::HID::NpadButton::StickRDown>();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) {

View file

@ -1,5 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 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
@ -184,6 +182,9 @@ private:
void StartInputThread();
void StopInputThread();
/// The thread where input is being polled and processed.
void InputThread();
std::unique_ptr<Ui::QtSoftwareKeyboardDialog> ui;
Core::System& system;
@ -219,7 +220,10 @@ private:
std::atomic<bool> caps_lock_enabled{false};
std::unique_ptr<InputInterpreter> input_interpreter;
std::jthread input_thread;
std::thread input_thread;
std::atomic<bool> input_thread_running{};
};
class QtSoftwareKeyboard final : public QObject, public Core::Frontend::SoftwareKeyboardApplet {

View file

@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -285,41 +282,54 @@ void QtNXWebEngineView::SendKeyPressEvent(int key) {
}
void QtNXWebEngineView::StartInputThread() {
input_thread = std::jthread([&](std::stop_token stoken) {
// Wait for 1 second before allowing any inputs to be processed.
std::this_thread::sleep_for(std::chrono::seconds(1));
if (is_local) {
QWidget::grabKeyboard();
}
while (!stoken.stop_requested()) {
input_interpreter->PollInput();
if (input_thread_running) {
return;
}
HandleWindowFooterButtonPressedOnce<Core::HID::NpadButton::A, Core::HID::NpadButton::B,
Core::HID::NpadButton::X, Core::HID::NpadButton::Y,
Core::HID::NpadButton::L, Core::HID::NpadButton::R>();
HandleWindowKeyButtonPressedOnce<
Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
Core::HID::NpadButton::StickLDown>();
HandleWindowKeyButtonHold<
Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
Core::HID::NpadButton::StickLDown>();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
});
input_thread_running = true;
input_thread = std::thread(&QtNXWebEngineView::InputThread, this);
}
void QtNXWebEngineView::StopInputThread() {
if (is_local) {
QWidget::releaseKeyboard();
}
input_thread.request_stop();
input_thread_running = false;
if (input_thread.joinable()) {
input_thread.join();
}
}
void QtNXWebEngineView::InputThread() {
// Wait for 1 second before allowing any inputs to be processed.
std::this_thread::sleep_for(std::chrono::seconds(1));
if (is_local) {
QWidget::grabKeyboard();
}
while (input_thread_running) {
input_interpreter->PollInput();
HandleWindowFooterButtonPressedOnce<Core::HID::NpadButton::A, Core::HID::NpadButton::B,
Core::HID::NpadButton::X, Core::HID::NpadButton::Y,
Core::HID::NpadButton::L, Core::HID::NpadButton::R>();
HandleWindowKeyButtonPressedOnce<
Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
Core::HID::NpadButton::StickLDown>();
HandleWindowKeyButtonHold<
Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
Core::HID::NpadButton::StickLDown>();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
void QtNXWebEngineView::LoadExtractedFonts() {

View file

@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -158,6 +155,9 @@ private:
void StartInputThread();
void StopInputThread();
/// The thread where input is being polled and processed.
void InputThread();
/// Loads the extracted fonts using JavaScript.
void LoadExtractedFonts();
@ -165,13 +165,24 @@ private:
void FocusFirstLinkElement();
InputCommon::InputSubsystem* input_subsystem;
std::unique_ptr<UrlRequestInterceptor> url_interceptor;
std::unique_ptr<InputInterpreter> input_interpreter;
std::jthread input_thread;
std::thread input_thread;
std::atomic<bool> input_thread_running{};
std::atomic<bool> finished{};
Service::AM::Frontend::WebExitReason exit_reason{Service::AM::Frontend::WebExitReason::EndButtonPressed};
Service::AM::Frontend::WebExitReason exit_reason{
Service::AM::Frontend::WebExitReason::EndButtonPressed};
std::string last_url{"http://localhost/"};
bool is_local{};
QWebEngineProfile* default_profile;
QWebEngineSettings* global_settings;
};

View file

@ -83,8 +83,7 @@ void ConfigureDebug::SetConfiguration() {
#ifdef YUZU_USE_QT_WEB_ENGINE
ui->disable_web_applet->setChecked(UISettings::values.disable_web_applet.GetValue());
#else
ui->disable_web_applet->setEnabled(false);
ui->disable_web_applet->setText(tr("Web applet not compiled"));
ui->disable_web_applet->setVisible(false);
#endif
}

View file

@ -1,5 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 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
@ -233,20 +231,34 @@ void OverlayDialog::TranslateButtonPress(Core::HID::NpadButton button) {
}
void OverlayDialog::StartInputThread() {
input_thread = std::jthread([&](std::stop_token stoken) {
while (!stoken.stop_requested()) {
input_interpreter->PollInput();
HandleButtonPressedOnce<Core::HID::NpadButton::A, Core::HID::NpadButton::B,
Core::HID::NpadButton::Left, Core::HID::NpadButton::Right,
Core::HID::NpadButton::StickLLeft,
Core::HID::NpadButton::StickLRight>();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
});
if (input_thread_running) {
return;
}
input_thread_running = true;
input_thread = std::thread(&OverlayDialog::InputThread, this);
}
void OverlayDialog::StopInputThread() {
input_thread.request_stop();
input_thread_running = false;
if (input_thread.joinable()) {
input_thread.join();
}
}
void OverlayDialog::InputThread() {
while (input_thread_running) {
input_interpreter->PollInput();
HandleButtonPressedOnce<Core::HID::NpadButton::A, Core::HID::NpadButton::B,
Core::HID::NpadButton::Left, Core::HID::NpadButton::Right,
Core::HID::NpadButton::StickLLeft,
Core::HID::NpadButton::StickLRight>();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
void OverlayDialog::keyPressEvent(QKeyEvent* e) {

View file

@ -1,10 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2025 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
#pragma once
#include <atomic>
#include <memory>
#include <thread>
@ -92,6 +91,9 @@ private:
void StartInputThread();
void StopInputThread();
/// The thread where input is being polled and processed.
void InputThread();
void keyPressEvent(QKeyEvent* e) override;
std::unique_ptr<Ui::OverlayDialog> ui;
@ -99,5 +101,8 @@ private:
bool use_rich_text;
std::unique_ptr<InputInterpreter> input_interpreter;
std::jthread input_thread;
std::thread input_thread;
std::atomic<bool> input_thread_running{};
};