Refactored Achievement Badges into Texture Layers

Achievement badges/icons are refactored into the type CustomTextureData::ArraySlice::Level as that is the data type images loaded from the filesystem will be. This includes everything that uses the badges in the Qt UI and OnScreenDisplay, and similarly removes the OSD::Icon type because Level already contains that information.
This commit is contained in:
LillyJadeKatrin 2024-05-13 00:15:21 -04:00
parent 75465f00cc
commit dc8f3f6eae
9 changed files with 83 additions and 101 deletions

View File

@ -23,11 +23,10 @@
#include "Core/System.h" #include "Core/System.h"
#include "DiscIO/Blob.h" #include "DiscIO/Blob.h"
#include "UICommon/DiscordPresence.h" #include "UICommon/DiscordPresence.h"
#include "VideoCommon/Assets/CustomTextureData.h"
#include "VideoCommon/OnScreenDisplay.h" #include "VideoCommon/OnScreenDisplay.h"
#include "VideoCommon/VideoEvents.h" #include "VideoCommon/VideoEvents.h"
static std::unique_ptr<OSD::Icon> DecodeBadgeToOSDIcon(const AchievementManager::Badge& badge);
AchievementManager& AchievementManager::GetInstance() AchievementManager& AchievementManager::GetInstance()
{ {
static AchievementManager s_instance; static AchievementManager s_instance;
@ -306,9 +305,9 @@ const AchievementManager::BadgeStatus& AchievementManager::GetGameBadge() const
const AchievementManager::BadgeStatus& AchievementManager::GetAchievementBadge(AchievementId id, const AchievementManager::BadgeStatus& AchievementManager::GetAchievementBadge(AchievementId id,
bool locked) const bool locked) const
{ {
auto& badge_list = locked ? m_locked_badges : m_locked_badges; auto& badge_list = locked ? m_locked_badges : m_unlocked_badges;
auto itr = badge_list.find(id); // Brief regression - difficult to return a default BadgeStatus, will be fixed in later commit
return (itr == badge_list.end()) ? m_default_badge : itr->second; return badge_list.find(id)->second;
} }
const AchievementManager::LeaderboardStatus* const AchievementManager::LeaderboardStatus*
@ -330,7 +329,7 @@ AchievementManager::RichPresence AchievementManager::GetRichPresence() const
return m_rich_presence; return m_rich_presence;
} }
const AchievementManager::NamedIconMap& AchievementManager::GetChallengeIcons() const const AchievementManager::NamedBadgeMap& AchievementManager::GetChallengeIcons() const
{ {
return m_active_challenges; return m_active_challenges;
} }
@ -639,8 +638,7 @@ void AchievementManager::DisplayWelcomeMessage()
rc_client_get_hardcore_enabled(m_client) ? OSD::Color::YELLOW : OSD::Color::CYAN; rc_client_get_hardcore_enabled(m_client) ? OSD::Color::YELLOW : OSD::Color::CYAN;
if (!m_game_badge.name.empty()) if (!m_game_badge.name.empty())
{ {
OSD::AddMessage("", OSD::Duration::VERY_LONG, OSD::Color::GREEN, OSD::AddMessage("", OSD::Duration::VERY_LONG, OSD::Color::GREEN, &m_game_badge.badge);
DecodeBadgeToOSDIcon(m_game_badge.badge));
} }
auto info = rc_client_get_game_info(m_client); auto info = rc_client_get_game_info(m_client);
if (!info) if (!info)
@ -671,15 +669,14 @@ void AchievementManager::DisplayWelcomeMessage()
void AchievementManager::HandleAchievementTriggeredEvent(const rc_client_event_t* client_event) void AchievementManager::HandleAchievementTriggeredEvent(const rc_client_event_t* client_event)
{ {
OSD::AddMessage(fmt::format("Unlocked: {} ({})", client_event->achievement->title, OSD::AddMessage(
client_event->achievement->points), fmt::format("Unlocked: {} ({})", client_event->achievement->title,
OSD::Duration::VERY_LONG, client_event->achievement->points),
(rc_client_get_hardcore_enabled(AchievementManager::GetInstance().m_client)) ? OSD::Duration::VERY_LONG,
OSD::Color::YELLOW : (rc_client_get_hardcore_enabled(AchievementManager::GetInstance().m_client)) ?
OSD::Color::CYAN, OSD::Color::YELLOW :
DecodeBadgeToOSDIcon(AchievementManager::GetInstance() OSD::Color::CYAN,
.m_unlocked_badges[client_event->achievement->id] &AchievementManager::GetInstance().m_unlocked_badges[client_event->achievement->id].badge);
.badge));
} }
void AchievementManager::HandleLeaderboardStartedEvent(const rc_client_event_t* client_event) void AchievementManager::HandleLeaderboardStartedEvent(const rc_client_event_t* client_event)
@ -741,7 +738,7 @@ void AchievementManager::HandleAchievementChallengeIndicatorShowEvent(
unlocked_iter != unlocked_badges.end()) unlocked_iter != unlocked_badges.end())
{ {
AchievementManager::GetInstance().m_active_challenges[client_event->achievement->badge_name] = AchievementManager::GetInstance().m_active_challenges[client_event->achievement->badge_name] =
DecodeBadgeToOSDIcon(unlocked_iter->second.badge); &unlocked_iter->second.badge;
} }
} }
@ -755,12 +752,11 @@ void AchievementManager::HandleAchievementChallengeIndicatorHideEvent(
void AchievementManager::HandleAchievementProgressIndicatorShowEvent( void AchievementManager::HandleAchievementProgressIndicatorShowEvent(
const rc_client_event_t* client_event) const rc_client_event_t* client_event)
{ {
OSD::AddMessage(fmt::format("{} {}", client_event->achievement->title, OSD::AddMessage(
client_event->achievement->measured_progress), fmt::format("{} {}", client_event->achievement->title,
OSD::Duration::SHORT, OSD::Color::GREEN, client_event->achievement->measured_progress),
DecodeBadgeToOSDIcon(AchievementManager::GetInstance() OSD::Duration::SHORT, OSD::Color::GREEN,
.m_unlocked_badges[client_event->achievement->id] &AchievementManager::GetInstance().m_unlocked_badges[client_event->achievement->id].badge);
.badge));
} }
void AchievementManager::HandleGameCompletedEvent(const rc_client_event_t* client_event, void AchievementManager::HandleGameCompletedEvent(const rc_client_event_t* client_event,
@ -777,7 +773,7 @@ void AchievementManager::HandleGameCompletedEvent(const rc_client_event_t* clien
OSD::AddMessage(fmt::format("Congratulations! {} has {} {}", user_info->display_name, OSD::AddMessage(fmt::format("Congratulations! {} has {} {}", user_info->display_name,
hardcore ? "mastered" : "completed", game_info->title), hardcore ? "mastered" : "completed", game_info->title),
OSD::Duration::VERY_LONG, hardcore ? OSD::Color::YELLOW : OSD::Color::CYAN, OSD::Duration::VERY_LONG, hardcore ? OSD::Color::YELLOW : OSD::Color::CYAN,
DecodeBadgeToOSDIcon(AchievementManager::GetInstance().m_game_badge.badge)); &AchievementManager::GetInstance().m_game_badge.badge);
} }
void AchievementManager::HandleResetEvent(const rc_client_event_t* client_event) void AchievementManager::HandleResetEvent(const rc_client_event_t* client_event)
@ -792,20 +788,6 @@ void AchievementManager::HandleServerErrorEvent(const rc_client_event_t* client_
client_event->server_error->api, client_event->server_error->error_message); client_event->server_error->api, client_event->server_error->error_message);
} }
static std::unique_ptr<OSD::Icon> DecodeBadgeToOSDIcon(const AchievementManager::Badge& badge)
{
if (badge.empty())
return nullptr;
auto icon = std::make_unique<OSD::Icon>();
if (!Common::LoadPNG(badge, &icon->rgba_data, &icon->width, &icon->height))
{
ERROR_LOG_FMT(ACHIEVEMENTS, "Error decoding badge.");
return nullptr;
}
return icon;
}
void AchievementManager::Request(const rc_api_request_t* request, void AchievementManager::Request(const rc_api_request_t* request,
rc_client_server_callback_t callback, void* callback_data, rc_client_server_callback_t callback, void* callback_data,
rc_client_t* client) rc_client_t* client)
@ -914,7 +896,6 @@ void AchievementManager::FetchBadge(AchievementManager::BadgeStatus* badge, u32
} }
rc_api_destroy_request(&api_request); rc_api_destroy_request(&api_request);
fetched_badge = std::move(*http_response);
INFO_LOG_FMT(ACHIEVEMENTS, "Successfully downloaded badge id {}.", name_to_fetch); INFO_LOG_FMT(ACHIEVEMENTS, "Successfully downloaded badge id {}.", name_to_fetch);
std::lock_guard lg{m_lock}; std::lock_guard lg{m_lock};
@ -923,7 +904,12 @@ void AchievementManager::FetchBadge(AchievementManager::BadgeStatus* badge, u32
INFO_LOG_FMT(ACHIEVEMENTS, "Requested outdated badge id {}.", name_to_fetch); INFO_LOG_FMT(ACHIEVEMENTS, "Requested outdated badge id {}.", name_to_fetch);
return; return;
} }
badge->badge = std::move(fetched_badge);
if (!LoadPNGTexture(&badge->badge, *http_response))
{
ERROR_LOG_FMT(ACHIEVEMENTS, "Default game badge '{}' failed to load",
DEFAULT_GAME_BADGE_FILENAME);
}
badge->name = std::move(name_to_fetch); badge->name = std::move(name_to_fetch);
m_update_callback(callback_data); m_update_callback(callback_data);

