Compare commits

..

22 Commits

Author SHA1 Message Date
b52f26a094 Android 229 2024-02-05 01:00:43 +00:00
e957bc2b4c Merge yuzu-emu#12915 2024-02-05 01:00:43 +00:00
363c96bdc3 Merge yuzu-emu#12905 2024-02-05 01:00:43 +00:00
514c3a75e1 Merge yuzu-emu#12903 2024-02-05 01:00:43 +00:00
bef9ee5000 Merge yuzu-emu#12873 2024-02-05 01:00:43 +00:00
78ce7f00f9 Merge yuzu-emu#12756 2024-02-05 01:00:43 +00:00
9b9634454a Merge yuzu-emu#12749 2024-02-05 01:00:43 +00:00
a5cdf0aed1 Merge yuzu-emu#12461 2024-02-05 01:00:42 +00:00
4cccbe7989 Merge pull request #12892 from liamwhite/serialization-stuff
cmif_serialization: enforce const for references
2024-02-04 09:48:33 -05:00
5da55cbac9 Merge pull request #12901 from Kelebek1/timezone_firmware_fix
Fix firmware timezone boot load check.
2024-02-03 11:10:30 -05:00
81cc4df1f9 Merge pull request #12895 from german77/files
service: fs: Skip non user id folders
2024-02-03 11:10:24 -05:00
25f3d358b1 Merge pull request #12877 from german77/npad-fixed
service: hid: Multiple fixes
2024-02-03 11:10:14 -05:00
a3c8bb251d Merge pull request #12852 from Calinou/multiplayer-color-player-counts
Color player counts in the multiplayer public lobby list
2024-02-03 11:10:00 -05:00
327533be1f Merge pull request #12851 from Calinou/multiplayer-persist-filters
Persist filters in multiplayer public lobby list
2024-02-03 11:09:51 -05:00
61ea2115c7 Merge pull request #12850 from Calinou/multiplayer-add-hotkeys
Add hotkeys for multiplayer actions
2024-02-03 11:09:41 -05:00
108a72ea8a Fix firmware timezone boot load check. 2024-02-03 15:21:10 +00:00
fb3ef957bb service: fs: Skip non user id folders 2024-02-02 13:25:38 -06:00
78f72b3bf5 cmif_serialization: enforce const for references 2024-02-02 09:32:10 -05:00
818721d12d service: hid: Multiple fixes 2024-02-01 10:37:44 -06:00
442aad9b27 Persist filters in multiplayer public lobby list
After connecting to a room, the chosen filter text, "Games I Own",
"Hide Empty Rooms" and "Hide Full Rooms" values are persisted
to configuration so they are preserved across restarts.

