diff options
Diffstat (limited to 'src')
43 files changed, 953 insertions, 207 deletions
diff --git a/src/common/settings.cpp b/src/common/settings.cpp index ba617aea1..ff53e80bb 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp | |||
| @@ -61,6 +61,7 @@ void LogSettings() { | |||
| 61 | log_setting("Renderer_NvdecEmulation", values.nvdec_emulation.GetValue()); | 61 | log_setting("Renderer_NvdecEmulation", values.nvdec_emulation.GetValue()); |
| 62 | log_setting("Renderer_AccelerateASTC", values.accelerate_astc.GetValue()); | 62 | log_setting("Renderer_AccelerateASTC", values.accelerate_astc.GetValue()); |
| 63 | log_setting("Renderer_AsyncASTC", values.async_astc.GetValue()); | 63 | log_setting("Renderer_AsyncASTC", values.async_astc.GetValue()); |
| 64 | log_setting("Renderer_AstcRecompression", values.astc_recompression.GetValue()); | ||
| 64 | log_setting("Renderer_UseVsync", values.vsync_mode.GetValue()); | 65 | log_setting("Renderer_UseVsync", values.vsync_mode.GetValue()); |
| 65 | log_setting("Renderer_UseReactiveFlushing", values.use_reactive_flushing.GetValue()); | 66 | log_setting("Renderer_UseReactiveFlushing", values.use_reactive_flushing.GetValue()); |
| 66 | log_setting("Renderer_ShaderBackend", values.shader_backend.GetValue()); | 67 | log_setting("Renderer_ShaderBackend", values.shader_backend.GetValue()); |
| @@ -224,6 +225,7 @@ void RestoreGlobalState(bool is_powered_on) { | |||
| 224 | values.nvdec_emulation.SetGlobal(true); | 225 | values.nvdec_emulation.SetGlobal(true); |
| 225 | values.accelerate_astc.SetGlobal(true); | 226 | values.accelerate_astc.SetGlobal(true); |
| 226 | values.async_astc.SetGlobal(true); | 227 | values.async_astc.SetGlobal(true); |
| 228 | values.astc_recompression.SetGlobal(true); | ||
| 227 | values.use_reactive_flushing.SetGlobal(true); | 229 | values.use_reactive_flushing.SetGlobal(true); |
| 228 | values.shader_backend.SetGlobal(true); | 230 | values.shader_backend.SetGlobal(true); |
| 229 | values.use_asynchronous_shaders.SetGlobal(true); | 231 | values.use_asynchronous_shaders.SetGlobal(true); |
diff --git a/src/common/settings.h b/src/common/settings.h index 36ffcd693..7f865b2a7 100644 --- a/src/common/settings.h +++ b/src/common/settings.h | |||
| @@ -90,6 +90,12 @@ enum class AntiAliasing : u32 { | |||
| 90 | LastAA = Smaa, | 90 | LastAA = Smaa, |
| 91 | }; | 91 | }; |
| 92 | 92 | ||
| 93 | enum class AstcRecompression : u32 { | ||
| 94 | Uncompressed = 0, | ||
| 95 | Bc1 = 1, | ||
| 96 | Bc3 = 2, | ||
| 97 | }; | ||
| 98 | |||
| 93 | struct ResolutionScalingInfo { | 99 | struct ResolutionScalingInfo { |
| 94 | u32 up_scale{1}; | 100 | u32 up_scale{1}; |
| 95 | u32 down_shift{0}; | 101 | u32 down_shift{0}; |
| @@ -473,6 +479,9 @@ struct Values { | |||
| 473 | SwitchableSetting<bool> use_vulkan_driver_pipeline_cache{true, | 479 | SwitchableSetting<bool> use_vulkan_driver_pipeline_cache{true, |
| 474 | "use_vulkan_driver_pipeline_cache"}; | 480 | "use_vulkan_driver_pipeline_cache"}; |
| 475 | SwitchableSetting<bool> enable_compute_pipelines{false, "enable_compute_pipelines"}; | 481 | SwitchableSetting<bool> enable_compute_pipelines{false, "enable_compute_pipelines"}; |
| 482 | SwitchableSetting<AstcRecompression, true> astc_recompression{ | ||
| 483 | AstcRecompression::Uncompressed, AstcRecompression::Uncompressed, AstcRecompression::Bc3, | ||
| 484 | "astc_recompression"}; | ||
| 476 | 485 | ||
| 477 | SwitchableSetting<u8> bg_red{0, "bg_red"}; | 486 | SwitchableSetting<u8> bg_red{0, "bg_red"}; |
| 478 | SwitchableSetting<u8> bg_green{0, "bg_green"}; | 487 | SwitchableSetting<u8> bg_green{0, "bg_green"}; |
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index 366880711..bbfea7117 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp | |||
| @@ -1283,9 +1283,14 @@ bool EmulatedController::HasNfc() const { | |||
| 1283 | } | 1283 | } |
| 1284 | 1284 | ||
| 1285 | bool EmulatedController::WriteNfc(const std::vector<u8>& data) { | 1285 | bool EmulatedController::WriteNfc(const std::vector<u8>& data) { |
| 1286 | auto& nfc_output_device = output_devices[3]; | 1286 | auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; |
| 1287 | auto& nfc_virtual_output_device = output_devices[3]; | ||
| 1288 | |||
| 1289 | if (nfc_output_device->SupportsNfc() != Common::Input::NfcState::NotSupported) { | ||
| 1290 | return nfc_output_device->WriteNfcData(data) == Common::Input::NfcState::Success; | ||
| 1291 | } | ||
| 1287 | 1292 | ||
| 1288 | return nfc_output_device->WriteNfcData(data) == Common::Input::NfcState::Success; | 1293 | return nfc_virtual_output_device->WriteNfcData(data) == Common::Input::NfcState::Success; |
| 1289 | } | 1294 | } |
| 1290 | 1295 | ||
| 1291 | void EmulatedController::SetLedPattern() { | 1296 | void EmulatedController::SetLedPattern() { |
diff --git a/src/core/hle/kernel/k_memory_block_manager.h b/src/core/hle/kernel/k_memory_block_manager.h index 7c0bd16f0..96496e990 100644 --- a/src/core/hle/kernel/k_memory_block_manager.h +++ b/src/core/hle/kernel/k_memory_block_manager.h | |||
| @@ -144,14 +144,10 @@ private: | |||
| 144 | 144 | ||
| 145 | class KScopedMemoryBlockManagerAuditor { | 145 | class KScopedMemoryBlockManagerAuditor { |
| 146 | public: | 146 | public: |
| 147 | explicit KScopedMemoryBlockManagerAuditor(KMemoryBlockManager* m) : m_manager(m) { | 147 | explicit KScopedMemoryBlockManagerAuditor(KMemoryBlockManager* m) : m_manager(m) {} |
| 148 | ASSERT(m_manager->CheckState()); | ||
| 149 | } | ||
| 150 | explicit KScopedMemoryBlockManagerAuditor(KMemoryBlockManager& m) | 148 | explicit KScopedMemoryBlockManagerAuditor(KMemoryBlockManager& m) |
| 151 | : KScopedMemoryBlockManagerAuditor(std::addressof(m)) {} | 149 | : KScopedMemoryBlockManagerAuditor(std::addressof(m)) {} |
| 152 | ~KScopedMemoryBlockManagerAuditor() { | 150 | ~KScopedMemoryBlockManagerAuditor() = default; |
| 153 | ASSERT(m_manager->CheckState()); | ||
| 154 | } | ||
| 155 | 151 | ||
| 156 | private: | 152 | private: |
| 157 | KMemoryBlockManager* m_manager; | 153 | KMemoryBlockManager* m_manager; |
diff --git a/src/core/hle/service/nfc/common/amiibo_crypto.cpp b/src/core/hle/service/nfc/common/amiibo_crypto.cpp index f3901ee8d..b2bcb68c3 100644 --- a/src/core/hle/service/nfc/common/amiibo_crypto.cpp +++ b/src/core/hle/service/nfc/common/amiibo_crypto.cpp | |||
| @@ -52,9 +52,6 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) { | |||
| 52 | if (ntag_file.compability_container != 0xEEFF10F1U) { | 52 | if (ntag_file.compability_container != 0xEEFF10F1U) { |
| 53 | return false; | 53 | return false; |
| 54 | } | 54 | } |
| 55 | if (amiibo_data.constant_value != 0xA5) { | ||
| 56 | return false; | ||
| 57 | } | ||
| 58 | if (amiibo_data.model_info.tag_type != NFC::PackedTagType::Type2) { | 55 | if (amiibo_data.model_info.tag_type != NFC::PackedTagType::Type2) { |
| 59 | return false; | 56 | return false; |
| 60 | } | 57 | } |
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp index 322bde2ed..0bd7900e1 100644 --- a/src/core/hle/service/nfc/common/device.cpp +++ b/src/core/hle/service/nfc/common/device.cpp | |||
| @@ -119,18 +119,31 @@ bool NfcDevice::LoadNfcTag(std::span<const u8> data) { | |||
| 119 | 119 | ||
| 120 | memcpy(&tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File)); | 120 | memcpy(&tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File)); |
| 121 | is_plain_amiibo = NFP::AmiiboCrypto::IsAmiiboValid(tag_data); | 121 | is_plain_amiibo = NFP::AmiiboCrypto::IsAmiiboValid(tag_data); |
| 122 | is_write_protected = false; | ||
| 122 | 123 | ||
| 124 | device_state = DeviceState::TagFound; | ||
| 125 | deactivate_event->GetReadableEvent().Clear(); | ||
| 126 | activate_event->Signal(); | ||
| 127 | |||
| 128 | // Fallback for plain amiibos | ||
| 123 | if (is_plain_amiibo) { | 129 | if (is_plain_amiibo) { |
| 124 | encrypted_tag_data = NFP::AmiiboCrypto::EncodedDataToNfcData(tag_data); | ||
| 125 | LOG_INFO(Service_NFP, "Using plain amiibo"); | 130 | LOG_INFO(Service_NFP, "Using plain amiibo"); |
| 126 | } else { | 131 | encrypted_tag_data = NFP::AmiiboCrypto::EncodedDataToNfcData(tag_data); |
| 127 | tag_data = {}; | 132 | return true; |
| 133 | } | ||
| 134 | |||
| 135 | // Fallback for encrypted amiibos without keys | ||
| 136 | if (!NFP::AmiiboCrypto::IsKeyAvailable()) { | ||
| 137 | LOG_INFO(Service_NFC, "Loading amiibo without keys"); | ||
| 128 | memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File)); | 138 | memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File)); |
| 139 | BuildAmiiboWithoutKeys(); | ||
| 140 | is_plain_amiibo = true; | ||
| 141 | is_write_protected = true; | ||
| 142 | return true; | ||
| 129 | } | 143 | } |
| 130 | 144 | ||
| 131 | device_state = DeviceState::TagFound; | 145 | tag_data = {}; |
| 132 | deactivate_event->GetReadableEvent().Clear(); | 146 | memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File)); |
| 133 | activate_event->Signal(); | ||
| 134 | return true; | 147 | return true; |
| 135 | } | 148 | } |
| 136 | 149 | ||
| @@ -346,23 +359,15 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target | |||
| 346 | return ResultWrongDeviceState; | 359 | return ResultWrongDeviceState; |
| 347 | } | 360 | } |
| 348 | 361 | ||
| 349 | // The loaded amiibo is not encrypted | ||
| 350 | if (is_plain_amiibo) { | ||
| 351 | device_state = DeviceState::TagMounted; | ||
| 352 | mount_target = mount_target_; | ||
| 353 | return ResultSuccess; | ||
| 354 | } | ||
| 355 | |||
| 356 | if (!NFP::AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) { | 362 | if (!NFP::AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) { |
| 357 | LOG_ERROR(Service_NFP, "Not an amiibo"); | 363 | LOG_ERROR(Service_NFP, "Not an amiibo"); |
| 358 | return ResultNotAnAmiibo; | 364 | return ResultNotAnAmiibo; |
| 359 | } | 365 | } |
| 360 | 366 | ||
| 361 | // Mark amiibos as read only when keys are missing | 367 | // The loaded amiibo is not encrypted |
| 362 | if (!NFP::AmiiboCrypto::IsKeyAvailable()) { | 368 | if (is_plain_amiibo) { |
| 363 | LOG_ERROR(Service_NFP, "No keys detected"); | ||
| 364 | device_state = DeviceState::TagMounted; | 369 | device_state = DeviceState::TagMounted; |
| 365 | mount_target = NFP::MountTarget::Rom; | 370 | mount_target = mount_target_; |
| 366 | return ResultSuccess; | 371 | return ResultSuccess; |
| 367 | } | 372 | } |
| 368 | 373 | ||
| @@ -421,11 +426,11 @@ Result NfcDevice::Flush() { | |||
| 421 | 426 | ||
| 422 | tag_data.write_counter++; | 427 | tag_data.write_counter++; |
| 423 | 428 | ||
| 424 | FlushWithBreak(NFP::BreakType::Normal); | 429 | const auto result = FlushWithBreak(NFP::BreakType::Normal); |
| 425 | 430 | ||
| 426 | is_data_moddified = false; | 431 | is_data_moddified = false; |
| 427 | 432 | ||
| 428 | return ResultSuccess; | 433 | return result; |
| 429 | } | 434 | } |
| 430 | 435 | ||
| 431 | Result NfcDevice::FlushDebug() { | 436 | Result NfcDevice::FlushDebug() { |
| @@ -444,11 +449,11 @@ Result NfcDevice::FlushDebug() { | |||
| 444 | 449 | ||
| 445 | tag_data.write_counter++; | 450 | tag_data.write_counter++; |
| 446 | 451 | ||
| 447 | FlushWithBreak(NFP::BreakType::Normal); | 452 | const auto result = FlushWithBreak(NFP::BreakType::Normal); |
| 448 | 453 | ||
| 449 | is_data_moddified = false; | 454 | is_data_moddified = false; |
| 450 | 455 | ||
| 451 | return ResultSuccess; | 456 | return result; |
| 452 | } | 457 | } |
| 453 | 458 | ||
| 454 | Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) { | 459 | Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) { |
| @@ -457,6 +462,11 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) { | |||
| 457 | return ResultWrongDeviceState; | 462 | return ResultWrongDeviceState; |
| 458 | } | 463 | } |
| 459 | 464 | ||
| 465 | if (is_write_protected) { | ||
| 466 | LOG_ERROR(Service_NFP, "No keys available skipping write request"); | ||
| 467 | return ResultSuccess; | ||
| 468 | } | ||
| 469 | |||
| 460 | std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File)); | 470 | std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File)); |
| 461 | if (is_plain_amiibo) { | 471 | if (is_plain_amiibo) { |
| 462 | memcpy(data.data(), &tag_data, sizeof(tag_data)); | 472 | memcpy(data.data(), &tag_data, sizeof(tag_data)); |
| @@ -1033,7 +1043,6 @@ Result NfcDevice::GetAll(NFP::NfpData& data) const { | |||
| 1033 | } | 1043 | } |
| 1034 | 1044 | ||
| 1035 | NFP::CommonInfo common_info{}; | 1045 | NFP::CommonInfo common_info{}; |
| 1036 | Service::Mii::MiiManager manager; | ||
| 1037 | const u64 application_id = tag_data.application_id; | 1046 | const u64 application_id = tag_data.application_id; |
| 1038 | 1047 | ||
| 1039 | GetCommonInfo(common_info); | 1048 | GetCommonInfo(common_info); |
| @@ -1249,6 +1258,28 @@ void NfcDevice::UpdateRegisterInfoCrc() { | |||
| 1249 | tag_data.register_info_crc = crc.checksum(); | 1258 | tag_data.register_info_crc = crc.checksum(); |
| 1250 | } | 1259 | } |
| 1251 | 1260 | ||
| 1261 | void NfcDevice::BuildAmiiboWithoutKeys() { | ||
| 1262 | Service::Mii::MiiManager manager; | ||
| 1263 | auto& settings = tag_data.settings; | ||
| 1264 | |||
| 1265 | tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_tag_data); | ||
| 1266 | |||
| 1267 | // Common info | ||
| 1268 | tag_data.write_counter = 0; | ||
| 1269 | tag_data.amiibo_version = 0; | ||
| 1270 | settings.write_date = GetAmiiboDate(GetCurrentPosixTime()); | ||
| 1271 | |||
| 1272 | // Register info | ||
| 1273 | SetAmiiboName(settings, {'y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o'}); | ||
| 1274 | settings.settings.font_region.Assign(0); | ||
| 1275 | settings.init_date = GetAmiiboDate(GetCurrentPosixTime()); | ||
| 1276 | tag_data.owner_mii = manager.BuildFromStoreData(manager.BuildDefault(0)); | ||
| 1277 | |||
| 1278 | // Admin info | ||
| 1279 | settings.settings.amiibo_initialized.Assign(1); | ||
| 1280 | settings.settings.appdata_initialized.Assign(0); | ||
| 1281 | } | ||
| 1282 | |||
| 1252 | u64 NfcDevice::GetHandle() const { | 1283 | u64 NfcDevice::GetHandle() const { |
| 1253 | // Generate a handle based of the npad id | 1284 | // Generate a handle based of the npad id |
| 1254 | return static_cast<u64>(npad_id); | 1285 | return static_cast<u64>(npad_id); |
diff --git a/src/core/hle/service/nfc/common/device.h b/src/core/hle/service/nfc/common/device.h index 98e1945c1..6a37e8458 100644 --- a/src/core/hle/service/nfc/common/device.h +++ b/src/core/hle/service/nfc/common/device.h | |||
| @@ -110,6 +110,8 @@ private: | |||
| 110 | void UpdateSettingsCrc(); | 110 | void UpdateSettingsCrc(); |
| 111 | void UpdateRegisterInfoCrc(); | 111 | void UpdateRegisterInfoCrc(); |
| 112 | 112 | ||
| 113 | void BuildAmiiboWithoutKeys(); | ||
| 114 | |||
| 113 | bool is_controller_set{}; | 115 | bool is_controller_set{}; |
| 114 | int callback_key; | 116 | int callback_key; |
| 115 | const Core::HID::NpadIdType npad_id; | 117 | const Core::HID::NpadIdType npad_id; |
| @@ -128,6 +130,7 @@ private: | |||
| 128 | bool is_data_moddified{}; | 130 | bool is_data_moddified{}; |
| 129 | bool is_app_area_open{}; | 131 | bool is_app_area_open{}; |
| 130 | bool is_plain_amiibo{}; | 132 | bool is_plain_amiibo{}; |
| 133 | bool is_write_protected{}; | ||
| 131 | NFP::MountTarget mount_target{NFP::MountTarget::None}; | 134 | NFP::MountTarget mount_target{NFP::MountTarget::None}; |
| 132 | 135 | ||
| 133 | NFP::NTAG215File tag_data{}; | 136 | NFP::NTAG215File tag_data{}; |
diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp index 8b57ebe07..b2b5677c8 100644 --- a/src/input_common/drivers/joycon.cpp +++ b/src/input_common/drivers/joycon.cpp | |||
| @@ -195,8 +195,8 @@ void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) { | |||
| 195 | OnMotionUpdate(port, type, id, value); | 195 | OnMotionUpdate(port, type, id, value); |
| 196 | }}, | 196 | }}, |
| 197 | .on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }}, | 197 | .on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }}, |
| 198 | .on_amiibo_data = {[this, port](const std::vector<u8>& amiibo_data) { | 198 | .on_amiibo_data = {[this, port, type](const std::vector<u8>& amiibo_data) { |
| 199 | OnAmiiboUpdate(port, amiibo_data); | 199 | OnAmiiboUpdate(port, type, amiibo_data); |
| 200 | }}, | 200 | }}, |
| 201 | .on_camera_data = {[this, port](const std::vector<u8>& camera_data, | 201 | .on_camera_data = {[this, port](const std::vector<u8>& camera_data, |
| 202 | Joycon::IrsResolution format) { | 202 | Joycon::IrsResolution format) { |
| @@ -291,9 +291,13 @@ Common::Input::NfcState Joycons::SupportsNfc(const PadIdentifier& identifier_) c | |||
| 291 | return Common::Input::NfcState::Success; | 291 | return Common::Input::NfcState::Success; |
| 292 | }; | 292 | }; |
| 293 | 293 | ||
| 294 | Common::Input::NfcState Joycons::WriteNfcData(const PadIdentifier& identifier_, | 294 | Common::Input::NfcState Joycons::WriteNfcData(const PadIdentifier& identifier, |
| 295 | const std::vector<u8>& data) { | 295 | const std::vector<u8>& data) { |
| 296 | return Common::Input::NfcState::NotSupported; | 296 | auto handle = GetHandle(identifier); |
| 297 | if (handle->WriteNfcData(data) != Joycon::DriverResult::Success) { | ||
| 298 | return Common::Input::NfcState::WriteFailed; | ||
| 299 | } | ||
| 300 | return Common::Input::NfcState::Success; | ||
| 297 | }; | 301 | }; |
| 298 | 302 | ||
| 299 | Common::Input::DriverResult Joycons::SetPollingMode(const PadIdentifier& identifier, | 303 | Common::Input::DriverResult Joycons::SetPollingMode(const PadIdentifier& identifier, |
| @@ -398,8 +402,9 @@ void Joycons::OnRingConUpdate(f32 ring_data) { | |||
| 398 | SetAxis(identifier, 100, ring_data); | 402 | SetAxis(identifier, 100, ring_data); |
| 399 | } | 403 | } |
| 400 | 404 | ||
| 401 | void Joycons::OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data) { | 405 | void Joycons::OnAmiiboUpdate(std::size_t port, Joycon::ControllerType type, |
| 402 | const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right); | 406 | const std::vector<u8>& amiibo_data) { |
| 407 | const auto identifier = GetIdentifier(port, type); | ||
| 403 | const auto nfc_state = amiibo_data.empty() ? Common::Input::NfcState::AmiiboRemoved | 408 | const auto nfc_state = amiibo_data.empty() ? Common::Input::NfcState::AmiiboRemoved |
| 404 | : Common::Input::NfcState::NewAmiibo; | 409 | : Common::Input::NfcState::NewAmiibo; |
| 405 | SetNfc(identifier, {nfc_state, amiibo_data}); | 410 | SetNfc(identifier, {nfc_state, amiibo_data}); |
diff --git a/src/input_common/drivers/joycon.h b/src/input_common/drivers/joycon.h index 5b40817e2..e3f0ad78f 100644 --- a/src/input_common/drivers/joycon.h +++ b/src/input_common/drivers/joycon.h | |||
| @@ -81,7 +81,8 @@ private: | |||
| 81 | void OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id, | 81 | void OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id, |
| 82 | const Joycon::MotionData& value); | 82 | const Joycon::MotionData& value); |
| 83 | void OnRingConUpdate(f32 ring_data); | 83 | void OnRingConUpdate(f32 ring_data); |
| 84 | void OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data); | 84 | void OnAmiiboUpdate(std::size_t port, Joycon::ControllerType type, |
| 85 | const std::vector<u8>& amiibo_data); | ||
| 85 | void OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data, | 86 | void OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data, |
| 86 | Joycon::IrsResolution format); | 87 | Joycon::IrsResolution format); |
| 87 | 88 | ||
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp index 83429a336..95106f16d 100644 --- a/src/input_common/helpers/joycon_driver.cpp +++ b/src/input_common/helpers/joycon_driver.cpp | |||
| @@ -492,6 +492,26 @@ DriverResult JoyconDriver::SetRingConMode() { | |||
| 492 | return result; | 492 | return result; |
| 493 | } | 493 | } |
| 494 | 494 | ||
| 495 | DriverResult JoyconDriver::WriteNfcData(std::span<const u8> data) { | ||
| 496 | std::scoped_lock lock{mutex}; | ||
| 497 | disable_input_thread = true; | ||
| 498 | |||
| 499 | if (!supported_features.nfc) { | ||
| 500 | return DriverResult::NotSupported; | ||
| 501 | } | ||
| 502 | if (!nfc_protocol->IsEnabled()) { | ||
| 503 | return DriverResult::Disabled; | ||
| 504 | } | ||
| 505 | if (!amiibo_detected) { | ||
| 506 | return DriverResult::ErrorWritingData; | ||
| 507 | } | ||
| 508 | |||
| 509 | const auto result = nfc_protocol->WriteAmiibo(data); | ||
| 510 | |||
| 511 | disable_input_thread = false; | ||
| 512 | return result; | ||
| 513 | } | ||
| 514 | |||
| 495 | bool JoyconDriver::IsConnected() const { | 515 | bool JoyconDriver::IsConnected() const { |
| 496 | std::scoped_lock lock{mutex}; | 516 | std::scoped_lock lock{mutex}; |
| 497 | return is_connected.load(); | 517 | return is_connected.load(); |
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h index 72a9e71dc..e9b2fccbb 100644 --- a/src/input_common/helpers/joycon_driver.h +++ b/src/input_common/helpers/joycon_driver.h | |||
| @@ -49,6 +49,7 @@ public: | |||
| 49 | DriverResult SetIrMode(); | 49 | DriverResult SetIrMode(); |
| 50 | DriverResult SetNfcMode(); | 50 | DriverResult SetNfcMode(); |
| 51 | DriverResult SetRingConMode(); | 51 | DriverResult SetRingConMode(); |
| 52 | DriverResult WriteNfcData(std::span<const u8> data); | ||
| 52 | 53 | ||
| 53 | void SetCallbacks(const JoyconCallbacks& callbacks); | 54 | void SetCallbacks(const JoyconCallbacks& callbacks); |
| 54 | 55 | ||
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.cpp b/src/input_common/helpers/joycon_protocol/common_protocol.cpp index 077d72cd0..51669261a 100644 --- a/src/input_common/helpers/joycon_protocol/common_protocol.cpp +++ b/src/input_common/helpers/joycon_protocol/common_protocol.cpp | |||
| @@ -265,7 +265,7 @@ DriverResult JoyconCommonProtocol::SendMCUData(ReportMode report_mode, MCUSubCom | |||
| 265 | 265 | ||
| 266 | DriverResult JoyconCommonProtocol::WaitSetMCUMode(ReportMode report_mode, MCUMode mode) { | 266 | DriverResult JoyconCommonProtocol::WaitSetMCUMode(ReportMode report_mode, MCUMode mode) { |
| 267 | MCUCommandResponse output{}; | 267 | MCUCommandResponse output{}; |
| 268 | constexpr std::size_t MaxTries{8}; | 268 | constexpr std::size_t MaxTries{16}; |
| 269 | std::size_t tries{}; | 269 | std::size_t tries{}; |
| 270 | 270 | ||
| 271 | do { | 271 | do { |
diff --git a/src/input_common/helpers/joycon_protocol/joycon_types.h b/src/input_common/helpers/joycon_protocol/joycon_types.h index 1c8d294b0..5007b0e18 100644 --- a/src/input_common/helpers/joycon_protocol/joycon_types.h +++ b/src/input_common/helpers/joycon_protocol/joycon_types.h | |||
| @@ -23,6 +23,7 @@ constexpr std::array<u8, 8> DefaultVibrationBuffer{0x0, 0x1, 0x40, 0x40, 0x0, 0x | |||
| 23 | 23 | ||
| 24 | using MacAddress = std::array<u8, 6>; | 24 | using MacAddress = std::array<u8, 6>; |
| 25 | using SerialNumber = std::array<u8, 15>; | 25 | using SerialNumber = std::array<u8, 15>; |
| 26 | using TagUUID = std::array<u8, 7>; | ||
| 26 | 27 | ||
| 27 | enum class ControllerType : u8 { | 28 | enum class ControllerType : u8 { |
| 28 | None = 0x00, | 29 | None = 0x00, |
| @@ -276,12 +277,13 @@ enum class MCUPacketFlag : u8 { | |||
| 276 | LastCommandPacket = 0x08, | 277 | LastCommandPacket = 0x08, |
| 277 | }; | 278 | }; |
| 278 | 279 | ||
| 279 | enum class NFCReadCommand : u8 { | 280 | enum class NFCCommand : u8 { |
| 280 | CancelAll = 0x00, | 281 | CancelAll = 0x00, |
| 281 | StartPolling = 0x01, | 282 | StartPolling = 0x01, |
| 282 | StopPolling = 0x02, | 283 | StopPolling = 0x02, |
| 283 | StartWaitingRecieve = 0x04, | 284 | StartWaitingRecieve = 0x04, |
| 284 | Ntag = 0x06, | 285 | ReadNtag = 0x06, |
| 286 | WriteNtag = 0x08, | ||
| 285 | Mifare = 0x0F, | 287 | Mifare = 0x0F, |
| 286 | }; | 288 | }; |
| 287 | 289 | ||
| @@ -292,14 +294,19 @@ enum class NFCTagType : u8 { | |||
| 292 | 294 | ||
| 293 | enum class NFCPages { | 295 | enum class NFCPages { |
| 294 | Block0 = 0, | 296 | Block0 = 0, |
| 297 | Block3 = 3, | ||
| 295 | Block45 = 45, | 298 | Block45 = 45, |
| 296 | Block135 = 135, | 299 | Block135 = 135, |
| 297 | Block231 = 231, | 300 | Block231 = 231, |
| 298 | }; | 301 | }; |
| 299 | 302 | ||
| 300 | enum class NFCStatus : u8 { | 303 | enum class NFCStatus : u8 { |
| 304 | Ready = 0x00, | ||
| 305 | Polling = 0x01, | ||
| 301 | LastPackage = 0x04, | 306 | LastPackage = 0x04, |
| 307 | WriteDone = 0x05, | ||
| 302 | TagLost = 0x07, | 308 | TagLost = 0x07, |
| 309 | WriteReady = 0x09, | ||
| 303 | }; | 310 | }; |
| 304 | 311 | ||
| 305 | enum class IrsMode : u8 { | 312 | enum class IrsMode : u8 { |
| @@ -559,13 +566,32 @@ static_assert(sizeof(NFCReadBlockCommand) == 0x9, "NFCReadBlockCommand is an inv | |||
| 559 | struct NFCReadCommandData { | 566 | struct NFCReadCommandData { |
| 560 | u8 unknown; | 567 | u8 unknown; |
| 561 | u8 uuid_length; | 568 | u8 uuid_length; |
| 562 | u8 unknown_2; | 569 | TagUUID uid; |
| 563 | std::array<u8, 6> uid; | ||
| 564 | NFCTagType tag_type; | 570 | NFCTagType tag_type; |
| 565 | NFCReadBlockCommand read_block; | 571 | NFCReadBlockCommand read_block; |
| 566 | }; | 572 | }; |
| 567 | static_assert(sizeof(NFCReadCommandData) == 0x13, "NFCReadCommandData is an invalid size"); | 573 | static_assert(sizeof(NFCReadCommandData) == 0x13, "NFCReadCommandData is an invalid size"); |
| 568 | 574 | ||
| 575 | #pragma pack(push, 1) | ||
| 576 | struct NFCWriteCommandData { | ||
| 577 | u8 unknown; | ||
| 578 | u8 uuid_length; | ||
| 579 | TagUUID uid; | ||
| 580 | NFCTagType tag_type; | ||
| 581 | u8 unknown2; | ||
| 582 | u8 unknown3; | ||
| 583 | u8 unknown4; | ||
| 584 | u8 unknown5; | ||
| 585 | u8 unknown6; | ||
| 586 | u8 unknown7; | ||
| 587 | u8 unknown8; | ||
| 588 | u8 magic; | ||
| 589 | u16_be write_count; | ||
| 590 | u8 amiibo_version; | ||
| 591 | }; | ||
| 592 | static_assert(sizeof(NFCWriteCommandData) == 0x15, "NFCWriteCommandData is an invalid size"); | ||
| 593 | #pragma pack(pop) | ||
| 594 | |||
| 569 | struct NFCPollingCommandData { | 595 | struct NFCPollingCommandData { |
| 570 | u8 enable_mifare; | 596 | u8 enable_mifare; |
| 571 | u8 unknown_1; | 597 | u8 unknown_1; |
| @@ -576,9 +602,9 @@ struct NFCPollingCommandData { | |||
| 576 | static_assert(sizeof(NFCPollingCommandData) == 0x05, "NFCPollingCommandData is an invalid size"); | 602 | static_assert(sizeof(NFCPollingCommandData) == 0x05, "NFCPollingCommandData is an invalid size"); |
| 577 | 603 | ||
| 578 | struct NFCRequestState { | 604 | struct NFCRequestState { |
| 579 | NFCReadCommand command_argument; | 605 | NFCCommand command_argument; |
| 606 | u8 block_id; | ||
| 580 | u8 packet_id; | 607 | u8 packet_id; |
| 581 | INSERT_PADDING_BYTES(0x1); | ||
| 582 | MCUPacketFlag packet_flag; | 608 | MCUPacketFlag packet_flag; |
| 583 | u8 data_length; | 609 | u8 data_length; |
| 584 | union { | 610 | union { |
| @@ -591,6 +617,18 @@ struct NFCRequestState { | |||
| 591 | }; | 617 | }; |
| 592 | static_assert(sizeof(NFCRequestState) == 0x26, "NFCRequestState is an invalid size"); | 618 | static_assert(sizeof(NFCRequestState) == 0x26, "NFCRequestState is an invalid size"); |
| 593 | 619 | ||
| 620 | struct NFCDataChunk { | ||
| 621 | u8 nfc_page; | ||
| 622 | u8 data_size; | ||
| 623 | std::array<u8, 0xFF> data; | ||
| 624 | }; | ||
| 625 | |||
| 626 | struct NFCWritePackage { | ||
| 627 | NFCWriteCommandData command_data; | ||
| 628 | u8 number_of_chunks; | ||
| 629 | std::array<NFCDataChunk, 4> data_chunks; | ||
| 630 | }; | ||
| 631 | |||
| 594 | struct IrsConfigure { | 632 | struct IrsConfigure { |
| 595 | MCUCommand command; | 633 | MCUCommand command; |
| 596 | MCUSubCommand sub_command; | 634 | MCUSubCommand sub_command; |
diff --git a/src/input_common/helpers/joycon_protocol/nfc.cpp b/src/input_common/helpers/joycon_protocol/nfc.cpp index 14818ae33..3b7a628e5 100644 --- a/src/input_common/helpers/joycon_protocol/nfc.cpp +++ b/src/input_common/helpers/joycon_protocol/nfc.cpp | |||
| @@ -34,6 +34,12 @@ DriverResult NfcProtocol::EnableNfc() { | |||
| 34 | 34 | ||
| 35 | result = ConfigureMCU(config); | 35 | result = ConfigureMCU(config); |
| 36 | } | 36 | } |
| 37 | if (result == DriverResult::Success) { | ||
| 38 | result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::NFC); | ||
| 39 | } | ||
| 40 | if (result == DriverResult::Success) { | ||
| 41 | result = WaitUntilNfcIs(NFCStatus::Ready); | ||
| 42 | } | ||
| 37 | 43 | ||
| 38 | return result; | 44 | return result; |
| 39 | } | 45 | } |
| @@ -56,13 +62,20 @@ DriverResult NfcProtocol::StartNFCPollingMode() { | |||
| 56 | LOG_DEBUG(Input, "Start NFC pooling Mode"); | 62 | LOG_DEBUG(Input, "Start NFC pooling Mode"); |
| 57 | ScopedSetBlocking sb(this); | 63 | ScopedSetBlocking sb(this); |
| 58 | DriverResult result{DriverResult::Success}; | 64 | DriverResult result{DriverResult::Success}; |
| 59 | TagFoundData tag_data{}; | ||
| 60 | 65 | ||
| 61 | if (result == DriverResult::Success) { | 66 | if (result == DriverResult::Success) { |
| 62 | result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::NFC); | 67 | MCUCommandResponse output{}; |
| 68 | result = SendStopPollingRequest(output); | ||
| 69 | } | ||
| 70 | if (result == DriverResult::Success) { | ||
| 71 | result = WaitUntilNfcIs(NFCStatus::Ready); | ||
| 72 | } | ||
| 73 | if (result == DriverResult::Success) { | ||
| 74 | MCUCommandResponse output{}; | ||
| 75 | result = SendStartPollingRequest(output); | ||
| 63 | } | 76 | } |
| 64 | if (result == DriverResult::Success) { | 77 | if (result == DriverResult::Success) { |
| 65 | result = WaitUntilNfcIsReady(); | 78 | result = WaitUntilNfcIs(NFCStatus::Polling); |
| 66 | } | 79 | } |
| 67 | if (result == DriverResult::Success) { | 80 | if (result == DriverResult::Success) { |
| 68 | is_enabled = true; | 81 | is_enabled = true; |
| @@ -77,49 +90,94 @@ DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) { | |||
| 77 | } | 90 | } |
| 78 | update_counter = 0; | 91 | update_counter = 0; |
| 79 | 92 | ||
| 80 | LOG_DEBUG(Input, "Start NFC pooling Mode"); | 93 | LOG_DEBUG(Input, "Scan for amiibos"); |
| 94 | ScopedSetBlocking sb(this); | ||
| 95 | DriverResult result{DriverResult::Success}; | ||
| 96 | TagFoundData tag_data{}; | ||
| 97 | |||
| 98 | if (result == DriverResult::Success) { | ||
| 99 | result = IsTagInRange(tag_data); | ||
| 100 | } | ||
| 101 | |||
| 102 | if (result == DriverResult::Success) { | ||
| 103 | std::string uuid_string; | ||
| 104 | for (auto& content : tag_data.uuid) { | ||
| 105 | uuid_string += fmt::format(" {:02x}", content); | ||
| 106 | } | ||
| 107 | LOG_INFO(Input, "Tag detected, type={}, uuid={}", tag_data.type, uuid_string); | ||
| 108 | result = GetAmiiboData(data); | ||
| 109 | } | ||
| 110 | |||
| 111 | return result; | ||
| 112 | } | ||
| 113 | |||
| 114 | DriverResult NfcProtocol::WriteAmiibo(std::span<const u8> data) { | ||
| 115 | LOG_DEBUG(Input, "Write amiibo"); | ||
| 81 | ScopedSetBlocking sb(this); | 116 | ScopedSetBlocking sb(this); |
| 82 | DriverResult result{DriverResult::Success}; | 117 | DriverResult result{DriverResult::Success}; |
| 118 | TagUUID tag_uuid = GetTagUUID(data); | ||
| 83 | TagFoundData tag_data{}; | 119 | TagFoundData tag_data{}; |
| 84 | 120 | ||
| 85 | if (result == DriverResult::Success) { | 121 | if (result == DriverResult::Success) { |
| 86 | result = StartPolling(tag_data); | 122 | result = IsTagInRange(tag_data, 7); |
| 87 | } | 123 | } |
| 88 | if (result == DriverResult::Success) { | 124 | if (result == DriverResult::Success) { |
| 89 | result = ReadTag(tag_data); | 125 | if (tag_data.uuid != tag_uuid) { |
| 126 | result = DriverResult::InvalidParameters; | ||
| 127 | } | ||
| 90 | } | 128 | } |
| 91 | if (result == DriverResult::Success) { | 129 | if (result == DriverResult::Success) { |
| 92 | result = WaitUntilNfcIsReady(); | 130 | MCUCommandResponse output{}; |
| 131 | result = SendStopPollingRequest(output); | ||
| 93 | } | 132 | } |
| 94 | if (result == DriverResult::Success) { | 133 | if (result == DriverResult::Success) { |
| 95 | result = StartPolling(tag_data, 7); | 134 | result = WaitUntilNfcIs(NFCStatus::Ready); |
| 96 | } | 135 | } |
| 97 | if (result == DriverResult::Success) { | 136 | if (result == DriverResult::Success) { |
| 98 | result = GetAmiiboData(data); | 137 | MCUCommandResponse output{}; |
| 138 | result = SendStartPollingRequest(output, true); | ||
| 139 | } | ||
| 140 | if (result == DriverResult::Success) { | ||
| 141 | result = WaitUntilNfcIs(NFCStatus::WriteReady); | ||
| 142 | } | ||
| 143 | if (result == DriverResult::Success) { | ||
| 144 | result = WriteAmiiboData(tag_uuid, data); | ||
| 145 | } | ||
| 146 | if (result == DriverResult::Success) { | ||
| 147 | result = WaitUntilNfcIs(NFCStatus::WriteDone); | ||
| 148 | } | ||
| 149 | if (result == DriverResult::Success) { | ||
| 150 | MCUCommandResponse output{}; | ||
| 151 | result = SendStopPollingRequest(output); | ||
| 99 | } | 152 | } |
| 100 | 153 | ||
| 101 | return result; | 154 | return result; |
| 102 | } | 155 | } |
| 103 | 156 | ||
| 104 | bool NfcProtocol::HasAmiibo() { | 157 | bool NfcProtocol::HasAmiibo() { |
| 158 | if (update_counter++ < AMIIBO_UPDATE_DELAY) { | ||
| 159 | return true; | ||
| 160 | } | ||
| 161 | update_counter = 0; | ||
| 162 | |||
| 105 | ScopedSetBlocking sb(this); | 163 | ScopedSetBlocking sb(this); |
| 106 | DriverResult result{DriverResult::Success}; | 164 | DriverResult result{DriverResult::Success}; |
| 107 | TagFoundData tag_data{}; | 165 | TagFoundData tag_data{}; |
| 108 | 166 | ||
| 109 | if (result == DriverResult::Success) { | 167 | if (result == DriverResult::Success) { |
| 110 | result = StartPolling(tag_data); | 168 | result = IsTagInRange(tag_data, 7); |
| 111 | } | 169 | } |
| 112 | 170 | ||
| 113 | return result == DriverResult::Success; | 171 | return result == DriverResult::Success; |
| 114 | } | 172 | } |
| 115 | 173 | ||
| 116 | DriverResult NfcProtocol::WaitUntilNfcIsReady() { | 174 | DriverResult NfcProtocol::WaitUntilNfcIs(NFCStatus status) { |
| 117 | constexpr std::size_t timeout_limit = 10; | 175 | constexpr std::size_t timeout_limit = 10; |
| 118 | MCUCommandResponse output{}; | 176 | MCUCommandResponse output{}; |
| 119 | std::size_t tries = 0; | 177 | std::size_t tries = 0; |
| 120 | 178 | ||
| 121 | do { | 179 | do { |
| 122 | auto result = SendStartWaitingRecieveRequest(output); | 180 | auto result = SendNextPackageRequest(output, {}); |
| 123 | 181 | ||
| 124 | if (result != DriverResult::Success) { | 182 | if (result != DriverResult::Success) { |
| 125 | return result; | 183 | return result; |
| @@ -129,18 +187,17 @@ DriverResult NfcProtocol::WaitUntilNfcIsReady() { | |||
| 129 | } | 187 | } |
| 130 | } while (output.mcu_report != MCUReport::NFCState || | 188 | } while (output.mcu_report != MCUReport::NFCState || |
| 131 | (output.mcu_data[1] << 8) + output.mcu_data[0] != 0x0500 || | 189 | (output.mcu_data[1] << 8) + output.mcu_data[0] != 0x0500 || |
| 132 | output.mcu_data[5] != 0x31 || output.mcu_data[6] != 0x00); | 190 | output.mcu_data[5] != 0x31 || output.mcu_data[6] != static_cast<u8>(status)); |
| 133 | 191 | ||
| 134 | return DriverResult::Success; | 192 | return DriverResult::Success; |
| 135 | } | 193 | } |
| 136 | 194 | ||
| 137 | DriverResult NfcProtocol::StartPolling(TagFoundData& data, std::size_t timeout_limit) { | 195 | DriverResult NfcProtocol::IsTagInRange(TagFoundData& data, std::size_t timeout_limit) { |
| 138 | LOG_DEBUG(Input, "Start Polling for tag"); | ||
| 139 | MCUCommandResponse output{}; | 196 | MCUCommandResponse output{}; |
| 140 | std::size_t tries = 0; | 197 | std::size_t tries = 0; |
| 141 | 198 | ||
| 142 | do { | 199 | do { |
| 143 | const auto result = SendStartPollingRequest(output); | 200 | const auto result = SendNextPackageRequest(output, {}); |
| 144 | if (result != DriverResult::Success) { | 201 | if (result != DriverResult::Success) { |
| 145 | return result; | 202 | return result; |
| 146 | } | 203 | } |
| @@ -149,32 +206,31 @@ DriverResult NfcProtocol::StartPolling(TagFoundData& data, std::size_t timeout_l | |||
| 149 | } | 206 | } |
| 150 | } while (output.mcu_report != MCUReport::NFCState || | 207 | } while (output.mcu_report != MCUReport::NFCState || |
| 151 | (output.mcu_data[1] << 8) + output.mcu_data[0] != 0x0500 || | 208 | (output.mcu_data[1] << 8) + output.mcu_data[0] != 0x0500 || |
| 152 | output.mcu_data[6] != 0x09); | 209 | (output.mcu_data[6] != 0x09 && output.mcu_data[6] != 0x04)); |
| 153 | 210 | ||
| 154 | data.type = output.mcu_data[12]; | 211 | data.type = output.mcu_data[12]; |
| 155 | data.uuid.resize(output.mcu_data[14]); | 212 | data.uuid_size = std::min(output.mcu_data[14], static_cast<u8>(sizeof(TagUUID))); |
| 156 | memcpy(data.uuid.data(), output.mcu_data.data() + 15, data.uuid.size()); | 213 | memcpy(data.uuid.data(), output.mcu_data.data() + 15, data.uuid.size()); |
| 157 | 214 | ||
| 158 | return DriverResult::Success; | 215 | return DriverResult::Success; |
| 159 | } | 216 | } |
| 160 | 217 | ||
| 161 | DriverResult NfcProtocol::ReadTag(const TagFoundData& data) { | 218 | DriverResult NfcProtocol::GetAmiiboData(std::vector<u8>& ntag_data) { |
| 162 | constexpr std::size_t timeout_limit = 10; | 219 | constexpr std::size_t timeout_limit = 60; |
| 163 | MCUCommandResponse output{}; | 220 | MCUCommandResponse output{}; |
| 164 | std::size_t tries = 0; | 221 | std::size_t tries = 0; |
| 165 | 222 | ||
| 166 | std::string uuid_string; | 223 | u8 package_index = 0; |
| 167 | for (auto& content : data.uuid) { | 224 | std::size_t ntag_buffer_pos = 0; |
| 168 | uuid_string += fmt::format(" {:02x}", content); | 225 | auto result = SendReadAmiiboRequest(output, NFCPages::Block135); |
| 169 | } | ||
| 170 | 226 | ||
| 171 | LOG_INFO(Input, "Tag detected, type={}, uuid={}", data.type, uuid_string); | 227 | if (result != DriverResult::Success) { |
| 228 | return result; | ||
| 229 | } | ||
| 172 | 230 | ||
| 173 | tries = 0; | ||
| 174 | NFCPages ntag_pages = NFCPages::Block0; | ||
| 175 | // Read Tag data | 231 | // Read Tag data |
| 176 | while (true) { | 232 | while (tries++ < timeout_limit) { |
| 177 | auto result = SendReadAmiiboRequest(output, ntag_pages); | 233 | result = SendNextPackageRequest(output, package_index); |
| 178 | const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]); | 234 | const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]); |
| 179 | 235 | ||
| 180 | if (result != DriverResult::Success) { | 236 | if (result != DriverResult::Success) { |
| @@ -187,56 +243,51 @@ DriverResult NfcProtocol::ReadTag(const TagFoundData& data) { | |||
| 187 | return DriverResult::ErrorReadingData; | 243 | return DriverResult::ErrorReadingData; |
| 188 | } | 244 | } |
| 189 | 245 | ||
| 190 | if (output.mcu_report == MCUReport::NFCReadData && output.mcu_data[1] == 0x07 && | 246 | if (output.mcu_report == MCUReport::NFCReadData && output.mcu_data[1] == 0x07) { |
| 191 | output.mcu_data[2] == 0x01) { | 247 | std::size_t payload_size = (output.mcu_data[4] << 8 | output.mcu_data[5]) & 0x7FF; |
| 192 | if (data.type != 2) { | 248 | if (output.mcu_data[2] == 0x01) { |
| 193 | continue; | 249 | memcpy(ntag_data.data() + ntag_buffer_pos, output.mcu_data.data() + 66, |
| 194 | } | 250 | payload_size - 60); |
| 195 | switch (output.mcu_data[24]) { | 251 | ntag_buffer_pos += payload_size - 60; |
| 196 | case 0: | 252 | } else { |
| 197 | ntag_pages = NFCPages::Block135; | 253 | memcpy(ntag_data.data() + ntag_buffer_pos, output.mcu_data.data() + 6, |
| 198 | break; | 254 | payload_size); |
| 199 | case 3: | ||
| 200 | ntag_pages = NFCPages::Block45; | ||
| 201 | break; | ||
| 202 | case 4: | ||
| 203 | ntag_pages = NFCPages::Block231; | ||
| 204 | break; | ||
| 205 | default: | ||
| 206 | return DriverResult::ErrorReadingData; | ||
| 207 | } | 255 | } |
| 256 | package_index++; | ||
| 208 | continue; | 257 | continue; |
| 209 | } | 258 | } |
| 210 | 259 | ||
| 211 | if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) { | 260 | if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) { |
| 212 | // finished | 261 | LOG_INFO(Input, "Finished reading amiibo"); |
| 213 | SendStopPollingRequest(output); | ||
| 214 | return DriverResult::Success; | 262 | return DriverResult::Success; |
| 215 | } | 263 | } |
| 216 | |||
| 217 | // Ignore other state reports | ||
| 218 | if (output.mcu_report == MCUReport::NFCState) { | ||
| 219 | continue; | ||
| 220 | } | ||
| 221 | |||
| 222 | if (tries++ > timeout_limit) { | ||
| 223 | return DriverResult::Timeout; | ||
| 224 | } | ||
| 225 | } | 264 | } |
| 226 | 265 | ||
| 227 | return DriverResult::Success; | 266 | return DriverResult::Timeout; |
| 228 | } | 267 | } |
| 229 | 268 | ||
| 230 | DriverResult NfcProtocol::GetAmiiboData(std::vector<u8>& ntag_data) { | 269 | DriverResult NfcProtocol::WriteAmiiboData(const TagUUID& tag_uuid, std::span<const u8> data) { |
| 231 | constexpr std::size_t timeout_limit = 10; | 270 | constexpr std::size_t timeout_limit = 60; |
| 271 | const auto nfc_data = MakeAmiiboWritePackage(tag_uuid, data); | ||
| 272 | const std::vector<u8> nfc_buffer_data = SerializeWritePackage(nfc_data); | ||
| 273 | std::span<const u8> buffer(nfc_buffer_data); | ||
| 232 | MCUCommandResponse output{}; | 274 | MCUCommandResponse output{}; |
| 275 | u8 block_id = 1; | ||
| 276 | u8 package_index = 0; | ||
| 233 | std::size_t tries = 0; | 277 | std::size_t tries = 0; |
| 278 | std::size_t current_position = 0; | ||
| 234 | 279 | ||
| 235 | NFCPages ntag_pages = NFCPages::Block135; | 280 | LOG_INFO(Input, "Writing amiibo data"); |
| 236 | std::size_t ntag_buffer_pos = 0; | 281 | |
| 237 | // Read Tag data | 282 | auto result = SendWriteAmiiboRequest(output, tag_uuid); |
| 238 | while (true) { | 283 | |
| 239 | auto result = SendReadAmiiboRequest(output, ntag_pages); | 284 | if (result != DriverResult::Success) { |
| 285 | return result; | ||
| 286 | } | ||
| 287 | |||
| 288 | // Read Tag data but ignore the actual sent data | ||
| 289 | while (tries++ < timeout_limit) { | ||
| 290 | result = SendNextPackageRequest(output, package_index); | ||
| 240 | const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]); | 291 | const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]); |
| 241 | 292 | ||
| 242 | if (result != DriverResult::Success) { | 293 | if (result != DriverResult::Success) { |
| @@ -250,47 +301,59 @@ DriverResult NfcProtocol::GetAmiiboData(std::vector<u8>& ntag_data) { | |||
| 250 | } | 301 | } |
| 251 | 302 | ||
| 252 | if (output.mcu_report == MCUReport::NFCReadData && output.mcu_data[1] == 0x07) { | 303 | if (output.mcu_report == MCUReport::NFCReadData && output.mcu_data[1] == 0x07) { |
| 253 | std::size_t payload_size = (output.mcu_data[4] << 8 | output.mcu_data[5]) & 0x7FF; | 304 | package_index++; |
| 254 | if (output.mcu_data[2] == 0x01) { | ||
| 255 | memcpy(ntag_data.data() + ntag_buffer_pos, output.mcu_data.data() + 66, | ||
| 256 | payload_size - 60); | ||
| 257 | ntag_buffer_pos += payload_size - 60; | ||
| 258 | } else { | ||
| 259 | memcpy(ntag_data.data() + ntag_buffer_pos, output.mcu_data.data() + 6, | ||
| 260 | payload_size); | ||
| 261 | } | ||
| 262 | continue; | 305 | continue; |
| 263 | } | 306 | } |
| 264 | 307 | ||
| 265 | if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) { | 308 | if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) { |
| 266 | LOG_INFO(Input, "Finished reading amiibo"); | 309 | LOG_INFO(Input, "Finished reading amiibo"); |
| 267 | return DriverResult::Success; | 310 | break; |
| 268 | } | 311 | } |
| 312 | } | ||
| 269 | 313 | ||
| 270 | // Ignore other state reports | 314 | // Send Data. Nfc buffer size is 31, Send the data in smaller packages |
| 271 | if (output.mcu_report == MCUReport::NFCState) { | 315 | while (current_position < buffer.size() && tries++ < timeout_limit) { |
| 272 | continue; | 316 | const std::size_t next_position = |
| 317 | std::min(current_position + sizeof(NFCRequestState::raw_data), buffer.size()); | ||
| 318 | const std::size_t block_size = next_position - current_position; | ||
| 319 | const bool is_last_packet = block_size < sizeof(NFCRequestState::raw_data); | ||
| 320 | |||
| 321 | SendWriteDataAmiiboRequest(output, block_id, is_last_packet, | ||
| 322 | buffer.subspan(current_position, block_size)); | ||
| 323 | |||
| 324 | const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]); | ||
| 325 | |||
| 326 | if ((output.mcu_report == MCUReport::NFCReadData || | ||
| 327 | output.mcu_report == MCUReport::NFCState) && | ||
| 328 | nfc_status == NFCStatus::TagLost) { | ||
| 329 | return DriverResult::ErrorReadingData; | ||
| 273 | } | 330 | } |
| 274 | 331 | ||
| 275 | if (tries++ > timeout_limit) { | 332 | // Increase position when data is confirmed by the joycon |
| 276 | return DriverResult::Timeout; | 333 | if (output.mcu_report == MCUReport::NFCState && |
| 334 | (output.mcu_data[1] << 8) + output.mcu_data[0] == 0x0500 && | ||
| 335 | output.mcu_data[3] == block_id) { | ||
| 336 | block_id++; | ||
| 337 | current_position = next_position; | ||
| 277 | } | 338 | } |
| 278 | } | 339 | } |
| 279 | 340 | ||
| 280 | return DriverResult::Success; | 341 | return result; |
| 281 | } | 342 | } |
| 282 | 343 | ||
| 283 | DriverResult NfcProtocol::SendStartPollingRequest(MCUCommandResponse& output) { | 344 | DriverResult NfcProtocol::SendStartPollingRequest(MCUCommandResponse& output, |
| 345 | bool is_second_attempt) { | ||
| 284 | NFCRequestState request{ | 346 | NFCRequestState request{ |
| 285 | .command_argument = NFCReadCommand::StartPolling, | 347 | .command_argument = NFCCommand::StartPolling, |
| 286 | .packet_id = 0x0, | 348 | .block_id = {}, |
| 349 | .packet_id = {}, | ||
| 287 | .packet_flag = MCUPacketFlag::LastCommandPacket, | 350 | .packet_flag = MCUPacketFlag::LastCommandPacket, |
| 288 | .data_length = sizeof(NFCPollingCommandData), | 351 | .data_length = sizeof(NFCPollingCommandData), |
| 289 | .nfc_polling = | 352 | .nfc_polling = |
| 290 | { | 353 | { |
| 291 | .enable_mifare = 0x01, | 354 | .enable_mifare = 0x00, |
| 292 | .unknown_1 = 0x00, | 355 | .unknown_1 = static_cast<u8>(is_second_attempt ? 0xe8 : 0x00), |
| 293 | .unknown_2 = 0x00, | 356 | .unknown_2 = static_cast<u8>(is_second_attempt ? 0x03 : 0x00), |
| 294 | .unknown_3 = 0x2c, | 357 | .unknown_3 = 0x2c, |
| 295 | .unknown_4 = 0x01, | 358 | .unknown_4 = 0x01, |
| 296 | }, | 359 | }, |
| @@ -306,10 +369,11 @@ DriverResult NfcProtocol::SendStartPollingRequest(MCUCommandResponse& output) { | |||
| 306 | 369 | ||
| 307 | DriverResult NfcProtocol::SendStopPollingRequest(MCUCommandResponse& output) { | 370 | DriverResult NfcProtocol::SendStopPollingRequest(MCUCommandResponse& output) { |
| 308 | NFCRequestState request{ | 371 | NFCRequestState request{ |
| 309 | .command_argument = NFCReadCommand::StopPolling, | 372 | .command_argument = NFCCommand::StopPolling, |
| 310 | .packet_id = 0x0, | 373 | .block_id = {}, |
| 374 | .packet_id = {}, | ||
| 311 | .packet_flag = MCUPacketFlag::LastCommandPacket, | 375 | .packet_flag = MCUPacketFlag::LastCommandPacket, |
| 312 | .data_length = 0, | 376 | .data_length = {}, |
| 313 | .raw_data = {}, | 377 | .raw_data = {}, |
| 314 | .crc = {}, | 378 | .crc = {}, |
| 315 | }; | 379 | }; |
| @@ -321,12 +385,13 @@ DriverResult NfcProtocol::SendStopPollingRequest(MCUCommandResponse& output) { | |||
| 321 | output); | 385 | output); |
| 322 | } | 386 | } |
| 323 | 387 | ||
| 324 | DriverResult NfcProtocol::SendStartWaitingRecieveRequest(MCUCommandResponse& output) { | 388 | DriverResult NfcProtocol::SendNextPackageRequest(MCUCommandResponse& output, u8 packet_id) { |
| 325 | NFCRequestState request{ | 389 | NFCRequestState request{ |
| 326 | .command_argument = NFCReadCommand::StartWaitingRecieve, | 390 | .command_argument = NFCCommand::StartWaitingRecieve, |
| 327 | .packet_id = 0x0, | 391 | .block_id = {}, |
| 392 | .packet_id = packet_id, | ||
| 328 | .packet_flag = MCUPacketFlag::LastCommandPacket, | 393 | .packet_flag = MCUPacketFlag::LastCommandPacket, |
| 329 | .data_length = 0, | 394 | .data_length = {}, |
| 330 | .raw_data = {}, | 395 | .raw_data = {}, |
| 331 | .crc = {}, | 396 | .crc = {}, |
| 332 | }; | 397 | }; |
| @@ -340,17 +405,17 @@ DriverResult NfcProtocol::SendStartWaitingRecieveRequest(MCUCommandResponse& out | |||
| 340 | 405 | ||
| 341 | DriverResult NfcProtocol::SendReadAmiiboRequest(MCUCommandResponse& output, NFCPages ntag_pages) { | 406 | DriverResult NfcProtocol::SendReadAmiiboRequest(MCUCommandResponse& output, NFCPages ntag_pages) { |
| 342 | NFCRequestState request{ | 407 | NFCRequestState request{ |
| 343 | .command_argument = NFCReadCommand::Ntag, | 408 | .command_argument = NFCCommand::ReadNtag, |
| 344 | .packet_id = 0x0, | 409 | .block_id = {}, |
| 410 | .packet_id = {}, | ||
| 345 | .packet_flag = MCUPacketFlag::LastCommandPacket, | 411 | .packet_flag = MCUPacketFlag::LastCommandPacket, |
| 346 | .data_length = sizeof(NFCReadCommandData), | 412 | .data_length = sizeof(NFCReadCommandData), |
| 347 | .nfc_read = | 413 | .nfc_read = |
| 348 | { | 414 | { |
| 349 | .unknown = 0xd0, | 415 | .unknown = 0xd0, |
| 350 | .uuid_length = 0x07, | 416 | .uuid_length = sizeof(NFCReadCommandData::uid), |
| 351 | .unknown_2 = 0x00, | ||
| 352 | .uid = {}, | 417 | .uid = {}, |
| 353 | .tag_type = NFCTagType::AllTags, | 418 | .tag_type = NFCTagType::Ntag215, |
| 354 | .read_block = GetReadBlockCommand(ntag_pages), | 419 | .read_block = GetReadBlockCommand(ntag_pages), |
| 355 | }, | 420 | }, |
| 356 | .crc = {}, | 421 | .crc = {}, |
| @@ -363,12 +428,135 @@ DriverResult NfcProtocol::SendReadAmiiboRequest(MCUCommandResponse& output, NFCP | |||
| 363 | output); | 428 | output); |
| 364 | } | 429 | } |
| 365 | 430 | ||
| 431 | DriverResult NfcProtocol::SendWriteAmiiboRequest(MCUCommandResponse& output, | ||
| 432 | const TagUUID& tag_uuid) { | ||
| 433 | NFCRequestState request{ | ||
| 434 | .command_argument = NFCCommand::ReadNtag, | ||
| 435 | .block_id = {}, | ||
| 436 | .packet_id = {}, | ||
| 437 | .packet_flag = MCUPacketFlag::LastCommandPacket, | ||
| 438 | .data_length = sizeof(NFCReadCommandData), | ||
| 439 | .nfc_read = | ||
| 440 | { | ||
| 441 | .unknown = 0xd0, | ||
| 442 | .uuid_length = sizeof(NFCReadCommandData::uid), | ||
| 443 | .uid = tag_uuid, | ||
| 444 | .tag_type = NFCTagType::Ntag215, | ||
| 445 | .read_block = GetReadBlockCommand(NFCPages::Block3), | ||
| 446 | }, | ||
| 447 | .crc = {}, | ||
| 448 | }; | ||
| 449 | |||
| 450 | std::array<u8, sizeof(NFCRequestState)> request_data{}; | ||
| 451 | memcpy(request_data.data(), &request, sizeof(NFCRequestState)); | ||
| 452 | request_data[36] = CalculateMCU_CRC8(request_data.data(), 36); | ||
| 453 | return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, MCUSubCommand::ReadDeviceMode, request_data, | ||
| 454 | output); | ||
| 455 | } | ||
| 456 | |||
| 457 | DriverResult NfcProtocol::SendWriteDataAmiiboRequest(MCUCommandResponse& output, u8 block_id, | ||
| 458 | bool is_last_packet, | ||
| 459 | std::span<const u8> data) { | ||
| 460 | const auto data_size = std::min(data.size(), sizeof(NFCRequestState::raw_data)); | ||
| 461 | NFCRequestState request{ | ||
| 462 | .command_argument = NFCCommand::WriteNtag, | ||
| 463 | .block_id = block_id, | ||
| 464 | .packet_id = {}, | ||
| 465 | .packet_flag = | ||
| 466 | is_last_packet ? MCUPacketFlag::LastCommandPacket : MCUPacketFlag::MorePacketsRemaining, | ||
| 467 | .data_length = static_cast<u8>(data_size), | ||
| 468 | .raw_data = {}, | ||
| 469 | .crc = {}, | ||
| 470 | }; | ||
| 471 | memcpy(request.raw_data.data(), data.data(), data_size); | ||
| 472 | |||
| 473 | std::array<u8, sizeof(NFCRequestState)> request_data{}; | ||
| 474 | memcpy(request_data.data(), &request, sizeof(NFCRequestState)); | ||
| 475 | request_data[36] = CalculateMCU_CRC8(request_data.data(), 36); | ||
| 476 | return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, MCUSubCommand::ReadDeviceMode, request_data, | ||
| 477 | output); | ||
| 478 | } | ||
| 479 | |||
| 480 | std::vector<u8> NfcProtocol::SerializeWritePackage(const NFCWritePackage& package) const { | ||
| 481 | const std::size_t header_size = | ||
| 482 | sizeof(NFCWriteCommandData) + sizeof(NFCWritePackage::number_of_chunks); | ||
| 483 | std::vector<u8> serialized_data(header_size); | ||
| 484 | std::size_t start_index = 0; | ||
| 485 | |||
| 486 | memcpy(serialized_data.data(), &package, header_size); | ||
| 487 | start_index += header_size; | ||
| 488 | |||
| 489 | for (const auto& data_chunk : package.data_chunks) { | ||
| 490 | const std::size_t chunk_size = | ||
| 491 | sizeof(NFCDataChunk::nfc_page) + sizeof(NFCDataChunk::data_size) + data_chunk.data_size; | ||
| 492 | |||
| 493 | serialized_data.resize(start_index + chunk_size); | ||
| 494 | memcpy(serialized_data.data() + start_index, &data_chunk, chunk_size); | ||
| 495 | start_index += chunk_size; | ||
| 496 | } | ||
| 497 | |||
| 498 | return serialized_data; | ||
| 499 | } | ||
| 500 | |||
| 501 | NFCWritePackage NfcProtocol::MakeAmiiboWritePackage(const TagUUID& tag_uuid, | ||
| 502 | std::span<const u8> data) const { | ||
| 503 | return { | ||
| 504 | .command_data{ | ||
| 505 | .unknown = 0xd0, | ||
| 506 | .uuid_length = sizeof(NFCReadCommandData::uid), | ||
| 507 | .uid = tag_uuid, | ||
| 508 | .tag_type = NFCTagType::Ntag215, | ||
| 509 | .unknown2 = 0x00, | ||
| 510 | .unknown3 = 0x01, | ||
| 511 | .unknown4 = 0x04, | ||
| 512 | .unknown5 = 0xff, | ||
| 513 | .unknown6 = 0xff, | ||
| 514 | .unknown7 = 0xff, | ||
| 515 | .unknown8 = 0xff, | ||
| 516 | .magic = data[16], | ||
| 517 | .write_count = static_cast<u16>((data[17] << 8) + data[18]), | ||
| 518 | .amiibo_version = data[19], | ||
| 519 | }, | ||
| 520 | .number_of_chunks = 3, | ||
| 521 | .data_chunks = | ||
| 522 | { | ||
| 523 | MakeAmiiboChunk(0x05, 0x20, data), | ||
| 524 | MakeAmiiboChunk(0x20, 0xf0, data), | ||
| 525 | MakeAmiiboChunk(0x5c, 0x98, data), | ||
| 526 | }, | ||
| 527 | }; | ||
| 528 | } | ||
| 529 | |||
| 530 | NFCDataChunk NfcProtocol::MakeAmiiboChunk(u8 page, u8 size, std::span<const u8> data) const { | ||
| 531 | constexpr u8 PAGE_SIZE = 4; | ||
| 532 | |||
| 533 | if (static_cast<std::size_t>(page * PAGE_SIZE) + size >= data.size()) { | ||
| 534 | return {}; | ||
| 535 | } | ||
| 536 | |||
| 537 | NFCDataChunk chunk{ | ||
| 538 | .nfc_page = page, | ||
| 539 | .data_size = size, | ||
| 540 | .data = {}, | ||
| 541 | }; | ||
| 542 | std::memcpy(chunk.data.data(), data.data() + (page * PAGE_SIZE), size); | ||
| 543 | return chunk; | ||
| 544 | } | ||
| 545 | |||
| 366 | NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(NFCPages pages) const { | 546 | NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(NFCPages pages) const { |
| 367 | switch (pages) { | 547 | switch (pages) { |
| 368 | case NFCPages::Block0: | 548 | case NFCPages::Block0: |
| 369 | return { | 549 | return { |
| 370 | .block_count = 1, | 550 | .block_count = 1, |
| 371 | }; | 551 | }; |
| 552 | case NFCPages::Block3: | ||
| 553 | return { | ||
| 554 | .block_count = 1, | ||
| 555 | .blocks = | ||
| 556 | { | ||
| 557 | NFCReadBlock{0x03, 0x03}, | ||
| 558 | }, | ||
| 559 | }; | ||
| 372 | case NFCPages::Block45: | 560 | case NFCPages::Block45: |
| 373 | return { | 561 | return { |
| 374 | .block_count = 1, | 562 | .block_count = 1, |
| @@ -403,6 +591,17 @@ NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(NFCPages pages) const { | |||
| 403 | }; | 591 | }; |
| 404 | } | 592 | } |
| 405 | 593 | ||
| 594 | TagUUID NfcProtocol::GetTagUUID(std::span<const u8> data) const { | ||
| 595 | if (data.size() < 10) { | ||
| 596 | return {}; | ||
| 597 | } | ||
| 598 | |||
| 599 | // crc byte 3 is omitted in this operation | ||
| 600 | return { | ||
| 601 | data[0], data[1], data[2], data[4], data[5], data[6], data[7], | ||
| 602 | }; | ||
| 603 | } | ||
| 604 | |||
| 406 | bool NfcProtocol::IsEnabled() const { | 605 | bool NfcProtocol::IsEnabled() const { |
| 407 | return is_enabled; | 606 | return is_enabled; |
| 408 | } | 607 | } |
diff --git a/src/input_common/helpers/joycon_protocol/nfc.h b/src/input_common/helpers/joycon_protocol/nfc.h index 4cb992d1d..eb58c427d 100644 --- a/src/input_common/helpers/joycon_protocol/nfc.h +++ b/src/input_common/helpers/joycon_protocol/nfc.h | |||
| @@ -27,6 +27,8 @@ public: | |||
| 27 | 27 | ||
| 28 | DriverResult ScanAmiibo(std::vector<u8>& data); | 28 | DriverResult ScanAmiibo(std::vector<u8>& data); |
| 29 | 29 | ||
| 30 | DriverResult WriteAmiibo(std::span<const u8> data); | ||
| 31 | |||
| 30 | bool HasAmiibo(); | 32 | bool HasAmiibo(); |
| 31 | 33 | ||
| 32 | bool IsEnabled() const; | 34 | bool IsEnabled() const; |
| @@ -37,27 +39,42 @@ private: | |||
| 37 | 39 | ||
| 38 | struct TagFoundData { | 40 | struct TagFoundData { |
| 39 | u8 type; | 41 | u8 type; |
| 40 | std::vector<u8> uuid; | 42 | u8 uuid_size; |
| 43 | TagUUID uuid; | ||
| 41 | }; | 44 | }; |
| 42 | 45 | ||
| 43 | DriverResult WaitUntilNfcIsReady(); | 46 | DriverResult WaitUntilNfcIs(NFCStatus status); |
| 44 | |||
| 45 | DriverResult StartPolling(TagFoundData& data, std::size_t timeout_limit = 1); | ||
| 46 | 47 | ||
| 47 | DriverResult ReadTag(const TagFoundData& data); | 48 | DriverResult IsTagInRange(TagFoundData& data, std::size_t timeout_limit = 1); |
| 48 | 49 | ||
| 49 | DriverResult GetAmiiboData(std::vector<u8>& data); | 50 | DriverResult GetAmiiboData(std::vector<u8>& data); |
| 50 | 51 | ||
| 51 | DriverResult SendStartPollingRequest(MCUCommandResponse& output); | 52 | DriverResult WriteAmiiboData(const TagUUID& tag_uuid, std::span<const u8> data); |
| 53 | |||
| 54 | DriverResult SendStartPollingRequest(MCUCommandResponse& output, | ||
| 55 | bool is_second_attempt = false); | ||
| 52 | 56 | ||
| 53 | DriverResult SendStopPollingRequest(MCUCommandResponse& output); | 57 | DriverResult SendStopPollingRequest(MCUCommandResponse& output); |
| 54 | 58 | ||
| 55 | DriverResult SendStartWaitingRecieveRequest(MCUCommandResponse& output); | 59 | DriverResult SendNextPackageRequest(MCUCommandResponse& output, u8 packet_id); |
| 56 | 60 | ||
| 57 | DriverResult SendReadAmiiboRequest(MCUCommandResponse& output, NFCPages ntag_pages); | 61 | DriverResult SendReadAmiiboRequest(MCUCommandResponse& output, NFCPages ntag_pages); |
| 58 | 62 | ||
| 63 | DriverResult SendWriteAmiiboRequest(MCUCommandResponse& output, const TagUUID& tag_uuid); | ||
| 64 | |||
| 65 | DriverResult SendWriteDataAmiiboRequest(MCUCommandResponse& output, u8 block_id, | ||
| 66 | bool is_last_packet, std::span<const u8> data); | ||
| 67 | |||
| 68 | std::vector<u8> SerializeWritePackage(const NFCWritePackage& package) const; | ||
| 69 | |||
| 70 | NFCWritePackage MakeAmiiboWritePackage(const TagUUID& tag_uuid, std::span<const u8> data) const; | ||
| 71 | |||
| 72 | NFCDataChunk MakeAmiiboChunk(u8 page, u8 size, std::span<const u8> data) const; | ||
| 73 | |||
| 59 | NFCReadBlockCommand GetReadBlockCommand(NFCPages pages) const; | 74 | NFCReadBlockCommand GetReadBlockCommand(NFCPages pages) const; |
| 60 | 75 | ||
| 76 | TagUUID GetTagUUID(std::span<const u8> data) const; | ||
| 77 | |||
| 61 | bool is_enabled{}; | 78 | bool is_enabled{}; |
| 62 | std::size_t update_counter{}; | 79 | std::size_t update_counter{}; |
| 63 | }; | 80 | }; |
diff --git a/src/input_common/input_engine.cpp b/src/input_common/input_engine.cpp index 91aa96aa7..e4c5b5b3c 100644 --- a/src/input_common/input_engine.cpp +++ b/src/input_common/input_engine.cpp | |||
| @@ -380,13 +380,16 @@ void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int mot | |||
| 380 | if (!configuring || !mapping_callback.on_data) { | 380 | if (!configuring || !mapping_callback.on_data) { |
| 381 | return; | 381 | return; |
| 382 | } | 382 | } |
| 383 | const auto old_value = GetMotion(identifier, motion); | ||
| 383 | bool is_active = false; | 384 | bool is_active = false; |
| 384 | if (std::abs(value.accel_x) > 1.5f || std::abs(value.accel_y) > 1.5f || | 385 | if (std::abs(value.accel_x - old_value.accel_x) > 1.5f || |
| 385 | std::abs(value.accel_z) > 1.5f) { | 386 | std::abs(value.accel_y - old_value.accel_y) > 1.5f || |
| 387 | std::abs(value.accel_z - old_value.accel_z) > 1.5f) { | ||
| 386 | is_active = true; | 388 | is_active = true; |
| 387 | } | 389 | } |
| 388 | if (std::abs(value.gyro_x) > 0.6f || std::abs(value.gyro_y) > 0.6f || | 390 | if (std::abs(value.gyro_x - old_value.gyro_x) > 0.6f || |
| 389 | std::abs(value.gyro_z) > 0.6f) { | 391 | std::abs(value.gyro_y - old_value.gyro_y) > 0.6f || |
| 392 | std::abs(value.gyro_z - old_value.gyro_z) > 0.6f) { | ||
| 390 | is_active = true; | 393 | is_active = true; |
| 391 | } | 394 | } |
| 392 | if (!is_active) { | 395 | if (!is_active) { |
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index a0009a36f..308d013d6 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt | |||
| @@ -246,10 +246,14 @@ add_library(video_core STATIC | |||
| 246 | texture_cache/util.h | 246 | texture_cache/util.h |
| 247 | textures/astc.h | 247 | textures/astc.h |
| 248 | textures/astc.cpp | 248 | textures/astc.cpp |
| 249 | textures/bcn.cpp | ||
| 250 | textures/bcn.h | ||
| 249 | textures/decoders.cpp | 251 | textures/decoders.cpp |
| 250 | textures/decoders.h | 252 | textures/decoders.h |
| 251 | textures/texture.cpp | 253 | textures/texture.cpp |
| 252 | textures/texture.h | 254 | textures/texture.h |
| 255 | textures/workers.cpp | ||
| 256 | textures/workers.h | ||
| 253 | transform_feedback.cpp | 257 | transform_feedback.cpp |
| 254 | transform_feedback.h | 258 | transform_feedback.h |
| 255 | video_core.cpp | 259 | video_core.cpp |
| @@ -275,7 +279,7 @@ add_library(video_core STATIC | |||
| 275 | create_target_directory_groups(video_core) | 279 | create_target_directory_groups(video_core) |
| 276 | 280 | ||
| 277 | target_link_libraries(video_core PUBLIC common core) | 281 | target_link_libraries(video_core PUBLIC common core) |
| 278 | target_link_libraries(video_core PUBLIC glad shader_recompiler) | 282 | target_link_libraries(video_core PUBLIC glad shader_recompiler stb) |
| 279 | 283 | ||
| 280 | if (YUZU_USE_BUNDLED_FFMPEG AND NOT WIN32) | 284 | if (YUZU_USE_BUNDLED_FFMPEG AND NOT WIN32) |
| 281 | add_dependencies(video_core ffmpeg-build) | 285 | add_dependencies(video_core ffmpeg-build) |
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 98756e4da..65494097b 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h | |||
| @@ -30,8 +30,8 @@ BufferCache<P>::BufferCache(VideoCore::RasterizerInterface& rasterizer_, | |||
| 30 | } | 30 | } |
| 31 | 31 | ||
| 32 | const s64 device_memory = static_cast<s64>(runtime.GetDeviceLocalMemory()); | 32 | const s64 device_memory = static_cast<s64>(runtime.GetDeviceLocalMemory()); |
| 33 | const s64 min_spacing_expected = device_memory - 1_GiB - 512_MiB; | 33 | const s64 min_spacing_expected = device_memory - 1_GiB; |
| 34 | const s64 min_spacing_critical = device_memory - 1_GiB; | 34 | const s64 min_spacing_critical = device_memory - 512_MiB; |
| 35 | const s64 mem_threshold = std::min(device_memory, TARGET_THRESHOLD); | 35 | const s64 mem_threshold = std::min(device_memory, TARGET_THRESHOLD); |
| 36 | const s64 min_vacancy_expected = (6 * mem_threshold) / 10; | 36 | const s64 min_vacancy_expected = (6 * mem_threshold) / 10; |
| 37 | const s64 min_vacancy_critical = (3 * mem_threshold) / 10; | 37 | const s64 min_vacancy_critical = (3 * mem_threshold) / 10; |
| @@ -1664,7 +1664,7 @@ typename BufferCache<P>::Binding BufferCache<P>::StorageBufferBinding(GPUVAddr s | |||
| 1664 | // cbufs, which do not store the sizes adjacent to the addresses, so use the fully | 1664 | // cbufs, which do not store the sizes adjacent to the addresses, so use the fully |
| 1665 | // mapped buffer size for now. | 1665 | // mapped buffer size for now. |
| 1666 | const u32 memory_layout_size = static_cast<u32>(gpu_memory->GetMemoryLayoutSize(gpu_addr)); | 1666 | const u32 memory_layout_size = static_cast<u32>(gpu_memory->GetMemoryLayoutSize(gpu_addr)); |
| 1667 | return memory_layout_size; | 1667 | return std::min(memory_layout_size, static_cast<u32>(8_MiB)); |
| 1668 | }(); | 1668 | }(); |
| 1669 | const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr); | 1669 | const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr); |
| 1670 | if (!cpu_addr || size == 0) { | 1670 | if (!cpu_addr || size == 0) { |
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index 31118886f..1e0823836 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp | |||
| @@ -233,6 +233,8 @@ void ApplySwizzle(GLuint handle, PixelFormat format, std::array<SwizzleSource, 4 | |||
| 233 | const VideoCommon::ImageInfo& info) { | 233 | const VideoCommon::ImageInfo& info) { |
| 234 | if (IsPixelFormatASTC(info.format) && info.size.depth == 1 && !runtime.HasNativeASTC()) { | 234 | if (IsPixelFormatASTC(info.format) && info.size.depth == 1 && !runtime.HasNativeASTC()) { |
| 235 | return Settings::values.accelerate_astc.GetValue() && | 235 | return Settings::values.accelerate_astc.GetValue() && |
| 236 | Settings::values.astc_recompression.GetValue() == | ||
| 237 | Settings::AstcRecompression::Uncompressed && | ||
| 236 | !Settings::values.async_astc.GetValue(); | 238 | !Settings::values.async_astc.GetValue(); |
| 237 | } | 239 | } |
| 238 | // Disable other accelerated uploads for now as they don't implement swizzled uploads | 240 | // Disable other accelerated uploads for now as they don't implement swizzled uploads |
| @@ -437,6 +439,19 @@ OGLTexture MakeImage(const VideoCommon::ImageInfo& info, GLenum gl_internal_form | |||
| 437 | return GL_R32UI; | 439 | return GL_R32UI; |
| 438 | } | 440 | } |
| 439 | 441 | ||
| 442 | [[nodiscard]] GLenum SelectAstcFormat(PixelFormat format, bool is_srgb) { | ||
| 443 | switch (Settings::values.astc_recompression.GetValue()) { | ||
| 444 | case Settings::AstcRecompression::Bc1: | ||
| 445 | return is_srgb ? GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT : GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; | ||
| 446 | break; | ||
| 447 | case Settings::AstcRecompression::Bc3: | ||
| 448 | return is_srgb ? GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT : GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; | ||
| 449 | break; | ||
| 450 | default: | ||
| 451 | return is_srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8; | ||
| 452 | } | ||
| 453 | } | ||
| 454 | |||
| 440 | } // Anonymous namespace | 455 | } // Anonymous namespace |
| 441 | 456 | ||
| 442 | ImageBufferMap::~ImageBufferMap() { | 457 | ImageBufferMap::~ImageBufferMap() { |
| @@ -739,9 +754,16 @@ Image::Image(TextureCacheRuntime& runtime_, const VideoCommon::ImageInfo& info_, | |||
| 739 | if (IsConverted(runtime->device, info.format, info.type)) { | 754 | if (IsConverted(runtime->device, info.format, info.type)) { |
| 740 | flags |= ImageFlagBits::Converted; | 755 | flags |= ImageFlagBits::Converted; |
| 741 | flags |= ImageFlagBits::CostlyLoad; | 756 | flags |= ImageFlagBits::CostlyLoad; |
| 742 | gl_internal_format = IsPixelFormatSRGB(info.format) ? GL_SRGB8_ALPHA8 : GL_RGBA8; | 757 | |
| 758 | const bool is_srgb = IsPixelFormatSRGB(info.format); | ||
| 759 | gl_internal_format = is_srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8; | ||
| 743 | gl_format = GL_RGBA; | 760 | gl_format = GL_RGBA; |
| 744 | gl_type = GL_UNSIGNED_INT_8_8_8_8_REV; | 761 | gl_type = GL_UNSIGNED_INT_8_8_8_8_REV; |
| 762 | |||
| 763 | if (IsPixelFormatASTC(info.format)) { | ||
| 764 | gl_internal_format = SelectAstcFormat(info.format, is_srgb); | ||
| 765 | gl_format = GL_NONE; | ||
| 766 | } | ||
| 745 | } else { | 767 | } else { |
| 746 | const auto& tuple = MaxwellToGL::GetFormatTuple(info.format); | 768 | const auto& tuple = MaxwellToGL::GetFormatTuple(info.format); |
| 747 | gl_internal_format = tuple.internal_format; | 769 | gl_internal_format = tuple.internal_format; |
| @@ -1130,7 +1152,12 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI | |||
| 1130 | views{runtime.null_image_views} { | 1152 | views{runtime.null_image_views} { |
| 1131 | const Device& device = runtime.device; | 1153 | const Device& device = runtime.device; |
| 1132 | if (True(image.flags & ImageFlagBits::Converted)) { | 1154 | if (True(image.flags & ImageFlagBits::Converted)) { |
| 1133 | internal_format = IsPixelFormatSRGB(info.format) ? GL_SRGB8_ALPHA8 : GL_RGBA8; | 1155 | const bool is_srgb = IsPixelFormatSRGB(info.format); |
| 1156 | internal_format = is_srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8; | ||
| 1157 | |||
| 1158 | if (IsPixelFormatASTC(info.format)) { | ||
| 1159 | internal_format = SelectAstcFormat(info.format, is_srgb); | ||
| 1160 | } | ||
| 1134 | } else { | 1161 | } else { |
| 1135 | internal_format = MaxwellToGL::GetFormatTuple(format).internal_format; | 1162 | internal_format = MaxwellToGL::GetFormatTuple(format).internal_format; |
| 1136 | } | 1163 | } |
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h index 1190999a8..3e9b3302b 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.h +++ b/src/video_core/renderer_opengl/gl_texture_cache.h | |||
| @@ -144,6 +144,10 @@ public: | |||
| 144 | return state_tracker; | 144 | return state_tracker; |
| 145 | } | 145 | } |
| 146 | 146 | ||
| 147 | void BarrierFeedbackLoop() const noexcept { | ||
| 148 | // OpenGL does not require a barrier for attachment feedback loops. | ||
| 149 | } | ||
| 150 | |||
| 147 | private: | 151 | private: |
| 148 | struct StagingBuffers { | 152 | struct StagingBuffers { |
| 149 | explicit StagingBuffers(GLenum storage_flags_, GLenum map_flags_); | 153 | explicit StagingBuffers(GLenum storage_flags_, GLenum map_flags_); |
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp index 8853cf0f7..b75d7220d 100644 --- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp +++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp | |||
| @@ -6,6 +6,7 @@ | |||
| 6 | #include "common/assert.h" | 6 | #include "common/assert.h" |
| 7 | #include "common/common_types.h" | 7 | #include "common/common_types.h" |
| 8 | #include "common/logging/log.h" | 8 | #include "common/logging/log.h" |
| 9 | #include "common/settings.h" | ||
| 9 | #include "video_core/engines/maxwell_3d.h" | 10 | #include "video_core/engines/maxwell_3d.h" |
| 10 | #include "video_core/renderer_vulkan/maxwell_to_vk.h" | 11 | #include "video_core/renderer_vulkan/maxwell_to_vk.h" |
| 11 | #include "video_core/surface.h" | 12 | #include "video_core/surface.h" |
| @@ -237,14 +238,25 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with | |||
| 237 | PixelFormat pixel_format) { | 238 | PixelFormat pixel_format) { |
| 238 | ASSERT(static_cast<size_t>(pixel_format) < std::size(tex_format_tuples)); | 239 | ASSERT(static_cast<size_t>(pixel_format) < std::size(tex_format_tuples)); |
| 239 | FormatTuple tuple = tex_format_tuples[static_cast<size_t>(pixel_format)]; | 240 | FormatTuple tuple = tex_format_tuples[static_cast<size_t>(pixel_format)]; |
| 240 | // Use A8B8G8R8_UNORM on hardware that doesn't support ASTC natively | 241 | // Transcode on hardware that doesn't support ASTC natively |
| 241 | if (!device.IsOptimalAstcSupported() && VideoCore::Surface::IsPixelFormatASTC(pixel_format)) { | 242 | if (!device.IsOptimalAstcSupported() && VideoCore::Surface::IsPixelFormatASTC(pixel_format)) { |
| 242 | const bool is_srgb = with_srgb && VideoCore::Surface::IsPixelFormatSRGB(pixel_format); | 243 | const bool is_srgb = with_srgb && VideoCore::Surface::IsPixelFormatSRGB(pixel_format); |
| 243 | if (is_srgb) { | 244 | |
| 244 | tuple.format = VK_FORMAT_A8B8G8R8_SRGB_PACK32; | 245 | switch (Settings::values.astc_recompression.GetValue()) { |
| 245 | } else { | 246 | case Settings::AstcRecompression::Uncompressed: |
| 246 | tuple.format = VK_FORMAT_A8B8G8R8_UNORM_PACK32; | 247 | if (is_srgb) { |
| 247 | tuple.usage |= Storage; | 248 | tuple.format = VK_FORMAT_A8B8G8R8_SRGB_PACK32; |
| 249 | } else { | ||
| 250 | tuple.format = VK_FORMAT_A8B8G8R8_UNORM_PACK32; | ||
| 251 | tuple.usage |= Storage; | ||
| 252 | } | ||
| 253 | break; | ||
| 254 | case Settings::AstcRecompression::Bc1: | ||
| 255 | tuple.format = is_srgb ? VK_FORMAT_BC1_RGBA_SRGB_BLOCK : VK_FORMAT_BC1_RGBA_UNORM_BLOCK; | ||
| 256 | break; | ||
| 257 | case Settings::AstcRecompression::Bc3: | ||
| 258 | tuple.format = is_srgb ? VK_FORMAT_BC3_SRGB_BLOCK : VK_FORMAT_BC3_UNORM_BLOCK; | ||
| 259 | break; | ||
| 248 | } | 260 | } |
| 249 | } | 261 | } |
| 250 | const bool attachable = (tuple.usage & Attachable) != 0; | 262 | const bool attachable = (tuple.usage & Attachable) != 0; |
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index f1bcd5cd6..506b78f08 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp | |||
| @@ -481,12 +481,13 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { | |||
| 481 | if constexpr (Spec::enabled_stages[4]) { | 481 | if constexpr (Spec::enabled_stages[4]) { |
| 482 | prepare_stage(4); | 482 | prepare_stage(4); |
| 483 | } | 483 | } |
| 484 | texture_cache.UpdateRenderTargets(false); | ||
| 485 | texture_cache.CheckFeedbackLoop(views); | ||
| 484 | ConfigureDraw(rescaling, render_area); | 486 | ConfigureDraw(rescaling, render_area); |
| 485 | } | 487 | } |
| 486 | 488 | ||
| 487 | void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling, | 489 | void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling, |
| 488 | const RenderAreaPushConstant& render_area) { | 490 | const RenderAreaPushConstant& render_area) { |
| 489 | texture_cache.UpdateRenderTargets(false); | ||
| 490 | scheduler.RequestRenderpass(texture_cache.GetFramebuffer()); | 491 | scheduler.RequestRenderpass(texture_cache.GetFramebuffer()); |
| 491 | 492 | ||
| 492 | if (!is_built.load(std::memory_order::relaxed)) { | 493 | if (!is_built.load(std::memory_order::relaxed)) { |
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp index 47c74e4d8..8b65aeaeb 100644 --- a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp | |||
| @@ -10,11 +10,16 @@ | |||
| 10 | 10 | ||
| 11 | namespace Vulkan { | 11 | namespace Vulkan { |
| 12 | 12 | ||
| 13 | constexpr u64 FENCE_RESERVE_SIZE = 8; | ||
| 14 | |||
| 13 | MasterSemaphore::MasterSemaphore(const Device& device_) : device(device_) { | 15 | MasterSemaphore::MasterSemaphore(const Device& device_) : device(device_) { |
| 14 | if (!device.HasTimelineSemaphore()) { | 16 | if (!device.HasTimelineSemaphore()) { |
| 15 | static constexpr VkFenceCreateInfo fence_ci{ | 17 | static constexpr VkFenceCreateInfo fence_ci{ |
| 16 | .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .pNext = nullptr, .flags = 0}; | 18 | .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .pNext = nullptr, .flags = 0}; |
| 17 | fence = device.GetLogical().CreateFence(fence_ci); | 19 | free_queue.resize(FENCE_RESERVE_SIZE); |
| 20 | std::ranges::generate(free_queue, | ||
| 21 | [&] { return device.GetLogical().CreateFence(fence_ci); }); | ||
| 22 | wait_thread = std::jthread([this](std::stop_token token) { WaitThread(token); }); | ||
| 18 | return; | 23 | return; |
| 19 | } | 24 | } |
| 20 | 25 | ||
| @@ -167,16 +172,53 @@ VkResult MasterSemaphore::SubmitQueueFence(vk::CommandBuffer& cmdbuf, VkSemaphor | |||
| 167 | .pSignalSemaphores = &signal_semaphore, | 172 | .pSignalSemaphores = &signal_semaphore, |
| 168 | }; | 173 | }; |
| 169 | 174 | ||
| 175 | auto fence = GetFreeFence(); | ||
| 170 | auto result = device.GetGraphicsQueue().Submit(submit_info, *fence); | 176 | auto result = device.GetGraphicsQueue().Submit(submit_info, *fence); |
| 171 | 177 | ||
| 172 | if (result == VK_SUCCESS) { | 178 | if (result == VK_SUCCESS) { |
| 179 | std::scoped_lock lock{wait_mutex}; | ||
| 180 | wait_queue.emplace(host_tick, std::move(fence)); | ||
| 181 | wait_cv.notify_one(); | ||
| 182 | } | ||
| 183 | |||
| 184 | return result; | ||
| 185 | } | ||
| 186 | |||
| 187 | void MasterSemaphore::WaitThread(std::stop_token token) { | ||
| 188 | while (!token.stop_requested()) { | ||
| 189 | u64 host_tick; | ||
| 190 | vk::Fence fence; | ||
| 191 | { | ||
| 192 | std::unique_lock lock{wait_mutex}; | ||
| 193 | Common::CondvarWait(wait_cv, lock, token, [this] { return !wait_queue.empty(); }); | ||
| 194 | if (token.stop_requested()) { | ||
| 195 | return; | ||
| 196 | } | ||
| 197 | std::tie(host_tick, fence) = std::move(wait_queue.front()); | ||
| 198 | wait_queue.pop(); | ||
| 199 | } | ||
| 200 | |||
| 173 | fence.Wait(); | 201 | fence.Wait(); |
| 174 | fence.Reset(); | 202 | fence.Reset(); |
| 175 | gpu_tick.store(host_tick); | 203 | gpu_tick.store(host_tick); |
| 176 | gpu_tick.notify_all(); | 204 | gpu_tick.notify_all(); |
| 205 | |||
| 206 | std::scoped_lock lock{free_mutex}; | ||
| 207 | free_queue.push_front(std::move(fence)); | ||
| 177 | } | 208 | } |
| 209 | } | ||
| 178 | 210 | ||
| 179 | return result; | 211 | vk::Fence MasterSemaphore::GetFreeFence() { |
| 212 | std::scoped_lock lock{free_mutex}; | ||
| 213 | if (free_queue.empty()) { | ||
| 214 | static constexpr VkFenceCreateInfo fence_ci{ | ||
| 215 | .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .pNext = nullptr, .flags = 0}; | ||
| 216 | return device.GetLogical().CreateFence(fence_ci); | ||
| 217 | } | ||
| 218 | |||
| 219 | auto fence = std::move(free_queue.back()); | ||
| 220 | free_queue.pop_back(); | ||
| 221 | return fence; | ||
| 180 | } | 222 | } |
| 181 | 223 | ||
| 182 | } // namespace Vulkan | 224 | } // namespace Vulkan |
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.h b/src/video_core/renderer_vulkan/vk_master_semaphore.h index f2f61f781..1e7c90215 100644 --- a/src/video_core/renderer_vulkan/vk_master_semaphore.h +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.h | |||
| @@ -5,8 +5,10 @@ | |||
| 5 | 5 | ||
| 6 | #include <atomic> | 6 | #include <atomic> |
| 7 | #include <condition_variable> | 7 | #include <condition_variable> |
| 8 | #include <deque> | ||
| 8 | #include <mutex> | 9 | #include <mutex> |
| 9 | #include <thread> | 10 | #include <thread> |
| 11 | #include <queue> | ||
| 10 | 12 | ||
| 11 | #include "common/common_types.h" | 13 | #include "common/common_types.h" |
| 12 | #include "common/polyfill_thread.h" | 14 | #include "common/polyfill_thread.h" |
| @@ -17,6 +19,8 @@ namespace Vulkan { | |||
| 17 | class Device; | 19 | class Device; |
| 18 | 20 | ||
| 19 | class MasterSemaphore { | 21 | class MasterSemaphore { |
| 22 | using Waitable = std::pair<u64, vk::Fence>; | ||
| 23 | |||
| 20 | public: | 24 | public: |
| 21 | explicit MasterSemaphore(const Device& device); | 25 | explicit MasterSemaphore(const Device& device); |
| 22 | ~MasterSemaphore(); | 26 | ~MasterSemaphore(); |
| @@ -57,13 +61,22 @@ private: | |||
| 57 | VkResult SubmitQueueFence(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore, | 61 | VkResult SubmitQueueFence(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore, |
| 58 | VkSemaphore wait_semaphore, u64 host_tick); | 62 | VkSemaphore wait_semaphore, u64 host_tick); |
| 59 | 63 | ||
| 64 | void WaitThread(std::stop_token token); | ||
| 65 | |||
| 66 | vk::Fence GetFreeFence(); | ||
| 67 | |||
| 60 | private: | 68 | private: |
| 61 | const Device& device; ///< Device. | 69 | const Device& device; ///< Device. |
| 62 | vk::Fence fence; ///< Fence. | ||
| 63 | vk::Semaphore semaphore; ///< Timeline semaphore. | 70 | vk::Semaphore semaphore; ///< Timeline semaphore. |
| 64 | std::atomic<u64> gpu_tick{0}; ///< Current known GPU tick. | 71 | std::atomic<u64> gpu_tick{0}; ///< Current known GPU tick. |
| 65 | std::atomic<u64> current_tick{1}; ///< Current logical tick. | 72 | std::atomic<u64> current_tick{1}; ///< Current logical tick. |
| 73 | std::mutex wait_mutex; | ||
| 74 | std::mutex free_mutex; | ||
| 75 | std::condition_variable_any wait_cv; | ||
| 76 | std::queue<Waitable> wait_queue; ///< Queue for the fences to be waited on by the wait thread. | ||
| 77 | std::deque<vk::Fence> free_queue; ///< Holds available fences for submission. | ||
| 66 | std::jthread debug_thread; ///< Debug thread to workaround validation layer bugs. | 78 | std::jthread debug_thread; ///< Debug thread to workaround validation layer bugs. |
| 79 | std::jthread wait_thread; ///< Helper thread that waits for submitted fences. | ||
| 67 | }; | 80 | }; |
| 68 | 81 | ||
| 69 | } // namespace Vulkan | 82 | } // namespace Vulkan |
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 1e80ce463..8c0dec590 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp | |||
| @@ -34,8 +34,8 @@ VkSurfaceFormatKHR ChooseSwapSurfaceFormat(vk::Span<VkSurfaceFormatKHR> formats) | |||
| 34 | return found != formats.end() ? *found : formats[0]; | 34 | return found != formats.end() ? *found : formats[0]; |
| 35 | } | 35 | } |
| 36 | 36 | ||
| 37 | static constexpr VkPresentModeKHR ChooseSwapPresentMode(bool has_imm, bool has_mailbox, | 37 | static VkPresentModeKHR ChooseSwapPresentMode(bool has_imm, bool has_mailbox, |
| 38 | bool has_fifo_relaxed) { | 38 | bool has_fifo_relaxed) { |
| 39 | // Mailbox doesn't lock the application like FIFO (vsync) | 39 | // Mailbox doesn't lock the application like FIFO (vsync) |
| 40 | // FIFO present mode locks the framerate to the monitor's refresh rate | 40 | // FIFO present mode locks the framerate to the monitor's refresh rate |
| 41 | Settings::VSyncMode setting = [has_imm, has_mailbox]() { | 41 | Settings::VSyncMode setting = [has_imm, has_mailbox]() { |
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 4d0481f2a..8711e2a87 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp | |||
| @@ -861,6 +861,10 @@ VkBuffer TextureCacheRuntime::GetTemporaryBuffer(size_t needed_size) { | |||
| 861 | return *buffers[level]; | 861 | return *buffers[level]; |
| 862 | } | 862 | } |
| 863 | 863 | ||
| 864 | void TextureCacheRuntime::BarrierFeedbackLoop() { | ||
| 865 | scheduler.RequestOutsideRenderPassOperationContext(); | ||
| 866 | } | ||
| 867 | |||
| 864 | void TextureCacheRuntime::ReinterpretImage(Image& dst, Image& src, | 868 | void TextureCacheRuntime::ReinterpretImage(Image& dst, Image& src, |
| 865 | std::span<const VideoCommon::ImageCopy> copies) { | 869 | std::span<const VideoCommon::ImageCopy> copies) { |
| 866 | std::vector<VkBufferImageCopy> vk_in_copies(copies.size()); | 870 | std::vector<VkBufferImageCopy> vk_in_copies(copies.size()); |
| @@ -1268,7 +1272,9 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu | |||
| 1268 | if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported()) { | 1272 | if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported()) { |
| 1269 | if (Settings::values.async_astc.GetValue()) { | 1273 | if (Settings::values.async_astc.GetValue()) { |
| 1270 | flags |= VideoCommon::ImageFlagBits::AsynchronousDecode; | 1274 | flags |= VideoCommon::ImageFlagBits::AsynchronousDecode; |
| 1271 | } else if (Settings::values.accelerate_astc.GetValue() && info.size.depth == 1) { | 1275 | } else if (Settings::values.astc_recompression.GetValue() == |
| 1276 | Settings::AstcRecompression::Uncompressed && | ||
| 1277 | Settings::values.accelerate_astc.GetValue() && info.size.depth == 1) { | ||
| 1272 | flags |= VideoCommon::ImageFlagBits::AcceleratedUpload; | 1278 | flags |= VideoCommon::ImageFlagBits::AcceleratedUpload; |
| 1273 | } | 1279 | } |
| 1274 | flags |= VideoCommon::ImageFlagBits::Converted; | 1280 | flags |= VideoCommon::ImageFlagBits::Converted; |
| @@ -1283,7 +1289,9 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu | |||
| 1283 | .usage = VK_IMAGE_USAGE_STORAGE_BIT, | 1289 | .usage = VK_IMAGE_USAGE_STORAGE_BIT, |
| 1284 | }; | 1290 | }; |
| 1285 | current_image = *original_image; | 1291 | current_image = *original_image; |
| 1286 | if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported()) { | 1292 | if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported() && |
| 1293 | Settings::values.astc_recompression.GetValue() == | ||
| 1294 | Settings::AstcRecompression::Uncompressed) { | ||
| 1287 | const auto& device = runtime->device.GetLogical(); | 1295 | const auto& device = runtime->device.GetLogical(); |
| 1288 | storage_image_views.reserve(info.resources.levels); | 1296 | storage_image_views.reserve(info.resources.levels); |
| 1289 | for (s32 level = 0; level < info.resources.levels; ++level) { | 1297 | for (s32 level = 0; level < info.resources.levels; ++level) { |
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 4166b3d20..0f7a5ffd4 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h | |||
| @@ -103,6 +103,8 @@ public: | |||
| 103 | 103 | ||
| 104 | [[nodiscard]] VkBuffer GetTemporaryBuffer(size_t needed_size); | 104 | [[nodiscard]] VkBuffer GetTemporaryBuffer(size_t needed_size); |
| 105 | 105 | ||
| 106 | void BarrierFeedbackLoop(); | ||
| 107 | |||
| 106 | const Device& device; | 108 | const Device& device; |
| 107 | Scheduler& scheduler; | 109 | Scheduler& scheduler; |
| 108 | MemoryAllocator& memory_allocator; | 110 | MemoryAllocator& memory_allocator; |
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index b24086fce..fe13cac93 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h | |||
| @@ -49,8 +49,8 @@ TextureCache<P>::TextureCache(Runtime& runtime_, VideoCore::RasterizerInterface& | |||
| 49 | 49 | ||
| 50 | if constexpr (HAS_DEVICE_MEMORY_INFO) { | 50 | if constexpr (HAS_DEVICE_MEMORY_INFO) { |
| 51 | const s64 device_memory = static_cast<s64>(runtime.GetDeviceLocalMemory()); | 51 | const s64 device_memory = static_cast<s64>(runtime.GetDeviceLocalMemory()); |
| 52 | const s64 min_spacing_expected = device_memory - 1_GiB - 512_MiB; | 52 | const s64 min_spacing_expected = device_memory - 1_GiB; |
| 53 | const s64 min_spacing_critical = device_memory - 1_GiB; | 53 | const s64 min_spacing_critical = device_memory - 512_MiB; |
| 54 | const s64 mem_threshold = std::min(device_memory, TARGET_THRESHOLD); | 54 | const s64 mem_threshold = std::min(device_memory, TARGET_THRESHOLD); |
| 55 | const s64 min_vacancy_expected = (6 * mem_threshold) / 10; | 55 | const s64 min_vacancy_expected = (6 * mem_threshold) / 10; |
| 56 | const s64 min_vacancy_critical = (3 * mem_threshold) / 10; | 56 | const s64 min_vacancy_critical = (3 * mem_threshold) / 10; |
| @@ -86,10 +86,12 @@ void TextureCache<P>::RunGarbageCollector() { | |||
| 86 | // used by the async decoder thread. | 86 | // used by the async decoder thread. |
| 87 | return false; | 87 | return false; |
| 88 | } | 88 | } |
| 89 | if (!aggressive_mode && True(image.flags & ImageFlagBits::CostlyLoad)) { | ||
| 90 | return false; | ||
| 91 | } | ||
| 89 | const bool must_download = | 92 | const bool must_download = |
| 90 | image.IsSafeDownload() && False(image.flags & ImageFlagBits::BadOverlap); | 93 | image.IsSafeDownload() && False(image.flags & ImageFlagBits::BadOverlap); |
| 91 | if (!high_priority_mode && | 94 | if (!high_priority_mode && must_download) { |
| 92 | (must_download || True(image.flags & ImageFlagBits::CostlyLoad))) { | ||
| 93 | return false; | 95 | return false; |
| 94 | } | 96 | } |
| 95 | if (must_download) { | 97 | if (must_download) { |
| @@ -137,7 +139,6 @@ void TextureCache<P>::TickFrame() { | |||
| 137 | TickAsyncDecode(); | 139 | TickAsyncDecode(); |
| 138 | 140 | ||
| 139 | runtime.TickFrame(); | 141 | runtime.TickFrame(); |
| 140 | critical_gc = 0; | ||
| 141 | ++frame_tick; | 142 | ++frame_tick; |
| 142 | 143 | ||
| 143 | if constexpr (IMPLEMENTS_ASYNC_DOWNLOADS) { | 144 | if constexpr (IMPLEMENTS_ASYNC_DOWNLOADS) { |
| @@ -184,6 +185,42 @@ void TextureCache<P>::FillComputeImageViews(std::span<ImageViewInOut> views) { | |||
| 184 | } | 185 | } |
| 185 | 186 | ||
| 186 | template <class P> | 187 | template <class P> |
| 188 | void TextureCache<P>::CheckFeedbackLoop(std::span<const ImageViewInOut> views) { | ||
| 189 | const bool requires_barrier = [&] { | ||
| 190 | for (const auto& view : views) { | ||
| 191 | if (!view.id) { | ||
| 192 | continue; | ||
| 193 | } | ||
| 194 | auto& image_view = slot_image_views[view.id]; | ||
| 195 | |||
| 196 | // Check color targets | ||
| 197 | for (const auto& ct_view_id : render_targets.color_buffer_ids) { | ||
| 198 | if (ct_view_id) { | ||
| 199 | auto& ct_view = slot_image_views[ct_view_id]; | ||
| 200 | if (image_view.image_id == ct_view.image_id) { | ||
| 201 | return true; | ||
| 202 | } | ||
| 203 | } | ||
| 204 | } | ||
| 205 | |||
| 206 | // Check zeta target | ||
| 207 | if (render_targets.depth_buffer_id) { | ||
| 208 | auto& zt_view = slot_image_views[render_targets.depth_buffer_id]; | ||
| 209 | if (image_view.image_id == zt_view.image_id) { | ||
| 210 | return true; | ||
| 211 | } | ||
| 212 | } | ||
| 213 | } | ||
| 214 | |||
| 215 | return false; | ||
| 216 | }(); | ||
| 217 | |||
| 218 | if (requires_barrier) { | ||
| 219 | runtime.BarrierFeedbackLoop(); | ||
| 220 | } | ||
| 221 | } | ||
| 222 | |||
| 223 | template <class P> | ||
| 187 | typename P::Sampler* TextureCache<P>::GetGraphicsSampler(u32 index) { | 224 | typename P::Sampler* TextureCache<P>::GetGraphicsSampler(u32 index) { |
| 188 | if (index > channel_state->graphics_sampler_table.Limit()) { | 225 | if (index > channel_state->graphics_sampler_table.Limit()) { |
| 189 | LOG_DEBUG(HW_GPU, "Invalid sampler index={}", index); | 226 | LOG_DEBUG(HW_GPU, "Invalid sampler index={}", index); |
| @@ -1469,7 +1506,7 @@ std::optional<typename TextureCache<P>::BlitImages> TextureCache<P>::GetBlitImag | |||
| 1469 | if (!copy.must_accelerate) { | 1506 | if (!copy.must_accelerate) { |
| 1470 | do { | 1507 | do { |
| 1471 | if (!src_id && !dst_id) { | 1508 | if (!src_id && !dst_id) { |
| 1472 | break; | 1509 | return std::nullopt; |
| 1473 | } | 1510 | } |
| 1474 | if (src_id && True(slot_images[src_id].flags & ImageFlagBits::GpuModified)) { | 1511 | if (src_id && True(slot_images[src_id].flags & ImageFlagBits::GpuModified)) { |
| 1475 | break; | 1512 | break; |
| @@ -1847,10 +1884,6 @@ void TextureCache<P>::RegisterImage(ImageId image_id) { | |||
| 1847 | tentative_size = EstimatedDecompressedSize(tentative_size, image.info.format); | 1884 | tentative_size = EstimatedDecompressedSize(tentative_size, image.info.format); |
| 1848 | } | 1885 | } |
| 1849 | total_used_memory += Common::AlignUp(tentative_size, 1024); | 1886 | total_used_memory += Common::AlignUp(tentative_size, 1024); |
| 1850 | if (total_used_memory > critical_memory && critical_gc < GC_EMERGENCY_COUNTS) { | ||
| 1851 | RunGarbageCollector(); | ||
| 1852 | critical_gc++; | ||
| 1853 | } | ||
| 1854 | image.lru_index = lru_cache.Insert(image_id, frame_tick); | 1887 | image.lru_index = lru_cache.Insert(image_id, frame_tick); |
| 1855 | 1888 | ||
| 1856 | ForEachGPUPage(image.gpu_addr, image.guest_size_bytes, [this, image_id](u64 page) { | 1889 | ForEachGPUPage(image.gpu_addr, image.guest_size_bytes, [this, image_id](u64 page) { |
diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index 0720494e5..cc27286f7 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h | |||
| @@ -148,6 +148,9 @@ public: | |||
| 148 | /// Fill image_view_ids with the compute images in indices | 148 | /// Fill image_view_ids with the compute images in indices |
| 149 | void FillComputeImageViews(std::span<ImageViewInOut> views); | 149 | void FillComputeImageViews(std::span<ImageViewInOut> views); |
| 150 | 150 | ||
| 151 | /// Handle feedback loops during draws. | ||
| 152 | void CheckFeedbackLoop(std::span<const ImageViewInOut> views); | ||
| 153 | |||
| 151 | /// Get the sampler from the graphics descriptor table in the specified index | 154 | /// Get the sampler from the graphics descriptor table in the specified index |
| 152 | Sampler* GetGraphicsSampler(u32 index); | 155 | Sampler* GetGraphicsSampler(u32 index); |
| 153 | 156 | ||
| @@ -424,7 +427,6 @@ private: | |||
| 424 | u64 minimum_memory; | 427 | u64 minimum_memory; |
| 425 | u64 expected_memory; | 428 | u64 expected_memory; |
| 426 | u64 critical_memory; | 429 | u64 critical_memory; |
| 427 | size_t critical_gc; | ||
| 428 | 430 | ||
| 429 | struct BufferDownload { | 431 | struct BufferDownload { |
| 430 | GPUVAddr address; | 432 | GPUVAddr address; |
diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp index f1071aa23..95a5b47d8 100644 --- a/src/video_core/texture_cache/util.cpp +++ b/src/video_core/texture_cache/util.cpp | |||
| @@ -18,6 +18,8 @@ | |||
| 18 | #include "common/bit_util.h" | 18 | #include "common/bit_util.h" |
| 19 | #include "common/common_types.h" | 19 | #include "common/common_types.h" |
| 20 | #include "common/div_ceil.h" | 20 | #include "common/div_ceil.h" |
| 21 | #include "common/scratch_buffer.h" | ||
| 22 | #include "common/settings.h" | ||
| 21 | #include "video_core/compatible_formats.h" | 23 | #include "video_core/compatible_formats.h" |
| 22 | #include "video_core/engines/maxwell_3d.h" | 24 | #include "video_core/engines/maxwell_3d.h" |
| 23 | #include "video_core/memory_manager.h" | 25 | #include "video_core/memory_manager.h" |
| @@ -28,6 +30,7 @@ | |||
| 28 | #include "video_core/texture_cache/samples_helper.h" | 30 | #include "video_core/texture_cache/samples_helper.h" |
| 29 | #include "video_core/texture_cache/util.h" | 31 | #include "video_core/texture_cache/util.h" |
| 30 | #include "video_core/textures/astc.h" | 32 | #include "video_core/textures/astc.h" |
| 33 | #include "video_core/textures/bcn.h" | ||
| 31 | #include "video_core/textures/decoders.h" | 34 | #include "video_core/textures/decoders.h" |
| 32 | 35 | ||
| 33 | namespace VideoCommon { | 36 | namespace VideoCommon { |
| @@ -120,7 +123,9 @@ template <u32 GOB_EXTENT> | |||
| 120 | return { | 123 | return { |
| 121 | .width = AdjustMipBlockSize<GOB_SIZE_X>(num_tiles.width, block_size.width, level), | 124 | .width = AdjustMipBlockSize<GOB_SIZE_X>(num_tiles.width, block_size.width, level), |
| 122 | .height = AdjustMipBlockSize<GOB_SIZE_Y>(num_tiles.height, block_size.height, level), | 125 | .height = AdjustMipBlockSize<GOB_SIZE_Y>(num_tiles.height, block_size.height, level), |
| 123 | .depth = AdjustMipBlockSize<GOB_SIZE_Z>(num_tiles.depth, block_size.depth, level), | 126 | .depth = level == 0 |
| 127 | ? block_size.depth | ||
| 128 | : AdjustMipBlockSize<GOB_SIZE_Z>(num_tiles.depth, block_size.depth, level), | ||
| 124 | }; | 129 | }; |
| 125 | } | 130 | } |
| 126 | 131 | ||
| @@ -162,6 +167,13 @@ template <u32 GOB_EXTENT> | |||
| 162 | } | 167 | } |
| 163 | 168 | ||
| 164 | [[nodiscard]] constexpr Extent3D TileShift(const LevelInfo& info, u32 level) { | 169 | [[nodiscard]] constexpr Extent3D TileShift(const LevelInfo& info, u32 level) { |
| 170 | if (level == 0) { | ||
| 171 | return Extent3D{ | ||
| 172 | .width = info.block.width, | ||
| 173 | .height = info.block.height, | ||
| 174 | .depth = info.block.depth, | ||
| 175 | }; | ||
| 176 | } | ||
| 165 | const Extent3D blocks = NumLevelBlocks(info, level); | 177 | const Extent3D blocks = NumLevelBlocks(info, level); |
| 166 | return Extent3D{ | 178 | return Extent3D{ |
| 167 | .width = AdjustTileSize(info.block.width, GOB_SIZE_X, blocks.width), | 179 | .width = AdjustTileSize(info.block.width, GOB_SIZE_X, blocks.width), |
| @@ -585,6 +597,21 @@ u32 CalculateConvertedSizeBytes(const ImageInfo& info) noexcept { | |||
| 585 | return info.size.width * BytesPerBlock(info.format); | 597 | return info.size.width * BytesPerBlock(info.format); |
| 586 | } | 598 | } |
| 587 | static constexpr Extent2D TILE_SIZE{1, 1}; | 599 | static constexpr Extent2D TILE_SIZE{1, 1}; |
| 600 | if (IsPixelFormatASTC(info.format) && Settings::values.astc_recompression.GetValue() != | ||
| 601 | Settings::AstcRecompression::Uncompressed) { | ||
| 602 | const u32 bpp_div = | ||
| 603 | Settings::values.astc_recompression.GetValue() == Settings::AstcRecompression::Bc1 ? 2 | ||
| 604 | : 1; | ||
| 605 | // NumBlocksPerLayer doesn't account for this correctly, so we have to do it manually. | ||
| 606 | u32 output_size = 0; | ||
| 607 | for (s32 i = 0; i < info.resources.levels; i++) { | ||
| 608 | const auto mip_size = AdjustMipSize(info.size, i); | ||
| 609 | const u32 plane_dim = | ||
| 610 | Common::AlignUp(mip_size.width, 4U) * Common::AlignUp(mip_size.height, 4U); | ||
| 611 | output_size += (plane_dim * info.size.depth * info.resources.layers) / bpp_div; | ||
| 612 | } | ||
| 613 | return output_size; | ||
| 614 | } | ||
| 588 | return NumBlocksPerLayer(info, TILE_SIZE) * info.resources.layers * CONVERTED_BYTES_PER_BLOCK; | 615 | return NumBlocksPerLayer(info, TILE_SIZE) * info.resources.layers * CONVERTED_BYTES_PER_BLOCK; |
| 589 | } | 616 | } |
| 590 | 617 | ||
| @@ -885,6 +912,7 @@ BufferCopy UploadBufferCopy(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr, | |||
| 885 | void ConvertImage(std::span<const u8> input, const ImageInfo& info, std::span<u8> output, | 912 | void ConvertImage(std::span<const u8> input, const ImageInfo& info, std::span<u8> output, |
| 886 | std::span<BufferImageCopy> copies) { | 913 | std::span<BufferImageCopy> copies) { |
| 887 | u32 output_offset = 0; | 914 | u32 output_offset = 0; |
| 915 | Common::ScratchBuffer<u8> decode_scratch; | ||
| 888 | 916 | ||
| 889 | const Extent2D tile_size = DefaultBlockSize(info.format); | 917 | const Extent2D tile_size = DefaultBlockSize(info.format); |
| 890 | for (BufferImageCopy& copy : copies) { | 918 | for (BufferImageCopy& copy : copies) { |
| @@ -895,22 +923,58 @@ void ConvertImage(std::span<const u8> input, const ImageInfo& info, std::span<u8 | |||
| 895 | ASSERT(copy.image_extent == mip_size); | 923 | ASSERT(copy.image_extent == mip_size); |
| 896 | ASSERT(copy.buffer_row_length == Common::AlignUp(mip_size.width, tile_size.width)); | 924 | ASSERT(copy.buffer_row_length == Common::AlignUp(mip_size.width, tile_size.width)); |
| 897 | ASSERT(copy.buffer_image_height == Common::AlignUp(mip_size.height, tile_size.height)); | 925 | ASSERT(copy.buffer_image_height == Common::AlignUp(mip_size.height, tile_size.height)); |
| 898 | if (IsPixelFormatASTC(info.format)) { | 926 | |
| 927 | const auto input_offset = input.subspan(copy.buffer_offset); | ||
| 928 | copy.buffer_offset = output_offset; | ||
| 929 | copy.buffer_row_length = mip_size.width; | ||
| 930 | copy.buffer_image_height = mip_size.height; | ||
| 931 | |||
| 932 | const auto recompression_setting = Settings::values.astc_recompression.GetValue(); | ||
| 933 | const bool astc = IsPixelFormatASTC(info.format); | ||
| 934 | |||
| 935 | if (astc && recompression_setting == Settings::AstcRecompression::Uncompressed) { | ||
| 899 | Tegra::Texture::ASTC::Decompress( | 936 | Tegra::Texture::ASTC::Decompress( |
| 900 | input.subspan(copy.buffer_offset), copy.image_extent.width, | 937 | input_offset, copy.image_extent.width, copy.image_extent.height, |
| 901 | copy.image_extent.height, | ||
| 902 | copy.image_subresource.num_layers * copy.image_extent.depth, tile_size.width, | 938 | copy.image_subresource.num_layers * copy.image_extent.depth, tile_size.width, |
| 903 | tile_size.height, output.subspan(output_offset)); | 939 | tile_size.height, output.subspan(output_offset)); |
| 940 | |||
| 941 | output_offset += copy.image_extent.width * copy.image_extent.height * | ||
| 942 | copy.image_subresource.num_layers * CONVERTED_BYTES_PER_BLOCK; | ||
| 943 | } else if (astc) { | ||
| 944 | // BC1 uses 0.5 bytes per texel | ||
| 945 | // BC3 uses 1 byte per texel | ||
| 946 | const auto compress = recompression_setting == Settings::AstcRecompression::Bc1 | ||
| 947 | ? Tegra::Texture::BCN::CompressBC1 | ||
| 948 | : Tegra::Texture::BCN::CompressBC3; | ||
| 949 | const auto bpp_div = recompression_setting == Settings::AstcRecompression::Bc1 ? 2 : 1; | ||
| 950 | |||
| 951 | const u32 plane_dim = copy.image_extent.width * copy.image_extent.height; | ||
| 952 | const u32 level_size = plane_dim * copy.image_extent.depth * | ||
| 953 | copy.image_subresource.num_layers * CONVERTED_BYTES_PER_BLOCK; | ||
| 954 | decode_scratch.resize_destructive(level_size); | ||
| 955 | |||
| 956 | Tegra::Texture::ASTC::Decompress( | ||
| 957 | input_offset, copy.image_extent.width, copy.image_extent.height, | ||
| 958 | copy.image_subresource.num_layers * copy.image_extent.depth, tile_size.width, | ||
| 959 | tile_size.height, decode_scratch); | ||
| 960 | |||
| 961 | compress(decode_scratch, copy.image_extent.width, copy.image_extent.height, | ||
| 962 | copy.image_subresource.num_layers * copy.image_extent.depth, | ||
| 963 | output.subspan(output_offset)); | ||
| 964 | |||
| 965 | const u32 aligned_plane_dim = Common::AlignUp(copy.image_extent.width, 4) * | ||
| 966 | Common::AlignUp(copy.image_extent.height, 4); | ||
| 967 | |||
| 968 | copy.buffer_size = | ||
| 969 | (aligned_plane_dim * copy.image_extent.depth * copy.image_subresource.num_layers) / | ||
| 970 | bpp_div; | ||
| 971 | output_offset += static_cast<u32>(copy.buffer_size); | ||
| 904 | } else { | 972 | } else { |
| 905 | DecompressBC4(input.subspan(copy.buffer_offset), copy.image_extent, | 973 | DecompressBC4(input_offset, copy.image_extent, output.subspan(output_offset)); |
| 906 | output.subspan(output_offset)); | ||
| 907 | } | ||
| 908 | copy.buffer_offset = output_offset; | ||
| 909 | copy.buffer_row_length = mip_size.width; | ||
| 910 | copy.buffer_image_height = mip_size.height; | ||
| 911 | 974 | ||
| 912 | output_offset += copy.image_extent.width * copy.image_extent.height * | 975 | output_offset += copy.image_extent.width * copy.image_extent.height * |
| 913 | copy.image_subresource.num_layers * CONVERTED_BYTES_PER_BLOCK; | 976 | copy.image_subresource.num_layers * CONVERTED_BYTES_PER_BLOCK; |
| 977 | } | ||
| 914 | } | 978 | } |
| 915 | } | 979 | } |
| 916 | 980 | ||
| @@ -1233,7 +1297,9 @@ u32 MapSizeBytes(const ImageBase& image) { | |||
| 1233 | 1297 | ||
| 1234 | static_assert(CalculateLevelSize(LevelInfo{{1920, 1080, 1}, {0, 2, 0}, {1, 1}, 2, 0}, 0) == | 1298 | static_assert(CalculateLevelSize(LevelInfo{{1920, 1080, 1}, {0, 2, 0}, {1, 1}, 2, 0}, 0) == |
| 1235 | 0x7f8000); | 1299 | 0x7f8000); |
| 1236 | static_assert(CalculateLevelSize(LevelInfo{{32, 32, 1}, {0, 0, 4}, {1, 1}, 4, 0}, 0) == 0x4000); | 1300 | static_assert(CalculateLevelSize(LevelInfo{{32, 32, 1}, {0, 0, 4}, {1, 1}, 4, 0}, 0) == 0x40000); |
| 1301 | |||
| 1302 | static_assert(CalculateLevelSize(LevelInfo{{128, 8, 1}, {0, 4, 0}, {1, 1}, 4, 0}, 0) == 0x40000); | ||
| 1237 | 1303 | ||
| 1238 | static_assert(CalculateLevelOffset(PixelFormat::R8_SINT, {1920, 1080, 1}, {0, 2, 0}, 0, 7) == | 1304 | static_assert(CalculateLevelOffset(PixelFormat::R8_SINT, {1920, 1080, 1}, {0, 2, 0}, 0, 7) == |
| 1239 | 0x2afc00); | 1305 | 0x2afc00); |
diff --git a/src/video_core/textures/astc.cpp b/src/video_core/textures/astc.cpp index a68bc0d77..fef0be31d 100644 --- a/src/video_core/textures/astc.cpp +++ b/src/video_core/textures/astc.cpp | |||
| @@ -16,8 +16,8 @@ | |||
| 16 | #include "common/alignment.h" | 16 | #include "common/alignment.h" |
| 17 | #include "common/common_types.h" | 17 | #include "common/common_types.h" |
| 18 | #include "common/polyfill_ranges.h" | 18 | #include "common/polyfill_ranges.h" |
| 19 | #include "common/thread_worker.h" | ||
| 20 | #include "video_core/textures/astc.h" | 19 | #include "video_core/textures/astc.h" |
| 20 | #include "video_core/textures/workers.h" | ||
| 21 | 21 | ||
| 22 | class InputBitStream { | 22 | class InputBitStream { |
| 23 | public: | 23 | public: |
| @@ -1656,8 +1656,7 @@ void Decompress(std::span<const uint8_t> data, uint32_t width, uint32_t height, | |||
| 1656 | const u32 rows = Common::DivideUp(height, block_height); | 1656 | const u32 rows = Common::DivideUp(height, block_height); |
| 1657 | const u32 cols = Common::DivideUp(width, block_width); | 1657 | const u32 cols = Common::DivideUp(width, block_width); |
| 1658 | 1658 | ||
| 1659 | static Common::ThreadWorker workers{std::max(std::thread::hardware_concurrency(), 2U) / 2, | 1659 | Common::ThreadWorker& workers{GetThreadWorkers()}; |
| 1660 | "ASTCDecompress"}; | ||
| 1661 | 1660 | ||
| 1662 | for (u32 z = 0; z < depth; ++z) { | 1661 | for (u32 z = 0; z < depth; ++z) { |
| 1663 | const u32 depth_offset = z * height * width * 4; | 1662 | const u32 depth_offset = z * height * width * 4; |
diff --git a/src/video_core/textures/bcn.cpp b/src/video_core/textures/bcn.cpp new file mode 100644 index 000000000..671212a49 --- /dev/null +++ b/src/video_core/textures/bcn.cpp | |||
| @@ -0,0 +1,87 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <stb_dxt.h> | ||
| 5 | #include <string.h> | ||
| 6 | |||
| 7 | #include "common/alignment.h" | ||
| 8 | #include "video_core/textures/bcn.h" | ||
| 9 | #include "video_core/textures/workers.h" | ||
| 10 | |||
| 11 | namespace Tegra::Texture::BCN { | ||
| 12 | |||
| 13 | using BCNCompressor = void(u8* block_output, const u8* block_input, bool any_alpha); | ||
| 14 | |||
| 15 | template <u32 BytesPerBlock, bool ThresholdAlpha = false> | ||
| 16 | void CompressBCN(std::span<const uint8_t> data, uint32_t width, uint32_t height, uint32_t depth, | ||
| 17 | std::span<uint8_t> output, BCNCompressor f) { | ||
| 18 | constexpr u8 alpha_threshold = 128; | ||
| 19 | constexpr u32 bytes_per_px = 4; | ||
| 20 | const u32 plane_dim = width * height; | ||
| 21 | |||
| 22 | Common::ThreadWorker& workers{GetThreadWorkers()}; | ||
| 23 | |||
| 24 | for (u32 z = 0; z < depth; z++) { | ||
| 25 | for (u32 y = 0; y < height; y += 4) { | ||
| 26 | auto compress_row = [z, y, width, height, plane_dim, f, data, output]() { | ||
| 27 | for (u32 x = 0; x < width; x += 4) { | ||
| 28 | // Gather 4x4 block of RGBA texels | ||
| 29 | u8 input_colors[4][4][4]; | ||
| 30 | bool any_alpha = false; | ||
| 31 | |||
| 32 | for (u32 j = 0; j < 4; j++) { | ||
| 33 | for (u32 i = 0; i < 4; i++) { | ||
| 34 | const size_t coord = | ||
| 35 | (z * plane_dim + (y + j) * width + (x + i)) * bytes_per_px; | ||
| 36 | |||
| 37 | if ((x + i < width) && (y + j < height)) { | ||
| 38 | if constexpr (ThresholdAlpha) { | ||
| 39 | if (data[coord + 3] >= alpha_threshold) { | ||
| 40 | input_colors[j][i][0] = data[coord + 0]; | ||
| 41 | input_colors[j][i][1] = data[coord + 1]; | ||
| 42 | input_colors[j][i][2] = data[coord + 2]; | ||
| 43 | input_colors[j][i][3] = 255; | ||
| 44 | } else { | ||
| 45 | any_alpha = true; | ||
| 46 | memset(input_colors[j][i], 0, bytes_per_px); | ||
| 47 | } | ||
| 48 | } else { | ||
| 49 | memcpy(input_colors[j][i], &data[coord], bytes_per_px); | ||
| 50 | } | ||
| 51 | } else { | ||
| 52 | memset(input_colors[j][i], 0, bytes_per_px); | ||
| 53 | } | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | const u32 bytes_per_row = BytesPerBlock * Common::DivideUp(width, 4U); | ||
| 58 | const u32 bytes_per_plane = bytes_per_row * Common::DivideUp(height, 4U); | ||
| 59 | f(output.data() + z * bytes_per_plane + (y / 4) * bytes_per_row + | ||
| 60 | (x / 4) * BytesPerBlock, | ||
| 61 | reinterpret_cast<u8*>(input_colors), any_alpha); | ||
| 62 | } | ||
| 63 | }; | ||
| 64 | workers.QueueWork(std::move(compress_row)); | ||
| 65 | } | ||
| 66 | workers.WaitForRequests(); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | void CompressBC1(std::span<const uint8_t> data, uint32_t width, uint32_t height, uint32_t depth, | ||
| 71 | std::span<uint8_t> output) { | ||
| 72 | CompressBCN<8, true>(data, width, height, depth, output, | ||
| 73 | [](u8* block_output, const u8* block_input, bool any_alpha) { | ||
| 74 | stb_compress_bc1_block(block_output, block_input, any_alpha, | ||
| 75 | STB_DXT_NORMAL); | ||
| 76 | }); | ||
| 77 | } | ||
| 78 | |||
| 79 | void CompressBC3(std::span<const uint8_t> data, uint32_t width, uint32_t height, uint32_t depth, | ||
| 80 | std::span<uint8_t> output) { | ||
| 81 | CompressBCN<16, false>(data, width, height, depth, output, | ||
| 82 | [](u8* block_output, const u8* block_input, bool any_alpha) { | ||
| 83 | stb_compress_bc3_block(block_output, block_input, STB_DXT_NORMAL); | ||
| 84 | }); | ||
| 85 | } | ||
| 86 | |||
| 87 | } // namespace Tegra::Texture::BCN | ||
diff --git a/src/video_core/textures/bcn.h b/src/video_core/textures/bcn.h new file mode 100644 index 000000000..6464af885 --- /dev/null +++ b/src/video_core/textures/bcn.h | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <span> | ||
| 7 | #include <stdint.h> | ||
| 8 | |||
| 9 | namespace Tegra::Texture::BCN { | ||
| 10 | |||
| 11 | void CompressBC1(std::span<const uint8_t> data, uint32_t width, uint32_t height, uint32_t depth, | ||
| 12 | std::span<uint8_t> output); | ||
| 13 | |||
| 14 | void CompressBC3(std::span<const uint8_t> data, uint32_t width, uint32_t height, uint32_t depth, | ||
| 15 | std::span<uint8_t> output); | ||
| 16 | |||
| 17 | } // namespace Tegra::Texture::BCN | ||
diff --git a/src/video_core/textures/workers.cpp b/src/video_core/textures/workers.cpp new file mode 100644 index 000000000..a71c305f4 --- /dev/null +++ b/src/video_core/textures/workers.cpp | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "video_core/textures/workers.h" | ||
| 5 | |||
| 6 | namespace Tegra::Texture { | ||
| 7 | |||
| 8 | Common::ThreadWorker& GetThreadWorkers() { | ||
| 9 | static Common::ThreadWorker workers{std::max(std::thread::hardware_concurrency(), 2U) / 2, | ||
| 10 | "ImageTranscode"}; | ||
| 11 | |||
| 12 | return workers; | ||
| 13 | } | ||
| 14 | |||
| 15 | } // namespace Tegra::Texture | ||
diff --git a/src/video_core/textures/workers.h b/src/video_core/textures/workers.h new file mode 100644 index 000000000..008dd05b3 --- /dev/null +++ b/src/video_core/textures/workers.h | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/thread_worker.h" | ||
| 7 | |||
| 8 | namespace Tegra::Texture { | ||
| 9 | |||
| 10 | Common::ThreadWorker& GetThreadWorkers(); | ||
| 11 | |||
| 12 | } | ||
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index f6e6f2736..3a7c2dedf 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp | |||
| @@ -1001,6 +1001,11 @@ u64 Device::GetDeviceMemoryUsage() const { | |||
| 1001 | } | 1001 | } |
| 1002 | 1002 | ||
| 1003 | void Device::CollectPhysicalMemoryInfo() { | 1003 | void Device::CollectPhysicalMemoryInfo() { |
| 1004 | // Account for resolution scaling in memory limits | ||
| 1005 | const size_t normal_memory = 6_GiB; | ||
| 1006 | const size_t scaler_memory = 1_GiB * Settings::values.resolution_info.ScaleUp(1); | ||
| 1007 | |||
| 1008 | // Calculate limits using memory budget | ||
| 1004 | VkPhysicalDeviceMemoryBudgetPropertiesEXT budget{}; | 1009 | VkPhysicalDeviceMemoryBudgetPropertiesEXT budget{}; |
| 1005 | budget.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT; | 1010 | budget.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT; |
| 1006 | const auto mem_info = | 1011 | const auto mem_info = |
| @@ -1030,11 +1035,12 @@ void Device::CollectPhysicalMemoryInfo() { | |||
| 1030 | if (!is_integrated) { | 1035 | if (!is_integrated) { |
| 1031 | const u64 reserve_memory = std::min<u64>(device_access_memory / 8, 1_GiB); | 1036 | const u64 reserve_memory = std::min<u64>(device_access_memory / 8, 1_GiB); |
| 1032 | device_access_memory -= reserve_memory; | 1037 | device_access_memory -= reserve_memory; |
| 1038 | device_access_memory = std::min<u64>(device_access_memory, normal_memory + scaler_memory); | ||
| 1033 | return; | 1039 | return; |
| 1034 | } | 1040 | } |
| 1035 | const s64 available_memory = static_cast<s64>(device_access_memory - device_initial_usage); | 1041 | const s64 available_memory = static_cast<s64>(device_access_memory - device_initial_usage); |
| 1036 | device_access_memory = static_cast<u64>(std::max<s64>( | 1042 | device_access_memory = static_cast<u64>(std::max<s64>( |
| 1037 | std::min<s64>(available_memory - 8_GiB, 4_GiB), static_cast<s64>(local_memory))); | 1043 | std::min<s64>(available_memory - 8_GiB, 4_GiB), std::min<s64>(local_memory, 4_GiB))); |
| 1038 | } | 1044 | } |
| 1039 | 1045 | ||
| 1040 | void Device::CollectToolingInfo() { | 1046 | void Device::CollectToolingInfo() { |
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 70737c54e..662651196 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp | |||
| @@ -711,6 +711,7 @@ void Config::ReadRendererValues() { | |||
| 711 | ReadGlobalSetting(Settings::values.nvdec_emulation); | 711 | ReadGlobalSetting(Settings::values.nvdec_emulation); |
| 712 | ReadGlobalSetting(Settings::values.accelerate_astc); | 712 | ReadGlobalSetting(Settings::values.accelerate_astc); |
| 713 | ReadGlobalSetting(Settings::values.async_astc); | 713 | ReadGlobalSetting(Settings::values.async_astc); |
| 714 | ReadGlobalSetting(Settings::values.astc_recompression); | ||
| 714 | ReadGlobalSetting(Settings::values.use_reactive_flushing); | 715 | ReadGlobalSetting(Settings::values.use_reactive_flushing); |
| 715 | ReadGlobalSetting(Settings::values.shader_backend); | 716 | ReadGlobalSetting(Settings::values.shader_backend); |
| 716 | ReadGlobalSetting(Settings::values.use_asynchronous_shaders); | 717 | ReadGlobalSetting(Settings::values.use_asynchronous_shaders); |
| @@ -1359,6 +1360,10 @@ void Config::SaveRendererValues() { | |||
| 1359 | Settings::values.nvdec_emulation.UsingGlobal()); | 1360 | Settings::values.nvdec_emulation.UsingGlobal()); |
| 1360 | WriteGlobalSetting(Settings::values.accelerate_astc); | 1361 | WriteGlobalSetting(Settings::values.accelerate_astc); |
| 1361 | WriteGlobalSetting(Settings::values.async_astc); | 1362 | WriteGlobalSetting(Settings::values.async_astc); |
| 1363 | WriteSetting(QString::fromStdString(Settings::values.astc_recompression.GetLabel()), | ||
| 1364 | static_cast<u32>(Settings::values.astc_recompression.GetValue(global)), | ||
| 1365 | static_cast<u32>(Settings::values.astc_recompression.GetDefault()), | ||
| 1366 | Settings::values.astc_recompression.UsingGlobal()); | ||
| 1362 | WriteGlobalSetting(Settings::values.use_reactive_flushing); | 1367 | WriteGlobalSetting(Settings::values.use_reactive_flushing); |
| 1363 | WriteSetting(QString::fromStdString(Settings::values.shader_backend.GetLabel()), | 1368 | WriteSetting(QString::fromStdString(Settings::values.shader_backend.GetLabel()), |
| 1364 | static_cast<u32>(Settings::values.shader_backend.GetValue(global)), | 1369 | static_cast<u32>(Settings::values.shader_backend.GetValue(global)), |
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 7d26e9ab6..9cb9db6cf 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h | |||
| @@ -208,3 +208,4 @@ Q_DECLARE_METATYPE(Settings::ScalingFilter); | |||
| 208 | Q_DECLARE_METATYPE(Settings::AntiAliasing); | 208 | Q_DECLARE_METATYPE(Settings::AntiAliasing); |
| 209 | Q_DECLARE_METATYPE(Settings::RendererBackend); | 209 | Q_DECLARE_METATYPE(Settings::RendererBackend); |
| 210 | Q_DECLARE_METATYPE(Settings::ShaderBackend); | 210 | Q_DECLARE_METATYPE(Settings::ShaderBackend); |
| 211 | Q_DECLARE_METATYPE(Settings::AstcRecompression); | ||
diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp index 1f3e489d0..896863f87 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.cpp +++ b/src/yuzu/configuration/configure_graphics_advanced.cpp | |||
| @@ -27,6 +27,7 @@ void ConfigureGraphicsAdvanced::SetConfiguration() { | |||
| 27 | ui->async_present->setEnabled(runtime_lock); | 27 | ui->async_present->setEnabled(runtime_lock); |
| 28 | ui->renderer_force_max_clock->setEnabled(runtime_lock); | 28 | ui->renderer_force_max_clock->setEnabled(runtime_lock); |
| 29 | ui->async_astc->setEnabled(runtime_lock); | 29 | ui->async_astc->setEnabled(runtime_lock); |
| 30 | ui->astc_recompression_combobox->setEnabled(runtime_lock); | ||
| 30 | ui->use_asynchronous_shaders->setEnabled(runtime_lock); | 31 | ui->use_asynchronous_shaders->setEnabled(runtime_lock); |
| 31 | ui->anisotropic_filtering_combobox->setEnabled(runtime_lock); | 32 | ui->anisotropic_filtering_combobox->setEnabled(runtime_lock); |
| 32 | ui->enable_compute_pipelines_checkbox->setEnabled(runtime_lock); | 33 | ui->enable_compute_pipelines_checkbox->setEnabled(runtime_lock); |
| @@ -47,14 +48,20 @@ void ConfigureGraphicsAdvanced::SetConfiguration() { | |||
| 47 | static_cast<int>(Settings::values.gpu_accuracy.GetValue())); | 48 | static_cast<int>(Settings::values.gpu_accuracy.GetValue())); |
| 48 | ui->anisotropic_filtering_combobox->setCurrentIndex( | 49 | ui->anisotropic_filtering_combobox->setCurrentIndex( |
| 49 | Settings::values.max_anisotropy.GetValue()); | 50 | Settings::values.max_anisotropy.GetValue()); |
| 51 | ui->astc_recompression_combobox->setCurrentIndex( | ||
| 52 | static_cast<int>(Settings::values.astc_recompression.GetValue())); | ||
| 50 | } else { | 53 | } else { |
| 51 | ConfigurationShared::SetPerGameSetting(ui->gpu_accuracy, &Settings::values.gpu_accuracy); | 54 | ConfigurationShared::SetPerGameSetting(ui->gpu_accuracy, &Settings::values.gpu_accuracy); |
| 52 | ConfigurationShared::SetPerGameSetting(ui->anisotropic_filtering_combobox, | 55 | ConfigurationShared::SetPerGameSetting(ui->anisotropic_filtering_combobox, |
| 53 | &Settings::values.max_anisotropy); | 56 | &Settings::values.max_anisotropy); |
| 57 | ConfigurationShared::SetPerGameSetting(ui->astc_recompression_combobox, | ||
| 58 | &Settings::values.astc_recompression); | ||
| 54 | ConfigurationShared::SetHighlight(ui->label_gpu_accuracy, | 59 | ConfigurationShared::SetHighlight(ui->label_gpu_accuracy, |
| 55 | !Settings::values.gpu_accuracy.UsingGlobal()); | 60 | !Settings::values.gpu_accuracy.UsingGlobal()); |
| 56 | ConfigurationShared::SetHighlight(ui->af_label, | 61 | ConfigurationShared::SetHighlight(ui->af_label, |
| 57 | !Settings::values.max_anisotropy.UsingGlobal()); | 62 | !Settings::values.max_anisotropy.UsingGlobal()); |
| 63 | ConfigurationShared::SetHighlight(ui->label_astc_recompression, | ||
| 64 | !Settings::values.astc_recompression.UsingGlobal()); | ||
| 58 | } | 65 | } |
| 59 | } | 66 | } |
| 60 | 67 | ||
| @@ -71,6 +78,8 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() { | |||
| 71 | ui->use_reactive_flushing, use_reactive_flushing); | 78 | ui->use_reactive_flushing, use_reactive_flushing); |
| 72 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.async_astc, ui->async_astc, | 79 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.async_astc, ui->async_astc, |
| 73 | async_astc); | 80 | async_astc); |
| 81 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.astc_recompression, | ||
| 82 | ui->astc_recompression_combobox); | ||
| 74 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_shaders, | 83 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_shaders, |
| 75 | ui->use_asynchronous_shaders, | 84 | ui->use_asynchronous_shaders, |
| 76 | use_asynchronous_shaders); | 85 | use_asynchronous_shaders); |
| @@ -105,6 +114,8 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() { | |||
| 105 | Settings::values.renderer_force_max_clock.UsingGlobal()); | 114 | Settings::values.renderer_force_max_clock.UsingGlobal()); |
| 106 | ui->use_reactive_flushing->setEnabled(Settings::values.use_reactive_flushing.UsingGlobal()); | 115 | ui->use_reactive_flushing->setEnabled(Settings::values.use_reactive_flushing.UsingGlobal()); |
| 107 | ui->async_astc->setEnabled(Settings::values.async_astc.UsingGlobal()); | 116 | ui->async_astc->setEnabled(Settings::values.async_astc.UsingGlobal()); |
| 117 | ui->astc_recompression_combobox->setEnabled( | ||
| 118 | Settings::values.astc_recompression.UsingGlobal()); | ||
| 108 | ui->use_asynchronous_shaders->setEnabled( | 119 | ui->use_asynchronous_shaders->setEnabled( |
| 109 | Settings::values.use_asynchronous_shaders.UsingGlobal()); | 120 | Settings::values.use_asynchronous_shaders.UsingGlobal()); |
| 110 | ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal()); | 121 | ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal()); |
| @@ -144,6 +155,9 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() { | |||
| 144 | ConfigurationShared::SetColoredComboBox( | 155 | ConfigurationShared::SetColoredComboBox( |
| 145 | ui->anisotropic_filtering_combobox, ui->af_label, | 156 | ui->anisotropic_filtering_combobox, ui->af_label, |
| 146 | static_cast<int>(Settings::values.max_anisotropy.GetValue(true))); | 157 | static_cast<int>(Settings::values.max_anisotropy.GetValue(true))); |
| 158 | ConfigurationShared::SetColoredComboBox( | ||
| 159 | ui->astc_recompression_combobox, ui->label_astc_recompression, | ||
| 160 | static_cast<int>(Settings::values.astc_recompression.GetValue(true))); | ||
| 147 | } | 161 | } |
| 148 | 162 | ||
| 149 | void ConfigureGraphicsAdvanced::ExposeComputeOption() { | 163 | void ConfigureGraphicsAdvanced::ExposeComputeOption() { |
diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui index 9ef7c8e8f..37757a918 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.ui +++ b/src/yuzu/configuration/configure_graphics_advanced.ui | |||
| @@ -70,6 +70,50 @@ | |||
| 70 | </widget> | 70 | </widget> |
| 71 | </item> | 71 | </item> |
| 72 | <item> | 72 | <item> |
| 73 | <widget class="QWidget" name="astc_recompression_layout" native="true"> | ||
| 74 | <layout class="QHBoxLayout" name="horizontalLayout_3"> | ||
| 75 | <property name="leftMargin"> | ||
| 76 | <number>0</number> | ||
| 77 | </property> | ||
| 78 | <property name="topMargin"> | ||
| 79 | <number>0</number> | ||
| 80 | </property> | ||
| 81 | <property name="rightMargin"> | ||
| 82 | <number>0</number> | ||
| 83 | </property> | ||
| 84 | <property name="bottomMargin"> | ||
| 85 | <number>0</number> | ||
| 86 | </property> | ||
| 87 | <item> | ||
| 88 | <widget class="QLabel" name="label_astc_recompression"> | ||
| 89 | <property name="text"> | ||
| 90 | <string>ASTC recompression:</string> | ||
| 91 | </property> | ||
| 92 | </widget> | ||
| 93 | </item> | ||
| 94 | <item> | ||
| 95 | <widget class="QComboBox" name="astc_recompression_combobox"> | ||
| 96 | <item> | ||
| 97 | <property name="text"> | ||
| 98 | <string>Uncompressed (Best quality)</string> | ||
| 99 | </property> | ||
| 100 | </item> | ||
| 101 | <item> | ||
| 102 | <property name="text"> | ||
| 103 | <string>BC1 (Low quality)</string> | ||
| 104 | </property> | ||
| 105 | </item> | ||
| 106 | <item> | ||
| 107 | <property name="text"> | ||
| 108 | <string>BC3 (Medium quality)</string> | ||
| 109 | </property> | ||
| 110 | </item> | ||
| 111 | </widget> | ||
| 112 | </item> | ||
| 113 | </layout> | ||
| 114 | </widget> | ||
| 115 | </item> | ||
| 116 | <item> | ||
| 73 | <widget class="QCheckBox" name="async_present"> | 117 | <widget class="QCheckBox" name="async_present"> |
| 74 | <property name="text"> | 118 | <property name="text"> |
| 75 | <string>Enable asynchronous presentation (Vulkan only)</string> | 119 | <string>Enable asynchronous presentation (Vulkan only)</string> |
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index dc9a3d68f..c5bc472ca 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp | |||
| @@ -318,6 +318,7 @@ void Config::ReadValues() { | |||
| 318 | ReadSetting("Renderer", Settings::values.nvdec_emulation); | 318 | ReadSetting("Renderer", Settings::values.nvdec_emulation); |
| 319 | ReadSetting("Renderer", Settings::values.accelerate_astc); | 319 | ReadSetting("Renderer", Settings::values.accelerate_astc); |
| 320 | ReadSetting("Renderer", Settings::values.async_astc); | 320 | ReadSetting("Renderer", Settings::values.async_astc); |
| 321 | ReadSetting("Renderer", Settings::values.astc_recompression); | ||
| 321 | ReadSetting("Renderer", Settings::values.use_fast_gpu_time); | 322 | ReadSetting("Renderer", Settings::values.use_fast_gpu_time); |
| 322 | ReadSetting("Renderer", Settings::values.use_vulkan_driver_pipeline_cache); | 323 | ReadSetting("Renderer", Settings::values.use_vulkan_driver_pipeline_cache); |
| 323 | 324 | ||
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 5e7c3ac04..644a30e59 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h | |||
| @@ -360,6 +360,10 @@ accelerate_astc = | |||
| 360 | # 0 (default): Off, 1: On | 360 | # 0 (default): Off, 1: On |
| 361 | async_astc = | 361 | async_astc = |
| 362 | 362 | ||
| 363 | # Recompress ASTC textures to a different format. | ||
| 364 | # 0 (default): Uncompressed, 1: BC1 (Low quality), 2: BC3: (Medium quality) | ||
| 365 | async_astc = | ||
| 366 | |||
| 363 | # Turns on the speed limiter, which will limit the emulation speed to the desired speed limit value | 367 | # Turns on the speed limiter, which will limit the emulation speed to the desired speed limit value |
| 364 | # 0: Off, 1: On (default) | 368 | # 0: Off, 1: On (default) |
| 365 | use_speed_limit = | 369 | use_speed_limit = |
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 5f39ece32..7b6d49c63 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp | |||
| @@ -227,7 +227,7 @@ int main(int argc, char** argv) { | |||
| 227 | }; | 227 | }; |
| 228 | 228 | ||
| 229 | while (optind < argc) { | 229 | while (optind < argc) { |
| 230 | int arg = getopt_long(argc, argv, "g:fhvp::c:", long_options, &option_index); | 230 | int arg = getopt_long(argc, argv, "g:fhvp::c:u:", long_options, &option_index); |
| 231 | if (arg != -1) { | 231 | if (arg != -1) { |
| 232 | switch (static_cast<char>(arg)) { | 232 | switch (static_cast<char>(arg)) { |
| 233 | case 'c': | 233 | case 'c': |
| @@ -283,7 +283,7 @@ int main(int argc, char** argv) { | |||
| 283 | break; | 283 | break; |
| 284 | case 'u': | 284 | case 'u': |
| 285 | selected_user = atoi(optarg); | 285 | selected_user = atoi(optarg); |
| 286 | return 0; | 286 | break; |
| 287 | case 'v': | 287 | case 'v': |
| 288 | PrintVersion(); | 288 | PrintVersion(); |
| 289 | return 0; | 289 | return 0; |