summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/CMakeLists.txt2
-rw-r--r--src/core/arm/cpu_interrupt_handler.cpp2
-rw-r--r--src/core/hle/service/acc/acc.cpp9
-rw-r--r--src/core/hle/service/acc/acc.h1
-rw-r--r--src/core/hle/service/acc/acc_su.cpp2
-rw-r--r--src/core/hle/service/acc/acc_u0.cpp4
-rw-r--r--src/core/hle/service/acc/acc_u1.cpp2
-rw-r--r--src/core/hle/service/am/am.cpp34
-rw-r--r--src/core/hle/service/set/set.cpp72
-rw-r--r--src/core/hle/service/set/set.h2
-rw-r--r--src/core/memory.cpp7
-rw-r--r--src/input_common/CMakeLists.txt6
-rw-r--r--src/input_common/gcadapter/gc_adapter.cpp398
-rw-r--r--src/input_common/gcadapter/gc_adapter.h161
-rw-r--r--src/input_common/gcadapter/gc_poller.cpp272
-rw-r--r--src/input_common/gcadapter/gc_poller.h67
-rw-r--r--src/input_common/main.cpp24
-rw-r--r--src/input_common/main.h5
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h6
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp17
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.h7
-rw-r--r--src/video_core/renderer_opengl/maxwell_to_gl.h6
-rw-r--r--src/video_core/renderer_vulkan/fixed_pipeline_state.cpp115
-rw-r--r--src/video_core/renderer_vulkan/fixed_pipeline_state.h207
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp6
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.h6
-rw-r--r--src/video_core/renderer_vulkan/vk_device.cpp26
-rw-r--r--src/video_core/renderer_vulkan/vk_device.h6
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp89
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp13
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.h8
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp276
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h13
-rw-r--r--src/video_core/renderer_vulkan/vk_state_tracker.cpp68
-rw-r--r--src/video_core/renderer_vulkan/vk_state_tracker.h50
-rw-r--r--src/video_core/renderer_vulkan/wrapper.cpp10
-rw-r--r--src/video_core/renderer_vulkan/wrapper.h54
-rw-r--r--src/video_core/shader_cache.h70
-rw-r--r--src/yuzu/configuration/config.cpp12
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp6
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp60
-rw-r--r--src/yuzu/main.cpp2
42 files changed, 1854 insertions, 349 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index f87d67db5..d1f173f42 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -614,7 +614,7 @@ endif()
create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
-target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::Opus unicorn zip)
+target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls opus unicorn zip)
if (YUZU_ENABLE_BOXCAT)
target_compile_definitions(core PRIVATE -DYUZU_ENABLE_BOXCAT)
diff --git a/src/core/arm/cpu_interrupt_handler.cpp b/src/core/arm/cpu_interrupt_handler.cpp
index 2f1a1a269..df0350881 100644
--- a/src/core/arm/cpu_interrupt_handler.cpp
+++ b/src/core/arm/cpu_interrupt_handler.cpp
@@ -2,8 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#pragma once
-
#include "common/thread.h"
#include "core/arm/cpu_interrupt_handler.h"
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index 94d8c1fc6..8ac856ec3 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -776,6 +776,15 @@ void Module::Interface::ListQualifiedUsers(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
}
+void Module::Interface::ListOpenContextStoredUsers(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_ACC, "(STUBBED) called");
+
+ // TODO(ogniK): Handle open contexts
+ ctx.WriteBuffer(profile_manager->GetOpenUsers());
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called");
// A u8 is passed into this function which we can safely ignore. It's to determine if we have
diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h
index 74ca39d6e..d4c6395c6 100644
--- a/src/core/hle/service/acc/acc.h
+++ b/src/core/hle/service/acc/acc.h
@@ -34,6 +34,7 @@ public:
void IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx);
void GetProfileEditor(Kernel::HLERequestContext& ctx);
void ListQualifiedUsers(Kernel::HLERequestContext& ctx);
+ void ListOpenContextStoredUsers(Kernel::HLERequestContext& ctx);
private:
ResultCode InitializeApplicationInfoBase();
diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp
index 85620bde3..d2bb8c2c8 100644
--- a/src/core/hle/service/acc/acc_su.cpp
+++ b/src/core/hle/service/acc/acc_su.cpp
@@ -20,7 +20,7 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{6, nullptr, "GetProfileDigest"}, // 3.0.0+
{50, &ACC_SU::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
{51, &ACC_SU::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
- {60, nullptr, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0
+ {60, &ACC_SU::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0
{99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+
{100, nullptr, "GetUserRegistrationNotifier"},
{101, nullptr, "GetUserStateChangeNotifier"},
diff --git a/src/core/hle/service/acc/acc_u0.cpp b/src/core/hle/service/acc/acc_u0.cpp
index 49f6e20f1..cb44e06b7 100644
--- a/src/core/hle/service/acc/acc_u0.cpp
+++ b/src/core/hle/service/acc/acc_u0.cpp
@@ -20,7 +20,7 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{6, nullptr, "GetProfileDigest"}, // 3.0.0+
{50, &ACC_U0::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
{51, &ACC_U0::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
- {60, nullptr, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0
+ {60, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0
{99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+
{100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"},
{101, &ACC_U0::GetBaasAccountManagerForApplication, "GetBaasAccountManagerForApplication"},
@@ -30,7 +30,7 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{111, nullptr, "ClearSaveDataThumbnail"},
{120, nullptr, "CreateGuestLoginRequest"},
{130, nullptr, "LoadOpenContext"}, // 5.0.0+
- {131, nullptr, "ListOpenContextStoredUsers"}, // 6.0.0+
+ {131, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 6.0.0+
{140, &ACC_U0::InitializeApplicationInfoRestricted, "InitializeApplicationInfoRestricted"}, // 6.0.0+
{141, &ACC_U0::ListQualifiedUsers, "ListQualifiedUsers"}, // 6.0.0+
{150, &ACC_U0::IsUserAccountSwitchLocked, "IsUserAccountSwitchLocked"}, // 6.0.0+
diff --git a/src/core/hle/service/acc/acc_u1.cpp b/src/core/hle/service/acc/acc_u1.cpp
index f47004f84..a4aa5316a 100644
--- a/src/core/hle/service/acc/acc_u1.cpp
+++ b/src/core/hle/service/acc/acc_u1.cpp
@@ -20,7 +20,7 @@ ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{6, nullptr, "GetProfileDigest"}, // 3.0.0+
{50, &ACC_U1::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
{51, &ACC_U1::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
- {60, nullptr, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0
+ {60, &ACC_U1::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0
{99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+
{100, nullptr, "GetUserRegistrationNotifier"},
{101, nullptr, "GetUserStateChangeNotifier"},
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 20f366635..24cfb370b 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -10,6 +10,7 @@
#include "core/core.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
+#include "core/file_sys/registered_cache.h"
#include "core/file_sys/savedata_factory.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/kernel.h"
@@ -841,7 +842,7 @@ public:
{110, nullptr, "NeedsToExitProcess"},
{120, nullptr, "GetLibraryAppletInfo"},
{150, nullptr, "RequestForAppletToGetForeground"},
- {160, nullptr, "GetIndirectLayerConsumerHandle"},
+ {160, &ILibraryAppletAccessor::GetIndirectLayerConsumerHandle, "GetIndirectLayerConsumerHandle"},
};
// clang-format on
@@ -960,6 +961,18 @@ private:
rb.PushCopyObjects(applet->GetBroker().GetInteractiveDataEvent());
}
+ void GetIndirectLayerConsumerHandle(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ // We require a non-zero handle to be valid. Using 0xdeadbeef allows us to trace if this is
+ // actually used anywhere
+ constexpr u64 handle = 0xdeadbeef;
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(handle);
+ }
+
std::shared_ptr<Applets::Applet> applet;
};
@@ -1341,14 +1354,25 @@ void IApplicationFunctions::GetDisplayVersion(Kernel::HLERequestContext& ctx) {
std::array<u8, 0x10> version_string{};
- FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()};
- const auto res = pm.GetControlMetadata();
+ const auto res = [this] {
+ const auto title_id = system.CurrentProcess()->GetTitleID();
+
+ FileSys::PatchManager pm{title_id};
+ auto res = pm.GetControlMetadata();
+ if (res.first != nullptr) {
+ return res;
+ }
+
+ FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(title_id)};
+ return pm_update.GetControlMetadata();
+ }();
+
if (res.first != nullptr) {
const auto& version = res.first->GetVersionString();
std::copy(version.begin(), version.end(), version_string.begin());
} else {
- constexpr u128 default_version = {1, 0};
- std::memcpy(version_string.data(), default_version.data(), sizeof(u128));
+ constexpr char default_version[]{"1.0.0"};
+ std::memcpy(version_string.data(), default_version, sizeof(default_version));
}
IPC::ResponseBuilder rb{ctx, 6};
diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp
index f3b4b286c..e5cfd2101 100644
--- a/src/core/hle/service/set/set.cpp
+++ b/src/core/hle/service/set/set.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <algorithm>
+#include <array>
#include <chrono>
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
@@ -31,6 +32,44 @@ constexpr std::array<LanguageCode, 17> available_language_codes = {{
LanguageCode::ZH_HANT,
}};
+enum class KeyboardLayout : u64 {
+ Japanese = 0,
+ EnglishUs = 1,
+ EnglishUsInternational = 2,
+ EnglishUk = 3,
+ French = 4,
+ FrenchCa = 5,
+ Spanish = 6,
+ SpanishLatin = 7,
+ German = 8,
+ Italian = 9,
+ Portuguese = 10,
+ Russian = 11,
+ Korean = 12,
+ ChineseSimplified = 13,
+ ChineseTraditional = 14,
+};
+
+constexpr std::array<std::pair<LanguageCode, KeyboardLayout>, 17> language_to_layout{{
+ {LanguageCode::JA, KeyboardLayout::Japanese},
+ {LanguageCode::EN_US, KeyboardLayout::EnglishUs},
+ {LanguageCode::FR, KeyboardLayout::French},
+ {LanguageCode::DE, KeyboardLayout::German},
+ {LanguageCode::IT, KeyboardLayout::Italian},
+ {LanguageCode::ES, KeyboardLayout::Spanish},
+ {LanguageCode::ZH_CN, KeyboardLayout::ChineseSimplified},
+ {LanguageCode::KO, KeyboardLayout::Korean},
+ {LanguageCode::NL, KeyboardLayout::EnglishUsInternational},
+ {LanguageCode::PT, KeyboardLayout::Portuguese},
+ {LanguageCode::RU, KeyboardLayout::Russian},
+ {LanguageCode::ZH_TW, KeyboardLayout::ChineseTraditional},
+ {LanguageCode::EN_GB, KeyboardLayout::EnglishUk},
+ {LanguageCode::FR_CA, KeyboardLayout::FrenchCa},
+ {LanguageCode::ES_419, KeyboardLayout::SpanishLatin},
+ {LanguageCode::ZH_HANS, KeyboardLayout::ChineseSimplified},
+ {LanguageCode::ZH_HANT, KeyboardLayout::ChineseTraditional},
+}};
+
constexpr std::size_t pre4_0_0_max_entries = 15;
constexpr std::size_t post4_0_0_max_entries = 17;
@@ -50,6 +89,25 @@ void GetAvailableLanguageCodesImpl(Kernel::HLERequestContext& ctx, std::size_t m
ctx.WriteBuffer(available_language_codes.data(), copy_size);
PushResponseLanguageCode(ctx, copy_amount);
}
+
+void GetKeyCodeMapImpl(Kernel::HLERequestContext& ctx) {
+ const auto language_code = available_language_codes[Settings::values.language_index];
+ const auto key_code =
+ std::find_if(language_to_layout.cbegin(), language_to_layout.cend(),
+ [=](const auto& element) { return element.first == language_code; });
+ KeyboardLayout layout = KeyboardLayout::EnglishUs;
+ if (key_code == language_to_layout.cend()) {
+ LOG_ERROR(Service_SET,
+ "Could not find keyboard layout for language index {}, defaulting to English us",
+ Settings::values.language_index);
+ } else {
+ layout = key_code->second;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ ctx.WriteBuffer(&layout, sizeof(KeyboardLayout));
+}
} // Anonymous namespace
LanguageCode GetLanguageCodeFromIndex(std::size_t index) {
@@ -120,6 +178,16 @@ void SET::GetRegionCode(Kernel::HLERequestContext& ctx) {
rb.Push(Settings::values.region_index);
}
+void SET::GetKeyCodeMap(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_SET, "Called {}", ctx.Description());
+ GetKeyCodeMapImpl(ctx);
+}
+
+void SET::GetKeyCodeMap2(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_SET, "Called {}", ctx.Description());
+ GetKeyCodeMapImpl(ctx);
+}
+
SET::SET() : ServiceFramework("set") {
// clang-format off
static const FunctionInfo functions[] = {
@@ -130,9 +198,9 @@ SET::SET() : ServiceFramework("set") {
{4, &SET::GetRegionCode, "GetRegionCode"},
{5, &SET::GetAvailableLanguageCodes2, "GetAvailableLanguageCodes2"},
{6, &SET::GetAvailableLanguageCodeCount2, "GetAvailableLanguageCodeCount2"},
- {7, nullptr, "GetKeyCodeMap"},
+ {7, &SET::GetKeyCodeMap, "GetKeyCodeMap"},
{8, &SET::GetQuestFlag, "GetQuestFlag"},
- {9, nullptr, "GetKeyCodeMap2"},
+ {9, &SET::GetKeyCodeMap2, "GetKeyCodeMap2"},
{10, nullptr, "GetFirmwareVersionForDebug"},
};
// clang-format on
diff --git a/src/core/hle/service/set/set.h b/src/core/hle/service/set/set.h
index 6084b345d..8ac9c169d 100644
--- a/src/core/hle/service/set/set.h
+++ b/src/core/hle/service/set/set.h
@@ -44,6 +44,8 @@ private:
void GetAvailableLanguageCodeCount2(Kernel::HLERequestContext& ctx);
void GetQuestFlag(Kernel::HLERequestContext& ctx);
void GetRegionCode(Kernel::HLERequestContext& ctx);
+ void GetKeyCodeMap(Kernel::HLERequestContext& ctx);
+ void GetKeyCodeMap2(Kernel::HLERequestContext& ctx);
};
} // namespace Service::Set
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 7def00768..2c5588933 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -548,9 +548,9 @@ struct Memory::Impl {
// longer exist, and we should just leave the pagetable entry blank.
page_type = Common::PageType::Unmapped;
} else {
- page_type = Common::PageType::Memory;
current_page_table->pointers[vaddr >> PAGE_BITS] =
pointer - (vaddr & ~PAGE_MASK);
+ page_type = Common::PageType::Memory;
}
break;
}
@@ -591,9 +591,12 @@ struct Memory::Impl {
base + page_table.pointers.size());
if (!target) {
+ ASSERT_MSG(type != Common::PageType::Memory,
+ "Mapping memory page without a pointer @ {:016x}", base * PAGE_SIZE);
+
while (base != end) {
- page_table.pointers[base] = nullptr;
page_table.attributes[base] = type;
+ page_table.pointers[base] = nullptr;
page_table.backing_addr[base] = 0;
base += 1;
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index a9c2392b1..3bd76dd23 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -7,6 +7,10 @@ add_library(input_common STATIC
main.h
motion_emu.cpp
motion_emu.h
+ gcadapter/gc_adapter.cpp
+ gcadapter/gc_adapter.h
+ gcadapter/gc_poller.cpp
+ gcadapter/gc_poller.h
sdl/sdl.cpp
sdl/sdl.h
udp/client.cpp
@@ -26,5 +30,7 @@ if(SDL2_FOUND)
target_compile_definitions(input_common PRIVATE HAVE_SDL2)
endif()
+target_link_libraries(input_common PUBLIC ${LIBUSB_LIBRARIES})
+
create_target_directory_groups(input_common)
target_link_libraries(input_common PUBLIC core PRIVATE common Boost::boost)
diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp
new file mode 100644
index 000000000..6d9f4d9eb
--- /dev/null
+++ b/src/input_common/gcadapter/gc_adapter.cpp
@@ -0,0 +1,398 @@
+// Copyright 2014 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include <chrono>
+#include <thread>
+#include "common/logging/log.h"
+#include "input_common/gcadapter/gc_adapter.h"
+
+namespace GCAdapter {
+
+/// Used to loop through and assign button in poller
+constexpr std::array<PadButton, 12> PadButtonArray{
+ PadButton::PAD_BUTTON_LEFT, PadButton::PAD_BUTTON_RIGHT, PadButton::PAD_BUTTON_DOWN,
+ PadButton::PAD_BUTTON_UP, PadButton::PAD_TRIGGER_Z, PadButton::PAD_TRIGGER_R,
+ PadButton::PAD_TRIGGER_L, PadButton::PAD_BUTTON_A, PadButton::PAD_BUTTON_B,
+ PadButton::PAD_BUTTON_X, PadButton::PAD_BUTTON_Y, PadButton::PAD_BUTTON_START,
+};
+
+Adapter::Adapter() {
+ if (usb_adapter_handle != nullptr) {
+ return;
+ }
+ LOG_INFO(Input, "GC Adapter Initialization started");
+
+ current_status = NO_ADAPTER_DETECTED;
+
+ const int init_res = libusb_init(&libusb_ctx);
+ if (init_res == LIBUSB_SUCCESS) {
+ StartScanThread();
+ } else {
+ LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res);
+ }
+}
+
+GCPadStatus Adapter::GetPadStatus(int port, const std::array<u8, 37>& adapter_payload) {
+ GCPadStatus pad = {};
+ bool get_origin = false;
+
+ ControllerTypes type = ControllerTypes(adapter_payload[1 + (9 * port)] >> 4);
+ if (type != ControllerTypes::None) {
+ get_origin = true;
+ }
+
+ adapter_controllers_status[port] = type;
+
+ static constexpr std::array<PadButton, 8> b1_buttons{
+ PadButton::PAD_BUTTON_A, PadButton::PAD_BUTTON_B, PadButton::PAD_BUTTON_X,
+ PadButton::PAD_BUTTON_Y, PadButton::PAD_BUTTON_LEFT, PadButton::PAD_BUTTON_RIGHT,
+ PadButton::PAD_BUTTON_DOWN, PadButton::PAD_BUTTON_UP,
+ };
+
+ static constexpr std::array<PadButton, 4> b2_buttons{
+ PadButton::PAD_BUTTON_START,
+ PadButton::PAD_TRIGGER_Z,
+ PadButton::PAD_TRIGGER_R,
+ PadButton::PAD_TRIGGER_L,
+ };
+
+ if (adapter_controllers_status[port] != ControllerTypes::None) {
+ const u8 b1 = adapter_payload[1 + (9 * port) + 1];
+ const u8 b2 = adapter_payload[1 + (9 * port) + 2];
+
+ for (std::size_t i = 0; i < b1_buttons.size(); ++i) {
+ if ((b1 & (1U << i)) != 0) {
+ pad.button |= static_cast<u16>(b1_buttons[i]);
+ }
+ }
+
+ for (std::size_t j = 0; j < b2_buttons.size(); ++j) {
+ if ((b2 & (1U << j)) != 0) {
+ pad.button |= static_cast<u16>(b2_buttons[j]);
+ }
+ }
+
+ if (get_origin) {
+ pad.button |= PAD_GET_ORIGIN;
+ }
+
+ pad.stick_x = adapter_payload[1 + (9 * port) + 3];
+ pad.stick_y = adapter_payload[1 + (9 * port) + 4];
+ pad.substick_x = adapter_payload[1 + (9 * port) + 5];
+ pad.substick_y = adapter_payload[1 + (9 * port) + 6];
+ pad.trigger_left = adapter_payload[1 + (9 * port) + 7];
+ pad.trigger_right = adapter_payload[1 + (9 * port) + 8];
+ }
+ return pad;
+}
+
+void Adapter::PadToState(const GCPadStatus& pad, GCState& state) {
+ for (const auto& button : PadButtonArray) {
+ const u16 button_value = static_cast<u16>(button);
+ state.buttons.insert_or_assign(button_value, pad.button & button_value);
+ }
+
+ state.axes.insert_or_assign(static_cast<u8>(PadAxes::StickX), pad.stick_x);
+ state.axes.insert_or_assign(static_cast<u8>(PadAxes::StickY), pad.stick_y);
+ state.axes.insert_or_assign(static_cast<u8>(PadAxes::SubstickX), pad.substick_x);
+ state.axes.insert_or_assign(static_cast<u8>(PadAxes::SubstickY), pad.substick_y);
+ state.axes.insert_or_assign(static_cast<u8>(PadAxes::TriggerLeft), pad.trigger_left);
+ state.axes.insert_or_assign(static_cast<u8>(PadAxes::TriggerRight), pad.trigger_right);
+}
+
+void Adapter::Read() {
+ LOG_DEBUG(Input, "GC Adapter Read() thread started");
+
+ int payload_size_in, payload_size_copy;
+ std::array<u8, 37> adapter_payload;
+ std::array<u8, 37> adapter_payload_copy;
+ std::array<GCPadStatus, 4> pads;
+
+ while (adapter_thread_running) {
+ libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(),
+ sizeof(adapter_payload), &payload_size_in, 16);
+ payload_size_copy = 0;
+ // this mutex might be redundant?
+ {
+ std::lock_guard<std::mutex> lk(s_mutex);
+ std::copy(std::begin(adapter_payload), std::end(adapter_payload),
+ std::begin(adapter_payload_copy));
+ payload_size_copy = payload_size_in;
+ }
+
+ if (payload_size_copy != sizeof(adapter_payload_copy) ||
+ adapter_payload_copy[0] != LIBUSB_DT_HID) {
+ LOG_ERROR(Input, "error reading payload (size: {}, type: {:02x})", payload_size_copy,
+ adapter_payload_copy[0]);
+ adapter_thread_running = false; // error reading from adapter, stop reading.
+ break;
+ }
+ for (std::size_t port = 0; port < pads.size(); ++port) {
+ pads[port] = GetPadStatus(port, adapter_payload_copy);
+ if (DeviceConnected(port) && configuring) {
+ if (pads[port].button != PAD_GET_ORIGIN) {
+ pad_queue[port].Push(pads[port]);
+ }
+
+ // Accounting for a threshold here because of some controller variance
+ if (pads[port].stick_x > pads[port].MAIN_STICK_CENTER_X + pads[port].THRESHOLD ||
+ pads[port].stick_x < pads[port].MAIN_STICK_CENTER_X - pads[port].THRESHOLD) {
+ pads[port].axis = GCAdapter::PadAxes::StickX;
+ pads[port].axis_value = pads[port].stick_x;
+ pad_queue[port].Push(pads[port]);
+ }
+ if (pads[port].stick_y > pads[port].MAIN_STICK_CENTER_Y + pads[port].THRESHOLD ||
+ pads[port].stick_y < pads[port].MAIN_STICK_CENTER_Y - pads[port].THRESHOLD) {
+ pads[port].axis = GCAdapter::PadAxes::StickY;
+ pads[port].axis_value = pads[port].stick_y;
+ pad_queue[port].Push(pads[port]);
+ }
+ if (pads[port].substick_x > pads[port].C_STICK_CENTER_X + pads[port].THRESHOLD ||
+ pads[port].substick_x < pads[port].C_STICK_CENTER_X - pads[port].THRESHOLD) {
+ pads[port].axis = GCAdapter::PadAxes::SubstickX;
+ pads[port].axis_value = pads[port].substick_x;
+ pad_queue[port].Push(pads[port]);
+ }
+ if (pads[port].substick_y > pads[port].C_STICK_CENTER_Y + pads[port].THRESHOLD ||
+ pads[port].substick_y < pads[port].C_STICK_CENTER_Y - pads[port].THRESHOLD) {
+ pads[port].axis = GCAdapter::PadAxes::SubstickY;
+ pads[port].axis_value = pads[port].substick_y;
+ pad_queue[port].Push(pads[port]);
+ }
+ if (pads[port].trigger_left > pads[port].TRIGGER_THRESHOLD) {
+ pads[port].axis = GCAdapter::PadAxes::TriggerLeft;
+ pads[port].axis_value = pads[port].trigger_left;
+ pad_queue[port].Push(pads[port]);
+ }
+ if (pads[port].trigger_right > pads[port].TRIGGER_THRESHOLD) {
+ pads[port].axis = GCAdapter::PadAxes::TriggerRight;
+ pads[port].axis_value = pads[port].trigger_right;
+ pad_queue[port].Push(pads[port]);
+ }
+ }
+ PadToState(pads[port], state[port]);
+ }
+ std::this_thread::yield();
+ }
+}
+
+void Adapter::ScanThreadFunc() {
+ LOG_INFO(Input, "GC Adapter scanning thread started");
+
+ while (detect_thread_running) {
+ if (usb_adapter_handle == nullptr) {
+ std::lock_guard<std::mutex> lk(initialization_mutex);
+ Setup();
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(500));
+ }
+}
+
+void Adapter::StartScanThread() {
+ if (detect_thread_running) {
+ return;
+ }
+ if (!libusb_ctx) {
+ return;
+ }
+
+ detect_thread_running = true;
+ detect_thread = std::thread([=] { ScanThreadFunc(); });
+}
+
+void Adapter::StopScanThread() {
+ detect_thread_running = false;
+ detect_thread.join();
+}
+
+void Adapter::Setup() {
+ // Reset the error status in case the adapter gets unplugged
+ if (current_status < 0) {
+ current_status = NO_ADAPTER_DETECTED;
+ }
+
+ adapter_controllers_status.fill(ControllerTypes::None);
+
+ // pointer to list of connected usb devices
+ libusb_device** devices{};
+
+ // populate the list of devices, get the count
+ const ssize_t device_count = libusb_get_device_list(libusb_ctx, &devices);
+ if (device_count < 0) {
+ LOG_ERROR(Input, "libusb_get_device_list failed with error: {}", device_count);
+ detect_thread_running = false; // Stop the loop constantly checking for gc adapter
+ // TODO: For hotplug+gc adapter checkbox implementation, revert this.
+ return;
+ }
+
+ if (devices != nullptr) {
+ for (std::size_t index = 0; index < device_count; ++index) {
+ if (CheckDeviceAccess(devices[index])) {
+ // GC Adapter found and accessible, registering it
+ GetGCEndpoint(devices[index]);
+ break;
+ }
+ }
+ libusb_free_device_list(devices, 1);
+ }
+}
+
+bool Adapter::CheckDeviceAccess(libusb_device* device) {
+ libusb_device_descriptor desc;
+ const int get_descriptor_error = libusb_get_device_descriptor(device, &desc);
+ if (get_descriptor_error) {
+ // could not acquire the descriptor, no point in trying to use it.
+ LOG_ERROR(Input, "libusb_get_device_descriptor failed with error: {}",
+ get_descriptor_error);
+ return false;
+ }
+
+ if (desc.idVendor != 0x057e || desc.idProduct != 0x0337) {
+ // This isn't the device we are looking for.
+ return false;
+ }
+ const int open_error = libusb_open(device, &usb_adapter_handle);
+
+ if (open_error == LIBUSB_ERROR_ACCESS) {
+ LOG_ERROR(Input, "Yuzu can not gain access to this device: ID {:04X}:{:04X}.",
+ desc.idVendor, desc.idProduct);
+ return false;
+ }
+ if (open_error) {
+ LOG_ERROR(Input, "libusb_open failed to open device with error = {}", open_error);
+ return false;
+ }
+
+ int kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0);
+ if (kernel_driver_error == 1) {
+ kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle, 0);
+ if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) {
+ LOG_ERROR(Input, "libusb_detach_kernel_driver failed with error = {}",
+ kernel_driver_error);
+ }
+ }
+
+ if (kernel_driver_error && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) {
+ libusb_close(usb_adapter_handle);
+ usb_adapter_handle = nullptr;
+ return false;
+ }
+
+ const int interface_claim_error = libusb_claim_interface(usb_adapter_handle, 0);
+ if (interface_claim_error) {
+ LOG_ERROR(Input, "libusb_claim_interface failed with error = {}", interface_claim_error);
+ libusb_close(usb_adapter_handle);
+ usb_adapter_handle = nullptr;
+ return false;
+ }
+
+ return true;
+}
+
+void Adapter::GetGCEndpoint(libusb_device* device) {
+ libusb_config_descriptor* config = nullptr;
+ const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config);
+ if (config_descriptor_return != LIBUSB_SUCCESS) {
+ LOG_ERROR(Input, "libusb_get_config_descriptor failed with error = {}",
+ config_descriptor_return);
+ return;
+ }
+
+ for (u8 ic = 0; ic < config->bNumInterfaces; ic++) {
+ const libusb_interface* interfaceContainer = &config->interface[ic];
+ for (int i = 0; i < interfaceContainer->num_altsetting; i++) {
+ const libusb_interface_descriptor* interface = &interfaceContainer->altsetting[i];
+ for (u8 e = 0; e < interface->bNumEndpoints; e++) {
+ const libusb_endpoint_descriptor* endpoint = &interface->endpoint[e];
+ if (endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) {
+ input_endpoint = endpoint->bEndpointAddress;
+ } else {
+ output_endpoint = endpoint->bEndpointAddress;
+ }
+ }
+ }
+ }
+ // This transfer seems to be responsible for clearing the state of the adapter
+ // Used to clear the "busy" state of when the device is unexpectedly unplugged
+ unsigned char clear_payload = 0x13;
+ libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, &clear_payload,
+ sizeof(clear_payload), nullptr, 16);
+
+ adapter_thread_running = true;
+ current_status = ADAPTER_DETECTED;
+ adapter_input_thread = std::thread([=] { Read(); }); // Read input
+}
+
+Adapter::~Adapter() {
+ StopScanThread();
+ Reset();
+}
+
+void Adapter::Reset() {
+ std::unique_lock<std::mutex> lock(initialization_mutex, std::defer_lock);
+ if (!lock.try_lock()) {
+ return;
+ }
+ if (current_status != ADAPTER_DETECTED) {
+ return;
+ }
+
+ if (adapter_thread_running) {
+ adapter_thread_running = false;
+ }
+ adapter_input_thread.join();
+
+ adapter_controllers_status.fill(ControllerTypes::None);
+ current_status = NO_ADAPTER_DETECTED;
+
+ if (usb_adapter_handle) {
+ libusb_release_interface(usb_adapter_handle, 1);
+ libusb_close(usb_adapter_handle);
+ usb_adapter_handle = nullptr;
+ }
+
+ if (libusb_ctx) {
+ libusb_exit(libusb_ctx);
+ }
+}
+
+bool Adapter::DeviceConnected(int port) {
+ return adapter_controllers_status[port] != ControllerTypes::None;
+}
+
+void Adapter::ResetDeviceType(int port) {
+ adapter_controllers_status[port] = ControllerTypes::None;
+}
+
+void Adapter::BeginConfiguration() {
+ for (auto& pq : pad_queue) {
+ pq.Clear();
+ }
+ configuring = true;
+}
+
+void Adapter::EndConfiguration() {
+ for (auto& pq : pad_queue) {
+ pq.Clear();
+ }
+ configuring = false;
+}
+
+std::array<Common::SPSCQueue<GCPadStatus>, 4>& Adapter::GetPadQueue() {
+ return pad_queue;
+}
+
+const std::array<Common::SPSCQueue<GCPadStatus>, 4>& Adapter::GetPadQueue() const {
+ return pad_queue;
+}
+
+std::array<GCState, 4>& Adapter::GetPadState() {
+ return state;
+}
+
+const std::array<GCState, 4>& Adapter::GetPadState() const {
+ return state;
+}
+
+} // namespace GCAdapter
diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h
new file mode 100644
index 000000000..b1c2a1958
--- /dev/null
+++ b/src/input_common/gcadapter/gc_adapter.h
@@ -0,0 +1,161 @@
+// Copyright 2014 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+#include <algorithm>
+#include <functional>
+#include <mutex>
+#include <thread>
+#include <unordered_map>
+#include <libusb.h>
+#include "common/common_types.h"
+#include "common/threadsafe_queue.h"
+
+namespace GCAdapter {
+
+enum {
+ PAD_USE_ORIGIN = 0x0080,
+ PAD_GET_ORIGIN = 0x2000,
+ PAD_ERR_STATUS = 0x8000,
+};
+
+enum class PadButton {
+ PAD_BUTTON_LEFT = 0x0001,
+ PAD_BUTTON_RIGHT = 0x0002,
+ PAD_BUTTON_DOWN = 0x0004,
+ PAD_BUTTON_UP = 0x0008,
+ PAD_TRIGGER_Z = 0x0010,
+ PAD_TRIGGER_R = 0x0020,
+ PAD_TRIGGER_L = 0x0040,
+ PAD_BUTTON_A = 0x0100,
+ PAD_BUTTON_B = 0x0200,
+ PAD_BUTTON_X = 0x0400,
+ PAD_BUTTON_Y = 0x0800,
+ PAD_BUTTON_START = 0x1000,
+ // Below is for compatibility with "AxisButton" type
+ PAD_STICK = 0x2000,
+};
+
+extern const std::array<PadButton, 12> PadButtonArray;
+
+enum class PadAxes : u8 {
+ StickX,
+ StickY,
+ SubstickX,
+ SubstickY,
+ TriggerLeft,
+ TriggerRight,
+ Undefined,
+};
+
+struct GCPadStatus {
+ u16 button{}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits
+ u8 stick_x{}; // 0 <= stick_x <= 255
+ u8 stick_y{}; // 0 <= stick_y <= 255
+ u8 substick_x{}; // 0 <= substick_x <= 255
+ u8 substick_y{}; // 0 <= substick_y <= 255
+ u8 trigger_left{}; // 0 <= trigger_left <= 255
+ u8 trigger_right{}; // 0 <= trigger_right <= 255
+
+ static constexpr u8 MAIN_STICK_CENTER_X = 0x80;
+ static constexpr u8 MAIN_STICK_CENTER_Y = 0x80;
+ static constexpr u8 MAIN_STICK_RADIUS = 0x7f;
+ static constexpr u8 C_STICK_CENTER_X = 0x80;
+ static constexpr u8 C_STICK_CENTER_Y = 0x80;
+ static constexpr u8 C_STICK_RADIUS = 0x7f;
+ static constexpr u8 THRESHOLD = 10;
+
+ // 256/4, at least a quarter press to count as a press. For polling mostly
+ static constexpr u8 TRIGGER_THRESHOLD = 64;
+
+ u8 port{};
+ PadAxes axis{PadAxes::Undefined};
+ u8 axis_value{255};
+};
+
+struct GCState {
+ std::unordered_map<int, bool> buttons;
+ std::unordered_map<int, u16> axes;
+};
+
+enum class ControllerTypes { None, Wired, Wireless };
+
+enum {
+ NO_ADAPTER_DETECTED = 0,
+ ADAPTER_DETECTED = 1,
+};
+
+class Adapter {
+public:
+ /// Initialize the GC Adapter capture and read sequence
+ Adapter();
+
+ /// Close the adapter read thread and release the adapter
+ ~Adapter();
+ /// Used for polling
+ void BeginConfiguration();
+ void EndConfiguration();
+
+ std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue();
+ const std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue() const;
+
+ std::array<GCState, 4>& GetPadState();
+ const std::array<GCState, 4>& GetPadState() const;
+
+private:
+ GCPadStatus GetPadStatus(int port, const std::array<u8, 37>& adapter_payload);
+
+ void PadToState(const GCPadStatus& pad, GCState& state);
+
+ void Read();
+ void ScanThreadFunc();
+ /// Begin scanning for the GC Adapter.
+ void StartScanThread();
+
+ /// Stop scanning for the adapter
+ void StopScanThread();
+
+ /// Returns true if there is a device connected to port
+ bool DeviceConnected(int port);
+
+ /// Resets status of device connected to port
+ void ResetDeviceType(int port);
+
+ /// Returns true if we successfully gain access to GC Adapter
+ bool CheckDeviceAccess(libusb_device* device);
+
+ /// Captures GC Adapter endpoint address,
+ void GetGCEndpoint(libusb_device* device);
+
+ /// For shutting down, clear all data, join all threads, release usb
+ void Reset();
+
+ /// For use in initialization, querying devices to find the adapter
+ void Setup();
+
+ int current_status = NO_ADAPTER_DETECTED;
+ libusb_device_handle* usb_adapter_handle = nullptr;
+ std::array<ControllerTypes, 4> adapter_controllers_status{};
+
+ std::mutex s_mutex;
+
+ std::thread adapter_input_thread;
+ bool adapter_thread_running;
+
+ std::mutex initialization_mutex;
+ std::thread detect_thread;
+ bool detect_thread_running = false;
+
+ libusb_context* libusb_ctx;
+
+ u8 input_endpoint = 0;
+ u8 output_endpoint = 0;
+
+ bool configuring = false;
+
+ std::array<Common::SPSCQueue<GCPadStatus>, 4> pad_queue;
+ std::array<GCState, 4> state;
+};
+
+} // namespace GCAdapter
diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp
new file mode 100644
index 000000000..385ce8430
--- /dev/null
+++ b/src/input_common/gcadapter/gc_poller.cpp
@@ -0,0 +1,272 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <atomic>
+#include <list>
+#include <mutex>
+#include <utility>
+#include "common/threadsafe_queue.h"
+#include "input_common/gcadapter/gc_adapter.h"
+#include "input_common/gcadapter/gc_poller.h"
+
+namespace InputCommon {
+
+class GCButton final : public Input::ButtonDevice {
+public:
+ explicit GCButton(int port_, int button_, GCAdapter::Adapter* adapter)
+ : port(port_), button(button_), gcadapter(adapter) {}
+
+ ~GCButton() override;
+
+ bool GetStatus() const override {
+ return gcadapter->GetPadState()[port].buttons.at(button);
+ }
+
+private:
+ const int port;
+ const int button;
+ GCAdapter::Adapter* gcadapter;
+};
+
+class GCAxisButton final : public Input::ButtonDevice {
+public:
+ explicit GCAxisButton(int port_, int axis_, float threshold_, bool trigger_if_greater_,
+ GCAdapter::Adapter* adapter)
+ : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_),
+ gcadapter(adapter) {
+ // L/R triggers range is only in positive direction beginning near 0
+ // 0.0 threshold equates to near half trigger press, but threshold accounts for variability.
+ if (axis > 3) {
+ threshold *= -0.5;
+ }
+ }
+
+ bool GetStatus() const override {
+ const float axis_value = (gcadapter->GetPadState()[port].axes.at(axis) - 128.0f) / 128.0f;
+ if (trigger_if_greater) {
+ // TODO: Might be worthwile to set a slider for the trigger threshold. It is currently
+ // always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick
+ return axis_value > threshold;
+ }
+ return axis_value < -threshold;
+ }
+
+private:
+ const int port;
+ const int axis;
+ float threshold;
+ bool trigger_if_greater;
+ GCAdapter::Adapter* gcadapter;
+};
+
+GCButtonFactory::GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
+ : adapter(std::move(adapter_)) {}
+
+GCButton::~GCButton() = default;
+
+std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::ParamPackage& params) {
+ const int button_id = params.Get("button", 0);
+ const int port = params.Get("port", 0);
+
+ constexpr int PAD_STICK_ID = static_cast<u16>(GCAdapter::PadButton::PAD_STICK);
+
+ // button is not an axis/stick button
+ if (button_id != PAD_STICK_ID) {
+ auto button = std::make_unique<GCButton>(port, button_id, adapter.get());
+ return std::move(button);
+ }
+
+ // For Axis buttons, used by the binary sticks.
+ if (button_id == PAD_STICK_ID) {
+ const int axis = params.Get("axis", 0);
+ const float threshold = params.Get("threshold", 0.25f);
+ const std::string direction_name = params.Get("direction", "");
+ bool trigger_if_greater;
+ if (direction_name == "+") {
+ trigger_if_greater = true;
+ } else if (direction_name == "-") {
+ trigger_if_greater = false;
+ } else {
+ trigger_if_greater = true;
+ LOG_ERROR(Input, "Unknown direction {}", direction_name);
+ }
+ return std::make_unique<GCAxisButton>(port, axis, threshold, trigger_if_greater,
+ adapter.get());
+ }
+}
+
+Common::ParamPackage GCButtonFactory::GetNextInput() {
+ Common::ParamPackage params;
+ GCAdapter::GCPadStatus pad;
+ auto& queue = adapter->GetPadQueue();
+ for (std::size_t port = 0; port < queue.size(); ++port) {
+ while (queue[port].Pop(pad)) {
+ // This while loop will break on the earliest detected button
+ params.Set("engine", "gcpad");
+ params.Set("port", static_cast<int>(port));
+ for (const auto& button : GCAdapter::PadButtonArray) {
+ const u16 button_value = static_cast<u16>(button);
+ if (pad.button & button_value) {
+ params.Set("button", button_value);
+ break;
+ }
+ }
+
+ // For Axis button implementation
+ if (pad.axis != GCAdapter::PadAxes::Undefined) {
+ params.Set("axis", static_cast<u8>(pad.axis));
+ params.Set("button", static_cast<u16>(GCAdapter::PadButton::PAD_STICK));
+ if (pad.axis_value > 128) {
+ params.Set("direction", "+");
+ params.Set("threshold", "0.25");
+ } else {
+ params.Set("direction", "-");
+ params.Set("threshold", "-0.25");
+ }
+ break;
+ }
+ }
+ }
+ return params;
+}
+
+void GCButtonFactory::BeginConfiguration() {
+ polling = true;
+ adapter->BeginConfiguration();
+}
+
+void GCButtonFactory::EndConfiguration() {
+ polling = false;
+ adapter->EndConfiguration();
+}
+
+class GCAnalog final : public Input::AnalogDevice {
+public:
+ GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_, GCAdapter::Adapter* adapter)
+ : port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter) {}
+
+ float GetAxis(int axis) const {
+ std::lock_guard lock{mutex};
+ // division is not by a perfect 128 to account for some variance in center location
+ // e.g. my device idled at 131 in X, 120 in Y, and full range of motion was in range
+ // [20-230]
+ return (gcadapter->GetPadState()[port].axes.at(axis) - 128.0f) / 95.0f;
+ }
+
+ std::pair<float, float> GetAnalog(int axis_x, int axis_y) const {
+ float x = GetAxis(axis_x);
+ float y = GetAxis(axis_y);
+
+ // Make sure the coordinates are in the unit circle,
+ // otherwise normalize it.
+ float r = x * x + y * y;
+ if (r > 1.0f) {
+ r = std::sqrt(r);
+ x /= r;
+ y /= r;
+ }
+
+ return {x, y};
+ }
+
+ std::tuple<float, float> GetStatus() const override {
+ const auto [x, y] = GetAnalog(axis_x, axis_y);
+ const float r = std::sqrt((x * x) + (y * y));
+ if (r > deadzone) {
+ return {x / r * (r - deadzone) / (1 - deadzone),
+ y / r * (r - deadzone) / (1 - deadzone)};
+ }
+ return {0.0f, 0.0f};
+ }
+
+ bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
+ const auto [x, y] = GetStatus();
+ const float directional_deadzone = 0.4f;
+ switch (direction) {
+ case Input::AnalogDirection::RIGHT:
+ return x > directional_deadzone;
+ case Input::AnalogDirection::LEFT:
+ return x < -directional_deadzone;
+ case Input::AnalogDirection::UP:
+ return y > directional_deadzone;
+ case Input::AnalogDirection::DOWN:
+ return y < -directional_deadzone;
+ }
+ return false;
+ }
+
+private:
+ const int port;
+ const int axis_x;
+ const int axis_y;
+ const float deadzone;
+ mutable std::mutex mutex;
+ GCAdapter::Adapter* gcadapter;
+};
+
+/// An analog device factory that creates analog devices from GC Adapter
+GCAnalogFactory::GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
+ : adapter(std::move(adapter_)) {}
+
+/**
+ * Creates analog device from joystick axes
+ * @param params contains parameters for creating the device:
+ * - "port": the nth gcpad on the adapter
+ * - "axis_x": the index of the axis to be bind as x-axis
+ * - "axis_y": the index of the axis to be bind as y-axis
+ */
+std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::ParamPackage& params) {
+ const int port = params.Get("port", 0);
+ const int axis_x = params.Get("axis_x", 0);
+ const int axis_y = params.Get("axis_y", 1);
+ const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f);
+
+ return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone, adapter.get());
+}
+
+void GCAnalogFactory::BeginConfiguration() {
+ polling = true;
+ adapter->BeginConfiguration();
+}
+
+void GCAnalogFactory::EndConfiguration() {
+ polling = false;
+ adapter->EndConfiguration();
+}
+
+Common::ParamPackage GCAnalogFactory::GetNextInput() {
+ GCAdapter::GCPadStatus pad;
+ auto& queue = adapter->GetPadQueue();
+ for (std::size_t port = 0; port < queue.size(); ++port) {
+ while (queue[port].Pop(pad)) {
+ if (pad.axis == GCAdapter::PadAxes::Undefined ||
+ std::abs((pad.axis_value - 128.0f) / 128.0f) < 0.1) {
+ continue;
+ }
+ // An analog device needs two axes, so we need to store the axis for later and wait for
+ // a second input event. The axes also must be from the same joystick.
+ const u8 axis = static_cast<u8>(pad.axis);
+ if (analog_x_axis == -1) {
+ analog_x_axis = axis;
+ controller_number = port;
+ } else if (analog_y_axis == -1 && analog_x_axis != axis && controller_number == port) {
+ analog_y_axis = axis;
+ }
+ }
+ }
+ Common::ParamPackage params;
+ if (analog_x_axis != -1 && analog_y_axis != -1) {
+ params.Set("engine", "gcpad");
+ params.Set("port", controller_number);
+ params.Set("axis_x", analog_x_axis);
+ params.Set("axis_y", analog_y_axis);
+ analog_x_axis = -1;
+ analog_y_axis = -1;
+ controller_number = -1;
+ return params;
+ }
+ return params;
+}
+
+} // namespace InputCommon
diff --git a/src/input_common/gcadapter/gc_poller.h b/src/input_common/gcadapter/gc_poller.h
new file mode 100644
index 000000000..e96af7d51
--- /dev/null
+++ b/src/input_common/gcadapter/gc_poller.h
@@ -0,0 +1,67 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include "core/frontend/input.h"
+#include "input_common/gcadapter/gc_adapter.h"
+
+namespace InputCommon {
+
+/**
+ * A button device factory representing a gcpad. It receives gcpad events and forward them
+ * to all button devices it created.
+ */
+class GCButtonFactory final : public Input::Factory<Input::ButtonDevice> {
+public:
+ explicit GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
+
+ /**
+ * Creates a button device from a button press
+ * @param params contains parameters for creating the device:
+ * - "code": the code of the key to bind with the button
+ */
+ std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
+
+ Common::ParamPackage GetNextInput();
+
+ /// For device input configuration/polling
+ void BeginConfiguration();
+ void EndConfiguration();
+
+ bool IsPolling() const {
+ return polling;
+ }
+
+private:
+ std::shared_ptr<GCAdapter::Adapter> adapter;
+ bool polling = false;
+};
+
+/// An analog device factory that creates analog devices from GC Adapter
+class GCAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
+public:
+ explicit GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
+
+ std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
+ Common::ParamPackage GetNextInput();
+
+ /// For device input configuration/polling
+ void BeginConfiguration();
+ void EndConfiguration();
+
+ bool IsPolling() const {
+ return polling;
+ }
+
+private:
+ std::shared_ptr<GCAdapter::Adapter> adapter;
+ int analog_x_axis = -1;
+ int analog_y_axis = -1;
+ int controller_number = -1;
+ bool polling = false;
+};
+
+} // namespace InputCommon
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index 95e351e24..fd0af1019 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -4,8 +4,11 @@
#include <memory>
#include <thread>
+#include <libusb.h>
#include "common/param_package.h"
#include "input_common/analog_from_button.h"
+#include "input_common/gcadapter/gc_adapter.h"
+#include "input_common/gcadapter/gc_poller.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
#include "input_common/motion_emu.h"
@@ -22,8 +25,16 @@ static std::shared_ptr<MotionEmu> motion_emu;
static std::unique_ptr<SDL::State> sdl;
#endif
static std::unique_ptr<CemuhookUDP::State> udp;
+static std::shared_ptr<GCButtonFactory> gcbuttons;
+static std::shared_ptr<GCAnalogFactory> gcanalog;
void Init() {
+ auto gcadapter = std::make_shared<GCAdapter::Adapter>();
+ gcbuttons = std::make_shared<GCButtonFactory>(gcadapter);
+ Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons);
+ gcanalog = std::make_shared<GCAnalogFactory>(gcadapter);
+ Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog);
+
keyboard = std::make_shared<Keyboard>();
Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
@@ -48,6 +59,11 @@ void Shutdown() {
sdl.reset();
#endif
udp.reset();
+ Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
+ Input::UnregisterFactory<Input::AnalogDevice>("gcpad");
+
+ gcbuttons.reset();
+ gcanalog.reset();
}
Keyboard* GetKeyboard() {
@@ -58,6 +74,14 @@ MotionEmu* GetMotionEmu() {
return motion_emu.get();
}
+GCButtonFactory* GetGCButtons() {
+ return gcbuttons.get();
+}
+
+GCAnalogFactory* GetGCAnalogs() {
+ return gcanalog.get();
+}
+
std::string GenerateKeyboardParam(int key_code) {
Common::ParamPackage param{
{"engine", "keyboard"},
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 77a0ce90b..0e32856f6 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -7,6 +7,7 @@
#include <memory>
#include <string>
#include <vector>
+#include "input_common/gcadapter/gc_poller.h"
namespace Common {
class ParamPackage;
@@ -30,6 +31,10 @@ class MotionEmu;
/// Gets the motion emulation factory.
MotionEmu* GetMotionEmu();
+GCButtonFactory* GetGCButtons();
+
+GCAnalogFactory* GetGCAnalogs();
+
/// Generates a serialized param package for creating a keyboard button device
std::string GenerateKeyboardParam(int key_code);
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index cf8bdd021..c6479af9f 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -322,8 +322,7 @@ protected:
}
private:
- MapInterval* MapAddress(const Buffer* block, GPUVAddr gpu_addr, VAddr cpu_addr,
- std::size_t size) {
+ MapInterval* MapAddress(Buffer* block, GPUVAddr gpu_addr, VAddr cpu_addr, std::size_t size) {
const VectorMapInterval overlaps = GetMapsInRange(cpu_addr, size);
if (overlaps.empty()) {
auto& memory_manager = system.GPU().MemoryManager();
@@ -377,8 +376,7 @@ private:
return map;
}
- void UpdateBlock(const Buffer* block, VAddr start, VAddr end,
- const VectorMapInterval& overlaps) {
+ void UpdateBlock(Buffer* block, VAddr start, VAddr end, const VectorMapInterval& overlaps) {
const IntervalType base_interval{start, end};
IntervalSet interval_set{};
interval_set.add(base_interval);
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index d9f7b4cc6..e461e4c70 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -34,20 +34,27 @@ Buffer::Buffer(const Device& device, VAddr cpu_addr, std::size_t size)
Buffer::~Buffer() = default;
-void Buffer::Upload(std::size_t offset, std::size_t size, const u8* data) const {
+void Buffer::Upload(std::size_t offset, std::size_t size, const u8* data) {
glNamedBufferSubData(Handle(), static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size),
data);
}
-void Buffer::Download(std::size_t offset, std::size_t size, u8* data) const {
+void Buffer::Download(std::size_t offset, std::size_t size, u8* data) {
MICROPROFILE_SCOPE(OpenGL_Buffer_Download);
+ const GLsizeiptr gl_size = static_cast<GLsizeiptr>(size);
+ const GLintptr gl_offset = static_cast<GLintptr>(offset);
+ if (read_buffer.handle == 0) {
+ read_buffer.Create();
+ glNamedBufferData(read_buffer.handle, static_cast<GLsizeiptr>(Size()), nullptr,
+ GL_STREAM_READ);
+ }
glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
- glGetNamedBufferSubData(Handle(), static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size),
- data);
+ glCopyNamedBufferSubData(gl_buffer.handle, read_buffer.handle, gl_offset, gl_offset, gl_size);
+ glGetNamedBufferSubData(read_buffer.handle, gl_offset, gl_size, data);
}
void Buffer::CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset,
- std::size_t size) const {
+ std::size_t size) {
glCopyNamedBufferSubData(src.Handle(), Handle(), static_cast<GLintptr>(src_offset),
static_cast<GLintptr>(dst_offset), static_cast<GLsizeiptr>(size));
}
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h
index 59d95adbc..88fdc0536 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.h
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.h
@@ -28,12 +28,12 @@ public:
explicit Buffer(const Device& device, VAddr cpu_addr, std::size_t size);
~Buffer();
- void Upload(std::size_t offset, std::size_t size, const u8* data) const;
+ void Upload(std::size_t offset, std::size_t size, const u8* data);
- void Download(std::size_t offset, std::size_t size, u8* data) const;
+ void Download(std::size_t offset, std::size_t size, u8* data);
void CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset,
- std::size_t size) const;
+ std::size_t size);
GLuint Handle() const noexcept {
return gl_buffer.handle;
@@ -45,6 +45,7 @@ public:
private:
OGLBuffer gl_buffer;
+ OGLBuffer read_buffer;
u64 gpu_address = 0;
};
diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h
index 774e70a5b..fe9bd4b5a 100644
--- a/src/video_core/renderer_opengl/maxwell_to_gl.h
+++ b/src/video_core/renderer_opengl/maxwell_to_gl.h
@@ -191,6 +191,12 @@ inline GLenum WrapMode(Tegra::Texture::WrapMode wrap_mode) {
} else {
return GL_MIRROR_CLAMP_TO_EDGE;
}
+ case Tegra::Texture::WrapMode::MirrorOnceClampOGL:
+ if (GL_EXT_texture_mirror_clamp) {
+ return GL_MIRROR_CLAMP_EXT;
+ } else {
+ return GL_MIRROR_CLAMP_TO_EDGE;
+ }
}
UNIMPLEMENTED_MSG("Unimplemented texture wrap mode={}", static_cast<u32>(wrap_mode));
return GL_REPEAT;
diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
index 424278816..d1f0ea932 100644
--- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
+++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
@@ -39,52 +39,18 @@ constexpr std::array POLYGON_OFFSET_ENABLE_LUT = {
} // Anonymous namespace
-void FixedPipelineState::DepthStencil::Fill(const Maxwell& regs) noexcept {
- raw = 0;
- front.action_stencil_fail.Assign(PackStencilOp(regs.stencil_front_op_fail));
- front.action_depth_fail.Assign(PackStencilOp(regs.stencil_front_op_zfail));
- front.action_depth_pass.Assign(PackStencilOp(regs.stencil_front_op_zpass));
- front.test_func.Assign(PackComparisonOp(regs.stencil_front_func_func));
- if (regs.stencil_two_side_enable) {
- back.action_stencil_fail.Assign(PackStencilOp(regs.stencil_back_op_fail));
- back.action_depth_fail.Assign(PackStencilOp(regs.stencil_back_op_zfail));
- back.action_depth_pass.Assign(PackStencilOp(regs.stencil_back_op_zpass));
- back.test_func.Assign(PackComparisonOp(regs.stencil_back_func_func));
- } else {
- back.action_stencil_fail.Assign(front.action_stencil_fail);
- back.action_depth_fail.Assign(front.action_depth_fail);
- back.action_depth_pass.Assign(front.action_depth_pass);
- back.test_func.Assign(front.test_func);
- }
- depth_test_enable.Assign(regs.depth_test_enable);
- depth_write_enable.Assign(regs.depth_write_enabled);
- depth_bounds_enable.Assign(regs.depth_bounds_enable);
- stencil_enable.Assign(regs.stencil_enable);
- depth_test_func.Assign(PackComparisonOp(regs.depth_test_func));
-}
-
-void FixedPipelineState::Rasterizer::Fill(const Maxwell& regs) noexcept {
+void FixedPipelineState::Fill(const Maxwell& regs, bool has_extended_dynamic_state) {
const auto& clip = regs.view_volume_clip_control;
const std::array enabled_lut = {regs.polygon_offset_point_enable,
regs.polygon_offset_line_enable,
regs.polygon_offset_fill_enable};
const u32 topology_index = static_cast<u32>(regs.draw.topology.Value());
- u32 packed_front_face = PackFrontFace(regs.front_face);
- if (regs.screen_y_control.triangle_rast_flip != 0) {
- // Flip front face
- packed_front_face = 1 - packed_front_face;
- }
-
raw = 0;
- topology.Assign(topology_index);
primitive_restart_enable.Assign(regs.primitive_restart.enabled != 0 ? 1 : 0);
- cull_enable.Assign(regs.cull_test_enabled != 0 ? 1 : 0);
depth_bias_enable.Assign(enabled_lut[POLYGON_OFFSET_ENABLE_LUT[topology_index]] != 0 ? 1 : 0);
depth_clamp_disabled.Assign(regs.view_volume_clip_control.depth_clamp_disabled.Value());
ndc_minus_one_to_one.Assign(regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1 : 0);
- cull_face.Assign(PackCullFace(regs.cull_face));
- front_face.Assign(packed_front_face);
polygon_mode.Assign(PackPolygonMode(regs.polygon_mode_front));
patch_control_points_minus_one.Assign(regs.patch_vertices - 1);
tessellation_primitive.Assign(static_cast<u32>(regs.tess_mode.prim.Value()));
@@ -93,19 +59,37 @@ void FixedPipelineState::Rasterizer::Fill(const Maxwell& regs) noexcept {
logic_op_enable.Assign(regs.logic_op.enable != 0 ? 1 : 0);
logic_op.Assign(PackLogicOp(regs.logic_op.operation));
rasterize_enable.Assign(regs.rasterize_enable != 0 ? 1 : 0);
+
std::memcpy(&point_size, &regs.point_size, sizeof(point_size)); // TODO: C++20 std::bit_cast
-}
-void FixedPipelineState::ColorBlending::Fill(const Maxwell& regs) noexcept {
+ for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
+ binding_divisors[index] =
+ regs.instanced_arrays.IsInstancingEnabled(index) ? regs.vertex_array[index].divisor : 0;
+ }
+
+ for (std::size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {
+ const auto& input = regs.vertex_attrib_format[index];
+ auto& attribute = attributes[index];
+ attribute.raw = 0;
+ attribute.enabled.Assign(input.IsConstant() ? 0 : 1);
+ attribute.buffer.Assign(input.buffer);
+ attribute.offset.Assign(input.offset);
+ attribute.type.Assign(static_cast<u32>(input.type.Value()));
+ attribute.size.Assign(static_cast<u32>(input.size.Value()));
+ }
+
for (std::size_t index = 0; index < std::size(attachments); ++index) {
attachments[index].Fill(regs, index);
}
-}
-void FixedPipelineState::ViewportSwizzles::Fill(const Maxwell& regs) noexcept {
const auto& transform = regs.viewport_transform;
- std::transform(transform.begin(), transform.end(), swizzles.begin(),
+ std::transform(transform.begin(), transform.end(), viewport_swizzles.begin(),
[](const auto& viewport) { return static_cast<u16>(viewport.swizzle.raw); });
+
+ if (!has_extended_dynamic_state) {
+ no_extended_dynamic_state.Assign(1);
+ dynamic_state.Fill(regs);
+ }
}
void FixedPipelineState::BlendingAttachment::Fill(const Maxwell& regs, std::size_t index) {
@@ -147,20 +131,57 @@ void FixedPipelineState::BlendingAttachment::Fill(const Maxwell& regs, std::size
enable.Assign(1);
}
-void FixedPipelineState::Fill(const Maxwell& regs) {
- rasterizer.Fill(regs);
- depth_stencil.Fill(regs);
- color_blending.Fill(regs);
- viewport_swizzles.Fill(regs);
+void FixedPipelineState::DynamicState::Fill(const Maxwell& regs) {
+ const u32 topology_index = static_cast<u32>(regs.draw.topology.Value());
+ u32 packed_front_face = PackFrontFace(regs.front_face);
+ if (regs.screen_y_control.triangle_rast_flip != 0) {
+ // Flip front face
+ packed_front_face = 1 - packed_front_face;
+ }
+
+ raw1 = 0;
+ raw2 = 0;
+ front.action_stencil_fail.Assign(PackStencilOp(regs.stencil_front_op_fail));
+ front.action_depth_fail.Assign(PackStencilOp(regs.stencil_front_op_zfail));
+ front.action_depth_pass.Assign(PackStencilOp(regs.stencil_front_op_zpass));
+ front.test_func.Assign(PackComparisonOp(regs.stencil_front_func_func));
+ if (regs.stencil_two_side_enable) {
+ back.action_stencil_fail.Assign(PackStencilOp(regs.stencil_back_op_fail));
+ back.action_depth_fail.Assign(PackStencilOp(regs.stencil_back_op_zfail));
+ back.action_depth_pass.Assign(PackStencilOp(regs.stencil_back_op_zpass));
+ back.test_func.Assign(PackComparisonOp(regs.stencil_back_func_func));
+ } else {
+ back.action_stencil_fail.Assign(front.action_stencil_fail);
+ back.action_depth_fail.Assign(front.action_depth_fail);
+ back.action_depth_pass.Assign(front.action_depth_pass);
+ back.test_func.Assign(front.test_func);
+ }
+ stencil_enable.Assign(regs.stencil_enable);
+ depth_write_enable.Assign(regs.depth_write_enabled);
+ depth_bounds_enable.Assign(regs.depth_bounds_enable);
+ depth_test_enable.Assign(regs.depth_test_enable);
+ front_face.Assign(packed_front_face);
+ depth_test_func.Assign(PackComparisonOp(regs.depth_test_func));
+ topology.Assign(topology_index);
+ cull_face.Assign(PackCullFace(regs.cull_face));
+ cull_enable.Assign(regs.cull_test_enabled != 0 ? 1 : 0);
+
+ for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
+ const auto& input = regs.vertex_array[index];
+ VertexBinding& binding = vertex_bindings[index];
+ binding.raw = 0;
+ binding.enabled.Assign(input.IsEnabled() ? 1 : 0);
+ binding.stride.Assign(static_cast<u16>(input.stride.Value()));
+ }
}
std::size_t FixedPipelineState::Hash() const noexcept {
- const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), sizeof *this);
+ const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), Size());
return static_cast<std::size_t>(hash);
}
bool FixedPipelineState::operator==(const FixedPipelineState& rhs) const noexcept {
- return std::memcmp(this, &rhs, sizeof *this) == 0;
+ return std::memcmp(this, &rhs, Size()) == 0;
}
u32 FixedPipelineState::PackComparisonOp(Maxwell::ComparisonOp op) noexcept {
diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h
index 31a6398f2..cdcbb65f5 100644
--- a/src/video_core/renderer_vulkan/fixed_pipeline_state.h
+++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h
@@ -60,14 +60,6 @@ struct FixedPipelineState {
void Fill(const Maxwell& regs, std::size_t index);
- std::size_t Hash() const noexcept;
-
- bool operator==(const BlendingAttachment& rhs) const noexcept;
-
- bool operator!=(const BlendingAttachment& rhs) const noexcept {
- return !operator==(rhs);
- }
-
constexpr std::array<bool, 4> Mask() const noexcept {
return {mask_r != 0, mask_g != 0, mask_b != 0, mask_a != 0};
}
@@ -97,156 +89,116 @@ struct FixedPipelineState {
}
};
- struct VertexInput {
- union Binding {
- u16 raw;
- BitField<0, 1, u16> enabled;
- BitField<1, 12, u16> stride;
- };
+ union VertexAttribute {
+ u32 raw;
+ BitField<0, 1, u32> enabled;
+ BitField<1, 5, u32> buffer;
+ BitField<6, 14, u32> offset;
+ BitField<20, 3, u32> type;
+ BitField<23, 6, u32> size;
- union Attribute {
- u32 raw;
- BitField<0, 1, u32> enabled;
- BitField<1, 5, u32> buffer;
- BitField<6, 14, u32> offset;
- BitField<20, 3, u32> type;
- BitField<23, 6, u32> size;
-
- constexpr Maxwell::VertexAttribute::Type Type() const noexcept {
- return static_cast<Maxwell::VertexAttribute::Type>(type.Value());
- }
-
- constexpr Maxwell::VertexAttribute::Size Size() const noexcept {
- return static_cast<Maxwell::VertexAttribute::Size>(size.Value());
- }
- };
-
- std::array<Binding, Maxwell::NumVertexArrays> bindings;
- std::array<u32, Maxwell::NumVertexArrays> binding_divisors;
- std::array<Attribute, Maxwell::NumVertexAttributes> attributes;
-
- void SetBinding(std::size_t index, bool enabled, u32 stride, u32 divisor) noexcept {
- auto& binding = bindings[index];
- binding.raw = 0;
- binding.enabled.Assign(enabled ? 1 : 0);
- binding.stride.Assign(static_cast<u16>(stride));
- binding_divisors[index] = divisor;
+ constexpr Maxwell::VertexAttribute::Type Type() const noexcept {
+ return static_cast<Maxwell::VertexAttribute::Type>(type.Value());
}
- void SetAttribute(std::size_t index, bool enabled, u32 buffer, u32 offset,
- Maxwell::VertexAttribute::Type type,
- Maxwell::VertexAttribute::Size size) noexcept {
- auto& attribute = attributes[index];
- attribute.raw = 0;
- attribute.enabled.Assign(enabled ? 1 : 0);
- attribute.buffer.Assign(buffer);
- attribute.offset.Assign(offset);
- attribute.type.Assign(static_cast<u32>(type));
- attribute.size.Assign(static_cast<u32>(size));
+ constexpr Maxwell::VertexAttribute::Size Size() const noexcept {
+ return static_cast<Maxwell::VertexAttribute::Size>(size.Value());
}
};
- struct Rasterizer {
- union {
- u32 raw;
- BitField<0, 4, u32> topology;
- BitField<4, 1, u32> primitive_restart_enable;
- BitField<5, 1, u32> cull_enable;
- BitField<6, 1, u32> depth_bias_enable;
- BitField<7, 1, u32> depth_clamp_disabled;
- BitField<8, 1, u32> ndc_minus_one_to_one;
- BitField<9, 2, u32> cull_face;
- BitField<11, 1, u32> front_face;
- BitField<12, 2, u32> polygon_mode;
- BitField<14, 5, u32> patch_control_points_minus_one;
- BitField<19, 2, u32> tessellation_primitive;
- BitField<21, 2, u32> tessellation_spacing;
- BitField<23, 1, u32> tessellation_clockwise;
- BitField<24, 1, u32> logic_op_enable;
- BitField<25, 4, u32> logic_op;
- BitField<29, 1, u32> rasterize_enable;
- };
-
- // TODO(Rodrigo): Move this to push constants
- u32 point_size;
+ template <std::size_t Position>
+ union StencilFace {
+ BitField<Position + 0, 3, u32> action_stencil_fail;
+ BitField<Position + 3, 3, u32> action_depth_fail;
+ BitField<Position + 6, 3, u32> action_depth_pass;
+ BitField<Position + 9, 3, u32> test_func;
- void Fill(const Maxwell& regs) noexcept;
+ Maxwell::StencilOp ActionStencilFail() const noexcept {
+ return UnpackStencilOp(action_stencil_fail);
+ }
- constexpr Maxwell::PrimitiveTopology Topology() const noexcept {
- return static_cast<Maxwell::PrimitiveTopology>(topology.Value());
+ Maxwell::StencilOp ActionDepthFail() const noexcept {
+ return UnpackStencilOp(action_depth_fail);
}
- Maxwell::CullFace CullFace() const noexcept {
- return UnpackCullFace(cull_face.Value());
+ Maxwell::StencilOp ActionDepthPass() const noexcept {
+ return UnpackStencilOp(action_depth_pass);
}
- Maxwell::FrontFace FrontFace() const noexcept {
- return UnpackFrontFace(front_face.Value());
+ Maxwell::ComparisonOp TestFunc() const noexcept {
+ return UnpackComparisonOp(test_func);
}
};
- struct DepthStencil {
- template <std::size_t Position>
- union StencilFace {
- BitField<Position + 0, 3, u32> action_stencil_fail;
- BitField<Position + 3, 3, u32> action_depth_fail;
- BitField<Position + 6, 3, u32> action_depth_pass;
- BitField<Position + 9, 3, u32> test_func;
-
- Maxwell::StencilOp ActionStencilFail() const noexcept {
- return UnpackStencilOp(action_stencil_fail);
- }
-
- Maxwell::StencilOp ActionDepthFail() const noexcept {
- return UnpackStencilOp(action_depth_fail);
- }
-
- Maxwell::StencilOp ActionDepthPass() const noexcept {
- return UnpackStencilOp(action_depth_pass);
- }
-
- Maxwell::ComparisonOp TestFunc() const noexcept {
- return UnpackComparisonOp(test_func);
- }
- };
+ union VertexBinding {
+ u16 raw;
+ BitField<0, 12, u16> stride;
+ BitField<12, 1, u16> enabled;
+ };
+ struct DynamicState {
union {
- u32 raw;
+ u32 raw1;
StencilFace<0> front;
StencilFace<12> back;
- BitField<24, 1, u32> depth_test_enable;
+ BitField<24, 1, u32> stencil_enable;
BitField<25, 1, u32> depth_write_enable;
BitField<26, 1, u32> depth_bounds_enable;
- BitField<27, 1, u32> stencil_enable;
- BitField<28, 3, u32> depth_test_func;
+ BitField<27, 1, u32> depth_test_enable;
+ BitField<28, 1, u32> front_face;
+ BitField<29, 3, u32> depth_test_func;
+ };
+ union {
+ u32 raw2;
+ BitField<0, 4, u32> topology;
+ BitField<4, 2, u32> cull_face;
+ BitField<6, 1, u32> cull_enable;
};
+ std::array<VertexBinding, Maxwell::NumVertexArrays> vertex_bindings;
- void Fill(const Maxwell& regs) noexcept;
+ void Fill(const Maxwell& regs);
Maxwell::ComparisonOp DepthTestFunc() const noexcept {
return UnpackComparisonOp(depth_test_func);
}
- };
-
- struct ColorBlending {
- std::array<BlendingAttachment, Maxwell::NumRenderTargets> attachments;
- void Fill(const Maxwell& regs) noexcept;
- };
+ Maxwell::CullFace CullFace() const noexcept {
+ return UnpackCullFace(cull_face.Value());
+ }
- struct ViewportSwizzles {
- std::array<u16, Maxwell::NumViewports> swizzles;
+ Maxwell::FrontFace FrontFace() const noexcept {
+ return UnpackFrontFace(front_face.Value());
+ }
- void Fill(const Maxwell& regs) noexcept;
+ constexpr Maxwell::PrimitiveTopology Topology() const noexcept {
+ return static_cast<Maxwell::PrimitiveTopology>(topology.Value());
+ }
};
- VertexInput vertex_input;
- Rasterizer rasterizer;
- DepthStencil depth_stencil;
- ColorBlending color_blending;
- ViewportSwizzles viewport_swizzles;
+ union {
+ u32 raw;
+ BitField<0, 1, u32> no_extended_dynamic_state;
+ BitField<2, 1, u32> primitive_restart_enable;
+ BitField<3, 1, u32> depth_bias_enable;
+ BitField<4, 1, u32> depth_clamp_disabled;
+ BitField<5, 1, u32> ndc_minus_one_to_one;
+ BitField<6, 2, u32> polygon_mode;
+ BitField<8, 5, u32> patch_control_points_minus_one;
+ BitField<13, 2, u32> tessellation_primitive;
+ BitField<15, 2, u32> tessellation_spacing;
+ BitField<17, 1, u32> tessellation_clockwise;
+ BitField<18, 1, u32> logic_op_enable;
+ BitField<19, 4, u32> logic_op;
+ BitField<23, 1, u32> rasterize_enable;
+ };
+ u32 point_size;
+ std::array<u32, Maxwell::NumVertexArrays> binding_divisors;
+ std::array<VertexAttribute, Maxwell::NumVertexAttributes> attributes;
+ std::array<BlendingAttachment, Maxwell::NumRenderTargets> attachments;
+ std::array<u16, Maxwell::NumViewports> viewport_swizzles;
+ DynamicState dynamic_state;
- void Fill(const Maxwell& regs);
+ void Fill(const Maxwell& regs, bool has_extended_dynamic_state);
std::size_t Hash() const noexcept;
@@ -255,6 +207,11 @@ struct FixedPipelineState {
bool operator!=(const FixedPipelineState& rhs) const noexcept {
return !operator==(rhs);
}
+
+ std::size_t Size() const noexcept {
+ const std::size_t total_size = sizeof *this;
+ return total_size - (no_extended_dynamic_state != 0 ? 0 : sizeof(DynamicState));
+ }
};
static_assert(std::has_unique_object_representations_v<FixedPipelineState>);
static_assert(std::is_trivially_copyable_v<FixedPipelineState>);
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index f10f96cd8..2be38d419 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -56,7 +56,7 @@ Buffer::Buffer(const VKDevice& device, VKMemoryManager& memory_manager, VKSchedu
Buffer::~Buffer() = default;
-void Buffer::Upload(std::size_t offset, std::size_t size, const u8* data) const {
+void Buffer::Upload(std::size_t offset, std::size_t size, const u8* data) {
const auto& staging = staging_pool.GetUnusedBuffer(size, true);
std::memcpy(staging.commit->Map(size), data, size);
@@ -81,7 +81,7 @@ void Buffer::Upload(std::size_t offset, std::size_t size, const u8* data) const
});
}
-void Buffer::Download(std::size_t offset, std::size_t size, u8* data) const {
+void Buffer::Download(std::size_t offset, std::size_t size, u8* data) {
const auto& staging = staging_pool.GetUnusedBuffer(size, true);
scheduler.RequestOutsideRenderPassOperationContext();
@@ -110,7 +110,7 @@ void Buffer::Download(std::size_t offset, std::size_t size, u8* data) const {
}
void Buffer::CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset,
- std::size_t size) const {
+ std::size_t size) {
scheduler.RequestOutsideRenderPassOperationContext();
const VkBuffer dst_buffer = Handle();
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h
index 3630aca77..991ee451c 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.h
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h
@@ -29,12 +29,12 @@ public:
VKStagingBufferPool& staging_pool, VAddr cpu_addr, std::size_t size);
~Buffer();
- void Upload(std::size_t offset, std::size_t size, const u8* data) const;
+ void Upload(std::size_t offset, std::size_t size, const u8* data);
- void Download(std::size_t offset, std::size_t size, u8* data) const;
+ void Download(std::size_t offset, std::size_t size, u8* data);
void CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset,
- std::size_t size) const;
+ std::size_t size);
VkBuffer Handle() const {
return *buffer.handle;
diff --git a/src/video_core/renderer_vulkan/vk_device.cpp b/src/video_core/renderer_vulkan/vk_device.cpp
index 9fd8ac3f6..fdaea4210 100644
--- a/src/video_core/renderer_vulkan/vk_device.cpp
+++ b/src/video_core/renderer_vulkan/vk_device.cpp
@@ -313,6 +313,16 @@ bool VKDevice::Create() {
LOG_INFO(Render_Vulkan, "Device doesn't support custom border colors");
}
+ VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynamic_state;
+ if (ext_extended_dynamic_state) {
+ dynamic_state.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT;
+ dynamic_state.pNext = nullptr;
+ dynamic_state.extendedDynamicState = VK_TRUE;
+ SetNext(next, dynamic_state);
+ } else {
+ LOG_INFO(Render_Vulkan, "Device doesn't support extended dynamic state");
+ }
+
if (!ext_depth_range_unrestricted) {
LOG_INFO(Render_Vulkan, "Device doesn't support depth range unrestricted");
}
@@ -541,6 +551,7 @@ std::vector<const char*> VKDevice::LoadExtensions() {
bool has_ext_subgroup_size_control{};
bool has_ext_transform_feedback{};
bool has_ext_custom_border_color{};
+ bool has_ext_extended_dynamic_state{};
for (const auto& extension : physical.EnumerateDeviceExtensionProperties()) {
Test(extension, nv_viewport_swizzle, VK_NV_VIEWPORT_SWIZZLE_EXTENSION_NAME, true);
Test(extension, khr_uniform_buffer_standard_layout,
@@ -558,6 +569,8 @@ std::vector<const char*> VKDevice::LoadExtensions() {
false);
Test(extension, has_ext_custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME,
false);
+ Test(extension, has_ext_extended_dynamic_state,
+ VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME, false);
if (Settings::values.renderer_debug) {
Test(extension, nv_device_diagnostics_config,
VK_NV_DEVICE_DIAGNOSTICS_CONFIG_EXTENSION_NAME, true);
@@ -643,6 +656,19 @@ std::vector<const char*> VKDevice::LoadExtensions() {
}
}
+ if (has_ext_extended_dynamic_state) {
+ VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynamic_state;
+ dynamic_state.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT;
+ dynamic_state.pNext = nullptr;
+ features.pNext = &dynamic_state;
+ physical.GetFeatures2KHR(features);
+
+ if (dynamic_state.extendedDynamicState) {
+ extensions.push_back(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
+ ext_extended_dynamic_state = true;
+ }
+ }
+
return extensions;
}
diff --git a/src/video_core/renderer_vulkan/vk_device.h b/src/video_core/renderer_vulkan/vk_device.h
index 6b9227b09..ae5c21baa 100644
--- a/src/video_core/renderer_vulkan/vk_device.h
+++ b/src/video_core/renderer_vulkan/vk_device.h
@@ -182,6 +182,11 @@ public:
return ext_custom_border_color;
}
+ /// Returns true if the device supports VK_EXT_extended_dynamic_state.
+ bool IsExtExtendedDynamicStateSupported() const {
+ return ext_extended_dynamic_state;
+ }
+
/// Returns the vendor name reported from Vulkan.
std::string_view GetVendorName() const {
return vendor_name;
@@ -239,6 +244,7 @@ private:
bool ext_shader_viewport_index_layer{}; ///< Support for VK_EXT_shader_viewport_index_layer.
bool ext_transform_feedback{}; ///< Support for VK_EXT_transform_feedback.
bool ext_custom_border_color{}; ///< Support for VK_EXT_custom_border_color.
+ bool ext_extended_dynamic_state{}; ///< Support for VK_EXT_extended_dynamic_state.
bool nv_device_diagnostics_config{}; ///< Support for VK_NV_device_diagnostics_config.
// Telemetry parameters
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index 69b6bba00..844445105 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -176,20 +176,32 @@ std::vector<vk::ShaderModule> VKGraphicsPipeline::CreateShaderModules(
vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpass_params,
const SPIRVProgram& program) const {
- const auto& vi = fixed_state.vertex_input;
- const auto& ds = fixed_state.depth_stencil;
- const auto& cd = fixed_state.color_blending;
- const auto& rs = fixed_state.rasterizer;
- const auto& viewport_swizzles = fixed_state.viewport_swizzles.swizzles;
+ const auto& state = fixed_state;
+ const auto& viewport_swizzles = state.viewport_swizzles;
+
+ FixedPipelineState::DynamicState dynamic;
+ if (device.IsExtExtendedDynamicStateSupported()) {
+ // Insert dummy values, as long as they are valid they don't matter as extended dynamic
+ // state is ignored
+ dynamic.raw1 = 0;
+ dynamic.raw2 = 0;
+ for (FixedPipelineState::VertexBinding& binding : dynamic.vertex_bindings) {
+ // Enable all vertex bindings
+ binding.raw = 0;
+ binding.enabled.Assign(1);
+ }
+ } else {
+ dynamic = state.dynamic_state;
+ }
std::vector<VkVertexInputBindingDescription> vertex_bindings;
std::vector<VkVertexInputBindingDivisorDescriptionEXT> vertex_binding_divisors;
- for (std::size_t index = 0; index < std::size(vi.bindings); ++index) {
- const auto& binding = vi.bindings[index];
+ for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
+ const auto& binding = dynamic.vertex_bindings[index];
if (!binding.enabled) {
continue;
}
- const bool instanced = vi.binding_divisors[index] != 0;
+ const bool instanced = state.binding_divisors[index] != 0;
const auto rate = instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX;
auto& vertex_binding = vertex_bindings.emplace_back();
@@ -200,14 +212,14 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
if (instanced) {
auto& binding_divisor = vertex_binding_divisors.emplace_back();
binding_divisor.binding = static_cast<u32>(index);
- binding_divisor.divisor = vi.binding_divisors[index];
+ binding_divisor.divisor = state.binding_divisors[index];
}
}
std::vector<VkVertexInputAttributeDescription> vertex_attributes;
const auto& input_attributes = program[0]->entries.attributes;
- for (std::size_t index = 0; index < std::size(vi.attributes); ++index) {
- const auto& attribute = vi.attributes[index];
+ for (std::size_t index = 0; index < state.attributes.size(); ++index) {
+ const auto& attribute = state.attributes[index];
if (!attribute.enabled) {
continue;
}
@@ -244,15 +256,15 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
input_assembly_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
input_assembly_ci.pNext = nullptr;
input_assembly_ci.flags = 0;
- input_assembly_ci.topology = MaxwellToVK::PrimitiveTopology(device, rs.Topology());
+ input_assembly_ci.topology = MaxwellToVK::PrimitiveTopology(device, dynamic.Topology());
input_assembly_ci.primitiveRestartEnable =
- rs.primitive_restart_enable != 0 && SupportsPrimitiveRestart(input_assembly_ci.topology);
+ state.primitive_restart_enable != 0 && SupportsPrimitiveRestart(input_assembly_ci.topology);
VkPipelineTessellationStateCreateInfo tessellation_ci;
tessellation_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
tessellation_ci.pNext = nullptr;
tessellation_ci.flags = 0;
- tessellation_ci.patchControlPoints = rs.patch_control_points_minus_one.Value() + 1;
+ tessellation_ci.patchControlPoints = state.patch_control_points_minus_one.Value() + 1;
VkPipelineViewportStateCreateInfo viewport_ci;
viewport_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
@@ -280,13 +292,13 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
rasterization_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterization_ci.pNext = nullptr;
rasterization_ci.flags = 0;
- rasterization_ci.depthClampEnable = rs.depth_clamp_disabled == 0 ? VK_TRUE : VK_FALSE;
- rasterization_ci.rasterizerDiscardEnable = rs.rasterize_enable == 0 ? VK_TRUE : VK_FALSE;
+ rasterization_ci.depthClampEnable = state.depth_clamp_disabled == 0 ? VK_TRUE : VK_FALSE;
+ rasterization_ci.rasterizerDiscardEnable = state.rasterize_enable == 0 ? VK_TRUE : VK_FALSE;
rasterization_ci.polygonMode = VK_POLYGON_MODE_FILL;
rasterization_ci.cullMode =
- rs.cull_enable ? MaxwellToVK::CullFace(rs.CullFace()) : VK_CULL_MODE_NONE;
- rasterization_ci.frontFace = MaxwellToVK::FrontFace(rs.FrontFace());
- rasterization_ci.depthBiasEnable = rs.depth_bias_enable;
+ dynamic.cull_enable ? MaxwellToVK::CullFace(dynamic.CullFace()) : VK_CULL_MODE_NONE;
+ rasterization_ci.frontFace = MaxwellToVK::FrontFace(dynamic.FrontFace());
+ rasterization_ci.depthBiasEnable = state.depth_bias_enable;
rasterization_ci.depthBiasConstantFactor = 0.0f;
rasterization_ci.depthBiasClamp = 0.0f;
rasterization_ci.depthBiasSlopeFactor = 0.0f;
@@ -307,14 +319,15 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
depth_stencil_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depth_stencil_ci.pNext = nullptr;
depth_stencil_ci.flags = 0;
- depth_stencil_ci.depthTestEnable = ds.depth_test_enable;
- depth_stencil_ci.depthWriteEnable = ds.depth_write_enable;
- depth_stencil_ci.depthCompareOp =
- ds.depth_test_enable ? MaxwellToVK::ComparisonOp(ds.DepthTestFunc()) : VK_COMPARE_OP_ALWAYS;
- depth_stencil_ci.depthBoundsTestEnable = ds.depth_bounds_enable;
- depth_stencil_ci.stencilTestEnable = ds.stencil_enable;
- depth_stencil_ci.front = GetStencilFaceState(ds.front);
- depth_stencil_ci.back = GetStencilFaceState(ds.back);
+ depth_stencil_ci.depthTestEnable = dynamic.depth_test_enable;
+ depth_stencil_ci.depthWriteEnable = dynamic.depth_write_enable;
+ depth_stencil_ci.depthCompareOp = dynamic.depth_test_enable
+ ? MaxwellToVK::ComparisonOp(dynamic.DepthTestFunc())
+ : VK_COMPARE_OP_ALWAYS;
+ depth_stencil_ci.depthBoundsTestEnable = dynamic.depth_bounds_enable;
+ depth_stencil_ci.stencilTestEnable = dynamic.stencil_enable;
+ depth_stencil_ci.front = GetStencilFaceState(dynamic.front);
+ depth_stencil_ci.back = GetStencilFaceState(dynamic.back);
depth_stencil_ci.minDepthBounds = 0.0f;
depth_stencil_ci.maxDepthBounds = 0.0f;
@@ -324,7 +337,7 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
static constexpr std::array COMPONENT_TABLE = {
VK_COLOR_COMPONENT_R_BIT, VK_COLOR_COMPONENT_G_BIT, VK_COLOR_COMPONENT_B_BIT,
VK_COLOR_COMPONENT_A_BIT};
- const auto& blend = cd.attachments[index];
+ const auto& blend = state.attachments[index];
VkColorComponentFlags color_components = 0;
for (std::size_t i = 0; i < COMPONENT_TABLE.size(); ++i) {
@@ -354,11 +367,27 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
color_blend_ci.pAttachments = cb_attachments.data();
std::memset(color_blend_ci.blendConstants, 0, sizeof(color_blend_ci.blendConstants));
- static constexpr std::array dynamic_states = {
+ std::vector dynamic_states = {
VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR,
VK_DYNAMIC_STATE_DEPTH_BIAS, VK_DYNAMIC_STATE_BLEND_CONSTANTS,
VK_DYNAMIC_STATE_DEPTH_BOUNDS, VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK,
- VK_DYNAMIC_STATE_STENCIL_WRITE_MASK, VK_DYNAMIC_STATE_STENCIL_REFERENCE};
+ VK_DYNAMIC_STATE_STENCIL_WRITE_MASK, VK_DYNAMIC_STATE_STENCIL_REFERENCE,
+ };
+ if (device.IsExtExtendedDynamicStateSupported()) {
+ static constexpr std::array extended = {
+ VK_DYNAMIC_STATE_CULL_MODE_EXT,
+ VK_DYNAMIC_STATE_FRONT_FACE_EXT,
+ VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT,
+ VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT,
+ VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE_EXT,
+ VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE_EXT,
+ VK_DYNAMIC_STATE_DEPTH_COMPARE_OP_EXT,
+ VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE_EXT,
+ VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE_EXT,
+ VK_DYNAMIC_STATE_STENCIL_OP_EXT,
+ };
+ dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.end());
+ }
VkPipelineDynamicStateCreateInfo dynamic_state_ci;
dynamic_state_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index ea66e621e..3da835324 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -116,12 +116,12 @@ u32 FillDescriptorLayout(const ShaderEntries& entries,
} // Anonymous namespace
std::size_t GraphicsPipelineCacheKey::Hash() const noexcept {
- const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), sizeof *this);
+ const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), Size());
return static_cast<std::size_t>(hash);
}
bool GraphicsPipelineCacheKey::operator==(const GraphicsPipelineCacheKey& rhs) const noexcept {
- return std::memcmp(&rhs, this, sizeof *this) == 0;
+ return std::memcmp(&rhs, this, Size()) == 0;
}
std::size_t ComputePipelineCacheKey::Hash() const noexcept {
@@ -312,18 +312,19 @@ VKPipelineCache::DecompileShaders(const GraphicsPipelineCacheKey& key) {
const auto& gpu = system.GPU().Maxwell3D();
Specialization specialization;
- if (fixed_state.rasterizer.Topology() == Maxwell::PrimitiveTopology::Points) {
+ if (fixed_state.dynamic_state.Topology() == Maxwell::PrimitiveTopology::Points ||
+ device.IsExtExtendedDynamicStateSupported()) {
float point_size;
- std::memcpy(&point_size, &fixed_state.rasterizer.point_size, sizeof(float));
+ std::memcpy(&point_size, &fixed_state.point_size, sizeof(float));
specialization.point_size = point_size;
ASSERT(point_size != 0.0f);
}
for (std::size_t i = 0; i < Maxwell::NumVertexAttributes; ++i) {
- const auto& attribute = fixed_state.vertex_input.attributes[i];
+ const auto& attribute = fixed_state.attributes[i];
specialization.enabled_attributes[i] = attribute.enabled.Value() != 0;
specialization.attribute_types[i] = attribute.Type();
}
- specialization.ndc_minus_one_to_one = fixed_state.rasterizer.ndc_minus_one_to_one;
+ specialization.ndc_minus_one_to_one = fixed_state.ndc_minus_one_to_one;
SPIRVProgram program;
std::vector<VkDescriptorSetLayoutBinding> bindings;
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h
index 0a36e5112..0a3fe65fb 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h
@@ -44,10 +44,10 @@ class VKUpdateDescriptorQueue;
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
struct GraphicsPipelineCacheKey {
- FixedPipelineState fixed_state;
RenderPassParams renderpass_params;
+ u32 padding;
std::array<GPUVAddr, Maxwell::MaxShaderProgram> shaders;
- u64 padding; // This is necessary for unique object representations
+ FixedPipelineState fixed_state;
std::size_t Hash() const noexcept;
@@ -56,6 +56,10 @@ struct GraphicsPipelineCacheKey {
bool operator!=(const GraphicsPipelineCacheKey& rhs) const noexcept {
return !operator==(rhs);
}
+
+ std::size_t Size() const noexcept {
+ return sizeof(renderpass_params) + sizeof(padding) + sizeof(shaders) + fixed_state.Size();
+ }
};
static_assert(std::has_unique_object_representations_v<GraphicsPipelineCacheKey>);
static_assert(std::is_trivially_copyable_v<GraphicsPipelineCacheKey>);
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index a8d94eac3..380ed532b 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -186,13 +186,22 @@ bool HasToPreserveDepthContents(bool is_clear, const Maxwell& regs) {
scissor.max_y < regs.zeta_height;
}
+template <std::size_t N>
+std::array<VkDeviceSize, N> ExpandStrides(const std::array<u16, N>& strides) {
+ std::array<VkDeviceSize, N> expanded;
+ std::copy(strides.begin(), strides.end(), expanded.begin());
+ return expanded;
+}
+
} // Anonymous namespace
class BufferBindings final {
public:
- void AddVertexBinding(VkBuffer buffer, VkDeviceSize offset) {
+ void AddVertexBinding(VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size, u32 stride) {
vertex.buffers[vertex.num_buffers] = buffer;
vertex.offsets[vertex.num_buffers] = offset;
+ vertex.sizes[vertex.num_buffers] = size;
+ vertex.strides[vertex.num_buffers] = static_cast<u16>(stride);
++vertex.num_buffers;
}
@@ -202,76 +211,76 @@ public:
index.type = type;
}
- void Bind(VKScheduler& scheduler) const {
+ void Bind(const VKDevice& device, VKScheduler& scheduler) const {
// Use this large switch case to avoid dispatching more memory in the record lambda than
// what we need. It looks horrible, but it's the best we can do on standard C++.
switch (vertex.num_buffers) {
case 0:
- return BindStatic<0>(scheduler);
+ return BindStatic<0>(device, scheduler);
case 1:
- return BindStatic<1>(scheduler);
+ return BindStatic<1>(device, scheduler);
case 2:
- return BindStatic<2>(scheduler);
+ return BindStatic<2>(device, scheduler);
case 3:
- return BindStatic<3>(scheduler);
+ return BindStatic<3>(device, scheduler);
case 4:
- return BindStatic<4>(scheduler);
+ return BindStatic<4>(device, scheduler);
case 5:
- return BindStatic<5>(scheduler);
+ return BindStatic<5>(device, scheduler);
case 6:
- return BindStatic<6>(scheduler);
+ return BindStatic<6>(device, scheduler);
case 7:
- return BindStatic<7>(scheduler);
+ return BindStatic<7>(device, scheduler);
case 8:
- return BindStatic<8>(scheduler);
+ return BindStatic<8>(device, scheduler);
case 9:
- return BindStatic<9>(scheduler);
+ return BindStatic<9>(device, scheduler);
case 10:
- return BindStatic<10>(scheduler);
+ return BindStatic<10>(device, scheduler);
case 11:
- return BindStatic<11>(scheduler);
+ return BindStatic<11>(device, scheduler);
case 12:
- return BindStatic<12>(scheduler);
+ return BindStatic<12>(device, scheduler);
case 13:
- return BindStatic<13>(scheduler);
+ return BindStatic<13>(device, scheduler);
case 14:
- return BindStatic<14>(scheduler);
+ return BindStatic<14>(device, scheduler);
case 15:
- return BindStatic<15>(scheduler);
+ return BindStatic<15>(device, scheduler);
case 16:
- return BindStatic<16>(scheduler);
+ return BindStatic<16>(device, scheduler);
case 17:
- return BindStatic<17>(scheduler);
+ return BindStatic<17>(device, scheduler);
case 18:
- return BindStatic<18>(scheduler);
+ return BindStatic<18>(device, scheduler);
case 19:
- return BindStatic<19>(scheduler);
+ return BindStatic<19>(device, scheduler);
case 20:
- return BindStatic<20>(scheduler);
+ return BindStatic<20>(device, scheduler);
case 21:
- return BindStatic<21>(scheduler);
+ return BindStatic<21>(device, scheduler);
case 22:
- return BindStatic<22>(scheduler);
+ return BindStatic<22>(device, scheduler);
case 23:
- return BindStatic<23>(scheduler);
+ return BindStatic<23>(device, scheduler);
case 24:
- return BindStatic<24>(scheduler);
+ return BindStatic<24>(device, scheduler);
case 25:
- return BindStatic<25>(scheduler);
+ return BindStatic<25>(device, scheduler);
case 26:
- return BindStatic<26>(scheduler);
+ return BindStatic<26>(device, scheduler);
case 27:
- return BindStatic<27>(scheduler);
+ return BindStatic<27>(device, scheduler);
case 28:
- return BindStatic<28>(scheduler);
+ return BindStatic<28>(device, scheduler);
case 29:
- return BindStatic<29>(scheduler);
+ return BindStatic<29>(device, scheduler);
case 30:
- return BindStatic<30>(scheduler);
+ return BindStatic<30>(device, scheduler);
case 31:
- return BindStatic<31>(scheduler);
+ return BindStatic<31>(device, scheduler);
case 32:
- return BindStatic<32>(scheduler);
+ return BindStatic<32>(device, scheduler);
}
UNREACHABLE();
}
@@ -282,6 +291,8 @@ private:
std::size_t num_buffers = 0;
std::array<VkBuffer, Maxwell::NumVertexArrays> buffers;
std::array<VkDeviceSize, Maxwell::NumVertexArrays> offsets;
+ std::array<VkDeviceSize, Maxwell::NumVertexArrays> sizes;
+ std::array<u16, Maxwell::NumVertexArrays> strides;
} vertex;
struct {
@@ -291,15 +302,23 @@ private:
} index;
template <std::size_t N>
- void BindStatic(VKScheduler& scheduler) const {
- if (index.buffer) {
- BindStatic<N, true>(scheduler);
+ void BindStatic(const VKDevice& device, VKScheduler& scheduler) const {
+ if (device.IsExtExtendedDynamicStateSupported()) {
+ if (index.buffer) {
+ BindStatic<N, true, true>(scheduler);
+ } else {
+ BindStatic<N, false, true>(scheduler);
+ }
} else {
- BindStatic<N, false>(scheduler);
+ if (index.buffer) {
+ BindStatic<N, true, false>(scheduler);
+ } else {
+ BindStatic<N, false, false>(scheduler);
+ }
}
}
- template <std::size_t N, bool is_indexed>
+ template <std::size_t N, bool is_indexed, bool has_extended_dynamic_state>
void BindStatic(VKScheduler& scheduler) const {
static_assert(N <= Maxwell::NumVertexArrays);
if constexpr (N == 0) {
@@ -311,6 +330,31 @@ private:
std::copy(vertex.buffers.begin(), vertex.buffers.begin() + N, buffers.begin());
std::copy(vertex.offsets.begin(), vertex.offsets.begin() + N, offsets.begin());
+ if constexpr (has_extended_dynamic_state) {
+ // With extended dynamic states we can specify the length and stride of a vertex buffer
+ // std::array<VkDeviceSize, N> sizes;
+ std::array<u16, N> strides;
+ // std::copy(vertex.sizes.begin(), vertex.sizes.begin() + N, sizes.begin());
+ std::copy(vertex.strides.begin(), vertex.strides.begin() + N, strides.begin());
+
+ if constexpr (is_indexed) {
+ scheduler.Record(
+ [buffers, offsets, strides, index = index](vk::CommandBuffer cmdbuf) {
+ cmdbuf.BindIndexBuffer(index.buffer, index.offset, index.type);
+ cmdbuf.BindVertexBuffers2EXT(0, static_cast<u32>(N), buffers.data(),
+ offsets.data(), nullptr,
+ ExpandStrides(strides).data());
+ });
+ } else {
+ scheduler.Record([buffers, offsets, strides](vk::CommandBuffer cmdbuf) {
+ cmdbuf.BindVertexBuffers2EXT(0, static_cast<u32>(N), buffers.data(),
+ offsets.data(), nullptr,
+ ExpandStrides(strides).data());
+ });
+ }
+ return;
+ }
+
if constexpr (is_indexed) {
// Indexed draw
scheduler.Record([buffers, offsets, index = index](vk::CommandBuffer cmdbuf) {
@@ -369,7 +413,7 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
const auto& gpu = system.GPU().Maxwell3D();
GraphicsPipelineCacheKey key;
- key.fixed_state.Fill(gpu.regs);
+ key.fixed_state.Fill(gpu.regs, device.IsExtExtendedDynamicStateSupported());
buffer_cache.Map(CalculateGraphicsStreamBufferSize(is_indexed));
@@ -402,7 +446,7 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
UpdateDynamicStates();
- buffer_bindings.Bind(scheduler);
+ buffer_bindings.Bind(device, scheduler);
BeginTransformFeedback();
@@ -822,7 +866,7 @@ RasterizerVulkan::DrawParameters RasterizerVulkan::SetupGeometry(FixedPipelineSt
const auto& gpu = system.GPU().Maxwell3D();
const auto& regs = gpu.regs;
- SetupVertexArrays(fixed_state.vertex_input, buffer_bindings);
+ SetupVertexArrays(buffer_bindings);
const u32 base_instance = regs.vb_base_instance;
const u32 num_instances = is_instanced ? gpu.mme_draw.instance_count : 1;
@@ -893,6 +937,17 @@ void RasterizerVulkan::UpdateDynamicStates() {
UpdateBlendConstants(regs);
UpdateDepthBounds(regs);
UpdateStencilFaces(regs);
+ if (device.IsExtExtendedDynamicStateSupported()) {
+ UpdateCullMode(regs);
+ UpdateDepthBoundsTestEnable(regs);
+ UpdateDepthTestEnable(regs);
+ UpdateDepthWriteEnable(regs);
+ UpdateDepthCompareOp(regs);
+ UpdateFrontFace(regs);
+ UpdatePrimitiveTopology(regs);
+ UpdateStencilOp(regs);
+ UpdateStencilTestEnable(regs);
+ }
}
void RasterizerVulkan::BeginTransformFeedback() {
@@ -940,41 +995,25 @@ void RasterizerVulkan::EndTransformFeedback() {
[](vk::CommandBuffer cmdbuf) { cmdbuf.EndTransformFeedbackEXT(0, 0, nullptr, nullptr); });
}
-void RasterizerVulkan::SetupVertexArrays(FixedPipelineState::VertexInput& vertex_input,
- BufferBindings& buffer_bindings) {
+void RasterizerVulkan::SetupVertexArrays(BufferBindings& buffer_bindings) {
const auto& regs = system.GPU().Maxwell3D().regs;
- for (std::size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {
- const auto& attrib = regs.vertex_attrib_format[index];
- if (attrib.IsConstant()) {
- vertex_input.SetAttribute(index, false, 0, 0, {}, {});
- continue;
- }
- vertex_input.SetAttribute(index, true, attrib.buffer, attrib.offset, attrib.type.Value(),
- attrib.size.Value());
- }
-
for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
const auto& vertex_array = regs.vertex_array[index];
if (!vertex_array.IsEnabled()) {
- vertex_input.SetBinding(index, false, 0, 0);
continue;
}
- vertex_input.SetBinding(
- index, true, vertex_array.stride,
- regs.instanced_arrays.IsInstancingEnabled(index) ? vertex_array.divisor : 0);
-
const GPUVAddr start{vertex_array.StartAddress()};
const GPUVAddr end{regs.vertex_array_limit[index].LimitAddress()};
ASSERT(end >= start);
- const std::size_t size{end - start};
+ const std::size_t size = end - start;
if (size == 0) {
- buffer_bindings.AddVertexBinding(DefaultBuffer(), 0);
+ buffer_bindings.AddVertexBinding(DefaultBuffer(), 0, DEFAULT_BUFFER_SIZE, 0);
continue;
}
const auto info = buffer_cache.UploadMemory(start, size);
- buffer_bindings.AddVertexBinding(info.handle, info.offset);
+ buffer_bindings.AddVertexBinding(info.handle, info.offset, size, vertex_array.stride);
}
}
@@ -1326,6 +1365,117 @@ void RasterizerVulkan::UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs)
}
}
+void RasterizerVulkan::UpdateCullMode(Tegra::Engines::Maxwell3D::Regs& regs) {
+ if (!state_tracker.TouchCullMode()) {
+ return;
+ }
+ scheduler.Record(
+ [enabled = regs.cull_test_enabled, cull_face = regs.cull_face](vk::CommandBuffer cmdbuf) {
+ cmdbuf.SetCullModeEXT(enabled ? MaxwellToVK::CullFace(cull_face) : VK_CULL_MODE_NONE);
+ });
+}
+
+void RasterizerVulkan::UpdateDepthBoundsTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
+ if (!state_tracker.TouchDepthBoundsTestEnable()) {
+ return;
+ }
+ scheduler.Record([enable = regs.depth_bounds_enable](vk::CommandBuffer cmdbuf) {
+ cmdbuf.SetDepthBoundsTestEnableEXT(enable);
+ });
+}
+
+void RasterizerVulkan::UpdateDepthTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
+ if (!state_tracker.TouchDepthTestEnable()) {
+ return;
+ }
+ scheduler.Record([enable = regs.depth_test_enable](vk::CommandBuffer cmdbuf) {
+ cmdbuf.SetDepthTestEnableEXT(enable);
+ });
+}
+
+void RasterizerVulkan::UpdateDepthWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
+ if (!state_tracker.TouchDepthWriteEnable()) {
+ return;
+ }
+ scheduler.Record([enable = regs.depth_write_enabled](vk::CommandBuffer cmdbuf) {
+ cmdbuf.SetDepthWriteEnableEXT(enable);
+ });
+}
+
+void RasterizerVulkan::UpdateDepthCompareOp(Tegra::Engines::Maxwell3D::Regs& regs) {
+ if (!state_tracker.TouchDepthCompareOp()) {
+ return;
+ }
+ scheduler.Record([func = regs.depth_test_func](vk::CommandBuffer cmdbuf) {
+ cmdbuf.SetDepthCompareOpEXT(MaxwellToVK::ComparisonOp(func));
+ });
+}
+
+void RasterizerVulkan::UpdateFrontFace(Tegra::Engines::Maxwell3D::Regs& regs) {
+ if (!state_tracker.TouchFrontFace()) {
+ return;
+ }
+
+ VkFrontFace front_face = MaxwellToVK::FrontFace(regs.front_face);
+ if (regs.screen_y_control.triangle_rast_flip != 0) {
+ front_face = front_face == VK_FRONT_FACE_CLOCKWISE ? VK_FRONT_FACE_COUNTER_CLOCKWISE
+ : VK_FRONT_FACE_CLOCKWISE;
+ }
+ scheduler.Record(
+ [front_face](vk::CommandBuffer cmdbuf) { cmdbuf.SetFrontFaceEXT(front_face); });
+}
+
+void RasterizerVulkan::UpdatePrimitiveTopology(Tegra::Engines::Maxwell3D::Regs& regs) {
+ if (!state_tracker.TouchPrimitiveTopology()) {
+ return;
+ }
+ const Maxwell::PrimitiveTopology primitive_topology = regs.draw.topology.Value();
+ scheduler.Record([this, primitive_topology](vk::CommandBuffer cmdbuf) {
+ cmdbuf.SetPrimitiveTopologyEXT(MaxwellToVK::PrimitiveTopology(device, primitive_topology));
+ });
+}
+
+void RasterizerVulkan::UpdateStencilOp(Tegra::Engines::Maxwell3D::Regs& regs) {
+ if (!state_tracker.TouchStencilOp()) {
+ return;
+ }
+ const Maxwell::StencilOp fail = regs.stencil_front_op_fail;
+ const Maxwell::StencilOp zfail = regs.stencil_front_op_zfail;
+ const Maxwell::StencilOp zpass = regs.stencil_front_op_zpass;
+ const Maxwell::ComparisonOp compare = regs.stencil_front_func_func;
+ if (regs.stencil_two_side_enable) {
+ scheduler.Record([fail, zfail, zpass, compare](vk::CommandBuffer cmdbuf) {
+ cmdbuf.SetStencilOpEXT(VK_STENCIL_FACE_FRONT_AND_BACK, MaxwellToVK::StencilOp(fail),
+ MaxwellToVK::StencilOp(zpass), MaxwellToVK::StencilOp(zfail),
+ MaxwellToVK::ComparisonOp(compare));
+ });
+ } else {
+ const Maxwell::StencilOp back_fail = regs.stencil_back_op_fail;
+ const Maxwell::StencilOp back_zfail = regs.stencil_back_op_zfail;
+ const Maxwell::StencilOp back_zpass = regs.stencil_back_op_zpass;
+ const Maxwell::ComparisonOp back_compare = regs.stencil_back_func_func;
+ scheduler.Record([fail, zfail, zpass, compare, back_fail, back_zfail, back_zpass,
+ back_compare](vk::CommandBuffer cmdbuf) {
+ cmdbuf.SetStencilOpEXT(VK_STENCIL_FACE_FRONT_BIT, MaxwellToVK::StencilOp(fail),
+ MaxwellToVK::StencilOp(zpass), MaxwellToVK::StencilOp(zfail),
+ MaxwellToVK::ComparisonOp(compare));
+ cmdbuf.SetStencilOpEXT(VK_STENCIL_FACE_BACK_BIT, MaxwellToVK::StencilOp(back_fail),
+ MaxwellToVK::StencilOp(back_zpass),
+ MaxwellToVK::StencilOp(back_zfail),
+ MaxwellToVK::ComparisonOp(back_compare));
+ });
+ }
+}
+
+void RasterizerVulkan::UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
+ if (!state_tracker.TouchStencilTestEnable()) {
+ return;
+ }
+ scheduler.Record([enable = regs.stencil_enable](vk::CommandBuffer cmdbuf) {
+ cmdbuf.SetStencilTestEnableEXT(enable);
+ });
+}
+
std::size_t RasterizerVulkan::CalculateGraphicsStreamBufferSize(bool is_indexed) const {
std::size_t size = CalculateVertexArraysSize();
if (is_indexed) {
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index 83e00e7e9..923178b0b 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -185,8 +185,7 @@ private:
bool WalkAttachmentOverlaps(const CachedSurfaceView& attachment);
- void SetupVertexArrays(FixedPipelineState::VertexInput& vertex_input,
- BufferBindings& buffer_bindings);
+ void SetupVertexArrays(BufferBindings& buffer_bindings);
void SetupIndexBuffer(BufferBindings& buffer_bindings, DrawParameters& params, bool is_indexed);
@@ -246,6 +245,16 @@ private:
void UpdateDepthBounds(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs);
+ void UpdateCullMode(Tegra::Engines::Maxwell3D::Regs& regs);
+ void UpdateDepthBoundsTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
+ void UpdateDepthTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
+ void UpdateDepthWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs);
+ void UpdateDepthCompareOp(Tegra::Engines::Maxwell3D::Regs& regs);
+ void UpdateFrontFace(Tegra::Engines::Maxwell3D::Regs& regs);
+ void UpdatePrimitiveTopology(Tegra::Engines::Maxwell3D::Regs& regs);
+ void UpdateStencilOp(Tegra::Engines::Maxwell3D::Regs& regs);
+ void UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
+
std::size_t CalculateGraphicsStreamBufferSize(bool is_indexed) const;
std::size_t CalculateComputeStreamBufferSize() const;
diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.cpp b/src/video_core/renderer_vulkan/vk_state_tracker.cpp
index 94a89e388..e5a583dd5 100644
--- a/src/video_core/renderer_vulkan/vk_state_tracker.cpp
+++ b/src/video_core/renderer_vulkan/vk_state_tracker.cpp
@@ -36,6 +36,15 @@ Flags MakeInvalidationFlags() {
flags[BlendConstants] = true;
flags[DepthBounds] = true;
flags[StencilProperties] = true;
+ flags[CullMode] = true;
+ flags[DepthBoundsEnable] = true;
+ flags[DepthTestEnable] = true;
+ flags[DepthWriteEnable] = true;
+ flags[DepthCompareOp] = true;
+ flags[FrontFace] = true;
+ flags[PrimitiveTopology] = true;
+ flags[StencilOp] = true;
+ flags[StencilTestEnable] = true;
return flags;
}
@@ -75,6 +84,57 @@ void SetupDirtyStencilProperties(Tables& tables) {
table[OFF(stencil_back_func_mask)] = StencilProperties;
}
+void SetupDirtyCullMode(Tables& tables) {
+ auto& table = tables[0];
+ table[OFF(cull_face)] = CullMode;
+ table[OFF(cull_test_enabled)] = CullMode;
+}
+
+void SetupDirtyDepthBoundsEnable(Tables& tables) {
+ tables[0][OFF(depth_bounds_enable)] = DepthBoundsEnable;
+}
+
+void SetupDirtyDepthTestEnable(Tables& tables) {
+ tables[0][OFF(depth_test_enable)] = DepthTestEnable;
+}
+
+void SetupDirtyDepthWriteEnable(Tables& tables) {
+ tables[0][OFF(depth_write_enabled)] = DepthWriteEnable;
+}
+
+void SetupDirtyDepthCompareOp(Tables& tables) {
+ tables[0][OFF(depth_test_func)] = DepthCompareOp;
+}
+
+void SetupDirtyFrontFace(Tables& tables) {
+ auto& table = tables[0];
+ table[OFF(front_face)] = FrontFace;
+ table[OFF(screen_y_control)] = FrontFace;
+}
+
+void SetupDirtyPrimitiveTopology(Tables& tables) {
+ tables[0][OFF(draw.topology)] = PrimitiveTopology;
+}
+
+void SetupDirtyStencilOp(Tables& tables) {
+ auto& table = tables[0];
+ table[OFF(stencil_front_op_fail)] = StencilOp;
+ table[OFF(stencil_front_op_zfail)] = StencilOp;
+ table[OFF(stencil_front_op_zpass)] = StencilOp;
+ table[OFF(stencil_front_func_func)] = StencilOp;
+ table[OFF(stencil_back_op_fail)] = StencilOp;
+ table[OFF(stencil_back_op_zfail)] = StencilOp;
+ table[OFF(stencil_back_op_zpass)] = StencilOp;
+ table[OFF(stencil_back_func_func)] = StencilOp;
+
+ // Table 0 is used by StencilProperties
+ tables[1][OFF(stencil_two_side_enable)] = StencilOp;
+}
+
+void SetupDirtyStencilTestEnable(Tables& tables) {
+ tables[0][OFF(stencil_enable)] = StencilTestEnable;
+}
+
} // Anonymous namespace
StateTracker::StateTracker(Core::System& system)
@@ -90,6 +150,14 @@ void StateTracker::Initialize() {
SetupDirtyBlendConstants(tables);
SetupDirtyDepthBounds(tables);
SetupDirtyStencilProperties(tables);
+ SetupDirtyCullMode(tables);
+ SetupDirtyDepthBoundsEnable(tables);
+ SetupDirtyDepthTestEnable(tables);
+ SetupDirtyDepthWriteEnable(tables);
+ SetupDirtyDepthCompareOp(tables);
+ SetupDirtyFrontFace(tables);
+ SetupDirtyPrimitiveTopology(tables);
+ SetupDirtyStencilOp(tables);
}
void StateTracker::InvalidateCommandBufferState() {
diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.h b/src/video_core/renderer_vulkan/vk_state_tracker.h
index 03bc415b2..54ca0d6c6 100644
--- a/src/video_core/renderer_vulkan/vk_state_tracker.h
+++ b/src/video_core/renderer_vulkan/vk_state_tracker.h
@@ -26,6 +26,16 @@ enum : u8 {
DepthBounds,
StencilProperties,
+ CullMode,
+ DepthBoundsEnable,
+ DepthTestEnable,
+ DepthWriteEnable,
+ DepthCompareOp,
+ FrontFace,
+ PrimitiveTopology,
+ StencilOp,
+ StencilTestEnable,
+
Last
};
static_assert(Last <= std::numeric_limits<u8>::max());
@@ -64,6 +74,46 @@ public:
return Exchange(Dirty::StencilProperties, false);
}
+ bool TouchCullMode() {
+ return Exchange(Dirty::CullMode, false);
+ }
+
+ bool TouchDepthBoundsTestEnable() {
+ return Exchange(Dirty::DepthBoundsEnable, false);
+ }
+
+ bool TouchDepthTestEnable() {
+ return Exchange(Dirty::DepthTestEnable, false);
+ }
+
+ bool TouchDepthBoundsEnable() {
+ return Exchange(Dirty::DepthBoundsEnable, false);
+ }
+
+ bool TouchDepthWriteEnable() {
+ return Exchange(Dirty::DepthWriteEnable, false);
+ }
+
+ bool TouchDepthCompareOp() {
+ return Exchange(Dirty::DepthCompareOp, false);
+ }
+
+ bool TouchFrontFace() {
+ return Exchange(Dirty::FrontFace, false);
+ }
+
+ bool TouchPrimitiveTopology() {
+ return Exchange(Dirty::PrimitiveTopology, false);
+ }
+
+ bool TouchStencilOp() {
+ return Exchange(Dirty::StencilOp, false);
+ }
+
+ bool TouchStencilTestEnable() {
+ return Exchange(Dirty::StencilTestEnable, false);
+ }
+
private:
bool Exchange(std::size_t id, bool new_value) const noexcept {
auto& flags = system.GPU().Maxwell3D().dirty.flags;
diff --git a/src/video_core/renderer_vulkan/wrapper.cpp b/src/video_core/renderer_vulkan/wrapper.cpp
index 0d485a662..051298cc8 100644
--- a/src/video_core/renderer_vulkan/wrapper.cpp
+++ b/src/video_core/renderer_vulkan/wrapper.cpp
@@ -88,6 +88,16 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
X(vkCmdSetStencilWriteMask);
X(vkCmdSetViewport);
X(vkCmdWaitEvents);
+ X(vkCmdBindVertexBuffers2EXT);
+ X(vkCmdSetCullModeEXT);
+ X(vkCmdSetDepthBoundsTestEnableEXT);
+ X(vkCmdSetDepthCompareOpEXT);
+ X(vkCmdSetDepthTestEnableEXT);
+ X(vkCmdSetDepthWriteEnableEXT);
+ X(vkCmdSetFrontFaceEXT);
+ X(vkCmdSetPrimitiveTopologyEXT);
+ X(vkCmdSetStencilOpEXT);
+ X(vkCmdSetStencilTestEnableEXT);
X(vkCreateBuffer);
X(vkCreateBufferView);
X(vkCreateCommandPool);
diff --git a/src/video_core/renderer_vulkan/wrapper.h b/src/video_core/renderer_vulkan/wrapper.h
index d56fdb3f9..71daac9d7 100644
--- a/src/video_core/renderer_vulkan/wrapper.h
+++ b/src/video_core/renderer_vulkan/wrapper.h
@@ -207,6 +207,16 @@ struct DeviceDispatch : public InstanceDispatch {
PFN_vkCmdSetStencilWriteMask vkCmdSetStencilWriteMask;
PFN_vkCmdSetViewport vkCmdSetViewport;
PFN_vkCmdWaitEvents vkCmdWaitEvents;
+ PFN_vkCmdBindVertexBuffers2EXT vkCmdBindVertexBuffers2EXT;
+ PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT;
+ PFN_vkCmdSetDepthBoundsTestEnableEXT vkCmdSetDepthBoundsTestEnableEXT;
+ PFN_vkCmdSetDepthCompareOpEXT vkCmdSetDepthCompareOpEXT;
+ PFN_vkCmdSetDepthTestEnableEXT vkCmdSetDepthTestEnableEXT;
+ PFN_vkCmdSetDepthWriteEnableEXT vkCmdSetDepthWriteEnableEXT;
+ PFN_vkCmdSetFrontFaceEXT vkCmdSetFrontFaceEXT;
+ PFN_vkCmdSetPrimitiveTopologyEXT vkCmdSetPrimitiveTopologyEXT;
+ PFN_vkCmdSetStencilOpEXT vkCmdSetStencilOpEXT;
+ PFN_vkCmdSetStencilTestEnableEXT vkCmdSetStencilTestEnableEXT;
PFN_vkCreateBuffer vkCreateBuffer;
PFN_vkCreateBufferView vkCreateBufferView;
PFN_vkCreateCommandPool vkCreateCommandPool;
@@ -969,6 +979,50 @@ public:
buffer_barriers.data(), image_barriers.size(), image_barriers.data());
}
+ void BindVertexBuffers2EXT(u32 first_binding, u32 binding_count, const VkBuffer* buffers,
+ const VkDeviceSize* offsets, const VkDeviceSize* sizes,
+ const VkDeviceSize* strides) const noexcept {
+ dld->vkCmdBindVertexBuffers2EXT(handle, first_binding, binding_count, buffers, offsets,
+ sizes, strides);
+ }
+
+ void SetCullModeEXT(VkCullModeFlags cull_mode) const noexcept {
+ dld->vkCmdSetCullModeEXT(handle, cull_mode);
+ }
+
+ void SetDepthBoundsTestEnableEXT(bool enable) const noexcept {
+ dld->vkCmdSetDepthBoundsTestEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
+ }
+
+ void SetDepthCompareOpEXT(VkCompareOp compare_op) const noexcept {
+ dld->vkCmdSetDepthCompareOpEXT(handle, compare_op);
+ }
+
+ void SetDepthTestEnableEXT(bool enable) const noexcept {
+ dld->vkCmdSetDepthTestEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
+ }
+
+ void SetDepthWriteEnableEXT(bool enable) const noexcept {
+ dld->vkCmdSetDepthWriteEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
+ }
+
+ void SetFrontFaceEXT(VkFrontFace front_face) const noexcept {
+ dld->vkCmdSetFrontFaceEXT(handle, front_face);
+ }
+
+ void SetPrimitiveTopologyEXT(VkPrimitiveTopology primitive_topology) const noexcept {
+ dld->vkCmdSetPrimitiveTopologyEXT(handle, primitive_topology);
+ }
+
+ void SetStencilOpEXT(VkStencilFaceFlags face_mask, VkStencilOp fail_op, VkStencilOp pass_op,
+ VkStencilOp depth_fail_op, VkCompareOp compare_op) const noexcept {
+ dld->vkCmdSetStencilOpEXT(handle, face_mask, fail_op, pass_op, depth_fail_op, compare_op);
+ }
+
+ void SetStencilTestEnableEXT(bool enable) const noexcept {
+ dld->vkCmdSetStencilTestEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
+ }
+
void BindTransformFeedbackBuffersEXT(u32 first, u32 count, const VkBuffer* buffers,
const VkDeviceSize* offsets,
const VkDeviceSize* sizes) const noexcept {
diff --git a/src/video_core/shader_cache.h b/src/video_core/shader_cache.h
index 2dd270e99..b7608fc7b 100644
--- a/src/video_core/shader_cache.h
+++ b/src/video_core/shader_cache.h
@@ -20,6 +20,7 @@ namespace VideoCommon {
template <class T>
class ShaderCache {
static constexpr u64 PAGE_BITS = 14;
+ static constexpr u64 PAGE_SIZE = u64(1) << PAGE_BITS;
struct Entry {
VAddr addr_start;
@@ -87,8 +88,8 @@ protected:
const VAddr addr_end = addr + size;
Entry* const entry = NewEntry(addr, addr_end, data.get());
- const u64 page_end = addr_end >> PAGE_BITS;
- for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) {
+ const u64 page_end = (addr_end + PAGE_SIZE - 1) >> PAGE_BITS;
+ for (u64 page = addr >> PAGE_BITS; page < page_end; ++page) {
invalidation_cache[page].push_back(entry);
}
@@ -108,20 +109,13 @@ private:
/// @pre invalidation_mutex is locked
void InvalidatePagesInRegion(VAddr addr, std::size_t size) {
const VAddr addr_end = addr + size;
- const u64 page_end = addr_end >> PAGE_BITS;
- for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) {
- const auto it = invalidation_cache.find(page);
+ const u64 page_end = (addr_end + PAGE_SIZE - 1) >> PAGE_BITS;
+ for (u64 page = addr >> PAGE_BITS; page < page_end; ++page) {
+ auto it = invalidation_cache.find(page);
if (it == invalidation_cache.end()) {
continue;
}
-
- std::vector<Entry*>& entries = it->second;
- InvalidatePageEntries(entries, addr, addr_end);
-
- // If there's nothing else in this page, remove it to avoid overpopulating the hash map.
- if (entries.empty()) {
- invalidation_cache.erase(it);
- }
+ InvalidatePageEntries(it->second, addr, addr_end);
}
}
@@ -131,15 +125,22 @@ private:
if (marked_for_removal.empty()) {
return;
}
- std::scoped_lock lock{lookup_mutex};
+ // Remove duplicates
+ std::sort(marked_for_removal.begin(), marked_for_removal.end());
+ marked_for_removal.erase(std::unique(marked_for_removal.begin(), marked_for_removal.end()),
+ marked_for_removal.end());
std::vector<T*> removed_shaders;
removed_shaders.reserve(marked_for_removal.size());
+ std::scoped_lock lock{lookup_mutex};
+
for (Entry* const entry : marked_for_removal) {
- if (lookup_cache.erase(entry->addr_start) > 0) {
- removed_shaders.push_back(entry->data);
- }
+ removed_shaders.push_back(entry->data);
+
+ const auto it = lookup_cache.find(entry->addr_start);
+ ASSERT(it != lookup_cache.end());
+ lookup_cache.erase(it);
}
marked_for_removal.clear();
@@ -154,17 +155,33 @@ private:
/// @param addr_end Non-inclusive end address of the invalidation
/// @pre invalidation_mutex is locked
void InvalidatePageEntries(std::vector<Entry*>& entries, VAddr addr, VAddr addr_end) {
- auto it = entries.begin();
- while (it != entries.end()) {
- Entry* const entry = *it;
+ std::size_t index = 0;
+ while (index < entries.size()) {
+ Entry* const entry = entries[index];
if (!entry->Overlaps(addr, addr_end)) {
- ++it;
+ ++index;
continue;
}
+
UnmarkMemory(entry);
+ RemoveEntryFromInvalidationCache(entry);
marked_for_removal.push_back(entry);
+ }
+ }
- it = entries.erase(it);
+ /// @brief Removes all references to an entry in the invalidation cache
+ /// @param entry Entry to remove from the invalidation cache
+ /// @pre invalidation_mutex is locked
+ void RemoveEntryFromInvalidationCache(const Entry* entry) {
+ const u64 page_end = (entry->addr_end + PAGE_SIZE - 1) >> PAGE_BITS;
+ for (u64 page = entry->addr_start >> PAGE_BITS; page < page_end; ++page) {
+ const auto entries_it = invalidation_cache.find(page);
+ ASSERT(entries_it != invalidation_cache.end());
+ std::vector<Entry*>& entries = entries_it->second;
+
+ const auto entry_it = std::find(entries.begin(), entries.end(), entry);
+ ASSERT(entry_it != entries.end());
+ entries.erase(entry_it);
}
}
@@ -182,16 +199,11 @@ private:
}
/// @brief Removes a vector of shaders from a list
- /// @param removed_shaders Shaders to be removed from the storage, it can contain duplicates
+ /// @param removed_shaders Shaders to be removed from the storage
/// @pre invalidation_mutex is locked
/// @pre lookup_mutex is locked
void RemoveShadersFromStorage(std::vector<T*> removed_shaders) {
- // Remove duplicates
- std::sort(removed_shaders.begin(), removed_shaders.end());
- removed_shaders.erase(std::unique(removed_shaders.begin(), removed_shaders.end()),
- removed_shaders.end());
-
- // Now that there are no duplicates, we can notify removals
+ // Notify removals
for (T* const shader : removed_shaders) {
OnShaderRemoval(shader);
}
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index bbbd96113..5e0d0e7af 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -212,7 +212,7 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default
// UISetting::values.shortcuts, which is alphabetically ordered.
// clang-format off
const std::array<UISettings::Shortcut, 16> Config::default_hotkeys{{
- {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::ApplicationShortcut}},
+ {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}},
{QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}},
{QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},
{QStringLiteral("Decrease Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("-"), Qt::ApplicationShortcut}},
@@ -220,8 +220,8 @@ const std::array<UISettings::Shortcut, 16> Config::default_hotkeys{{
{QStringLiteral("Exit yuzu"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), Qt::WindowShortcut}},
{QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), Qt::WindowShortcut}},
{QStringLiteral("Increase Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("+"), Qt::ApplicationShortcut}},
- {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::ApplicationShortcut}},
- {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WindowShortcut}},
+ {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::WidgetWithChildrenShortcut}},
+ {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}},
{QStringLiteral("Mute Audio"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},
{QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
{QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}},
@@ -665,11 +665,13 @@ void Config::ReadShortcutValues() {
const auto& [keyseq, context] = shortcut;
qt_config->beginGroup(group);
qt_config->beginGroup(name);
+ // No longer using ReadSetting for shortcut.second as it innacurately returns a value of 1
+ // for WidgetWithChildrenShortcut which is a value of 3. Needed to fix shortcuts the open
+ // a file dialog in windowed mode
UISettings::values.shortcuts.push_back(
{name,
group,
- {ReadSetting(QStringLiteral("KeySeq"), keyseq).toString(),
- ReadSetting(QStringLiteral("Context"), context).toInt()}});
+ {ReadSetting(QStringLiteral("KeySeq"), keyseq).toString(), shortcut.second}});
qt_config->endGroup();
qt_config->endGroup();
}
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index 304625cd7..431f51d73 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -28,9 +28,9 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
SetConfiguration();
- connect(ui->api, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
+ connect(ui->api, qOverload<int>(&QComboBox::currentIndexChanged), this,
[this] { UpdateDeviceComboBox(); });
- connect(ui->device, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this,
+ connect(ui->device, qOverload<int>(&QComboBox::activated), this,
[this](int device) { UpdateDeviceSelection(device); });
connect(ui->bg_button, &QPushButton::clicked, this, [this] {
@@ -112,7 +112,7 @@ void ConfigureGraphics::UpdateDeviceComboBox() {
enabled = false;
break;
case Settings::RendererBackend::Vulkan:
- for (const auto device : vulkan_devices) {
+ for (const auto& device : vulkan_devices) {
ui->device->addItem(device);
}
ui->device->setCurrentIndex(vulkan_device);
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index a05fa64ba..00433926d 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -70,6 +70,20 @@ static QString ButtonToText(const Common::ParamPackage& param) {
return GetKeyName(param.Get("code", 0));
}
+ if (param.Get("engine", "") == "gcpad") {
+ if (param.Has("axis")) {
+ const QString axis_str = QString::fromStdString(param.Get("axis", ""));
+ const QString direction_str = QString::fromStdString(param.Get("direction", ""));
+
+ return QObject::tr("GC Axis %1%2").arg(axis_str, direction_str);
+ }
+ if (param.Has("button")) {
+ const QString button_str = QString::number(int(std::log2(param.Get("button", 0))));
+ return QObject::tr("GC Button %1").arg(button_str);
+ }
+ return GetKeyName(param.Get("code", 0));
+ }
+
if (param.Get("engine", "") == "sdl") {
if (param.Has("hat")) {
const QString hat_str = QString::fromStdString(param.Get("hat", ""));
@@ -126,6 +140,25 @@ static QString AnalogToText(const Common::ParamPackage& param, const std::string
return {};
}
+ if (param.Get("engine", "") == "gcpad") {
+ if (dir == "modifier") {
+ return QObject::tr("[unused]");
+ }
+
+ if (dir == "left" || dir == "right") {
+ const QString axis_x_str = QString::fromStdString(param.Get("axis_x", ""));
+
+ return QObject::tr("GC Axis %1").arg(axis_x_str);
+ }
+
+ if (dir == "up" || dir == "down") {
+ const QString axis_y_str = QString::fromStdString(param.Get("axis_y", ""));
+
+ return QObject::tr("GC Axis %1").arg(axis_y_str);
+ }
+
+ return {};
+ }
return QObject::tr("[unknown]");
}
@@ -332,7 +365,8 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
connect(analog_map_deadzone_and_modifier_slider[analog_id], &QSlider::valueChanged, [=] {
const float slider_value = analog_map_deadzone_and_modifier_slider[analog_id]->value();
- if (analogs_param[analog_id].Get("engine", "") == "sdl") {
+ if (analogs_param[analog_id].Get("engine", "") == "sdl" ||
+ analogs_param[analog_id].Get("engine", "") == "gcpad") {
analog_map_deadzone_and_modifier_slider_label[analog_id]->setText(
tr("Deadzone: %1%").arg(slider_value));
analogs_param[analog_id].Set("deadzone", slider_value / 100.0f);
@@ -352,6 +386,20 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
connect(poll_timer.get(), &QTimer::timeout, [this] {
Common::ParamPackage params;
+ if (InputCommon::GetGCButtons()->IsPolling()) {
+ params = InputCommon::GetGCButtons()->GetNextInput();
+ if (params.Has("engine")) {
+ SetPollingResult(params, false);
+ return;
+ }
+ }
+ if (InputCommon::GetGCAnalogs()->IsPolling()) {
+ params = InputCommon::GetGCAnalogs()->GetNextInput();
+ if (params.Has("engine")) {
+ SetPollingResult(params, false);
+ return;
+ }
+ }
for (auto& poller : device_pollers) {
params = poller->GetNextInput();
if (params.Has("engine")) {
@@ -534,7 +582,7 @@ void ConfigureInputPlayer::UpdateButtonLabels() {
analog_map_deadzone_and_modifier_slider_label[analog_id];
if (param.Has("engine")) {
- if (param.Get("engine", "") == "sdl") {
+ if (param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad") {
if (!param.Has("deadzone")) {
param.Set("deadzone", 0.1f);
}
@@ -583,6 +631,11 @@ void ConfigureInputPlayer::HandleClick(
grabKeyboard();
grabMouse();
+ if (type == InputCommon::Polling::DeviceType::Button) {
+ InputCommon::GetGCButtons()->BeginConfiguration();
+ } else {
+ InputCommon::GetGCAnalogs()->BeginConfiguration();
+ }
timeout_timer->start(5000); // Cancel after 5 seconds
poll_timer->start(200); // Check for new inputs every 200ms
}
@@ -596,6 +649,9 @@ void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params,
poller->Stop();
}
+ InputCommon::GetGCButtons()->EndConfiguration();
+ InputCommon::GetGCAnalogs()->EndConfiguration();
+
if (!abort) {
(*input_setter)(params);
}
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index fb299a39b..9844e4764 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -753,7 +753,7 @@ void GMainWindow::InitializeHotkeys() {
});
connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Capture Screenshot"), this),
&QShortcut::activated, this, [&] {
- if (emu_thread->IsRunning()) {
+ if (emu_thread != nullptr && emu_thread->IsRunning()) {
OnCaptureScreenshot();
}
});