View File

@ -22,6 +22,7 @@
#include "Common/HttpRequest.h" #include "Common/HttpRequest.h"
#include "Common/WorkQueueThread.h" #include "Common/WorkQueueThread.h"
#include "DiscIO/Volume.h" #include "DiscIO/Volume.h"
#include "VideoCommon/Assets/CustomTextureData.h"
namespace Core namespace Core
{ {
@ -29,11 +30,6 @@ class CPUThreadGuard;
class System; class System;
} // namespace Core } // namespace Core
namespace OSD
{
struct Icon;
}
class AchievementManager class AchievementManager
{ {
public: public:
@ -47,8 +43,8 @@ public:
using LeaderboardRank = u32; using LeaderboardRank = u32;
static constexpr size_t RP_SIZE = 256; static constexpr size_t RP_SIZE = 256;
using RichPresence = std::array<char, RP_SIZE>; using RichPresence = std::array<char, RP_SIZE>;
using Badge = std::vector<u8>; using Badge = VideoCommon::CustomTextureData::ArraySlice::Level;
using NamedIconMap = std::map<std::string, std::unique_ptr<OSD::Icon>, std::less<>>; using NamedBadgeMap = std::unordered_map<std::string, const Badge*>;
static constexpr size_t MAX_DISPLAYED_LBOARDS = 4; static constexpr size_t MAX_DISPLAYED_LBOARDS = 4;
struct BadgeStatus struct BadgeStatus
@ -116,7 +112,7 @@ public:
const BadgeStatus& GetAchievementBadge(AchievementId id, bool locked) const; const BadgeStatus& GetAchievementBadge(AchievementId id, bool locked) const;
const LeaderboardStatus* GetLeaderboardInfo(AchievementId leaderboard_id); const LeaderboardStatus* GetLeaderboardInfo(AchievementId leaderboard_id);
RichPresence GetRichPresence() const; RichPresence GetRichPresence() const;
const NamedIconMap& GetChallengeIcons() const; const NamedBadgeMap& GetChallengeIcons() const;
std::vector<std::string> GetActiveLeaderboards() const; std::vector<std::string> GetActiveLeaderboards() const;
void DoState(PointerWrap& p); void DoState(PointerWrap& p);
@ -134,8 +130,6 @@ private:
std::unique_ptr<DiscIO::Volume> volume; std::unique_ptr<DiscIO::Volume> volume;
}; };
const BadgeStatus m_default_badge;
static void* FilereaderOpenByFilepath(const char* path_utf8); static void* FilereaderOpenByFilepath(const char* path_utf8);
static void* FilereaderOpenByVolume(const char* path_utf8); static void* FilereaderOpenByVolume(const char* path_utf8);
static void FilereaderSeek(void* file_handle, int64_t offset, int origin); static void FilereaderSeek(void* file_handle, int64_t offset, int origin);
@ -200,7 +194,7 @@ private:
std::chrono::steady_clock::time_point m_last_rp_time = std::chrono::steady_clock::now(); std::chrono::steady_clock::time_point m_last_rp_time = std::chrono::steady_clock::now();
std::unordered_map<AchievementId, LeaderboardStatus> m_leaderboard_map; std::unordered_map<AchievementId, LeaderboardStatus> m_leaderboard_map;
NamedIconMap m_active_challenges; NamedBadgeMap m_active_challenges;
std::vector<rc_client_leaderboard_tracker_t> m_active_leaderboards; std::vector<rc_client_leaderboard_tracker_t> m_active_leaderboards;
Common::WorkQueueThread<std::function<void()>> m_queue; Common::WorkQueueThread<std::function<void()>> m_queue;

View File

@ -63,15 +63,13 @@ void AchievementBox::UpdateData()
color = AchievementManager::BLUE; color = AchievementManager::BLUE;
if (badge.name != "") if (badge.name != "")
{ {
QImage i_badge{}; QImage i_badge(&badge.badge.data.front(), badge.badge.width, badge.badge.height,
if (i_badge.loadFromData(&badge.badge.front(), static_cast<int>(badge.badge.size()))) QImage::Format_RGBA8888);
{ m_badge->setPixmap(
m_badge->setPixmap(QPixmap::fromImage(i_badge).scaled(64, 64, Qt::KeepAspectRatio, QPixmap::fromImage(i_badge).scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation));
Qt::SmoothTransformation)); m_badge->adjustSize();
m_badge->adjustSize(); m_badge->setStyleSheet(
m_badge->setStyleSheet( QStringLiteral("border: 4px solid %1").arg(QtUtils::FromStdString(color)));
QStringLiteral("border: 4px solid %1").arg(QtUtils::FromStdString(color)));
}
} }
else else
{ {

View File

@ -76,15 +76,13 @@ void AchievementHeaderWidget::UpdateData()
m_user_icon->setText({}); m_user_icon->setText({});
if (!player_badge.name.empty()) if (!player_badge.name.empty())
{ {
QImage i_user_icon{}; QImage i_user_icon(&player_badge.badge.data.front(), player_badge.badge.width,
if (i_user_icon.loadFromData(&player_badge.badge.front(), (int)player_badge.badge.size())) player_badge.badge.height, QImage::Format_RGBA8888);
{ m_user_icon->setPixmap(QPixmap::fromImage(i_user_icon)
m_user_icon->setPixmap(QPixmap::fromImage(i_user_icon) .scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation));
.scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation)); m_user_icon->adjustSize();
m_user_icon->adjustSize(); m_user_icon->setStyleSheet(QStringLiteral("border: 4px solid transparent"));
m_user_icon->setStyleSheet(QStringLiteral("border: 4px solid transparent")); m_user_icon->setVisible(true);
m_user_icon->setVisible(true);
}
} }
m_game_icon->setVisible(false); m_game_icon->setVisible(false);
m_game_icon->clear(); m_game_icon->clear();
@ -97,22 +95,20 @@ void AchievementHeaderWidget::UpdateData()
if (!game_badge.name.empty()) if (!game_badge.name.empty())
{ {
QImage i_game_icon{}; QImage i_game_icon(&game_badge.badge.data.front(), game_badge.badge.width,
if (i_game_icon.loadFromData(&game_badge.badge.front(), (int)game_badge.badge.size())) game_badge.badge.height, QImage::Format_RGBA8888);
m_game_icon->setPixmap(QPixmap::fromImage(i_game_icon)
.scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation));
m_game_icon->adjustSize();
std::string_view color = AchievementManager::GRAY;
if (game_summary.num_core_achievements == game_summary.num_unlocked_achievements)
{ {
m_game_icon->setPixmap(QPixmap::fromImage(i_game_icon) color =
.scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation)); instance.IsHardcoreModeActive() ? AchievementManager::GOLD : AchievementManager::BLUE;
m_game_icon->adjustSize();
std::string_view color = AchievementManager::GRAY;
if (game_summary.num_core_achievements == game_summary.num_unlocked_achievements)
{
color =
instance.IsHardcoreModeActive() ? AchievementManager::GOLD : AchievementManager::BLUE;
}
m_game_icon->setStyleSheet(
QStringLiteral("border: 4px solid %1").arg(QtUtils::FromStdString(color)));
m_game_icon->setVisible(true);
} }
m_game_icon->setStyleSheet(
QStringLiteral("border: 4px solid %1").arg(QtUtils::FromStdString(color)));
m_game_icon->setVisible(true);
} }
m_name->setText(tr("%1 is playing %2").arg(user_name).arg(game_name)); m_name->setText(tr("%1 is playing %2").arg(user_name).arg(game_name));