This makes it easier to rejoin a room if you regularly play the same
game, or after a crash.
2024-01-30 17:40:29 +01:00
8e0f97ac96 Color player counts in the multiplayer public lobby list
- Full lobbies have their player count displayed in red.
- Lobbies with one slot left have their player count displayed in orange.
- Empty lobbies have their player count grayed out.
2024-01-30 17:38:21 +01:00
345d691328 Add hotkeys for multiplayer actions
Default shortcuts were chosen as to be intuitive (use the first letter
of the action, or the second word's first letter) and work on all
types of keyboards. The hotkeys can be used while playing a game too,
as they are application-wide.
2024-01-30 01:32:14 +01:00
32 changed files with 404 additions and 135 deletions

View File

@ -2,9 +2,11 @@
|----|----|----|----|----| |----|----|----|----|----|
| [12461](https://github.com/yuzu-emu/yuzu-android//pull/12461) | [`4c08a0e6d`](https://github.com/yuzu-emu/yuzu-android//pull/12461/files) | Rework Nvdec and VIC to fix out-of-order videos, and speed up decoding. | [Kelebek1](https://github.com/Kelebek1/) | Yes | | [12461](https://github.com/yuzu-emu/yuzu-android//pull/12461) | [`4c08a0e6d`](https://github.com/yuzu-emu/yuzu-android//pull/12461/files) | Rework Nvdec and VIC to fix out-of-order videos, and speed up decoding. | [Kelebek1](https://github.com/Kelebek1/) | Yes |
| [12749](https://github.com/yuzu-emu/yuzu-android//pull/12749) | [`aad4b0d6f`](https://github.com/yuzu-emu/yuzu-android//pull/12749/files) | general: workarounds for SMMU syncing issues | [liamwhite](https://github.com/liamwhite/) | Yes | | [12749](https://github.com/yuzu-emu/yuzu-android//pull/12749) | [`aad4b0d6f`](https://github.com/yuzu-emu/yuzu-android//pull/12749/files) | general: workarounds for SMMU syncing issues | [liamwhite](https://github.com/liamwhite/) | Yes |
| [12756](https://github.com/yuzu-emu/yuzu-android//pull/12756) | [`f6e8080cb`](https://github.com/yuzu-emu/yuzu-android//pull/12756/files) | general: applet multiprocess | [liamwhite](https://github.com/liamwhite/) | Yes | | [12756](https://github.com/yuzu-emu/yuzu-android//pull/12756) | [`cd3de0848`](https://github.com/yuzu-emu/yuzu-android//pull/12756/files) | general: applet multiprocess | [liamwhite](https://github.com/liamwhite/) | Yes |
| [12874](https://github.com/yuzu-emu/yuzu-android//pull/12874) | [`f410cf681`](https://github.com/yuzu-emu/yuzu-android//pull/12874/files) | Revert "shader_recompiler: fix Offset operand usage for non-OpImage*Gather" | [liamwhite](https://github.com/liamwhite/) | Yes | | [12873](https://github.com/yuzu-emu/yuzu-android//pull/12873) | [`a8488952f`](https://github.com/yuzu-emu/yuzu-android//pull/12873/files) | GPU: Implement channel scheduling. | [FernandoS27](https://github.com/FernandoS27/) | Yes |
| [12877](https://github.com/yuzu-emu/yuzu-android//pull/12877) | [`818721d12`](https://github.com/yuzu-emu/yuzu-android//pull/12877/files) | service: hid: Multiple fixes | [german77](https://github.com/german77/) | Yes | | [12903](https://github.com/yuzu-emu/yuzu-android//pull/12903) | [`5be8121af`](https://github.com/yuzu-emu/yuzu-android//pull/12903/files) | shader_recompiler: use only ConstOffset for OpImageFetch | [liamwhite](https://github.com/liamwhite/) | Yes |
| [12905](https://github.com/yuzu-emu/yuzu-android//pull/12905) | [`5eb5c9675`](https://github.com/yuzu-emu/yuzu-android//pull/12905/files) | nvnflinger: release buffers before presentation sleep | [liamwhite](https://github.com/liamwhite/) | Yes |
| [12915](https://github.com/yuzu-emu/yuzu-android//pull/12915) | [`504abbd6e`](https://github.com/yuzu-emu/yuzu-android//pull/12915/files) | dmnt: cheats: Update cheat vm to latest version | [german77](https://github.com/german77/) | Yes |
End of merge log. You can find the original README.md below the break. End of merge log. You can find the original README.md below the break.

View File

@ -9,6 +9,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <fmt/format.h> #include <fmt/format.h>
#include "common/assert.h"
#include "common/common_types.h" #include "common/common_types.h"
namespace Common { namespace Common {
@ -29,6 +30,8 @@ namespace Common {
template <std::size_t Size, bool le = false> template <std::size_t Size, bool le = false>
[[nodiscard]] constexpr std::array<u8, Size> HexStringToArray(std::string_view str) { [[nodiscard]] constexpr std::array<u8, Size> HexStringToArray(std::string_view str) {
ASSERT_MSG(Size * 2 <= str.size(), "Invalid string size");
std::array<u8, Size> out{}; std::array<u8, Size> out{};
if constexpr (le) { if constexpr (le) {
for (std::size_t i = 2 * Size - 2; i <= 2 * Size; i -= 2) { for (std::size_t i = 2 * Size - 2; i <= 2 * Size; i -= 2) {

View File

@ -118,23 +118,17 @@ std::shared_ptr<ILibraryAppletAccessor> CreateGuestApplet(Core::System& system,
switch (mode) { switch (mode) {
case LibraryAppletMode::AllForeground: case LibraryAppletMode::AllForeground:
case LibraryAppletMode::NoUi: case LibraryAppletMode::NoUi:
applet->focus_state = FocusState::InFocus;
applet->hid_registration.EnableAppletToGetInput(true);
applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
break;
case LibraryAppletMode::AllForegroundInitiallyHidden:
applet->system_buffer_manager.SetWindowVisibility(false);
applet->focus_state = FocusState::NotInFocus;
applet->hid_registration.EnableAppletToGetInput(false);
applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
break;
case LibraryAppletMode::PartialForeground: case LibraryAppletMode::PartialForeground:
case LibraryAppletMode::PartialForegroundIndirectDisplay: case LibraryAppletMode::PartialForegroundIndirectDisplay:
default:
applet->focus_state = FocusState::Background;
applet->hid_registration.EnableAppletToGetInput(true); applet->hid_registration.EnableAppletToGetInput(true);
applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); applet->focus_state = FocusState::InFocus;
applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
break;
case LibraryAppletMode::AllForegroundInitiallyHidden:
applet->hid_registration.EnableAppletToGetInput(false);
applet->focus_state = FocusState::NotInFocus;
applet->system_buffer_manager.SetWindowVisibility(false);
applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoBackground);
break; break;
} }

View File

@ -1,10 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/hle/result.h"
#include "core/hle/service/am/am_results.h" #include "core/hle/service/am/am_results.h"
#include "core/hle/service/am/frontend/applets.h" #include "core/hle/service/am/frontend/applets.h"
#include "core/hle/service/am/self_controller.h" #include "core/hle/service/am/self_controller.h"
#include "core/hle/service/caps/caps_su.h" #include "core/hle/service/caps/caps_su.h"
#include "core/hle/service/hle_ipc.h"
#include "core/hle/service/ipc_helpers.h" #include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h" #include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
#include "core/hle/service/nvnflinger/nvnflinger.h" #include "core/hle/service/nvnflinger/nvnflinger.h"
@ -47,7 +50,7 @@ ISelfController::ISelfController(Core::System& system_, std::shared_ptr<Applet>
{50, &ISelfController::SetHandlesRequestToDisplay, "SetHandlesRequestToDisplay"}, {50, &ISelfController::SetHandlesRequestToDisplay, "SetHandlesRequestToDisplay"},
{51, &ISelfController::ApproveToDisplay, "ApproveToDisplay"}, {51, &ISelfController::ApproveToDisplay, "ApproveToDisplay"},
{60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"}, {60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"},
{61, nullptr, "SetMediaPlaybackState"}, {61, &ISelfController::SetMediaPlaybackState, "SetMediaPlaybackState"},
{62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"}, {62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"},
{63, &ISelfController::GetIdleTimeDetectionExtension, "GetIdleTimeDetectionExtension"}, {63, &ISelfController::GetIdleTimeDetectionExtension, "GetIdleTimeDetectionExtension"},
{64, nullptr, "SetInputDetectionSourceSet"}, {64, nullptr, "SetInputDetectionSourceSet"},
@ -324,6 +327,16 @@ void ISelfController::ApproveToDisplay(HLERequestContext& ctx) {
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
} }
void ISelfController::SetMediaPlaybackState(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u8 state = rp.Pop<u8>();
LOG_WARNING(Service_AM, "(STUBBED) called, state={}", state);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void ISelfController::SetIdleTimeDetectionExtension(HLERequestContext& ctx) { void ISelfController::SetIdleTimeDetectionExtension(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};

View File

@ -3,6 +3,7 @@
#pragma once #pragma once
#include "core/hle/service/hle_ipc.h"
#include "core/hle/service/kernel_helpers.h" #include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
@ -38,6 +39,7 @@ private:
void CreateManagedDisplaySeparableLayer(HLERequestContext& ctx); void CreateManagedDisplaySeparableLayer(HLERequestContext& ctx);
void SetHandlesRequestToDisplay(HLERequestContext& ctx); void SetHandlesRequestToDisplay(HLERequestContext& ctx);
void ApproveToDisplay(HLERequestContext& ctx); void ApproveToDisplay(HLERequestContext& ctx);
void SetMediaPlaybackState(HLERequestContext& ctx);
void SetIdleTimeDetectionExtension(HLERequestContext& ctx); void SetIdleTimeDetectionExtension(HLERequestContext& ctx);
void GetIdleTimeDetectionExtension(HLERequestContext& ctx); void GetIdleTimeDetectionExtension(HLERequestContext& ctx);
void ReportUserIsActive(HLERequestContext& ctx); void ReportUserIsActive(HLERequestContext& ctx);

View File

@ -38,7 +38,8 @@ bool SystemBufferManager::Initialize(Nvnflinger::Nvnflinger* nvnflinger, Kernel:
} }
Nvnflinger::LayerBlending blending = Nvnflinger::LayerBlending::None; Nvnflinger::LayerBlending blending = Nvnflinger::LayerBlending::None;
if (mode == LibraryAppletMode::PartialForeground) { if (mode == LibraryAppletMode::PartialForeground ||
mode == LibraryAppletMode::PartialForegroundIndirectDisplay) {
blending = Nvnflinger::LayerBlending::Coverage; blending = Nvnflinger::LayerBlending::Coverage;
} }

View File

@ -62,12 +62,12 @@ void IWindowController::SetAppletWindowVisibility(HLERequestContext& ctx) {
applet->hid_registration.EnableAppletToGetInput(visible); applet->hid_registration.EnableAppletToGetInput(visible);
if (visible) { if (visible) {
applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
applet->focus_state = FocusState::InFocus; applet->focus_state = FocusState::InFocus;
applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
} else { } else {
applet->focus_state = FocusState::NotInFocus; applet->focus_state = FocusState::NotInFocus;
applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoBackground);
} }
applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);

View File

@ -115,6 +115,11 @@ struct ArgumentTraits {
static constexpr ArgumentType Type = ArgumentType::InData; static constexpr ArgumentType Type = ArgumentType::InData;
}; };
template <typename... Ts>
consteval bool ConstIfReference() {
return ((!std::is_reference_v<Ts> || std::is_const_v<std::remove_reference_t<Ts>>) && ... && true);
}
struct RequestLayout { struct RequestLayout {
u32 copy_handle_count; u32 copy_handle_count;
u32 move_handle_count; u32 move_handle_count;
@ -435,6 +440,7 @@ void CmifReplyWrapImpl(HLERequestContext& ctx, T& t, Result (T::*f)(A...)) {
} }
const bool is_domain = Domain ? ctx.GetManager()->IsDomain() : false; const bool is_domain = Domain ? ctx.GetManager()->IsDomain() : false;
static_assert(ConstIfReference<A...>(), "Arguments taken by reference must be const");
using MethodArguments = std::tuple<std::remove_cvref_t<A>...>; using MethodArguments = std::tuple<std::remove_cvref_t<A>...>;
OutTemporaryBuffers buffers{}; OutTemporaryBuffers buffers{};

View File

@ -4,10 +4,9 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include <span>
#include "common/common_funcs.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "core/hle/service/hle_ipc.h"
namespace Service { namespace Service {
@ -22,8 +21,10 @@ class Out {
public: public:
using Type = T; using Type = T;
/* implicit */ Out(const Out& t) : raw(t.raw) {}
/* implicit */ Out(AutoOut<Type>& t) : raw(&t.raw) {} /* implicit */ Out(AutoOut<Type>& t) : raw(&t.raw) {}
/* implicit */ Out(Type* t) : raw(t) {} /* implicit */ Out(Type* t) : raw(t) {}
Out& operator=(const Out&) = delete;
Type* Get() const { Type* Get() const {
return raw; return raw;
@ -37,6 +38,10 @@ public:
return raw; return raw;
} }
operator Type*() const {
return raw;
}
private: private:
Type* raw; Type* raw;
}; };
@ -113,8 +118,10 @@ class OutCopyHandle {
public: public:
using Type = T*; using Type = T*;
/* implicit */ OutCopyHandle(const OutCopyHandle& t) : raw(t.raw) {}
/* implicit */ OutCopyHandle(AutoOut<Type>& t) : raw(&t.raw) {} /* implicit */ OutCopyHandle(AutoOut<Type>& t) : raw(&t.raw) {}
/* implicit */ OutCopyHandle(Type* t) : raw(t) {} /* implicit */ OutCopyHandle(Type* t) : raw(t) {}
OutCopyHandle& operator=(const OutCopyHandle&) = delete;
Type* Get() const { Type* Get() const {
return raw; return raw;
@ -128,6 +135,10 @@ public:
return raw; return raw;
} }
operator Type*() const {
return raw;
}
private: private:
Type* raw; Type* raw;
}; };
@ -137,8 +148,10 @@ class OutMoveHandle {
public: public:
using Type = T*; using Type = T*;
/* implicit */ OutMoveHandle(const OutMoveHandle& t) : raw(t.raw) {}
/* implicit */ OutMoveHandle(AutoOut<Type>& t) : raw(&t.raw) {} /* implicit */ OutMoveHandle(AutoOut<Type>& t) : raw(&t.raw) {}
/* implicit */ OutMoveHandle(Type* t) : raw(t) {} /* implicit */ OutMoveHandle(Type* t) : raw(t) {}
OutMoveHandle& operator=(const OutMoveHandle&) = delete;
Type* Get() const { Type* Get() const {
return raw; return raw;
@ -152,6 +165,10 @@ public:
return raw; return raw;
} }
operator Type*() const {
return raw;
}
private: private:
Type* raw; Type* raw;
}; };
@ -248,8 +265,10 @@ public:
static constexpr BufferAttr Attr = static_cast<BufferAttr>(A | BufferAttr_In | BufferAttr_FixedSize); static constexpr BufferAttr Attr = static_cast<BufferAttr>(A | BufferAttr_In | BufferAttr_FixedSize);
using Type = T; using Type = T;
/* implicit */ OutLargeData(const OutLargeData& t) : raw(t.raw) {}
/* implicit */ OutLargeData(Type* t) : raw(t) {} /* implicit */ OutLargeData(Type* t) : raw(t) {}
/* implicit */ OutLargeData(AutoOut<T>& t) : raw(&t.raw) {} /* implicit */ OutLargeData(AutoOut<T>& t) : raw(&t.raw) {}
OutLargeData& operator=(const OutLargeData&) = delete;
Type* Get() const { Type* Get() const {
return raw; return raw;
@ -263,6 +282,10 @@ public:
return raw; return raw;
} }
operator Type*() const {
return raw;
}
private: private:
Type* raw; Type* raw;
}; };

View File

@ -115,6 +115,11 @@ private:
if (type->GetName() == "save") { if (type->GetName() == "save") {
for (const auto& save_id : type->GetSubdirectories()) { for (const auto& save_id : type->GetSubdirectories()) {
for (const auto& user_id : save_id->GetSubdirectories()) { for (const auto& user_id : save_id->GetSubdirectories()) {
// Skip non user id subdirectories
if (user_id->GetName().size() != 0x20) {
continue;
}
const auto save_id_numeric = stoull_be(save_id->GetName()); const auto save_id_numeric = stoull_be(save_id->GetName());
auto user_id_numeric = Common::HexStringToArray<0x10>(user_id->GetName()); auto user_id_numeric = Common::HexStringToArray<0x10>(user_id->GetName());
std::reverse(user_id_numeric.begin(), user_id_numeric.end()); std::reverse(user_id_numeric.begin(), user_id_numeric.end());
@ -160,6 +165,10 @@ private:
} else if (space == FileSys::SaveDataSpaceId::TemporaryStorage) { } else if (space == FileSys::SaveDataSpaceId::TemporaryStorage) {
// Temporary Storage // Temporary Storage
for (const auto& user_id : type->GetSubdirectories()) { for (const auto& user_id : type->GetSubdirectories()) {
// Skip non user id subdirectories
if (user_id->GetName().size() != 0x20) {
continue;
}
for (const auto& title_id : user_id->GetSubdirectories()) { for (const auto& title_id : user_id->GetSubdirectories()) {
if (!title_id->GetFiles().empty() || if (!title_id->GetFiles().empty() ||
!title_id->GetSubdirectories().empty()) { !title_id->GetSubdirectories().empty()) {

View File

@ -65,6 +65,7 @@ Result MountTimeZoneBinary(Core::System& system) {
// Validate that the romfs is readable, using invalid firmware keys can cause this to get // Validate that the romfs is readable, using invalid firmware keys can cause this to get
// set but the files to be garbage. In that case, we want to hit the next path and // set but the files to be garbage. In that case, we want to hit the next path and
// synthesise them instead. // synthesise them instead.
g_time_zone_binary_mount_result = ResultSuccess;
Service::PSC::Time::LocationName name{"Etc/GMT"}; Service::PSC::Time::LocationName name{"Etc/GMT"};
if (!IsTimeZoneBinaryValid(name)) { if (!IsTimeZoneBinaryValid(name)) {
ResetTimeZoneBinary(); ResetTimeZoneBinary();

View File

@ -7,7 +7,6 @@
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h" #include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
#include "core/hle/service/nvnflinger/buffer_item.h" #include "core/hle/service/nvnflinger/buffer_item.h"
#include "core/hle/service/nvnflinger/buffer_item_consumer.h" #include "core/hle/service/nvnflinger/buffer_item_consumer.h"
#include "core/hle/service/nvnflinger/buffer_queue_producer.h"
#include "core/hle/service/nvnflinger/hardware_composer.h" #include "core/hle/service/nvnflinger/hardware_composer.h"
#include "core/hle/service/nvnflinger/hwc_layer.h" #include "core/hle/service/nvnflinger/hwc_layer.h"
#include "core/hle/service/nvnflinger/ui/graphic_buffer.h" #include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
@ -46,31 +45,9 @@ HardwareComposer::HardwareComposer() = default;
HardwareComposer::~HardwareComposer() = default; HardwareComposer::~HardwareComposer() = default;
u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, VI::Display& display, u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, VI::Display& display,
Nvidia::Devices::nvdisp_disp0& nvdisp, u32 frame_advance) { Nvidia::Devices::nvdisp_disp0& nvdisp) {
boost::container::small_vector<HwcLayer, 2> composition_stack; boost::container::small_vector<HwcLayer, 2> composition_stack;
m_frame_number += frame_advance;
// Release any necessary framebuffers.
for (auto& [layer_id, framebuffer] : m_framebuffers) {
if (framebuffer.release_frame_number > m_frame_number) {
// Not yet ready to release this framebuffer.
continue;
}
if (!framebuffer.is_acquired) {
// Already released.
continue;
}
if (auto* layer = display.FindLayer(layer_id); layer != nullptr) {
// TODO: support release fence
// This is needed to prevent screen tearing
layer->GetConsumer().ReleaseBuffer(framebuffer.item, android::Fence::NoFence());
framebuffer.is_acquired = false;
}
}
// Set default speed limit to 100%. // Set default speed limit to 100%.
*out_speed_scale = 1.0f; *out_speed_scale = 1.0f;
@ -143,7 +120,30 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, VI::Display& display,
MicroProfileFlip(); MicroProfileFlip();
// Advance by at least one frame. // Advance by at least one frame.
return swap_interval.value_or(1); const u32 frame_advance = swap_interval.value_or(1);
m_frame_number += frame_advance;
// Release any necessary framebuffers.
for (auto& [layer_id, framebuffer] : m_framebuffers) {
if (framebuffer.release_frame_number > m_frame_number) {
// Not yet ready to release this framebuffer.
continue;
}
if (!framebuffer.is_acquired) {
// Already released.
continue;
}
if (auto* layer = display.FindLayer(layer_id); layer != nullptr) {
// TODO: support release fence
// This is needed to prevent screen tearing
layer->GetConsumer().ReleaseBuffer(framebuffer.item, android::Fence::NoFence());
framebuffer.is_acquired = false;
}
}
return frame_advance;
} }
void HardwareComposer::RemoveLayerLocked(VI::Display& display, LayerId layer_id) { void HardwareComposer::RemoveLayerLocked(VI::Display& display, LayerId layer_id) {

View File

@ -27,7 +27,7 @@ public:
~HardwareComposer(); ~HardwareComposer();
u32 ComposeLocked(f32* out_speed_scale, VI::Display& display, u32 ComposeLocked(f32* out_speed_scale, VI::Display& display,
Nvidia::Devices::nvdisp_disp0& nvdisp, u32 frame_advance); Nvidia::Devices::nvdisp_disp0& nvdisp);
void RemoveLayerLocked(VI::Display& display, LayerId layer_id); void RemoveLayerLocked(VI::Display& display, LayerId layer_id);
private: private:

View File

@ -292,8 +292,7 @@ void Nvnflinger::Compose() {
auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>(disp_fd); auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>(disp_fd);
ASSERT(nvdisp); ASSERT(nvdisp);
swap_interval = display.GetComposer().ComposeLocked(&compose_speed_scale, display, *nvdisp, swap_interval = display.GetComposer().ComposeLocked(&compose_speed_scale, display, *nvdisp);
swap_interval);
} }
} }

View File

@ -9,6 +9,7 @@
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/hle/kernel/k_page_table.h" #include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_process_page_table.h"
#include "core/hle/service/hid/hid_server.h" #include "core/hle/service/hid/hid_server.h"
#include "core/hle/service/sm/sm.h" #include "core/hle/service/sm/sm.h"
#include "core/memory.h" #include "core/memory.h"
@ -85,8 +86,12 @@ VAddr StandardVmCallbacks::SanitizeAddress(VAddr in) const {
if ((in < metadata.main_nso_extents.base || if ((in < metadata.main_nso_extents.base ||
in >= metadata.main_nso_extents.base + metadata.main_nso_extents.size) && in >= metadata.main_nso_extents.base + metadata.main_nso_extents.size) &&
(in < metadata.heap_extents.base || (in < metadata.heap_extents.base ||
in >= metadata.heap_extents.base + metadata.heap_extents.size)) { in >= metadata.heap_extents.base + metadata.heap_extents.size) &&
LOG_ERROR(CheatEngine, (in < metadata.alias_extents.base ||
in >= metadata.heap_extents.base + metadata.alias_extents.size) &&
(in < metadata.aslr_extents.base ||
in >= metadata.heap_extents.base + metadata.aslr_extents.size)) {
LOG_DEBUG(CheatEngine,
"Cheat attempting to access memory at invalid address={:016X}, if this " "Cheat attempting to access memory at invalid address={:016X}, if this "
"persists, " "persists, "
"the cheat may be incorrect. However, this may be normal early in execution if " "the cheat may be incorrect. However, this may be normal early in execution if "
@ -211,16 +216,14 @@ void CheatEngine::Initialize() {
.base = GetInteger(page_table.GetHeapRegionStart()), .base = GetInteger(page_table.GetHeapRegionStart()),
.size = page_table.GetHeapRegionSize(), .size = page_table.GetHeapRegionSize(),
}; };
metadata.aslr_extents = {
metadata.address_space_extents = {
.base = GetInteger(page_table.GetAddressSpaceStart()),
.size = page_table.GetAddressSpaceSize(),
};
metadata.alias_extents = {
.base = GetInteger(page_table.GetAliasCodeRegionStart()), .base = GetInteger(page_table.GetAliasCodeRegionStart()),
.size = page_table.GetAliasCodeRegionSize(), .size = page_table.GetAliasCodeRegionSize(),
}; };
metadata.alias_extents = {
.base = GetInteger(page_table.GetAliasRegionStart()),
.size = page_table.GetAliasRegionSize(),
};
is_pending_reload.exchange(true); is_pending_reload.exchange(true);
} }

View File

@ -37,7 +37,7 @@ private:
VAddr SanitizeAddress(VAddr address) const; VAddr SanitizeAddress(VAddr address) const;
const CheatProcessMetadata& metadata; const CheatProcessMetadata& metadata;
System& system; Core::System& system;
}; };
// Intermediary class that parses a text file or other disk format for storing cheats into a // Intermediary class that parses a text file or other disk format for storing cheats into a

View File

@ -18,7 +18,7 @@ struct CheatProcessMetadata {
MemoryRegionExtents main_nso_extents{}; MemoryRegionExtents main_nso_extents{};
MemoryRegionExtents heap_extents{}; MemoryRegionExtents heap_extents{};
MemoryRegionExtents alias_extents{}; MemoryRegionExtents alias_extents{};
MemoryRegionExtents address_space_extents{}; MemoryRegionExtents aslr_extents{};
std::array<u8, 0x20> main_nso_build_id{}; std::array<u8, 0x20> main_nso_build_id{};
}; };

View File

@ -322,8 +322,9 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
} break; } break;
case CheatVmOpcodeType::EndConditionalBlock: { case CheatVmOpcodeType::EndConditionalBlock: {
// 20000000 // 20000000
// There's actually nothing left to process here! opcode.opcode = EndConditionalOpcode{
opcode.opcode = EndConditionalOpcode{}; .is_else = ((first_dword >> 24) & 0xf) == 1,
};
} break; } break;
case CheatVmOpcodeType::ControlLoop: { case CheatVmOpcodeType::ControlLoop: {
// 300R0000 VVVVVVVV // 300R0000 VVVVVVVV
@ -555,6 +556,18 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
.idx = first_dword & 0xF, .idx = first_dword & 0xF,
}; };
} break; } break;
case CheatVmOpcodeType::PauseProcess: {
/* FF0????? */
/* FF0 = opcode 0xFF0 */
/* Pauses the current process. */
opcode.opcode = PauseProcessOpcode{};
} break;
case CheatVmOpcodeType::ResumeProcess: {
/* FF0????? */
/* FF0 = opcode 0xFF0 */
/* Pauses the current process. */
opcode.opcode = ResumeProcessOpcode{};
} break;
case CheatVmOpcodeType::DebugLog: { case CheatVmOpcodeType::DebugLog: {
// FFFTIX## // FFFTIX##
// FFFTI0Ma aaaaaaaa // FFFTI0Ma aaaaaaaa
@ -621,7 +634,7 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
return valid; return valid;
} }
void DmntCheatVm::SkipConditionalBlock() { void DmntCheatVm::SkipConditionalBlock(bool is_if) {
if (condition_depth > 0) { if (condition_depth > 0) {
// We want to continue until we're out of the current block. // We want to continue until we're out of the current block.
const std::size_t desired_depth = condition_depth - 1; const std::size_t desired_depth = condition_depth - 1;
@ -637,8 +650,12 @@ void DmntCheatVm::SkipConditionalBlock() {
// We also support nesting of conditional blocks, and Gateway does not. // We also support nesting of conditional blocks, and Gateway does not.
if (skip_opcode.begin_conditional_block) { if (skip_opcode.begin_conditional_block) {
condition_depth++; condition_depth++;
} else if (std::holds_alternative<EndConditionalOpcode>(skip_opcode.opcode)) { } else if (auto end_cond = std::get_if<EndConditionalOpcode>(&skip_opcode.opcode)) {
condition_depth--; if (!end_cond->is_else) {
condition_depth--;
} else if (is_if && condition_depth - 1 == desired_depth) {
break;
}
} }
} }
} else { } else {
@ -675,6 +692,10 @@ u64 DmntCheatVm::GetCheatProcessAddress(const CheatProcessMetadata& metadata,
return metadata.main_nso_extents.base + rel_address; return metadata.main_nso_extents.base + rel_address;
case MemoryAccessType::Heap: case MemoryAccessType::Heap:
return metadata.heap_extents.base + rel_address; return metadata.heap_extents.base + rel_address;
case MemoryAccessType::Alias:
return metadata.alias_extents.base + rel_address;
case MemoryAccessType::Aslr:
return metadata.aslr_extents.base + rel_address;
} }
} }
@ -682,7 +703,6 @@ void DmntCheatVm::ResetState() {
registers.fill(0); registers.fill(0);
saved_values.fill(0); saved_values.fill(0);
loop_tops.fill(0); loop_tops.fill(0);
static_registers.fill(0);
instruction_ptr = 0; instruction_ptr = 0;
condition_depth = 0; condition_depth = 0;
decode_success = true; decode_success = true;
@ -794,13 +814,18 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
} }
// Skip conditional block if condition not met. // Skip conditional block if condition not met.
if (!cond_met) { if (!cond_met) {
SkipConditionalBlock(); SkipConditionalBlock(true);
} }
} else if (std::holds_alternative<EndConditionalOpcode>(cur_opcode.opcode)) { } else if (auto end_cond = std::get_if<EndConditionalOpcode>(&cur_opcode.opcode)) {
// Decrement the condition depth. if (end_cond->is_else) {
// We will assume, graciously, that mismatched conditional block ends are a nop. /* Skip to the end of the conditional block. */
if (condition_depth > 0) { this->SkipConditionalBlock(false);
condition_depth--; } else {
/* Decrement the condition depth. */
/* We will assume, graciously, that mismatched conditional block ends are a nop. */
if (condition_depth > 0) {
condition_depth--;
}
} }
} else if (auto ctrl_loop = std::get_if<ControlLoopOpcode>(&cur_opcode.opcode)) { } else if (auto ctrl_loop = std::get_if<ControlLoopOpcode>(&cur_opcode.opcode)) {
if (ctrl_loop->start_loop) { if (ctrl_loop->start_loop) {
@ -908,7 +933,7 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
// Check for keypress. // Check for keypress.
if ((begin_keypress_cond->key_mask & kDown) != begin_keypress_cond->key_mask) { if ((begin_keypress_cond->key_mask & kDown) != begin_keypress_cond->key_mask) {
// Keys not pressed. Skip conditional block. // Keys not pressed. Skip conditional block.
SkipConditionalBlock(); SkipConditionalBlock(true);
} }
} else if (auto perform_math_reg = } else if (auto perform_math_reg =
std::get_if<PerformArithmeticRegisterOpcode>(&cur_opcode.opcode)) { std::get_if<PerformArithmeticRegisterOpcode>(&cur_opcode.opcode)) {
@ -1116,7 +1141,7 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
// Skip conditional block if condition not met. // Skip conditional block if condition not met.
if (!cond_met) { if (!cond_met) {
SkipConditionalBlock(); SkipConditionalBlock(true);
} }
} else if (auto save_restore_reg = } else if (auto save_restore_reg =
std::get_if<SaveRestoreRegisterOpcode>(&cur_opcode.opcode)) { std::get_if<SaveRestoreRegisterOpcode>(&cur_opcode.opcode)) {
@ -1178,6 +1203,10 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
// Store a register to a static register. // Store a register to a static register.
static_registers[rw_static_reg->static_idx] = registers[rw_static_reg->idx]; static_registers[rw_static_reg->static_idx] = registers[rw_static_reg->idx];
} }
} else if (std::holds_alternative<PauseProcessOpcode>(cur_opcode.opcode)) {
// TODO: Pause cheat process
} else if (std::holds_alternative<ResumeProcessOpcode>(cur_opcode.opcode)) {
// TODO: Resume cheat process
} else if (auto debug_log = std::get_if<DebugLogOpcode>(&cur_opcode.opcode)) { } else if (auto debug_log = std::get_if<DebugLogOpcode>(&cur_opcode.opcode)) {
// Read value from memory. // Read value from memory.
u64 log_value = 0; u64 log_value = 0;

View File

@ -42,12 +42,16 @@ enum class CheatVmOpcodeType : u32 {
DoubleExtendedWidth = 0xF0, DoubleExtendedWidth = 0xF0,
// Double-extended width opcodes. // Double-extended width opcodes.
PauseProcess = 0xFF0,
ResumeProcess = 0xFF1,
DebugLog = 0xFFF, DebugLog = 0xFFF,
}; };
enum class MemoryAccessType : u32 { enum class MemoryAccessType : u32 {
MainNso = 0, MainNso = 0,
Heap = 1, Heap = 1,
Alias = 2,
Aslr = 3,
}; };
enum class ConditionalComparisonType : u32 { enum class ConditionalComparisonType : u32 {
@ -131,7 +135,9 @@ struct BeginConditionalOpcode {
VmInt value{}; VmInt value{};
}; };
struct EndConditionalOpcode {}; struct EndConditionalOpcode {
bool is_else;
};
struct ControlLoopOpcode { struct ControlLoopOpcode {
bool start_loop{}; bool start_loop{};
@ -222,6 +228,10 @@ struct ReadWriteStaticRegisterOpcode {
u32 idx{}; u32 idx{};
}; };
struct PauseProcessOpcode {};
struct ResumeProcessOpcode {};
struct DebugLogOpcode { struct DebugLogOpcode {
u32 bit_width{}; u32 bit_width{};
u32 log_id{}; u32 log_id{};
@ -244,8 +254,8 @@ struct CheatVmOpcode {
PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode, PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode, PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode, BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, DebugLogOpcode, SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, PauseProcessOpcode,
UnrecognizedInstruction> ResumeProcessOpcode, DebugLogOpcode, UnrecognizedInstruction>
opcode{}; opcode{};
}; };
@ -296,7 +306,7 @@ private:
std::array<std::size_t, NumRegisters> loop_tops{}; std::array<std::size_t, NumRegisters> loop_tops{};
bool DecodeNextOpcode(CheatVmOpcode& out); bool DecodeNextOpcode(CheatVmOpcode& out);
void SkipConditionalBlock(); void SkipConditionalBlock(bool is_if);
void ResetState(); void ResetState();
// For implementing the DebugLog opcode. // For implementing the DebugLog opcode.

View File

@ -12,6 +12,11 @@ namespace Shader::Backend::SPIRV {
namespace { namespace {
class ImageOperands { class ImageOperands {
public: public:
[[maybe_unused]] static constexpr bool ImageSampleOffsetAllowed = false;
[[maybe_unused]] static constexpr bool ImageGatherOffsetAllowed = true;
[[maybe_unused]] static constexpr bool ImageFetchOffsetAllowed = false;
[[maybe_unused]] static constexpr bool ImageGradientOffsetAllowed = false;
explicit ImageOperands(EmitContext& ctx, bool has_bias, bool has_lod, bool has_lod_clamp, explicit ImageOperands(EmitContext& ctx, bool has_bias, bool has_lod, bool has_lod_clamp,
Id lod, const IR::Value& offset) { Id lod, const IR::Value& offset) {
if (has_bias) { if (has_bias) {
@ -22,7 +27,7 @@ public:
const Id lod_value{has_lod_clamp ? ctx.OpCompositeExtract(ctx.F32[1], lod, 0) : lod}; const Id lod_value{has_lod_clamp ? ctx.OpCompositeExtract(ctx.F32[1], lod, 0) : lod};
Add(spv::ImageOperandsMask::Lod, lod_value); Add(spv::ImageOperandsMask::Lod, lod_value);
} }
AddOffset(ctx, offset); AddOffset(ctx, offset, ImageSampleOffsetAllowed);
if (has_lod_clamp) { if (has_lod_clamp) {
const Id lod_clamp{has_bias ? ctx.OpCompositeExtract(ctx.F32[1], lod, 1) : lod}; const Id lod_clamp{has_bias ? ctx.OpCompositeExtract(ctx.F32[1], lod, 1) : lod};
Add(spv::ImageOperandsMask::MinLod, lod_clamp); Add(spv::ImageOperandsMask::MinLod, lod_clamp);
@ -55,20 +60,18 @@ public:
Add(spv::ImageOperandsMask::ConstOffsets, offsets); Add(spv::ImageOperandsMask::ConstOffsets, offsets);
} }
explicit ImageOperands(Id offset, Id lod, Id ms) { explicit ImageOperands(EmitContext& ctx, const IR::Value& offset, Id lod, Id ms) {
AddOffset(ctx, offset, ImageFetchOffsetAllowed);
if (Sirit::ValidId(lod)) { if (Sirit::ValidId(lod)) {
Add(spv::ImageOperandsMask::Lod, lod); Add(spv::ImageOperandsMask::Lod, lod);
} }
if (Sirit::ValidId(offset)) {
Add(spv::ImageOperandsMask::Offset, offset);
}
if (Sirit::ValidId(ms)) { if (Sirit::ValidId(ms)) {
Add(spv::ImageOperandsMask::Sample, ms); Add(spv::ImageOperandsMask::Sample, ms);
} }
} }
explicit ImageOperands(EmitContext& ctx, bool has_lod_clamp, Id derivatives, explicit ImageOperands(EmitContext& ctx, bool has_lod_clamp, Id derivatives,
u32 num_derivatives, Id offset, Id lod_clamp) { u32 num_derivatives, const IR::Value& offset, Id lod_clamp) {
if (!Sirit::ValidId(derivatives)) { if (!Sirit::ValidId(derivatives)) {
throw LogicError("Derivatives must be present"); throw LogicError("Derivatives must be present");
} }
@ -83,16 +86,14 @@ public:
const Id derivatives_Y{ctx.OpCompositeConstruct( const Id derivatives_Y{ctx.OpCompositeConstruct(
ctx.F32[num_derivatives], std::span{deriv_y_accum.data(), deriv_y_accum.size()})}; ctx.F32[num_derivatives], std::span{deriv_y_accum.data(), deriv_y_accum.size()})};
Add(spv::ImageOperandsMask::Grad, derivatives_X, derivatives_Y); Add(spv::ImageOperandsMask::Grad, derivatives_X, derivatives_Y);
if (Sirit::ValidId(offset)) { AddOffset(ctx, offset, ImageGradientOffsetAllowed);
Add(spv::ImageOperandsMask::Offset, offset);
}
if (has_lod_clamp) { if (has_lod_clamp) {
Add(spv::ImageOperandsMask::MinLod, lod_clamp); Add(spv::ImageOperandsMask::MinLod, lod_clamp);
} }
} }
explicit ImageOperands(EmitContext& ctx, bool has_lod_clamp, Id derivatives_1, Id derivatives_2, explicit ImageOperands(EmitContext& ctx, bool has_lod_clamp, Id derivatives_1, Id derivatives_2,
Id offset, Id lod_clamp) { const IR::Value& offset, Id lod_clamp) {
if (!Sirit::ValidId(derivatives_1) || !Sirit::ValidId(derivatives_2)) { if (!Sirit::ValidId(derivatives_1) || !Sirit::ValidId(derivatives_2)) {
throw LogicError("Derivatives must be present"); throw LogicError("Derivatives must be present");
} }
@ -111,9 +112,7 @@ public:
const Id derivatives_id2{ctx.OpCompositeConstruct( const Id derivatives_id2{ctx.OpCompositeConstruct(
ctx.F32[3], std::span{deriv_2_accum.data(), deriv_2_accum.size()})}; ctx.F32[3], std::span{deriv_2_accum.data(), deriv_2_accum.size()})};
Add(spv::ImageOperandsMask::Grad, derivatives_id1, derivatives_id2); Add(spv::ImageOperandsMask::Grad, derivatives_id1, derivatives_id2);
if (Sirit::ValidId(offset)) { AddOffset(ctx, offset, ImageGradientOffsetAllowed);
Add(spv::ImageOperandsMask::Offset, offset);
}
if (has_lod_clamp) { if (has_lod_clamp) {
Add(spv::ImageOperandsMask::MinLod, lod_clamp); Add(spv::ImageOperandsMask::MinLod, lod_clamp);
} }
@ -132,7 +131,7 @@ public:
} }
private: private:
void AddOffset(EmitContext& ctx, const IR::Value& offset) { void AddOffset(EmitContext& ctx, const IR::Value& offset, bool runtime_offset_allowed) {
if (offset.IsEmpty()) { if (offset.IsEmpty()) {
return; return;
} }
@ -165,7 +164,9 @@ private:
break; break;
} }
} }
Add(spv::ImageOperandsMask::Offset, ctx.Def(offset)); if (runtime_offset_allowed) {
Add(spv::ImageOperandsMask::Offset, ctx.Def(offset));
}
} }
void Add(spv::ImageOperandsMask new_mask, Id value) { void Add(spv::ImageOperandsMask new_mask, Id value) {
@ -493,8 +494,8 @@ Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, const IR::Value& index,
operands.Span()); operands.Span());
} }
Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id offset, Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id lod, Id ms) { const IR::Value& offset, Id lod, Id ms) {
const auto info{inst->Flags<IR::TextureInstInfo>()}; const auto info{inst->Flags<IR::TextureInstInfo>()};
if (info.type == TextureType::Buffer) { if (info.type == TextureType::Buffer) {
lod = Id{}; lod = Id{};
@ -503,7 +504,7 @@ Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id c
// This image is multisampled, lod must be implicit // This image is multisampled, lod must be implicit
lod = Id{}; lod = Id{};
} }
const ImageOperands operands(offset, lod, ms); const ImageOperands operands(ctx, offset, lod, ms);
return Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst, ctx.F32[4], return Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst, ctx.F32[4],
TextureImage(ctx, info, index), coords, operands.MaskOptional(), operands.Span()); TextureImage(ctx, info, index), coords, operands.MaskOptional(), operands.Span());
} }
@ -548,13 +549,13 @@ Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, I
} }
Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id derivatives, Id offset, Id lod_clamp) { Id derivatives, const IR::Value& offset, Id lod_clamp) {
const auto info{inst->Flags<IR::TextureInstInfo>()}; const auto info{inst->Flags<IR::TextureInstInfo>()};
const auto operands = const auto operands = info.num_derivatives == 3
info.num_derivatives == 3 ? ImageOperands(ctx, info.has_lod_clamp != 0, derivatives,
? ImageOperands(ctx, info.has_lod_clamp != 0, derivatives, offset, {}, lod_clamp) ctx.Def(offset), {}, lod_clamp)
: ImageOperands(ctx, info.has_lod_clamp != 0, derivatives, info.num_derivatives, offset, : ImageOperands(ctx, info.has_lod_clamp != 0, derivatives,
lod_clamp); info.num_derivatives, offset, lod_clamp);
return Emit(&EmitContext::OpImageSparseSampleExplicitLod, return Emit(&EmitContext::OpImageSparseSampleExplicitLod,
&EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4], &EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4],
Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); Texture(ctx, info, index), coords, operands.Mask(), operands.Span());

View File

@ -537,13 +537,13 @@ Id EmitImageGather(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id
const IR::Value& offset, const IR::Value& offset2); const IR::Value& offset, const IR::Value& offset2);
Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
const IR::Value& offset, const IR::Value& offset2, Id dref); const IR::Value& offset, const IR::Value& offset2, Id dref);
Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id offset, Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id lod, Id ms); const IR::Value& offset, Id lod, Id ms);
Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id lod, Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id lod,
const IR::Value& skip_mips); const IR::Value& skip_mips);
Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords); Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords);
Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id derivatives, Id offset, Id lod_clamp); Id derivatives, const IR::Value& offset, Id lod_clamp);
Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords); Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords);
void EmitImageWrite(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id color); void EmitImageWrite(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id color);
Id EmitIsTextureScaled(EmitContext& ctx, const IR::Value& index); Id EmitIsTextureScaled(EmitContext& ctx, const IR::Value& index);

