diff --git a/.github/linux-appimage-qt.sh b/.github/linux-appimage-qt.sh index e33a4b21f..fe77c678c 100755 --- a/.github/linux-appimage-qt.sh +++ b/.github/linux-appimage-qt.sh @@ -14,12 +14,10 @@ export PATH="$Qt6_DIR/bin:$PATH" wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt-x86_64.sh -wget -q https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gstreamer/master/linuxdeploy-plugin-gstreamer.sh chmod a+x linuxdeploy-x86_64.AppImage chmod a+x linuxdeploy-plugin-qt-x86_64.AppImage chmod a+x linuxdeploy-plugin-checkrt-x86_64.sh -chmod a+x linuxdeploy-plugin-gstreamer.sh # Build AppImage ./linuxdeploy-x86_64.AppImage --appdir AppDir @@ -27,5 +25,7 @@ chmod a+x linuxdeploy-plugin-gstreamer.sh cp -a "$GITHUB_WORKSPACE/build/translations" AppDir/usr/bin -./linuxdeploy-x86_64.AppImage --appdir AppDir -d "$GITHUB_WORKSPACE"/.github/shadps4.desktop -e "$GITHUB_WORKSPACE"/build/shadps4 -i "$GITHUB_WORKSPACE"/.github/shadps4.png --plugin qt --plugin gstreamer --output appimage +./linuxdeploy-x86_64.AppImage --appdir AppDir -d "$GITHUB_WORKSPACE"/.github/shadps4.desktop -e "$GITHUB_WORKSPACE"/build/shadps4 -i "$GITHUB_WORKSPACE"/.github/shadps4.png --plugin qt +rm AppDir/usr/plugins/multimedia/libgstreamermediaplugin.so +./linuxdeploy-x86_64.AppImage --appdir AppDir --output appimage mv Shadps4-x86_64.AppImage Shadps4-qt.AppImage diff --git a/src/core/libraries/np_trophy/np_trophy.cpp b/src/core/libraries/np_trophy/np_trophy.cpp index 91fdeb991..0641a2c06 100644 --- a/src/core/libraries/np_trophy/np_trophy.cpp +++ b/src/core/libraries/np_trophy/np_trophy.cpp @@ -14,8 +14,6 @@ namespace Libraries::NpTrophy { -static TrophyUI g_trophy_ui; - std::string game_serial; static constexpr auto MaxTrophyHandles = 4u; @@ -223,6 +221,14 @@ int PS4_SYSV_ABI sceNpTrophyGetGameIcon(OrbisNpTrophyContext context, OrbisNpTro return ORBIS_OK; } +struct GameTrophyInfo { + uint32_t num_groups; + uint32_t num_trophies; + uint32_t num_trophies_by_rarity[5]; + uint32_t unlocked_trophies; + uint32_t unlocked_trophies_by_rarity[5]; +}; + int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, OrbisNpTrophyGameDetails* details, OrbisNpTrophyGameData* data) { @@ -240,79 +246,66 @@ int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTro if (details->size != 0x4A0 || data->size != 0x20) return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT; - const auto trophyDir = + const auto trophy_dir = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles"; + auto trophy_file = trophy_dir / "trophy00" / "Xml" / "TROP.XML"; pugi::xml_document doc; - pugi::xml_parse_result result = - doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str()); + pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str()); - if (result) { + ASSERT_MSG(result, "Couldnt parse trophy XML : {}", result.description()); - uint32_t numGroups = 0; - uint32_t numTrophies = 0; - uint32_t numTrophiesByRarity[5]; - numTrophiesByRarity[1] = 0; - numTrophiesByRarity[2] = 0; - numTrophiesByRarity[3] = 0; - numTrophiesByRarity[4] = 0; - uint32_t unlockedTrophies = 0; - uint32_t unlockedTrophiesByRarity[5]; - unlockedTrophiesByRarity[1] = 0; - unlockedTrophiesByRarity[2] = 0; - unlockedTrophiesByRarity[3] = 0; - unlockedTrophiesByRarity[4] = 0; + GameTrophyInfo game_info{}; - auto trophyconf = doc.child("trophyconf"); - for (pugi::xml_node_iterator it = trophyconf.children().begin(); - it != trophyconf.children().end(); ++it) { + auto trophyconf = doc.child("trophyconf"); + for (const pugi::xml_node& node : trophyconf.children()) { + std::string_view node_name = node.name(); - if (std::string(it->name()) == "title-name") { - strncpy(details->title, it->text().as_string(), - ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE); - } - - if (std::string(it->name()) == "title-detail") { - strncpy(details->description, it->text().as_string(), - ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE); - } - - if (std::string(it->name()) == "group") - numGroups++; - - if (std::string(it->name()) == "trophy") { - std::string currentTrophyUnlockState = it->attribute("unlockstate").value(); - std::string currentTrophyGrade = it->attribute("ttype").value(); - - numTrophies++; - if (!currentTrophyGrade.empty()) { - int trophyGrade = GetTrophyGradeFromChar(currentTrophyGrade.at(0)); - numTrophiesByRarity[trophyGrade]++; - if (currentTrophyUnlockState == "unlocked") { - unlockedTrophies++; - unlockedTrophiesByRarity[trophyGrade]++; - } - } - } + if (node_name == "title-name") { + strncpy(details->title, node.text().as_string(), ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE); } - details->numGroups = numGroups; - details->numTrophies = numTrophies; - details->numPlatinum = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_PLATINUM]; - details->numGold = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_GOLD]; - details->numSilver = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_SILVER]; - details->numBronze = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_BRONZE]; - data->unlockedTrophies = unlockedTrophies; - data->unlockedPlatinum = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_PLATINUM]; - data->unlockedGold = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_GOLD]; - data->unlockedSilver = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_SILVER]; - data->unlockedBronze = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_BRONZE]; + if (node_name == "title-detail") { + strncpy(details->description, node.text().as_string(), + ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE); + } - // maybe this should be 1 instead of 100? - data->progressPercentage = 100; + if (node_name == "group") + game_info.num_groups++; - } else - LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description()); + if (node_name == "trophy") { + bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool(); + std::string_view current_trophy_grade = node.attribute("ttype").value(); + + if (current_trophy_grade.empty()) { + continue; + } + + game_info.num_trophies++; + int trophy_grade = GetTrophyGradeFromChar(current_trophy_grade.at(0)); + game_info.num_trophies_by_rarity[trophy_grade]++; + + if (current_trophy_unlockstate) { + game_info.unlocked_trophies++; + game_info.unlocked_trophies_by_rarity[trophy_grade]++; + } + } + } + + details->num_groups = game_info.num_groups; + details->num_trophies = game_info.num_trophies; + details->num_platinum = game_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_PLATINUM]; + details->num_gold = game_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_GOLD]; + details->num_silver = game_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_SILVER]; + details->num_bronze = game_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_BRONZE]; + data->unlocked_trophies = game_info.unlocked_trophies; + data->unlocked_platinum = game_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_PLATINUM]; + data->unlocked_gold = game_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_GOLD]; + data->unlocked_silver = game_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_SILVER]; + data->unlocked_bronze = game_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_BRONZE]; + + // maybe this should be 1 instead of 100? + data->progress_percentage = 100; return ORBIS_OK; } @@ -323,6 +316,13 @@ int PS4_SYSV_ABI sceNpTrophyGetGroupIcon(OrbisNpTrophyContext context, OrbisNpTr return ORBIS_OK; } +struct GroupTrophyInfo { + uint32_t num_trophies; + uint32_t num_trophies_by_rarity[5]; + uint32_t unlocked_trophies; + uint32_t unlocked_trophies_by_rarity[5]; +}; + int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle, OrbisNpTrophyGroupId groupId, OrbisNpTrophyGroupDetails* details, @@ -341,89 +341,75 @@ int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTr if (details->size != 0x4A0 || data->size != 0x28) return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT; - const auto trophyDir = + const auto trophy_dir = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles"; + auto trophy_file = trophy_dir / "trophy00" / "Xml" / "TROP.XML"; pugi::xml_document doc; - pugi::xml_parse_result result = - doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str()); + pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str()); - if (result) { + ASSERT_MSG(result, "Couldnt parse trophy XML : {}", result.description()); - uint32_t numGroups = 0; - uint32_t numTrophies = 0; - uint32_t numTrophiesByRarity[5]; - numTrophiesByRarity[1] = 0; - numTrophiesByRarity[2] = 0; - numTrophiesByRarity[3] = 0; - numTrophiesByRarity[4] = 0; - uint32_t unlockedTrophies = 0; - uint32_t unlockedTrophiesByRarity[5]; - unlockedTrophiesByRarity[1] = 0; - unlockedTrophiesByRarity[2] = 0; - unlockedTrophiesByRarity[3] = 0; - unlockedTrophiesByRarity[4] = 0; + GroupTrophyInfo group_info{}; - auto trophyconf = doc.child("trophyconf"); - for (pugi::xml_node_iterator it = trophyconf.children().begin(); - it != trophyconf.children().end(); ++it) { + auto trophyconf = doc.child("trophyconf"); + for (const pugi::xml_node& node : trophyconf.children()) { + std::string_view node_name = node.name(); - if (std::string(it->name()) == "group") { - numGroups++; - std::string currentGroupId = it->attribute("id").value(); - if (!currentGroupId.empty()) { - if (std::stoi(currentGroupId) == groupId) { - std::string currentGroupName = it->child("name").text().as_string(); - std::string currentGroupDescription = - it->child("detail").text().as_string(); + if (node_name == "group") { + int current_group_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_GROUP_ID); + if (current_group_id != ORBIS_NP_TROPHY_INVALID_GROUP_ID) { + if (current_group_id == groupId) { + std::string_view current_group_name = node.child("name").text().as_string(); + std::string_view current_group_description = + node.child("detail").text().as_string(); - strncpy(details->title, currentGroupName.c_str(), - ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE); - strncpy(details->description, currentGroupDescription.c_str(), - ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE); - } - } - } - - data->groupId = groupId; - - if (std::string(it->name()) == "trophy") { - std::string currentTrophyUnlockState = it->attribute("unlockstate").value(); - std::string currentTrophyGrade = it->attribute("ttype").value(); - std::string currentTrophyGroupID = it->attribute("gid").value(); - - if (!currentTrophyGroupID.empty()) { - if (std::stoi(currentTrophyGroupID) == groupId) { - numTrophies++; - if (!currentTrophyGrade.empty()) { - int trophyGrade = GetTrophyGradeFromChar(currentTrophyGrade.at(0)); - numTrophiesByRarity[trophyGrade]++; - if (currentTrophyUnlockState == "unlocked") { - unlockedTrophies++; - unlockedTrophiesByRarity[trophyGrade]++; - } - } - } + strncpy(details->title, current_group_name.data(), + ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE); + strncpy(details->description, current_group_description.data(), + ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE); } } } - details->numTrophies = numTrophies; - details->numPlatinum = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_PLATINUM]; - details->numGold = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_GOLD]; - details->numSilver = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_SILVER]; - details->numBronze = numTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_BRONZE]; - data->unlockedTrophies = unlockedTrophies; - data->unlockedPlatinum = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_PLATINUM]; - data->unlockedGold = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_GOLD]; - data->unlockedSilver = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_SILVER]; - data->unlockedBronze = unlockedTrophiesByRarity[ORBIS_NP_TROPHY_GRADE_BRONZE]; + details->group_id = groupId; + data->group_id = groupId; - // maybe this should be 1 instead of 100? - data->progressPercentage = 100; + if (node_name == "trophy") { + bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool(); + std::string_view current_trophy_grade = node.attribute("ttype").value(); + int current_trophy_group_id = node.attribute("gid").as_int(-1); - } else - LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description()); + if (current_trophy_grade.empty()) { + continue; + } + + if (current_trophy_group_id == groupId) { + group_info.num_trophies++; + int trophyGrade = GetTrophyGradeFromChar(current_trophy_grade.at(0)); + group_info.num_trophies_by_rarity[trophyGrade]++; + if (current_trophy_unlockstate) { + group_info.unlocked_trophies++; + group_info.unlocked_trophies_by_rarity[trophyGrade]++; + } + } + } + } + + details->num_trophies = group_info.num_trophies; + details->num_platinum = group_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_PLATINUM]; + details->num_gold = group_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_GOLD]; + details->num_silver = group_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_SILVER]; + details->num_bronze = group_info.num_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_BRONZE]; + data->unlocked_trophies = group_info.unlocked_trophies; + data->unlocked_platinum = + group_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_PLATINUM]; + data->unlocked_gold = group_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_GOLD]; + data->unlocked_silver = group_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_SILVER]; + data->unlocked_bronze = group_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_BRONZE]; + + // maybe this should be 1 instead of 100? + data->progress_percentage = 100; return ORBIS_OK; } @@ -454,87 +440,48 @@ int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpT if (details->size != 0x498 || data->size != 0x18) return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT; - const auto trophyDir = + const auto trophy_dir = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles"; + auto trophy_file = trophy_dir / "trophy00" / "Xml" / "TROP.XML"; pugi::xml_document doc; - pugi::xml_parse_result result = - doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str()); + pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str()); - if (result) { - auto trophyconf = doc.child("trophyconf"); - for (pugi::xml_node_iterator it = trophyconf.children().begin(); - it != trophyconf.children().end(); ++it) { + ASSERT_MSG(result, "Couldnt parse trophy XML : {}", result.description()); - if (std::string(it->name()) == "trophy") { - std::string currentTrophyId = it->attribute("id").value(); - if (std::stoi(currentTrophyId) == trophyId) { - std::string currentTrophyUnlockState = it->attribute("unlockstate").value(); - std::string currentTrophyTimestamp = it->attribute("timestamp").value(); - std::string currentTrophyGrade = it->attribute("ttype").value(); - std::string currentTrophyGroupID = it->attribute("gid").value(); - std::string currentTrophyHidden = it->attribute("hidden").value(); - std::string currentTrophyName = it->child("name").text().as_string(); - std::string currentTrophyDescription = it->child("detail").text().as_string(); + auto trophyconf = doc.child("trophyconf"); - if (currentTrophyUnlockState == "unlocked") { - details->trophyId = trophyId; - if (currentTrophyGrade.empty()) { - details->trophyGrade = ORBIS_NP_TROPHY_GRADE_UNKNOWN; - } else { - details->trophyGrade = GetTrophyGradeFromChar(currentTrophyGrade.at(0)); - } - if (currentTrophyGroupID.empty()) { - details->groupId = ORBIS_NP_TROPHY_BASE_GAME_GROUP_ID; - } else { - details->groupId = std::stoi(currentTrophyGroupID); - } - if (currentTrophyHidden == "yes") { - details->hidden = true; - } else { - details->hidden = false; - } + for (const pugi::xml_node& node : trophyconf.children()) { + std::string_view node_name = node.name(); - strncpy(details->name, currentTrophyName.c_str(), - ORBIS_NP_TROPHY_NAME_MAX_SIZE); - strncpy(details->description, currentTrophyDescription.c_str(), - ORBIS_NP_TROPHY_DESCR_MAX_SIZE); + if (node_name == "trophy") { + int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID); + if (current_trophy_id == trophyId) { + bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool(); + std::string_view current_trophy_grade = node.attribute("ttype").value(); + std::string_view current_trophy_name = node.child("name").text().as_string(); + std::string_view current_trophy_description = + node.child("detail").text().as_string(); - data->trophyId = trophyId; - data->unlocked = true; - data->timestamp.tick = std::stoull(currentTrophyTimestamp); - } else { - details->trophyId = trophyId; - if (currentTrophyGrade.empty()) { - details->trophyGrade = ORBIS_NP_TROPHY_GRADE_UNKNOWN; - } else { - details->trophyGrade = GetTrophyGradeFromChar(currentTrophyGrade.at(0)); - } - if (currentTrophyGroupID.empty()) { - details->groupId = ORBIS_NP_TROPHY_BASE_GAME_GROUP_ID; - } else { - details->groupId = std::stoi(currentTrophyGroupID); - } - if (currentTrophyHidden == "yes") { - details->hidden = true; - } else { - details->hidden = false; - } + uint64_t current_trophy_timestamp = node.attribute("timestamp").as_ullong(); + int current_trophy_groupid = node.attribute("gid").as_int(-1); + bool current_trophy_hidden = node.attribute("hidden").as_bool(); - strncpy(details->name, currentTrophyName.c_str(), - ORBIS_NP_TROPHY_NAME_MAX_SIZE); - strncpy(details->description, currentTrophyDescription.c_str(), - ORBIS_NP_TROPHY_DESCR_MAX_SIZE); + details->trophy_id = trophyId; + details->trophy_grade = GetTrophyGradeFromChar(current_trophy_grade.at(0)); + details->group_id = current_trophy_groupid; + details->hidden = current_trophy_hidden; - data->trophyId = trophyId; - data->unlocked = false; - data->timestamp.tick = 0; - } - } + strncpy(details->name, current_trophy_name.data(), ORBIS_NP_TROPHY_NAME_MAX_SIZE); + strncpy(details->description, current_trophy_description.data(), + ORBIS_NP_TROPHY_DESCR_MAX_SIZE); + + data->trophy_id = trophyId; + data->unlocked = current_trophy_unlockstate; + data->timestamp.tick = current_trophy_timestamp; } } - } else - LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description()); + } return ORBIS_OK; } @@ -555,35 +502,33 @@ s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context, ORBIS_NP_TROPHY_FLAG_ZERO(flags); - const auto trophyDir = + const auto trophy_dir = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles"; - auto trophy_file = trophyDir / "trophy00" / "Xml" / "TROP.XML"; + auto trophy_file = trophy_dir / "trophy00" / "Xml" / "TROP.XML"; pugi::xml_document doc; pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str()); - int numTrophies = 0; + ASSERT_MSG(result, "Couldnt parse trophy XML : {}", result.description()); - if (result) { - auto trophyconf = doc.child("trophyconf"); - for (pugi::xml_node_iterator it = trophyconf.children().begin(); - it != trophyconf.children().end(); ++it) { + int num_trophies = 0; + auto trophyconf = doc.child("trophyconf"); - std::string currentTrophyId = it->attribute("id").value(); - std::string currentTrophyUnlockState = it->attribute("unlockstate").value(); + for (const pugi::xml_node& node : trophyconf.children()) { + std::string_view node_name = node.name(); + int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID); + bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool(); - if (std::string(it->name()) == "trophy") { - numTrophies++; - } - - if (currentTrophyUnlockState == "unlocked") { - ORBIS_NP_TROPHY_FLAG_SET(std::stoi(currentTrophyId), flags); - } + if (node_name == "trophy") { + num_trophies++; } - } else - LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description()); - *count = numTrophies; + if (current_trophy_unlockstate) { + ORBIS_NP_TROPHY_FLAG_SET(current_trophy_id, flags); + } + } + + *count = num_trophies; return ORBIS_OK; } @@ -912,148 +857,116 @@ int PS4_SYSV_ABI sceNpTrophyUnlockTrophy(OrbisNpTrophyContext context, OrbisNpTr if (platinumId == nullptr) return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT; - const auto trophyDir = + const auto trophy_dir = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles"; + auto trophy_file = trophy_dir / "trophy00" / "Xml" / "TROP.XML"; pugi::xml_document doc; - pugi::xml_parse_result result = - doc.load_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str()); + pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str()); + + ASSERT_MSG(result, "Couldnt parse trophy XML : {}", result.description()); *platinumId = ORBIS_NP_TROPHY_INVALID_TROPHY_ID; - int numTrophies = 0; - int numTrophiesUnlocked = 0; + int num_trophies = 0; + int num_trophies_unlocked = 0; + pugi::xml_node platinum_node; - pugi::xml_node_iterator platinumIt; - int platinumTrophyGroup = -1; + auto trophyconf = doc.child("trophyconf"); - if (result) { - auto trophyconf = doc.child("trophyconf"); - for (pugi::xml_node_iterator it = trophyconf.children().begin(); - it != trophyconf.children().end(); ++it) { + for (pugi::xml_node& node : trophyconf.children()) { + int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID); + bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool(); + const char* current_trophy_name = node.child("name").text().as_string(); + std::string_view current_trophy_description = node.child("detail").text().as_string(); + std::string_view current_trophy_type = node.attribute("ttype").value(); - std::string currentTrophyId = it->attribute("id").value(); - std::string currentTrophyName = it->child("name").text().as_string(); - std::string currentTrophyDescription = it->child("detail").text().as_string(); - std::string currentTrophyType = it->attribute("ttype").value(); - std::string currentTrophyUnlockState = it->attribute("unlockstate").value(); + if (current_trophy_type == "P") { + platinum_node = node; + if (trophyId == current_trophy_id) { + return ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK; + } + } - if (currentTrophyType == "P") { - platinumIt = it; - - if (std::string(platinumIt->attribute("gid").value()).empty()) { - platinumTrophyGroup = -1; - } else { - platinumTrophyGroup = - std::stoi(std::string(platinumIt->attribute("gid").value())); - } - - if (trophyId == std::stoi(currentTrophyId)) { - return ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK; + if (std::string_view(node.name()) == "trophy") { + if (node.attribute("pid").as_int(-1) != ORBIS_NP_TROPHY_INVALID_TROPHY_ID) { + num_trophies++; + if (current_trophy_unlockstate) { + num_trophies_unlocked++; } } - if (std::string(it->name()) == "trophy") { - if (platinumTrophyGroup == -1) { - if (std::string(it->attribute("gid").value()).empty()) { - numTrophies++; - if (currentTrophyUnlockState == "unlocked") { - numTrophiesUnlocked++; - } - } + if (current_trophy_id == trophyId) { + if (current_trophy_unlockstate) { + LOG_INFO(Lib_NpTrophy, "Trophy already unlocked"); + return ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED; } else { - if (!std::string(it->attribute("gid").value()).empty()) { - if (std::stoi(std::string(it->attribute("gid").value())) == - platinumTrophyGroup) { - numTrophies++; - if (currentTrophyUnlockState == "unlocked") { - numTrophiesUnlocked++; - } - } - } - } - - if (std::stoi(currentTrophyId) == trophyId) { - LOG_INFO(Lib_NpTrophy, "Found trophy to unlock {} : {}", - it->child("name").text().as_string(), - it->child("detail").text().as_string()); - if (currentTrophyUnlockState == "unlocked") { - LOG_INFO(Lib_NpTrophy, "Trophy already unlocked"); - return ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED; + if (node.attribute("unlockstate").empty()) { + node.append_attribute("unlockstate") = "true"; } else { - if (std::string(it->attribute("unlockstate").value()).empty()) { - it->append_attribute("unlockstate") = "unlocked"; - } else { - it->attribute("unlockstate").set_value("unlocked"); - } - - Rtc::OrbisRtcTick trophyTimestamp; - Rtc::sceRtcGetCurrentTick(&trophyTimestamp); - - if (std::string(it->attribute("timestamp").value()).empty()) { - it->append_attribute("timestamp") = - std::to_string(trophyTimestamp.tick).c_str(); - } else { - it->attribute("timestamp") - .set_value(std::to_string(trophyTimestamp.tick).c_str()); - } - - g_trophy_ui.AddTrophyToQueue(trophyId, currentTrophyName); + node.attribute("unlockstate").set_value("true"); } + + Rtc::OrbisRtcTick trophyTimestamp; + Rtc::sceRtcGetCurrentTick(&trophyTimestamp); + + if (node.attribute("timestamp").empty()) { + node.append_attribute("timestamp") = + std::to_string(trophyTimestamp.tick).c_str(); + } else { + node.attribute("timestamp") + .set_value(std::to_string(trophyTimestamp.tick).c_str()); + } + + std::string trophy_icon_file = "TROP"; + trophy_icon_file.append(node.attribute("id").value()); + trophy_icon_file.append(".PNG"); + + std::filesystem::path current_icon_path = + trophy_dir / "trophy00" / "Icons" / trophy_icon_file; + + AddTrophyToQueue(current_icon_path, current_trophy_name); } } } + } - if (std::string(platinumIt->attribute("unlockstate").value()).empty()) { - if ((numTrophies - 2) == numTrophiesUnlocked) { - - platinumIt->append_attribute("unlockstate") = "unlocked"; - - Rtc::OrbisRtcTick trophyTimestamp; - Rtc::sceRtcGetCurrentTick(&trophyTimestamp); - - if (std::string(platinumIt->attribute("timestamp").value()).empty()) { - platinumIt->append_attribute("timestamp") = - std::to_string(trophyTimestamp.tick).c_str(); - } else { - platinumIt->attribute("timestamp") - .set_value(std::to_string(trophyTimestamp.tick).c_str()); - } - - std::string platinumTrophyId = platinumIt->attribute("id").value(); - std::string platinumTrophyName = platinumIt->child("name").text().as_string(); - - *platinumId = std::stoi(platinumTrophyId); - g_trophy_ui.AddTrophyToQueue(*platinumId, platinumTrophyName); + if (!platinum_node.attribute("unlockstate").as_bool()) { + if ((num_trophies - 1) == num_trophies_unlocked) { + if (platinum_node.attribute("unlockstate").empty()) { + platinum_node.append_attribute("unlockstate") = "true"; + } else { + platinum_node.attribute("unlockstate").set_value("true"); } - } else if (std::string(platinumIt->attribute("unlockstate").value()) == "locked") { - if ((numTrophies - 2) == numTrophiesUnlocked) { - platinumIt->attribute("unlockstate").set_value("unlocked"); + Rtc::OrbisRtcTick trophyTimestamp; + Rtc::sceRtcGetCurrentTick(&trophyTimestamp); - Rtc::OrbisRtcTick trophyTimestamp; - Rtc::sceRtcGetCurrentTick(&trophyTimestamp); - - if (std::string(platinumIt->attribute("timestamp").value()).empty()) { - platinumIt->append_attribute("timestamp") = - std::to_string(trophyTimestamp.tick).c_str(); - } else { - platinumIt->attribute("timestamp") - .set_value(std::to_string(trophyTimestamp.tick).c_str()); - } - - std::string platinumTrophyId = platinumIt->attribute("id").value(); - std::string platinumTrophyName = platinumIt->child("name").text().as_string(); - - *platinumId = std::stoi(platinumTrophyId); - g_trophy_ui.AddTrophyToQueue(*platinumId, platinumTrophyName); + if (platinum_node.attribute("timestamp").empty()) { + platinum_node.append_attribute("timestamp") = + std::to_string(trophyTimestamp.tick).c_str(); + } else { + platinum_node.attribute("timestamp") + .set_value(std::to_string(trophyTimestamp.tick).c_str()); } + + int platinum_trophy_id = + platinum_node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID); + const char* platinum_trophy_name = platinum_node.child("name").text().as_string(); + + std::string platinum_icon_file = "TROP"; + platinum_icon_file.append(platinum_node.attribute("id").value()); + platinum_icon_file.append(".PNG"); + + std::filesystem::path platinum_icon_path = + trophy_dir / "trophy00" / "Icons" / platinum_icon_file; + + *platinumId = platinum_trophy_id; + AddTrophyToQueue(platinum_icon_path, platinum_trophy_name); } + } - doc.save_file((trophyDir.string() + "/trophy00/Xml/TROP.XML").c_str()); - - } else - LOG_INFO(Lib_NpTrophy, "couldnt parse xml : {}", result.description()); + doc.save_file((trophy_dir.string() + "/trophy00/Xml/TROP.XML").c_str()); return ORBIS_OK; } diff --git a/src/core/libraries/np_trophy/np_trophy.h b/src/core/libraries/np_trophy/np_trophy.h index ae08b2969..ac13a9ab7 100644 --- a/src/core/libraries/np_trophy/np_trophy.h +++ b/src/core/libraries/np_trophy/np_trophy.h @@ -47,7 +47,7 @@ bool ORBIS_NP_TROPHY_FLAG_ISSET(int32_t trophyId, OrbisNpTrophyFlagArray* p); struct OrbisNpTrophyData { size_t size; - OrbisNpTrophyId trophyId; + OrbisNpTrophyId trophy_id; bool unlocked; uint8_t reserved[3]; Rtc::OrbisRtcTick timestamp; @@ -66,9 +66,9 @@ constexpr int ORBIS_NP_TROPHY_INVALID_GROUP_ID = -2; struct OrbisNpTrophyDetails { size_t size; - OrbisNpTrophyId trophyId; - OrbisNpTrophyGrade trophyGrade; - OrbisNpTrophyGroupId groupId; + OrbisNpTrophyId trophy_id; + OrbisNpTrophyGrade trophy_grade; + OrbisNpTrophyGroupId group_id; bool hidden; uint8_t reserved[3]; char name[ORBIS_NP_TROPHY_NAME_MAX_SIZE]; @@ -77,46 +77,46 @@ struct OrbisNpTrophyDetails { struct OrbisNpTrophyGameData { size_t size; - uint32_t unlockedTrophies; - uint32_t unlockedPlatinum; - uint32_t unlockedGold; - uint32_t unlockedSilver; - uint32_t unlockedBronze; - uint32_t progressPercentage; + uint32_t unlocked_trophies; + uint32_t unlocked_platinum; + uint32_t unlocked_gold; + uint32_t unlocked_silver; + uint32_t unlocked_bronze; + uint32_t progress_percentage; }; struct OrbisNpTrophyGameDetails { size_t size; - uint32_t numGroups; - uint32_t numTrophies; - uint32_t numPlatinum; - uint32_t numGold; - uint32_t numSilver; - uint32_t numBronze; + uint32_t num_groups; + uint32_t num_trophies; + uint32_t num_platinum; + uint32_t num_gold; + uint32_t num_silver; + uint32_t num_bronze; char title[ORBIS_NP_TROPHY_GAME_TITLE_MAX_SIZE]; char description[ORBIS_NP_TROPHY_GAME_DESCR_MAX_SIZE]; }; struct OrbisNpTrophyGroupData { size_t size; - OrbisNpTrophyGroupId groupId; - uint32_t unlockedTrophies; - uint32_t unlockedPlatinum; - uint32_t unlockedGold; - uint32_t unlockedSilver; - uint32_t unlockedBronze; - uint32_t progressPercentage; + OrbisNpTrophyGroupId group_id; + uint32_t unlocked_trophies; + uint32_t unlocked_platinum; + uint32_t unlocked_gold; + uint32_t unlocked_silver; + uint32_t unlocked_bronze; + uint32_t progress_percentage; uint8_t reserved[4]; }; struct OrbisNpTrophyGroupDetails { size_t size; - OrbisNpTrophyGroupId groupId; - uint32_t numTrophies; - uint32_t numPlatinum; - uint32_t numGold; - uint32_t numSilver; - uint32_t numBronze; + OrbisNpTrophyGroupId group_id; + uint32_t num_trophies; + uint32_t num_platinum; + uint32_t num_gold; + uint32_t num_silver; + uint32_t num_bronze; char title[ORBIS_NP_TROPHY_GROUP_TITLE_MAX_SIZE]; char description[ORBIS_NP_TROPHY_GROUP_DESCR_MAX_SIZE]; }; diff --git a/src/core/libraries/np_trophy/trophy_ui.cpp b/src/core/libraries/np_trophy/trophy_ui.cpp index 8deaac25b..740bd3a10 100644 --- a/src/core/libraries/np_trophy/trophy_ui.cpp +++ b/src/core/libraries/np_trophy/trophy_ui.cpp @@ -2,15 +2,27 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include "common/assert.h" +#include "common/singleton.h" #include "imgui/imgui_std.h" #include "trophy_ui.h" using namespace ImGui; -using namespace Libraries::NpTrophy; +namespace Libraries::NpTrophy { -TrophyUI::TrophyUI() { +std::optional current_trophy_ui; +std::queue trophy_queue; +std::mutex queueMtx; + +TrophyUI::TrophyUI(std::filesystem::path trophyIconPath, std::string trophyName) + : trophy_name(trophyName) { + if (std::filesystem::exists(trophyIconPath)) { + trophy_icon = RefCountedTexture::DecodePngFile(trophyIconPath); + } else { + LOG_ERROR(Lib_NpTrophy, "Couldnt load trophy icon at {}", trophyIconPath.string()); + } AddLayer(this); } @@ -18,57 +30,65 @@ TrophyUI::~TrophyUI() { Finish(); } -void Libraries::NpTrophy::TrophyUI::AddTrophyToQueue(int trophyId, std::string trophyName) { - TrophyInfo newInfo; - newInfo.trophyId = trophyId; - newInfo.trophyName = trophyName; - trophyQueue.push_back(newInfo); -} - void TrophyUI::Finish() { RemoveLayer(this); } -bool displayingTrophy; -std::chrono::steady_clock::time_point trophyStartedTime; - void TrophyUI::Draw() { const auto& io = GetIO(); const ImVec2 window_size{ - std::min(io.DisplaySize.x, 200.f), - std::min(io.DisplaySize.y, 75.f), + std::min(io.DisplaySize.x, 250.f), + std::min(io.DisplaySize.y, 70.f), }; - if (trophyQueue.size() != 0) { - if (!displayingTrophy) { - displayingTrophy = true; - trophyStartedTime = std::chrono::steady_clock::now(); + SetNextWindowSize(window_size); + SetNextWindowCollapsed(false); + SetNextWindowPos(ImVec2(io.DisplaySize.x - 250, 50)); + KeepNavHighlight(); + + if (Begin("Trophy Window", nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoInputs)) { + if (trophy_icon) { + Image(trophy_icon.GetTexture().im_id, ImVec2(50, 50)); + ImGui::SameLine(); + } else { + // placeholder + const auto pos = GetCursorScreenPos(); + ImGui::GetWindowDrawList()->AddRectFilled(pos, pos + ImVec2{50.0f}, + GetColorU32(ImVec4{0.7f})); + ImGui::Indent(60); } + TextWrapped("Trophy earned!\n%s", trophy_name.c_str()); + } + End(); - std::chrono::steady_clock::time_point timeNow = std::chrono::steady_clock::now(); - std::chrono::seconds duration = - std::chrono::duration_cast(timeNow - trophyStartedTime); - - if (duration.count() >= 5) { - trophyQueue.erase(trophyQueue.begin()); - displayingTrophy = false; - } - - if (trophyQueue.size() != 0) { - SetNextWindowSize(window_size); - SetNextWindowCollapsed(false); - SetNextWindowPos(ImVec2(io.DisplaySize.x - 200, 50)); - KeepNavHighlight(); - - TrophyInfo currentTrophyInfo = trophyQueue[0]; - if (Begin("Trophy Window", nullptr, - ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | - ImGuiWindowFlags_NoInputs)) { - Text("Trophy earned!"); - TextWrapped("%s", currentTrophyInfo.trophyName.c_str()); - } - End(); + trophy_timer -= io.DeltaTime; + if (trophy_timer <= 0) { + queueMtx.lock(); + if (!trophy_queue.empty()) { + TrophyInfo next_trophy = trophy_queue.front(); + trophy_queue.pop(); + current_trophy_ui.emplace(next_trophy.trophy_icon_path, next_trophy.trophy_name); + } else { + current_trophy_ui.reset(); } + queueMtx.unlock(); } } + +void AddTrophyToQueue(std::filesystem::path trophyIconPath, std::string trophyName) { + queueMtx.lock(); + if (current_trophy_ui.has_value()) { + TrophyInfo new_trophy; + new_trophy.trophy_icon_path = trophyIconPath; + new_trophy.trophy_name = trophyName; + trophy_queue.push(new_trophy); + } else { + current_trophy_ui.emplace(trophyIconPath, trophyName); + } + queueMtx.unlock(); +} + +} // namespace Libraries::NpTrophy \ No newline at end of file diff --git a/src/core/libraries/np_trophy/trophy_ui.h b/src/core/libraries/np_trophy/trophy_ui.h index 060d80dec..4448c2281 100644 --- a/src/core/libraries/np_trophy/trophy_ui.h +++ b/src/core/libraries/np_trophy/trophy_ui.h @@ -5,32 +5,36 @@ #include #include -#include +#include #include "common/fixed_value.h" #include "common/types.h" #include "core/libraries/np_trophy/np_trophy.h" #include "imgui/imgui_layer.h" +#include "imgui/imgui_texture.h" namespace Libraries::NpTrophy { -struct TrophyInfo { - int trophyId = -1; - std::string trophyName; -}; - class TrophyUI final : public ImGui::Layer { - std::vector trophyQueue; - public: - TrophyUI(); + TrophyUI(std::filesystem::path trophyIconPath, std::string trophyName); ~TrophyUI() override; - void AddTrophyToQueue(int trophyId, std::string trophyName); - void Finish(); void Draw() override; + +private: + std::string trophy_name; + float trophy_timer = 5.0f; + ImGui::RefCountedTexture trophy_icon; }; +struct TrophyInfo { + std::filesystem::path trophy_icon_path; + std::string trophy_name; +}; + +void AddTrophyToQueue(std::filesystem::path trophyIconPath, std::string trophyName); + }; // namespace Libraries::NpTrophy \ No newline at end of file diff --git a/src/qt_gui/trophy_viewer.cpp b/src/qt_gui/trophy_viewer.cpp index e4394c7e6..a14da1189 100644 --- a/src/qt_gui/trophy_viewer.cpp +++ b/src/qt_gui/trophy_viewer.cpp @@ -76,7 +76,7 @@ void TrophyViewer::PopulateTrophyWidget(QString title) { trpType.append(reader.attributes().value("ttype").toString()); trpPid.append(reader.attributes().value("pid").toString()); if (reader.attributes().hasAttribute("unlockstate")) { - if (reader.attributes().value("unlockstate").toString() == "unlocked") { + if (reader.attributes().value("unlockstate").toString() == "true") { trpUnlocked.append("unlocked"); } else { trpUnlocked.append("locked"); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index 8aa292b1c..891b26084 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -233,6 +233,7 @@ void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) { ctx.AddExecutionMode(main, spv::ExecutionMode::OriginUpperLeft); } if (info.has_discard) { + ctx.AddExtension("SPV_EXT_demote_to_helper_invocation"); ctx.AddCapability(spv::Capability::DemoteToHelperInvocationEXT); } if (info.stores.Get(IR::Attribute::Depth)) { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp index 92279c5fb..b6bf1eed6 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp @@ -49,12 +49,13 @@ Id OutputAttrPointer(EmitContext& ctx, IR::Attribute attr, u32 element) { if (info.num_components == 1) { return info.id; } else { - return ctx.OpAccessChain(ctx.output_f32, info.id, ctx.ConstU32(element)); + return ctx.OpAccessChain(info.pointer_type, info.id, ctx.ConstU32(element)); } } switch (attr) { case IR::Attribute::Position0: { return ctx.OpAccessChain(ctx.output_f32, ctx.output_position, ctx.ConstU32(element)); + } case IR::Attribute::Position1: case IR::Attribute::Position2: case IR::Attribute::Position3: { @@ -70,17 +71,47 @@ Id OutputAttrPointer(EmitContext& ctx, IR::Attribute attr, u32 element) { case IR::Attribute::RenderTarget6: case IR::Attribute::RenderTarget7: { const u32 index = u32(attr) - u32(IR::Attribute::RenderTarget0); - if (ctx.frag_num_comp[index] > 1) { - return ctx.OpAccessChain(ctx.output_f32, ctx.frag_color[index], ctx.ConstU32(element)); + const auto& info{ctx.frag_outputs.at(index)}; + if (info.num_components > 1) { + return ctx.OpAccessChain(info.pointer_type, info.id, ctx.ConstU32(element)); } else { - return ctx.frag_color[index]; + return info.id; } } case IR::Attribute::Depth: return ctx.frag_depth; default: - throw NotImplementedException("Read attribute {}", attr); + throw NotImplementedException("Write attribute {}", attr); } +} + +std::pair OutputAttrComponentType(EmitContext& ctx, IR::Attribute attr) { + if (IR::IsParam(attr)) { + const u32 index{u32(attr) - u32(IR::Attribute::Param0)}; + const auto& info{ctx.output_params.at(index)}; + return {info.component_type, info.is_integer}; + } + switch (attr) { + case IR::Attribute::Position0: + case IR::Attribute::Position1: + case IR::Attribute::Position2: + case IR::Attribute::Position3: + case IR::Attribute::Depth: + return {ctx.F32[1], false}; + case IR::Attribute::RenderTarget0: + case IR::Attribute::RenderTarget1: + case IR::Attribute::RenderTarget2: + case IR::Attribute::RenderTarget3: + case IR::Attribute::RenderTarget4: + case IR::Attribute::RenderTarget5: + case IR::Attribute::RenderTarget6: + case IR::Attribute::RenderTarget7: { + const u32 index = u32(attr) - u32(IR::Attribute::RenderTarget0); + const auto& info{ctx.frag_outputs.at(index)}; + return {info.component_type, info.is_integer}; + } + default: + throw NotImplementedException("Write attribute {}", attr); } } } // Anonymous namespace @@ -156,17 +187,21 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, u32 comp) { // Attribute is disabled or varying component is not written return ctx.ConstF32(comp == 3 ? 1.0f : 0.0f); } - if (param.is_default) { - return ctx.OpCompositeExtract(param.component_type, param.id, comp); - } - if (param.num_components > 1) { + Id result; + if (param.is_default) { + result = ctx.OpCompositeExtract(param.component_type, param.id, comp); + } else if (param.num_components > 1) { const Id pointer{ ctx.OpAccessChain(param.pointer_type, param.id, ctx.ConstU32(comp))}; - return ctx.OpLoad(param.component_type, pointer); + result = ctx.OpLoad(param.component_type, pointer); } else { - return ctx.OpLoad(param.component_type, param.id); + result = ctx.OpLoad(param.component_type, param.id); } + if (param.is_integer) { + result = ctx.OpBitcast(ctx.F32[1], result); + } + return result; } else { const auto step_rate = EmitReadStepRate(ctx, param.id.value); const auto offset = ctx.OpIAdd( @@ -222,7 +257,12 @@ void EmitSetAttribute(EmitContext& ctx, IR::Attribute attr, Id value, u32 elemen return; } const Id pointer{OutputAttrPointer(ctx, attr, element)}; - ctx.OpStore(pointer, ctx.OpBitcast(ctx.F32[1], value)); + const auto component_type{OutputAttrComponentType(ctx, attr)}; + if (component_type.second) { + ctx.OpStore(pointer, ctx.OpBitcast(component_type.first, value)); + } else { + ctx.OpStore(pointer, value); + } } template diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index b66f09664..05b308b05 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -120,6 +120,7 @@ void EmitContext::DefineArithmeticTypes() { output_f32 = Name(TypePointer(spv::StorageClass::Output, F32[1]), "output_f32"); output_u32 = Name(TypePointer(spv::StorageClass::Output, U32[1]), "output_u32"); + output_s32 = Name(TypePointer(spv::StorageClass::Output, S32[1]), "output_s32"); full_result_i32x2 = Name(TypeStruct(S32[1], S32[1]), "full_result_i32x2"); full_result_u32x2 = Name(TypeStruct(U32[1], U32[1]), "full_result_u32x2"); @@ -151,21 +152,21 @@ const VectorIds& GetAttributeType(EmitContext& ctx, AmdGpu::NumberFormat fmt) { UNREACHABLE_MSG("Invalid attribute type {}", fmt); } -EmitContext::SpirvAttribute EmitContext::GetAttributeInfo(AmdGpu::NumberFormat fmt, Id id) { +EmitContext::SpirvAttribute EmitContext::GetAttributeInfo(AmdGpu::NumberFormat fmt, Id id, + bool output) { switch (fmt) { case AmdGpu::NumberFormat::Float: case AmdGpu::NumberFormat::Unorm: case AmdGpu::NumberFormat::Snorm: case AmdGpu::NumberFormat::SnormNz: - return {id, input_f32, F32[1], 4}; - case AmdGpu::NumberFormat::Uint: - return {id, input_u32, U32[1], 4}; - case AmdGpu::NumberFormat::Sint: - return {id, input_s32, S32[1], 4}; case AmdGpu::NumberFormat::Sscaled: - return {id, input_f32, F32[1], 4}; case AmdGpu::NumberFormat::Uscaled: - return {id, input_f32, F32[1], 4}; + case AmdGpu::NumberFormat::Srgb: + return {id, output ? output_f32 : input_f32, F32[1], 4, false}; + case AmdGpu::NumberFormat::Uint: + return {id, output ? output_u32 : input_u32, U32[1], 4, true}; + case AmdGpu::NumberFormat::Sint: + return {id, output ? output_s32 : input_s32, S32[1], 4, true}; default: break; } @@ -236,9 +237,13 @@ void EmitContext::DefineInputs() { : 1; // Note that we pass index rather than Id input_params[input.binding] = { - rate_idx, input_u32, - U32[1], input.num_components, - false, input.instance_data_buf, + rate_idx, + input_u32, + U32[1], + input.num_components, + true, + false, + input.instance_data_buf, }; } else { Id id{DefineInput(type, input.binding)}; @@ -247,7 +252,7 @@ void EmitContext::DefineInputs() { } else { Name(id, fmt::format("vs_in_attr{}", input.binding)); } - input_params[input.binding] = GetAttributeInfo(input.fmt, id); + input_params[input.binding] = GetAttributeInfo(input.fmt, id, false); interfaces.push_back(id); } } @@ -320,10 +325,12 @@ void EmitContext::DefineOutputs() { continue; } const u32 num_components = info.stores.NumComponents(mrt); - frag_color[i] = DefineOutput(F32[num_components], i); - frag_num_comp[i] = num_components; - Name(frag_color[i], fmt::format("frag_color{}", i)); - interfaces.push_back(frag_color[i]); + const AmdGpu::NumberFormat num_format{runtime_info.fs_info.color_buffers[i].num_format}; + const Id type{GetAttributeType(*this, num_format)[num_components]}; + const Id id = DefineOutput(type, i); + Name(id, fmt::format("frag_color{}", i)); + frag_outputs[i] = GetAttributeInfo(num_format, id, true); + interfaces.push_back(id); } break; default: diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index fb7b29b3e..3dfc8bd61 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -166,6 +166,7 @@ public: Id input_s32{}; Id output_u32{}; Id output_f32{}; + Id output_s32{}; boost::container::small_vector interfaces; @@ -177,8 +178,6 @@ public: Id frag_coord{}; Id front_facing{}; Id frag_depth{}; - std::array frag_color{}; - std::array frag_num_comp{}; Id clip_distances{}; Id cull_distances{}; @@ -237,11 +236,13 @@ public: Id pointer_type; Id component_type; u32 num_components; + bool is_integer{}; bool is_default{}; s32 buffer_handle{-1}; }; std::array input_params{}; std::array output_params{}; + std::array frag_outputs{}; private: void DefineArithmeticTypes(); @@ -254,7 +255,7 @@ private: void DefineImagesAndSamplers(); void DefineSharedMemory(); - SpirvAttribute GetAttributeInfo(AmdGpu::NumberFormat fmt, Id id); + SpirvAttribute GetAttributeInfo(AmdGpu::NumberFormat fmt, Id id, bool output); }; } // namespace Shader::Backend::SPIRV diff --git a/src/shader_recompiler/frontend/translate/export.cpp b/src/shader_recompiler/frontend/translate/export.cpp index 7d901822d..f82f8fc1b 100644 --- a/src/shader_recompiler/frontend/translate/export.cpp +++ b/src/shader_recompiler/frontend/translate/export.cpp @@ -25,7 +25,7 @@ void Translator::EmitExport(const GcnInst& inst) { return comp; } const u32 index = u32(attrib) - u32(IR::Attribute::RenderTarget0); - switch (runtime_info.fs_info.mrt_swizzles[index]) { + switch (runtime_info.fs_info.color_buffers[index].mrt_swizzle) { case MrtSwizzle::Identity: return comp; case MrtSwizzle::Alt: diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h index 1bb065544..115bbe10d 100644 --- a/src/shader_recompiler/runtime_info.h +++ b/src/shader_recompiler/runtime_info.h @@ -80,10 +80,16 @@ struct FragmentRuntimeInfo { auto operator<=>(const PsInput&) const noexcept = default; }; boost::container::static_vector inputs; - std::array mrt_swizzles; + struct PsColorBuffer { + AmdGpu::NumberFormat num_format; + MrtSwizzle mrt_swizzle; + + auto operator<=>(const PsColorBuffer&) const noexcept = default; + }; + std::array color_buffers; bool operator==(const FragmentRuntimeInfo& other) const noexcept { - return std::ranges::equal(mrt_swizzles, other.mrt_swizzles) && + return std::ranges::equal(color_buffers, other.color_buffers) && std::ranges::equal(inputs, other.inputs); } }; diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h index 411b25ed1..b1036c019 100644 --- a/src/video_core/amdgpu/liverpool.h +++ b/src/video_core/amdgpu/liverpool.h @@ -253,6 +253,13 @@ struct Liverpool { } }; + struct ModeControl { + s32 msaa_enable : 1; + s32 vport_scissor_enable : 1; + s32 line_stripple_enable : 1; + s32 send_unlit_stiles_to_pkr : 1; + }; + enum class ZOrder : u32 { LateZ = 0, EarlyZLateZ = 1, @@ -559,29 +566,39 @@ struct Liverpool { s16 top_left_x; s16 top_left_y; }; - union { - BitField<0, 15, u32> bottom_right_x; - BitField<16, 15, u32> bottom_right_y; + struct { + s16 bottom_right_x; + s16 bottom_right_y; }; + // From AMD spec: 'Negative numbers clamped to 0' + static s16 Clamp(s16 value) { + return std::max(s16(0), value); + } + u32 GetWidth() const { - return static_cast(bottom_right_x - top_left_x); + return static_cast(Clamp(bottom_right_x) - Clamp(top_left_x)); } u32 GetHeight() const { - return static_cast(bottom_right_y - top_left_y); + return static_cast(Clamp(bottom_right_y) - Clamp(top_left_y)); } }; + struct WindowOffset { + s32 window_x_offset : 16; + s32 window_y_offset : 16; + }; + struct ViewportScissor { union { BitField<0, 15, s32> top_left_x; - BitField<15, 15, s32> top_left_y; - BitField<30, 1, s32> window_offset_disable; + BitField<16, 15, s32> top_left_y; + BitField<31, 1, s32> window_offset_disable; }; - union { - BitField<0, 15, s32> bottom_right_x; - BitField<15, 15, s32> bottom_right_y; + struct { + s16 bottom_right_x; + s16 bottom_right_y; }; u32 GetWidth() const { @@ -953,10 +970,14 @@ struct Liverpool { Scissor screen_scissor; INSERT_PADDING_WORDS(0xA010 - 0xA00C - 2); DepthBuffer depth_buffer; - INSERT_PADDING_WORDS(0xA08E - 0xA018); + INSERT_PADDING_WORDS(0xA080 - 0xA018); + WindowOffset window_offset; + ViewportScissor window_scissor; + INSERT_PADDING_WORDS(0xA08E - 0xA081 - 2); ColorBufferMask color_target_mask; ColorBufferMask color_shader_mask; - INSERT_PADDING_WORDS(0xA094 - 0xA08E - 2); + ViewportScissor generic_scissor; + INSERT_PADDING_WORDS(2); std::array viewport_scissors; std::array viewport_depths; INSERT_PADDING_WORDS(0xA103 - 0xA0D4); @@ -994,7 +1015,9 @@ struct Liverpool { PolygonControl polygon_control; ViewportControl viewport_control; VsOutputControl vs_output_control; - INSERT_PADDING_WORDS(0xA29E - 0xA207 - 2); + INSERT_PADDING_WORDS(0xA292 - 0xA207 - 1); + ModeControl mode_control; + INSERT_PADDING_WORDS(0xA29D - 0xA292 - 1); u32 index_size; u32 max_index_size; IndexBufferType index_buffer_type; @@ -1206,8 +1229,11 @@ static_assert(GFX6_3D_REG_INDEX(depth_htile_data_base) == 0xA005); static_assert(GFX6_3D_REG_INDEX(screen_scissor) == 0xA00C); static_assert(GFX6_3D_REG_INDEX(depth_buffer.z_info) == 0xA010); static_assert(GFX6_3D_REG_INDEX(depth_buffer.depth_slice) == 0xA017); +static_assert(GFX6_3D_REG_INDEX(window_offset) == 0xA080); +static_assert(GFX6_3D_REG_INDEX(window_scissor) == 0xA081); static_assert(GFX6_3D_REG_INDEX(color_target_mask) == 0xA08E); static_assert(GFX6_3D_REG_INDEX(color_shader_mask) == 0xA08F); +static_assert(GFX6_3D_REG_INDEX(generic_scissor) == 0xA090); static_assert(GFX6_3D_REG_INDEX(viewport_scissors) == 0xA094); static_assert(GFX6_3D_REG_INDEX(primitive_restart_index) == 0xA103); static_assert(GFX6_3D_REG_INDEX(stencil_control) == 0xA10B); @@ -1227,6 +1253,7 @@ static_assert(GFX6_3D_REG_INDEX(color_control) == 0xA202); static_assert(GFX6_3D_REG_INDEX(clipper_control) == 0xA204); static_assert(GFX6_3D_REG_INDEX(viewport_control) == 0xA206); static_assert(GFX6_3D_REG_INDEX(vs_output_control) == 0xA207); +static_assert(GFX6_3D_REG_INDEX(mode_control) == 0xA292); static_assert(GFX6_3D_REG_INDEX(index_size) == 0xA29D); static_assert(GFX6_3D_REG_INDEX(index_buffer_type) == 0xA29F); static_assert(GFX6_3D_REG_INDEX(enable_primitive_id) == 0xA2A1); diff --git a/src/video_core/buffer_cache/buffer.cpp b/src/video_core/buffer_cache/buffer.cpp index f8afd6991..5a049c185 100644 --- a/src/video_core/buffer_cache/buffer.cpp +++ b/src/video_core/buffer_cache/buffer.cpp @@ -95,7 +95,8 @@ Buffer::Buffer(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, // Create buffer object. const vk::BufferCreateInfo buffer_ci = { .size = size_bytes, - .usage = flags, + // When maintenance5 is not supported, use all flags since we can't add flags to views. + .usage = instance->IsMaintenance5Supported() ? flags : AllFlags, }; VmaAllocationInfo alloc_info{}; buffer.Create(buffer_ci, usage, &alloc_info); @@ -119,7 +120,7 @@ vk::BufferView Buffer::View(u32 offset, u32 size, bool is_written, AmdGpu::DataF : vk::BufferUsageFlagBits2KHR::eUniformTexelBuffer, }; const vk::BufferViewCreateInfo view_ci = { - .pNext = &usage_flags, + .pNext = instance->IsMaintenance5Supported() ? &usage_flags : nullptr, .buffer = buffer.buffer, .format = Vulkan::LiverpoolToVK::SurfaceFormat(dfmt, nfmt), .offset = offset, diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index ce79d4231..c10cac6cb 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -88,11 +88,17 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul "Rectangle List primitive type is only supported for embedded VS"); } + auto prim_restart = key.enable_primitive_restart != 0; + if (prim_restart && IsPrimitiveListTopology() && !instance.IsListRestartSupported()) { + LOG_WARNING(Render_Vulkan, + "Primitive restart is enabled for list topology but not supported by driver."); + prim_restart = false; + } const vk::PipelineInputAssemblyStateCreateInfo input_assembly = { .topology = LiverpoolToVK::PrimitiveType(key.prim_type), - .primitiveRestartEnable = key.enable_primitive_restart != 0, + .primitiveRestartEnable = prim_restart, }; - ASSERT_MSG(!key.enable_primitive_restart || key.primitive_restart_index == 0xFFFF || + ASSERT_MSG(!prim_restart || key.primitive_restart_index == 0xFFFF || key.primitive_restart_index == 0xFFFFFFFF, "Primitive restart index other than -1 is not supported yet"); @@ -387,7 +393,7 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, for (const auto& buffer : stage->buffers) { const auto vsharp = buffer.GetSharp(*stage); const bool is_storage = buffer.IsStorage(vsharp); - if (vsharp) { + if (vsharp && vsharp.GetSize() > 0) { const VAddr address = vsharp.base_address; if (texture_cache.IsMeta(address)) { LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a PS shader (buffer)"); diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index dda9183a5..ba4996742 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -26,6 +26,7 @@ using Liverpool = AmdGpu::Liverpool; struct GraphicsPipelineKey { std::array stage_hashes; std::array color_formats; + std::array color_num_formats; std::array mrt_swizzles; vk::Format depth_format; vk::Format stencil_format; @@ -84,6 +85,16 @@ public: return key.depth_stencil.depth_enable.Value(); } + [[nodiscard]] bool IsPrimitiveListTopology() const { + return key.prim_type == Liverpool::PrimitiveType::PointList || + key.prim_type == Liverpool::PrimitiveType::LineList || + key.prim_type == Liverpool::PrimitiveType::TriangleList || + key.prim_type == Liverpool::PrimitiveType::AdjLineList || + key.prim_type == Liverpool::PrimitiveType::AdjTriangleList || + key.prim_type == Liverpool::PrimitiveType::RectList || + key.prim_type == Liverpool::PrimitiveType::QuadList; + } + private: void BuildDescSetLayout(); diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 2aeb77ec8..022ef16c5 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -260,9 +260,8 @@ bool Instance::CreateDevice() { color_write_en &= add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME); const bool calibrated_timestamps = add_extension(VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME); const bool robustness = add_extension(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME); - const bool topology_restart = - add_extension(VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME); - const bool maintenance5 = add_extension(VK_KHR_MAINTENANCE_5_EXTENSION_NAME); + list_restart = add_extension(VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME); + maintenance5 = add_extension(VK_KHR_MAINTENANCE_5_EXTENSION_NAME); // These extensions are promoted by Vulkan 1.3, but for greater compatibility we use Vulkan 1.2 // with extensions. @@ -415,7 +414,7 @@ bool Instance::CreateDevice() { if (!workgroup_memory_explicit_layout) { device_chain.unlink(); } - if (!topology_restart) { + if (!list_restart) { device_chain.unlink(); } if (robustness) { diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index e6e39ab1f..e019ffba3 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -138,6 +138,15 @@ public: return null_descriptor; } + /// Returns true when VK_KHR_maintenance5 is supported. + bool IsMaintenance5Supported() const { + return maintenance5; + } + + bool IsListRestartSupported() const { + return list_restart; + } + /// Returns the vendor ID of the physical device u32 GetVendorID() const { return properties.vendorID; @@ -280,6 +289,8 @@ private: bool color_write_en{}; bool vertex_input_dynamic_state{}; bool null_descriptor{}; + bool maintenance5{}; + bool list_restart{}; u64 min_imported_host_pointer_alignment{}; u32 subgroup_size{}; bool tooling_info{}; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 7a66fc0a2..0ccaf3fac 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -95,10 +95,6 @@ Shader::RuntimeInfo PipelineCache::BuildRuntimeInfo(Shader::Stage stage) { case Shader::Stage::Fragment: { info.num_user_data = regs.ps_program.settings.num_user_regs; info.num_allocated_vgprs = regs.ps_program.settings.num_vgprs * 4; - std::ranges::transform(graphics_key.mrt_swizzles, info.fs_info.mrt_swizzles.begin(), - [](Liverpool::ColorBuffer::SwapMode mode) { - return static_cast(mode); - }); const auto& ps_inputs = regs.ps_inputs; for (u32 i = 0; i < regs.num_interp; i++) { info.fs_info.inputs.push_back({ @@ -108,6 +104,12 @@ Shader::RuntimeInfo PipelineCache::BuildRuntimeInfo(Shader::Stage stage) { .default_value = u8(ps_inputs[i].default_value), }); } + for (u32 i = 0; i < Shader::MaxColorBuffers; i++) { + info.fs_info.color_buffers[i] = { + .num_format = graphics_key.color_num_formats[i], + .mrt_swizzle = static_cast(graphics_key.mrt_swizzles[i]), + }; + } break; } case Shader::Stage::Compute: { @@ -244,6 +246,7 @@ bool PipelineCache::RefreshGraphicsKey() { // attachments. This might be not a case as HW color buffers can be bound in an arbitrary // order. We need to do some arrays compaction at this stage key.color_formats.fill(vk::Format::eUndefined); + key.color_num_formats.fill(AmdGpu::NumberFormat::Unorm); key.blend_controls.fill({}); key.write_masks.fill({}); key.mrt_swizzles.fill(Liverpool::ColorBuffer::SwapMode::Standard); @@ -261,6 +264,7 @@ bool PipelineCache::RefreshGraphicsKey() { const bool is_vo_surface = renderer->IsVideoOutSurface(col_buf); key.color_formats[remapped_cb] = LiverpoolToVK::AdjustColorBufferFormat( base_format, col_buf.info.comp_swap.Value(), false /*is_vo_surface*/); + key.color_num_formats[remapped_cb] = col_buf.NumFormat(); if (base_format == key.color_formats[remapped_cb]) { key.mrt_swizzles[remapped_cb] = col_buf.info.comp_swap.Value(); } diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index e511c161e..159b489d8 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -368,11 +368,55 @@ void Rasterizer::UpdateViewportScissorState() { .maxDepth = vp.zscale + vp.zoffset, }); } - const auto& sc = regs.screen_scissor; - scissors.push_back({ - .offset = {sc.top_left_x, sc.top_left_y}, - .extent = {sc.GetWidth(), sc.GetHeight()}, - }); + + const bool enable_offset = !regs.window_scissor.window_offset_disable.Value(); + Liverpool::Scissor scsr{}; + const auto combined_scissor_value_tl = [](s16 scr, s16 win, s16 gen, s16 win_offset) { + return std::max({scr, s16(win + win_offset), s16(gen + win_offset)}); + }; + + scsr.top_left_x = combined_scissor_value_tl( + regs.screen_scissor.top_left_x, s16(regs.window_scissor.top_left_x.Value()), + s16(regs.generic_scissor.top_left_x.Value()), + enable_offset ? regs.window_offset.window_x_offset : 0); + + scsr.top_left_y = combined_scissor_value_tl( + regs.screen_scissor.top_left_y, s16(regs.window_scissor.top_left_y.Value()), + s16(regs.generic_scissor.top_left_y.Value()), + enable_offset ? regs.window_offset.window_y_offset : 0); + + const auto combined_scissor_value_br = [](s16 scr, s16 win, s16 gen, s16 win_offset) { + return std::min({scr, s16(win + win_offset), s16(gen + win_offset)}); + }; + + scsr.bottom_right_x = combined_scissor_value_br( + regs.screen_scissor.bottom_right_x, regs.window_scissor.bottom_right_x, + regs.generic_scissor.bottom_right_x, + enable_offset ? regs.window_offset.window_x_offset : 0); + + scsr.bottom_right_y = combined_scissor_value_br( + regs.screen_scissor.bottom_right_y, regs.window_scissor.bottom_right_y, + regs.generic_scissor.bottom_right_y, + enable_offset ? regs.window_offset.window_y_offset : 0); + + for (u32 idx = 0; idx < Liverpool::NumViewports; idx++) { + auto vp_scsr = scsr; + if (regs.mode_control.vport_scissor_enable) { + vp_scsr.top_left_x = + std::max(vp_scsr.top_left_x, s16(regs.viewport_scissors[idx].top_left_x.Value())); + vp_scsr.top_left_y = + std::max(vp_scsr.top_left_y, s16(regs.viewport_scissors[idx].top_left_y.Value())); + vp_scsr.bottom_right_x = + std::min(vp_scsr.bottom_right_x, regs.viewport_scissors[idx].bottom_right_x); + vp_scsr.bottom_right_y = + std::min(vp_scsr.bottom_right_y, regs.viewport_scissors[idx].bottom_right_y); + } + scissors.push_back({ + .offset = {vp_scsr.top_left_x, vp_scsr.top_left_y}, + .extent = {vp_scsr.GetWidth(), vp_scsr.GetHeight()}, + }); + } + const auto cmdbuf = scheduler.CommandBuffer(); cmdbuf.setViewport(0, viewports); cmdbuf.setScissor(0, scissors);