View File

@ -574,6 +574,14 @@ bool LoadPNGTexture(CustomTextureData::ArraySlice::Level* level, const std::stri
std::vector<u8> buffer(file.GetSize()); std::vector<u8> buffer(file.GetSize());
file.ReadBytes(buffer.data(), file.GetSize()); file.ReadBytes(buffer.data(), file.GetSize());
return LoadPNGTexture(level, buffer);
}
bool LoadPNGTexture(CustomTextureData::ArraySlice::Level* level, const std::vector<u8>& buffer)
{
if (!level) [[unlikely]]
return false;
if (!Common::LoadPNG(buffer, &level->data, &level->width, &level->height)) if (!Common::LoadPNG(buffer, &level->data, &level->width, &level->height))
return false; return false;

View File

@ -33,4 +33,5 @@ bool LoadDDSTexture(CustomTextureData* texture, const std::string& filename);
bool LoadDDSTexture(CustomTextureData::ArraySlice::Level* level, const std::string& filename, bool LoadDDSTexture(CustomTextureData::ArraySlice::Level* level, const std::string& filename,
u32 mip_level); u32 mip_level);
bool LoadPNGTexture(CustomTextureData::ArraySlice::Level* level, const std::string& filename); bool LoadPNGTexture(CustomTextureData::ArraySlice::Level* level, const std::string& filename);
bool LoadPNGTexture(CustomTextureData::ArraySlice::Level* level, const std::vector<u8>& buffer);
} // namespace VideoCommon } // namespace VideoCommon