View File

@ -13,20 +13,101 @@ Scheduler::Scheduler(GPU& gpu_) : gpu{gpu_} {}
Scheduler::~Scheduler() = default; Scheduler::~Scheduler() = default;
void Scheduler::Init() {
master_control = Common::Fiber::ThreadToFiber();
}
void Scheduler::Resume() {
bool nothing_pending;
do {
nothing_pending = true;
current_fifo = nullptr;
{
std::unique_lock lk(scheduling_guard);
size_t num_iters = gpfifos.size();
for (size_t i = 0; i < num_iters; i++) {
size_t current_id = (current_fifo_rotation_id + i) % gpfifos.size();
auto& fifo = gpfifos[current_id];
if (!fifo.is_active) {
continue;
}
std::scoped_lock lk2(fifo.guard);
if (!fifo.pending_work.empty() || fifo.working.load(std::memory_order_acquire)) {
current_fifo = &fifo;
current_fifo_rotation_id = current_id;
nothing_pending = false;
break;
}
}
}
if (current_fifo) {
Common::Fiber::YieldTo(master_control, *current_fifo->context);
current_fifo = nullptr;
}
} while (!nothing_pending);
}
void Scheduler::Yield() {
ASSERT(current_fifo != nullptr);
Common::Fiber::YieldTo(current_fifo->context, *master_control);
}
void Scheduler::Push(s32 channel, CommandList&& entries) { void Scheduler::Push(s32 channel, CommandList&& entries) {
std::unique_lock lk(scheduling_guard); std::unique_lock lk(scheduling_guard);
auto it = channels.find(channel); auto it = channel_gpfifo_ids.find(channel);
ASSERT(it != channels.end()); ASSERT(it != channel_gpfifo_ids.end());
auto channel_state = it->second; auto gpfifo_id = it->second;
gpu.BindChannel(channel_state->bind_id); auto& fifo = gpfifos[gpfifo_id];
channel_state->dma_pusher->Push(std::move(entries)); {
channel_state->dma_pusher->DispatchCalls(); std::scoped_lock lk2(fifo.guard);
fifo.pending_work.emplace_back(std::move(entries));
}
}
void Scheduler::ChannelLoop(size_t gpfifo_id, s32 channel_id) {
gpu.BindChannel(channel_id);
auto& fifo = gpfifos[gpfifo_id];
while (true) {
auto* channel_state = channels[channel_id].get();
fifo.guard.lock();
while (!fifo.pending_work.empty()) {
{
fifo.working.store(true, std::memory_order_release);
CommandList&& entries = std::move(fifo.pending_work.front());
channel_state->dma_pusher->Push(std::move(entries));
fifo.pending_work.pop_front();
}
fifo.guard.unlock();
channel_state->dma_pusher->DispatchCalls();
fifo.guard.lock();
}
fifo.working.store(false, std::memory_order_relaxed);
fifo.guard.unlock();
Common::Fiber::YieldTo(fifo.context, *master_control);
gpu.BindChannel(channel_id);
}
} }
void Scheduler::DeclareChannel(std::shared_ptr<ChannelState> new_channel) { void Scheduler::DeclareChannel(std::shared_ptr<ChannelState> new_channel) {
s32 channel = new_channel->bind_id; s32 channel = new_channel->bind_id;
std::unique_lock lk(scheduling_guard); std::unique_lock lk(scheduling_guard);
channels.emplace(channel, new_channel); channels.emplace(channel, new_channel);
size_t new_fifo_id;
if (!free_fifos.empty()) {
new_fifo_id = free_fifos.front();
free_fifos.pop_front();
} else {
new_fifo_id = gpfifos.size();
gpfifos.emplace_back();
}
auto& new_fifo = gpfifos[new_fifo_id];
channel_gpfifo_ids[channel] = new_fifo_id;
new_fifo.is_active = true;
new_fifo.bind_id = channel;
new_fifo.pending_work.clear();
std::function<void()> callback = std::bind(&Scheduler::ChannelLoop, this, new_fifo_id, channel);
new_fifo.context = std::make_shared<Common::Fiber>(std::move(callback));
} }
} // namespace Tegra::Control } // namespace Tegra::Control

