diff --git a/CMakeLists.txt b/CMakeLists.txt index 770a448e84..1d7b7f0a8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1612,6 +1612,8 @@ list(APPEND NativeAppSource UI/CustomButtonMappingScreen.cpp UI/Theme.h UI/Theme.cpp + UI/UIAtlas.h + UI/UIAtlas.cpp UI/RetroAchievementScreens.cpp UI/RetroAchievementScreens.h ) diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index c93e7a7b9b..475c5ef3e1 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -130,6 +130,7 @@ #include "UI/OnScreenDisplay.h" #include "UI/RemoteISOScreen.h" #include "UI/Theme.h" +#include "UI/UIAtlas.h" #if defined(USING_QT_UI) #include diff --git a/UI/Theme.cpp b/UI/Theme.cpp index 309126f7e3..1029e8c098 100644 --- a/UI/Theme.cpp +++ b/UI/Theme.cpp @@ -21,15 +21,10 @@ #include "UI/Theme.h" -#include "Common/File/FileUtil.h" -#include "Common/File/VFS/VFS.h" #include "Common/Data/Format/IniFile.h" #include "Common/File/DirListing.h" #include "Common/Log/LogManager.h" -#include "Common/Render/ManagedTexture.h" -#include "Common/Render/AtlasGen.h" -#include "Common/TimeUtil.h" -#include "Common/StringUtils.h" +#include "Common/File/VFS/VFS.h" #include "Core/Config.h" @@ -77,10 +72,6 @@ struct ThemeInfo { static UI::Theme ui_theme; static std::vector themeInfos; -// TODO: Don't really belong here. -static Atlas ui_atlas; -static Atlas font_atlas; - static void LoadThemeInfo(const std::vector &directories) { themeInfos.clear(); ThemeInfo def{}; @@ -175,17 +166,6 @@ static UI::Style MakeStyle(uint32_t fg, uint32_t bg) { return s; } -static void LoadAtlasMetadata(Atlas &metadata, const char *filename) { - size_t atlas_data_size = 0; - const uint8_t *atlas_data = g_VFS.ReadFile(filename, &atlas_data_size); - bool load_success = atlas_data != nullptr && metadata.LoadMeta(atlas_data, atlas_data_size); - if (!load_success) { - ERROR_LOG(Log::G3D, "Failed to load %s - graphics may be broken", filename); - // Stumble along with broken visuals instead of dying... - } - delete[] atlas_data; -} - void UpdateTheme() { // First run, get the default in at least if (themeInfos.empty()) { @@ -258,14 +238,6 @@ UI::Theme *GetTheme() { return &ui_theme; } -Atlas *GetFontAtlas() { - return &font_atlas; -} - -Atlas *GetUIAtlas() { - return &ui_atlas; -} - void ReloadAllThemeInfo() { std::vector directories; directories.push_back(Path("themes")); // For VFS @@ -280,197 +252,3 @@ std::vector GetThemeInfoNames() { } return names; } - -static const ImageDesc imageDescs[] = { - {"I_SOLIDWHITE", "white.png"}, - {"I_CROSS", "cross.png"}, - {"I_CIRCLE", "circle.png"}, - {"I_SQUARE", "square.png"}, - {"I_TRIANGLE", "triangle.png"}, - {"I_SELECT", "select.png"}, - {"I_START", "start.png"}, - {"I_ARROW", "arrow.png"}, - {"I_DIR", "dir.png"}, - {"I_ROUND", "round.png"}, - {"I_RECT", "rect.png"}, - {"I_STICK", "stick.png"}, - {"I_STICK_BG", "stick_bg.png"}, - {"I_SHOULDER", "shoulder.png"}, - {"I_DIR_LINE", "dir_line.png"}, - {"I_ROUND_LINE", "round_line.png"}, - {"I_RECT_LINE", "rect_line.png"}, - {"I_SHOULDER_LINE", "shoulder_line.png"}, - {"I_STICK_LINE", "stick_line.png"}, - {"I_STICK_BG_LINE", "stick_bg_line.png"}, - {"I_CHECKEDBOX", "checkedbox.png"}, - {"I_BG", "background2.png"}, - {"I_L", "L.png"}, - {"I_R", "R.png"}, - {"I_DROP_SHADOW", "dropshadow.png"}, - {"I_LINES", "lines.png"}, - {"I_GRID", "grid.png"}, - {"I_LOGO", "logo.png"}, - {"I_ICON", "icon_regular_72.png"}, - {"I_ICONGOLD", "icon_gold_72.png"}, - {"I_FOLDER", "folder_line.png"}, - {"I_UP_DIRECTORY", "up_line.png"}, - {"I_GEAR", "gear.png"}, - {"I_1", "1.png"}, - {"I_2", "2.png"}, - {"I_3", "3.png"}, - {"I_4", "4.png"}, - {"I_5", "5.png"}, - {"I_6", "6.png"}, - {"I_PSP_DISPLAY", "psp_display.png"}, - {"I_FLAG_JP", "flag_jp.png"}, - {"I_FLAG_US", "flag_us.png"}, - {"I_FLAG_EU", "flag_eu.png"}, - {"I_FLAG_HK", "flag_hk.png"}, - {"I_FLAG_AS", "flag_as.png"}, - {"I_FLAG_KO", "flag_ko.png"}, - {"I_FULLSCREEN", "fullscreen.png"}, - {"I_RESTORE", "restore.png"}, - {"I_SDCARD", "sdcard.png"}, - {"I_HOME", "home.png"}, - {"I_A", "a.png"}, - {"I_B", "b.png"}, - {"I_C", "c.png"}, - {"I_D", "d.png"}, - {"I_E", "e.png"}, - {"I_F", "f.png"}, - {"I_SQUARE_SHAPE", "square_shape.png"}, - {"I_SQUARE_SHAPE_LINE", "square_shape_line.png"}, - {"I_FOLDER_OPEN", "folder_open_line.png"}, - {"I_WARNING", "warning.png"}, - {"I_TRASHCAN", "trashcan.png"}, - {"I_PLUS", "plus.png"}, - {"I_ROTATE_LEFT", "rotate_left.png"}, - {"I_ROTATE_RIGHT", "rotate_right.png"}, - {"I_ARROW_LEFT", "arrow_left.png"}, - {"I_ARROW_RIGHT", "arrow_right.png"}, - {"I_ARROW_UP", "arrow_up.png"}, - {"I_ARROW_DOWN", "arrow_down.png"}, - {"I_SLIDERS", "sliders.png"}, - {"I_THREE_DOTS", "three_dots.png"}, - {"I_INFO", "info.png"}, - {"I_RETROACHIEVEMENTS_LOGO", "retroachievements_logo.png"}, - {"I_CHECKMARK", "checkmark.png"}, - {"I_PLAY", "play.png"}, - {"I_STOP", "stop.png"}, - {"I_PAUSE", "pause.png"}, - {"I_FASTFORWARD", "fast_forward.png"}, - {"I_RECORD", "record.png"}, - {"I_SPEAKER", "speaker.png"}, - {"I_SPEAKER_MAX", "speaker_max.png"}, - {"I_SPEAKER_OFF", "speaker_off.png"}, - {"I_WINNER_CUP", "winner_cup.png"}, - {"I_EMPTY", "empty.png"}, -}; - -Draw::Texture *GenerateUIAtlas(Draw::DrawContext *draw, Atlas *atlas) { - Bucket bucket; - - // Script fully read, now read images and rasterize the fonts. - Image images[ARRAY_SIZE(imageDescs)]; - int resultIds[ARRAY_SIZE(imageDescs)]; - - Instant start = Instant::Now(); - - // TODO: This can be parallelized if needed. - for (int i = 0; i < ARRAY_SIZE(imageDescs); i++) { - resultIds[i] = i; - - Image &img = images[i]; - - bool success = true; - if (equals(imageDescs[i].fileName, "white.png")) { - img.resize(16, 16); - img.fill(0xFFFFFFFF); - } else { - std::string name = "ui_images/"; - name.append(imageDescs[i].fileName); - bool success = img.LoadPNG(name.c_str()); - if (!success) { - ERROR_LOG(Log::G3D, "Failed to load %s\n", name.c_str()); - } - } - } - INFO_LOG(Log::G3D, " - Loaded %zu images in %.2f ms\n", ARRAY_SIZE(images), start.ElapsedMs()); - - Instant addStart = Instant::Now(); - for (int i = 0; i < ARRAY_SIZE(imageDescs); i++) { - bucket.AddImage(std::move(images[i]), i); - } - - INFO_LOG(Log::G3D, " - Added %zu images in %.2f ms\n", bucket.data.size(), addStart.ElapsedMs()); - - int image_width = 512; - Image dest; - - Instant bucketStart = Instant::Now(); - std::vector results = bucket.Resolve(image_width, dest); - INFO_LOG(Log::G3D, " - Bucketed %zu images in %.2f ms\n", results.size(), bucketStart.ElapsedMs()); - - // Fill out the atlas structure. - std::vector genAtlasImages; - genAtlasImages.reserve(ARRAY_SIZE(imageDescs)); - for (int i = 0; i < ARRAY_SIZE(imageDescs); i++) { - genAtlasImages.push_back(imageDescs[i].ToAtlasImage(resultIds[i], (float)dest.width(), (float)dest.height(), results)); - } - - atlas->Clear(); - atlas->images = new AtlasImage[genAtlasImages.size()]; - std::copy(genAtlasImages.begin(), genAtlasImages.end(), atlas->images); - atlas->num_images = (int)genAtlasImages.size(); - - // For debug, write out the atlas. - // dest.SavePNG("../gen.png"); - - // Then, create the texture too. - Draw::TextureDesc desc{}; - desc.width = image_width; - desc.height = dest.height(); - desc.depth = 1; - desc.mipLevels = 1; - desc.format = Draw::DataFormat::R8G8B8A8_UNORM; - desc.type = Draw::TextureType::LINEAR2D; - desc.initData.push_back((const u8 *)dest.data()); - desc.tag = "UIAtlas"; - - INFO_LOG(Log::G3D, "UI atlas generated in %.2f ms, size %dx%d with %zu images\n", start.ElapsedMs(), desc.width, desc.height, genAtlasImages.size()); - return draw->CreateTexture(desc); -} - -AtlasData AtlasProvider(Draw::DrawContext *draw, AtlasChoice atlas) { - switch (atlas) { - case AtlasChoice::General: - { - // Generate the atlas from scratch. - Draw::Texture *tex = GenerateUIAtlas(draw, &ui_atlas); - return { &ui_atlas, tex }; - } - case AtlasChoice::Font: - { - Draw::Texture *fontTexture = nullptr; -#if PPSSPP_PLATFORM(WINDOWS) || PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS) - // Load the smaller ascii font only, like on Android. For debug ui etc. - // NOTE: We better be sure here that the correct metadata is loaded.. - LoadAtlasMetadata(font_atlas, "asciifont_atlas.meta"); - fontTexture = CreateTextureFromFile(draw, "asciifont_atlas.zim", ImageFileType::ZIM, false); - if (!fontTexture) { - WARN_LOG(Log::System, "Failed to load font_atlas.zim or asciifont_atlas.zim"); - } -#else - // Load the full font texture. - LoadAtlasMetadata(font_atlas, "font_atlas.meta"); - fontTexture = CreateTextureFromFile(draw, "font_atlas.zim", ImageFileType::ZIM, false); -#endif - return { - &font_atlas, - fontTexture, - }; - } - default: - return {}; - }; -} diff --git a/UI/Theme.h b/UI/Theme.h index ed0dc68a55..cae6d1e95a 100644 --- a/UI/Theme.h +++ b/UI/Theme.h @@ -27,8 +27,4 @@ void ReloadAllThemeInfo(); std::vector GetThemeInfoNames(); void UpdateTheme(); -Atlas *GetFontAtlas(); -Atlas *GetUIAtlas(); UI::Theme *GetTheme(); - -AtlasData AtlasProvider(Draw::DrawContext *draw, AtlasChoice atlas); diff --git a/UI/UI.vcxproj b/UI/UI.vcxproj index c097c4c229..7ac06b83a8 100644 --- a/UI/UI.vcxproj +++ b/UI/UI.vcxproj @@ -72,6 +72,7 @@ + @@ -118,6 +119,7 @@ + diff --git a/UI/UI.vcxproj.filters b/UI/UI.vcxproj.filters index fb3178b7f7..58b4bf47d5 100644 --- a/UI/UI.vcxproj.filters +++ b/UI/UI.vcxproj.filters @@ -122,6 +122,7 @@ Screens + @@ -244,6 +245,7 @@ Screens + diff --git a/UI/UIAtlas.cpp b/UI/UIAtlas.cpp new file mode 100644 index 0000000000..05389ef557 --- /dev/null +++ b/UI/UIAtlas.cpp @@ -0,0 +1,229 @@ +#include +#include "Common/File/Path.h" +#include "Common/File/FileUtil.h" +#include "Common/File/VFS/VFS.h" +#include "Common/TimeUtil.h" +#include "Common/GPU/thin3d.h" +#include "Common/StringUtils.h" +#include "Common/UI/Context.h" +#include "Common/Render/AtlasGen.h" +#include "Common/Render/ManagedTexture.h" +#include "Common/Common.h" +#include "Common/Log.h" +#include "UI/UIAtlas.h" + +static Atlas ui_atlas; +static Atlas font_atlas; + +const Atlas *GetFontAtlas() { + return &font_atlas; +} + +Atlas *GetUIAtlas() { + return &ui_atlas; +} + +static const ImageDesc imageDescs[] = { + {"I_SOLIDWHITE", "white.png"}, + {"I_CROSS", "cross.png"}, + {"I_CIRCLE", "circle.png"}, + {"I_SQUARE", "square.png"}, + {"I_TRIANGLE", "triangle.png"}, + {"I_SELECT", "select.png"}, + {"I_START", "start.png"}, + {"I_ARROW", "arrow.png"}, + {"I_DIR", "dir.png"}, + {"I_ROUND", "round.png"}, + {"I_RECT", "rect.png"}, + {"I_STICK", "stick.png"}, + {"I_STICK_BG", "stick_bg.png"}, + {"I_SHOULDER", "shoulder.png"}, + {"I_DIR_LINE", "dir_line.png"}, + {"I_ROUND_LINE", "round_line.png"}, + {"I_RECT_LINE", "rect_line.png"}, + {"I_SHOULDER_LINE", "shoulder_line.png"}, + {"I_STICK_LINE", "stick_line.png"}, + {"I_STICK_BG_LINE", "stick_bg_line.png"}, + {"I_CHECKEDBOX", "checkedbox.png"}, + {"I_BG", "background2.png"}, + {"I_L", "L.png"}, + {"I_R", "R.png"}, + {"I_DROP_SHADOW", "dropshadow.png"}, + {"I_LINES", "lines.png"}, + {"I_GRID", "grid.png"}, + {"I_LOGO", "logo.png"}, + {"I_ICON", "icon_regular_72.png"}, + {"I_ICONGOLD", "icon_gold_72.png"}, + {"I_FOLDER", "folder_line.png"}, + {"I_UP_DIRECTORY", "up_line.png"}, + {"I_GEAR", "gear.png"}, + {"I_1", "1.png"}, + {"I_2", "2.png"}, + {"I_3", "3.png"}, + {"I_4", "4.png"}, + {"I_5", "5.png"}, + {"I_6", "6.png"}, + {"I_PSP_DISPLAY", "psp_display.png"}, + {"I_FLAG_JP", "flag_jp.png"}, + {"I_FLAG_US", "flag_us.png"}, + {"I_FLAG_EU", "flag_eu.png"}, + {"I_FLAG_HK", "flag_hk.png"}, + {"I_FLAG_AS", "flag_as.png"}, + {"I_FLAG_KO", "flag_ko.png"}, + {"I_FULLSCREEN", "fullscreen.png"}, + {"I_RESTORE", "restore.png"}, + {"I_SDCARD", "sdcard.png"}, + {"I_HOME", "home.png"}, + {"I_A", "a.png"}, + {"I_B", "b.png"}, + {"I_C", "c.png"}, + {"I_D", "d.png"}, + {"I_E", "e.png"}, + {"I_F", "f.png"}, + {"I_SQUARE_SHAPE", "square_shape.png"}, + {"I_SQUARE_SHAPE_LINE", "square_shape_line.png"}, + {"I_FOLDER_OPEN", "folder_open_line.png"}, + {"I_WARNING", "warning.png"}, + {"I_TRASHCAN", "trashcan.png"}, + {"I_PLUS", "plus.png"}, + {"I_ROTATE_LEFT", "rotate_left.png"}, + {"I_ROTATE_RIGHT", "rotate_right.png"}, + {"I_ARROW_LEFT", "arrow_left.png"}, + {"I_ARROW_RIGHT", "arrow_right.png"}, + {"I_ARROW_UP", "arrow_up.png"}, + {"I_ARROW_DOWN", "arrow_down.png"}, + {"I_SLIDERS", "sliders.png"}, + {"I_THREE_DOTS", "three_dots.png"}, + {"I_INFO", "info.png"}, + {"I_RETROACHIEVEMENTS_LOGO", "retroachievements_logo.png"}, + {"I_CHECKMARK", "checkmark.png"}, + {"I_PLAY", "play.png"}, + {"I_STOP", "stop.png"}, + {"I_PAUSE", "pause.png"}, + {"I_FASTFORWARD", "fast_forward.png"}, + {"I_RECORD", "record.png"}, + {"I_SPEAKER", "speaker.png"}, + {"I_SPEAKER_MAX", "speaker_max.png"}, + {"I_SPEAKER_OFF", "speaker_off.png"}, + {"I_WINNER_CUP", "winner_cup.png"}, + {"I_EMPTY", "empty.png"}, +}; + +Draw::Texture *GenerateUIAtlas(Draw::DrawContext *draw, Atlas *atlas) { + Bucket bucket; + + // Script fully read, now read images and rasterize the fonts. + Image images[ARRAY_SIZE(imageDescs)]; + int resultIds[ARRAY_SIZE(imageDescs)]; + + Instant start = Instant::Now(); + + // TODO: This can be parallelized if needed. + for (int i = 0; i < ARRAY_SIZE(imageDescs); i++) { + resultIds[i] = i; + + Image &img = images[i]; + + bool success = true; + if (equals(imageDescs[i].fileName, "white.png")) { + img.resize(16, 16); + img.fill(0xFFFFFFFF); + } else { + std::string name = "ui_images/"; + name.append(imageDescs[i].fileName); + bool success = img.LoadPNG(name.c_str()); + if (!success) { + ERROR_LOG(Log::G3D, "Failed to load %s\n", name.c_str()); + } + } + } + INFO_LOG(Log::G3D, " - Loaded %zu images in %.2f ms\n", ARRAY_SIZE(images), start.ElapsedMs()); + + Instant addStart = Instant::Now(); + for (int i = 0; i < ARRAY_SIZE(imageDescs); i++) { + bucket.AddImage(std::move(images[i]), i); + } + + INFO_LOG(Log::G3D, " - Added %zu images in %.2f ms\n", bucket.data.size(), addStart.ElapsedMs()); + + int image_width = 512; + Image dest; + + Instant bucketStart = Instant::Now(); + std::vector results = bucket.Resolve(image_width, dest); + INFO_LOG(Log::G3D, " - Bucketed %zu images in %.2f ms\n", results.size(), bucketStart.ElapsedMs()); + + // Fill out the atlas structure. + std::vector genAtlasImages; + genAtlasImages.reserve(ARRAY_SIZE(imageDescs)); + for (int i = 0; i < ARRAY_SIZE(imageDescs); i++) { + genAtlasImages.push_back(imageDescs[i].ToAtlasImage(resultIds[i], (float)dest.width(), (float)dest.height(), results)); + } + + atlas->Clear(); + atlas->images = new AtlasImage[genAtlasImages.size()]; + std::copy(genAtlasImages.begin(), genAtlasImages.end(), atlas->images); + atlas->num_images = (int)genAtlasImages.size(); + + // For debug, write out the atlas. + // dest.SavePNG("../gen.png"); + + // Then, create the texture too. + Draw::TextureDesc desc{}; + desc.width = image_width; + desc.height = dest.height(); + desc.depth = 1; + desc.mipLevels = 1; + desc.format = Draw::DataFormat::R8G8B8A8_UNORM; + desc.type = Draw::TextureType::LINEAR2D; + desc.initData.push_back((const u8 *)dest.data()); + desc.tag = "UIAtlas"; + + INFO_LOG(Log::G3D, "UI atlas generated in %.2f ms, size %dx%d with %zu images\n", start.ElapsedMs(), desc.width, desc.height, genAtlasImages.size()); + return draw->CreateTexture(desc); +} + +static void LoadAtlasMetadata(Atlas &metadata, const char *filename) { + size_t atlas_data_size = 0; + const uint8_t *atlas_data = g_VFS.ReadFile(filename, &atlas_data_size); + bool load_success = atlas_data != nullptr && metadata.LoadMeta(atlas_data, atlas_data_size); + if (!load_success) { + ERROR_LOG(Log::G3D, "Failed to load %s - graphics may be broken", filename); + // Stumble along with broken visuals instead of dying... + } + delete[] atlas_data; +} + +AtlasData AtlasProvider(Draw::DrawContext *draw, AtlasChoice atlas) { + switch (atlas) { + case AtlasChoice::General: + { + // Generate the atlas from scratch. + Draw::Texture *tex = GenerateUIAtlas(draw, &ui_atlas); + return {&ui_atlas, tex}; + } + case AtlasChoice::Font: + { + Draw::Texture *fontTexture = nullptr; +#if PPSSPP_PLATFORM(WINDOWS) || PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS) + // Load the smaller ascii font only, like on Android. For debug ui etc. + // NOTE: We better be sure here that the correct metadata is loaded.. + LoadAtlasMetadata(font_atlas, "asciifont_atlas.meta"); + fontTexture = CreateTextureFromFile(draw, "asciifont_atlas.zim", ImageFileType::ZIM, false); + if (!fontTexture) { + WARN_LOG(Log::System, "Failed to load font_atlas.zim or asciifont_atlas.zim"); + } +#else + // Load the full font texture. + LoadAtlasMetadata(font_atlas, "font_atlas.meta"); + fontTexture = CreateTextureFromFile(draw, "font_atlas.zim", ImageFileType::ZIM, false); +#endif + return { + &font_atlas, + fontTexture, + }; + } + default: + return {}; + }; +} diff --git a/UI/UIAtlas.h b/UI/UIAtlas.h new file mode 100644 index 0000000000..045fde8a3d --- /dev/null +++ b/UI/UIAtlas.h @@ -0,0 +1,8 @@ +#pragma once + +#include "Common/UI/Context.h" +#include "Common/Render/TextureAtlas.h" + +const Atlas *GetFontAtlas(); +Atlas *GetUIAtlas(); +AtlasData AtlasProvider(Draw::DrawContext *draw, AtlasChoice atlas); diff --git a/UWP/UI_UWP/UI_UWP.vcxproj b/UWP/UI_UWP/UI_UWP.vcxproj index 7a35d655d7..cadf1d7156 100644 --- a/UWP/UI_UWP/UI_UWP.vcxproj +++ b/UWP/UI_UWP/UI_UWP.vcxproj @@ -129,6 +129,7 @@ + @@ -178,6 +179,7 @@ + Create diff --git a/UWP/UI_UWP/UI_UWP.vcxproj.filters b/UWP/UI_UWP/UI_UWP.vcxproj.filters index 455d5a8899..ea3c1b9038 100644 --- a/UWP/UI_UWP/UI_UWP.vcxproj.filters +++ b/UWP/UI_UWP/UI_UWP.vcxproj.filters @@ -59,6 +59,7 @@ ImDebugger + @@ -119,10 +120,11 @@ ImDebugger + {4013cb89-6145-451c-9cb1-d63d01f66bd5} - + \ No newline at end of file diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 29c5ea777c..42afbdb69e 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -933,6 +933,7 @@ LOCAL_SRC_FILES := \ $(SRC)/UI/ProfilerDraw.cpp \ $(SRC)/UI/NativeApp.cpp \ $(SRC)/UI/Theme.cpp \ + $(SRC)/UI/UIAtlas.cpp \ $(SRC)/UI/CustomButtonMappingScreen.cpp \ $(SRC)/UI/RetroAchievementScreens.cpp