chore: sync workspace state
This commit is contained in:
@@ -6,9 +6,13 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cwctype>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <wrl/client.h>
|
||||
|
||||
#include <stb_image.h>
|
||||
@@ -36,12 +40,26 @@ struct BuiltInTexture {
|
||||
|
||||
struct BuiltInIconState {
|
||||
ImGuiBackendBridge* backend = nullptr;
|
||||
ID3D12Device* device = nullptr;
|
||||
ID3D12CommandQueue* commandQueue = nullptr;
|
||||
BuiltInTexture folder;
|
||||
BuiltInTexture gameObject;
|
||||
BuiltInTexture scene;
|
||||
struct CachedAssetPreview {
|
||||
BuiltInTexture texture;
|
||||
bool loadAttempted = false;
|
||||
int lastUsedFrame = -1;
|
||||
};
|
||||
std::unordered_map<std::string, CachedAssetPreview> assetPreviews;
|
||||
int lastPreviewBudgetFrame = -1;
|
||||
int previewLoadsThisFrame = 0;
|
||||
};
|
||||
|
||||
BuiltInIconState g_icons;
|
||||
|
||||
constexpr size_t kMaxCachedAssetPreviews = 40;
|
||||
constexpr int kMaxPreviewLoadsPerFrame = 2;
|
||||
|
||||
std::filesystem::path ResolveFolderIconPath() {
|
||||
const std::filesystem::path exeDir(Platform::GetExecutableDirectoryUtf8());
|
||||
return (exeDir / ".." / ".." / "resources" / "Icons" / "folder_icon.png").lexically_normal();
|
||||
@@ -52,6 +70,33 @@ std::filesystem::path ResolveGameObjectIconPath() {
|
||||
return (exeDir / ".." / ".." / "resources" / "Icons" / "gameobject_icon.png").lexically_normal();
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveSceneIconPath() {
|
||||
const std::filesystem::path exeDir(Platform::GetExecutableDirectoryUtf8());
|
||||
return (exeDir / ".." / ".." / "resources" / "Icons" / "scene_icon.png").lexically_normal();
|
||||
}
|
||||
|
||||
std::string MakeAssetPreviewKey(const std::string& filePath) {
|
||||
std::wstring key = std::filesystem::path(Platform::Utf8ToWide(filePath)).lexically_normal().generic_wstring();
|
||||
std::transform(key.begin(), key.end(), key.begin(), ::towlower);
|
||||
return Platform::WideToUtf8(key);
|
||||
}
|
||||
|
||||
bool ReadFileBytes(const std::filesystem::path& filePath, std::vector<stbi_uc>& bytes) {
|
||||
std::ifstream stream(filePath, std::ios::binary | std::ios::ate);
|
||||
if (!stream.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::streamsize size = stream.tellg();
|
||||
if (size <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bytes.resize(static_cast<size_t>(size));
|
||||
stream.seekg(0, std::ios::beg);
|
||||
return stream.read(reinterpret_cast<char*>(bytes.data()), size).good();
|
||||
}
|
||||
|
||||
void ResetTexture(BuiltInTexture& texture) {
|
||||
if (g_icons.backend && texture.cpuHandle.ptr != 0) {
|
||||
g_icons.backend->FreeTextureDescriptor(texture.cpuHandle, texture.gpuHandle);
|
||||
@@ -65,6 +110,13 @@ void ResetTexture(BuiltInTexture& texture) {
|
||||
texture.height = 0;
|
||||
}
|
||||
|
||||
void ResetAssetPreviewCache() {
|
||||
for (auto& entry : g_icons.assetPreviews) {
|
||||
ResetTexture(entry.second.texture);
|
||||
}
|
||||
g_icons.assetPreviews.clear();
|
||||
}
|
||||
|
||||
bool WaitForQueueIdle(ID3D12Device* device, ID3D12CommandQueue* commandQueue) {
|
||||
if (!device || !commandQueue) {
|
||||
return false;
|
||||
@@ -109,10 +161,21 @@ bool LoadTextureFromFile(
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<stbi_uc> fileData;
|
||||
if (!ReadFileBytes(filePath, fileData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int channels = 0;
|
||||
stbi_uc* pixels = stbi_load(filePath.string().c_str(), &width, &height, &channels, STBI_rgb_alpha);
|
||||
stbi_uc* pixels = stbi_load_from_memory(
|
||||
fileData.data(),
|
||||
static_cast<int>(fileData.size()),
|
||||
&width,
|
||||
&height,
|
||||
&channels,
|
||||
STBI_rgb_alpha);
|
||||
if (!pixels || width <= 0 || height <= 0) {
|
||||
if (pixels) {
|
||||
stbi_image_free(pixels);
|
||||
@@ -328,6 +391,76 @@ void DrawBuiltInFileIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2&
|
||||
drawList->AddLine(foldA, foldB, lineColor);
|
||||
}
|
||||
|
||||
BuiltInIconState::CachedAssetPreview* GetOrCreateAssetPreview(const std::string& filePath) {
|
||||
if (!g_icons.backend || !g_icons.device || !g_icons.commandQueue || filePath.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::string key = MakeAssetPreviewKey(filePath);
|
||||
auto [it, inserted] = g_icons.assetPreviews.try_emplace(key);
|
||||
BuiltInIconState::CachedAssetPreview& preview = it->second;
|
||||
preview.lastUsedFrame = ImGui::GetFrameCount();
|
||||
|
||||
if (!inserted && !std::filesystem::exists(std::filesystem::path(Platform::Utf8ToWide(filePath)))) {
|
||||
ResetTexture(preview.texture);
|
||||
preview.loadAttempted = true;
|
||||
return &preview;
|
||||
}
|
||||
|
||||
if (preview.texture.IsValid() || preview.loadAttempted) {
|
||||
return &preview;
|
||||
}
|
||||
|
||||
const int frame = ImGui::GetFrameCount();
|
||||
if (g_icons.lastPreviewBudgetFrame != frame) {
|
||||
g_icons.lastPreviewBudgetFrame = frame;
|
||||
g_icons.previewLoadsThisFrame = 0;
|
||||
}
|
||||
if (g_icons.previewLoadsThisFrame >= kMaxPreviewLoadsPerFrame) {
|
||||
return &preview;
|
||||
}
|
||||
|
||||
preview.loadAttempted = true;
|
||||
++g_icons.previewLoadsThisFrame;
|
||||
LoadTextureFromFile(
|
||||
*g_icons.backend,
|
||||
g_icons.device,
|
||||
g_icons.commandQueue,
|
||||
std::filesystem::path(Platform::Utf8ToWide(filePath)),
|
||||
preview.texture);
|
||||
return &preview;
|
||||
}
|
||||
|
||||
void PruneAssetPreviewCache() {
|
||||
if (g_icons.assetPreviews.size() <= kMaxCachedAssetPreviews) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, int>> candidates;
|
||||
candidates.reserve(g_icons.assetPreviews.size());
|
||||
for (const auto& entry : g_icons.assetPreviews) {
|
||||
candidates.emplace_back(entry.first, entry.second.lastUsedFrame);
|
||||
}
|
||||
|
||||
std::sort(
|
||||
candidates.begin(),
|
||||
candidates.end(),
|
||||
[](const auto& lhs, const auto& rhs) {
|
||||
return lhs.second < rhs.second;
|
||||
});
|
||||
|
||||
size_t removeCount = g_icons.assetPreviews.size() - kMaxCachedAssetPreviews;
|
||||
for (size_t i = 0; i < removeCount && i < candidates.size(); ++i) {
|
||||
auto it = g_icons.assetPreviews.find(candidates[i].first);
|
||||
if (it == g_icons.assetPreviews.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ResetTexture(it->second.texture);
|
||||
g_icons.assetPreviews.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void InitializeBuiltInIcons(
|
||||
@@ -336,14 +469,23 @@ void InitializeBuiltInIcons(
|
||||
ID3D12CommandQueue* commandQueue) {
|
||||
ShutdownBuiltInIcons();
|
||||
g_icons.backend = &backend;
|
||||
g_icons.device = device;
|
||||
g_icons.commandQueue = commandQueue;
|
||||
LoadTextureFromFile(backend, device, commandQueue, ResolveFolderIconPath(), g_icons.folder);
|
||||
LoadTextureFromFile(backend, device, commandQueue, ResolveGameObjectIconPath(), g_icons.gameObject);
|
||||
LoadTextureFromFile(backend, device, commandQueue, ResolveSceneIconPath(), g_icons.scene);
|
||||
}
|
||||
|
||||
void ShutdownBuiltInIcons() {
|
||||
ResetAssetPreviewCache();
|
||||
ResetTexture(g_icons.folder);
|
||||
ResetTexture(g_icons.gameObject);
|
||||
ResetTexture(g_icons.scene);
|
||||
g_icons.backend = nullptr;
|
||||
g_icons.device = nullptr;
|
||||
g_icons.commandQueue = nullptr;
|
||||
g_icons.lastPreviewBudgetFrame = -1;
|
||||
g_icons.previewLoadsThisFrame = 0;
|
||||
}
|
||||
|
||||
void DrawAssetIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& max, AssetIconKind kind) {
|
||||
@@ -367,9 +509,34 @@ void DrawAssetIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& max, A
|
||||
return;
|
||||
}
|
||||
|
||||
if (kind == AssetIconKind::Scene) {
|
||||
if (g_icons.scene.IsValid()) {
|
||||
DrawTextureIcon(drawList, g_icons.scene, min, max);
|
||||
return;
|
||||
}
|
||||
|
||||
DrawBuiltInFileIcon(drawList, min, max);
|
||||
return;
|
||||
}
|
||||
|
||||
DrawBuiltInFileIcon(drawList, min, max);
|
||||
}
|
||||
|
||||
bool DrawTextureAssetPreview(
|
||||
ImDrawList* drawList,
|
||||
const ImVec2& min,
|
||||
const ImVec2& max,
|
||||
const std::string& filePath) {
|
||||
BuiltInIconState::CachedAssetPreview* preview = GetOrCreateAssetPreview(filePath);
|
||||
if (!preview || !preview->texture.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DrawTextureIcon(drawList, preview->texture, min, max);
|
||||
PruneAssetPreviewCache();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
|
||||
Reference in New Issue
Block a user