View File

@ -3,10 +3,13 @@
#pragma once #pragma once
#include <atomic>
#include <deque>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <unordered_map> #include <unordered_map>
#include "common/fiber.h"
#include "video_core/dma_pusher.h" #include "video_core/dma_pusher.h"
namespace Tegra { namespace Tegra {
@ -22,14 +25,36 @@ public:
explicit Scheduler(GPU& gpu_); explicit Scheduler(GPU& gpu_);
~Scheduler(); ~Scheduler();
void Init();
void Resume();
void Yield();
void Push(s32 channel, CommandList&& entries); void Push(s32 channel, CommandList&& entries);
void DeclareChannel(std::shared_ptr<ChannelState> new_channel); void DeclareChannel(std::shared_ptr<ChannelState> new_channel);
private: private:
void ChannelLoop(size_t gpfifo_id, s32 channel_id);
std::unordered_map<s32, std::shared_ptr<ChannelState>> channels; std::unordered_map<s32, std::shared_ptr<ChannelState>> channels;
std::unordered_map<s32, size_t> channel_gpfifo_ids;
std::mutex scheduling_guard; std::mutex scheduling_guard;
std::shared_ptr<Common::Fiber> master_control;
struct GPFifoContext {
bool is_active;
std::shared_ptr<Common::Fiber> context;
std::deque<CommandList> pending_work;
std::atomic<bool> working{};
std::mutex guard;
s32 bind_id;
};
std::deque<GPFifoContext> gpfifos;
std::deque<size_t> free_fifos;
GPU& gpu; GPU& gpu;
size_t current_fifo_rotation_id{};
GPFifoContext* current_fifo{};
}; };
} // namespace Control } // namespace Control

