Compare commits
2 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5462485cc3 | ||
|
|
2c47f8aa18 |
@@ -17,7 +17,7 @@ It is written in C++ with portability in mind, and we actively maintain builds f
|
||||
alt="Azure Mainline CI Build Status">
|
||||
</a>
|
||||
<a href="https://discord.com/invite/u77vRWY">
|
||||
<img src="https://img.shields.io/discord/398318088170242053?color=5865F2&label=yuzu&logo=discord&logoColor=white"
|
||||
<img src="https://img.shields.io/discord/398318088170242053?color=%237289DA&label=yuzu&logo=discord&logoColor=white"
|
||||
alt="Discord">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include "audio_core/delay_line.h"
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <iterator>
|
||||
|
||||
@@ -15,26 +15,26 @@
|
||||
namespace Common {
|
||||
|
||||
u64 EstimateRDTSCFrequency() {
|
||||
// Discard the first result measuring the rdtsc.
|
||||
const auto milli_10 = std::chrono::milliseconds{10};
|
||||
// get current time
|
||||
_mm_mfence();
|
||||
__rdtsc();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{1});
|
||||
const u64 tscStart = __rdtsc();
|
||||
const auto startTime = std::chrono::high_resolution_clock::now();
|
||||
// wait roughly 3 seconds
|
||||
while (true) {
|
||||
auto milli = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::high_resolution_clock::now() - startTime);
|
||||
if (milli.count() >= 3000)
|
||||
break;
|
||||
std::this_thread::sleep_for(milli_10);
|
||||
}
|
||||
const auto endTime = std::chrono::high_resolution_clock::now();
|
||||
_mm_mfence();
|
||||
__rdtsc();
|
||||
|
||||
// Get the current time.
|
||||
const auto start_time = std::chrono::steady_clock::now();
|
||||
_mm_mfence();
|
||||
const u64 tsc_start = __rdtsc();
|
||||
// Wait for 200 milliseconds.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{200});
|
||||
const auto end_time = std::chrono::steady_clock::now();
|
||||
_mm_mfence();
|
||||
const u64 tsc_end = __rdtsc();
|
||||
// Calculate differences.
|
||||
const u64 timer_diff = static_cast<u64>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count());
|
||||
const u64 tsc_diff = tsc_end - tsc_start;
|
||||
const u64 tscEnd = __rdtsc();
|
||||
// calculate difference
|
||||
const u64 timer_diff =
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(endTime - startTime).count();
|
||||
const u64 tsc_diff = tscEnd - tscStart;
|
||||
const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
|
||||
return tsc_freq;
|
||||
}
|
||||
|
||||
@@ -866,52 +866,7 @@ void EmulatedController::SetLedPattern() {
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles) {
|
||||
supported_style_tag = supported_styles;
|
||||
if (!is_connected) {
|
||||
return;
|
||||
}
|
||||
if (!IsControllerSupported()) {
|
||||
LOG_ERROR(Service_HID, "Controller type {} is not supported. Disconnecting controller",
|
||||
npad_type);
|
||||
Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
bool EmulatedController::IsControllerSupported() const {
|
||||
switch (npad_type) {
|
||||
case NpadStyleIndex::ProController:
|
||||
return supported_style_tag.fullkey;
|
||||
case NpadStyleIndex::Handheld:
|
||||
return supported_style_tag.handheld;
|
||||
case NpadStyleIndex::JoyconDual:
|
||||
return supported_style_tag.joycon_dual;
|
||||
case NpadStyleIndex::JoyconLeft:
|
||||
return supported_style_tag.joycon_left;
|
||||
case NpadStyleIndex::JoyconRight:
|
||||
return supported_style_tag.joycon_right;
|
||||
case NpadStyleIndex::GameCube:
|
||||
return supported_style_tag.gamecube;
|
||||
case NpadStyleIndex::Pokeball:
|
||||
return supported_style_tag.palma;
|
||||
case NpadStyleIndex::NES:
|
||||
return supported_style_tag.lark;
|
||||
case NpadStyleIndex::SNES:
|
||||
return supported_style_tag.lucia;
|
||||
case NpadStyleIndex::N64:
|
||||
return supported_style_tag.lagoon;
|
||||
case NpadStyleIndex::SegaGenesis:
|
||||
return supported_style_tag.lager;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatedController::Connect() {
|
||||
if (!IsControllerSupported()) {
|
||||
LOG_ERROR(Service_HID, "Controller type {} is not supported", npad_type);
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::lock_guard lock{mutex};
|
||||
if (is_configuring) {
|
||||
|
||||
@@ -160,13 +160,6 @@ public:
|
||||
*/
|
||||
NpadStyleIndex GetNpadStyleIndex(bool get_temporary_value = false) const;
|
||||
|
||||
/**
|
||||
* Sets the supported controller types. Disconnects the controller if current type is not
|
||||
* supported
|
||||
* @param supported_styles bitflag with supported types
|
||||
*/
|
||||
void SetSupportedNpadStyleTag(NpadStyleTag supported_styles);
|
||||
|
||||
/// Sets the connected status to true
|
||||
void Connect();
|
||||
|
||||
@@ -317,12 +310,6 @@ private:
|
||||
/// Set the params for TAS devices
|
||||
void LoadTASParams();
|
||||
|
||||
/**
|
||||
* Checks the current controller type against the supported_style_tag
|
||||
* @return true if the controller is supported
|
||||
*/
|
||||
bool IsControllerSupported() const;
|
||||
|
||||
/**
|
||||
* Updates the button status of the controller
|
||||
* @param callback A CallbackStatus containing the button status
|
||||
@@ -367,7 +354,6 @@ private:
|
||||
|
||||
NpadIdType npad_id_type;
|
||||
NpadStyleIndex npad_type{NpadStyleIndex::None};
|
||||
NpadStyleTag supported_style_tag{NpadStyleSet::All};
|
||||
bool is_connected{false};
|
||||
bool is_configuring{false};
|
||||
f32 motion_sensitivity{0.01f};
|
||||
|
||||
@@ -108,16 +108,6 @@ const EmulatedController* HIDCore::GetEmulatedControllerByIndex(std::size_t inde
|
||||
|
||||
void HIDCore::SetSupportedStyleTag(NpadStyleTag style_tag) {
|
||||
supported_style_tag.raw = style_tag.raw;
|
||||
player_1->SetSupportedNpadStyleTag(supported_style_tag);
|
||||
player_2->SetSupportedNpadStyleTag(supported_style_tag);
|
||||
player_3->SetSupportedNpadStyleTag(supported_style_tag);
|
||||
player_4->SetSupportedNpadStyleTag(supported_style_tag);
|
||||
player_5->SetSupportedNpadStyleTag(supported_style_tag);
|
||||
player_6->SetSupportedNpadStyleTag(supported_style_tag);
|
||||
player_7->SetSupportedNpadStyleTag(supported_style_tag);
|
||||
player_8->SetSupportedNpadStyleTag(supported_style_tag);
|
||||
other->SetSupportedNpadStyleTag(supported_style_tag);
|
||||
handheld->SetSupportedNpadStyleTag(supported_style_tag);
|
||||
}
|
||||
|
||||
NpadStyleTag HIDCore::GetSupportedStyleTag() const {
|
||||
|
||||
@@ -73,7 +73,7 @@ private:
|
||||
std::unique_ptr<EmulatedController> handheld;
|
||||
std::unique_ptr<EmulatedConsole> console;
|
||||
std::unique_ptr<EmulatedDevices> devices;
|
||||
NpadStyleTag supported_style_tag{NpadStyleSet::All};
|
||||
NpadStyleTag supported_style_tag;
|
||||
};
|
||||
|
||||
} // namespace Core::HID
|
||||
|
||||
@@ -256,8 +256,6 @@ enum class NpadStyleSet : u32 {
|
||||
Lager = 1U << 11,
|
||||
SystemExt = 1U << 29,
|
||||
System = 1U << 30,
|
||||
|
||||
All = 0xFFFFFFFFU,
|
||||
};
|
||||
static_assert(sizeof(NpadStyleSet) == 4, "NpadStyleSet is an invalid size");
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
#include "core/hle/service/apm/apm_controller.h"
|
||||
#include "core/hle/service/apm/apm_interface.h"
|
||||
#include "core/hle/service/bcat/backend/backend.h"
|
||||
#include "core/hle/service/caps/caps.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/ns/ns.h"
|
||||
#include "core/hle/service/nvflinger/nvflinger.h"
|
||||
@@ -299,7 +298,7 @@ ISelfController::ISelfController(Core::System& system_, NVFlinger::NVFlinger& nv
|
||||
{91, &ISelfController::GetAccumulatedSuspendedTickChangedEvent, "GetAccumulatedSuspendedTickChangedEvent"},
|
||||
{100, &ISelfController::SetAlbumImageTakenNotificationEnabled, "SetAlbumImageTakenNotificationEnabled"},
|
||||
{110, nullptr, "SetApplicationAlbumUserData"},
|
||||
{120, &ISelfController::SaveCurrentScreenshot, "SaveCurrentScreenshot"},
|
||||
{120, nullptr, "SaveCurrentScreenshot"},
|
||||
{130, nullptr, "SetRecordVolumeMuted"},
|
||||
{1000, nullptr, "GetDebugStorageChannel"},
|
||||
};
|
||||
@@ -580,17 +579,6 @@ void ISelfController::SetAlbumImageTakenNotificationEnabled(Kernel::HLERequestCo
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void ISelfController::SaveCurrentScreenshot(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const auto album_report_option = rp.PopEnum<Capture::AlbumReportOption>();
|
||||
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called. album_report_option={}", album_report_option);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
AppletMessageQueue::AppletMessageQueue(Core::System& system)
|
||||
: service_context{system, "AppletMessageQueue"} {
|
||||
on_new_message = service_context.CreateEvent("AMMessageQueue:OnMessageReceived");
|
||||
|
||||
@@ -151,7 +151,6 @@ private:
|
||||
void GetAccumulatedSuspendedTickValue(Kernel::HLERequestContext& ctx);
|
||||
void GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequestContext& ctx);
|
||||
void SetAlbumImageTakenNotificationEnabled(Kernel::HLERequestContext& ctx);
|
||||
void SaveCurrentScreenshot(Kernel::HLERequestContext& ctx);
|
||||
|
||||
enum class ScreenshotPermission : u32 {
|
||||
Inherit = 0,
|
||||
|
||||
@@ -96,7 +96,7 @@ private:
|
||||
|
||||
bool DecodeOpusData(u32& consumed, u32& sample_count, const std::vector<u8>& input,
|
||||
std::vector<opus_int16>& output, u64* out_performance_time) const {
|
||||
const auto start_time = std::chrono::steady_clock::now();
|
||||
const auto start_time = std::chrono::high_resolution_clock::now();
|
||||
const std::size_t raw_output_sz = output.size() * sizeof(opus_int16);
|
||||
if (sizeof(OpusPacketHeader) > input.size()) {
|
||||
LOG_ERROR(Audio, "Input is smaller than the header size, header_sz={}, input_sz={}",
|
||||
@@ -135,7 +135,7 @@ private:
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto end_time = std::chrono::steady_clock::now() - start_time;
|
||||
const auto end_time = std::chrono::high_resolution_clock::now() - start_time;
|
||||
sample_count = out_sample_count;
|
||||
consumed = static_cast<u32>(sizeof(OpusPacketHeader) + hdr.size);
|
||||
if (out_performance_time != nullptr) {
|
||||
|
||||
@@ -24,7 +24,7 @@ enum class AlbumImageOrientation {
|
||||
Orientation3 = 3,
|
||||
};
|
||||
|
||||
enum class AlbumReportOption : s32 {
|
||||
enum class AlbumReportOption {
|
||||
Disable = 0,
|
||||
Enable = 1,
|
||||
};
|
||||
|
||||
@@ -126,11 +126,8 @@ void Controller_NPad::ControllerUpdate(Core::HID::ControllerTriggerType type,
|
||||
}
|
||||
|
||||
void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
|
||||
auto& controller = GetControllerFromNpadIdType(npad_id);
|
||||
if (!IsControllerSupported(controller.device->GetNpadStyleIndex())) {
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG(Service_HID, "Npad connected {}", npad_id);
|
||||
auto& controller = GetControllerFromNpadIdType(npad_id);
|
||||
const auto controller_type = controller.device->GetNpadStyleIndex();
|
||||
auto& shared_memory = controller.shared_memory_entry;
|
||||
if (controller_type == Core::HID::NpadStyleIndex::None) {
|
||||
@@ -258,7 +255,19 @@ void Controller_NPad::OnInit() {
|
||||
|
||||
if (hid_core.GetSupportedStyleTag().raw == Core::HID::NpadStyleSet::None) {
|
||||
// We want to support all controllers
|
||||
hid_core.SetSupportedStyleTag({Core::HID::NpadStyleSet::All});
|
||||
Core::HID::NpadStyleTag style{};
|
||||
style.handheld.Assign(1);
|
||||
style.joycon_left.Assign(1);
|
||||
style.joycon_right.Assign(1);
|
||||
style.joycon_dual.Assign(1);
|
||||
style.fullkey.Assign(1);
|
||||
style.gamecube.Assign(1);
|
||||
style.palma.Assign(1);
|
||||
style.lark.Assign(1);
|
||||
style.lucia.Assign(1);
|
||||
style.lagoon.Assign(1);
|
||||
style.lager.Assign(1);
|
||||
hid_core.SetSupportedStyleTag(style);
|
||||
}
|
||||
|
||||
supported_npad_id_types.resize(npad_id_list.size());
|
||||
@@ -1063,18 +1072,13 @@ bool Controller_NPad::SwapNpadAssignment(Core::HID::NpadIdType npad_id_1,
|
||||
const auto& controller_2 = GetControllerFromNpadIdType(npad_id_2).device;
|
||||
const auto type_index_1 = controller_1->GetNpadStyleIndex();
|
||||
const auto type_index_2 = controller_2->GetNpadStyleIndex();
|
||||
const auto is_connected_1 = controller_1->IsConnected();
|
||||
const auto is_connected_2 = controller_2->IsConnected();
|
||||
|
||||
if (!IsControllerSupported(type_index_1) && is_connected_1) {
|
||||
return false;
|
||||
}
|
||||
if (!IsControllerSupported(type_index_2) && is_connected_2) {
|
||||
if (!IsControllerSupported(type_index_1) || !IsControllerSupported(type_index_2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UpdateControllerAt(type_index_2, npad_id_1, is_connected_2);
|
||||
UpdateControllerAt(type_index_1, npad_id_2, is_connected_1);
|
||||
AddNewControllerAt(type_index_2, npad_id_1);
|
||||
AddNewControllerAt(type_index_1, npad_id_2);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -20,8 +20,12 @@ NvResult nvhost_nvdec::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>&
|
||||
switch (command.group) {
|
||||
case 0x0:
|
||||
switch (command.cmd) {
|
||||
case 0x1:
|
||||
return Submit(input, output);
|
||||
case 0x1: {
|
||||
if (!fd_to_id.contains(fd)) {
|
||||
fd_to_id[fd] = next_id++;
|
||||
}
|
||||
return Submit(fd, input, output);
|
||||
}
|
||||
case 0x2:
|
||||
return GetSyncpoint(input, output);
|
||||
case 0x3:
|
||||
@@ -66,7 +70,10 @@ void nvhost_nvdec::OnOpen(DeviceFD fd) {}
|
||||
|
||||
void nvhost_nvdec::OnClose(DeviceFD fd) {
|
||||
LOG_INFO(Service_NVDRV, "NVDEC video stream ended");
|
||||
system.GPU().ClearCdmaInstance();
|
||||
const auto iter = fd_to_id.find(fd);
|
||||
if (iter != fd_to_id.end()) {
|
||||
system.GPU().ClearCdmaInstance(iter->second);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Service::Nvidia::Devices
|
||||
|
||||
@@ -24,6 +24,9 @@ public:
|
||||
|
||||
void OnOpen(DeviceFD fd) override;
|
||||
void OnClose(DeviceFD fd) override;
|
||||
|
||||
private:
|
||||
u32 next_id{};
|
||||
};
|
||||
|
||||
} // namespace Service::Nvidia::Devices
|
||||
|
||||
@@ -59,7 +59,8 @@ NvResult nvhost_nvdec_common::SetNVMAPfd(const std::vector<u8>& input) {
|
||||
return NvResult::Success;
|
||||
}
|
||||
|
||||
NvResult nvhost_nvdec_common::Submit(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
NvResult nvhost_nvdec_common::Submit(DeviceFD fd, const std::vector<u8>& input,
|
||||
std::vector<u8>& output) {
|
||||
IoctlSubmit params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlSubmit));
|
||||
LOG_DEBUG(Service_NVDRV, "called NVDEC Submit, cmd_buffer_count={}", params.cmd_buffer_count);
|
||||
@@ -93,7 +94,7 @@ NvResult nvhost_nvdec_common::Submit(const std::vector<u8>& input, std::vector<u
|
||||
Tegra::ChCommandHeaderList cmdlist(cmd_buffer.word_count);
|
||||
system.Memory().ReadBlock(object->addr + cmd_buffer.offset, cmdlist.data(),
|
||||
cmdlist.size() * sizeof(u32));
|
||||
gpu.PushCommandBuffer(cmdlist);
|
||||
gpu.PushCommandBuffer(fd_to_id[fd], cmdlist);
|
||||
}
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlSubmit));
|
||||
// Some games expect command_buffers to be written back
|
||||
|
||||
@@ -104,13 +104,14 @@ protected:
|
||||
|
||||
/// Ioctl command implementations
|
||||
NvResult SetNVMAPfd(const std::vector<u8>& input);
|
||||
NvResult Submit(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
NvResult Submit(DeviceFD fd, const std::vector<u8>& input, std::vector<u8>& output);
|
||||
NvResult GetSyncpoint(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
NvResult GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
NvResult MapBuffer(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
NvResult UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
NvResult SetSubmitTimeout(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
|
||||
std::unordered_map<DeviceFD, u32> fd_to_id{};
|
||||
s32_le nvmap_fd{};
|
||||
u32_le submit_timeout{};
|
||||
std::shared_ptr<nvmap> nvmap_dev;
|
||||
|
||||
@@ -21,7 +21,10 @@ NvResult nvhost_vic::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& i
|
||||
case 0x0:
|
||||
switch (command.cmd) {
|
||||
case 0x1:
|
||||
return Submit(input, output);
|
||||
if (!fd_to_id.contains(fd)) {
|
||||
fd_to_id[fd] = next_id++;
|
||||
}
|
||||
return Submit(fd, input, output);
|
||||
case 0x2:
|
||||
return GetSyncpoint(input, output);
|
||||
case 0x3:
|
||||
@@ -65,7 +68,10 @@ NvResult nvhost_vic::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& i
|
||||
void nvhost_vic::OnOpen(DeviceFD fd) {}
|
||||
|
||||
void nvhost_vic::OnClose(DeviceFD fd) {
|
||||
system.GPU().ClearCdmaInstance();
|
||||
const auto iter = fd_to_id.find(fd);
|
||||
if (iter != fd_to_id.end()) {
|
||||
system.GPU().ClearCdmaInstance(iter->second);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Service::Nvidia::Devices
|
||||
|
||||
@@ -23,5 +23,8 @@ public:
|
||||
|
||||
void OnOpen(DeviceFD fd) override;
|
||||
void OnClose(DeviceFD fd) override;
|
||||
|
||||
private:
|
||||
u32 next_id{};
|
||||
};
|
||||
} // namespace Service::Nvidia::Devices
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
@@ -125,9 +125,8 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
|
||||
}
|
||||
metadata.Print();
|
||||
|
||||
const auto static_modules = {"rtld", "main", "subsdk0", "subsdk1", "subsdk2",
|
||||
"subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7",
|
||||
"subsdk8", "subsdk9", "sdk"};
|
||||
const auto static_modules = {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3",
|
||||
"subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"};
|
||||
|
||||
// Use the NSO module loader to figure out the code layout
|
||||
std::size_t code_size{};
|
||||
|
||||
@@ -33,7 +33,7 @@ public:
|
||||
explicit PerfStats(u64 title_id_);
|
||||
~PerfStats();
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
using Clock = std::chrono::high_resolution_clock;
|
||||
|
||||
void BeginSystemFrame();
|
||||
void EndSystemFrame();
|
||||
@@ -87,7 +87,7 @@ private:
|
||||
|
||||
class SpeedLimiter {
|
||||
public:
|
||||
using Clock = std::chrono::steady_clock;
|
||||
using Clock = std::chrono::high_resolution_clock;
|
||||
|
||||
void DoSpeedLimiting(std::chrono::microseconds current_system_time_us);
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
@@ -3,7 +3,6 @@ add_subdirectory(host_shaders)
|
||||
if(LIBVA_FOUND)
|
||||
set_source_files_properties(command_classes/codecs/codec.cpp
|
||||
PROPERTIES COMPILE_DEFINITIONS LIBVA_FOUND=1)
|
||||
list(APPEND FFmpeg_LIBRARIES ${LIBVA_LIBRARIES})
|
||||
endif()
|
||||
|
||||
add_library(video_core STATIC
|
||||
|
||||
@@ -17,28 +17,12 @@
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/opt.h>
|
||||
#ifdef LIBVA_FOUND
|
||||
// for querying VAAPI driver information
|
||||
#include <libavutil/hwcontext_vaapi.h>
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace Tegra {
|
||||
namespace {
|
||||
constexpr AVPixelFormat PREFERRED_GPU_FMT = AV_PIX_FMT_NV12;
|
||||
constexpr AVPixelFormat PREFERRED_CPU_FMT = AV_PIX_FMT_YUV420P;
|
||||
constexpr std::array PREFERRED_GPU_DECODERS = {
|
||||
AV_HWDEVICE_TYPE_CUDA,
|
||||
#ifdef _WIN32
|
||||
AV_HWDEVICE_TYPE_D3D11VA,
|
||||
AV_HWDEVICE_TYPE_DXVA2,
|
||||
#elif defined(__linux__)
|
||||
AV_HWDEVICE_TYPE_VAAPI,
|
||||
AV_HWDEVICE_TYPE_VDPAU,
|
||||
#endif
|
||||
// last resort for Linux Flatpak (w/ NVIDIA)
|
||||
AV_HWDEVICE_TYPE_VULKAN,
|
||||
};
|
||||
|
||||
void AVPacketDeleter(AVPacket* ptr) {
|
||||
av_packet_free(&ptr);
|
||||
@@ -77,50 +61,83 @@ Codec::~Codec() {
|
||||
av_buffer_unref(&av_gpu_decoder);
|
||||
}
|
||||
|
||||
// List all the currently available hwcontext in ffmpeg
|
||||
static std::vector<AVHWDeviceType> ListSupportedContexts() {
|
||||
std::vector<AVHWDeviceType> contexts{};
|
||||
AVHWDeviceType current_device_type = AV_HWDEVICE_TYPE_NONE;
|
||||
do {
|
||||
current_device_type = av_hwdevice_iterate_types(current_device_type);
|
||||
contexts.push_back(current_device_type);
|
||||
} while (current_device_type != AV_HWDEVICE_TYPE_NONE);
|
||||
return contexts;
|
||||
}
|
||||
|
||||
bool Codec::CreateGpuAvDevice() {
|
||||
static constexpr auto HW_CONFIG_METHOD = AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX;
|
||||
static const auto supported_contexts = ListSupportedContexts();
|
||||
for (const auto& type : PREFERRED_GPU_DECODERS) {
|
||||
if (std::none_of(supported_contexts.begin(), supported_contexts.end(),
|
||||
[&type](const auto& context) { return context == type; })) {
|
||||
LOG_DEBUG(Service_NVDRV, "{} explicitly unsupported", av_hwdevice_get_type_name(type));
|
||||
#ifdef LIBVA_FOUND
|
||||
// List all the currently loaded Linux modules
|
||||
static std::vector<std::string> ListLinuxKernelModules() {
|
||||
using FILEPtr = std::unique_ptr<FILE, decltype(&std::fclose)>;
|
||||
auto module_listing = FILEPtr{fopen("/proc/modules", "rt"), std::fclose};
|
||||
std::vector<std::string> modules{};
|
||||
if (!module_listing) {
|
||||
LOG_WARNING(Service_NVDRV, "Could not open /proc/modules to collect available modules");
|
||||
return modules;
|
||||
}
|
||||
char* buffer = nullptr;
|
||||
size_t buf_len = 0;
|
||||
while (getline(&buffer, &buf_len, module_listing.get()) != -1) {
|
||||
// format for the module listing file (sysfs)
|
||||
// <name> <module_size> <depended_by_count> <depended_by_names> <status> <load_address>
|
||||
auto line = std::string(buffer);
|
||||
// we are only interested in module names
|
||||
auto name_pos = line.find_first_of(" ");
|
||||
if (name_pos == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
modules.push_back(line.erase(name_pos));
|
||||
}
|
||||
free(buffer);
|
||||
return modules;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool Codec::CreateGpuAvDevice() {
|
||||
#if defined(LIBVA_FOUND)
|
||||
static constexpr std::array<const char*, 3> VAAPI_DRIVERS = {
|
||||
"i915",
|
||||
"iHD",
|
||||
"amdgpu",
|
||||
};
|
||||
AVDictionary* hwdevice_options = nullptr;
|
||||
const auto loaded_modules = ListLinuxKernelModules();
|
||||
av_dict_set(&hwdevice_options, "connection_type", "drm", 0);
|
||||
for (const auto& driver : VAAPI_DRIVERS) {
|
||||
// first check if the target driver is loaded in the kernel
|
||||
bool found = std::any_of(loaded_modules.begin(), loaded_modules.end(),
|
||||
[&driver](const auto& module) { return module == driver; });
|
||||
if (!found) {
|
||||
LOG_DEBUG(Service_NVDRV, "Kernel driver {} is not loaded, trying the next one", driver);
|
||||
continue;
|
||||
}
|
||||
av_dict_set(&hwdevice_options, "kernel_driver", driver, 0);
|
||||
const int hwdevice_error = av_hwdevice_ctx_create(&av_gpu_decoder, AV_HWDEVICE_TYPE_VAAPI,
|
||||
nullptr, hwdevice_options, 0);
|
||||
if (hwdevice_error >= 0) {
|
||||
LOG_INFO(Service_NVDRV, "Using VA-API with {}", driver);
|
||||
av_dict_free(&hwdevice_options);
|
||||
av_codec_ctx->pix_fmt = AV_PIX_FMT_VAAPI;
|
||||
return true;
|
||||
}
|
||||
LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed {}", hwdevice_error);
|
||||
}
|
||||
LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed for all drivers");
|
||||
av_dict_free(&hwdevice_options);
|
||||
#endif
|
||||
static constexpr auto HW_CONFIG_METHOD = AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX;
|
||||
static constexpr std::array GPU_DECODER_TYPES{
|
||||
#ifdef linux
|
||||
AV_HWDEVICE_TYPE_VDPAU,
|
||||
#endif
|
||||
AV_HWDEVICE_TYPE_CUDA,
|
||||
#ifdef _WIN32
|
||||
AV_HWDEVICE_TYPE_D3D11VA,
|
||||
#endif
|
||||
};
|
||||
for (const auto& type : GPU_DECODER_TYPES) {
|
||||
const int hwdevice_res = av_hwdevice_ctx_create(&av_gpu_decoder, type, nullptr, nullptr, 0);
|
||||
if (hwdevice_res < 0) {
|
||||
LOG_DEBUG(Service_NVDRV, "{} av_hwdevice_ctx_create failed {}",
|
||||
av_hwdevice_get_type_name(type), hwdevice_res);
|
||||
continue;
|
||||
}
|
||||
#ifdef LIBVA_FOUND
|
||||
if (type == AV_HWDEVICE_TYPE_VAAPI) {
|
||||
// we need to determine if this is an impersonated VAAPI driver
|
||||
AVHWDeviceContext* hwctx =
|
||||
static_cast<AVHWDeviceContext*>(static_cast<void*>(av_gpu_decoder->data));
|
||||
AVVAAPIDeviceContext* vactx = static_cast<AVVAAPIDeviceContext*>(hwctx->hwctx);
|
||||
const char* vendor_name = vaQueryVendorString(vactx->display);
|
||||
if (strstr(vendor_name, "VDPAU backend")) {
|
||||
// VDPAU impersonated VAAPI impl's are super buggy, we need to skip them
|
||||
LOG_DEBUG(Service_NVDRV, "Skipping vdapu impersonated VAAPI driver");
|
||||
continue;
|
||||
} else {
|
||||
// according to some user testing, certain vaapi driver (Intel?) could be buggy
|
||||
// so let's log the driver name which may help the developers/supporters
|
||||
LOG_DEBUG(Service_NVDRV, "Using VAAPI driver: {}", vendor_name);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
for (int i = 0;; i++) {
|
||||
const AVCodecHWConfig* config = avcodec_get_hw_config(av_codec, i);
|
||||
if (!config) {
|
||||
|
||||
@@ -185,16 +185,6 @@ struct GPU::Impl {
|
||||
return *dma_pusher;
|
||||
}
|
||||
|
||||
/// Returns a reference to the GPU CDMA pusher.
|
||||
[[nodiscard]] Tegra::CDmaPusher& CDmaPusher() {
|
||||
return *cdma_pusher;
|
||||
}
|
||||
|
||||
/// Returns a const reference to the GPU CDMA pusher.
|
||||
[[nodiscard]] const Tegra::CDmaPusher& CDmaPusher() const {
|
||||
return *cdma_pusher;
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying renderer.
|
||||
[[nodiscard]] VideoCore::RendererBase& Renderer() {
|
||||
return *renderer;
|
||||
@@ -338,25 +328,27 @@ struct GPU::Impl {
|
||||
}
|
||||
|
||||
/// Push GPU command buffer entries to be processed
|
||||
void PushCommandBuffer(Tegra::ChCommandHeaderList& entries) {
|
||||
void PushCommandBuffer(u32 id, Tegra::ChCommandHeaderList& entries) {
|
||||
if (!use_nvdec) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cdma_pusher) {
|
||||
cdma_pusher = std::make_unique<Tegra::CDmaPusher>(gpu);
|
||||
if (!cdma_pushers.contains(id)) {
|
||||
cdma_pushers.insert_or_assign(id, std::make_unique<Tegra::CDmaPusher>(gpu));
|
||||
}
|
||||
|
||||
// SubmitCommandBuffer would make the nvdec operations async, this is not currently working
|
||||
// TODO(ameerj): RE proper async nvdec operation
|
||||
// gpu_thread.SubmitCommandBuffer(std::move(entries));
|
||||
|
||||
cdma_pusher->ProcessEntries(std::move(entries));
|
||||
cdma_pushers[id]->ProcessEntries(std::move(entries));
|
||||
}
|
||||
|
||||
/// Frees the CDMAPusher instance to free up resources
|
||||
void ClearCdmaInstance() {
|
||||
cdma_pusher.reset();
|
||||
void ClearCdmaInstance(u32 id) {
|
||||
const auto iter = cdma_pushers.find(id);
|
||||
if (iter != cdma_pushers.end()) {
|
||||
cdma_pushers.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
/// Swap buffers (render frame)
|
||||
@@ -659,7 +651,7 @@ struct GPU::Impl {
|
||||
Core::System& system;
|
||||
std::unique_ptr<Tegra::MemoryManager> memory_manager;
|
||||
std::unique_ptr<Tegra::DmaPusher> dma_pusher;
|
||||
std::unique_ptr<Tegra::CDmaPusher> cdma_pusher;
|
||||
std::map<u32, std::unique_ptr<Tegra::CDmaPusher>> cdma_pushers;
|
||||
std::unique_ptr<VideoCore::RendererBase> renderer;
|
||||
VideoCore::RasterizerInterface* rasterizer = nullptr;
|
||||
const bool use_nvdec;
|
||||
@@ -811,14 +803,6 @@ const Tegra::DmaPusher& GPU::DmaPusher() const {
|
||||
return impl->DmaPusher();
|
||||
}
|
||||
|
||||
Tegra::CDmaPusher& GPU::CDmaPusher() {
|
||||
return impl->CDmaPusher();
|
||||
}
|
||||
|
||||
const Tegra::CDmaPusher& GPU::CDmaPusher() const {
|
||||
return impl->CDmaPusher();
|
||||
}
|
||||
|
||||
VideoCore::RendererBase& GPU::Renderer() {
|
||||
return impl->Renderer();
|
||||
}
|
||||
@@ -887,12 +871,12 @@ void GPU::PushGPUEntries(Tegra::CommandList&& entries) {
|
||||
impl->PushGPUEntries(std::move(entries));
|
||||
}
|
||||
|
||||
void GPU::PushCommandBuffer(Tegra::ChCommandHeaderList& entries) {
|
||||
impl->PushCommandBuffer(entries);
|
||||
void GPU::PushCommandBuffer(u32 id, Tegra::ChCommandHeaderList& entries) {
|
||||
impl->PushCommandBuffer(id, entries);
|
||||
}
|
||||
|
||||
void GPU::ClearCdmaInstance() {
|
||||
impl->ClearCdmaInstance();
|
||||
void GPU::ClearCdmaInstance(u32 id) {
|
||||
impl->ClearCdmaInstance(id);
|
||||
}
|
||||
|
||||
void GPU::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
|
||||
|
||||
@@ -242,10 +242,10 @@ public:
|
||||
void PushGPUEntries(Tegra::CommandList&& entries);
|
||||
|
||||
/// Push GPU command buffer entries to be processed
|
||||
void PushCommandBuffer(Tegra::ChCommandHeaderList& entries);
|
||||
void PushCommandBuffer(u32 id, Tegra::ChCommandHeaderList& entries);
|
||||
|
||||
/// Frees the CDMAPusher instance to free up resources
|
||||
void ClearCdmaInstance();
|
||||
void ClearCdmaInstance(u32 id);
|
||||
|
||||
/// Swap buffers (render frame)
|
||||
void SwapBuffers(const Tegra::FramebufferConfig* framebuffer);
|
||||
|
||||
@@ -18,7 +18,7 @@ int ShaderNotify::ShadersBuilding() noexcept {
|
||||
const int now_complete = num_complete.load(std::memory_order::relaxed);
|
||||
const int now_building = num_building.load(std::memory_order::relaxed);
|
||||
if (now_complete == now_building) {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto now = std::chrono::high_resolution_clock::now();
|
||||
if (completed && num_complete == num_when_completed) {
|
||||
if (now - complete_time > TIME_TO_STOP_REPORTING) {
|
||||
report_base = now_complete;
|
||||
|
||||
@@ -28,6 +28,6 @@ private:
|
||||
|
||||
bool completed{};
|
||||
int num_when_completed{};
|
||||
std::chrono::steady_clock::time_point complete_time;
|
||||
std::chrono::high_resolution_clock::time_point complete_time;
|
||||
};
|
||||
} // namespace VideoCore
|
||||
|
||||
@@ -152,8 +152,6 @@ add_executable(yuzu
|
||||
main.ui
|
||||
uisettings.cpp
|
||||
uisettings.h
|
||||
util/controller_navigation.cpp
|
||||
util/controller_navigation.h
|
||||
util/limitable_input_dialog.cpp
|
||||
util/limitable_input_dialog.h
|
||||
util/overlay_dialog.cpp
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <QDialog>
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/applets/controller.h"
|
||||
|
||||
class GMainWindow;
|
||||
@@ -31,9 +32,8 @@ class System;
|
||||
}
|
||||
|
||||
namespace Core::HID {
|
||||
class HIDCore;
|
||||
enum class NpadStyleIndex : u8;
|
||||
} // namespace Core::HID
|
||||
}
|
||||
|
||||
class QtControllerSelectorDialog final : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <mutex>
|
||||
#include <QApplication>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
@@ -17,7 +16,6 @@
|
||||
#include "core/hle/lock.h"
|
||||
#include "yuzu/applets/qt_profile_select.h"
|
||||
#include "yuzu/main.h"
|
||||
#include "yuzu/util/controller_navigation.h"
|
||||
|
||||
namespace {
|
||||
QString FormatUserEntryText(const QString& username, Common::UUID uuid) {
|
||||
@@ -47,7 +45,7 @@ QPixmap GetIcon(Common::UUID uuid) {
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
QtProfileSelectionDialog::QtProfileSelectionDialog(Core::HID::HIDCore& hid_core, QWidget* parent)
|
||||
QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)
|
||||
: QDialog(parent), profile_manager(std::make_unique<Service::Account::ProfileManager>()) {
|
||||
outer_layout = new QVBoxLayout;
|
||||
|
||||
@@ -67,7 +65,6 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(Core::HID::HIDCore& hid_core,
|
||||
tree_view = new QTreeView;
|
||||
item_model = new QStandardItemModel(tree_view);
|
||||
tree_view->setModel(item_model);
|
||||
controller_navigation = new ControllerNavigation(hid_core, this);
|
||||
|
||||
tree_view->setAlternatingRowColors(true);
|
||||
tree_view->setSelectionMode(QHeaderView::SingleSelection);
|
||||
@@ -94,14 +91,6 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(Core::HID::HIDCore& hid_core,
|
||||
scroll_area->setLayout(layout);
|
||||
|
||||
connect(tree_view, &QTreeView::clicked, this, &QtProfileSelectionDialog::SelectUser);
|
||||
connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
|
||||
[this](Qt::Key key) {
|
||||
if (!this->isActiveWindow()) {
|
||||
return;
|
||||
}
|
||||
QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
|
||||
QCoreApplication::postEvent(tree_view, event);
|
||||
});
|
||||
|
||||
const auto& profiles = profile_manager->GetAllUsers();
|
||||
for (const auto& user : profiles) {
|
||||
@@ -124,9 +113,7 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(Core::HID::HIDCore& hid_core,
|
||||
resize(550, 400);
|
||||
}
|
||||
|
||||
QtProfileSelectionDialog::~QtProfileSelectionDialog() {
|
||||
controller_navigation->UnloadController();
|
||||
};
|
||||
QtProfileSelectionDialog::~QtProfileSelectionDialog() = default;
|
||||
|
||||
int QtProfileSelectionDialog::exec() {
|
||||
// Skip profile selection when there's only one.
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "core/frontend/applets/profile_select.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
|
||||
class ControllerNavigation;
|
||||
class GMainWindow;
|
||||
class QDialogButtonBox;
|
||||
class QGraphicsScene;
|
||||
@@ -21,15 +20,11 @@ class QStandardItem;
|
||||
class QStandardItemModel;
|
||||
class QVBoxLayout;
|
||||
|
||||
namespace Core::HID {
|
||||
class HIDCore;
|
||||
} // namespace Core::HID
|
||||
|
||||
class QtProfileSelectionDialog final : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QtProfileSelectionDialog(Core::HID::HIDCore& hid_core, QWidget* parent);
|
||||
explicit QtProfileSelectionDialog(QWidget* parent);
|
||||
~QtProfileSelectionDialog() override;
|
||||
|
||||
int exec() override;
|
||||
@@ -56,7 +51,6 @@ private:
|
||||
QDialogButtonBox* buttons;
|
||||
|
||||
std::unique_ptr<Service::Account::ProfileManager> profile_manager;
|
||||
ControllerNavigation* controller_navigation = nullptr;
|
||||
};
|
||||
|
||||
class QtProfileSelector final : public QObject, public Core::Frontend::ProfileSelectApplet {
|
||||
|
||||
@@ -907,79 +907,88 @@ void ConfigureInputPlayer::UpdateUI() {
|
||||
}
|
||||
|
||||
void ConfigureInputPlayer::SetConnectableControllers() {
|
||||
Core::HID::NpadStyleTag npad_style_set = hid_core.GetSupportedStyleTag();
|
||||
index_controller_type_pairs.clear();
|
||||
ui->comboControllerType->clear();
|
||||
const auto add_controllers = [this](bool enable_all,
|
||||
Core::HID::NpadStyleTag npad_style_set = {}) {
|
||||
index_controller_type_pairs.clear();
|
||||
ui->comboControllerType->clear();
|
||||
|
||||
if (npad_style_set.fullkey == 1) {
|
||||
index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
|
||||
Core::HID::NpadStyleIndex::ProController);
|
||||
ui->comboControllerType->addItem(tr("Pro Controller"));
|
||||
}
|
||||
if (enable_all || npad_style_set.fullkey == 1) {
|
||||
index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
|
||||
Core::HID::NpadStyleIndex::ProController);
|
||||
ui->comboControllerType->addItem(tr("Pro Controller"));
|
||||
}
|
||||
|
||||
if (npad_style_set.joycon_dual == 1) {
|
||||
index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
|
||||
Core::HID::NpadStyleIndex::JoyconDual);
|
||||
ui->comboControllerType->addItem(tr("Dual Joycons"));
|
||||
}
|
||||
if (enable_all || npad_style_set.joycon_dual == 1) {
|
||||
index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
|
||||
Core::HID::NpadStyleIndex::JoyconDual);
|
||||
ui->comboControllerType->addItem(tr("Dual Joycons"));
|
||||
}
|
||||
|
||||
if (npad_style_set.joycon_left == 1) {
|
||||
index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
|
||||
Core::HID::NpadStyleIndex::JoyconLeft);
|
||||
ui->comboControllerType->addItem(tr("Left Joycon"));
|
||||
}
|
||||
if (enable_all || npad_style_set.joycon_left == 1) {
|
||||
index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
|
||||
Core::HID::NpadStyleIndex::JoyconLeft);
|
||||
ui->comboControllerType->addItem(tr("Left Joycon"));
|
||||
}
|
||||
|
||||
if (npad_style_set.joycon_right == 1) {
|
||||
index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
|
||||
Core::HID::NpadStyleIndex::JoyconRight);
|
||||
ui->comboControllerType->addItem(tr("Right Joycon"));
|
||||
}
|
||||
if (enable_all || npad_style_set.joycon_right == 1) {
|
||||
index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
|
||||
Core::HID::NpadStyleIndex::JoyconRight);
|
||||
ui->comboControllerType->addItem(tr("Right Joycon"));
|
||||
}
|
||||
|
||||
if (player_index == 0 && npad_style_set.handheld == 1) {
|
||||
index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
|
||||
Core::HID::NpadStyleIndex::Handheld);
|
||||
ui->comboControllerType->addItem(tr("Handheld"));
|
||||
}
|
||||
if (player_index == 0 && (enable_all || npad_style_set.handheld == 1)) {
|
||||
index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
|
||||
Core::HID::NpadStyleIndex::Handheld);
|
||||
ui->comboControllerType->addItem(tr("Handheld"));
|
||||
}
|
||||
|
||||
if (npad_style_set.gamecube == 1) {
|
||||
index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
|
||||
Core::HID::NpadStyleIndex::GameCube);
|
||||
ui->comboControllerType->addItem(tr("GameCube Controller"));
|
||||
}
|
||||
if (enable_all || npad_style_set.gamecube == 1) {
|
||||
index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
|
||||
Core::HID::NpadStyleIndex::GameCube);
|
||||
ui->comboControllerType->addItem(tr("GameCube Controller"));
|
||||
}
|
||||
|
||||
// Disable all unsupported controllers
|
||||
if (!Settings::values.enable_all_controllers) {
|
||||
// Disable all unsupported controllers
|
||||
if (!Settings::values.enable_all_controllers) {
|
||||
return;
|
||||
}
|
||||
if (enable_all || npad_style_set.palma == 1) {
|
||||
index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
|
||||
Core::HID::NpadStyleIndex::Pokeball);
|
||||
ui->comboControllerType->addItem(tr("Poke Ball Plus"));
|
||||
}
|
||||
|
||||
if (enable_all || npad_style_set.lark == 1) {
|
||||
index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
|
||||
Core::HID::NpadStyleIndex::NES);
|
||||
ui->comboControllerType->addItem(tr("NES Controller"));
|
||||
}
|
||||
|
||||
if (enable_all || npad_style_set.lucia == 1) {
|
||||
index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
|
||||
Core::HID::NpadStyleIndex::SNES);
|
||||
ui->comboControllerType->addItem(tr("SNES Controller"));
|
||||
}
|
||||
|
||||
if (enable_all || npad_style_set.lagoon == 1) {
|
||||
index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
|
||||
Core::HID::NpadStyleIndex::N64);
|
||||
ui->comboControllerType->addItem(tr("N64 Controller"));
|
||||
}
|
||||
|
||||
if (enable_all || npad_style_set.lager == 1) {
|
||||
index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
|
||||
Core::HID::NpadStyleIndex::SegaGenesis);
|
||||
ui->comboControllerType->addItem(tr("Sega Genesis"));
|
||||
}
|
||||
};
|
||||
|
||||
if (!is_powered_on) {
|
||||
add_controllers(true);
|
||||
return;
|
||||
}
|
||||
if (npad_style_set.palma == 1) {
|
||||
index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
|
||||
Core::HID::NpadStyleIndex::Pokeball);
|
||||
ui->comboControllerType->addItem(tr("Poke Ball Plus"));
|
||||
}
|
||||
|
||||
if (npad_style_set.lark == 1) {
|
||||
index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
|
||||
Core::HID::NpadStyleIndex::NES);
|
||||
ui->comboControllerType->addItem(tr("NES Controller"));
|
||||
}
|
||||
|
||||
if (npad_style_set.lucia == 1) {
|
||||
index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
|
||||
Core::HID::NpadStyleIndex::SNES);
|
||||
ui->comboControllerType->addItem(tr("SNES Controller"));
|
||||
}
|
||||
|
||||
if (npad_style_set.lagoon == 1) {
|
||||
index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
|
||||
Core::HID::NpadStyleIndex::N64);
|
||||
ui->comboControllerType->addItem(tr("N64 Controller"));
|
||||
}
|
||||
|
||||
if (npad_style_set.lager == 1) {
|
||||
index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
|
||||
Core::HID::NpadStyleIndex::SegaGenesis);
|
||||
ui->comboControllerType->addItem(tr("Sega Genesis"));
|
||||
}
|
||||
add_controllers(false, hid_core.GetSupportedStyleTag());
|
||||
}
|
||||
|
||||
Core::HID::NpadStyleIndex ConfigureInputPlayer::GetControllerTypeFromIndex(int index) const {
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include <fmt/format.h>
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
@@ -26,7 +25,6 @@
|
||||
#include "yuzu/game_list_worker.h"
|
||||
#include "yuzu/main.h"
|
||||
#include "yuzu/uisettings.h"
|
||||
#include "yuzu/util/controller_navigation.h"
|
||||
|
||||
GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist, QObject* parent)
|
||||
: QObject(parent), gamelist{gamelist} {}
|
||||
@@ -314,7 +312,6 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide
|
||||
this->main_window = parent;
|
||||
layout = new QVBoxLayout;
|
||||
tree_view = new QTreeView;
|
||||
controller_navigation = new ControllerNavigation(system.HIDCore(), this);
|
||||
search_field = new GameListSearchField(this);
|
||||
item_model = new QStandardItemModel(tree_view);
|
||||
tree_view->setModel(item_model);
|
||||
@@ -344,18 +341,6 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide
|
||||
connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
|
||||
connect(tree_view, &QTreeView::expanded, this, &GameList::OnItemExpanded);
|
||||
connect(tree_view, &QTreeView::collapsed, this, &GameList::OnItemExpanded);
|
||||
connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
|
||||
[this](Qt::Key key) {
|
||||
// Avoid pressing buttons while playing
|
||||
if (system.IsPoweredOn()) {
|
||||
return;
|
||||
}
|
||||
if (!this->isActiveWindow()) {
|
||||
return;
|
||||
}
|
||||
QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
|
||||
QCoreApplication::postEvent(tree_view, event);
|
||||
});
|
||||
|
||||
// We must register all custom types with the Qt Automoc system so that we are able to use
|
||||
// it with signals/slots. In this case, QList falls under the umbrells of custom types.
|
||||
@@ -368,12 +353,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void GameList::UnloadController() {
|
||||
controller_navigation->UnloadController();
|
||||
}
|
||||
|
||||
GameList::~GameList() {
|
||||
UnloadController();
|
||||
emit ShouldCancelWorker();
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "uisettings.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
|
||||
class ControllerNavigation;
|
||||
class GameListWorker;
|
||||
class GameListSearchField;
|
||||
class GameListDir;
|
||||
@@ -89,9 +88,6 @@ public:
|
||||
void SaveInterfaceLayout();
|
||||
void LoadInterfaceLayout();
|
||||
|
||||
/// Disables events from the emulated controller
|
||||
void UnloadController();
|
||||
|
||||
static const QStringList supported_file_extensions;
|
||||
|
||||
signals:
|
||||
@@ -147,7 +143,6 @@ private:
|
||||
QStandardItemModel* item_model = nullptr;
|
||||
GameListWorker* current_worker = nullptr;
|
||||
QFileSystemWatcher* watcher = nullptr;
|
||||
ControllerNavigation* controller_navigation = nullptr;
|
||||
CompatibilityList compatibility_list;
|
||||
|
||||
friend class GameListSearchField;
|
||||
|
||||
@@ -136,7 +136,7 @@ void LoadingScreen::OnLoadComplete() {
|
||||
void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value,
|
||||
std::size_t total) {
|
||||
using namespace std::chrono;
|
||||
const auto now = steady_clock::now();
|
||||
const auto now = high_resolution_clock::now();
|
||||
// reset the timer if the stage changes
|
||||
if (stage != previous_stage) {
|
||||
ui->progress_bar->setStyleSheet(QString::fromUtf8(progressbar_style[stage]));
|
||||
@@ -160,7 +160,7 @@ void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size
|
||||
// If theres a drastic slowdown in the rate, then display an estimate
|
||||
if (now - previous_time > milliseconds{50} || slow_shader_compile_start) {
|
||||
if (!slow_shader_compile_start) {
|
||||
slow_shader_start = steady_clock::now();
|
||||
slow_shader_start = high_resolution_clock::now();
|
||||
slow_shader_compile_start = true;
|
||||
slow_shader_first_value = value;
|
||||
}
|
||||
|
||||
@@ -84,8 +84,8 @@ private:
|
||||
// shaders, it will start quickly but end slow if new shaders were added since previous launch.
|
||||
// These variables are used to detect the change in speed so we can generate an ETA
|
||||
bool slow_shader_compile_start = false;
|
||||
std::chrono::steady_clock::time_point slow_shader_start;
|
||||
std::chrono::steady_clock::time_point previous_time;
|
||||
std::chrono::high_resolution_clock::time_point slow_shader_start;
|
||||
std::chrono::high_resolution_clock::time_point previous_time;
|
||||
std::size_t slow_shader_first_value = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -449,7 +449,7 @@ void GMainWindow::ControllerSelectorReconfigureControllers(
|
||||
}
|
||||
|
||||
void GMainWindow::ProfileSelectorSelectProfile() {
|
||||
QtProfileSelectionDialog dialog(system->HIDCore(), this);
|
||||
QtProfileSelectionDialog dialog(this);
|
||||
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint |
|
||||
Qt::WindowTitleHint | Qt::WindowSystemMenuHint |
|
||||
Qt::WindowCloseButtonHint);
|
||||
@@ -1346,7 +1346,7 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p
|
||||
}
|
||||
|
||||
void GMainWindow::SelectAndSetCurrentUser() {
|
||||
QtProfileSelectionDialog dialog(system->HIDCore(), this);
|
||||
QtProfileSelectionDialog dialog(this);
|
||||
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
|
||||
Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
|
||||
dialog.setWindowModality(Qt::WindowModal);
|
||||
@@ -1516,9 +1516,6 @@ void GMainWindow::ShutdownGame() {
|
||||
input_subsystem->GetTas()->Stop();
|
||||
OnTasStateChanged();
|
||||
|
||||
// Enable all controllers
|
||||
system->HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All});
|
||||
|
||||
render_window->removeEventFilter(render_window);
|
||||
render_window->setAttribute(Qt::WA_Hover, false);
|
||||
|
||||
@@ -1611,7 +1608,7 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
|
||||
if (has_user_save) {
|
||||
// User save data
|
||||
const auto select_profile = [this] {
|
||||
QtProfileSelectionDialog dialog(system->HIDCore(), this);
|
||||
QtProfileSelectionDialog dialog(this);
|
||||
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
|
||||
Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
|
||||
dialog.setWindowModality(Qt::WindowModal);
|
||||
@@ -3379,10 +3376,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
|
||||
UpdateUISettings();
|
||||
game_list->SaveInterfaceLayout();
|
||||
hotkey_registry.SaveHotkeys();
|
||||
|
||||
// Unload controllers early
|
||||
controller_dialog->UnloadController();
|
||||
game_list->UnloadController();
|
||||
system->HIDCore().UnloadInputDevices();
|
||||
|
||||
// Shutdown session if the emu thread is active...
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included
|
||||
|
||||
#include "common/settings_input.h"
|
||||
#include "core/hid/emulated_controller.h"
|
||||
#include "core/hid/hid_core.h"
|
||||
#include "yuzu/util/controller_navigation.h"
|
||||
|
||||
ControllerNavigation::ControllerNavigation(Core::HID::HIDCore& hid_core, QWidget* parent) {
|
||||
player1_controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
|
||||
handheld_controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
|
||||
Core::HID::ControllerUpdateCallback engine_callback{
|
||||
.on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdateEvent(type); },
|
||||
.is_npad_service = false,
|
||||
};
|
||||
player1_callback_key = player1_controller->SetCallback(engine_callback);
|
||||
handheld_callback_key = handheld_controller->SetCallback(engine_callback);
|
||||
is_controller_set = true;
|
||||
}
|
||||
|
||||
ControllerNavigation::~ControllerNavigation() {
|
||||
UnloadController();
|
||||
}
|
||||
|
||||
void ControllerNavigation::UnloadController() {
|
||||
if (is_controller_set) {
|
||||
player1_controller->DeleteCallback(player1_callback_key);
|
||||
handheld_controller->DeleteCallback(handheld_callback_key);
|
||||
is_controller_set = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerNavigation::TriggerButton(Settings::NativeButton::Values native_button,
|
||||
Qt::Key key) {
|
||||
if (button_values[native_button].value && !button_values[native_button].locked) {
|
||||
emit TriggerKeyboardEvent(key);
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerNavigation::ControllerUpdateEvent(Core::HID::ControllerTriggerType type) {
|
||||
std::lock_guard lock{mutex};
|
||||
if (type == Core::HID::ControllerTriggerType::Button) {
|
||||
ControllerUpdateButton();
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == Core::HID::ControllerTriggerType::Stick) {
|
||||
ControllerUpdateStick();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerNavigation::ControllerUpdateButton() {
|
||||
const auto controller_type = player1_controller->GetNpadStyleIndex();
|
||||
const auto& player1_buttons = player1_controller->GetButtonsValues();
|
||||
const auto& handheld_buttons = handheld_controller->GetButtonsValues();
|
||||
|
||||
for (std::size_t i = 0; i < player1_buttons.size(); ++i) {
|
||||
const bool button = player1_buttons[i].value || handheld_buttons[i].value;
|
||||
// Trigger only once
|
||||
button_values[i].locked = button == button_values[i].value;
|
||||
button_values[i].value = button;
|
||||
}
|
||||
|
||||
switch (controller_type) {
|
||||
case Core::HID::NpadStyleIndex::ProController:
|
||||
case Core::HID::NpadStyleIndex::JoyconDual:
|
||||
case Core::HID::NpadStyleIndex::Handheld:
|
||||
case Core::HID::NpadStyleIndex::GameCube:
|
||||
TriggerButton(Settings::NativeButton::A, Qt::Key_Enter);
|
||||
TriggerButton(Settings::NativeButton::B, Qt::Key_Escape);
|
||||
TriggerButton(Settings::NativeButton::DDown, Qt::Key_Down);
|
||||
TriggerButton(Settings::NativeButton::DLeft, Qt::Key_Left);
|
||||
TriggerButton(Settings::NativeButton::DRight, Qt::Key_Right);
|
||||
TriggerButton(Settings::NativeButton::DUp, Qt::Key_Up);
|
||||
break;
|
||||
case Core::HID::NpadStyleIndex::JoyconLeft:
|
||||
TriggerButton(Settings::NativeButton::DDown, Qt::Key_Enter);
|
||||
TriggerButton(Settings::NativeButton::DLeft, Qt::Key_Escape);
|
||||
break;
|
||||
case Core::HID::NpadStyleIndex::JoyconRight:
|
||||
TriggerButton(Settings::NativeButton::X, Qt::Key_Enter);
|
||||
TriggerButton(Settings::NativeButton::A, Qt::Key_Escape);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerNavigation::ControllerUpdateStick() {
|
||||
const auto controller_type = player1_controller->GetNpadStyleIndex();
|
||||
const auto& player1_sticks = player1_controller->GetSticksValues();
|
||||
const auto& handheld_sticks = player1_controller->GetSticksValues();
|
||||
bool update = false;
|
||||
|
||||
for (std::size_t i = 0; i < player1_sticks.size(); ++i) {
|
||||
const Common::Input::StickStatus stick{
|
||||
.left = player1_sticks[i].left || handheld_sticks[i].left,
|
||||
.right = player1_sticks[i].right || handheld_sticks[i].right,
|
||||
.up = player1_sticks[i].up || handheld_sticks[i].up,
|
||||
.down = player1_sticks[i].down || handheld_sticks[i].down,
|
||||
};
|
||||
// Trigger only once
|
||||
if (stick.down != stick_values[i].down || stick.left != stick_values[i].left ||
|
||||
stick.right != stick_values[i].right || stick.up != stick_values[i].up) {
|
||||
update = true;
|
||||
}
|
||||
stick_values[i] = stick;
|
||||
}
|
||||
|
||||
if (!update) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (controller_type) {
|
||||
case Core::HID::NpadStyleIndex::ProController:
|
||||
case Core::HID::NpadStyleIndex::JoyconDual:
|
||||
case Core::HID::NpadStyleIndex::Handheld:
|
||||
case Core::HID::NpadStyleIndex::GameCube:
|
||||
if (stick_values[Settings::NativeAnalog::LStick].down) {
|
||||
emit TriggerKeyboardEvent(Qt::Key_Down);
|
||||
return;
|
||||
}
|
||||
if (stick_values[Settings::NativeAnalog::LStick].left) {
|
||||
emit TriggerKeyboardEvent(Qt::Key_Left);
|
||||
return;
|
||||
}
|
||||
if (stick_values[Settings::NativeAnalog::LStick].right) {
|
||||
emit TriggerKeyboardEvent(Qt::Key_Right);
|
||||
return;
|
||||
}
|
||||
if (stick_values[Settings::NativeAnalog::LStick].up) {
|
||||
emit TriggerKeyboardEvent(Qt::Key_Up);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case Core::HID::NpadStyleIndex::JoyconLeft:
|
||||
if (stick_values[Settings::NativeAnalog::LStick].left) {
|
||||
emit TriggerKeyboardEvent(Qt::Key_Down);
|
||||
return;
|
||||
}
|
||||
if (stick_values[Settings::NativeAnalog::LStick].up) {
|
||||
emit TriggerKeyboardEvent(Qt::Key_Left);
|
||||
return;
|
||||
}
|
||||
if (stick_values[Settings::NativeAnalog::LStick].down) {
|
||||
emit TriggerKeyboardEvent(Qt::Key_Right);
|
||||
return;
|
||||
}
|
||||
if (stick_values[Settings::NativeAnalog::LStick].right) {
|
||||
emit TriggerKeyboardEvent(Qt::Key_Up);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case Core::HID::NpadStyleIndex::JoyconRight:
|
||||
if (stick_values[Settings::NativeAnalog::RStick].right) {
|
||||
emit TriggerKeyboardEvent(Qt::Key_Down);
|
||||
return;
|
||||
}
|
||||
if (stick_values[Settings::NativeAnalog::RStick].down) {
|
||||
emit TriggerKeyboardEvent(Qt::Key_Left);
|
||||
return;
|
||||
}
|
||||
if (stick_values[Settings::NativeAnalog::RStick].up) {
|
||||
emit TriggerKeyboardEvent(Qt::Key_Right);
|
||||
return;
|
||||
}
|
||||
if (stick_values[Settings::NativeAnalog::RStick].left) {
|
||||
emit TriggerKeyboardEvent(Qt::Key_Up);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QKeyEvent>
|
||||
#include <QObject>
|
||||
|
||||
#include "common/input.h"
|
||||
#include "common/settings_input.h"
|
||||
|
||||
namespace Core::HID {
|
||||
using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>;
|
||||
using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>;
|
||||
enum class ControllerTriggerType;
|
||||
class EmulatedController;
|
||||
class HIDCore;
|
||||
} // namespace Core::HID
|
||||
|
||||
class ControllerNavigation : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ControllerNavigation(Core::HID::HIDCore& hid_core, QWidget* parent = nullptr);
|
||||
~ControllerNavigation();
|
||||
|
||||
/// Disables events from the emulated controller
|
||||
void UnloadController();
|
||||
|
||||
signals:
|
||||
void TriggerKeyboardEvent(Qt::Key key);
|
||||
|
||||
private:
|
||||
void TriggerButton(Settings::NativeButton::Values native_button, Qt::Key key);
|
||||
void ControllerUpdateEvent(Core::HID::ControllerTriggerType type);
|
||||
|
||||
void ControllerUpdateButton();
|
||||
|
||||
void ControllerUpdateStick();
|
||||
|
||||
Core::HID::ButtonValues button_values{};
|
||||
Core::HID::SticksValues stick_values{};
|
||||
|
||||
int player1_callback_key{};
|
||||
int handheld_callback_key{};
|
||||
bool is_controller_set{};
|
||||
mutable std::mutex mutex;
|
||||
Core::HID::EmulatedController* player1_controller;
|
||||
Core::HID::EmulatedController* handheld_controller;
|
||||
};
|
||||
Reference in New Issue
Block a user