|
|
|
@@ -5,12 +5,16 @@
|
|
|
|
#include "StyleTokens.h"
|
|
|
|
#include "StyleTokens.h"
|
|
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
#include <condition_variable>
|
|
|
|
#include <cstdint>
|
|
|
|
#include <cstdint>
|
|
|
|
#include <cwctype>
|
|
|
|
#include <cwctype>
|
|
|
|
|
|
|
|
#include <deque>
|
|
|
|
#include <cstring>
|
|
|
|
#include <cstring>
|
|
|
|
#include <filesystem>
|
|
|
|
#include <filesystem>
|
|
|
|
#include <fstream>
|
|
|
|
#include <fstream>
|
|
|
|
|
|
|
|
#include <memory>
|
|
|
|
#include <string>
|
|
|
|
#include <string>
|
|
|
|
|
|
|
|
#include <thread>
|
|
|
|
#include <unordered_map>
|
|
|
|
#include <unordered_map>
|
|
|
|
#include <vector>
|
|
|
|
#include <vector>
|
|
|
|
#include <wrl/client.h>
|
|
|
|
#include <wrl/client.h>
|
|
|
|
@@ -38,6 +42,30 @@ struct BuiltInTexture {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct LoadedTexturePixels {
|
|
|
|
|
|
|
|
std::vector<stbi_uc> rgbaPixels;
|
|
|
|
|
|
|
|
int width = 0;
|
|
|
|
|
|
|
|
int height = 0;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct PreviewGpuUpload {
|
|
|
|
|
|
|
|
ComPtr<ID3D12Resource> uploadResource;
|
|
|
|
|
|
|
|
ComPtr<ID3D12CommandAllocator> commandAllocator;
|
|
|
|
|
|
|
|
ComPtr<ID3D12GraphicsCommandList> commandList;
|
|
|
|
|
|
|
|
UINT64 fenceValue = 0;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct PreviewDecodeJob {
|
|
|
|
|
|
|
|
std::string key;
|
|
|
|
|
|
|
|
std::filesystem::path filePath;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct PreviewDecodeResult {
|
|
|
|
|
|
|
|
std::string key;
|
|
|
|
|
|
|
|
LoadedTexturePixels pixels;
|
|
|
|
|
|
|
|
bool success = false;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
struct BuiltInIconState {
|
|
|
|
struct BuiltInIconState {
|
|
|
|
ImGuiBackendBridge* backend = nullptr;
|
|
|
|
ImGuiBackendBridge* backend = nullptr;
|
|
|
|
ID3D12Device* device = nullptr;
|
|
|
|
ID3D12Device* device = nullptr;
|
|
|
|
@@ -47,18 +75,35 @@ struct BuiltInIconState {
|
|
|
|
BuiltInTexture scene;
|
|
|
|
BuiltInTexture scene;
|
|
|
|
struct CachedAssetPreview {
|
|
|
|
struct CachedAssetPreview {
|
|
|
|
BuiltInTexture texture;
|
|
|
|
BuiltInTexture texture;
|
|
|
|
bool loadAttempted = false;
|
|
|
|
std::unique_ptr<LoadedTexturePixels> decodedPixels;
|
|
|
|
|
|
|
|
std::unique_ptr<PreviewGpuUpload> pendingUpload;
|
|
|
|
|
|
|
|
bool decodeQueued = false;
|
|
|
|
|
|
|
|
bool loadFailed = false;
|
|
|
|
int lastUsedFrame = -1;
|
|
|
|
int lastUsedFrame = -1;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
std::unordered_map<std::string, CachedAssetPreview> assetPreviews;
|
|
|
|
std::unordered_map<std::string, CachedAssetPreview> assetPreviews;
|
|
|
|
|
|
|
|
std::vector<std::unique_ptr<PreviewGpuUpload>> pendingIconUploads;
|
|
|
|
|
|
|
|
std::vector<std::thread> previewWorkers;
|
|
|
|
|
|
|
|
std::deque<PreviewDecodeJob> previewDecodeQueue;
|
|
|
|
|
|
|
|
std::deque<PreviewDecodeResult> previewDecodeResults;
|
|
|
|
|
|
|
|
std::mutex previewQueueMutex;
|
|
|
|
|
|
|
|
std::condition_variable previewQueueEvent;
|
|
|
|
|
|
|
|
bool previewWorkersRunning = false;
|
|
|
|
|
|
|
|
size_t pendingPreviewDecodeJobs = 0;
|
|
|
|
|
|
|
|
ComPtr<ID3D12Fence> uploadFence;
|
|
|
|
|
|
|
|
UINT64 nextUploadFenceValue = 1;
|
|
|
|
int lastPreviewBudgetFrame = -1;
|
|
|
|
int lastPreviewBudgetFrame = -1;
|
|
|
|
int previewLoadsThisFrame = 0;
|
|
|
|
int previewLoadsThisFrame = 0;
|
|
|
|
|
|
|
|
int lastMaintenanceFrame = -1;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
BuiltInIconState g_icons;
|
|
|
|
BuiltInIconState g_icons;
|
|
|
|
|
|
|
|
|
|
|
|
constexpr size_t kMaxCachedAssetPreviews = 40;
|
|
|
|
constexpr size_t kMaxCachedAssetPreviews = 40;
|
|
|
|
constexpr int kMaxPreviewLoadsPerFrame = 2;
|
|
|
|
constexpr int kMaxPreviewLoadsPerFrame = 64;
|
|
|
|
|
|
|
|
constexpr size_t kMaxQueuedPreviewDecodeJobs = 64;
|
|
|
|
|
|
|
|
constexpr int kMaxPreviewThumbnailExtent = 192;
|
|
|
|
|
|
|
|
constexpr size_t kPreviewWorkerCount = 2;
|
|
|
|
|
|
|
|
|
|
|
|
std::filesystem::path ResolveFolderIconPath() {
|
|
|
|
std::filesystem::path ResolveFolderIconPath() {
|
|
|
|
const std::filesystem::path exeDir(Platform::GetExecutableDirectoryUtf8());
|
|
|
|
const std::filesystem::path exeDir(Platform::GetExecutableDirectoryUtf8());
|
|
|
|
@@ -97,6 +142,90 @@ bool ReadFileBytes(const std::filesystem::path& filePath, std::vector<stbi_uc>&
|
|
|
|
return stream.read(reinterpret_cast<char*>(bytes.data()), size).good();
|
|
|
|
return stream.read(reinterpret_cast<char*>(bytes.data()), size).good();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void ResizeRgbaImageBilinear(
|
|
|
|
|
|
|
|
const stbi_uc* srcPixels,
|
|
|
|
|
|
|
|
int srcWidth,
|
|
|
|
|
|
|
|
int srcHeight,
|
|
|
|
|
|
|
|
int dstWidth,
|
|
|
|
|
|
|
|
int dstHeight,
|
|
|
|
|
|
|
|
std::vector<stbi_uc>& dstPixels) {
|
|
|
|
|
|
|
|
dstPixels.resize(static_cast<size_t>(dstWidth) * static_cast<size_t>(dstHeight) * 4u);
|
|
|
|
|
|
|
|
if (!srcPixels || srcWidth <= 0 || srcHeight <= 0 || dstWidth <= 0 || dstHeight <= 0) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int dstY = 0; dstY < dstHeight; ++dstY) {
|
|
|
|
|
|
|
|
const float srcY = dstHeight > 1
|
|
|
|
|
|
|
|
? static_cast<float>(dstY) * static_cast<float>(srcHeight - 1) / static_cast<float>(dstHeight - 1)
|
|
|
|
|
|
|
|
: 0.0f;
|
|
|
|
|
|
|
|
const int y0 = static_cast<int>(srcY);
|
|
|
|
|
|
|
|
const int y1 = (std::min)(y0 + 1, srcHeight - 1);
|
|
|
|
|
|
|
|
const float ty = srcY - static_cast<float>(y0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int dstX = 0; dstX < dstWidth; ++dstX) {
|
|
|
|
|
|
|
|
const float srcX = dstWidth > 1
|
|
|
|
|
|
|
|
? static_cast<float>(dstX) * static_cast<float>(srcWidth - 1) / static_cast<float>(dstWidth - 1)
|
|
|
|
|
|
|
|
: 0.0f;
|
|
|
|
|
|
|
|
const int x0 = static_cast<int>(srcX);
|
|
|
|
|
|
|
|
const int x1 = (std::min)(x0 + 1, srcWidth - 1);
|
|
|
|
|
|
|
|
const float tx = srcX - static_cast<float>(x0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const size_t dstOffset =
|
|
|
|
|
|
|
|
(static_cast<size_t>(dstY) * static_cast<size_t>(dstWidth) + static_cast<size_t>(dstX)) * 4u;
|
|
|
|
|
|
|
|
const size_t srcOffset00 =
|
|
|
|
|
|
|
|
(static_cast<size_t>(y0) * static_cast<size_t>(srcWidth) + static_cast<size_t>(x0)) * 4u;
|
|
|
|
|
|
|
|
const size_t srcOffset10 =
|
|
|
|
|
|
|
|
(static_cast<size_t>(y0) * static_cast<size_t>(srcWidth) + static_cast<size_t>(x1)) * 4u;
|
|
|
|
|
|
|
|
const size_t srcOffset01 =
|
|
|
|
|
|
|
|
(static_cast<size_t>(y1) * static_cast<size_t>(srcWidth) + static_cast<size_t>(x0)) * 4u;
|
|
|
|
|
|
|
|
const size_t srcOffset11 =
|
|
|
|
|
|
|
|
(static_cast<size_t>(y1) * static_cast<size_t>(srcWidth) + static_cast<size_t>(x1)) * 4u;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (size_t channel = 0; channel < 4u; ++channel) {
|
|
|
|
|
|
|
|
const float top =
|
|
|
|
|
|
|
|
static_cast<float>(srcPixels[srcOffset00 + channel]) * (1.0f - tx) +
|
|
|
|
|
|
|
|
static_cast<float>(srcPixels[srcOffset10 + channel]) * tx;
|
|
|
|
|
|
|
|
const float bottom =
|
|
|
|
|
|
|
|
static_cast<float>(srcPixels[srcOffset01 + channel]) * (1.0f - tx) +
|
|
|
|
|
|
|
|
static_cast<float>(srcPixels[srcOffset11 + channel]) * tx;
|
|
|
|
|
|
|
|
dstPixels[dstOffset + channel] = static_cast<stbi_uc>(top * (1.0f - ty) + bottom * ty + 0.5f);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool DownscalePreviewTextureIfNeeded(LoadedTexturePixels& texturePixels) {
|
|
|
|
|
|
|
|
if (texturePixels.width <= 0 || texturePixels.height <= 0 || texturePixels.rgbaPixels.empty()) {
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const int maxExtent = (std::max)(texturePixels.width, texturePixels.height);
|
|
|
|
|
|
|
|
if (maxExtent <= kMaxPreviewThumbnailExtent) {
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const float scale = static_cast<float>(kMaxPreviewThumbnailExtent) / static_cast<float>(maxExtent);
|
|
|
|
|
|
|
|
const int dstWidth = (std::max)(1, static_cast<int>(static_cast<float>(texturePixels.width) * scale + 0.5f));
|
|
|
|
|
|
|
|
const int dstHeight = (std::max)(1, static_cast<int>(static_cast<float>(texturePixels.height) * scale + 0.5f));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<stbi_uc> resizedPixels;
|
|
|
|
|
|
|
|
ResizeRgbaImageBilinear(
|
|
|
|
|
|
|
|
texturePixels.rgbaPixels.data(),
|
|
|
|
|
|
|
|
texturePixels.width,
|
|
|
|
|
|
|
|
texturePixels.height,
|
|
|
|
|
|
|
|
dstWidth,
|
|
|
|
|
|
|
|
dstHeight,
|
|
|
|
|
|
|
|
resizedPixels);
|
|
|
|
|
|
|
|
if (resizedPixels.empty()) {
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
texturePixels.rgbaPixels = std::move(resizedPixels);
|
|
|
|
|
|
|
|
texturePixels.width = dstWidth;
|
|
|
|
|
|
|
|
texturePixels.height = dstHeight;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ResetTexture(BuiltInTexture& texture) {
|
|
|
|
void ResetTexture(BuiltInTexture& texture) {
|
|
|
|
if (g_icons.backend && texture.cpuHandle.ptr != 0) {
|
|
|
|
if (g_icons.backend && texture.cpuHandle.ptr != 0) {
|
|
|
|
g_icons.backend->FreeTextureDescriptor(texture.cpuHandle, texture.gpuHandle);
|
|
|
|
g_icons.backend->FreeTextureDescriptor(texture.cpuHandle, texture.gpuHandle);
|
|
|
|
@@ -117,6 +246,14 @@ void ResetAssetPreviewCache() {
|
|
|
|
g_icons.assetPreviews.clear();
|
|
|
|
g_icons.assetPreviews.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool EnsureUploadFence(ID3D12Device* device) {
|
|
|
|
|
|
|
|
if (g_icons.uploadFence) {
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return SUCCEEDED(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&g_icons.uploadFence)));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool WaitForQueueIdle(ID3D12Device* device, ID3D12CommandQueue* commandQueue) {
|
|
|
|
bool WaitForQueueIdle(ID3D12Device* device, ID3D12CommandQueue* commandQueue) {
|
|
|
|
if (!device || !commandQueue) {
|
|
|
|
if (!device || !commandQueue) {
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
@@ -151,13 +288,10 @@ bool WaitForQueueIdle(ID3D12Device* device, ID3D12CommandQueue* commandQueue) {
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool LoadTextureFromFile(
|
|
|
|
bool DecodeTextureFromFile(
|
|
|
|
ImGuiBackendBridge& backend,
|
|
|
|
|
|
|
|
ID3D12Device* device,
|
|
|
|
|
|
|
|
ID3D12CommandQueue* commandQueue,
|
|
|
|
|
|
|
|
const std::filesystem::path& filePath,
|
|
|
|
const std::filesystem::path& filePath,
|
|
|
|
BuiltInTexture& outTexture) {
|
|
|
|
LoadedTexturePixels& outTexturePixels) {
|
|
|
|
if (!device || !commandQueue || !std::filesystem::exists(filePath)) {
|
|
|
|
if (!std::filesystem::exists(filePath)) {
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -183,13 +317,34 @@ bool LoadTextureFromFile(
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const UINT srcRowPitch = static_cast<UINT>(width * 4);
|
|
|
|
outTexturePixels.width = width;
|
|
|
|
|
|
|
|
outTexturePixels.height = height;
|
|
|
|
|
|
|
|
outTexturePixels.rgbaPixels.assign(
|
|
|
|
|
|
|
|
pixels,
|
|
|
|
|
|
|
|
pixels + static_cast<size_t>(width) * static_cast<size_t>(height) * 4u);
|
|
|
|
|
|
|
|
stbi_image_free(pixels);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return DownscalePreviewTextureIfNeeded(outTexturePixels);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool UploadTexturePixels(
|
|
|
|
|
|
|
|
ImGuiBackendBridge& backend,
|
|
|
|
|
|
|
|
ID3D12Device* device,
|
|
|
|
|
|
|
|
ID3D12CommandQueue* commandQueue,
|
|
|
|
|
|
|
|
const LoadedTexturePixels& texturePixels,
|
|
|
|
|
|
|
|
BuiltInTexture& outTexture,
|
|
|
|
|
|
|
|
std::unique_ptr<PreviewGpuUpload>& outPendingUpload) {
|
|
|
|
|
|
|
|
if (!device || !commandQueue || texturePixels.width <= 0 || texturePixels.height <= 0 || texturePixels.rgbaPixels.empty()) {
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const UINT srcRowPitch = static_cast<UINT>(texturePixels.width * 4);
|
|
|
|
|
|
|
|
|
|
|
|
D3D12_RESOURCE_DESC textureDesc = {};
|
|
|
|
D3D12_RESOURCE_DESC textureDesc = {};
|
|
|
|
textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
|
|
|
textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
|
|
|
textureDesc.Alignment = 0;
|
|
|
|
textureDesc.Alignment = 0;
|
|
|
|
textureDesc.Width = static_cast<UINT64>(width);
|
|
|
|
textureDesc.Width = static_cast<UINT64>(texturePixels.width);
|
|
|
|
textureDesc.Height = static_cast<UINT>(height);
|
|
|
|
textureDesc.Height = static_cast<UINT>(texturePixels.height);
|
|
|
|
textureDesc.DepthOrArraySize = 1;
|
|
|
|
textureDesc.DepthOrArraySize = 1;
|
|
|
|
textureDesc.MipLevels = 1;
|
|
|
|
textureDesc.MipLevels = 1;
|
|
|
|
textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
|
|
textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
|
|
@@ -207,7 +362,6 @@ bool LoadTextureFromFile(
|
|
|
|
D3D12_RESOURCE_STATE_COPY_DEST,
|
|
|
|
D3D12_RESOURCE_STATE_COPY_DEST,
|
|
|
|
nullptr,
|
|
|
|
nullptr,
|
|
|
|
IID_PPV_ARGS(&textureResource)))) {
|
|
|
|
IID_PPV_ARGS(&textureResource)))) {
|
|
|
|
stbi_image_free(pixels);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -237,24 +391,21 @@ bool LoadTextureFromFile(
|
|
|
|
D3D12_RESOURCE_STATE_GENERIC_READ,
|
|
|
|
D3D12_RESOURCE_STATE_GENERIC_READ,
|
|
|
|
nullptr,
|
|
|
|
nullptr,
|
|
|
|
IID_PPV_ARGS(&uploadResource)))) {
|
|
|
|
IID_PPV_ARGS(&uploadResource)))) {
|
|
|
|
stbi_image_free(pixels);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
std::uint8_t* mappedData = nullptr;
|
|
|
|
std::uint8_t* mappedData = nullptr;
|
|
|
|
if (FAILED(uploadResource->Map(0, nullptr, reinterpret_cast<void**>(&mappedData)))) {
|
|
|
|
if (FAILED(uploadResource->Map(0, nullptr, reinterpret_cast<void**>(&mappedData)))) {
|
|
|
|
stbi_image_free(pixels);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (UINT row = 0; row < numRows; ++row) {
|
|
|
|
for (UINT row = 0; row < numRows; ++row) {
|
|
|
|
std::memcpy(
|
|
|
|
std::memcpy(
|
|
|
|
mappedData + footprint.Offset + static_cast<SIZE_T>(row) * footprint.Footprint.RowPitch,
|
|
|
|
mappedData + footprint.Offset + static_cast<SIZE_T>(row) * footprint.Footprint.RowPitch,
|
|
|
|
pixels + static_cast<size_t>(row) * srcRowPitch,
|
|
|
|
texturePixels.rgbaPixels.data() + static_cast<size_t>(row) * srcRowPitch,
|
|
|
|
srcRowPitch);
|
|
|
|
srcRowPitch);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
uploadResource->Unmap(0, nullptr);
|
|
|
|
uploadResource->Unmap(0, nullptr);
|
|
|
|
stbi_image_free(pixels);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ComPtr<ID3D12CommandAllocator> commandAllocator;
|
|
|
|
ComPtr<ID3D12CommandAllocator> commandAllocator;
|
|
|
|
ComPtr<ID3D12GraphicsCommandList> commandList;
|
|
|
|
ComPtr<ID3D12GraphicsCommandList> commandList;
|
|
|
|
@@ -294,14 +445,16 @@ bool LoadTextureFromFile(
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ID3D12CommandList* commandLists[] = { commandList.Get() };
|
|
|
|
if (!EnsureUploadFence(device)) {
|
|
|
|
commandQueue->ExecuteCommandLists(1, commandLists);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!WaitForQueueIdle(device, commandQueue)) {
|
|
|
|
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ResetTexture(outTexture);
|
|
|
|
backend.AllocateTextureDescriptor(&outTexture.cpuHandle, &outTexture.gpuHandle);
|
|
|
|
backend.AllocateTextureDescriptor(&outTexture.cpuHandle, &outTexture.gpuHandle);
|
|
|
|
|
|
|
|
if (outTexture.cpuHandle.ptr == 0 || outTexture.gpuHandle.ptr == 0) {
|
|
|
|
|
|
|
|
ResetTexture(outTexture);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
|
|
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
|
|
|
|
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
|
|
|
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
|
|
|
@@ -312,11 +465,180 @@ bool LoadTextureFromFile(
|
|
|
|
|
|
|
|
|
|
|
|
outTexture.texture = textureResource;
|
|
|
|
outTexture.texture = textureResource;
|
|
|
|
outTexture.textureId = (ImTextureID)(static_cast<intptr_t>(outTexture.gpuHandle.ptr));
|
|
|
|
outTexture.textureId = (ImTextureID)(static_cast<intptr_t>(outTexture.gpuHandle.ptr));
|
|
|
|
outTexture.width = width;
|
|
|
|
outTexture.width = texturePixels.width;
|
|
|
|
outTexture.height = height;
|
|
|
|
outTexture.height = texturePixels.height;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ID3D12CommandList* commandLists[] = { commandList.Get() };
|
|
|
|
|
|
|
|
commandQueue->ExecuteCommandLists(1, commandLists);
|
|
|
|
|
|
|
|
const UINT64 fenceValue = g_icons.nextUploadFenceValue++;
|
|
|
|
|
|
|
|
if (FAILED(commandQueue->Signal(g_icons.uploadFence.Get(), fenceValue))) {
|
|
|
|
|
|
|
|
ResetTexture(outTexture);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
outPendingUpload = std::make_unique<PreviewGpuUpload>();
|
|
|
|
|
|
|
|
outPendingUpload->uploadResource = std::move(uploadResource);
|
|
|
|
|
|
|
|
outPendingUpload->commandAllocator = std::move(commandAllocator);
|
|
|
|
|
|
|
|
outPendingUpload->commandList = std::move(commandList);
|
|
|
|
|
|
|
|
outPendingUpload->fenceValue = fenceValue;
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool LoadTextureFromFile(
|
|
|
|
|
|
|
|
ImGuiBackendBridge& backend,
|
|
|
|
|
|
|
|
ID3D12Device* device,
|
|
|
|
|
|
|
|
ID3D12CommandQueue* commandQueue,
|
|
|
|
|
|
|
|
const std::filesystem::path& filePath,
|
|
|
|
|
|
|
|
BuiltInTexture& outTexture,
|
|
|
|
|
|
|
|
std::unique_ptr<PreviewGpuUpload>& outPendingUpload) {
|
|
|
|
|
|
|
|
LoadedTexturePixels texturePixels;
|
|
|
|
|
|
|
|
if (!DecodeTextureFromFile(filePath, texturePixels)) {
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return UploadTexturePixels(
|
|
|
|
|
|
|
|
backend,
|
|
|
|
|
|
|
|
device,
|
|
|
|
|
|
|
|
commandQueue,
|
|
|
|
|
|
|
|
texturePixels,
|
|
|
|
|
|
|
|
outTexture,
|
|
|
|
|
|
|
|
outPendingUpload);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void PreviewWorkerMain() {
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
|
|
PreviewDecodeJob job;
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
std::unique_lock<std::mutex> lock(g_icons.previewQueueMutex);
|
|
|
|
|
|
|
|
g_icons.previewQueueEvent.wait(lock, [] {
|
|
|
|
|
|
|
|
return !g_icons.previewWorkersRunning || !g_icons.previewDecodeQueue.empty();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!g_icons.previewWorkersRunning && g_icons.previewDecodeQueue.empty()) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
job = std::move(g_icons.previewDecodeQueue.front());
|
|
|
|
|
|
|
|
g_icons.previewDecodeQueue.pop_front();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PreviewDecodeResult result;
|
|
|
|
|
|
|
|
result.key = std::move(job.key);
|
|
|
|
|
|
|
|
result.success = DecodeTextureFromFile(job.filePath, result.pixels);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(g_icons.previewQueueMutex);
|
|
|
|
|
|
|
|
g_icons.previewDecodeResults.push_back(std::move(result));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void StartPreviewWorkers() {
|
|
|
|
|
|
|
|
if (g_icons.previewWorkersRunning) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
g_icons.previewWorkersRunning = true;
|
|
|
|
|
|
|
|
g_icons.previewWorkers.reserve(kPreviewWorkerCount);
|
|
|
|
|
|
|
|
for (size_t workerIndex = 0; workerIndex < kPreviewWorkerCount; ++workerIndex) {
|
|
|
|
|
|
|
|
g_icons.previewWorkers.emplace_back(PreviewWorkerMain);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void StopPreviewWorkers() {
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(g_icons.previewQueueMutex);
|
|
|
|
|
|
|
|
g_icons.previewWorkersRunning = false;
|
|
|
|
|
|
|
|
g_icons.previewDecodeQueue.clear();
|
|
|
|
|
|
|
|
g_icons.previewDecodeResults.clear();
|
|
|
|
|
|
|
|
g_icons.pendingPreviewDecodeJobs = 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
g_icons.previewQueueEvent.notify_all();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (std::thread& worker : g_icons.previewWorkers) {
|
|
|
|
|
|
|
|
if (worker.joinable()) {
|
|
|
|
|
|
|
|
worker.join();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
g_icons.previewWorkers.clear();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool QueuePreviewDecode(const std::string& key, const std::filesystem::path& filePath) {
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(g_icons.previewQueueMutex);
|
|
|
|
|
|
|
|
if (!g_icons.previewWorkersRunning || g_icons.pendingPreviewDecodeJobs >= kMaxQueuedPreviewDecodeJobs) {
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
g_icons.previewDecodeQueue.push_back(PreviewDecodeJob{ key, filePath });
|
|
|
|
|
|
|
|
++g_icons.pendingPreviewDecodeJobs;
|
|
|
|
|
|
|
|
g_icons.previewQueueEvent.notify_one();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void DrainPreviewDecodeResults() {
|
|
|
|
|
|
|
|
std::deque<PreviewDecodeResult> completedResults;
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(g_icons.previewQueueMutex);
|
|
|
|
|
|
|
|
completedResults.swap(g_icons.previewDecodeResults);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (PreviewDecodeResult& result : completedResults) {
|
|
|
|
|
|
|
|
if (g_icons.pendingPreviewDecodeJobs > 0) {
|
|
|
|
|
|
|
|
--g_icons.pendingPreviewDecodeJobs;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto it = g_icons.assetPreviews.find(result.key);
|
|
|
|
|
|
|
|
if (it == g_icons.assetPreviews.end()) {
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BuiltInIconState::CachedAssetPreview& preview = it->second;
|
|
|
|
|
|
|
|
preview.decodeQueued = false;
|
|
|
|
|
|
|
|
if (!result.success) {
|
|
|
|
|
|
|
|
preview.loadFailed = true;
|
|
|
|
|
|
|
|
preview.decodedPixels.reset();
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
preview.loadFailed = false;
|
|
|
|
|
|
|
|
preview.decodedPixels = std::make_unique<LoadedTexturePixels>(std::move(result.pixels));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void PollCompletedUploads() {
|
|
|
|
|
|
|
|
if (!g_icons.uploadFence) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const UINT64 completedFenceValue = g_icons.uploadFence->GetCompletedValue();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (auto& entry : g_icons.assetPreviews) {
|
|
|
|
|
|
|
|
auto& preview = entry.second;
|
|
|
|
|
|
|
|
if (preview.pendingUpload && completedFenceValue >= preview.pendingUpload->fenceValue) {
|
|
|
|
|
|
|
|
preview.pendingUpload.reset();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto eraseIt = std::remove_if(
|
|
|
|
|
|
|
|
g_icons.pendingIconUploads.begin(),
|
|
|
|
|
|
|
|
g_icons.pendingIconUploads.end(),
|
|
|
|
|
|
|
|
[completedFenceValue](const std::unique_ptr<PreviewGpuUpload>& upload) {
|
|
|
|
|
|
|
|
return upload && completedFenceValue >= upload->fenceValue;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
g_icons.pendingIconUploads.erase(eraseIt, g_icons.pendingIconUploads.end());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MaintainIconRuntimeState() {
|
|
|
|
|
|
|
|
const int frame = ImGui::GetFrameCount();
|
|
|
|
|
|
|
|
if (g_icons.lastMaintenanceFrame == frame) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
g_icons.lastMaintenanceFrame = frame;
|
|
|
|
|
|
|
|
DrainPreviewDecodeResults();
|
|
|
|
|
|
|
|
PollCompletedUploads();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ImVec2 ComputeFittedIconSize(const BuiltInTexture& texture, const ImVec2& min, const ImVec2& max) {
|
|
|
|
ImVec2 ComputeFittedIconSize(const BuiltInTexture& texture, const ImVec2& min, const ImVec2& max) {
|
|
|
|
const float availableWidth = max.x - min.x;
|
|
|
|
const float availableWidth = max.x - min.x;
|
|
|
|
const float availableHeight = max.y - min.y;
|
|
|
|
const float availableHeight = max.y - min.y;
|
|
|
|
@@ -396,18 +718,51 @@ BuiltInIconState::CachedAssetPreview* GetOrCreateAssetPreview(const std::string&
|
|
|
|
return nullptr;
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MaintainIconRuntimeState();
|
|
|
|
|
|
|
|
|
|
|
|
const std::string key = MakeAssetPreviewKey(filePath);
|
|
|
|
const std::string key = MakeAssetPreviewKey(filePath);
|
|
|
|
auto [it, inserted] = g_icons.assetPreviews.try_emplace(key);
|
|
|
|
auto [it, inserted] = g_icons.assetPreviews.try_emplace(key);
|
|
|
|
BuiltInIconState::CachedAssetPreview& preview = it->second;
|
|
|
|
BuiltInIconState::CachedAssetPreview& preview = it->second;
|
|
|
|
preview.lastUsedFrame = ImGui::GetFrameCount();
|
|
|
|
preview.lastUsedFrame = ImGui::GetFrameCount();
|
|
|
|
|
|
|
|
|
|
|
|
if (!inserted && !std::filesystem::exists(std::filesystem::path(Platform::Utf8ToWide(filePath)))) {
|
|
|
|
if (!inserted && !std::filesystem::exists(std::filesystem::path(Platform::Utf8ToWide(filePath)))) {
|
|
|
|
ResetTexture(preview.texture);
|
|
|
|
if (!preview.pendingUpload) {
|
|
|
|
preview.loadAttempted = true;
|
|
|
|
ResetTexture(preview.texture);
|
|
|
|
|
|
|
|
preview.decodedPixels.reset();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
preview.loadFailed = true;
|
|
|
|
|
|
|
|
preview.decodeQueued = false;
|
|
|
|
return &preview;
|
|
|
|
return &preview;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (preview.texture.IsValid() || preview.loadAttempted) {
|
|
|
|
if (preview.texture.IsValid()) {
|
|
|
|
|
|
|
|
if (preview.pendingUpload && preview.pendingUpload->fenceValue <= g_icons.uploadFence->GetCompletedValue()) {
|
|
|
|
|
|
|
|
preview.pendingUpload.reset();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return &preview;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (preview.decodedPixels) {
|
|
|
|
|
|
|
|
std::unique_ptr<PreviewGpuUpload> pendingUpload;
|
|
|
|
|
|
|
|
if (!UploadTexturePixels(
|
|
|
|
|
|
|
|
*g_icons.backend,
|
|
|
|
|
|
|
|
g_icons.device,
|
|
|
|
|
|
|
|
g_icons.commandQueue,
|
|
|
|
|
|
|
|
*preview.decodedPixels,
|
|
|
|
|
|
|
|
preview.texture,
|
|
|
|
|
|
|
|
pendingUpload)) {
|
|
|
|
|
|
|
|
preview.decodedPixels.reset();
|
|
|
|
|
|
|
|
preview.loadFailed = true;
|
|
|
|
|
|
|
|
return &preview;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
preview.decodedPixels.reset();
|
|
|
|
|
|
|
|
preview.loadFailed = false;
|
|
|
|
|
|
|
|
preview.pendingUpload = std::move(pendingUpload);
|
|
|
|
|
|
|
|
return &preview;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (preview.decodeQueued || preview.loadFailed) {
|
|
|
|
return &preview;
|
|
|
|
return &preview;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -420,14 +775,10 @@ BuiltInIconState::CachedAssetPreview* GetOrCreateAssetPreview(const std::string&
|
|
|
|
return &preview;
|
|
|
|
return &preview;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
preview.loadAttempted = true;
|
|
|
|
if (QueuePreviewDecode(key, std::filesystem::path(Platform::Utf8ToWide(filePath)))) {
|
|
|
|
++g_icons.previewLoadsThisFrame;
|
|
|
|
preview.decodeQueued = true;
|
|
|
|
LoadTextureFromFile(
|
|
|
|
++g_icons.previewLoadsThisFrame;
|
|
|
|
*g_icons.backend,
|
|
|
|
}
|
|
|
|
g_icons.device,
|
|
|
|
|
|
|
|
g_icons.commandQueue,
|
|
|
|
|
|
|
|
std::filesystem::path(Platform::Utf8ToWide(filePath)),
|
|
|
|
|
|
|
|
preview.texture);
|
|
|
|
|
|
|
|
return &preview;
|
|
|
|
return &preview;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -439,6 +790,9 @@ void PruneAssetPreviewCache() {
|
|
|
|
std::vector<std::pair<std::string, int>> candidates;
|
|
|
|
std::vector<std::pair<std::string, int>> candidates;
|
|
|
|
candidates.reserve(g_icons.assetPreviews.size());
|
|
|
|
candidates.reserve(g_icons.assetPreviews.size());
|
|
|
|
for (const auto& entry : g_icons.assetPreviews) {
|
|
|
|
for (const auto& entry : g_icons.assetPreviews) {
|
|
|
|
|
|
|
|
if (entry.second.pendingUpload || entry.second.decodeQueued) {
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
candidates.emplace_back(entry.first, entry.second.lastUsedFrame);
|
|
|
|
candidates.emplace_back(entry.first, entry.second.lastUsedFrame);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -471,24 +825,47 @@ void InitializeBuiltInIcons(
|
|
|
|
g_icons.backend = &backend;
|
|
|
|
g_icons.backend = &backend;
|
|
|
|
g_icons.device = device;
|
|
|
|
g_icons.device = device;
|
|
|
|
g_icons.commandQueue = commandQueue;
|
|
|
|
g_icons.commandQueue = commandQueue;
|
|
|
|
LoadTextureFromFile(backend, device, commandQueue, ResolveFolderIconPath(), g_icons.folder);
|
|
|
|
StartPreviewWorkers();
|
|
|
|
LoadTextureFromFile(backend, device, commandQueue, ResolveGameObjectIconPath(), g_icons.gameObject);
|
|
|
|
|
|
|
|
LoadTextureFromFile(backend, device, commandQueue, ResolveSceneIconPath(), g_icons.scene);
|
|
|
|
std::unique_ptr<PreviewGpuUpload> pendingUpload;
|
|
|
|
|
|
|
|
if (LoadTextureFromFile(backend, device, commandQueue, ResolveFolderIconPath(), g_icons.folder, pendingUpload)) {
|
|
|
|
|
|
|
|
g_icons.pendingIconUploads.push_back(std::move(pendingUpload));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pendingUpload.reset();
|
|
|
|
|
|
|
|
if (LoadTextureFromFile(backend, device, commandQueue, ResolveGameObjectIconPath(), g_icons.gameObject, pendingUpload)) {
|
|
|
|
|
|
|
|
g_icons.pendingIconUploads.push_back(std::move(pendingUpload));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pendingUpload.reset();
|
|
|
|
|
|
|
|
if (LoadTextureFromFile(backend, device, commandQueue, ResolveSceneIconPath(), g_icons.scene, pendingUpload)) {
|
|
|
|
|
|
|
|
g_icons.pendingIconUploads.push_back(std::move(pendingUpload));
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ShutdownBuiltInIcons() {
|
|
|
|
void ShutdownBuiltInIcons() {
|
|
|
|
|
|
|
|
if (g_icons.device && g_icons.commandQueue) {
|
|
|
|
|
|
|
|
WaitForQueueIdle(g_icons.device, g_icons.commandQueue);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
StopPreviewWorkers();
|
|
|
|
ResetAssetPreviewCache();
|
|
|
|
ResetAssetPreviewCache();
|
|
|
|
|
|
|
|
g_icons.pendingIconUploads.clear();
|
|
|
|
ResetTexture(g_icons.folder);
|
|
|
|
ResetTexture(g_icons.folder);
|
|
|
|
ResetTexture(g_icons.gameObject);
|
|
|
|
ResetTexture(g_icons.gameObject);
|
|
|
|
ResetTexture(g_icons.scene);
|
|
|
|
ResetTexture(g_icons.scene);
|
|
|
|
g_icons.backend = nullptr;
|
|
|
|
g_icons.backend = nullptr;
|
|
|
|
g_icons.device = nullptr;
|
|
|
|
g_icons.device = nullptr;
|
|
|
|
g_icons.commandQueue = nullptr;
|
|
|
|
g_icons.commandQueue = nullptr;
|
|
|
|
|
|
|
|
g_icons.uploadFence.Reset();
|
|
|
|
|
|
|
|
g_icons.nextUploadFenceValue = 1;
|
|
|
|
|
|
|
|
g_icons.lastMaintenanceFrame = -1;
|
|
|
|
g_icons.lastPreviewBudgetFrame = -1;
|
|
|
|
g_icons.lastPreviewBudgetFrame = -1;
|
|
|
|
g_icons.previewLoadsThisFrame = 0;
|
|
|
|
g_icons.previewLoadsThisFrame = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DrawAssetIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& max, AssetIconKind kind) {
|
|
|
|
void DrawAssetIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& max, AssetIconKind kind) {
|
|
|
|
|
|
|
|
MaintainIconRuntimeState();
|
|
|
|
|
|
|
|
|
|
|
|
if (kind == AssetIconKind::Folder) {
|
|
|
|
if (kind == AssetIconKind::Folder) {
|
|
|
|
if (g_icons.folder.IsValid()) {
|
|
|
|
if (g_icons.folder.IsValid()) {
|
|
|
|
DrawTextureIcon(drawList, g_icons.folder, min, max);
|
|
|
|
DrawTextureIcon(drawList, g_icons.folder, min, max);
|
|
|
|
|