Skip to content

Commit

Permalink
Icon cache for UI rendering
Browse files Browse the repository at this point in the history
This will be needed for achievements, and has uses already now.
  • Loading branch information
hrydgard committed Jun 18, 2023
1 parent 20adf2f commit 9f88dbd
Show file tree
Hide file tree
Showing 9 changed files with 271 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Expand Up @@ -759,6 +759,8 @@ add_library(Common STATIC
Common/UI/UI.h
Common/UI/Context.cpp
Common/UI/Context.h
Common/UI/IconCache.cpp
Common/UI/IconCache.h
Common/UI/UIScreen.cpp
Common/UI/UIScreen.h
Common/UI/Tween.cpp
Expand Down
2 changes: 2 additions & 0 deletions Common/Common.vcxproj
Expand Up @@ -576,6 +576,7 @@
<ClInclude Include="TimeUtil.h" />
<ClInclude Include="UI\AsyncImageFileView.h" />
<ClInclude Include="UI\Context.h" />
<ClInclude Include="UI\IconCache.h" />
<ClInclude Include="UI\PopupScreens.h" />
<ClInclude Include="UI\Root.h" />
<ClInclude Include="UI\Screen.h" />
Expand Down Expand Up @@ -1029,6 +1030,7 @@
<ClCompile Include="TimeUtil.cpp" />
<ClCompile Include="UI\AsyncImageFileView.cpp" />
<ClCompile Include="UI\Context.cpp" />
<ClCompile Include="UI\IconCache.cpp" />
<ClCompile Include="UI\PopupScreens.cpp" />
<ClCompile Include="UI\Root.cpp" />
<ClCompile Include="UI\Screen.cpp" />
Expand Down
6 changes: 6 additions & 0 deletions Common/Common.vcxproj.filters
Expand Up @@ -506,6 +506,9 @@
<ClInclude Include="GPU\MiscTypes.h">
<Filter>GPU</Filter>
</ClInclude>
<ClInclude Include="UI\IconCache.h">
<Filter>UI</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="ABI.cpp" />
Expand Down Expand Up @@ -947,6 +950,9 @@
<ClCompile Include="GPU\GPUBackendCommon.cpp">
<Filter>GPU</Filter>
</ClCompile>
<ClCompile Include="UI\IconCache.cpp">
<Filter>UI</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="Crypto">
Expand Down
202 changes: 202 additions & 0 deletions Common/UI/IconCache.cpp
@@ -0,0 +1,202 @@
#include "Common/UI/IconCache.h"
#include "Common/UI/Context.h"
#include "Common/TimeUtil.h"
#include "Common/Data/Format/PNGLoad.h"
#include "Common/Log.h"

#define ICON_CACHE_VERSION 1

#define MK_FOURCC(str) (str[0] | ((uint8_t)str[1] << 8) | ((uint8_t)str[2] << 16) | ((uint8_t)str[3] << 24))

const uint32_t ICON_CACHE_MAGIC = MK_FOURCC("pICN");

IconCache g_iconCache;

struct DiskCacheHeader {
uint32_t magic;
uint32_t version;
uint32_t entryCount;
};

struct DiskCacheEntry {
uint32_t keyLen;
uint32_t dataLen;
IconFormat format;
double insertedTimestamp;
};

void IconCache::SaveToFile(FILE *file) {
Decimate();

std::unique_lock<std::mutex> lock(lock_);

DiskCacheHeader header;
header.magic = ICON_CACHE_MAGIC;
header.version = ICON_CACHE_VERSION;
header.entryCount = 0; // (uint32_t)cache_.size();

fwrite(&header, 1, sizeof(header), file);

return;

for (auto &iter : cache_) {
DiskCacheEntry entryHeader;
entryHeader.keyLen = (uint32_t)iter.first.size();
entryHeader.dataLen = (uint32_t)iter.second.data.size();
entryHeader.format = iter.second.format;
entryHeader.insertedTimestamp = iter.second.insertedTimeStamp;
fwrite(&entryHeader, 1, sizeof(entryHeader), file);
fwrite(iter.first.c_str(), 1, iter.first.size(), file);
fwrite(iter.second.data.data(), 1, iter.second.data.size(), file);
}
}

bool IconCache::LoadFromFile(FILE *file) {
std::unique_lock<std::mutex> lock(lock_);

DiskCacheHeader header;
if (fread(&header, 1, sizeof(header), file) != sizeof(DiskCacheHeader)) {
return false;
}
if (header.magic != ICON_CACHE_MAGIC || header.version != ICON_CACHE_VERSION) {
return false;
}

double now = time_now_d();

for (uint32_t i = 0; i < header.entryCount; i++) {
DiskCacheEntry entryHeader;
if (fread(&entryHeader, 1, sizeof(entryHeader), file) != sizeof(entryHeader)) {
break;
}

std::string key;
key.resize(entryHeader.keyLen, 0);
if (entryHeader.keyLen > 0x1000) {
// Let's say this is invalid, probably a corrupted file.
break;
}

fread(&key[0], 1, entryHeader.keyLen, file);

// Check if we already have the entry somehow.
if (cache_.find(key) != cache_.end()) {
// Seek past the data and go to the next entry.
fseek(file, entryHeader.dataLen, SEEK_CUR);
continue;
}

std::string data;
data.resize(entryHeader.dataLen);
fread(&data[0], 1, entryHeader.dataLen, file);

Entry entry{};
entry.data = data;
entry.format = entryHeader.format;
entry.insertedTimeStamp = entryHeader.insertedTimestamp;
entry.usedTimeStamp = now;
cache_.insert(std::pair<std::string, Entry>(key, entry));
}

return true;
}

void IconCache::ClearTextures() {
std::unique_lock<std::mutex> lock(lock_);
for (auto &iter : cache_) {
iter.second.texture->Release();
}
}

void IconCache::ClearData() {
ClearTextures();
std::unique_lock<std::mutex> lock(lock_);
cache_.clear();
}

void IconCache::Decimate() {
std::unique_lock<std::mutex> lock(lock_);

}

bool IconCache::InsertIcon(const std::string &key, IconFormat format, std::string &&data) {
std::unique_lock<std::mutex> lock(lock_);

if (key.empty()) {
return false;
}

if (data.empty()) {
ERROR_LOG(G3D, "Can't insert empty data into icon cache");
return false;
}

if (cache_.find(key) != cache_.end()) {
// Already have this entry.
return false;
}

double now = time_now_d();
cache_.emplace(key, Entry{ std::move(data), format, nullptr, now, now, false });
return true;
}

bool IconCache::BindIconTexture(UIContext *context, const std::string &key) {
std::unique_lock<std::mutex> lock(lock_);
auto iter = cache_.find(key);
if (iter == cache_.end()) {
// Don't have this entry.
return false;
}

if (iter->second.texture) {
context->GetDrawContext()->BindTexture(0, iter->second.texture);
iter->second.usedTimeStamp = time_now_d();
return true;
}

if (iter->second.badData) {
return false;
}

// OK, don't have a texture. Upload it!
int width = 0;
int height = 0;
Draw::DataFormat dataFormat;
unsigned char *buffer = nullptr;

switch (iter->second.format) {
case IconFormat::PNG:
{
int result = pngLoadPtr((const unsigned char *)iter->second.data.data(), iter->second.data.size(), &width,
&height, &buffer);

if (result != 1) {
ERROR_LOG(G3D, "IconCache: Failed to load png (%d bytes) for key %s", (int)iter->second.data.size(), key.c_str());
iter->second.badData = true;
return false;
}
dataFormat = Draw::DataFormat::R8G8B8A8_UNORM;
break;
}
default:
return false;
}

Draw::TextureDesc iconDesc{};
iconDesc.width = width;
iconDesc.height = height;
iconDesc.depth = 1;
iconDesc.initData.push_back((const uint8_t *)buffer);
iconDesc.mipLevels = 1;
iconDesc.swizzle = Draw::TextureSwizzle::DEFAULT;
iconDesc.generateMips = false;
iconDesc.tag = key.c_str();

Draw::Texture *texture = context->GetDrawContext()->CreateTexture(iconDesc);
iter->second.texture = texture;

free(buffer);

return true;
}
49 changes: 49 additions & 0 deletions Common/UI/IconCache.h
@@ -0,0 +1,49 @@
#pragma once

#include <map>
#include <string>
#include <cstdio>
#include <mutex>
#include <cstdint>

#include "Common/GPU/thin3d.h"

class UIContext;

enum class IconFormat : uint32_t {
PNG,
};

// TODO: Possibly make this smarter and use instead of ManagedTexture?

class IconCache {
public:
bool BindIconTexture(UIContext *context, const std::string &key);

// It's okay to call this from any thread.
bool InsertIcon(const std::string &key, IconFormat format, std::string &&pngData);

void SaveToFile(FILE *file);
bool LoadFromFile(FILE *file);

void ClearTextures();
void ClearData();

private:
struct Entry {
std::string data;
IconFormat format;
Draw::Texture *texture;
double insertedTimeStamp;
double usedTimeStamp;
bool badData;
};

void Decimate();

std::map<std::string, Entry> cache_;

std::mutex lock_;
};

extern IconCache g_iconCache;
2 changes: 2 additions & 0 deletions UWP/CommonUWP/CommonUWP.vcxproj
Expand Up @@ -401,6 +401,7 @@
<ClInclude Include="..\..\Common\TimeUtil.h" />
<ClInclude Include="..\..\Common\UI\AsyncImageFileView.h" />
<ClInclude Include="..\..\Common\UI\Context.h" />
<ClInclude Include="..\..\Common\UI\IconCache.h" />
<ClInclude Include="..\..\Common\UI\PopupScreens.h" />
<ClInclude Include="..\..\Common\UI\Root.h" />
<ClInclude Include="..\..\Common\UI\Screen.h" />
Expand Down Expand Up @@ -535,6 +536,7 @@
<ClCompile Include="..\..\Common\TimeUtil.cpp" />
<ClCompile Include="..\..\Common\UI\AsyncImageFileView.cpp" />
<ClCompile Include="..\..\Common\UI\Context.cpp" />
<ClCompile Include="..\..\Common\UI\IconCache.cpp" />
<ClCompile Include="..\..\Common\UI\PopupScreens.cpp" />
<ClCompile Include="..\..\Common\UI\Root.cpp" />
<ClCompile Include="..\..\Common\UI\Screen.cpp" />
Expand Down
6 changes: 6 additions & 0 deletions UWP/CommonUWP/CommonUWP.vcxproj.filters
Expand Up @@ -438,6 +438,9 @@
<ClCompile Include="..\..\Common\GPU\GPUBackendCommon.cpp">
<Filter>GPU</Filter>
</ClCompile>
<ClCompile Include="..\..\Common\UI\IconCache.cpp">
<Filter>UI</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="targetver.h" />
Expand Down Expand Up @@ -829,6 +832,9 @@
<ClInclude Include="..\..\Common\GPU\GPUBackendCommon.h">
<Filter>GPU</Filter>
</ClInclude>
<ClInclude Include="..\..\Common\UI\IconCache.h">
<Filter>UI</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="..\..\Common\Math\fast\fast_matrix_neon.S">
Expand Down
1 change: 1 addition & 0 deletions android/jni/Android.mk
Expand Up @@ -228,6 +228,7 @@ EXEC_AND_LIB_FILES := \
$(SRC)/Common/UI/Context.cpp \
$(SRC)/Common/UI/UIScreen.cpp \
$(SRC)/Common/UI/Tween.cpp \
$(SRC)/Common/UI/IconCache.cpp \
$(SRC)/Common/UI/View.cpp \
$(SRC)/Common/UI/ViewGroup.cpp \
$(SRC)/Common/UI/ScrollView.cpp \
Expand Down
1 change: 1 addition & 0 deletions libretro/Makefile.common
Expand Up @@ -340,6 +340,7 @@ SOURCES_CXX += \
$(COMMONDIR)/UI/Context.cpp \
$(COMMONDIR)/UI/UIScreen.cpp \
$(COMMONDIR)/UI/Tween.cpp \
$(COMMONDIR)/UI/IconCache.cpp \
$(COMMONDIR)/UI/View.cpp \
$(COMMONDIR)/UI/ViewGroup.cpp \
$(COMMONDIR)/UI/ScrollView.cpp \
Expand Down

0 comments on commit 9f88dbd

Please sign in to comment.