View File

@ -20,6 +20,7 @@
#include "VideoCommon/AbstractGfx.h" #include "VideoCommon/AbstractGfx.h"
#include "VideoCommon/AbstractTexture.h" #include "VideoCommon/AbstractTexture.h"
#include "VideoCommon/Assets/CustomTextureData.h"
#include "VideoCommon/TextureConfig.h" #include "VideoCommon/TextureConfig.h"
namespace OSD namespace OSD
@ -36,8 +37,9 @@ static std::atomic<int> s_obscured_pixels_top = 0;
struct Message struct Message
{ {
Message() = default; Message() = default;
Message(std::string text_, u32 duration_, u32 color_, std::unique_ptr<Icon> icon_ = nullptr) Message(std::string text_, u32 duration_, u32 color_,
: text(std::move(text_)), duration(duration_), color(color_), icon(std::move(icon_)) const VideoCommon::CustomTextureData::ArraySlice::Level* icon_ = nullptr)
: text(std::move(text_)), duration(duration_), color(color_), icon(icon_)
{ {
timer.Start(); timer.Start();
} }
@ -48,7 +50,7 @@ struct Message
bool ever_drawn = false; bool ever_drawn = false;
bool should_discard = false; bool should_discard = false;
u32 color = 0; u32 color = 0;
std::unique_ptr<Icon> icon; const VideoCommon::CustomTextureData::ArraySlice::Level* icon;
std::unique_ptr<AbstractTexture> texture; std::unique_ptr<AbstractTexture> texture;
}; };
static std::multimap<MessageType, Message> s_messages; static std::multimap<MessageType, Message> s_messages;
@ -95,13 +97,13 @@ static float DrawMessage(int index, Message& msg, const ImVec2& position, int ti
msg.texture = g_gfx->CreateTexture(tex_config); msg.texture = g_gfx->CreateTexture(tex_config);
if (msg.texture) if (msg.texture)
{ {
msg.texture->Load(0, width, height, width, msg.icon->rgba_data.data(), msg.texture->Load(0, width, height, width, msg.icon->data.data(),
sizeof(u32) * width * height); sizeof(u32) * width * height);
} }
else else
{ {
// don't try again next time // don't try again next time
msg.icon.reset(); msg.icon = nullptr;
} }
} }
@ -127,7 +129,7 @@ static float DrawMessage(int index, Message& msg, const ImVec2& position, int ti
} }
void AddTypedMessage(MessageType type, std::string message, u32 ms, u32 argb, void AddTypedMessage(MessageType type, std::string message, u32 ms, u32 argb,
std::unique_ptr<Icon> icon) const VideoCommon::CustomTextureData::ArraySlice::Level* icon)
{ {
std::lock_guard lock{s_messages_mutex}; std::lock_guard lock{s_messages_mutex};
@ -141,7 +143,8 @@ void AddTypedMessage(MessageType type, std::string message, u32 ms, u32 argb,
s_messages.emplace(type, Message(std::move(message), ms, argb, std::move(icon))); s_messages.emplace(type, Message(std::move(message), ms, argb, std::move(icon)));
} }
void AddMessage(std::string message, u32 ms, u32 argb, std::unique_ptr<Icon> icon) void AddMessage(std::string message, u32 ms, u32 argb,
const VideoCommon::CustomTextureData::ArraySlice::Level* icon)
{ {
std::lock_guard lock{s_messages_mutex}; std::lock_guard lock{s_messages_mutex};
s_messages.emplace(MessageType::Typeless, Message(std::move(message), ms, argb, std::move(icon))); s_messages.emplace(MessageType::Typeless, Message(std::move(message), ms, argb, std::move(icon)));

View File

@ -10,6 +10,8 @@
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "VideoCommon/Assets/CustomTextureData.h"
namespace OSD namespace OSD
{ {
enum class MessageType enum class MessageType
@ -37,18 +39,12 @@ constexpr u32 NORMAL = 5000;
constexpr u32 VERY_LONG = 10000; constexpr u32 VERY_LONG = 10000;
}; // namespace Duration }; // namespace Duration
struct Icon
{
std::vector<u8> rgba_data;
u32 width = 0;
u32 height = 0;
}; // struct Icon
// On-screen message display (colored yellow by default) // On-screen message display (colored yellow by default)
void AddMessage(std::string message, u32 ms = Duration::SHORT, u32 argb = Color::YELLOW, void AddMessage(std::string message, u32 ms = Duration::SHORT, u32 argb = Color::YELLOW,
std::unique_ptr<Icon> icon = nullptr); const VideoCommon::CustomTextureData::ArraySlice::Level* icon = nullptr);
void AddTypedMessage(MessageType type, std::string message, u32 ms = Duration::SHORT, void AddTypedMessage(MessageType type, std::string message, u32 ms = Duration::SHORT,
u32 argb = Color::YELLOW, std::unique_ptr<Icon> icon = nullptr); u32 argb = Color::YELLOW,
const VideoCommon::CustomTextureData::ArraySlice::Level* icon = nullptr);
// Draw the current messages on the screen. Only call once per frame. // Draw the current messages on the screen. Only call once per frame.
void DrawMessages(); void DrawMessages();

View File

@ -358,7 +358,7 @@ void OnScreenUI::DrawChallengesAndLeaderboards()
TextureConfig tex_config(width, height, 1, 1, 1, AbstractTextureFormat::RGBA8, 0, TextureConfig tex_config(width, height, 1, 1, 1, AbstractTextureFormat::RGBA8, 0,
AbstractTextureType::Texture_2DArray); AbstractTextureType::Texture_2DArray);
auto res = m_challenge_texture_map.insert_or_assign(name, g_gfx->CreateTexture(tex_config)); auto res = m_challenge_texture_map.insert_or_assign(name, g_gfx->CreateTexture(tex_config));
res.first->second->Load(0, width, height, width, icon->rgba_data.data(), res.first->second->Load(0, width, height, width, icon->data.data(),
sizeof(u32) * width * height); sizeof(u32) * width * height);
} }
for (auto& [name, texture] : m_challenge_texture_map) for (auto& [name, texture] : m_challenge_texture_map)