View File

@ -6,6 +6,7 @@
#include "common/settings.h" #include "common/settings.h"
#include "core/core.h" #include "core/core.h"
#include "video_core/control/channel_state.h" #include "video_core/control/channel_state.h"
#include "video_core/control/scheduler.h"
#include "video_core/dma_pusher.h" #include "video_core/dma_pusher.h"
#include "video_core/engines/fermi_2d.h" #include "video_core/engines/fermi_2d.h"
#include "video_core/engines/kepler_compute.h" #include "video_core/engines/kepler_compute.h"
@ -14,6 +15,8 @@
#include "video_core/engines/maxwell_dma.h" #include "video_core/engines/maxwell_dma.h"
#include "video_core/engines/puller.h" #include "video_core/engines/puller.h"
#include "video_core/gpu.h" #include "video_core/gpu.h"
#include "video_core/host1x/host1x.h"
#include "video_core/host1x/syncpoint_manager.h"
#include "video_core/memory_manager.h" #include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h" #include "video_core/rasterizer_interface.h"
@ -60,11 +63,14 @@ void Puller::ProcessBindMethod(const MethodCall& method_call) {
} }
void Puller::ProcessFenceActionMethod() { void Puller::ProcessFenceActionMethod() {
auto& syncpoint_manager = gpu.Host1x().GetSyncpointManager();
switch (regs.fence_action.op) { switch (regs.fence_action.op) {
case Puller::FenceOperation::Acquire: case Puller::FenceOperation::Acquire:
// UNIMPLEMENTED_MSG("Channel Scheduling pending."); while (regs.fence_value >
// WaitFence(regs.fence_action.syncpoint_id, regs.fence_value); syncpoint_manager.GetGuestSyncpointValue(regs.fence_action.syncpoint_id)) {
rasterizer->ReleaseFences(); rasterizer->ReleaseFences();
gpu.Scheduler().Yield();
}
break; break;
case Puller::FenceOperation::Increment: case Puller::FenceOperation::Increment:
rasterizer->SignalSyncPoint(regs.fence_action.syncpoint_id); rasterizer->SignalSyncPoint(regs.fence_action.syncpoint_id);

View File

@ -387,6 +387,14 @@ std::shared_ptr<Control::ChannelState> GPU::AllocateChannel() {
return impl->AllocateChannel(); return impl->AllocateChannel();
} }
Tegra::Control::Scheduler& GPU::Scheduler() {
return *impl->scheduler;
}
const Tegra::Control::Scheduler& GPU::Scheduler() const {
return *impl->scheduler;
}
void GPU::InitChannel(Control::ChannelState& to_init) { void GPU::InitChannel(Control::ChannelState& to_init) {
impl->InitChannel(to_init); impl->InitChannel(to_init);
} }

View File

@ -124,7 +124,8 @@ class KeplerCompute;
namespace Control { namespace Control {
struct ChannelState; struct ChannelState;
} class Scheduler;
} // namespace Control
namespace Host1x { namespace Host1x {
class Host1x; class Host1x;
@ -204,6 +205,12 @@ public:
/// Returns a const reference to the shader notifier. /// Returns a const reference to the shader notifier.
[[nodiscard]] const VideoCore::ShaderNotify& ShaderNotify() const; [[nodiscard]] const VideoCore::ShaderNotify& ShaderNotify() const;
/// Returns GPU Channel Scheduler.
[[nodiscard]] Tegra::Control::Scheduler& Scheduler();
/// Returns GPU Channel Scheduler.
[[nodiscard]] const Tegra::Control::Scheduler& Scheduler() const;
[[nodiscard]] u64 GetTicks() const; [[nodiscard]] u64 GetTicks() const;
[[nodiscard]] bool IsAsync() const; [[nodiscard]] bool IsAsync() const;

View File

@ -34,13 +34,15 @@ static void RunThread(std::stop_token stop_token, Core::System& system,
CommandDataContainer next; CommandDataContainer next;
scheduler.Init();
while (!stop_token.stop_requested()) { while (!stop_token.stop_requested()) {
state.queue.PopWait(next, stop_token); state.queue.PopWait(next, stop_token);
if (stop_token.stop_requested()) { if (stop_token.stop_requested()) {
break; break;
} }
if (auto* submit_list = std::get_if<SubmitListCommand>(&next.data)) { if (std::holds_alternative<SubmitListCommand>(next.data)) {
scheduler.Push(submit_list->channel, std::move(submit_list->entries)); scheduler.Resume();
} else if (std::holds_alternative<GPUTickCommand>(next.data)) { } else if (std::holds_alternative<GPUTickCommand>(next.data)) {
system.GPU().TickWork(); system.GPU().TickWork();
} else if (const auto* flush = std::get_if<FlushRegionCommand>(&next.data)) { } else if (const auto* flush = std::get_if<FlushRegionCommand>(&next.data)) {
@ -67,14 +69,16 @@ ThreadManager::~ThreadManager() = default;
void ThreadManager::StartThread(VideoCore::RendererBase& renderer, void ThreadManager::StartThread(VideoCore::RendererBase& renderer,
Core::Frontend::GraphicsContext& context, Core::Frontend::GraphicsContext& context,
Tegra::Control::Scheduler& scheduler) { Tegra::Control::Scheduler& scheduler_) {
rasterizer = renderer.ReadRasterizer(); rasterizer = renderer.ReadRasterizer();
scheduler = &scheduler_;
thread = std::jthread(RunThread, std::ref(system), std::ref(renderer), std::ref(context), thread = std::jthread(RunThread, std::ref(system), std::ref(renderer), std::ref(context),
std::ref(scheduler), std::ref(state)); std::ref(scheduler_), std::ref(state));
} }
void ThreadManager::SubmitList(s32 channel, Tegra::CommandList&& entries) { void ThreadManager::SubmitList(s32 channel, Tegra::CommandList&& entries) {
PushCommand(SubmitListCommand(channel, std::move(entries))); scheduler->Push(channel, std::move(entries));
PushCommand(SubmitListCommand());
} }
void ThreadManager::FlushRegion(DAddr addr, u64 size) { void ThreadManager::FlushRegion(DAddr addr, u64 size) {

View File

@ -36,13 +36,7 @@ class RendererBase;
namespace VideoCommon::GPUThread { namespace VideoCommon::GPUThread {
/// Command to signal to the GPU thread that a command list is ready for processing /// Command to signal to the GPU thread that a command list is ready for processing
struct SubmitListCommand final { struct SubmitListCommand final {};
explicit SubmitListCommand(s32 channel_, Tegra::CommandList&& entries_)
: channel{channel_}, entries{std::move(entries_)} {}
s32 channel;
Tegra::CommandList entries;
};
/// Command to signal to the GPU thread to flush a region /// Command to signal to the GPU thread to flush a region
struct FlushRegionCommand final { struct FlushRegionCommand final {
@ -124,6 +118,7 @@ public:
private: private:
/// Pushes a command to be executed by the GPU thread /// Pushes a command to be executed by the GPU thread
u64 PushCommand(CommandData&& command_data, bool block = false); u64 PushCommand(CommandData&& command_data, bool block = false);
Tegra::Control::Scheduler* scheduler;
Core::System& system; Core::System& system;
const bool is_async; const bool is_async;

View File

@ -1353,6 +1353,13 @@ void GMainWindow::InitializeHotkeys() {
LinkActionShortcut(ui->action_TAS_Start, QStringLiteral("TAS Start/Stop"), true); LinkActionShortcut(ui->action_TAS_Start, QStringLiteral("TAS Start/Stop"), true);
LinkActionShortcut(ui->action_TAS_Record, QStringLiteral("TAS Record"), true); LinkActionShortcut(ui->action_TAS_Record, QStringLiteral("TAS Record"), true);
LinkActionShortcut(ui->action_TAS_Reset, QStringLiteral("TAS Reset"), true); LinkActionShortcut(ui->action_TAS_Reset, QStringLiteral("TAS Reset"), true);
LinkActionShortcut(ui->action_View_Lobby,
QStringLiteral("Multiplayer Browse Public Game Lobby"));
LinkActionShortcut(ui->action_Start_Room, QStringLiteral("Multiplayer Create Room"));
LinkActionShortcut(ui->action_Connect_To_Room,
QStringLiteral("Multiplayer Direct Connect to Room"));
LinkActionShortcut(ui->action_Show_Room, QStringLiteral("Multiplayer Show Current Room"));
LinkActionShortcut(ui->action_Leave_Room, QStringLiteral("Multiplayer Leave Room"));
static const QString main_window = QStringLiteral("Main Window"); static const QString main_window = QStringLiteral("Main Window");
const auto connect_shortcut = [&]<typename Fn>(const QString& action_name, const Fn& function) { const auto connect_shortcut = [&]<typename Fn>(const QString& action_name, const Fn& function) {

View File

@ -77,16 +77,23 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list,
// UI Buttons // UI Buttons
connect(ui->refresh_list, &QPushButton::clicked, this, &Lobby::RefreshLobby); connect(ui->refresh_list, &QPushButton::clicked, this, &Lobby::RefreshLobby);
connect(ui->search, &QLineEdit::textChanged, proxy, &LobbyFilterProxyModel::SetFilterSearch);
connect(ui->games_owned, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterOwned); connect(ui->games_owned, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterOwned);
connect(ui->hide_empty, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterEmpty); connect(ui->hide_empty, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterEmpty);
connect(ui->hide_full, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterFull); connect(ui->hide_full, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterFull);
connect(ui->search, &QLineEdit::textChanged, proxy, &LobbyFilterProxyModel::SetFilterSearch);
connect(ui->room_list, &QTreeView::doubleClicked, this, &Lobby::OnJoinRoom); connect(ui->room_list, &QTreeView::doubleClicked, this, &Lobby::OnJoinRoom);
connect(ui->room_list, &QTreeView::clicked, this, &Lobby::OnExpandRoom); connect(ui->room_list, &QTreeView::clicked, this, &Lobby::OnExpandRoom);
// Actions // Actions
connect(&room_list_watcher, &QFutureWatcher<AnnounceMultiplayerRoom::RoomList>::finished, this, connect(&room_list_watcher, &QFutureWatcher<AnnounceMultiplayerRoom::RoomList>::finished, this,
&Lobby::OnRefreshLobby); &Lobby::OnRefreshLobby);
// Load persistent filters after events are connected to make sure they apply
ui->search->setText(
QString::fromStdString(UISettings::values.multiplayer_filter_text.GetValue()));
ui->games_owned->setChecked(UISettings::values.multiplayer_filter_games_owned.GetValue());
ui->hide_empty->setChecked(UISettings::values.multiplayer_filter_hide_empty.GetValue());
ui->hide_full->setChecked(UISettings::values.multiplayer_filter_hide_full.GetValue());
} }
Lobby::~Lobby() = default; Lobby::~Lobby() = default;
@ -204,6 +211,10 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
// Save settings // Save settings
UISettings::values.multiplayer_nickname = ui->nickname->text().toStdString(); UISettings::values.multiplayer_nickname = ui->nickname->text().toStdString();
UISettings::values.multiplayer_filter_text = ui->search->text().toStdString();
UISettings::values.multiplayer_filter_games_owned = ui->games_owned->isChecked();
UISettings::values.multiplayer_filter_hide_empty = ui->hide_empty->isChecked();
UISettings::values.multiplayer_filter_hide_full = ui->hide_full->isChecked();
UISettings::values.multiplayer_ip = UISettings::values.multiplayer_ip =
proxy->data(connection_index, LobbyItemHost::HostIPRole).value<QString>().toStdString(); proxy->data(connection_index, LobbyItemHost::HostIPRole).value<QString>().toStdString();
UISettings::values.multiplayer_port = UISettings::values.multiplayer_port =

View File

@ -193,12 +193,29 @@ public:
} }
QVariant data(int role) const override { QVariant data(int role) const override {
if (role != Qt::DisplayRole) { switch (role) {
case Qt::DisplayRole: {
auto members = data(MemberListRole).toList();
return QStringLiteral("%1 / %2 ")
.arg(QString::number(members.size()), data(MaxPlayerRole).toString());
}
case Qt::ForegroundRole: {
auto members = data(MemberListRole).toList();
auto max_players = data(MaxPlayerRole).toInt();
if (members.size() >= max_players) {
return QBrush(QColor(255, 48, 32));
} else if (members.size() == (max_players - 1)) {
return QBrush(QColor(255, 140, 32));
} else if (members.size() == 0) {
return QBrush(QColor(128, 128, 128));
}
// FIXME: How to return a value that tells Qt not to modify the
// text color from the default (as if Qt::ForegroundRole wasn't overridden)?
return QBrush(nullptr);
}
default:
return LobbyItem::data(role); return LobbyItem::data(role);
} }
auto members = data(MemberListRole).toList();
return QStringLiteral("%1 / %2 ")
.arg(QString::number(members.size()), data(MaxPlayerRole).toString());
} }
bool operator<(const QStandardItem& other) const override { bool operator<(const QStandardItem& other) const override {

View File

@ -169,6 +169,13 @@ struct Values {
// multiplayer settings // multiplayer settings
Setting<std::string> multiplayer_nickname{linkage, {}, "nickname", Category::Multiplayer}; Setting<std::string> multiplayer_nickname{linkage, {}, "nickname", Category::Multiplayer};
Setting<std::string> multiplayer_filter_text{linkage, {}, "filter_text", Category::Multiplayer};
Setting<bool> multiplayer_filter_games_owned{linkage, false, "filter_games_owned",
Category::Multiplayer};
Setting<bool> multiplayer_filter_hide_empty{linkage, false, "filter_games_hide_empty",
Category::Multiplayer};
Setting<bool> multiplayer_filter_hide_full{linkage, false, "filter_games_hide_full",
Category::Multiplayer};
Setting<std::string> multiplayer_ip{linkage, {}, "ip", Category::Multiplayer}; Setting<std::string> multiplayer_ip{linkage, {}, "ip", Category::Multiplayer};
Setting<u16, true> multiplayer_port{linkage, 24872, 0, Setting<u16, true> multiplayer_port{linkage, 24872, 0,
UINT16_MAX, "port", Category::Multiplayer}; UINT16_MAX, "port", Category::Multiplayer};
@ -222,7 +229,7 @@ void RestoreWindowState(std::unique_ptr<QtConfig>& qtConfig);
// This must be in alphabetical order according to action name as it must have the same order as // This must be in alphabetical order according to action name as it must have the same order as
// UISetting::values.shortcuts, which is alphabetically ordered. // UISetting::values.shortcuts, which is alphabetically ordered.
// clang-format off // clang-format off
const std::array<Shortcut, 23> default_hotkeys{{ const std::array<Shortcut, 28> default_hotkeys{{
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+M"), std::string("Home+Dpad_Right"), Qt::WindowShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+M"), std::string("Home+Dpad_Right"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("-"), std::string("Home+Dpad_Down"), Qt::ApplicationShortcut, true}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("-"), std::string("Home+Dpad_Down"), Qt::ApplicationShortcut, true}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("="), std::string("Home+Dpad_Up"), Qt::ApplicationShortcut, true}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("="), std::string("Home+Dpad_Up"), Qt::ApplicationShortcut, true}},
@ -236,6 +243,11 @@ const std::array<Shortcut, 23> default_hotkeys{{
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F11"), std::string("Home+B"), Qt::WindowShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F11"), std::string("Home+B"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+O"), std::string(""), Qt::WidgetWithChildrenShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+O"), std::string(""), Qt::WidgetWithChildrenShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F2"), std::string("Home+A"), Qt::WidgetWithChildrenShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F2"), std::string("Home+A"), Qt::WidgetWithChildrenShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Multiplayer Browse Public Game Lobby")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+B"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Multiplayer Create Room")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+N"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Multiplayer Direct Connect to Room")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+C"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Multiplayer Leave Room")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+L"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Multiplayer Show Current Room")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+R"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F6"), std::string("R+Plus+Minus"), Qt::WindowShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F6"), std::string("R+Plus+Minus"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F5"), std::string("L+Plus+Minus"), Qt::WindowShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F5"), std::string("L+Plus+Minus"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F7"), std::string(""), Qt::ApplicationShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F7"), std::string(""), Qt::ApplicationShortcut, false}},