use oss-nvjpg for loading jpeg images (homebrew, games and themezer).
slightly faster loading on avg compared to stbi.
This commit is contained in:
31
README.md
31
README.md
@@ -4,7 +4,7 @@ A homebrew menu for the Nintendo Switch.
|
|||||||
|
|
||||||
[See the GBATemp thread for more details / discussion](https://gbatemp.net/threads/sphaira-hbmenu-replacement.664523/).
|
[See the GBATemp thread for more details / discussion](https://gbatemp.net/threads/sphaira-hbmenu-replacement.664523/).
|
||||||
|
|
||||||
[We have now have a Discord server!](https://discord.gg/8vZBsrprEc). Please use the issues tab to report bugs, as it is much easier for me to track.
|
[We have now have a Discord server!](https://discord.gg/8vZBsrprEc) Please use the issues tab to report bugs, as it is much easier for me to track.
|
||||||
|
|
||||||
## Showcase
|
## Showcase
|
||||||
|
|
||||||
@@ -86,16 +86,21 @@ The output will be found in `build/MinSizeRel/sphaira.nro`
|
|||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
- borealis
|
- [borealis](https://github.com/natinusala/borealis)
|
||||||
- stb
|
- [stb](https://github.com/nothings/stb)
|
||||||
- yyjson
|
- [yyjson](https://github.com/ibireme/yyjson)
|
||||||
- nx-hbmenu
|
- [nx-hbmenu](https://github.com/switchbrew/nx-hbmenu)
|
||||||
- nx-hbloader
|
- [nx-hbloader](https://github.com/switchbrew/nx-hbloader)
|
||||||
- deko3d-nanovg
|
- [deko3d-nanovg](https://github.com/Adubbz/nanovg-deko3d)
|
||||||
- libpulsar
|
- [libpulsar](https://github.com/p-sam/switch-libpulsar)
|
||||||
- minIni
|
- [minIni](https://github.com/compuphase/minIni)
|
||||||
- GBATemp
|
- [GBATemp](https://gbatemp.net/threads/sphaira-hbmenu-replacement.664523/)
|
||||||
- hb-appstore
|
- [hb-appstore](https://github.com/fortheusers/hb-appstore)
|
||||||
- haze
|
- [haze](https://github.com/Atmosphere-NX/Atmosphere/tree/master/troposphere/haze)
|
||||||
- nxdumptool (for gamecard bin dumping and rsa verify code)
|
- [nxdumptool](https://github.com/DarkMatterCore/nxdumptool) (for gamecard bin dumping and rsa verify code)
|
||||||
|
- [libusbhsfs](https://github.com/DarkMatterCore/libusbhsfs)
|
||||||
|
- [libnxtc](https://github.com/DarkMatterCore/libnxtc)
|
||||||
|
- [oss-nvjpg](https://github.com/averne/oss-nvjpg)
|
||||||
|
- [nsz](https://github.com/nicoboss/nsz)
|
||||||
|
- [themezer](https://themezer.net/)
|
||||||
- Everyone who has contributed to this project!
|
- Everyone who has contributed to this project!
|
||||||
|
|||||||
@@ -202,6 +202,11 @@ FetchContent_Declare(libnxtc
|
|||||||
GIT_TAG v0.0.2
|
GIT_TAG v0.0.2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
FetchContent_Declare(nvjpg
|
||||||
|
GIT_REPOSITORY https://github.com/averne/oss-nvjpg.git
|
||||||
|
GIT_TAG fdcaba8
|
||||||
|
)
|
||||||
|
|
||||||
set(USE_NEW_ZSTD ON)
|
set(USE_NEW_ZSTD ON)
|
||||||
|
|
||||||
set(ZSTD_BUILD_STATIC ON)
|
set(ZSTD_BUILD_STATIC ON)
|
||||||
@@ -255,6 +260,7 @@ FetchContent_MakeAvailable(
|
|||||||
zstd
|
zstd
|
||||||
libusbhsfs
|
libusbhsfs
|
||||||
libnxtc
|
libnxtc
|
||||||
|
nvjpg
|
||||||
)
|
)
|
||||||
|
|
||||||
set(FTPSRV_LIB_BUILD TRUE)
|
set(FTPSRV_LIB_BUILD TRUE)
|
||||||
@@ -304,6 +310,14 @@ add_library(libnxtc
|
|||||||
)
|
)
|
||||||
target_include_directories(libnxtc PUBLIC ${libnxtc_SOURCE_DIR}/include)
|
target_include_directories(libnxtc PUBLIC ${libnxtc_SOURCE_DIR}/include)
|
||||||
|
|
||||||
|
add_library(nvjpg
|
||||||
|
${nvjpg_SOURCE_DIR}/lib/decoder.cpp
|
||||||
|
${nvjpg_SOURCE_DIR}/lib/image.cpp
|
||||||
|
${nvjpg_SOURCE_DIR}/lib/surface.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(nvjpg PUBLIC ${nvjpg_SOURCE_DIR}/include)
|
||||||
|
set_target_properties(nvjpg PROPERTIES CXX_STANDARD 26)
|
||||||
|
|
||||||
find_package(ZLIB REQUIRED)
|
find_package(ZLIB REQUIRED)
|
||||||
find_library(minizip_lib minizip REQUIRED)
|
find_library(minizip_lib minizip REQUIRED)
|
||||||
find_path(minizip_inc minizip REQUIRED)
|
find_path(minizip_inc minizip REQUIRED)
|
||||||
@@ -334,6 +348,7 @@ target_link_libraries(sphaira PRIVATE
|
|||||||
yyjson
|
yyjson
|
||||||
# libusbhsfs
|
# libusbhsfs
|
||||||
libnxtc
|
libnxtc
|
||||||
|
nvjpg
|
||||||
|
|
||||||
${minizip_lib}
|
${minizip_lib}
|
||||||
ZLIB::ZLIB
|
ZLIB::ZLIB
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "fs.hpp"
|
#include "fs.hpp"
|
||||||
#include "log.hpp"
|
#include "log.hpp"
|
||||||
|
|
||||||
|
#include <nvjpg.hpp>
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -272,6 +273,8 @@ public:
|
|||||||
|
|
||||||
PLSR_PlayerSoundId m_sound_ids[SoundEffect_MAX]{};
|
PLSR_PlayerSoundId m_sound_ids[SoundEffect_MAX]{};
|
||||||
|
|
||||||
|
nj::Decoder m_decoder;
|
||||||
|
|
||||||
private: // from nanovg decko3d example by adubbz
|
private: // from nanovg decko3d example by adubbz
|
||||||
static constexpr unsigned NumFramebuffers = 2;
|
static constexpr unsigned NumFramebuffers = 2;
|
||||||
static constexpr unsigned StaticCmdSize = 0x1000;
|
static constexpr unsigned StaticCmdSize = 0x1000;
|
||||||
|
|||||||
@@ -12,8 +12,14 @@ struct ImageResult {
|
|||||||
int w, h;
|
int w, h;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto ImageLoadFromMemory(std::span<const u8> data) -> ImageResult;
|
enum ImageFlag {
|
||||||
auto ImageLoadFromFile(const fs::FsPath& file) -> ImageResult;
|
ImageFlag_None = 0,
|
||||||
|
// set this if the image is a jpeg, will use oss-nvjpg to load.
|
||||||
|
ImageFlag_JPEG = 1 << 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
auto ImageLoadFromMemory(std::span<const u8> data, u32 flags = ImageFlag_None) -> ImageResult;
|
||||||
|
auto ImageLoadFromFile(const fs::FsPath& file, u32 flags = ImageFlag_None) -> ImageResult;
|
||||||
auto ImageResize(std::span<const u8> data, int inx, int iny, int outx, int outy) -> ImageResult;
|
auto ImageResize(std::span<const u8> data, int inx, int iny, int outx, int outy) -> ImageResult;
|
||||||
auto ImageConvertToJpg(std::span<const u8> data, int x, int y) -> ImageResult;
|
auto ImageConvertToJpg(std::span<const u8> data, int x, int y) -> ImageResult;
|
||||||
|
|
||||||
|
|||||||
@@ -1400,6 +1400,10 @@ App::App(const char* argv0) {
|
|||||||
|
|
||||||
curl::Init();
|
curl::Init();
|
||||||
|
|
||||||
|
// this has to be init before deko3d.
|
||||||
|
nj::initialize();
|
||||||
|
m_decoder.initialize();
|
||||||
|
|
||||||
// get current size of the framebuffer
|
// get current size of the framebuffer
|
||||||
const auto fb = GetFrameBufferSize();
|
const auto fb = GetFrameBufferSize();
|
||||||
s_width = fb.size.x;
|
s_width = fb.size.x;
|
||||||
@@ -1849,6 +1853,7 @@ App::~App() {
|
|||||||
ON_SCOPE_EXIT(appletSetCpuBoostMode(ApmCpuBoostMode_Normal));
|
ON_SCOPE_EXIT(appletSetCpuBoostMode(ApmCpuBoostMode_Normal));
|
||||||
|
|
||||||
log_write("starting to exit\n");
|
log_write("starting to exit\n");
|
||||||
|
TimeStamp ts;
|
||||||
|
|
||||||
i18n::exit();
|
i18n::exit();
|
||||||
curl::Exit();
|
curl::Exit();
|
||||||
@@ -1878,6 +1883,9 @@ App::~App() {
|
|||||||
nvgDeleteDk(this->vg);
|
nvgDeleteDk(this->vg);
|
||||||
this->renderer.reset();
|
this->renderer.reset();
|
||||||
|
|
||||||
|
m_decoder.finalize();
|
||||||
|
nj::finalize();
|
||||||
|
|
||||||
// backup hbmenu if it is not sphaira
|
// backup hbmenu if it is not sphaira
|
||||||
if (App::GetReplaceHbmenuEnable() && !IsHbmenu()) {
|
if (App::GetReplaceHbmenuEnable() && !IsHbmenu()) {
|
||||||
NacpStruct hbmenu_nacp;
|
NacpStruct hbmenu_nacp;
|
||||||
@@ -1950,6 +1958,8 @@ App::~App() {
|
|||||||
usbHsFsExit();
|
usbHsFsExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log_write("\t[EXIT] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
||||||
|
|
||||||
if (App::GetLogEnable()) {
|
if (App::GetLogEnable()) {
|
||||||
log_write("closing log\n");
|
log_write("closing log\n");
|
||||||
log_file_exit();
|
log_file_exit();
|
||||||
|
|||||||
@@ -20,7 +20,9 @@
|
|||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
|
|
||||||
|
#include "app.hpp"
|
||||||
#include "log.hpp"
|
#include "log.hpp"
|
||||||
|
#include <nvjpg.hpp>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
namespace sphaira {
|
namespace sphaira {
|
||||||
@@ -30,7 +32,6 @@ constexpr int BPP = 4;
|
|||||||
|
|
||||||
auto ImageLoadInternal(stbi_uc* image_data, int x, int y) -> ImageResult {
|
auto ImageLoadInternal(stbi_uc* image_data, int x, int y) -> ImageResult {
|
||||||
if (image_data) {
|
if (image_data) {
|
||||||
log_write("loaded image: w: %d h: %d\n", x, y);
|
|
||||||
ImageResult result{};
|
ImageResult result{};
|
||||||
result.data.resize(x*y*BPP);
|
result.data.resize(x*y*BPP);
|
||||||
result.w = x;
|
result.w = x;
|
||||||
@@ -44,18 +45,64 @@ auto ImageLoadInternal(stbi_uc* image_data, int x, int y) -> ImageResult {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto ImageLoadInternal(nj::Image&& image) -> ImageResult {
|
||||||
|
if (!image.is_valid() || image.parse()) {
|
||||||
|
log_write("[NVJPG] failed to parse image\n");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
nj::Surface surf{image.width, image.height};
|
||||||
|
if (surf.allocate()) {
|
||||||
|
log_write("[NVJPG] failed to allocate surf\n");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (R_FAILED(App::GetApp()->m_decoder.render(image, surf, 255))) {
|
||||||
|
log_write("[NVJPG] failed to render\n");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (R_FAILED(App::GetApp()->m_decoder.wait(surf))) {
|
||||||
|
log_write("[NVJPG] failed to wait\n");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageResult result{};
|
||||||
|
result.w = image.width;
|
||||||
|
result.h = image.height;
|
||||||
|
result.data.resize(surf.size());
|
||||||
|
std::memcpy(result.data.data(), surf.data(), result.data.size());
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
auto ImageLoadFromMemory(std::span<const u8> data) -> ImageResult {
|
auto ImageLoadFromMemory(std::span<const u8> data, u32 flags) -> ImageResult {
|
||||||
|
if (flags & ImageFlag_JPEG) {
|
||||||
|
auto shared_vec = std::make_shared<std::vector<u8>>(data.size());
|
||||||
|
std::memcpy(shared_vec->data(), data.data(), shared_vec->size());
|
||||||
|
// don't make const as it prevents RTO.
|
||||||
|
auto result = ImageLoadInternal(nj::Image{shared_vec});
|
||||||
|
// if it failed, try again but without using oss-jpg.
|
||||||
|
return result.data.empty() ? ImageLoadFromMemory(data, 0) : result;
|
||||||
|
} else {
|
||||||
int x, y, channels;
|
int x, y, channels;
|
||||||
return ImageLoadInternal(stbi_load_from_memory(data.data(), data.size(), &x, &y, &channels, BPP), x, y);
|
return ImageLoadInternal(stbi_load_from_memory(data.data(), data.size(), &x, &y, &channels, BPP), x, y);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto ImageLoadFromFile(const fs::FsPath& file) -> ImageResult {
|
auto ImageLoadFromFile(const fs::FsPath& file, u32 flags) -> ImageResult {
|
||||||
log_write("doing file load\n");
|
if (flags & ImageFlag_JPEG) {
|
||||||
|
// don't make const as it prevents RTO.
|
||||||
|
auto result = ImageLoadInternal(nj::Image{file});
|
||||||
|
// if it failed, try again but without using oss-jpg.
|
||||||
|
return result.data.empty() ? ImageLoadFromFile(file, 0) : result;
|
||||||
|
} else {
|
||||||
int x, y, channels;
|
int x, y, channels;
|
||||||
return ImageLoadInternal(stbi_load(file, &x, &y, &channels, BPP), x, y);
|
return ImageLoadInternal(stbi_load(file, &x, &y, &channels, BPP), x, y);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto ImageResize(std::span<const u8> data, int inx, int iny, int outx, int outy) -> ImageResult {
|
auto ImageResize(std::span<const u8> data, int inx, int iny, int outx, int outy) -> ImageResult {
|
||||||
log_write("doing resize inx: %d iny: %d outx: %d outy: %d\n", inx, iny, outx, outy);
|
log_write("doing resize inx: %d iny: %d outx: %d outy: %d\n", inx, iny, outx, outy);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "dumper.hpp"
|
#include "dumper.hpp"
|
||||||
#include "defines.hpp"
|
#include "defines.hpp"
|
||||||
#include "i18n.hpp"
|
#include "i18n.hpp"
|
||||||
|
#include "image.hpp"
|
||||||
|
|
||||||
#include "ui/menus/game_menu.hpp"
|
#include "ui/menus/game_menu.hpp"
|
||||||
#include "ui/sidebar.hpp"
|
#include "ui/sidebar.hpp"
|
||||||
@@ -318,12 +319,16 @@ void FakeNacpEntry(ThreadResultData& e) {
|
|||||||
|
|
||||||
bool LoadControlImage(Entry& e) {
|
bool LoadControlImage(Entry& e) {
|
||||||
if (!e.image && e.control) {
|
if (!e.image && e.control) {
|
||||||
|
ON_SCOPE_EXIT(e.control.reset());
|
||||||
|
|
||||||
TimeStamp ts;
|
TimeStamp ts;
|
||||||
e.image = nvgCreateImageMem(App::GetVg(), 0, e.control->icon, e.jpeg_size);
|
const auto image = ImageLoadFromMemory({e.control->icon, e.jpeg_size}, ImageFlag_JPEG);
|
||||||
e.control.reset();
|
if (!image.data.empty()) {
|
||||||
log_write("\t\t[image load] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
e.image = nvgCreateImageRGBA(App::GetVg(), image.w, image.h, 0, image.data.data());
|
||||||
|
log_write("\t[image load] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "i18n.hpp"
|
#include "i18n.hpp"
|
||||||
#include "download.hpp"
|
#include "download.hpp"
|
||||||
#include "dumper.hpp"
|
#include "dumper.hpp"
|
||||||
|
#include "image.hpp"
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -965,7 +966,13 @@ void Menu::OnChangeIndex(s64 new_index) {
|
|||||||
|
|
||||||
const auto& e = m_entries[m_entry_index];
|
const auto& e = m_entries[m_entry_index];
|
||||||
const auto jpeg_size = e.control_size - sizeof(NacpStruct);
|
const auto jpeg_size = e.control_size - sizeof(NacpStruct);
|
||||||
m_icon = nvgCreateImageMem(App::GetVg(), 0, e.control->icon, jpeg_size);
|
|
||||||
|
TimeStamp ts;
|
||||||
|
const auto image = ImageLoadFromMemory({e.control->icon, jpeg_size}, ImageFlag_JPEG);
|
||||||
|
if (!image.data.empty()) {
|
||||||
|
m_icon = nvgCreateImageRGBA(App::GetVg(), image.w, image.h, 0, image.data.data());
|
||||||
|
log_write("\t[image load] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "owo.hpp"
|
#include "owo.hpp"
|
||||||
#include "defines.hpp"
|
#include "defines.hpp"
|
||||||
#include "i18n.hpp"
|
#include "i18n.hpp"
|
||||||
|
#include "image.hpp"
|
||||||
|
|
||||||
#include <minIni.h>
|
#include <minIni.h>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -175,9 +176,17 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
|||||||
// really, switch-tools should handle this by resizing the image before
|
// really, switch-tools should handle this by resizing the image before
|
||||||
// adding it to the nro, as well as validate its a valid jpeg.
|
// adding it to the nro, as well as validate its a valid jpeg.
|
||||||
const auto icon = nro_get_icon(e.path, e.icon_size, e.icon_offset);
|
const auto icon = nro_get_icon(e.path, e.icon_size, e.icon_offset);
|
||||||
|
TimeStamp ts;
|
||||||
if (!icon.empty()) {
|
if (!icon.empty()) {
|
||||||
e.image = nvgCreateImageMem(vg, 0, icon.data(), icon.size());
|
const auto image = ImageLoadFromMemory(icon, ImageFlag_JPEG);
|
||||||
|
if (!image.data.empty()) {
|
||||||
|
e.image = nvgCreateImageRGBA(vg, image.w, image.h, 0, image.data.data());
|
||||||
|
log_write("\t[image load] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
||||||
image_load_count++;
|
image_load_count++;
|
||||||
|
} else {
|
||||||
|
// prevent loading of this icon again as it's already failed.
|
||||||
|
e.icon_offset = e.icon_size = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "swkbd.hpp"
|
#include "swkbd.hpp"
|
||||||
#include "i18n.hpp"
|
#include "i18n.hpp"
|
||||||
#include "threaded_file_transfer.hpp"
|
#include "threaded_file_transfer.hpp"
|
||||||
|
#include "image.hpp"
|
||||||
|
|
||||||
#include <minIni.h>
|
#include <minIni.h>
|
||||||
#include <stb_image.h>
|
#include <stb_image.h>
|
||||||
@@ -134,19 +135,14 @@ auto loadThemeImage(ThemeEntry& e) -> bool {
|
|||||||
}
|
}
|
||||||
auto vg = App::GetVg();
|
auto vg = App::GetVg();
|
||||||
|
|
||||||
fs::FsNativeSd fs;
|
|
||||||
std::vector<u8> image_buf;
|
|
||||||
|
|
||||||
const auto path = apiBuildIconCache(e);
|
const auto path = apiBuildIconCache(e);
|
||||||
if (R_FAILED(fs.read_entire_file(path, image_buf))) {
|
TimeStamp ts;
|
||||||
log_write("failed to load image from file: %s\n", path.s);
|
const auto data = ImageLoadFromFile(path, ImageFlag_JPEG);
|
||||||
} else {
|
if (!data.data.empty()) {
|
||||||
int channels_in_file;
|
image.w = data.w;
|
||||||
auto buf = stbi_load_from_memory(image_buf.data(), image_buf.size(), &image.w, &image.h, &channels_in_file, 4);
|
image.h = data.h;
|
||||||
if (buf) {
|
image.image = nvgCreateImageRGBA(vg, data.w, data.h, 0, data.data.data());
|
||||||
ON_SCOPE_EXIT(stbi_image_free(buf));
|
log_write("\t[image load] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
||||||
image.image = nvgCreateImageRGBA(vg, image.w, image.h, 0, buf);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!image.image) {
|
if (!image.image) {
|
||||||
|
|||||||
Reference in New Issue
Block a user