Skip to content

Commit

Permalink
Merge pull request #17599 from hrydgard/ui-icon-cache
Browse files Browse the repository at this point in the history
UI: Icon image cache
  • Loading branch information
hrydgard committed Jun 18, 2023
2 parents 20adf2f + 300a64a commit 264c6d7
Show file tree
Hide file tree
Showing 15 changed files with 468 additions and 32 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
1 change: 1 addition & 0 deletions Common/GPU/OpenGL/GLRenderManager.h
Expand Up @@ -259,6 +259,7 @@ class GLRenderManager {
// We pass in width/height here even though it's not strictly needed until we support glTextureStorage
// and then we'll also need formats and stuff.
GLRTexture *CreateTexture(GLenum target, int width, int height, int depth, int numMips) {
_dbg_assert_(target != 0);
GLRInitStep &step = initSteps_.push_uninitialized();
step.stepType = GLRInitStepType::CREATE_TEXTURE;
step.create_texture.texture = new GLRTexture(caps_, width, height, depth, numMips);
Expand Down
6 changes: 5 additions & 1 deletion Common/GPU/OpenGL/thin3d_gl.cpp
Expand Up @@ -822,7 +822,7 @@ InputLayout *OpenGLContext::CreateInputLayout(const InputLayoutDesc &desc) {
return fmt;
}

GLuint TypeToTarget(TextureType type) {
static GLuint TypeToTarget(TextureType type) {
switch (type) {
#ifndef USING_GLES2
case TextureType::LINEAR1D: return GL_TEXTURE_1D;
Expand Down Expand Up @@ -875,6 +875,10 @@ class OpenGLTexture : public Texture {
};

OpenGLTexture::OpenGLTexture(GLRenderManager *render, const TextureDesc &desc) : render_(render) {
_dbg_assert_(desc.format != Draw::DataFormat::UNDEFINED);
_dbg_assert_(desc.width > 0 && desc.height > 0 && desc.depth > 0);
_dbg_assert_(type_ != Draw::TextureType::UNKNOWN);

generatedMips_ = false;
generateMips_ = desc.generateMips;
width_ = desc.width;
Expand Down
8 changes: 4 additions & 4 deletions Common/GPU/thin3d.h
Expand Up @@ -463,10 +463,10 @@ class Buffer : public RefCountedObject {
class Texture : public RefCountedObject {
public:
Texture() : RefCountedObject("Texture") {}
int Width() { return width_; }
int Height() { return height_; }
int Depth() { return depth_; }
DataFormat Format() { return format_; }
int Width() const { return width_; }
int Height() const { return height_; }
int Depth() const { return depth_; }
DataFormat Format() const { return format_; }

protected:
int width_ = -1, height_ = -1, depth_ = -1;
Expand Down
282 changes: 282 additions & 0 deletions Common/UI/IconCache.cpp
@@ -0,0 +1,282 @@
#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) {
std::unique_lock<std::mutex> lock(lock_);

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

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

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_) {
if (iter.second.texture) {
iter.second.texture->Release();
iter.second.texture = nullptr;
}
}
}

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

void IconCache::FrameUpdate() {
std::unique_lock<std::mutex> lock(lock_);
// Remove old textures after a while.
double now = time_now_d();
if (now > lastUpdate_ + 2.0) {
for (auto &iter : cache_) {
double useAge = now - iter.second.usedTimeStamp;
if (useAge > 5.0) {
// Release the texture after a few seconds of no use.
// Still, keep the png data loaded, it's small.
if (iter.second.texture) {
iter.second.texture->Release();
iter.second.texture = nullptr;
}
}
}
lastUpdate_ = now;
}
}

bool IconCache::GetDimensions(const std::string &key, int *width, int *height) {
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) {
// TODO: Store the width/height in the cache.
*width = iter->second.texture->Width();
*height = iter->second.texture->Height();
return true;
} else {
return false;
}
}

bool IconCache::Contains(const std::string &key) {
std::unique_lock<std::mutex> lock(lock_);
return cache_.find(key) != cache_.end();
}

bool IconCache::MarkPending(const std::string &key) {
std::unique_lock<std::mutex> lock(lock_);
if (cache_.find(key) != cache_.end()) {
return false;
}
if (pending_.find(key) != pending_.end()) {
return false;
}
pending_.insert(key);
return true;
}

void IconCache::Cancel(const std::string &key) {
std::unique_lock<std::mutex> lock(lock_);
pending_.erase(key);
}

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;
}

if (data.size() > 1024 * 512) {
WARN_LOG(G3D, "Unusually large icon inserted in icon cache: %s (%d bytes)", key.c_str(), (int)data.size());
}

pending_.erase(key);

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

Draw::Texture *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 nullptr;
}

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

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

// 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 nullptr;
}
dataFormat = Draw::DataFormat::R8G8B8A8_UNORM;
break;
}
default:
return nullptr;
}

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();
iconDesc.format = dataFormat;
iconDesc.type = Draw::TextureType::LINEAR2D;

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

free(buffer);

return texture;
}

IconCacheStats IconCache::GetStats() {
IconCacheStats stats{};

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

for (auto &iter : cache_) {
stats.cachedCount++;
if (iter.second.texture)
stats.textureCount++;
stats.dataSize += iter.second.data.size();
}

stats.pending = pending_.size();

return stats;
}

0 comments on commit 264c6d7

Please sign in to comment.