use oss-nvjpg for loading jpeg images (homebrew, games and themezer).

slightly faster loading on avg compared to stbi.
This commit is contained in:
ITotalJustice
2025-06-02 22:18:38 +01:00
parent 8485ff1e99
commit 4be1d48215
10 changed files with 146 additions and 43 deletions

View File

@@ -1400,6 +1400,10 @@ App::App(const char* argv0) {
curl::Init();
// this has to be init before deko3d.
nj::initialize();
m_decoder.initialize();
// get current size of the framebuffer
const auto fb = GetFrameBufferSize();
s_width = fb.size.x;
@@ -1849,6 +1853,7 @@ App::~App() {
ON_SCOPE_EXIT(appletSetCpuBoostMode(ApmCpuBoostMode_Normal));
log_write("starting to exit\n");
TimeStamp ts;
i18n::exit();
curl::Exit();
@@ -1878,6 +1883,9 @@ App::~App() {
nvgDeleteDk(this->vg);
this->renderer.reset();
m_decoder.finalize();
nj::finalize();
// backup hbmenu if it is not sphaira
if (App::GetReplaceHbmenuEnable() && !IsHbmenu()) {
NacpStruct hbmenu_nacp;
@@ -1950,6 +1958,8 @@ App::~App() {
usbHsFsExit();
}
log_write("\t[EXIT] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
if (App::GetLogEnable()) {
log_write("closing log\n");
log_file_exit();

View File

@@ -20,7 +20,9 @@
#pragma GCC diagnostic pop
#pragma GCC diagnostic pop
#include "app.hpp"
#include "log.hpp"
#include <nvjpg.hpp>
#include <cstring>
namespace sphaira {
@@ -30,7 +32,6 @@ constexpr int BPP = 4;
auto ImageLoadInternal(stbi_uc* image_data, int x, int y) -> ImageResult {
if (image_data) {
log_write("loaded image: w: %d h: %d\n", x, y);
ImageResult result{};
result.data.resize(x*y*BPP);
result.w = x;
@@ -44,17 +45,63 @@ auto ImageLoadInternal(stbi_uc* image_data, int x, int y) -> ImageResult {
return {};
}
} // namespace
auto ImageLoadInternal(nj::Image&& image) -> ImageResult {
if (!image.is_valid() || image.parse()) {
log_write("[NVJPG] failed to parse image\n");
return {};
}
auto ImageLoadFromMemory(std::span<const u8> data) -> ImageResult {
int x, y, channels;
return ImageLoadInternal(stbi_load_from_memory(data.data(), data.size(), &x, &y, &channels, BPP), x, y);
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;
}
auto ImageLoadFromFile(const fs::FsPath& file) -> ImageResult {
log_write("doing file load\n");
int x, y, channels;
return ImageLoadInternal(stbi_load(file, &x, &y, &channels, BPP), x, y);
} // namespace
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;
return ImageLoadInternal(stbi_load_from_memory(data.data(), data.size(), &x, &y, &channels, BPP), x, y);
}
}
auto ImageLoadFromFile(const fs::FsPath& file, u32 flags) -> ImageResult {
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;
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 {

View File

@@ -4,6 +4,7 @@
#include "dumper.hpp"
#include "defines.hpp"
#include "i18n.hpp"
#include "image.hpp"
#include "ui/menus/game_menu.hpp"
#include "ui/sidebar.hpp"
@@ -318,11 +319,15 @@ void FakeNacpEntry(ThreadResultData& e) {
bool LoadControlImage(Entry& e) {
if (!e.image && e.control) {
ON_SCOPE_EXIT(e.control.reset());
TimeStamp ts;
e.image = nvgCreateImageMem(App::GetVg(), 0, e.control->icon, e.jpeg_size);
e.control.reset();
log_write("\t\t[image load] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
return true;
const auto image = ImageLoadFromMemory({e.control->icon, e.jpeg_size}, ImageFlag_JPEG);
if (!image.data.empty()) {
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 false;

View File

@@ -13,6 +13,7 @@
#include "i18n.hpp"
#include "download.hpp"
#include "dumper.hpp"
#include "image.hpp"
#include <cstring>
#include <algorithm>
@@ -965,7 +966,13 @@ void Menu::OnChangeIndex(s64 new_index) {
const auto& e = m_entries[m_entry_index];
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());
}
}
}

View File

@@ -10,6 +10,7 @@
#include "owo.hpp"
#include "defines.hpp"
#include "i18n.hpp"
#include "image.hpp"
#include <minIni.h>
#include <utility>
@@ -175,9 +176,17 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
// really, switch-tools should handle this by resizing the image before
// 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);
TimeStamp ts;
if (!icon.empty()) {
e.image = nvgCreateImageMem(vg, 0, icon.data(), icon.size());
image_load_count++;
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++;
} else {
// prevent loading of this icon again as it's already failed.
e.icon_offset = e.icon_size = 0;
}
}
}
}

View File

@@ -12,6 +12,7 @@
#include "swkbd.hpp"
#include "i18n.hpp"
#include "threaded_file_transfer.hpp"
#include "image.hpp"
#include <minIni.h>
#include <stb_image.h>
@@ -134,19 +135,14 @@ auto loadThemeImage(ThemeEntry& e) -> bool {
}
auto vg = App::GetVg();
fs::FsNativeSd fs;
std::vector<u8> image_buf;
const auto path = apiBuildIconCache(e);
if (R_FAILED(fs.read_entire_file(path, image_buf))) {
log_write("failed to load image from file: %s\n", path.s);
} else {
int channels_in_file;
auto buf = stbi_load_from_memory(image_buf.data(), image_buf.size(), &image.w, &image.h, &channels_in_file, 4);
if (buf) {
ON_SCOPE_EXIT(stbi_image_free(buf));
image.image = nvgCreateImageRGBA(vg, image.w, image.h, 0, buf);
}
TimeStamp ts;
const auto data = ImageLoadFromFile(path, ImageFlag_JPEG);
if (!data.data.empty()) {
image.w = data.w;
image.h = data.h;
image.image = nvgCreateImageRGBA(vg, data.w, data.h, 0, data.data.data());
log_write("\t[image load] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
}
if (!image.image) {