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()
614create_target_directory_groups(core) 614create_target_directory_groups(core)
615 615
616target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) 616target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
617target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::Opus unicorn zip) 617target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls opus unicorn zip)
618 618
619if (YUZU_ENABLE_BOXCAT) 619if (YUZU_ENABLE_BOXCAT)
620 target_compile_definitions(core PRIVATE -DYUZU_ENABLE_BOXCAT) 620 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 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#pragma once
6
7#include "common/thread.h" 5#include "common/thread.h"
8#include "core/arm/cpu_interrupt_handler.h" 6#include "core/arm/cpu_interrupt_handler.h"
9 7
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) {
776 rb.Push(RESULT_SUCCESS); 776 rb.Push(RESULT_SUCCESS);
777} 777}
778 778
779void Module::Interface::ListOpenContextStoredUsers(Kernel::HLERequestContext& ctx) {
780 LOG_WARNING(Service_ACC, "(STUBBED) called");
781
782 // TODO(ogniK): Handle open contexts
783 ctx.WriteBuffer(profile_manager->GetOpenUsers());
784 IPC::ResponseBuilder rb{ctx, 2};
785 rb.Push(RESULT_SUCCESS);
786}
787
779void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx) { 788void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx) {
780 LOG_DEBUG(Service_ACC, "called"); 789 LOG_DEBUG(Service_ACC, "called");
781 // A u8 is passed into this function which we can safely ignore. It's to determine if we have 790 // 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:
34 void IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx); 34 void IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx);
35 void GetProfileEditor(Kernel::HLERequestContext& ctx); 35 void GetProfileEditor(Kernel::HLERequestContext& ctx);
36 void ListQualifiedUsers(Kernel::HLERequestContext& ctx); 36 void ListQualifiedUsers(Kernel::HLERequestContext& ctx);
37 void ListOpenContextStoredUsers(Kernel::HLERequestContext& ctx);
37 38
38 private: 39 private:
39 ResultCode InitializeApplicationInfoBase(); 40 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
20 {6, nullptr, "GetProfileDigest"}, // 3.0.0+ 20 {6, nullptr, "GetProfileDigest"}, // 3.0.0+
21 {50, &ACC_SU::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, 21 {50, &ACC_SU::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
22 {51, &ACC_SU::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"}, 22 {51, &ACC_SU::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
23 {60, nullptr, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0 23 {60, &ACC_SU::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0
24 {99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+ 24 {99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+
25 {100, nullptr, "GetUserRegistrationNotifier"}, 25 {100, nullptr, "GetUserRegistrationNotifier"},
26 {101, nullptr, "GetUserStateChangeNotifier"}, 26 {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
20 {6, nullptr, "GetProfileDigest"}, // 3.0.0+ 20 {6, nullptr, "GetProfileDigest"}, // 3.0.0+
21 {50, &ACC_U0::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, 21 {50, &ACC_U0::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
22 {51, &ACC_U0::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"}, 22 {51, &ACC_U0::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
23 {60, nullptr, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0 23 {60, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0
24 {99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+ 24 {99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+
25 {100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"}, 25 {100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"},
26 {101, &ACC_U0::GetBaasAccountManagerForApplication, "GetBaasAccountManagerForApplication"}, 26 {101, &ACC_U0::GetBaasAccountManagerForApplication, "GetBaasAccountManagerForApplication"},
@@ -30,7 +30,7 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
30 {111, nullptr, "ClearSaveDataThumbnail"}, 30 {111, nullptr, "ClearSaveDataThumbnail"},
31 {120, nullptr, "CreateGuestLoginRequest"}, 31 {120, nullptr, "CreateGuestLoginRequest"},
32 {130, nullptr, "LoadOpenContext"}, // 5.0.0+ 32 {130, nullptr, "LoadOpenContext"}, // 5.0.0+
33 {131, nullptr, "ListOpenContextStoredUsers"}, // 6.0.0+ 33 {131, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 6.0.0+
34 {140, &ACC_U0::InitializeApplicationInfoRestricted, "InitializeApplicationInfoRestricted"}, // 6.0.0+ 34 {140, &ACC_U0::InitializeApplicationInfoRestricted, "InitializeApplicationInfoRestricted"}, // 6.0.0+
35 {141, &ACC_U0::ListQualifiedUsers, "ListQualifiedUsers"}, // 6.0.0+ 35 {141, &ACC_U0::ListQualifiedUsers, "ListQualifiedUsers"}, // 6.0.0+
36 {150, &ACC_U0::IsUserAccountSwitchLocked, "IsUserAccountSwitchLocked"}, // 6.0.0+ 36 {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
20 {6, nullptr, "GetProfileDigest"}, // 3.0.0+ 20 {6, nullptr, "GetProfileDigest"}, // 3.0.0+
21 {50, &ACC_U1::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, 21 {50, &ACC_U1::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
22 {51, &ACC_U1::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"}, 22 {51, &ACC_U1::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
23 {60, nullptr, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0 23 {60, &ACC_U1::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0
24 {99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+ 24 {99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+
25 {100, nullptr, "GetUserRegistrationNotifier"}, 25 {100, nullptr, "GetUserRegistrationNotifier"},
26 {101, nullptr, "GetUserStateChangeNotifier"}, 26 {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 @@
10#include "core/core.h" 10#include "core/core.h"
11#include "core/file_sys/control_metadata.h" 11#include "core/file_sys/control_metadata.h"
12#include "core/file_sys/patch_manager.h" 12#include "core/file_sys/patch_manager.h"
13#include "core/file_sys/registered_cache.h"
13#include "core/file_sys/savedata_factory.h" 14#include "core/file_sys/savedata_factory.h"
14#include "core/hle/ipc_helpers.h" 15#include "core/hle/ipc_helpers.h"
15#include "core/hle/kernel/kernel.h" 16#include "core/hle/kernel/kernel.h"
@@ -841,7 +842,7 @@ public:
841 {110, nullptr, "NeedsToExitProcess"}, 842 {110, nullptr, "NeedsToExitProcess"},
842 {120, nullptr, "GetLibraryAppletInfo"}, 843 {120, nullptr, "GetLibraryAppletInfo"},
843 {150, nullptr, "RequestForAppletToGetForeground"}, 844 {150, nullptr, "RequestForAppletToGetForeground"},
844 {160, nullptr, "GetIndirectLayerConsumerHandle"}, 845 {160, &ILibraryAppletAccessor::GetIndirectLayerConsumerHandle, "GetIndirectLayerConsumerHandle"},
845 }; 846 };
846 // clang-format on 847 // clang-format on
847 848
@@ -960,6 +961,18 @@ private:
960 rb.PushCopyObjects(applet->GetBroker().GetInteractiveDataEvent()); 961 rb.PushCopyObjects(applet->GetBroker().GetInteractiveDataEvent());
961 } 962 }
962 963
964 void GetIndirectLayerConsumerHandle(Kernel::HLERequestContext& ctx) {
965 LOG_WARNING(Service_AM, "(STUBBED) called");
966
967 // We require a non-zero handle to be valid. Using 0xdeadbeef allows us to trace if this is
968 // actually used anywhere
969 constexpr u64 handle = 0xdeadbeef;
970
971 IPC::ResponseBuilder rb{ctx, 4};
972 rb.Push(RESULT_SUCCESS);
973 rb.Push(handle);
974 }
975
963 std::shared_ptr<Applets::Applet> applet; 976 std::shared_ptr<Applets::Applet> applet;
964}; 977};
965 978
@@ -1341,14 +1354,25 @@ void IApplicationFunctions::GetDisplayVersion(Kernel::HLERequestContext& ctx) {
1341 1354
1342 std::array<u8, 0x10> version_string{}; 1355 std::array<u8, 0x10> version_string{};
1343 1356
1344 FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()}; 1357 const auto res = [this] {
1345 const auto res = pm.GetControlMetadata(); 1358 const auto title_id = system.CurrentProcess()->GetTitleID();
1359
1360 FileSys::PatchManager pm{title_id};
1361 auto res = pm.GetControlMetadata();
1362 if (res.first != nullptr) {
1363 return res;
1364 }
1365
1366 FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(title_id)};
1367 return pm_update.GetControlMetadata();
1368 }();
1369
1346 if (res.first != nullptr) { 1370 if (res.first != nullptr) {
1347 const auto& version = res.first->GetVersionString(); 1371 const auto& version = res.first->GetVersionString();
1348 std::copy(version.begin(), version.end(), version_string.begin()); 1372 std::copy(version.begin(), version.end(), version_string.begin());
1349 } else { 1373 } else {
1350 constexpr u128 default_version = {1, 0}; 1374 constexpr char default_version[]{"1.0.0"};
1351 std::memcpy(version_string.data(), default_version.data(), sizeof(u128)); 1375 std::memcpy(version_string.data(), default_version, sizeof(default_version));
1352 } 1376 }
1353 1377
1354 IPC::ResponseBuilder rb{ctx, 6}; 1378 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 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <algorithm> 5#include <algorithm>
6#include <array>
6#include <chrono> 7#include <chrono>
7#include "common/logging/log.h" 8#include "common/logging/log.h"
8#include "core/hle/ipc_helpers.h" 9#include "core/hle/ipc_helpers.h"
@@ -31,6 +32,44 @@ constexpr std::array<LanguageCode, 17> available_language_codes = {{
31 LanguageCode::ZH_HANT, 32 LanguageCode::ZH_HANT,
32}}; 33}};
33 34
35enum class KeyboardLayout : u64 {
36 Japanese = 0,
37 EnglishUs = 1,
38 EnglishUsInternational = 2,
39 EnglishUk = 3,
40 French = 4,
41 FrenchCa = 5,
42 Spanish = 6,
43 SpanishLatin = 7,
44 German = 8,
45 Italian = 9,
46 Portuguese = 10,
47 Russian = 11,
48 Korean = 12,
49 ChineseSimplified = 13,
50 ChineseTraditional = 14,
51};
52
53constexpr std::array<std::pair<LanguageCode, KeyboardLayout>, 17> language_to_layout{{
54 {LanguageCode::JA, KeyboardLayout::Japanese},
55 {LanguageCode::EN_US, KeyboardLayout::EnglishUs},
56 {LanguageCode::FR, KeyboardLayout::French},
57 {LanguageCode::DE, KeyboardLayout::German},
58 {LanguageCode::IT, KeyboardLayout::Italian},
59 {LanguageCode::ES, KeyboardLayout::Spanish},
60 {LanguageCode::ZH_CN, KeyboardLayout::ChineseSimplified},
61 {LanguageCode::KO, KeyboardLayout::Korean},
62 {LanguageCode::NL, KeyboardLayout::EnglishUsInternational},
63 {LanguageCode::PT, KeyboardLayout::Portuguese},
64 {LanguageCode::RU, KeyboardLayout::Russian},
65 {LanguageCode::ZH_TW, KeyboardLayout::ChineseTraditional},
66 {LanguageCode::EN_GB, KeyboardLayout::EnglishUk},
67 {LanguageCode::FR_CA, KeyboardLayout::FrenchCa},
68 {LanguageCode::ES_419, KeyboardLayout::SpanishLatin},
69 {LanguageCode::ZH_HANS, KeyboardLayout::ChineseSimplified},
70 {LanguageCode::ZH_HANT, KeyboardLayout::ChineseTraditional},
71}};
72
34constexpr std::size_t pre4_0_0_max_entries = 15; 73constexpr std::size_t pre4_0_0_max_entries = 15;
35constexpr std::size_t post4_0_0_max_entries = 17; 74constexpr std::size_t post4_0_0_max_entries = 17;
36 75
@@ -50,6 +89,25 @@ void GetAvailableLanguageCodesImpl(Kernel::HLERequestContext& ctx, std::size_t m
50 ctx.WriteBuffer(available_language_codes.data(), copy_size); 89 ctx.WriteBuffer(available_language_codes.data(), copy_size);
51 PushResponseLanguageCode(ctx, copy_amount); 90 PushResponseLanguageCode(ctx, copy_amount);
52} 91}
92
93void GetKeyCodeMapImpl(Kernel::HLERequestContext& ctx) {
94 const auto language_code = available_language_codes[Settings::values.language_index];
95 const auto key_code =
96 std::find_if(language_to_layout.cbegin(), language_to_layout.cend(),
97 [=](const auto& element) { return element.first == language_code; });
98 KeyboardLayout layout = KeyboardLayout::EnglishUs;
99 if (key_code == language_to_layout.cend()) {
100 LOG_ERROR(Service_SET,
101 "Could not find keyboard layout for language index {}, defaulting to English us",
102 Settings::values.language_index);
103 } else {
104 layout = key_code->second;
105 }
106
107 IPC::ResponseBuilder rb{ctx, 2};
108 rb.Push(RESULT_SUCCESS);
109 ctx.WriteBuffer(&layout, sizeof(KeyboardLayout));
110}
53} // Anonymous namespace 111} // Anonymous namespace
54 112
55LanguageCode GetLanguageCodeFromIndex(std::size_t index) { 113LanguageCode GetLanguageCodeFromIndex(std::size_t index) {
@@ -120,6 +178,16 @@ void SET::GetRegionCode(Kernel::HLERequestContext& ctx) {
120 rb.Push(Settings::values.region_index); 178 rb.Push(Settings::values.region_index);
121} 179}
122 180
181void SET::GetKeyCodeMap(Kernel::HLERequestContext& ctx) {
182 LOG_DEBUG(Service_SET, "Called {}", ctx.Description());
183 GetKeyCodeMapImpl(ctx);
184}
185
186void SET::GetKeyCodeMap2(Kernel::HLERequestContext& ctx) {
187 LOG_DEBUG(Service_SET, "Called {}", ctx.Description());
188 GetKeyCodeMapImpl(ctx);
189}
190
123SET::SET() : ServiceFramework("set") { 191SET::SET() : ServiceFramework("set") {
124 // clang-format off 192 // clang-format off
125 static const FunctionInfo functions[] = { 193 static const FunctionInfo functions[] = {
@@ -130,9 +198,9 @@ SET::SET() : ServiceFramework("set") {
130 {4, &SET::GetRegionCode, "GetRegionCode"}, 198 {4, &SET::GetRegionCode, "GetRegionCode"},
131 {5, &SET::GetAvailableLanguageCodes2, "GetAvailableLanguageCodes2"}, 199 {5, &SET::GetAvailableLanguageCodes2, "GetAvailableLanguageCodes2"},
132 {6, &SET::GetAvailableLanguageCodeCount2, "GetAvailableLanguageCodeCount2"}, 200 {6, &SET::GetAvailableLanguageCodeCount2, "GetAvailableLanguageCodeCount2"},
133 {7, nullptr, "GetKeyCodeMap"}, 201 {7, &SET::GetKeyCodeMap, "GetKeyCodeMap"},
134 {8, &SET::GetQuestFlag, "GetQuestFlag"}, 202 {8, &SET::GetQuestFlag, "GetQuestFlag"},
135 {9, nullptr, "GetKeyCodeMap2"}, 203 {9, &SET::GetKeyCodeMap2, "GetKeyCodeMap2"},
136 {10, nullptr, "GetFirmwareVersionForDebug"}, 204 {10, nullptr, "GetFirmwareVersionForDebug"},
137 }; 205 };
138 // clang-format on 206 // 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:
44 void GetAvailableLanguageCodeCount2(Kernel::HLERequestContext& ctx); 44 void GetAvailableLanguageCodeCount2(Kernel::HLERequestContext& ctx);
45 void GetQuestFlag(Kernel::HLERequestContext& ctx); 45 void GetQuestFlag(Kernel::HLERequestContext& ctx);
46 void GetRegionCode(Kernel::HLERequestContext& ctx); 46 void GetRegionCode(Kernel::HLERequestContext& ctx);
47 void GetKeyCodeMap(Kernel::HLERequestContext& ctx);
48 void GetKeyCodeMap2(Kernel::HLERequestContext& ctx);
47}; 49};
48 50
49} // namespace Service::Set 51} // 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 {
548 // longer exist, and we should just leave the pagetable entry blank. 548 // longer exist, and we should just leave the pagetable entry blank.
549 page_type = Common::PageType::Unmapped; 549 page_type = Common::PageType::Unmapped;
550 } else { 550 } else {
551 page_type = Common::PageType::Memory;
552 current_page_table->pointers[vaddr >> PAGE_BITS] = 551 current_page_table->pointers[vaddr >> PAGE_BITS] =
553 pointer - (vaddr & ~PAGE_MASK); 552 pointer - (vaddr & ~PAGE_MASK);
553 page_type = Common::PageType::Memory;
554 } 554 }
555 break; 555 break;
556 } 556 }
@@ -591,9 +591,12 @@ struct Memory::Impl {
591 base + page_table.pointers.size()); 591 base + page_table.pointers.size());
592 592
593 if (!target) { 593 if (!target) {
594 ASSERT_MSG(type != Common::PageType::Memory,
595 "Mapping memory page without a pointer @ {:016x}", base * PAGE_SIZE);
596
594 while (base != end) { 597 while (base != end) {
595 page_table.pointers[base] = nullptr;
596 page_table.attributes[base] = type; 598 page_table.attributes[base] = type;
599 page_table.pointers[base] = nullptr;
597 page_table.backing_addr[base] = 0; 600 page_table.backing_addr[base] = 0;
598 601
599 base += 1; 602 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
7 main.h 7 main.h
8 motion_emu.cpp 8 motion_emu.cpp
9 motion_emu.h 9 motion_emu.h
10 gcadapter/gc_adapter.cpp
11 gcadapter/gc_adapter.h
12 gcadapter/gc_poller.cpp
13 gcadapter/gc_poller.h
10 sdl/sdl.cpp 14 sdl/sdl.cpp
11 sdl/sdl.h 15 sdl/sdl.h
12 udp/client.cpp 16 udp/client.cpp
@@ -26,5 +30,7 @@ if(SDL2_FOUND)
26 target_compile_definitions(input_common PRIVATE HAVE_SDL2) 30 target_compile_definitions(input_common PRIVATE HAVE_SDL2)
27endif() 31endif()
28 32
33target_link_libraries(input_common PUBLIC ${LIBUSB_LIBRARIES})
34
29create_target_directory_groups(input_common) 35create_target_directory_groups(input_common)
30target_link_libraries(input_common PUBLIC core PRIVATE common Boost::boost) 36target_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 @@
1// Copyright 2014 Dolphin Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#include <chrono>
6#include <thread>
7#include "common/logging/log.h"
8#include "input_common/gcadapter/gc_adapter.h"
9
10namespace GCAdapter {
11
12/// Used to loop through and assign button in poller
13constexpr std::array<PadButton, 12> PadButtonArray{
14 PadButton::PAD_BUTTON_LEFT, PadButton::PAD_BUTTON_RIGHT, PadButton::PAD_BUTTON_DOWN,
15 PadButton::PAD_BUTTON_UP, PadButton::PAD_TRIGGER_Z, PadButton::PAD_TRIGGER_R,
16 PadButton::PAD_TRIGGER_L, PadButton::PAD_BUTTON_A, PadButton::PAD_BUTTON_B,
17 PadButton::PAD_BUTTON_X, PadButton::PAD_BUTTON_Y, PadButton::PAD_BUTTON_START,
18};
19
20Adapter::Adapter() {
21 if (usb_adapter_handle != nullptr) {
22 return;
23 }
24 LOG_INFO(Input, "GC Adapter Initialization started");
25
26 current_status = NO_ADAPTER_DETECTED;
27
28 const int init_res = libusb_init(&libusb_ctx);
29 if (init_res == LIBUSB_SUCCESS) {
30 StartScanThread();
31 } else {
32 LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res);
33 }
34}
35
36GCPadStatus Adapter::GetPadStatus(int port, const std::array<u8, 37>& adapter_payload) {
37 GCPadStatus pad = {};
38 bool get_origin = false;
39
40 ControllerTypes type = ControllerTypes(adapter_payload[1 + (9 * port)] >> 4);
41 if (type != ControllerTypes::None) {
42 get_origin = true;
43 }
44
45 adapter_controllers_status[port] = type;
46
47 static constexpr std::array<PadButton, 8> b1_buttons{
48 PadButton::PAD_BUTTON_A, PadButton::PAD_BUTTON_B, PadButton::PAD_BUTTON_X,
49 PadButton::PAD_BUTTON_Y, PadButton::PAD_BUTTON_LEFT, PadButton::PAD_BUTTON_RIGHT,
50 PadButton::PAD_BUTTON_DOWN, PadButton::PAD_BUTTON_UP,
51 };
52
53 static constexpr std::array<PadButton, 4> b2_buttons{
54 PadButton::PAD_BUTTON_START,
55 PadButton::PAD_TRIGGER_Z,
56 PadButton::PAD_TRIGGER_R,
57 PadButton::PAD_TRIGGER_L,
58 };
59
60 if (adapter_controllers_status[port] != ControllerTypes::None) {
61 const u8 b1 = adapter_payload[1 + (9 * port) + 1];
62 const u8 b2 = adapter_payload[1 + (9 * port) + 2];
63
64 for (std::size_t i = 0; i < b1_buttons.size(); ++i) {
65 if ((b1 & (1U << i)) != 0) {
66 pad.button |= static_cast<u16>(b1_buttons[i]);
67 }
68 }
69
70 for (std::size_t j = 0; j < b2_buttons.size(); ++j) {
71 if ((b2 & (1U << j)) != 0) {
72 pad.button |= static_cast<u16>(b2_buttons[j]);
73 }
74 }
75
76 if (get_origin) {
77 pad.button |= PAD_GET_ORIGIN;
78 }
79
80 pad.stick_x = adapter_payload[1 + (9 * port) + 3];
81 pad.stick_y = adapter_payload[1 + (9 * port) + 4];
82 pad.substick_x = adapter_payload[1 + (9 * port) + 5];
83 pad.substick_y = adapter_payload[1 + (9 * port) + 6];
84 pad.trigger_left = adapter_payload[1 + (9 * port) + 7];
85 pad.trigger_right = adapter_payload[1 + (9 * port) + 8];
86 }
87 return pad;
88}
89
90void Adapter::PadToState(const GCPadStatus& pad, GCState& state) {
91 for (const auto& button : PadButtonArray) {
92 const u16 button_value = static_cast<u16>(button);
93 state.buttons.insert_or_assign(button_value, pad.button & button_value);
94 }
95
96 state.axes.insert_or_assign(static_cast<u8>(PadAxes::StickX), pad.stick_x);
97 state.axes.insert_or_assign(static_cast<u8>(PadAxes::StickY), pad.stick_y);
98 state.axes.insert_or_assign(static_cast<u8>(PadAxes::SubstickX), pad.substick_x);
99 state.axes.insert_or_assign(static_cast<u8>(PadAxes::SubstickY), pad.substick_y);
100 state.axes.insert_or_assign(static_cast<u8>(PadAxes::TriggerLeft), pad.trigger_left);
101 state.axes.insert_or_assign(static_cast<u8>(PadAxes::TriggerRight), pad.trigger_right);
102}
103
104void Adapter::Read() {
105 LOG_DEBUG(Input, "GC Adapter Read() thread started");
106
107 int payload_size_in, payload_size_copy;
108 std::array<u8, 37> adapter_payload;
109 std::array<u8, 37> adapter_payload_copy;
110 std::array<GCPadStatus, 4> pads;
111
112 while (adapter_thread_running) {
113 libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(),
114 sizeof(adapter_payload), &payload_size_in, 16);
115 payload_size_copy = 0;
116 // this mutex might be redundant?
117 {
118 std::lock_guard<std::mutex> lk(s_mutex);
119 std::copy(std::begin(adapter_payload), std::end(adapter_payload),
120 std::begin(adapter_payload_copy));
121 payload_size_copy = payload_size_in;
122 }
123
124 if (payload_size_copy != sizeof(adapter_payload_copy) ||
125 adapter_payload_copy[0] != LIBUSB_DT_HID) {
126 LOG_ERROR(Input, "error reading payload (size: {}, type: {:02x})", payload_size_copy,
127 adapter_payload_copy[0]);
128 adapter_thread_running = false; // error reading from adapter, stop reading.
129 break;
130 }
131 for (std::size_t port = 0; port < pads.size(); ++port) {
132 pads[port] = GetPadStatus(port, adapter_payload_copy);
133 if (DeviceConnected(port) && configuring) {
134 if (pads[port].button != PAD_GET_ORIGIN) {
135 pad_queue[port].Push(pads[port]);
136 }
137
138 // Accounting for a threshold here because of some controller variance
139 if (pads[port].stick_x > pads[port].MAIN_STICK_CENTER_X + pads[port].THRESHOLD ||
140 pads[port].stick_x < pads[port].MAIN_STICK_CENTER_X - pads[port].THRESHOLD) {
141 pads[port].axis = GCAdapter::PadAxes::StickX;
142 pads[port].axis_value = pads[port].stick_x;
143 pad_queue[port].Push(pads[port]);
144 }
145 if (pads[port].stick_y > pads[port].MAIN_STICK_CENTER_Y + pads[port].THRESHOLD ||
146 pads[port].stick_y < pads[port].MAIN_STICK_CENTER_Y - pads[port].THRESHOLD) {
147 pads[port].axis = GCAdapter::PadAxes::StickY;
148 pads[port].axis_value = pads[port].stick_y;
149 pad_queue[port].Push(pads[port]);
150 }
151 if (pads[port].substick_x > pads[port].C_STICK_CENTER_X + pads[port].THRESHOLD ||
152 pads[port].substick_x < pads[port].C_STICK_CENTER_X - pads[port].THRESHOLD) {
153 pads[port].axis = GCAdapter::PadAxes::SubstickX;
154 pads[port].axis_value = pads[port].substick_x;
155 pad_queue[port].Push(pads[port]);
156 }
157 if (pads[port].substick_y > pads[port].C_STICK_CENTER_Y + pads[port].THRESHOLD ||
158 pads[port].substick_y < pads[port].C_STICK_CENTER_Y - pads[port].THRESHOLD) {
159 pads[port].axis = GCAdapter::PadAxes::SubstickY;
160 pads[port].axis_value = pads[port].substick_y;
161 pad_queue[port].Push(pads[port]);
162 }
163 if (pads[port].trigger_left > pads[port].TRIGGER_THRESHOLD) {
164 pads[port].axis = GCAdapter::PadAxes::TriggerLeft;
165 pads[port].axis_value = pads[port].trigger_left;
166 pad_queue[port].Push(pads[port]);
167 }
168 if (pads[port].trigger_right > pads[port].TRIGGER_THRESHOLD) {
169 pads[port].axis = GCAdapter::PadAxes::TriggerRight;
170 pads[port].axis_value = pads[port].trigger_right;
171 pad_queue[port].Push(pads[port]);
172 }
173 }
174 PadToState(pads[port], state[port]);
175 }
176 std::this_thread::yield();
177 }
178}
179
180void Adapter::ScanThreadFunc() {
181 LOG_INFO(Input, "GC Adapter scanning thread started");
182
183 while (detect_thread_running) {
184 if (usb_adapter_handle == nullptr) {
185 std::lock_guard<std::mutex> lk(initialization_mutex);
186 Setup();
187 }
188 std::this_thread::sleep_for(std::chrono::milliseconds(500));
189 }
190}
191
192void Adapter::StartScanThread() {
193 if (detect_thread_running) {
194 return;
195 }
196 if (!libusb_ctx) {
197 return;
198 }
199
200 detect_thread_running = true;
201 detect_thread = std::thread([=] { ScanThreadFunc(); });
202}
203
204void Adapter::StopScanThread() {
205 detect_thread_running = false;
206 detect_thread.join();
207}
208
209void Adapter::Setup() {
210 // Reset the error status in case the adapter gets unplugged
211 if (current_status < 0) {
212 current_status = NO_ADAPTER_DETECTED;
213 }
214
215 adapter_controllers_status.fill(ControllerTypes::None);
216
217 // pointer to list of connected usb devices
218 libusb_device** devices{};
219
220 // populate the list of devices, get the count
221 const ssize_t device_count = libusb_get_device_list(libusb_ctx, &devices);
222 if (device_count < 0) {
223 LOG_ERROR(Input, "libusb_get_device_list failed with error: {}", device_count);
224 detect_thread_running = false; // Stop the loop constantly checking for gc adapter
225 // TODO: For hotplug+gc adapter checkbox implementation, revert this.
226 return;
227 }
228
229 if (devices != nullptr) {
230 for (std::size_t index = 0; index < device_count; ++index) {
231 if (CheckDeviceAccess(devices[index])) {
232 // GC Adapter found and accessible, registering it
233 GetGCEndpoint(devices[index]);
234 break;
235 }
236 }
237 libusb_free_device_list(devices, 1);
238 }
239}
240
241bool Adapter::CheckDeviceAccess(libusb_device* device) {
242 libusb_device_descriptor desc;
243 const int get_descriptor_error = libusb_get_device_descriptor(device, &desc);
244 if (get_descriptor_error) {
245 // could not acquire the descriptor, no point in trying to use it.
246 LOG_ERROR(Input, "libusb_get_device_descriptor failed with error: {}",
247 get_descriptor_error);
248 return false;
249 }
250
251 if (desc.idVendor != 0x057e || desc.idProduct != 0x0337) {
252 // This isn't the device we are looking for.
253 return false;
254 }
255 const int open_error = libusb_open(device, &usb_adapter_handle);
256
257 if (open_error == LIBUSB_ERROR_ACCESS) {
258 LOG_ERROR(Input, "Yuzu can not gain access to this device: ID {:04X}:{:04X}.",
259 desc.idVendor, desc.idProduct);
260 return false;
261 }
262 if (open_error) {
263 LOG_ERROR(Input, "libusb_open failed to open device with error = {}", open_error);
264 return false;
265 }
266
267 int kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0);
268 if (kernel_driver_error == 1) {
269 kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle, 0);
270 if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) {
271 LOG_ERROR(Input, "libusb_detach_kernel_driver failed with error = {}",
272 kernel_driver_error);
273 }
274 }
275
276 if (kernel_driver_error && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) {
277 libusb_close(usb_adapter_handle);
278 usb_adapter_handle = nullptr;
279 return false;
280 }
281
282 const int interface_claim_error = libusb_claim_interface(usb_adapter_handle, 0);
283 if (interface_claim_error) {
284 LOG_ERROR(Input, "libusb_claim_interface failed with error = {}", interface_claim_error);
285 libusb_close(usb_adapter_handle);
286 usb_adapter_handle = nullptr;
287 return false;
288 }
289
290 return true;
291}
292
293void Adapter::GetGCEndpoint(libusb_device* device) {
294 libusb_config_descriptor* config = nullptr;
295 const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config);
296 if (config_descriptor_return != LIBUSB_SUCCESS) {
297 LOG_ERROR(Input, "libusb_get_config_descriptor failed with error = {}",
298 config_descriptor_return);
299 return;
300 }
301
302 for (u8 ic = 0; ic < config->bNumInterfaces; ic++) {
303 const libusb_interface* interfaceContainer = &config->interface[ic];
304 for (int i = 0; i < interfaceContainer->num_altsetting; i++) {
305 const libusb_interface_descriptor* interface = &interfaceContainer->altsetting[i];
306 for (u8 e = 0; e < interface->bNumEndpoints; e++) {
307 const libusb_endpoint_descriptor* endpoint = &interface->endpoint[e];
308 if (endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) {
309 input_endpoint = endpoint->bEndpointAddress;
310 } else {
311 output_endpoint = endpoint->bEndpointAddress;
312 }
313 }
314 }
315 }
316 // This transfer seems to be responsible for clearing the state of the adapter
317 // Used to clear the "busy" state of when the device is unexpectedly unplugged
318 unsigned char clear_payload = 0x13;
319 libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, &clear_payload,
320 sizeof(clear_payload), nullptr, 16);
321
322 adapter_thread_running = true;
323 current_status = ADAPTER_DETECTED;
324 adapter_input_thread = std::thread([=] { Read(); }); // Read input
325}
326
327Adapter::~Adapter() {
328 StopScanThread();
329 Reset();
330}
331
332void Adapter::Reset() {
333 std::unique_lock<std::mutex> lock(initialization_mutex, std::defer_lock);
334 if (!lock.try_lock()) {
335 return;
336 }
337 if (current_status != ADAPTER_DETECTED) {
338 return;
339 }
340
341 if (adapter_thread_running) {
342 adapter_thread_running = false;
343 }
344 adapter_input_thread.join();
345
346 adapter_controllers_status.fill(ControllerTypes::None);
347 current_status = NO_ADAPTER_DETECTED;
348
349 if (usb_adapter_handle) {
350 libusb_release_interface(usb_adapter_handle, 1);
351 libusb_close(usb_adapter_handle);
352 usb_adapter_handle = nullptr;
353 }
354
355 if (libusb_ctx) {
356 libusb_exit(libusb_ctx);
357 }
358}
359
360bool Adapter::DeviceConnected(int port) {
361 return adapter_controllers_status[port] != ControllerTypes::None;
362}
363
364void Adapter::ResetDeviceType(int port) {
365 adapter_controllers_status[port] = ControllerTypes::None;
366}
367
368void Adapter::BeginConfiguration() {
369 for (auto& pq : pad_queue) {
370 pq.Clear();
371 }
372 configuring = true;
373}
374
375void Adapter::EndConfiguration() {
376 for (auto& pq : pad_queue) {
377 pq.Clear();
378 }
379 configuring = false;
380}
381
382std::array<Common::SPSCQueue<GCPadStatus>, 4>& Adapter::GetPadQueue() {
383 return pad_queue;
384}
385
386const std::array<Common::SPSCQueue<GCPadStatus>, 4>& Adapter::GetPadQueue() const {
387 return pad_queue;
388}
389
390std::array<GCState, 4>& Adapter::GetPadState() {
391 return state;
392}
393
394const std::array<GCState, 4>& Adapter::GetPadState() const {
395 return state;
396}
397
398} // 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 @@
1// Copyright 2014 Dolphin Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#pragma once
6#include <algorithm>
7#include <functional>
8#include <mutex>
9#include <thread>
10#include <unordered_map>
11#include <libusb.h>
12#include "common/common_types.h"
13#include "common/threadsafe_queue.h"
14
15namespace GCAdapter {
16
17enum {
18 PAD_USE_ORIGIN = 0x0080,
19 PAD_GET_ORIGIN = 0x2000,
20 PAD_ERR_STATUS = 0x8000,
21};
22
23enum class PadButton {
24 PAD_BUTTON_LEFT = 0x0001,
25 PAD_BUTTON_RIGHT = 0x0002,
26 PAD_BUTTON_DOWN = 0x0004,
27 PAD_BUTTON_UP = 0x0008,
28 PAD_TRIGGER_Z = 0x0010,
29 PAD_TRIGGER_R = 0x0020,
30 PAD_TRIGGER_L = 0x0040,
31 PAD_BUTTON_A = 0x0100,
32 PAD_BUTTON_B = 0x0200,
33 PAD_BUTTON_X = 0x0400,
34 PAD_BUTTON_Y = 0x0800,
35 PAD_BUTTON_START = 0x1000,
36 // Below is for compatibility with "AxisButton" type
37 PAD_STICK = 0x2000,
38};
39
40extern const std::array<PadButton, 12> PadButtonArray;
41
42enum class PadAxes : u8 {
43 StickX,
44 StickY,
45 SubstickX,
46 SubstickY,
47 TriggerLeft,
48 TriggerRight,
49 Undefined,
50};
51
52struct GCPadStatus {
53 u16 button{}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits
54 u8 stick_x{}; // 0 <= stick_x <= 255
55 u8 stick_y{}; // 0 <= stick_y <= 255
56 u8 substick_x{}; // 0 <= substick_x <= 255
57 u8 substick_y{}; // 0 <= substick_y <= 255
58 u8 trigger_left{}; // 0 <= trigger_left <= 255
59 u8 trigger_right{}; // 0 <= trigger_right <= 255
60
61 static constexpr u8 MAIN_STICK_CENTER_X = 0x80;
62 static constexpr u8 MAIN_STICK_CENTER_Y = 0x80;
63 static constexpr u8 MAIN_STICK_RADIUS = 0x7f;
64 static constexpr u8 C_STICK_CENTER_X = 0x80;
65 static constexpr u8 C_STICK_CENTER_Y = 0x80;
66 static constexpr u8 C_STICK_RADIUS = 0x7f;
67 static constexpr u8 THRESHOLD = 10;
68
69 // 256/4, at least a quarter press to count as a press. For polling mostly
70 static constexpr u8 TRIGGER_THRESHOLD = 64;
71
72 u8 port{};
73 PadAxes axis{PadAxes::Undefined};
74 u8 axis_value{255};
75};
76
77struct GCState {
78 std::unordered_map<int, bool> buttons;
79 std::unordered_map<int, u16> axes;
80};
81
82enum class ControllerTypes { None, Wired, Wireless };
83
84enum {
85 NO_ADAPTER_DETECTED = 0,
86 ADAPTER_DETECTED = 1,
87};
88
89class Adapter {
90public:
91 /// Initialize the GC Adapter capture and read sequence
92 Adapter();
93
94 /// Close the adapter read thread and release the adapter
95 ~Adapter();
96 /// Used for polling
97 void BeginConfiguration();
98 void EndConfiguration();
99
100 std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue();
101 const std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue() const;
102
103 std::array<GCState, 4>& GetPadState();
104 const std::array<GCState, 4>& GetPadState() const;
105
106private:
107 GCPadStatus GetPadStatus(int port, const std::array<u8, 37>& adapter_payload);
108
109 void PadToState(const GCPadStatus& pad, GCState& state);
110
111 void Read();
112 void ScanThreadFunc();
113 /// Begin scanning for the GC Adapter.
114 void StartScanThread();
115
116 /// Stop scanning for the adapter
117 void StopScanThread();
118
119 /// Returns true if there is a device connected to port
120 bool DeviceConnected(int port);
121
122 /// Resets status of device connected to port
123 void ResetDeviceType(int port);
124
125 /// Returns true if we successfully gain access to GC Adapter
126 bool CheckDeviceAccess(libusb_device* device);
127
128 /// Captures GC Adapter endpoint address,
129 void GetGCEndpoint(libusb_device* device);
130
131 /// For shutting down, clear all data, join all threads, release usb
132 void Reset();
133
134 /// For use in initialization, querying devices to find the adapter
135 void Setup();
136
137 int current_status = NO_ADAPTER_DETECTED;
138 libusb_device_handle* usb_adapter_handle = nullptr;
139 std::array<ControllerTypes, 4> adapter_controllers_status{};
140
141 std::mutex s_mutex;
142
143 std::thread adapter_input_thread;
144 bool adapter_thread_running;
145
146 std::mutex initialization_mutex;
147 std::thread detect_thread;
148 bool detect_thread_running = false;
149
150 libusb_context* libusb_ctx;
151
152 u8 input_endpoint = 0;
153 u8 output_endpoint = 0;
154
155 bool configuring = false;
156
157 std::array<Common::SPSCQueue<GCPadStatus>, 4> pad_queue;
158 std::array<GCState, 4> state;
159};
160
161} // 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 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <atomic>
6#include <list>
7#include <mutex>
8#include <utility>
9#include "common/threadsafe_queue.h"
10#include "input_common/gcadapter/gc_adapter.h"
11#include "input_common/gcadapter/gc_poller.h"
12
13namespace InputCommon {
14
15class GCButton final : public Input::ButtonDevice {
16public:
17 explicit GCButton(int port_, int button_, GCAdapter::Adapter* adapter)
18 : port(port_), button(button_), gcadapter(adapter) {}
19
20 ~GCButton() override;
21
22 bool GetStatus() const override {
23 return gcadapter->GetPadState()[port].buttons.at(button);
24 }
25
26private:
27 const int port;
28 const int button;
29 GCAdapter::Adapter* gcadapter;
30};
31
32class GCAxisButton final : public Input::ButtonDevice {
33public:
34 explicit GCAxisButton(int port_, int axis_, float threshold_, bool trigger_if_greater_,
35 GCAdapter::Adapter* adapter)
36 : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_),
37 gcadapter(adapter) {
38 // L/R triggers range is only in positive direction beginning near 0
39 // 0.0 threshold equates to near half trigger press, but threshold accounts for variability.
40 if (axis > 3) {
41 threshold *= -0.5;
42 }
43 }
44
45 bool GetStatus() const override {
46 const float axis_value = (gcadapter->GetPadState()[port].axes.at(axis) - 128.0f) / 128.0f;
47 if (trigger_if_greater) {
48 // TODO: Might be worthwile to set a slider for the trigger threshold. It is currently
49 // always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick
50 return axis_value > threshold;
51 }
52 return axis_value < -threshold;
53 }
54
55private:
56 const int port;
57 const int axis;
58 float threshold;
59 bool trigger_if_greater;
60 GCAdapter::Adapter* gcadapter;
61};
62
63GCButtonFactory::GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
64 : adapter(std::move(adapter_)) {}
65
66GCButton::~GCButton() = default;
67
68std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::ParamPackage& params) {
69 const int button_id = params.Get("button", 0);
70 const int port = params.Get("port", 0);
71
72 constexpr int PAD_STICK_ID = static_cast<u16>(GCAdapter::PadButton::PAD_STICK);
73
74 // button is not an axis/stick button
75 if (button_id != PAD_STICK_ID) {
76 auto button = std::make_unique<GCButton>(port, button_id, adapter.get());
77 return std::move(button);
78 }
79
80 // For Axis buttons, used by the binary sticks.
81 if (button_id == PAD_STICK_ID) {
82 const int axis = params.Get("axis", 0);
83 const float threshold = params.Get("threshold", 0.25f);
84 const std::string direction_name = params.Get("direction", "");
85 bool trigger_if_greater;
86 if (direction_name == "+") {
87 trigger_if_greater = true;
88 } else if (direction_name == "-") {
89 trigger_if_greater = false;
90 } else {
91 trigger_if_greater = true;
92 LOG_ERROR(Input, "Unknown direction {}", direction_name);
93 }
94 return std::make_unique<GCAxisButton>(port, axis, threshold, trigger_if_greater,
95 adapter.get());
96 }
97}
98
99Common::ParamPackage GCButtonFactory::GetNextInput() {
100 Common::ParamPackage params;
101 GCAdapter::GCPadStatus pad;
102 auto& queue = adapter->GetPadQueue();
103 for (std::size_t port = 0; port < queue.size(); ++port) {
104 while (queue[port].Pop(pad)) {
105 // This while loop will break on the earliest detected button
106 params.Set("engine", "gcpad");
107 params.Set("port", static_cast<int>(port));
108 for (const auto& button : GCAdapter::PadButtonArray) {
109 const u16 button_value = static_cast<u16>(button);
110 if (pad.button & button_value) {
111 params.Set("button", button_value);
112 break;
113 }
114 }
115
116 // For Axis button implementation
117 if (pad.axis != GCAdapter::PadAxes::Undefined) {
118 params.Set("axis", static_cast<u8>(pad.axis));
119 params.Set("button", static_cast<u16>(GCAdapter::PadButton::PAD_STICK));
120 if (pad.axis_value > 128) {
121 params.Set("direction", "+");
122 params.Set("threshold", "0.25");
123 } else {
124 params.Set("direction", "-");
125 params.Set("threshold", "-0.25");
126 }
127 break;
128 }
129 }
130 }
131 return params;
132}
133
134void GCButtonFactory::BeginConfiguration() {
135 polling = true;
136 adapter->BeginConfiguration();
137}
138
139void GCButtonFactory::EndConfiguration() {
140 polling = false;
141 adapter->EndConfiguration();
142}
143
144class GCAnalog final : public Input::AnalogDevice {
145public:
146 GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_, GCAdapter::Adapter* adapter)
147 : port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter) {}
148
149 float GetAxis(int axis) const {
150 std::lock_guard lock{mutex};
151 // division is not by a perfect 128 to account for some variance in center location
152 // e.g. my device idled at 131 in X, 120 in Y, and full range of motion was in range
153 // [20-230]
154 return (gcadapter->GetPadState()[port].axes.at(axis) - 128.0f) / 95.0f;
155 }
156
157 std::pair<float, float> GetAnalog(int axis_x, int axis_y) const {
158 float x = GetAxis(axis_x);
159 float y = GetAxis(axis_y);
160
161 // Make sure the coordinates are in the unit circle,
162 // otherwise normalize it.
163 float r = x * x + y * y;
164 if (r > 1.0f) {
165 r = std::sqrt(r);
166 x /= r;
167 y /= r;
168 }
169
170 return {x, y};
171 }
172
173 std::tuple<float, float> GetStatus() const override {
174 const auto [x, y] = GetAnalog(axis_x, axis_y);
175 const float r = std::sqrt((x * x) + (y * y));
176 if (r > deadzone) {
177 return {x / r * (r - deadzone) / (1 - deadzone),
178 y / r * (r - deadzone) / (1 - deadzone)};
179 }
180 return {0.0f, 0.0f};
181 }
182
183 bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
184 const auto [x, y] = GetStatus();
185 const float directional_deadzone = 0.4f;
186 switch (direction) {
187 case Input::AnalogDirection::RIGHT:
188 return x > directional_deadzone;
189 case Input::AnalogDirection::LEFT:
190 return x < -directional_deadzone;
191 case Input::AnalogDirection::UP:
192 return y > directional_deadzone;
193 case Input::AnalogDirection::DOWN:
194 return y < -directional_deadzone;
195 }
196 return false;
197 }
198
199private:
200 const int port;
201 const int axis_x;
202 const int axis_y;
203 const float deadzone;
204 mutable std::mutex mutex;
205 GCAdapter::Adapter* gcadapter;
206};
207
208/// An analog device factory that creates analog devices from GC Adapter
209GCAnalogFactory::GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
210 : adapter(std::move(adapter_)) {}
211
212/**
213 * Creates analog device from joystick axes
214 * @param params contains parameters for creating the device:
215 * - "port": the nth gcpad on the adapter
216 * - "axis_x": the index of the axis to be bind as x-axis
217 * - "axis_y": the index of the axis to be bind as y-axis
218 */
219std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::ParamPackage& params) {
220 const int port = params.Get("port", 0);
221 const int axis_x = params.Get("axis_x", 0);
222 const int axis_y = params.Get("axis_y", 1);
223 const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f);
224
225 return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone, adapter.get());
226}
227
228void GCAnalogFactory::BeginConfiguration() {
229 polling = true;
230 adapter->BeginConfiguration();
231}
232
233void GCAnalogFactory::EndConfiguration() {
234 polling = false;
235 adapter->EndConfiguration();
236}
237
238Common::ParamPackage GCAnalogFactory::GetNextInput() {
239 GCAdapter::GCPadStatus pad;
240 auto& queue = adapter->GetPadQueue();
241 for (std::size_t port = 0; port < queue.size(); ++port) {
242 while (queue[port].Pop(pad)) {
243 if (pad.axis == GCAdapter::PadAxes::Undefined ||
244 std::abs((pad.axis_value - 128.0f) / 128.0f) < 0.1) {
245 continue;
246 }
247 // An analog device needs two axes, so we need to store the axis for later and wait for
248 // a second input event. The axes also must be from the same joystick.
249 const u8 axis = static_cast<u8>(pad.axis);
250 if (analog_x_axis == -1) {
251 analog_x_axis = axis;
252 controller_number = port;
253 } else if (analog_y_axis == -1 && analog_x_axis != axis && controller_number == port) {
254 analog_y_axis = axis;
255 }
256 }
257 }
258 Common::ParamPackage params;
259 if (analog_x_axis != -1 && analog_y_axis != -1) {
260 params.Set("engine", "gcpad");
261 params.Set("port", controller_number);
262 params.Set("axis_x", analog_x_axis);
263 params.Set("axis_y", analog_y_axis);
264 analog_x_axis = -1;
265 analog_y_axis = -1;
266 controller_number = -1;
267 return params;
268 }
269 return params;
270}
271
272} // 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 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "core/frontend/input.h"
9#include "input_common/gcadapter/gc_adapter.h"
10
11namespace InputCommon {
12
13/**
14 * A button device factory representing a gcpad. It receives gcpad events and forward them
15 * to all button devices it created.
16 */
17class GCButtonFactory final : public Input::Factory<Input::ButtonDevice> {
18public:
19 explicit GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
20
21 /**
22 * Creates a button device from a button press
23 * @param params contains parameters for creating the device:
24 * - "code": the code of the key to bind with the button
25 */
26 std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
27
28 Common::ParamPackage GetNextInput();
29
30 /// For device input configuration/polling
31 void BeginConfiguration();
32 void EndConfiguration();
33
34 bool IsPolling() const {
35 return polling;
36 }
37
38private:
39 std::shared_ptr<GCAdapter::Adapter> adapter;
40 bool polling = false;
41};
42
43/// An analog device factory that creates analog devices from GC Adapter
44class GCAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
45public:
46 explicit GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
47
48 std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
49 Common::ParamPackage GetNextInput();
50
51 /// For device input configuration/polling
52 void BeginConfiguration();
53 void EndConfiguration();
54
55 bool IsPolling() const {
56 return polling;
57 }
58
59private:
60 std::shared_ptr<GCAdapter::Adapter> adapter;
61 int analog_x_axis = -1;
62 int analog_y_axis = -1;
63 int controller_number = -1;
64 bool polling = false;
65};
66
67} // 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 @@
4 4
5#include <memory> 5#include <memory>
6#include <thread> 6#include <thread>
7#include <libusb.h>
7#include "common/param_package.h" 8#include "common/param_package.h"
8#include "input_common/analog_from_button.h" 9#include "input_common/analog_from_button.h"
10#include "input_common/gcadapter/gc_adapter.h"
11#include "input_common/gcadapter/gc_poller.h"
9#include "input_common/keyboard.h" 12#include "input_common/keyboard.h"
10#include "input_common/main.h" 13#include "input_common/main.h"
11#include "input_common/motion_emu.h" 14#include "input_common/motion_emu.h"
@@ -22,8 +25,16 @@ static std::shared_ptr<MotionEmu> motion_emu;
22static std::unique_ptr<SDL::State> sdl; 25static std::unique_ptr<SDL::State> sdl;
23#endif 26#endif
24static std::unique_ptr<CemuhookUDP::State> udp; 27static std::unique_ptr<CemuhookUDP::State> udp;
28static std::shared_ptr<GCButtonFactory> gcbuttons;
29static std::shared_ptr<GCAnalogFactory> gcanalog;
25 30
26void Init() { 31void Init() {
32 auto gcadapter = std::make_shared<GCAdapter::Adapter>();
33 gcbuttons = std::make_shared<GCButtonFactory>(gcadapter);
34 Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons);
35 gcanalog = std::make_shared<GCAnalogFactory>(gcadapter);
36 Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog);
37
27 keyboard = std::make_shared<Keyboard>(); 38 keyboard = std::make_shared<Keyboard>();
28 Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard); 39 Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
29 Input::RegisterFactory<Input::AnalogDevice>("analog_from_button", 40 Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
@@ -48,6 +59,11 @@ void Shutdown() {
48 sdl.reset(); 59 sdl.reset();
49#endif 60#endif
50 udp.reset(); 61 udp.reset();
62 Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
63 Input::UnregisterFactory<Input::AnalogDevice>("gcpad");
64
65 gcbuttons.reset();
66 gcanalog.reset();
51} 67}
52 68
53Keyboard* GetKeyboard() { 69Keyboard* GetKeyboard() {
@@ -58,6 +74,14 @@ MotionEmu* GetMotionEmu() {
58 return motion_emu.get(); 74 return motion_emu.get();
59} 75}
60 76
77GCButtonFactory* GetGCButtons() {
78 return gcbuttons.get();
79}
80
81GCAnalogFactory* GetGCAnalogs() {
82 return gcanalog.get();
83}
84
61std::string GenerateKeyboardParam(int key_code) { 85std::string GenerateKeyboardParam(int key_code) {
62 Common::ParamPackage param{ 86 Common::ParamPackage param{
63 {"engine", "keyboard"}, 87 {"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 @@
7#include <memory> 7#include <memory>
8#include <string> 8#include <string>
9#include <vector> 9#include <vector>
10#include "input_common/gcadapter/gc_poller.h"
10 11
11namespace Common { 12namespace Common {
12class ParamPackage; 13class ParamPackage;
@@ -30,6 +31,10 @@ class MotionEmu;
30/// Gets the motion emulation factory. 31/// Gets the motion emulation factory.
31MotionEmu* GetMotionEmu(); 32MotionEmu* GetMotionEmu();
32 33
34GCButtonFactory* GetGCButtons();
35
36GCAnalogFactory* GetGCAnalogs();
37
33/// Generates a serialized param package for creating a keyboard button device 38/// Generates a serialized param package for creating a keyboard button device
34std::string GenerateKeyboardParam(int key_code); 39std::string GenerateKeyboardParam(int key_code);
35 40
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:
322 } 322 }
323 323
324private: 324private:
325 MapInterval* MapAddress(const Buffer* block, GPUVAddr gpu_addr, VAddr cpu_addr, 325 MapInterval* MapAddress(Buffer* block, GPUVAddr gpu_addr, VAddr cpu_addr, std::size_t size) {
326 std::size_t size) {
327 const VectorMapInterval overlaps = GetMapsInRange(cpu_addr, size); 326 const VectorMapInterval overlaps = GetMapsInRange(cpu_addr, size);
328 if (overlaps.empty()) { 327 if (overlaps.empty()) {
329 auto& memory_manager = system.GPU().MemoryManager(); 328 auto& memory_manager = system.GPU().MemoryManager();
@@ -377,8 +376,7 @@ private:
377 return map; 376 return map;
378 } 377 }
379 378
380 void UpdateBlock(const Buffer* block, VAddr start, VAddr end, 379 void UpdateBlock(Buffer* block, VAddr start, VAddr end, const VectorMapInterval& overlaps) {
381 const VectorMapInterval& overlaps) {
382 const IntervalType base_interval{start, end}; 380 const IntervalType base_interval{start, end};
383 IntervalSet interval_set{}; 381 IntervalSet interval_set{};
384 interval_set.add(base_interval); 382 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)
34 34
35Buffer::~Buffer() = default; 35Buffer::~Buffer() = default;
36 36
37void Buffer::Upload(std::size_t offset, std::size_t size, const u8* data) const { 37void Buffer::Upload(std::size_t offset, std::size_t size, const u8* data) {
38 glNamedBufferSubData(Handle(), static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size), 38 glNamedBufferSubData(Handle(), static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size),
39 data); 39 data);
40} 40}
41 41
42void Buffer::Download(std::size_t offset, std::size_t size, u8* data) const { 42void Buffer::Download(std::size_t offset, std::size_t size, u8* data) {
43 MICROPROFILE_SCOPE(OpenGL_Buffer_Download); 43 MICROPROFILE_SCOPE(OpenGL_Buffer_Download);
44 const GLsizeiptr gl_size = static_cast<GLsizeiptr>(size);
45 const GLintptr gl_offset = static_cast<GLintptr>(offset);
46 if (read_buffer.handle == 0) {
47 read_buffer.Create();
48 glNamedBufferData(read_buffer.handle, static_cast<GLsizeiptr>(Size()), nullptr,
49 GL_STREAM_READ);
50 }
44 glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT); 51 glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
45 glGetNamedBufferSubData(Handle(), static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size), 52 glCopyNamedBufferSubData(gl_buffer.handle, read_buffer.handle, gl_offset, gl_offset, gl_size);
46 data); 53 glGetNamedBufferSubData(read_buffer.handle, gl_offset, gl_size, data);
47} 54}
48 55
49void Buffer::CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset, 56void Buffer::CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset,
50 std::size_t size) const { 57 std::size_t size) {
51 glCopyNamedBufferSubData(src.Handle(), Handle(), static_cast<GLintptr>(src_offset), 58 glCopyNamedBufferSubData(src.Handle(), Handle(), static_cast<GLintptr>(src_offset),
52 static_cast<GLintptr>(dst_offset), static_cast<GLsizeiptr>(size)); 59 static_cast<GLintptr>(dst_offset), static_cast<GLsizeiptr>(size));
53} 60}
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:
28 explicit Buffer(const Device& device, VAddr cpu_addr, std::size_t size); 28 explicit Buffer(const Device& device, VAddr cpu_addr, std::size_t size);
29 ~Buffer(); 29 ~Buffer();
30 30
31 void Upload(std::size_t offset, std::size_t size, const u8* data) const; 31 void Upload(std::size_t offset, std::size_t size, const u8* data);
32 32
33 void Download(std::size_t offset, std::size_t size, u8* data) const; 33 void Download(std::size_t offset, std::size_t size, u8* data);
34 34
35 void CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset, 35 void CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset,
36 std::size_t size) const; 36 std::size_t size);
37 37
38 GLuint Handle() const noexcept { 38 GLuint Handle() const noexcept {
39 return gl_buffer.handle; 39 return gl_buffer.handle;
@@ -45,6 +45,7 @@ public:
45 45
46private: 46private:
47 OGLBuffer gl_buffer; 47 OGLBuffer gl_buffer;
48 OGLBuffer read_buffer;
48 u64 gpu_address = 0; 49 u64 gpu_address = 0;
49}; 50};
50 51
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) {
191 } else { 191 } else {
192 return GL_MIRROR_CLAMP_TO_EDGE; 192 return GL_MIRROR_CLAMP_TO_EDGE;
193 } 193 }
194 case Tegra::Texture::WrapMode::MirrorOnceClampOGL:
195 if (GL_EXT_texture_mirror_clamp) {
196 return GL_MIRROR_CLAMP_EXT;
197 } else {
198 return GL_MIRROR_CLAMP_TO_EDGE;
199 }
194 } 200 }
195 UNIMPLEMENTED_MSG("Unimplemented texture wrap mode={}", static_cast<u32>(wrap_mode)); 201 UNIMPLEMENTED_MSG("Unimplemented texture wrap mode={}", static_cast<u32>(wrap_mode));
196 return GL_REPEAT; 202 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 = {
39 39
40} // Anonymous namespace 40} // Anonymous namespace
41 41
42void FixedPipelineState::DepthStencil::Fill(const Maxwell& regs) noexcept { 42void FixedPipelineState::Fill(const Maxwell& regs, bool has_extended_dynamic_state) {
43 raw = 0;
44 front.action_stencil_fail.Assign(PackStencilOp(regs.stencil_front_op_fail));
45 front.action_depth_fail.Assign(PackStencilOp(regs.stencil_front_op_zfail));
46 front.action_depth_pass.Assign(PackStencilOp(regs.stencil_front_op_zpass));
47 front.test_func.Assign(PackComparisonOp(regs.stencil_front_func_func));
48 if (regs.stencil_two_side_enable) {
49 back.action_stencil_fail.Assign(PackStencilOp(regs.stencil_back_op_fail));
50 back.action_depth_fail.Assign(PackStencilOp(regs.stencil_back_op_zfail));
51 back.action_depth_pass.Assign(PackStencilOp(regs.stencil_back_op_zpass));
52 back.test_func.Assign(PackComparisonOp(regs.stencil_back_func_func));
53 } else {
54 back.action_stencil_fail.Assign(front.action_stencil_fail);
55 back.action_depth_fail.Assign(front.action_depth_fail);
56 back.action_depth_pass.Assign(front.action_depth_pass);
57 back.test_func.Assign(front.test_func);
58 }
59 depth_test_enable.Assign(regs.depth_test_enable);
60 depth_write_enable.Assign(regs.depth_write_enabled);
61 depth_bounds_enable.Assign(regs.depth_bounds_enable);
62 stencil_enable.Assign(regs.stencil_enable);
63 depth_test_func.Assign(PackComparisonOp(regs.depth_test_func));
64}
65
66void FixedPipelineState::Rasterizer::Fill(const Maxwell& regs) noexcept {
67 const auto& clip = regs.view_volume_clip_control; 43 const auto& clip = regs.view_volume_clip_control;
68 const std::array enabled_lut = {regs.polygon_offset_point_enable, 44 const std::array enabled_lut = {regs.polygon_offset_point_enable,
69 regs.polygon_offset_line_enable, 45 regs.polygon_offset_line_enable,
70 regs.polygon_offset_fill_enable}; 46 regs.polygon_offset_fill_enable};
71 const u32 topology_index = static_cast<u32>(regs.draw.topology.Value()); 47 const u32 topology_index = static_cast<u32>(regs.draw.topology.Value());
72 48
73 u32 packed_front_face = PackFrontFace(regs.front_face);
74 if (regs.screen_y_control.triangle_rast_flip != 0) {
75 // Flip front face
76 packed_front_face = 1 - packed_front_face;
77 }
78
79 raw = 0; 49 raw = 0;
80 topology.Assign(topology_index);
81 primitive_restart_enable.Assign(regs.primitive_restart.enabled != 0 ? 1 : 0); 50 primitive_restart_enable.Assign(regs.primitive_restart.enabled != 0 ? 1 : 0);
82 cull_enable.Assign(regs.cull_test_enabled != 0 ? 1 : 0);
83 depth_bias_enable.Assign(enabled_lut[POLYGON_OFFSET_ENABLE_LUT[topology_index]] != 0 ? 1 : 0); 51 depth_bias_enable.Assign(enabled_lut[POLYGON_OFFSET_ENABLE_LUT[topology_index]] != 0 ? 1 : 0);
84 depth_clamp_disabled.Assign(regs.view_volume_clip_control.depth_clamp_disabled.Value()); 52 depth_clamp_disabled.Assign(regs.view_volume_clip_control.depth_clamp_disabled.Value());
85 ndc_minus_one_to_one.Assign(regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1 : 0); 53 ndc_minus_one_to_one.Assign(regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1 : 0);
86 cull_face.Assign(PackCullFace(regs.cull_face));
87 front_face.Assign(packed_front_face);
88 polygon_mode.Assign(PackPolygonMode(regs.polygon_mode_front)); 54 polygon_mode.Assign(PackPolygonMode(regs.polygon_mode_front));
89 patch_control_points_minus_one.Assign(regs.patch_vertices - 1); 55 patch_control_points_minus_one.Assign(regs.patch_vertices - 1);
90 tessellation_primitive.Assign(static_cast<u32>(regs.tess_mode.prim.Value())); 56 tessellation_primitive.Assign(static_cast<u32>(regs.tess_mode.prim.Value()));
@@ -93,19 +59,37 @@ void FixedPipelineState::Rasterizer::Fill(const Maxwell& regs) noexcept {
93 logic_op_enable.Assign(regs.logic_op.enable != 0 ? 1 : 0); 59 logic_op_enable.Assign(regs.logic_op.enable != 0 ? 1 : 0);
94 logic_op.Assign(PackLogicOp(regs.logic_op.operation)); 60 logic_op.Assign(PackLogicOp(regs.logic_op.operation));
95 rasterize_enable.Assign(regs.rasterize_enable != 0 ? 1 : 0); 61 rasterize_enable.Assign(regs.rasterize_enable != 0 ? 1 : 0);
62
96 std::memcpy(&point_size, &regs.point_size, sizeof(point_size)); // TODO: C++20 std::bit_cast 63 std::memcpy(&point_size, &regs.point_size, sizeof(point_size)); // TODO: C++20 std::bit_cast
97}
98 64
99void FixedPipelineState::ColorBlending::Fill(const Maxwell& regs) noexcept { 65 for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
66 binding_divisors[index] =
67 regs.instanced_arrays.IsInstancingEnabled(index) ? regs.vertex_array[index].divisor : 0;
68 }
69
70 for (std::size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {
71 const auto& input = regs.vertex_attrib_format[index];
72 auto& attribute = attributes[index];
73 attribute.raw = 0;
74 attribute.enabled.Assign(input.IsConstant() ? 0 : 1);
75 attribute.buffer.Assign(input.buffer);
76 attribute.offset.Assign(input.offset);
77 attribute.type.Assign(static_cast<u32>(input.type.Value()));
78 attribute.size.Assign(static_cast<u32>(input.size.Value()));
79 }
80
100 for (std::size_t index = 0; index < std::size(attachments); ++index) { 81 for (std::size_t index = 0; index < std::size(attachments); ++index) {
101 attachments[index].Fill(regs, index); 82 attachments[index].Fill(regs, index);
102 } 83 }
103}
104 84
105void FixedPipelineState::ViewportSwizzles::Fill(const Maxwell& regs) noexcept {
106 const auto& transform = regs.viewport_transform; 85 const auto& transform = regs.viewport_transform;
107 std::transform(transform.begin(), transform.end(), swizzles.begin(), 86 std::transform(transform.begin(), transform.end(), viewport_swizzles.begin(),
108 [](const auto& viewport) { return static_cast<u16>(viewport.swizzle.raw); }); 87 [](const auto& viewport) { return static_cast<u16>(viewport.swizzle.raw); });
88
89 if (!has_extended_dynamic_state) {
90 no_extended_dynamic_state.Assign(1);
91 dynamic_state.Fill(regs);
92 }
109} 93}
110 94
111void FixedPipelineState::BlendingAttachment::Fill(const Maxwell& regs, std::size_t index) { 95void FixedPipelineState::BlendingAttachment::Fill(const Maxwell& regs, std::size_t index) {
@@ -147,20 +131,57 @@ void FixedPipelineState::BlendingAttachment::Fill(const Maxwell& regs, std::size
147 enable.Assign(1); 131 enable.Assign(1);
148} 132}
149 133
150void FixedPipelineState::Fill(const Maxwell& regs) { 134void FixedPipelineState::DynamicState::Fill(const Maxwell& regs) {
151 rasterizer.Fill(regs); 135 const u32 topology_index = static_cast<u32>(regs.draw.topology.Value());
152 depth_stencil.Fill(regs); 136 u32 packed_front_face = PackFrontFace(regs.front_face);
153 color_blending.Fill(regs); 137 if (regs.screen_y_control.triangle_rast_flip != 0) {
154 viewport_swizzles.Fill(regs); 138 // Flip front face
139 packed_front_face = 1 - packed_front_face;
140 }
141
142 raw1 = 0;
143 raw2 = 0;
144 front.action_stencil_fail.Assign(PackStencilOp(regs.stencil_front_op_fail));
145 front.action_depth_fail.Assign(PackStencilOp(regs.stencil_front_op_zfail));
146 front.action_depth_pass.Assign(PackStencilOp(regs.stencil_front_op_zpass));
147 front.test_func.Assign(PackComparisonOp(regs.stencil_front_func_func));
148 if (regs.stencil_two_side_enable) {
149 back.action_stencil_fail.Assign(PackStencilOp(regs.stencil_back_op_fail));
150 back.action_depth_fail.Assign(PackStencilOp(regs.stencil_back_op_zfail));
151 back.action_depth_pass.Assign(PackStencilOp(regs.stencil_back_op_zpass));
152 back.test_func.Assign(PackComparisonOp(regs.stencil_back_func_func));
153 } else {
154 back.action_stencil_fail.Assign(front.action_stencil_fail);
155 back.action_depth_fail.Assign(front.action_depth_fail);
156 back.action_depth_pass.Assign(front.action_depth_pass);
157 back.test_func.Assign(front.test_func);
158 }
159 stencil_enable.Assign(regs.stencil_enable);
160 depth_write_enable.Assign(regs.depth_write_enabled);
161 depth_bounds_enable.Assign(regs.depth_bounds_enable);
162 depth_test_enable.Assign(regs.depth_test_enable);
163 front_face.Assign(packed_front_face);
164 depth_test_func.Assign(PackComparisonOp(regs.depth_test_func));
165 topology.Assign(topology_index);
166 cull_face.Assign(PackCullFace(regs.cull_face));
167 cull_enable.Assign(regs.cull_test_enabled != 0 ? 1 : 0);
168
169 for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
170 const auto& input = regs.vertex_array[index];
171 VertexBinding& binding = vertex_bindings[index];
172 binding.raw = 0;
173 binding.enabled.Assign(input.IsEnabled() ? 1 : 0);
174 binding.stride.Assign(static_cast<u16>(input.stride.Value()));
175 }
155} 176}
156 177
157std::size_t FixedPipelineState::Hash() const noexcept { 178std::size_t FixedPipelineState::Hash() const noexcept {
158 const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), sizeof *this); 179 const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), Size());
159 return static_cast<std::size_t>(hash); 180 return static_cast<std::size_t>(hash);
160} 181}
161 182
162bool FixedPipelineState::operator==(const FixedPipelineState& rhs) const noexcept { 183bool FixedPipelineState::operator==(const FixedPipelineState& rhs) const noexcept {
163 return std::memcmp(this, &rhs, sizeof *this) == 0; 184 return std::memcmp(this, &rhs, Size()) == 0;
164} 185}
165 186
166u32 FixedPipelineState::PackComparisonOp(Maxwell::ComparisonOp op) noexcept { 187u32 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 {
60 60
61 void Fill(const Maxwell& regs, std::size_t index); 61 void Fill(const Maxwell& regs, std::size_t index);
62 62
63 std::size_t Hash() const noexcept;
64
65 bool operator==(const BlendingAttachment& rhs) const noexcept;
66
67 bool operator!=(const BlendingAttachment& rhs) const noexcept {
68 return !operator==(rhs);
69 }
70
71 constexpr std::array<bool, 4> Mask() const noexcept { 63 constexpr std::array<bool, 4> Mask() const noexcept {
72 return {mask_r != 0, mask_g != 0, mask_b != 0, mask_a != 0}; 64 return {mask_r != 0, mask_g != 0, mask_b != 0, mask_a != 0};
73 } 65 }
@@ -97,156 +89,116 @@ struct FixedPipelineState {
97 } 89 }
98 }; 90 };
99 91
100 struct VertexInput { 92 union VertexAttribute {
101 union Binding { 93 u32 raw;
102 u16 raw; 94 BitField<0, 1, u32> enabled;
103 BitField<0, 1, u16> enabled; 95 BitField<1, 5, u32> buffer;
104 BitField<1, 12, u16> stride; 96 BitField<6, 14, u32> offset;
105 }; 97 BitField<20, 3, u32> type;
98 BitField<23, 6, u32> size;
106 99
107 union Attribute { 100 constexpr Maxwell::VertexAttribute::Type Type() const noexcept {
108 u32 raw; 101 return static_cast<Maxwell::VertexAttribute::Type>(type.Value());
109 BitField<0, 1, u32> enabled;
110 BitField<1, 5, u32> buffer;
111 BitField<6, 14, u32> offset;
112 BitField<20, 3, u32> type;
113 BitField<23, 6, u32> size;
114
115 constexpr Maxwell::VertexAttribute::Type Type() const noexcept {
116 return static_cast<Maxwell::VertexAttribute::Type>(type.Value());
117 }
118
119 constexpr Maxwell::VertexAttribute::Size Size() const noexcept {
120 return static_cast<Maxwell::VertexAttribute::Size>(size.Value());
121 }
122 };
123
124 std::array<Binding, Maxwell::NumVertexArrays> bindings;
125 std::array<u32, Maxwell::NumVertexArrays> binding_divisors;
126 std::array<Attribute, Maxwell::NumVertexAttributes> attributes;
127
128 void SetBinding(std::size_t index, bool enabled, u32 stride, u32 divisor) noexcept {
129 auto& binding = bindings[index];
130 binding.raw = 0;
131 binding.enabled.Assign(enabled ? 1 : 0);
132 binding.stride.Assign(static_cast<u16>(stride));
133 binding_divisors[index] = divisor;
134 } 102 }
135 103
136 void SetAttribute(std::size_t index, bool enabled, u32 buffer, u32 offset, 104 constexpr Maxwell::VertexAttribute::Size Size() const noexcept {
137 Maxwell::VertexAttribute::Type type, 105 return static_cast<Maxwell::VertexAttribute::Size>(size.Value());
138 Maxwell::VertexAttribute::Size size) noexcept {
139 auto& attribute = attributes[index];
140 attribute.raw = 0;
141 attribute.enabled.Assign(enabled ? 1 : 0);
142 attribute.buffer.Assign(buffer);
143 attribute.offset.Assign(offset);
144 attribute.type.Assign(static_cast<u32>(type));
145 attribute.size.Assign(static_cast<u32>(size));
146 } 106 }
147 }; 107 };
148 108
149 struct Rasterizer { 109 template <std::size_t Position>
150 union { 110 union StencilFace {
151 u32 raw; 111 BitField<Position + 0, 3, u32> action_stencil_fail;
152 BitField<0, 4, u32> topology; 112 BitField<Position + 3, 3, u32> action_depth_fail;
153 BitField<4, 1, u32> primitive_restart_enable; 113 BitField<Position + 6, 3, u32> action_depth_pass;
154 BitField<5, 1, u32> cull_enable; 114 BitField<Position + 9, 3, u32> test_func;
155 BitField<6, 1, u32> depth_bias_enable;
156 BitField<7, 1, u32> depth_clamp_disabled;
157 BitField<8, 1, u32> ndc_minus_one_to_one;
158 BitField<9, 2, u32> cull_face;
159 BitField<11, 1, u32> front_face;
160 BitField<12, 2, u32> polygon_mode;
161 BitField<14, 5, u32> patch_control_points_minus_one;
162 BitField<19, 2, u32> tessellation_primitive;
163 BitField<21, 2, u32> tessellation_spacing;
164 BitField<23, 1, u32> tessellation_clockwise;
165 BitField<24, 1, u32> logic_op_enable;
166 BitField<25, 4, u32> logic_op;
167 BitField<29, 1, u32> rasterize_enable;
168 };
169
170 // TODO(Rodrigo): Move this to push constants
171 u32 point_size;
172 115
173 void Fill(const Maxwell& regs) noexcept; 116 Maxwell::StencilOp ActionStencilFail() const noexcept {
117 return UnpackStencilOp(action_stencil_fail);
118 }
174 119
175 constexpr Maxwell::PrimitiveTopology Topology() const noexcept { 120 Maxwell::StencilOp ActionDepthFail() const noexcept {
176 return static_cast<Maxwell::PrimitiveTopology>(topology.Value()); 121 return UnpackStencilOp(action_depth_fail);
177 } 122 }
178 123
179 Maxwell::CullFace CullFace() const noexcept { 124 Maxwell::StencilOp ActionDepthPass() const noexcept {
180 return UnpackCullFace(cull_face.Value()); 125 return UnpackStencilOp(action_depth_pass);
181 } 126 }
182 127
183 Maxwell::FrontFace FrontFace() const noexcept { 128 Maxwell::ComparisonOp TestFunc() const noexcept {
184 return UnpackFrontFace(front_face.Value()); 129 return UnpackComparisonOp(test_func);
185 } 130 }
186 }; 131 };
187 132
188 struct DepthStencil { 133 union VertexBinding {
189 template <std::size_t Position> 134 u16 raw;
190 union StencilFace { 135 BitField<0, 12, u16> stride;
191 BitField<Position + 0, 3, u32> action_stencil_fail; 136 BitField<12, 1, u16> enabled;
192 BitField<Position + 3, 3, u32> action_depth_fail; 137 };
193 BitField<Position + 6, 3, u32> action_depth_pass;
194 BitField<Position + 9, 3, u32> test_func;
195
196 Maxwell::StencilOp ActionStencilFail() const noexcept {
197 return UnpackStencilOp(action_stencil_fail);
198 }
199
200 Maxwell::StencilOp ActionDepthFail() const noexcept {
201 return UnpackStencilOp(action_depth_fail);
202 }
203
204 Maxwell::StencilOp ActionDepthPass() const noexcept {
205 return UnpackStencilOp(action_depth_pass);
206 }
207
208 Maxwell::ComparisonOp TestFunc() const noexcept {
209 return UnpackComparisonOp(test_func);
210 }
211 };
212 138
139 struct DynamicState {
213 union { 140 union {
214 u32 raw; 141 u32 raw1;
215 StencilFace<0> front; 142 StencilFace<0> front;
216 StencilFace<12> back; 143 StencilFace<12> back;
217 BitField<24, 1, u32> depth_test_enable; 144 BitField<24, 1, u32> stencil_enable;
218 BitField<25, 1, u32> depth_write_enable; 145 BitField<25, 1, u32> depth_write_enable;
219 BitField<26, 1, u32> depth_bounds_enable; 146 BitField<26, 1, u32> depth_bounds_enable;
220 BitField<27, 1, u32> stencil_enable; 147 BitField<27, 1, u32> depth_test_enable;
221 BitField<28, 3, u32> depth_test_func; 148 BitField<28, 1, u32> front_face;
149 BitField<29, 3, u32> depth_test_func;
150 };
151 union {
152 u32 raw2;
153 BitField<0, 4, u32> topology;
154 BitField<4, 2, u32> cull_face;
155 BitField<6, 1, u32> cull_enable;
222 }; 156 };
157 std::array<VertexBinding, Maxwell::NumVertexArrays> vertex_bindings;
223 158
224 void Fill(const Maxwell& regs) noexcept; 159 void Fill(const Maxwell& regs);
225 160
226 Maxwell::ComparisonOp DepthTestFunc() const noexcept { 161 Maxwell::ComparisonOp DepthTestFunc() const noexcept {
227 return UnpackComparisonOp(depth_test_func); 162 return UnpackComparisonOp(depth_test_func);
228 } 163 }
229 };
230
231 struct ColorBlending {
232 std::array<BlendingAttachment, Maxwell::NumRenderTargets> attachments;
233 164
234 void Fill(const Maxwell& regs) noexcept; 165 Maxwell::CullFace CullFace() const noexcept {
235 }; 166 return UnpackCullFace(cull_face.Value());
167 }
236 168
237 struct ViewportSwizzles { 169 Maxwell::FrontFace FrontFace() const noexcept {
238 std::array<u16, Maxwell::NumViewports> swizzles; 170 return UnpackFrontFace(front_face.Value());
171 }
239 172
240 void Fill(const Maxwell& regs) noexcept; 173 constexpr Maxwell::PrimitiveTopology Topology() const noexcept {
174 return static_cast<Maxwell::PrimitiveTopology>(topology.Value());
175 }
241 }; 176 };
242 177
243 VertexInput vertex_input; 178 union {
244 Rasterizer rasterizer; 179 u32 raw;
245 DepthStencil depth_stencil; 180 BitField<0, 1, u32> no_extended_dynamic_state;
246 ColorBlending color_blending; 181 BitField<2, 1, u32> primitive_restart_enable;
247 ViewportSwizzles viewport_swizzles; 182 BitField<3, 1, u32> depth_bias_enable;
183 BitField<4, 1, u32> depth_clamp_disabled;
184 BitField<5, 1, u32> ndc_minus_one_to_one;
185 BitField<6, 2, u32> polygon_mode;
186 BitField<8, 5, u32> patch_control_points_minus_one;
187 BitField<13, 2, u32> tessellation_primitive;
188 BitField<15, 2, u32> tessellation_spacing;
189 BitField<17, 1, u32> tessellation_clockwise;
190 BitField<18, 1, u32> logic_op_enable;
191 BitField<19, 4, u32> logic_op;
192 BitField<23, 1, u32> rasterize_enable;
193 };
194 u32 point_size;
195 std::array<u32, Maxwell::NumVertexArrays> binding_divisors;
196 std::array<VertexAttribute, Maxwell::NumVertexAttributes> attributes;
197 std::array<BlendingAttachment, Maxwell::NumRenderTargets> attachments;
198 std::array<u16, Maxwell::NumViewports> viewport_swizzles;
199 DynamicState dynamic_state;
248 200
249 void Fill(const Maxwell& regs); 201 void Fill(const Maxwell& regs, bool has_extended_dynamic_state);
250 202
251 std::size_t Hash() const noexcept; 203 std::size_t Hash() const noexcept;
252 204
@@ -255,6 +207,11 @@ struct FixedPipelineState {
255 bool operator!=(const FixedPipelineState& rhs) const noexcept { 207 bool operator!=(const FixedPipelineState& rhs) const noexcept {
256 return !operator==(rhs); 208 return !operator==(rhs);
257 } 209 }
210
211 std::size_t Size() const noexcept {
212 const std::size_t total_size = sizeof *this;
213 return total_size - (no_extended_dynamic_state != 0 ? 0 : sizeof(DynamicState));
214 }
258}; 215};
259static_assert(std::has_unique_object_representations_v<FixedPipelineState>); 216static_assert(std::has_unique_object_representations_v<FixedPipelineState>);
260static_assert(std::is_trivially_copyable_v<FixedPipelineState>); 217static_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
56 56
57Buffer::~Buffer() = default; 57Buffer::~Buffer() = default;
58 58
59void Buffer::Upload(std::size_t offset, std::size_t size, const u8* data) const { 59void Buffer::Upload(std::size_t offset, std::size_t size, const u8* data) {
60 const auto& staging = staging_pool.GetUnusedBuffer(size, true); 60 const auto& staging = staging_pool.GetUnusedBuffer(size, true);
61 std::memcpy(staging.commit->Map(size), data, size); 61 std::memcpy(staging.commit->Map(size), data, size);
62 62
@@ -81,7 +81,7 @@ void Buffer::Upload(std::size_t offset, std::size_t size, const u8* data) const
81 }); 81 });
82} 82}
83 83
84void Buffer::Download(std::size_t offset, std::size_t size, u8* data) const { 84void Buffer::Download(std::size_t offset, std::size_t size, u8* data) {
85 const auto& staging = staging_pool.GetUnusedBuffer(size, true); 85 const auto& staging = staging_pool.GetUnusedBuffer(size, true);
86 scheduler.RequestOutsideRenderPassOperationContext(); 86 scheduler.RequestOutsideRenderPassOperationContext();
87 87
@@ -110,7 +110,7 @@ void Buffer::Download(std::size_t offset, std::size_t size, u8* data) const {
110} 110}
111 111
112void Buffer::CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset, 112void Buffer::CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset,
113 std::size_t size) const { 113 std::size_t size) {
114 scheduler.RequestOutsideRenderPassOperationContext(); 114 scheduler.RequestOutsideRenderPassOperationContext();
115 115
116 const VkBuffer dst_buffer = Handle(); 116 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:
29 VKStagingBufferPool& staging_pool, VAddr cpu_addr, std::size_t size); 29 VKStagingBufferPool& staging_pool, VAddr cpu_addr, std::size_t size);
30 ~Buffer(); 30 ~Buffer();
31 31
32 void Upload(std::size_t offset, std::size_t size, const u8* data) const; 32 void Upload(std::size_t offset, std::size_t size, const u8* data);
33 33
34 void Download(std::size_t offset, std::size_t size, u8* data) const; 34 void Download(std::size_t offset, std::size_t size, u8* data);
35 35
36 void CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset, 36 void CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset,
37 std::size_t size) const; 37 std::size_t size);
38 38
39 VkBuffer Handle() const { 39 VkBuffer Handle() const {
40 return *buffer.handle; 40 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() {
313 LOG_INFO(Render_Vulkan, "Device doesn't support custom border colors"); 313 LOG_INFO(Render_Vulkan, "Device doesn't support custom border colors");
314 } 314 }
315 315
316 VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynamic_state;
317 if (ext_extended_dynamic_state) {
318 dynamic_state.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT;
319 dynamic_state.pNext = nullptr;
320 dynamic_state.extendedDynamicState = VK_TRUE;
321 SetNext(next, dynamic_state);
322 } else {
323 LOG_INFO(Render_Vulkan, "Device doesn't support extended dynamic state");
324 }
325
316 if (!ext_depth_range_unrestricted) { 326 if (!ext_depth_range_unrestricted) {
317 LOG_INFO(Render_Vulkan, "Device doesn't support depth range unrestricted"); 327 LOG_INFO(Render_Vulkan, "Device doesn't support depth range unrestricted");
318 } 328 }
@@ -541,6 +551,7 @@ std::vector<const char*> VKDevice::LoadExtensions() {
541 bool has_ext_subgroup_size_control{}; 551 bool has_ext_subgroup_size_control{};
542 bool has_ext_transform_feedback{}; 552 bool has_ext_transform_feedback{};
543 bool has_ext_custom_border_color{}; 553 bool has_ext_custom_border_color{};
554 bool has_ext_extended_dynamic_state{};
544 for (const auto& extension : physical.EnumerateDeviceExtensionProperties()) { 555 for (const auto& extension : physical.EnumerateDeviceExtensionProperties()) {
545 Test(extension, nv_viewport_swizzle, VK_NV_VIEWPORT_SWIZZLE_EXTENSION_NAME, true); 556 Test(extension, nv_viewport_swizzle, VK_NV_VIEWPORT_SWIZZLE_EXTENSION_NAME, true);
546 Test(extension, khr_uniform_buffer_standard_layout, 557 Test(extension, khr_uniform_buffer_standard_layout,
@@ -558,6 +569,8 @@ std::vector<const char*> VKDevice::LoadExtensions() {
558 false); 569 false);
559 Test(extension, has_ext_custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME, 570 Test(extension, has_ext_custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME,
560 false); 571 false);
572 Test(extension, has_ext_extended_dynamic_state,
573 VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME, false);
561 if (Settings::values.renderer_debug) { 574 if (Settings::values.renderer_debug) {
562 Test(extension, nv_device_diagnostics_config, 575 Test(extension, nv_device_diagnostics_config,
563 VK_NV_DEVICE_DIAGNOSTICS_CONFIG_EXTENSION_NAME, true); 576 VK_NV_DEVICE_DIAGNOSTICS_CONFIG_EXTENSION_NAME, true);
@@ -643,6 +656,19 @@ std::vector<const char*> VKDevice::LoadExtensions() {
643 } 656 }
644 } 657 }
645 658
659 if (has_ext_extended_dynamic_state) {
660 VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynamic_state;
661 dynamic_state.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT;
662 dynamic_state.pNext = nullptr;
663 features.pNext = &dynamic_state;
664 physical.GetFeatures2KHR(features);
665
666 if (dynamic_state.extendedDynamicState) {
667 extensions.push_back(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
668 ext_extended_dynamic_state = true;
669 }
670 }
671
646 return extensions; 672 return extensions;
647} 673}
648 674
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:
182 return ext_custom_border_color; 182 return ext_custom_border_color;
183 } 183 }
184 184
185 /// Returns true if the device supports VK_EXT_extended_dynamic_state.
186 bool IsExtExtendedDynamicStateSupported() const {
187 return ext_extended_dynamic_state;
188 }
189
185 /// Returns the vendor name reported from Vulkan. 190 /// Returns the vendor name reported from Vulkan.
186 std::string_view GetVendorName() const { 191 std::string_view GetVendorName() const {
187 return vendor_name; 192 return vendor_name;
@@ -239,6 +244,7 @@ private:
239 bool ext_shader_viewport_index_layer{}; ///< Support for VK_EXT_shader_viewport_index_layer. 244 bool ext_shader_viewport_index_layer{}; ///< Support for VK_EXT_shader_viewport_index_layer.
240 bool ext_transform_feedback{}; ///< Support for VK_EXT_transform_feedback. 245 bool ext_transform_feedback{}; ///< Support for VK_EXT_transform_feedback.
241 bool ext_custom_border_color{}; ///< Support for VK_EXT_custom_border_color. 246 bool ext_custom_border_color{}; ///< Support for VK_EXT_custom_border_color.
247 bool ext_extended_dynamic_state{}; ///< Support for VK_EXT_extended_dynamic_state.
242 bool nv_device_diagnostics_config{}; ///< Support for VK_NV_device_diagnostics_config. 248 bool nv_device_diagnostics_config{}; ///< Support for VK_NV_device_diagnostics_config.
243 249
244 // Telemetry parameters 250 // 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(
176 176
177vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpass_params, 177vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpass_params,
178 const SPIRVProgram& program) const { 178 const SPIRVProgram& program) const {
179 const auto& vi = fixed_state.vertex_input; 179 const auto& state = fixed_state;
180 const auto& ds = fixed_state.depth_stencil; 180 const auto& viewport_swizzles = state.viewport_swizzles;
181 const auto& cd = fixed_state.color_blending; 181
182 const auto& rs = fixed_state.rasterizer; 182 FixedPipelineState::DynamicState dynamic;
183 const auto& viewport_swizzles = fixed_state.viewport_swizzles.swizzles; 183 if (device.IsExtExtendedDynamicStateSupported()) {
184 // Insert dummy values, as long as they are valid they don't matter as extended dynamic
185 // state is ignored
186 dynamic.raw1 = 0;
187 dynamic.raw2 = 0;
188 for (FixedPipelineState::VertexBinding& binding : dynamic.vertex_bindings) {
189 // Enable all vertex bindings
190 binding.raw = 0;
191 binding.enabled.Assign(1);
192 }
193 } else {
194 dynamic = state.dynamic_state;
195 }
184 196
185 std::vector<VkVertexInputBindingDescription> vertex_bindings; 197 std::vector<VkVertexInputBindingDescription> vertex_bindings;
186 std::vector<VkVertexInputBindingDivisorDescriptionEXT> vertex_binding_divisors; 198 std::vector<VkVertexInputBindingDivisorDescriptionEXT> vertex_binding_divisors;
187 for (std::size_t index = 0; index < std::size(vi.bindings); ++index) { 199 for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
188 const auto& binding = vi.bindings[index]; 200 const auto& binding = dynamic.vertex_bindings[index];
189 if (!binding.enabled) { 201 if (!binding.enabled) {
190 continue; 202 continue;
191 } 203 }
192 const bool instanced = vi.binding_divisors[index] != 0; 204 const bool instanced = state.binding_divisors[index] != 0;
193 const auto rate = instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX; 205 const auto rate = instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX;
194 206
195 auto& vertex_binding = vertex_bindings.emplace_back(); 207 auto& vertex_binding = vertex_bindings.emplace_back();
@@ -200,14 +212,14 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
200 if (instanced) { 212 if (instanced) {
201 auto& binding_divisor = vertex_binding_divisors.emplace_back(); 213 auto& binding_divisor = vertex_binding_divisors.emplace_back();
202 binding_divisor.binding = static_cast<u32>(index); 214 binding_divisor.binding = static_cast<u32>(index);
203 binding_divisor.divisor = vi.binding_divisors[index]; 215 binding_divisor.divisor = state.binding_divisors[index];
204 } 216 }
205 } 217 }
206 218
207 std::vector<VkVertexInputAttributeDescription> vertex_attributes; 219 std::vector<VkVertexInputAttributeDescription> vertex_attributes;
208 const auto& input_attributes = program[0]->entries.attributes; 220 const auto& input_attributes = program[0]->entries.attributes;
209 for (std::size_t index = 0; index < std::size(vi.attributes); ++index) { 221 for (std::size_t index = 0; index < state.attributes.size(); ++index) {
210 const auto& attribute = vi.attributes[index]; 222 const auto& attribute = state.attributes[index];
211 if (!attribute.enabled) { 223 if (!attribute.enabled) {
212 continue; 224 continue;
213 } 225 }
@@ -244,15 +256,15 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
244 input_assembly_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; 256 input_assembly_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
245 input_assembly_ci.pNext = nullptr; 257 input_assembly_ci.pNext = nullptr;
246 input_assembly_ci.flags = 0; 258 input_assembly_ci.flags = 0;
247 input_assembly_ci.topology = MaxwellToVK::PrimitiveTopology(device, rs.Topology()); 259 input_assembly_ci.topology = MaxwellToVK::PrimitiveTopology(device, dynamic.Topology());
248 input_assembly_ci.primitiveRestartEnable = 260 input_assembly_ci.primitiveRestartEnable =
249 rs.primitive_restart_enable != 0 && SupportsPrimitiveRestart(input_assembly_ci.topology); 261 state.primitive_restart_enable != 0 && SupportsPrimitiveRestart(input_assembly_ci.topology);
250 262
251 VkPipelineTessellationStateCreateInfo tessellation_ci; 263 VkPipelineTessellationStateCreateInfo tessellation_ci;
252 tessellation_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO; 264 tessellation_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
253 tessellation_ci.pNext = nullptr; 265 tessellation_ci.pNext = nullptr;
254 tessellation_ci.flags = 0; 266 tessellation_ci.flags = 0;
255 tessellation_ci.patchControlPoints = rs.patch_control_points_minus_one.Value() + 1; 267 tessellation_ci.patchControlPoints = state.patch_control_points_minus_one.Value() + 1;
256 268
257 VkPipelineViewportStateCreateInfo viewport_ci; 269 VkPipelineViewportStateCreateInfo viewport_ci;
258 viewport_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; 270 viewport_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
@@ -280,13 +292,13 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
280 rasterization_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; 292 rasterization_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
281 rasterization_ci.pNext = nullptr; 293 rasterization_ci.pNext = nullptr;
282 rasterization_ci.flags = 0; 294 rasterization_ci.flags = 0;
283 rasterization_ci.depthClampEnable = rs.depth_clamp_disabled == 0 ? VK_TRUE : VK_FALSE; 295 rasterization_ci.depthClampEnable = state.depth_clamp_disabled == 0 ? VK_TRUE : VK_FALSE;
284 rasterization_ci.rasterizerDiscardEnable = rs.rasterize_enable == 0 ? VK_TRUE : VK_FALSE; 296 rasterization_ci.rasterizerDiscardEnable = state.rasterize_enable == 0 ? VK_TRUE : VK_FALSE;
285 rasterization_ci.polygonMode = VK_POLYGON_MODE_FILL; 297 rasterization_ci.polygonMode = VK_POLYGON_MODE_FILL;
286 rasterization_ci.cullMode = 298 rasterization_ci.cullMode =
287 rs.cull_enable ? MaxwellToVK::CullFace(rs.CullFace()) : VK_CULL_MODE_NONE; 299 dynamic.cull_enable ? MaxwellToVK::CullFace(dynamic.CullFace()) : VK_CULL_MODE_NONE;
288 rasterization_ci.frontFace = MaxwellToVK::FrontFace(rs.FrontFace()); 300 rasterization_ci.frontFace = MaxwellToVK::FrontFace(dynamic.FrontFace());
289 rasterization_ci.depthBiasEnable = rs.depth_bias_enable; 301 rasterization_ci.depthBiasEnable = state.depth_bias_enable;
290 rasterization_ci.depthBiasConstantFactor = 0.0f; 302 rasterization_ci.depthBiasConstantFactor = 0.0f;
291 rasterization_ci.depthBiasClamp = 0.0f; 303 rasterization_ci.depthBiasClamp = 0.0f;
292 rasterization_ci.depthBiasSlopeFactor = 0.0f; 304 rasterization_ci.depthBiasSlopeFactor = 0.0f;
@@ -307,14 +319,15 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
307 depth_stencil_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; 319 depth_stencil_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
308 depth_stencil_ci.pNext = nullptr; 320 depth_stencil_ci.pNext = nullptr;
309 depth_stencil_ci.flags = 0; 321 depth_stencil_ci.flags = 0;
310 depth_stencil_ci.depthTestEnable = ds.depth_test_enable; 322 depth_stencil_ci.depthTestEnable = dynamic.depth_test_enable;
311 depth_stencil_ci.depthWriteEnable = ds.depth_write_enable; 323 depth_stencil_ci.depthWriteEnable = dynamic.depth_write_enable;
312 depth_stencil_ci.depthCompareOp = 324 depth_stencil_ci.depthCompareOp = dynamic.depth_test_enable
313 ds.depth_test_enable ? MaxwellToVK::ComparisonOp(ds.DepthTestFunc()) : VK_COMPARE_OP_ALWAYS; 325 ? MaxwellToVK::ComparisonOp(dynamic.DepthTestFunc())
314 depth_stencil_ci.depthBoundsTestEnable = ds.depth_bounds_enable; 326 : VK_COMPARE_OP_ALWAYS;
315 depth_stencil_ci.stencilTestEnable = ds.stencil_enable; 327 depth_stencil_ci.depthBoundsTestEnable = dynamic.depth_bounds_enable;
316 depth_stencil_ci.front = GetStencilFaceState(ds.front); 328 depth_stencil_ci.stencilTestEnable = dynamic.stencil_enable;
317 depth_stencil_ci.back = GetStencilFaceState(ds.back); 329 depth_stencil_ci.front = GetStencilFaceState(dynamic.front);
330 depth_stencil_ci.back = GetStencilFaceState(dynamic.back);
318 depth_stencil_ci.minDepthBounds = 0.0f; 331 depth_stencil_ci.minDepthBounds = 0.0f;
319 depth_stencil_ci.maxDepthBounds = 0.0f; 332 depth_stencil_ci.maxDepthBounds = 0.0f;
320 333
@@ -324,7 +337,7 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
324 static constexpr std::array COMPONENT_TABLE = { 337 static constexpr std::array COMPONENT_TABLE = {
325 VK_COLOR_COMPONENT_R_BIT, VK_COLOR_COMPONENT_G_BIT, VK_COLOR_COMPONENT_B_BIT, 338 VK_COLOR_COMPONENT_R_BIT, VK_COLOR_COMPONENT_G_BIT, VK_COLOR_COMPONENT_B_BIT,
326 VK_COLOR_COMPONENT_A_BIT}; 339 VK_COLOR_COMPONENT_A_BIT};
327 const auto& blend = cd.attachments[index]; 340 const auto& blend = state.attachments[index];
328 341
329 VkColorComponentFlags color_components = 0; 342 VkColorComponentFlags color_components = 0;
330 for (std::size_t i = 0; i < COMPONENT_TABLE.size(); ++i) { 343 for (std::size_t i = 0; i < COMPONENT_TABLE.size(); ++i) {
@@ -354,11 +367,27 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
354 color_blend_ci.pAttachments = cb_attachments.data(); 367 color_blend_ci.pAttachments = cb_attachments.data();
355 std::memset(color_blend_ci.blendConstants, 0, sizeof(color_blend_ci.blendConstants)); 368 std::memset(color_blend_ci.blendConstants, 0, sizeof(color_blend_ci.blendConstants));
356 369
357 static constexpr std::array dynamic_states = { 370 std::vector dynamic_states = {
358 VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, 371 VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR,
359 VK_DYNAMIC_STATE_DEPTH_BIAS, VK_DYNAMIC_STATE_BLEND_CONSTANTS, 372 VK_DYNAMIC_STATE_DEPTH_BIAS, VK_DYNAMIC_STATE_BLEND_CONSTANTS,
360 VK_DYNAMIC_STATE_DEPTH_BOUNDS, VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK, 373 VK_DYNAMIC_STATE_DEPTH_BOUNDS, VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK,
361 VK_DYNAMIC_STATE_STENCIL_WRITE_MASK, VK_DYNAMIC_STATE_STENCIL_REFERENCE}; 374 VK_DYNAMIC_STATE_STENCIL_WRITE_MASK, VK_DYNAMIC_STATE_STENCIL_REFERENCE,
375 };
376 if (device.IsExtExtendedDynamicStateSupported()) {
377 static constexpr std::array extended = {
378 VK_DYNAMIC_STATE_CULL_MODE_EXT,
379 VK_DYNAMIC_STATE_FRONT_FACE_EXT,
380 VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT,
381 VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT,
382 VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE_EXT,
383 VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE_EXT,
384 VK_DYNAMIC_STATE_DEPTH_COMPARE_OP_EXT,
385 VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE_EXT,
386 VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE_EXT,
387 VK_DYNAMIC_STATE_STENCIL_OP_EXT,
388 };
389 dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.end());
390 }
362 391
363 VkPipelineDynamicStateCreateInfo dynamic_state_ci; 392 VkPipelineDynamicStateCreateInfo dynamic_state_ci;
364 dynamic_state_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; 393 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,
116} // Anonymous namespace 116} // Anonymous namespace
117 117
118std::size_t GraphicsPipelineCacheKey::Hash() const noexcept { 118std::size_t GraphicsPipelineCacheKey::Hash() const noexcept {
119 const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), sizeof *this); 119 const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), Size());
120 return static_cast<std::size_t>(hash); 120 return static_cast<std::size_t>(hash);
121} 121}
122 122
123bool GraphicsPipelineCacheKey::operator==(const GraphicsPipelineCacheKey& rhs) const noexcept { 123bool GraphicsPipelineCacheKey::operator==(const GraphicsPipelineCacheKey& rhs) const noexcept {
124 return std::memcmp(&rhs, this, sizeof *this) == 0; 124 return std::memcmp(&rhs, this, Size()) == 0;
125} 125}
126 126
127std::size_t ComputePipelineCacheKey::Hash() const noexcept { 127std::size_t ComputePipelineCacheKey::Hash() const noexcept {
@@ -312,18 +312,19 @@ VKPipelineCache::DecompileShaders(const GraphicsPipelineCacheKey& key) {
312 const auto& gpu = system.GPU().Maxwell3D(); 312 const auto& gpu = system.GPU().Maxwell3D();
313 313
314 Specialization specialization; 314 Specialization specialization;
315 if (fixed_state.rasterizer.Topology() == Maxwell::PrimitiveTopology::Points) { 315 if (fixed_state.dynamic_state.Topology() == Maxwell::PrimitiveTopology::Points ||
316 device.IsExtExtendedDynamicStateSupported()) {
316 float point_size; 317 float point_size;
317 std::memcpy(&point_size, &fixed_state.rasterizer.point_size, sizeof(float)); 318 std::memcpy(&point_size, &fixed_state.point_size, sizeof(float));
318 specialization.point_size = point_size; 319 specialization.point_size = point_size;
319 ASSERT(point_size != 0.0f); 320 ASSERT(point_size != 0.0f);
320 } 321 }
321 for (std::size_t i = 0; i < Maxwell::NumVertexAttributes; ++i) { 322 for (std::size_t i = 0; i < Maxwell::NumVertexAttributes; ++i) {
322 const auto& attribute = fixed_state.vertex_input.attributes[i]; 323 const auto& attribute = fixed_state.attributes[i];
323 specialization.enabled_attributes[i] = attribute.enabled.Value() != 0; 324 specialization.enabled_attributes[i] = attribute.enabled.Value() != 0;
324 specialization.attribute_types[i] = attribute.Type(); 325 specialization.attribute_types[i] = attribute.Type();
325 } 326 }
326 specialization.ndc_minus_one_to_one = fixed_state.rasterizer.ndc_minus_one_to_one; 327 specialization.ndc_minus_one_to_one = fixed_state.ndc_minus_one_to_one;
327 328
328 SPIRVProgram program; 329 SPIRVProgram program;
329 std::vector<VkDescriptorSetLayoutBinding> bindings; 330 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;
44using Maxwell = Tegra::Engines::Maxwell3D::Regs; 44using Maxwell = Tegra::Engines::Maxwell3D::Regs;
45 45
46struct GraphicsPipelineCacheKey { 46struct GraphicsPipelineCacheKey {
47 FixedPipelineState fixed_state;
48 RenderPassParams renderpass_params; 47 RenderPassParams renderpass_params;
48 u32 padding;
49 std::array<GPUVAddr, Maxwell::MaxShaderProgram> shaders; 49 std::array<GPUVAddr, Maxwell::MaxShaderProgram> shaders;
50 u64 padding; // This is necessary for unique object representations 50 FixedPipelineState fixed_state;
51 51
52 std::size_t Hash() const noexcept; 52 std::size_t Hash() const noexcept;
53 53
@@ -56,6 +56,10 @@ struct GraphicsPipelineCacheKey {
56 bool operator!=(const GraphicsPipelineCacheKey& rhs) const noexcept { 56 bool operator!=(const GraphicsPipelineCacheKey& rhs) const noexcept {
57 return !operator==(rhs); 57 return !operator==(rhs);
58 } 58 }
59
60 std::size_t Size() const noexcept {
61 return sizeof(renderpass_params) + sizeof(padding) + sizeof(shaders) + fixed_state.Size();
62 }
59}; 63};
60static_assert(std::has_unique_object_representations_v<GraphicsPipelineCacheKey>); 64static_assert(std::has_unique_object_representations_v<GraphicsPipelineCacheKey>);
61static_assert(std::is_trivially_copyable_v<GraphicsPipelineCacheKey>); 65static_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) {
186 scissor.max_y < regs.zeta_height; 186 scissor.max_y < regs.zeta_height;
187} 187}
188 188
189template <std::size_t N>
190std::array<VkDeviceSize, N> ExpandStrides(const std::array<u16, N>& strides) {
191 std::array<VkDeviceSize, N> expanded;
192 std::copy(strides.begin(), strides.end(), expanded.begin());
193 return expanded;
194}
195
189} // Anonymous namespace 196} // Anonymous namespace
190 197
191class BufferBindings final { 198class BufferBindings final {
192public: 199public:
193 void AddVertexBinding(VkBuffer buffer, VkDeviceSize offset) { 200 void AddVertexBinding(VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size, u32 stride) {
194 vertex.buffers[vertex.num_buffers] = buffer; 201 vertex.buffers[vertex.num_buffers] = buffer;
195 vertex.offsets[vertex.num_buffers] = offset; 202 vertex.offsets[vertex.num_buffers] = offset;
203 vertex.sizes[vertex.num_buffers] = size;
204 vertex.strides[vertex.num_buffers] = static_cast<u16>(stride);
196 ++vertex.num_buffers; 205 ++vertex.num_buffers;
197 } 206 }
198 207
@@ -202,76 +211,76 @@ public:
202 index.type = type; 211 index.type = type;
203 } 212 }
204 213
205 void Bind(VKScheduler& scheduler) const { 214 void Bind(const VKDevice& device, VKScheduler& scheduler) const {
206 // Use this large switch case to avoid dispatching more memory in the record lambda than 215 // Use this large switch case to avoid dispatching more memory in the record lambda than
207 // what we need. It looks horrible, but it's the best we can do on standard C++. 216 // what we need. It looks horrible, but it's the best we can do on standard C++.
208 switch (vertex.num_buffers) { 217 switch (vertex.num_buffers) {
209 case 0: 218 case 0:
210 return BindStatic<0>(scheduler); 219 return BindStatic<0>(device, scheduler);
211 case 1: 220 case 1:
212 return BindStatic<1>(scheduler); 221 return BindStatic<1>(device, scheduler);
213 case 2: 222 case 2:
214 return BindStatic<2>(scheduler); 223 return BindStatic<2>(device, scheduler);
215 case 3: 224 case 3:
216 return BindStatic<3>(scheduler); 225 return BindStatic<3>(device, scheduler);
217 case 4: 226 case 4:
218 return BindStatic<4>(scheduler); 227 return BindStatic<4>(device, scheduler);
219 case 5: 228 case 5:
220 return BindStatic<5>(scheduler); 229 return BindStatic<5>(device, scheduler);
221 case 6: 230 case 6:
222 return BindStatic<6>(scheduler); 231 return BindStatic<6>(device, scheduler);
223 case 7: 232 case 7:
224 return BindStatic<7>(scheduler); 233 return BindStatic<7>(device, scheduler);
225 case 8: 234 case 8:
226 return BindStatic<8>(scheduler); 235 return BindStatic<8>(device, scheduler);
227 case 9: 236 case 9:
228 return BindStatic<9>(scheduler); 237 return BindStatic<9>(device, scheduler);
229 case 10: 238 case 10:
230 return BindStatic<10>(scheduler); 239 return BindStatic<10>(device, scheduler);
231 case 11: 240 case 11:
232 return BindStatic<11>(scheduler); 241 return BindStatic<11>(device, scheduler);
233 case 12: 242 case 12:
234 return BindStatic<12>(scheduler); 243 return BindStatic<12>(device, scheduler);
235 case 13: 244 case 13:
236 return BindStatic<13>(scheduler); 245 return BindStatic<13>(device, scheduler);
237 case 14: 246 case 14:
238 return BindStatic<14>(scheduler); 247 return BindStatic<14>(device, scheduler);
239 case 15: 248 case 15:
240 return BindStatic<15>(scheduler); 249 return BindStatic<15>(device, scheduler);
241 case 16: 250 case 16:
242 return BindStatic<16>(scheduler); 251 return BindStatic<16>(device, scheduler);
243 case 17: 252 case 17:
244 return BindStatic<17>(scheduler); 253 return BindStatic<17>(device, scheduler);
245 case 18: 254 case 18:
246 return BindStatic<18>(scheduler); 255 return BindStatic<18>(device, scheduler);
247 case 19: 256 case 19:
248 return BindStatic<19>(scheduler); 257 return BindStatic<19>(device, scheduler);
249 case 20: 258 case 20:
250 return BindStatic<20>(scheduler); 259 return BindStatic<20>(device, scheduler);
251 case 21: 260 case 21:
252 return BindStatic<21>(scheduler); 261 return BindStatic<21>(device, scheduler);
253 case 22: 262 case 22:
254 return BindStatic<22>(scheduler); 263 return BindStatic<22>(device, scheduler);
255 case 23: 264 case 23:
256 return BindStatic<23>(scheduler); 265 return BindStatic<23>(device, scheduler);
257 case 24: 266 case 24:
258 return BindStatic<24>(scheduler); 267 return BindStatic<24>(device, scheduler);
259 case 25: 268 case 25:
260 return BindStatic<25>(scheduler); 269 return BindStatic<25>(device, scheduler);
261 case 26: 270 case 26:
262 return BindStatic<26>(scheduler); 271 return BindStatic<26>(device, scheduler);
263 case 27: 272 case 27:
264 return BindStatic<27>(scheduler); 273 return BindStatic<27>(device, scheduler);
265 case 28: 274 case 28:
266 return BindStatic<28>(scheduler); 275 return BindStatic<28>(device, scheduler);
267 case 29: 276 case 29:
268 return BindStatic<29>(scheduler); 277 return BindStatic<29>(device, scheduler);
269 case 30: 278 case 30:
270 return BindStatic<30>(scheduler); 279 return BindStatic<30>(device, scheduler);
271 case 31: 280 case 31:
272 return BindStatic<31>(scheduler); 281 return BindStatic<31>(device, scheduler);
273 case 32: 282 case 32:
274 return BindStatic<32>(scheduler); 283 return BindStatic<32>(device, scheduler);
275 } 284 }
276 UNREACHABLE(); 285 UNREACHABLE();
277 } 286 }
@@ -282,6 +291,8 @@ private:
282 std::size_t num_buffers = 0; 291 std::size_t num_buffers = 0;
283 std::array<VkBuffer, Maxwell::NumVertexArrays> buffers; 292 std::array<VkBuffer, Maxwell::NumVertexArrays> buffers;
284 std::array<VkDeviceSize, Maxwell::NumVertexArrays> offsets; 293 std::array<VkDeviceSize, Maxwell::NumVertexArrays> offsets;
294 std::array<VkDeviceSize, Maxwell::NumVertexArrays> sizes;
295 std::array<u16, Maxwell::NumVertexArrays> strides;
285 } vertex; 296 } vertex;
286 297
287 struct { 298 struct {
@@ -291,15 +302,23 @@ private:
291 } index; 302 } index;
292 303
293 template <std::size_t N> 304 template <std::size_t N>
294 void BindStatic(VKScheduler& scheduler) const { 305 void BindStatic(const VKDevice& device, VKScheduler& scheduler) const {
295 if (index.buffer) { 306 if (device.IsExtExtendedDynamicStateSupported()) {
296 BindStatic<N, true>(scheduler); 307 if (index.buffer) {
308 BindStatic<N, true, true>(scheduler);
309 } else {
310 BindStatic<N, false, true>(scheduler);
311 }
297 } else { 312 } else {
298 BindStatic<N, false>(scheduler); 313 if (index.buffer) {
314 BindStatic<N, true, false>(scheduler);
315 } else {
316 BindStatic<N, false, false>(scheduler);
317 }
299 } 318 }
300 } 319 }
301 320
302 template <std::size_t N, bool is_indexed> 321 template <std::size_t N, bool is_indexed, bool has_extended_dynamic_state>
303 void BindStatic(VKScheduler& scheduler) const { 322 void BindStatic(VKScheduler& scheduler) const {
304 static_assert(N <= Maxwell::NumVertexArrays); 323 static_assert(N <= Maxwell::NumVertexArrays);
305 if constexpr (N == 0) { 324 if constexpr (N == 0) {
@@ -311,6 +330,31 @@ private:
311 std::copy(vertex.buffers.begin(), vertex.buffers.begin() + N, buffers.begin()); 330 std::copy(vertex.buffers.begin(), vertex.buffers.begin() + N, buffers.begin());
312 std::copy(vertex.offsets.begin(), vertex.offsets.begin() + N, offsets.begin()); 331 std::copy(vertex.offsets.begin(), vertex.offsets.begin() + N, offsets.begin());
313 332
333 if constexpr (has_extended_dynamic_state) {
334 // With extended dynamic states we can specify the length and stride of a vertex buffer
335 // std::array<VkDeviceSize, N> sizes;
336 std::array<u16, N> strides;
337 // std::copy(vertex.sizes.begin(), vertex.sizes.begin() + N, sizes.begin());
338 std::copy(vertex.strides.begin(), vertex.strides.begin() + N, strides.begin());
339
340 if constexpr (is_indexed) {
341 scheduler.Record(
342 [buffers, offsets, strides, index = index](vk::CommandBuffer cmdbuf) {
343 cmdbuf.BindIndexBuffer(index.buffer, index.offset, index.type);
344 cmdbuf.BindVertexBuffers2EXT(0, static_cast<u32>(N), buffers.data(),
345 offsets.data(), nullptr,
346 ExpandStrides(strides).data());
347 });
348 } else {
349 scheduler.Record([buffers, offsets, strides](vk::CommandBuffer cmdbuf) {
350 cmdbuf.BindVertexBuffers2EXT(0, static_cast<u32>(N), buffers.data(),
351 offsets.data(), nullptr,
352 ExpandStrides(strides).data());
353 });
354 }
355 return;
356 }
357
314 if constexpr (is_indexed) { 358 if constexpr (is_indexed) {
315 // Indexed draw 359 // Indexed draw
316 scheduler.Record([buffers, offsets, index = index](vk::CommandBuffer cmdbuf) { 360 scheduler.Record([buffers, offsets, index = index](vk::CommandBuffer cmdbuf) {
@@ -369,7 +413,7 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
369 413
370 const auto& gpu = system.GPU().Maxwell3D(); 414 const auto& gpu = system.GPU().Maxwell3D();
371 GraphicsPipelineCacheKey key; 415 GraphicsPipelineCacheKey key;
372 key.fixed_state.Fill(gpu.regs); 416 key.fixed_state.Fill(gpu.regs, device.IsExtExtendedDynamicStateSupported());
373 417
374 buffer_cache.Map(CalculateGraphicsStreamBufferSize(is_indexed)); 418 buffer_cache.Map(CalculateGraphicsStreamBufferSize(is_indexed));
375 419
@@ -402,7 +446,7 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
402 446
403 UpdateDynamicStates(); 447 UpdateDynamicStates();
404 448
405 buffer_bindings.Bind(scheduler); 449 buffer_bindings.Bind(device, scheduler);
406 450
407 BeginTransformFeedback(); 451 BeginTransformFeedback();
408 452
@@ -822,7 +866,7 @@ RasterizerVulkan::DrawParameters RasterizerVulkan::SetupGeometry(FixedPipelineSt
822 const auto& gpu = system.GPU().Maxwell3D(); 866 const auto& gpu = system.GPU().Maxwell3D();
823 const auto& regs = gpu.regs; 867 const auto& regs = gpu.regs;
824 868
825 SetupVertexArrays(fixed_state.vertex_input, buffer_bindings); 869 SetupVertexArrays(buffer_bindings);
826 870
827 const u32 base_instance = regs.vb_base_instance; 871 const u32 base_instance = regs.vb_base_instance;
828 const u32 num_instances = is_instanced ? gpu.mme_draw.instance_count : 1; 872 const u32 num_instances = is_instanced ? gpu.mme_draw.instance_count : 1;
@@ -893,6 +937,17 @@ void RasterizerVulkan::UpdateDynamicStates() {
893 UpdateBlendConstants(regs); 937 UpdateBlendConstants(regs);
894 UpdateDepthBounds(regs); 938 UpdateDepthBounds(regs);
895 UpdateStencilFaces(regs); 939 UpdateStencilFaces(regs);
940 if (device.IsExtExtendedDynamicStateSupported()) {
941 UpdateCullMode(regs);
942 UpdateDepthBoundsTestEnable(regs);
943 UpdateDepthTestEnable(regs);
944 UpdateDepthWriteEnable(regs);
945 UpdateDepthCompareOp(regs);
946 UpdateFrontFace(regs);
947 UpdatePrimitiveTopology(regs);
948 UpdateStencilOp(regs);
949 UpdateStencilTestEnable(regs);
950 }
896} 951}
897 952
898void RasterizerVulkan::BeginTransformFeedback() { 953void RasterizerVulkan::BeginTransformFeedback() {
@@ -940,41 +995,25 @@ void RasterizerVulkan::EndTransformFeedback() {
940 [](vk::CommandBuffer cmdbuf) { cmdbuf.EndTransformFeedbackEXT(0, 0, nullptr, nullptr); }); 995 [](vk::CommandBuffer cmdbuf) { cmdbuf.EndTransformFeedbackEXT(0, 0, nullptr, nullptr); });
941} 996}
942 997
943void RasterizerVulkan::SetupVertexArrays(FixedPipelineState::VertexInput& vertex_input, 998void RasterizerVulkan::SetupVertexArrays(BufferBindings& buffer_bindings) {
944 BufferBindings& buffer_bindings) {
945 const auto& regs = system.GPU().Maxwell3D().regs; 999 const auto& regs = system.GPU().Maxwell3D().regs;
946 1000
947 for (std::size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {
948 const auto& attrib = regs.vertex_attrib_format[index];
949 if (attrib.IsConstant()) {
950 vertex_input.SetAttribute(index, false, 0, 0, {}, {});
951 continue;
952 }
953 vertex_input.SetAttribute(index, true, attrib.buffer, attrib.offset, attrib.type.Value(),
954 attrib.size.Value());
955 }
956
957 for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { 1001 for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
958 const auto& vertex_array = regs.vertex_array[index]; 1002 const auto& vertex_array = regs.vertex_array[index];
959 if (!vertex_array.IsEnabled()) { 1003 if (!vertex_array.IsEnabled()) {
960 vertex_input.SetBinding(index, false, 0, 0);
961 continue; 1004 continue;
962 } 1005 }
963 vertex_input.SetBinding(
964 index, true, vertex_array.stride,
965 regs.instanced_arrays.IsInstancingEnabled(index) ? vertex_array.divisor : 0);
966
967 const GPUVAddr start{vertex_array.StartAddress()}; 1006 const GPUVAddr start{vertex_array.StartAddress()};
968 const GPUVAddr end{regs.vertex_array_limit[index].LimitAddress()}; 1007 const GPUVAddr end{regs.vertex_array_limit[index].LimitAddress()};
969 1008
970 ASSERT(end >= start); 1009 ASSERT(end >= start);
971 const std::size_t size{end - start}; 1010 const std::size_t size = end - start;
972 if (size == 0) { 1011 if (size == 0) {
973 buffer_bindings.AddVertexBinding(DefaultBuffer(), 0); 1012 buffer_bindings.AddVertexBinding(DefaultBuffer(), 0, DEFAULT_BUFFER_SIZE, 0);
974 continue; 1013 continue;
975 } 1014 }
976 const auto info = buffer_cache.UploadMemory(start, size); 1015 const auto info = buffer_cache.UploadMemory(start, size);
977 buffer_bindings.AddVertexBinding(info.handle, info.offset); 1016 buffer_bindings.AddVertexBinding(info.handle, info.offset, size, vertex_array.stride);
978 } 1017 }
979} 1018}
980 1019
@@ -1326,6 +1365,117 @@ void RasterizerVulkan::UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs)
1326 } 1365 }
1327} 1366}
1328 1367
1368void RasterizerVulkan::UpdateCullMode(Tegra::Engines::Maxwell3D::Regs& regs) {
1369 if (!state_tracker.TouchCullMode()) {
1370 return;
1371 }
1372 scheduler.Record(
1373 [enabled = regs.cull_test_enabled, cull_face = regs.cull_face](vk::CommandBuffer cmdbuf) {
1374 cmdbuf.SetCullModeEXT(enabled ? MaxwellToVK::CullFace(cull_face) : VK_CULL_MODE_NONE);
1375 });
1376}
1377
1378void RasterizerVulkan::UpdateDepthBoundsTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
1379 if (!state_tracker.TouchDepthBoundsTestEnable()) {
1380 return;
1381 }
1382 scheduler.Record([enable = regs.depth_bounds_enable](vk::CommandBuffer cmdbuf) {
1383 cmdbuf.SetDepthBoundsTestEnableEXT(enable);
1384 });
1385}
1386
1387void RasterizerVulkan::UpdateDepthTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
1388 if (!state_tracker.TouchDepthTestEnable()) {
1389 return;
1390 }
1391 scheduler.Record([enable = regs.depth_test_enable](vk::CommandBuffer cmdbuf) {
1392 cmdbuf.SetDepthTestEnableEXT(enable);
1393 });
1394}
1395
1396void RasterizerVulkan::UpdateDepthWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
1397 if (!state_tracker.TouchDepthWriteEnable()) {
1398 return;
1399 }
1400 scheduler.Record([enable = regs.depth_write_enabled](vk::CommandBuffer cmdbuf) {
1401 cmdbuf.SetDepthWriteEnableEXT(enable);
1402 });
1403}
1404
1405void RasterizerVulkan::UpdateDepthCompareOp(Tegra::Engines::Maxwell3D::Regs& regs) {
1406 if (!state_tracker.TouchDepthCompareOp()) {
1407 return;
1408 }
1409 scheduler.Record([func = regs.depth_test_func](vk::CommandBuffer cmdbuf) {
1410 cmdbuf.SetDepthCompareOpEXT(MaxwellToVK::ComparisonOp(func));
1411 });
1412}
1413
1414void RasterizerVulkan::UpdateFrontFace(Tegra::Engines::Maxwell3D::Regs& regs) {
1415 if (!state_tracker.TouchFrontFace()) {
1416 return;
1417 }
1418
1419 VkFrontFace front_face = MaxwellToVK::FrontFace(regs.front_face);
1420 if (regs.screen_y_control.triangle_rast_flip != 0) {
1421 front_face = front_face == VK_FRONT_FACE_CLOCKWISE ? VK_FRONT_FACE_COUNTER_CLOCKWISE
1422 : VK_FRONT_FACE_CLOCKWISE;
1423 }
1424 scheduler.Record(
1425 [front_face](vk::CommandBuffer cmdbuf) { cmdbuf.SetFrontFaceEXT(front_face); });
1426}
1427
1428void RasterizerVulkan::UpdatePrimitiveTopology(Tegra::Engines::Maxwell3D::Regs& regs) {
1429 if (!state_tracker.TouchPrimitiveTopology()) {
1430 return;
1431 }
1432 const Maxwell::PrimitiveTopology primitive_topology = regs.draw.topology.Value();
1433 scheduler.Record([this, primitive_topology](vk::CommandBuffer cmdbuf) {
1434 cmdbuf.SetPrimitiveTopologyEXT(MaxwellToVK::PrimitiveTopology(device, primitive_topology));
1435 });
1436}
1437
1438void RasterizerVulkan::UpdateStencilOp(Tegra::Engines::Maxwell3D::Regs& regs) {
1439 if (!state_tracker.TouchStencilOp()) {
1440 return;
1441 }
1442 const Maxwell::StencilOp fail = regs.stencil_front_op_fail;
1443 const Maxwell::StencilOp zfail = regs.stencil_front_op_zfail;
1444 const Maxwell::StencilOp zpass = regs.stencil_front_op_zpass;
1445 const Maxwell::ComparisonOp compare = regs.stencil_front_func_func;
1446 if (regs.stencil_two_side_enable) {
1447 scheduler.Record([fail, zfail, zpass, compare](vk::CommandBuffer cmdbuf) {
1448 cmdbuf.SetStencilOpEXT(VK_STENCIL_FACE_FRONT_AND_BACK, MaxwellToVK::StencilOp(fail),
1449 MaxwellToVK::StencilOp(zpass), MaxwellToVK::StencilOp(zfail),
1450 MaxwellToVK::ComparisonOp(compare));
1451 });
1452 } else {
1453 const Maxwell::StencilOp back_fail = regs.stencil_back_op_fail;
1454 const Maxwell::StencilOp back_zfail = regs.stencil_back_op_zfail;
1455 const Maxwell::StencilOp back_zpass = regs.stencil_back_op_zpass;
1456 const Maxwell::ComparisonOp back_compare = regs.stencil_back_func_func;
1457 scheduler.Record([fail, zfail, zpass, compare, back_fail, back_zfail, back_zpass,
1458 back_compare](vk::CommandBuffer cmdbuf) {
1459 cmdbuf.SetStencilOpEXT(VK_STENCIL_FACE_FRONT_BIT, MaxwellToVK::StencilOp(fail),
1460 MaxwellToVK::StencilOp(zpass), MaxwellToVK::StencilOp(zfail),
1461 MaxwellToVK::ComparisonOp(compare));
1462 cmdbuf.SetStencilOpEXT(VK_STENCIL_FACE_BACK_BIT, MaxwellToVK::StencilOp(back_fail),
1463 MaxwellToVK::StencilOp(back_zpass),
1464 MaxwellToVK::StencilOp(back_zfail),
1465 MaxwellToVK::ComparisonOp(back_compare));
1466 });
1467 }
1468}
1469
1470void RasterizerVulkan::UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
1471 if (!state_tracker.TouchStencilTestEnable()) {
1472 return;
1473 }
1474 scheduler.Record([enable = regs.stencil_enable](vk::CommandBuffer cmdbuf) {
1475 cmdbuf.SetStencilTestEnableEXT(enable);
1476 });
1477}
1478
1329std::size_t RasterizerVulkan::CalculateGraphicsStreamBufferSize(bool is_indexed) const { 1479std::size_t RasterizerVulkan::CalculateGraphicsStreamBufferSize(bool is_indexed) const {
1330 std::size_t size = CalculateVertexArraysSize(); 1480 std::size_t size = CalculateVertexArraysSize();
1331 if (is_indexed) { 1481 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:
185 185
186 bool WalkAttachmentOverlaps(const CachedSurfaceView& attachment); 186 bool WalkAttachmentOverlaps(const CachedSurfaceView& attachment);
187 187
188 void SetupVertexArrays(FixedPipelineState::VertexInput& vertex_input, 188 void SetupVertexArrays(BufferBindings& buffer_bindings);
189 BufferBindings& buffer_bindings);
190 189
191 void SetupIndexBuffer(BufferBindings& buffer_bindings, DrawParameters& params, bool is_indexed); 190 void SetupIndexBuffer(BufferBindings& buffer_bindings, DrawParameters& params, bool is_indexed);
192 191
@@ -246,6 +245,16 @@ private:
246 void UpdateDepthBounds(Tegra::Engines::Maxwell3D::Regs& regs); 245 void UpdateDepthBounds(Tegra::Engines::Maxwell3D::Regs& regs);
247 void UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs); 246 void UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs);
248 247
248 void UpdateCullMode(Tegra::Engines::Maxwell3D::Regs& regs);
249 void UpdateDepthBoundsTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
250 void UpdateDepthTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
251 void UpdateDepthWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs);
252 void UpdateDepthCompareOp(Tegra::Engines::Maxwell3D::Regs& regs);
253 void UpdateFrontFace(Tegra::Engines::Maxwell3D::Regs& regs);
254 void UpdatePrimitiveTopology(Tegra::Engines::Maxwell3D::Regs& regs);
255 void UpdateStencilOp(Tegra::Engines::Maxwell3D::Regs& regs);
256 void UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
257
249 std::size_t CalculateGraphicsStreamBufferSize(bool is_indexed) const; 258 std::size_t CalculateGraphicsStreamBufferSize(bool is_indexed) const;
250 259
251 std::size_t CalculateComputeStreamBufferSize() const; 260 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() {
36 flags[BlendConstants] = true; 36 flags[BlendConstants] = true;
37 flags[DepthBounds] = true; 37 flags[DepthBounds] = true;
38 flags[StencilProperties] = true; 38 flags[StencilProperties] = true;
39 flags[CullMode] = true;
40 flags[DepthBoundsEnable] = true;
41 flags[DepthTestEnable] = true;
42 flags[DepthWriteEnable] = true;
43 flags[DepthCompareOp] = true;
44 flags[FrontFace] = true;
45 flags[PrimitiveTopology] = true;
46 flags[StencilOp] = true;
47 flags[StencilTestEnable] = true;
39 return flags; 48 return flags;
40} 49}
41 50
@@ -75,6 +84,57 @@ void SetupDirtyStencilProperties(Tables& tables) {
75 table[OFF(stencil_back_func_mask)] = StencilProperties; 84 table[OFF(stencil_back_func_mask)] = StencilProperties;
76} 85}
77 86
87void SetupDirtyCullMode(Tables& tables) {
88 auto& table = tables[0];
89 table[OFF(cull_face)] = CullMode;
90 table[OFF(cull_test_enabled)] = CullMode;
91}
92
93void SetupDirtyDepthBoundsEnable(Tables& tables) {
94 tables[0][OFF(depth_bounds_enable)] = DepthBoundsEnable;
95}
96
97void SetupDirtyDepthTestEnable(Tables& tables) {
98 tables[0][OFF(depth_test_enable)] = DepthTestEnable;
99}
100
101void SetupDirtyDepthWriteEnable(Tables& tables) {
102 tables[0][OFF(depth_write_enabled)] = DepthWriteEnable;
103}
104
105void SetupDirtyDepthCompareOp(Tables& tables) {
106 tables[0][OFF(depth_test_func)] = DepthCompareOp;
107}
108
109void SetupDirtyFrontFace(Tables& tables) {
110 auto& table = tables[0];
111 table[OFF(front_face)] = FrontFace;
112 table[OFF(screen_y_control)] = FrontFace;
113}
114
115void SetupDirtyPrimitiveTopology(Tables& tables) {
116 tables[0][OFF(draw.topology)] = PrimitiveTopology;
117}
118
119void SetupDirtyStencilOp(Tables& tables) {
120 auto& table = tables[0];
121 table[OFF(stencil_front_op_fail)] = StencilOp;
122 table[OFF(stencil_front_op_zfail)] = StencilOp;
123 table[OFF(stencil_front_op_zpass)] = StencilOp;
124 table[OFF(stencil_front_func_func)] = StencilOp;
125 table[OFF(stencil_back_op_fail)] = StencilOp;
126 table[OFF(stencil_back_op_zfail)] = StencilOp;
127 table[OFF(stencil_back_op_zpass)] = StencilOp;
128 table[OFF(stencil_back_func_func)] = StencilOp;
129
130 // Table 0 is used by StencilProperties
131 tables[1][OFF(stencil_two_side_enable)] = StencilOp;
132}
133
134void SetupDirtyStencilTestEnable(Tables& tables) {
135 tables[0][OFF(stencil_enable)] = StencilTestEnable;
136}
137
78} // Anonymous namespace 138} // Anonymous namespace
79 139
80StateTracker::StateTracker(Core::System& system) 140StateTracker::StateTracker(Core::System& system)
@@ -90,6 +150,14 @@ void StateTracker::Initialize() {
90 SetupDirtyBlendConstants(tables); 150 SetupDirtyBlendConstants(tables);
91 SetupDirtyDepthBounds(tables); 151 SetupDirtyDepthBounds(tables);
92 SetupDirtyStencilProperties(tables); 152 SetupDirtyStencilProperties(tables);
153 SetupDirtyCullMode(tables);
154 SetupDirtyDepthBoundsEnable(tables);
155 SetupDirtyDepthTestEnable(tables);
156 SetupDirtyDepthWriteEnable(tables);
157 SetupDirtyDepthCompareOp(tables);
158 SetupDirtyFrontFace(tables);
159 SetupDirtyPrimitiveTopology(tables);
160 SetupDirtyStencilOp(tables);
93} 161}
94 162
95void StateTracker::InvalidateCommandBufferState() { 163void 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 {
26 DepthBounds, 26 DepthBounds,
27 StencilProperties, 27 StencilProperties,
28 28
29 CullMode,
30 DepthBoundsEnable,
31 DepthTestEnable,
32 DepthWriteEnable,
33 DepthCompareOp,
34 FrontFace,
35 PrimitiveTopology,
36 StencilOp,
37 StencilTestEnable,
38
29 Last 39 Last
30}; 40};
31static_assert(Last <= std::numeric_limits<u8>::max()); 41static_assert(Last <= std::numeric_limits<u8>::max());
@@ -64,6 +74,46 @@ public:
64 return Exchange(Dirty::StencilProperties, false); 74 return Exchange(Dirty::StencilProperties, false);
65 } 75 }
66 76
77 bool TouchCullMode() {
78 return Exchange(Dirty::CullMode, false);
79 }
80
81 bool TouchDepthBoundsTestEnable() {
82 return Exchange(Dirty::DepthBoundsEnable, false);
83 }
84
85 bool TouchDepthTestEnable() {
86 return Exchange(Dirty::DepthTestEnable, false);
87 }
88
89 bool TouchDepthBoundsEnable() {
90 return Exchange(Dirty::DepthBoundsEnable, false);
91 }
92
93 bool TouchDepthWriteEnable() {
94 return Exchange(Dirty::DepthWriteEnable, false);
95 }
96
97 bool TouchDepthCompareOp() {
98 return Exchange(Dirty::DepthCompareOp, false);
99 }
100
101 bool TouchFrontFace() {
102 return Exchange(Dirty::FrontFace, false);
103 }
104
105 bool TouchPrimitiveTopology() {
106 return Exchange(Dirty::PrimitiveTopology, false);
107 }
108
109 bool TouchStencilOp() {
110 return Exchange(Dirty::StencilOp, false);
111 }
112
113 bool TouchStencilTestEnable() {
114 return Exchange(Dirty::StencilTestEnable, false);
115 }
116
67private: 117private:
68 bool Exchange(std::size_t id, bool new_value) const noexcept { 118 bool Exchange(std::size_t id, bool new_value) const noexcept {
69 auto& flags = system.GPU().Maxwell3D().dirty.flags; 119 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 {
88 X(vkCmdSetStencilWriteMask); 88 X(vkCmdSetStencilWriteMask);
89 X(vkCmdSetViewport); 89 X(vkCmdSetViewport);
90 X(vkCmdWaitEvents); 90 X(vkCmdWaitEvents);
91 X(vkCmdBindVertexBuffers2EXT);
92 X(vkCmdSetCullModeEXT);
93 X(vkCmdSetDepthBoundsTestEnableEXT);
94 X(vkCmdSetDepthCompareOpEXT);
95 X(vkCmdSetDepthTestEnableEXT);
96 X(vkCmdSetDepthWriteEnableEXT);
97 X(vkCmdSetFrontFaceEXT);
98 X(vkCmdSetPrimitiveTopologyEXT);
99 X(vkCmdSetStencilOpEXT);
100 X(vkCmdSetStencilTestEnableEXT);
91 X(vkCreateBuffer); 101 X(vkCreateBuffer);
92 X(vkCreateBufferView); 102 X(vkCreateBufferView);
93 X(vkCreateCommandPool); 103 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 {
207 PFN_vkCmdSetStencilWriteMask vkCmdSetStencilWriteMask; 207 PFN_vkCmdSetStencilWriteMask vkCmdSetStencilWriteMask;
208 PFN_vkCmdSetViewport vkCmdSetViewport; 208 PFN_vkCmdSetViewport vkCmdSetViewport;
209 PFN_vkCmdWaitEvents vkCmdWaitEvents; 209 PFN_vkCmdWaitEvents vkCmdWaitEvents;
210 PFN_vkCmdBindVertexBuffers2EXT vkCmdBindVertexBuffers2EXT;
211 PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT;
212 PFN_vkCmdSetDepthBoundsTestEnableEXT vkCmdSetDepthBoundsTestEnableEXT;
213 PFN_vkCmdSetDepthCompareOpEXT vkCmdSetDepthCompareOpEXT;
214 PFN_vkCmdSetDepthTestEnableEXT vkCmdSetDepthTestEnableEXT;
215 PFN_vkCmdSetDepthWriteEnableEXT vkCmdSetDepthWriteEnableEXT;
216 PFN_vkCmdSetFrontFaceEXT vkCmdSetFrontFaceEXT;
217 PFN_vkCmdSetPrimitiveTopologyEXT vkCmdSetPrimitiveTopologyEXT;
218 PFN_vkCmdSetStencilOpEXT vkCmdSetStencilOpEXT;
219 PFN_vkCmdSetStencilTestEnableEXT vkCmdSetStencilTestEnableEXT;
210 PFN_vkCreateBuffer vkCreateBuffer; 220 PFN_vkCreateBuffer vkCreateBuffer;
211 PFN_vkCreateBufferView vkCreateBufferView; 221 PFN_vkCreateBufferView vkCreateBufferView;
212 PFN_vkCreateCommandPool vkCreateCommandPool; 222 PFN_vkCreateCommandPool vkCreateCommandPool;
@@ -969,6 +979,50 @@ public:
969 buffer_barriers.data(), image_barriers.size(), image_barriers.data()); 979 buffer_barriers.data(), image_barriers.size(), image_barriers.data());
970 } 980 }
971 981
982 void BindVertexBuffers2EXT(u32 first_binding, u32 binding_count, const VkBuffer* buffers,
983 const VkDeviceSize* offsets, const VkDeviceSize* sizes,
984 const VkDeviceSize* strides) const noexcept {
985 dld->vkCmdBindVertexBuffers2EXT(handle, first_binding, binding_count, buffers, offsets,
986 sizes, strides);
987 }
988
989 void SetCullModeEXT(VkCullModeFlags cull_mode) const noexcept {
990 dld->vkCmdSetCullModeEXT(handle, cull_mode);
991 }
992
993 void SetDepthBoundsTestEnableEXT(bool enable) const noexcept {
994 dld->vkCmdSetDepthBoundsTestEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
995 }
996
997 void SetDepthCompareOpEXT(VkCompareOp compare_op) const noexcept {
998 dld->vkCmdSetDepthCompareOpEXT(handle, compare_op);
999 }
1000
1001 void SetDepthTestEnableEXT(bool enable) const noexcept {
1002 dld->vkCmdSetDepthTestEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
1003 }
1004
1005 void SetDepthWriteEnableEXT(bool enable) const noexcept {
1006 dld->vkCmdSetDepthWriteEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
1007 }
1008
1009 void SetFrontFaceEXT(VkFrontFace front_face) const noexcept {
1010 dld->vkCmdSetFrontFaceEXT(handle, front_face);
1011 }
1012
1013 void SetPrimitiveTopologyEXT(VkPrimitiveTopology primitive_topology) const noexcept {
1014 dld->vkCmdSetPrimitiveTopologyEXT(handle, primitive_topology);
1015 }
1016
1017 void SetStencilOpEXT(VkStencilFaceFlags face_mask, VkStencilOp fail_op, VkStencilOp pass_op,
1018 VkStencilOp depth_fail_op, VkCompareOp compare_op) const noexcept {
1019 dld->vkCmdSetStencilOpEXT(handle, face_mask, fail_op, pass_op, depth_fail_op, compare_op);
1020 }
1021
1022 void SetStencilTestEnableEXT(bool enable) const noexcept {
1023 dld->vkCmdSetStencilTestEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
1024 }
1025
972 void BindTransformFeedbackBuffersEXT(u32 first, u32 count, const VkBuffer* buffers, 1026 void BindTransformFeedbackBuffersEXT(u32 first, u32 count, const VkBuffer* buffers,
973 const VkDeviceSize* offsets, 1027 const VkDeviceSize* offsets,
974 const VkDeviceSize* sizes) const noexcept { 1028 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 {
20template <class T> 20template <class T>
21class ShaderCache { 21class ShaderCache {
22 static constexpr u64 PAGE_BITS = 14; 22 static constexpr u64 PAGE_BITS = 14;
23 static constexpr u64 PAGE_SIZE = u64(1) << PAGE_BITS;
23 24
24 struct Entry { 25 struct Entry {
25 VAddr addr_start; 26 VAddr addr_start;
@@ -87,8 +88,8 @@ protected:
87 const VAddr addr_end = addr + size; 88 const VAddr addr_end = addr + size;
88 Entry* const entry = NewEntry(addr, addr_end, data.get()); 89 Entry* const entry = NewEntry(addr, addr_end, data.get());
89 90
90 const u64 page_end = addr_end >> PAGE_BITS; 91 const u64 page_end = (addr_end + PAGE_SIZE - 1) >> PAGE_BITS;
91 for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) { 92 for (u64 page = addr >> PAGE_BITS; page < page_end; ++page) {
92 invalidation_cache[page].push_back(entry); 93 invalidation_cache[page].push_back(entry);
93 } 94 }
94 95
@@ -108,20 +109,13 @@ private:
108 /// @pre invalidation_mutex is locked 109 /// @pre invalidation_mutex is locked
109 void InvalidatePagesInRegion(VAddr addr, std::size_t size) { 110 void InvalidatePagesInRegion(VAddr addr, std::size_t size) {
110 const VAddr addr_end = addr + size; 111 const VAddr addr_end = addr + size;
111 const u64 page_end = addr_end >> PAGE_BITS; 112 const u64 page_end = (addr_end + PAGE_SIZE - 1) >> PAGE_BITS;
112 for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) { 113 for (u64 page = addr >> PAGE_BITS; page < page_end; ++page) {
113 const auto it = invalidation_cache.find(page); 114 auto it = invalidation_cache.find(page);
114 if (it == invalidation_cache.end()) { 115 if (it == invalidation_cache.end()) {
115 continue; 116 continue;
116 } 117 }
117 118 InvalidatePageEntries(it->second, addr, addr_end);
118 std::vector<Entry*>& entries = it->second;
119 InvalidatePageEntries(entries, addr, addr_end);
120
121 // If there's nothing else in this page, remove it to avoid overpopulating the hash map.
122 if (entries.empty()) {
123 invalidation_cache.erase(it);
124 }
125 } 119 }
126 } 120 }
127 121
@@ -131,15 +125,22 @@ private:
131 if (marked_for_removal.empty()) { 125 if (marked_for_removal.empty()) {
132 return; 126 return;
133 } 127 }
134 std::scoped_lock lock{lookup_mutex}; 128 // Remove duplicates
129 std::sort(marked_for_removal.begin(), marked_for_removal.end());
130 marked_for_removal.erase(std::unique(marked_for_removal.begin(), marked_for_removal.end()),
131 marked_for_removal.end());
135 132
136 std::vector<T*> removed_shaders; 133 std::vector<T*> removed_shaders;
137 removed_shaders.reserve(marked_for_removal.size()); 134 removed_shaders.reserve(marked_for_removal.size());
138 135
136 std::scoped_lock lock{lookup_mutex};
137
139 for (Entry* const entry : marked_for_removal) { 138 for (Entry* const entry : marked_for_removal) {
140 if (lookup_cache.erase(entry->addr_start) > 0) { 139 removed_shaders.push_back(entry->data);
141 removed_shaders.push_back(entry->data); 140
142 } 141 const auto it = lookup_cache.find(entry->addr_start);
142 ASSERT(it != lookup_cache.end());
143 lookup_cache.erase(it);
143 } 144 }
144 marked_for_removal.clear(); 145 marked_for_removal.clear();
145 146
@@ -154,17 +155,33 @@ private:
154 /// @param addr_end Non-inclusive end address of the invalidation 155 /// @param addr_end Non-inclusive end address of the invalidation
155 /// @pre invalidation_mutex is locked 156 /// @pre invalidation_mutex is locked
156 void InvalidatePageEntries(std::vector<Entry*>& entries, VAddr addr, VAddr addr_end) { 157 void InvalidatePageEntries(std::vector<Entry*>& entries, VAddr addr, VAddr addr_end) {
157 auto it = entries.begin(); 158 std::size_t index = 0;
158 while (it != entries.end()) { 159 while (index < entries.size()) {
159 Entry* const entry = *it; 160 Entry* const entry = entries[index];
160 if (!entry->Overlaps(addr, addr_end)) { 161 if (!entry->Overlaps(addr, addr_end)) {
161 ++it; 162 ++index;
162 continue; 163 continue;
163 } 164 }
165
164 UnmarkMemory(entry); 166 UnmarkMemory(entry);
167 RemoveEntryFromInvalidationCache(entry);
165 marked_for_removal.push_back(entry); 168 marked_for_removal.push_back(entry);
169 }
170 }
166 171
167 it = entries.erase(it); 172 /// @brief Removes all references to an entry in the invalidation cache
173 /// @param entry Entry to remove from the invalidation cache
174 /// @pre invalidation_mutex is locked
175 void RemoveEntryFromInvalidationCache(const Entry* entry) {
176 const u64 page_end = (entry->addr_end + PAGE_SIZE - 1) >> PAGE_BITS;
177 for (u64 page = entry->addr_start >> PAGE_BITS; page < page_end; ++page) {
178 const auto entries_it = invalidation_cache.find(page);
179 ASSERT(entries_it != invalidation_cache.end());
180 std::vector<Entry*>& entries = entries_it->second;
181
182 const auto entry_it = std::find(entries.begin(), entries.end(), entry);
183 ASSERT(entry_it != entries.end());
184 entries.erase(entry_it);
168 } 185 }
169 } 186 }
170 187
@@ -182,16 +199,11 @@ private:
182 } 199 }
183 200
184 /// @brief Removes a vector of shaders from a list 201 /// @brief Removes a vector of shaders from a list
185 /// @param removed_shaders Shaders to be removed from the storage, it can contain duplicates 202 /// @param removed_shaders Shaders to be removed from the storage
186 /// @pre invalidation_mutex is locked 203 /// @pre invalidation_mutex is locked
187 /// @pre lookup_mutex is locked 204 /// @pre lookup_mutex is locked
188 void RemoveShadersFromStorage(std::vector<T*> removed_shaders) { 205 void RemoveShadersFromStorage(std::vector<T*> removed_shaders) {
189 // Remove duplicates 206 // Notify removals
190 std::sort(removed_shaders.begin(), removed_shaders.end());
191 removed_shaders.erase(std::unique(removed_shaders.begin(), removed_shaders.end()),
192 removed_shaders.end());
193
194 // Now that there are no duplicates, we can notify removals
195 for (T* const shader : removed_shaders) { 207 for (T* const shader : removed_shaders) {
196 OnShaderRemoval(shader); 208 OnShaderRemoval(shader);
197 } 209 }
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
212// UISetting::values.shortcuts, which is alphabetically ordered. 212// UISetting::values.shortcuts, which is alphabetically ordered.
213// clang-format off 213// clang-format off
214const std::array<UISettings::Shortcut, 16> Config::default_hotkeys{{ 214const std::array<UISettings::Shortcut, 16> Config::default_hotkeys{{
215 {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::ApplicationShortcut}}, 215 {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}},
216 {QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}}, 216 {QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}},
217 {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}}, 217 {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},
218 {QStringLiteral("Decrease Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("-"), Qt::ApplicationShortcut}}, 218 {QStringLiteral("Decrease Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("-"), Qt::ApplicationShortcut}},
@@ -220,8 +220,8 @@ const std::array<UISettings::Shortcut, 16> Config::default_hotkeys{{
220 {QStringLiteral("Exit yuzu"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), Qt::WindowShortcut}}, 220 {QStringLiteral("Exit yuzu"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), Qt::WindowShortcut}},
221 {QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), Qt::WindowShortcut}}, 221 {QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), Qt::WindowShortcut}},
222 {QStringLiteral("Increase Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("+"), Qt::ApplicationShortcut}}, 222 {QStringLiteral("Increase Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("+"), Qt::ApplicationShortcut}},
223 {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::ApplicationShortcut}}, 223 {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::WidgetWithChildrenShortcut}},
224 {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WindowShortcut}}, 224 {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}},
225 {QStringLiteral("Mute Audio"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}}, 225 {QStringLiteral("Mute Audio"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},
226 {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}}, 226 {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
227 {QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}}, 227 {QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}},
@@ -665,11 +665,13 @@ void Config::ReadShortcutValues() {
665 const auto& [keyseq, context] = shortcut; 665 const auto& [keyseq, context] = shortcut;
666 qt_config->beginGroup(group); 666 qt_config->beginGroup(group);
667 qt_config->beginGroup(name); 667 qt_config->beginGroup(name);
668 // No longer using ReadSetting for shortcut.second as it innacurately returns a value of 1
669 // for WidgetWithChildrenShortcut which is a value of 3. Needed to fix shortcuts the open
670 // a file dialog in windowed mode
668 UISettings::values.shortcuts.push_back( 671 UISettings::values.shortcuts.push_back(
669 {name, 672 {name,
670 group, 673 group,
671 {ReadSetting(QStringLiteral("KeySeq"), keyseq).toString(), 674 {ReadSetting(QStringLiteral("KeySeq"), keyseq).toString(), shortcut.second}});
672 ReadSetting(QStringLiteral("Context"), context).toInt()}});
673 qt_config->endGroup(); 675 qt_config->endGroup();
674 qt_config->endGroup(); 676 qt_config->endGroup();
675 } 677 }
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)
28 28
29 SetConfiguration(); 29 SetConfiguration();
30 30
31 connect(ui->api, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, 31 connect(ui->api, qOverload<int>(&QComboBox::currentIndexChanged), this,
32 [this] { UpdateDeviceComboBox(); }); 32 [this] { UpdateDeviceComboBox(); });
33 connect(ui->device, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, 33 connect(ui->device, qOverload<int>(&QComboBox::activated), this,
34 [this](int device) { UpdateDeviceSelection(device); }); 34 [this](int device) { UpdateDeviceSelection(device); });
35 35
36 connect(ui->bg_button, &QPushButton::clicked, this, [this] { 36 connect(ui->bg_button, &QPushButton::clicked, this, [this] {
@@ -112,7 +112,7 @@ void ConfigureGraphics::UpdateDeviceComboBox() {
112 enabled = false; 112 enabled = false;
113 break; 113 break;
114 case Settings::RendererBackend::Vulkan: 114 case Settings::RendererBackend::Vulkan:
115 for (const auto device : vulkan_devices) { 115 for (const auto& device : vulkan_devices) {
116 ui->device->addItem(device); 116 ui->device->addItem(device);
117 } 117 }
118 ui->device->setCurrentIndex(vulkan_device); 118 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) {
70 return GetKeyName(param.Get("code", 0)); 70 return GetKeyName(param.Get("code", 0));
71 } 71 }
72 72
73 if (param.Get("engine", "") == "gcpad") {
74 if (param.Has("axis")) {
75 const QString axis_str = QString::fromStdString(param.Get("axis", ""));
76 const QString direction_str = QString::fromStdString(param.Get("direction", ""));
77
78 return QObject::tr("GC Axis %1%2").arg(axis_str, direction_str);
79 }
80 if (param.Has("button")) {
81 const QString button_str = QString::number(int(std::log2(param.Get("button", 0))));
82 return QObject::tr("GC Button %1").arg(button_str);
83 }
84 return GetKeyName(param.Get("code", 0));
85 }
86
73 if (param.Get("engine", "") == "sdl") { 87 if (param.Get("engine", "") == "sdl") {
74 if (param.Has("hat")) { 88 if (param.Has("hat")) {
75 const QString hat_str = QString::fromStdString(param.Get("hat", "")); 89 const QString hat_str = QString::fromStdString(param.Get("hat", ""));
@@ -126,6 +140,25 @@ static QString AnalogToText(const Common::ParamPackage& param, const std::string
126 return {}; 140 return {};
127 } 141 }
128 142
143 if (param.Get("engine", "") == "gcpad") {
144 if (dir == "modifier") {
145 return QObject::tr("[unused]");
146 }
147
148 if (dir == "left" || dir == "right") {
149 const QString axis_x_str = QString::fromStdString(param.Get("axis_x", ""));
150
151 return QObject::tr("GC Axis %1").arg(axis_x_str);
152 }
153
154 if (dir == "up" || dir == "down") {
155 const QString axis_y_str = QString::fromStdString(param.Get("axis_y", ""));
156
157 return QObject::tr("GC Axis %1").arg(axis_y_str);
158 }
159
160 return {};
161 }
129 return QObject::tr("[unknown]"); 162 return QObject::tr("[unknown]");
130} 163}
131 164
@@ -332,7 +365,8 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
332 365
333 connect(analog_map_deadzone_and_modifier_slider[analog_id], &QSlider::valueChanged, [=] { 366 connect(analog_map_deadzone_and_modifier_slider[analog_id], &QSlider::valueChanged, [=] {
334 const float slider_value = analog_map_deadzone_and_modifier_slider[analog_id]->value(); 367 const float slider_value = analog_map_deadzone_and_modifier_slider[analog_id]->value();
335 if (analogs_param[analog_id].Get("engine", "") == "sdl") { 368 if (analogs_param[analog_id].Get("engine", "") == "sdl" ||
369 analogs_param[analog_id].Get("engine", "") == "gcpad") {
336 analog_map_deadzone_and_modifier_slider_label[analog_id]->setText( 370 analog_map_deadzone_and_modifier_slider_label[analog_id]->setText(
337 tr("Deadzone: %1%").arg(slider_value)); 371 tr("Deadzone: %1%").arg(slider_value));
338 analogs_param[analog_id].Set("deadzone", slider_value / 100.0f); 372 analogs_param[analog_id].Set("deadzone", slider_value / 100.0f);
@@ -352,6 +386,20 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
352 386
353 connect(poll_timer.get(), &QTimer::timeout, [this] { 387 connect(poll_timer.get(), &QTimer::timeout, [this] {
354 Common::ParamPackage params; 388 Common::ParamPackage params;
389 if (InputCommon::GetGCButtons()->IsPolling()) {
390 params = InputCommon::GetGCButtons()->GetNextInput();
391 if (params.Has("engine")) {
392 SetPollingResult(params, false);
393 return;
394 }
395 }
396 if (InputCommon::GetGCAnalogs()->IsPolling()) {
397 params = InputCommon::GetGCAnalogs()->GetNextInput();
398 if (params.Has("engine")) {
399 SetPollingResult(params, false);
400 return;
401 }
402 }
355 for (auto& poller : device_pollers) { 403 for (auto& poller : device_pollers) {
356 params = poller->GetNextInput(); 404 params = poller->GetNextInput();
357 if (params.Has("engine")) { 405 if (params.Has("engine")) {
@@ -534,7 +582,7 @@ void ConfigureInputPlayer::UpdateButtonLabels() {
534 analog_map_deadzone_and_modifier_slider_label[analog_id]; 582 analog_map_deadzone_and_modifier_slider_label[analog_id];
535 583
536 if (param.Has("engine")) { 584 if (param.Has("engine")) {
537 if (param.Get("engine", "") == "sdl") { 585 if (param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad") {
538 if (!param.Has("deadzone")) { 586 if (!param.Has("deadzone")) {
539 param.Set("deadzone", 0.1f); 587 param.Set("deadzone", 0.1f);
540 } 588 }
@@ -583,6 +631,11 @@ void ConfigureInputPlayer::HandleClick(
583 631
584 grabKeyboard(); 632 grabKeyboard();
585 grabMouse(); 633 grabMouse();
634 if (type == InputCommon::Polling::DeviceType::Button) {
635 InputCommon::GetGCButtons()->BeginConfiguration();
636 } else {
637 InputCommon::GetGCAnalogs()->BeginConfiguration();
638 }
586 timeout_timer->start(5000); // Cancel after 5 seconds 639 timeout_timer->start(5000); // Cancel after 5 seconds
587 poll_timer->start(200); // Check for new inputs every 200ms 640 poll_timer->start(200); // Check for new inputs every 200ms
588} 641}
@@ -596,6 +649,9 @@ void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params,
596 poller->Stop(); 649 poller->Stop();
597 } 650 }
598 651
652 InputCommon::GetGCButtons()->EndConfiguration();
653 InputCommon::GetGCAnalogs()->EndConfiguration();
654
599 if (!abort) { 655 if (!abort) {
600 (*input_setter)(params); 656 (*input_setter)(params);
601 } 657 }
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() {
753 }); 753 });
754 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Capture Screenshot"), this), 754 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Capture Screenshot"), this),
755 &QShortcut::activated, this, [&] { 755 &QShortcut::activated, this, [&] {
756 if (emu_thread->IsRunning()) { 756 if (emu_thread != nullptr && emu_thread->IsRunning()) {
757 OnCaptureScreenshot(); 757 OnCaptureScreenshot();
758 } 758 }
759 }); 759 });