refactor: move built-in icon upload into imgui backend

This commit is contained in:
2026-04-01 20:03:44 +08:00
parent bc6e20de48
commit 6cd4cd9be9
5 changed files with 385 additions and 273 deletions

View File

@@ -0,0 +1,32 @@
#pragma once
#include <imgui.h>
#include <algorithm>
namespace XCEngine {
namespace Editor {
namespace UI {
inline ImVec2 ComputeFittedIconSize(
int textureWidth,
int textureHeight,
const ImVec2& min,
const ImVec2& max) {
const float availableWidth = max.x - min.x;
const float availableHeight = max.y - min.y;
if (availableWidth <= 0.0f || availableHeight <= 0.0f || textureWidth <= 0 || textureHeight <= 0) {
return ImVec2(0.0f, 0.0f);
}
const float scale = (std::min)(
availableWidth / static_cast<float>(textureWidth),
availableHeight / static_cast<float>(textureHeight));
return ImVec2(
static_cast<float>(textureWidth) * scale,
static_cast<float>(textureHeight) * scale);
}
} // namespace UI
} // namespace Editor
} // namespace XCEngine

View File

@@ -1,6 +1,7 @@
#include "BuiltInIcons.h"
#include "ImGuiBackendBridge.h"
#include "BuiltInIconLayoutUtils.h"
#include "Platform/Win32Utf8.h"
#include "StyleTokens.h"
@@ -19,7 +20,6 @@
#include <thread>
#include <unordered_map>
#include <vector>
#include <wrl/client.h>
#include <stb_image.h>
@@ -29,20 +29,8 @@ namespace UI {
namespace {
using Microsoft::WRL::ComPtr;
struct BuiltInTexture {
ImTextureID textureId = {};
D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle = {};
D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle = {};
ComPtr<ID3D12Resource> texture;
int width = 0;
int height = 0;
bool IsValid() const {
return textureId != ImTextureID{} && texture != nullptr && width > 0 && height > 0;
}
};
using BuiltInTexture = ImGuiBackendBridge::UploadedTexture;
using PreviewGpuUpload = ImGuiBackendBridge::PendingTextureUpload;
struct LoadedTexturePixels {
std::vector<stbi_uc> rgbaPixels;
@@ -50,13 +38,6 @@ struct LoadedTexturePixels {
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;
@@ -137,8 +118,6 @@ struct BuiltInIconState {
std::condition_variable previewQueueEvent;
bool previewWorkersRunning = false;
size_t pendingPreviewDecodeJobs = 0;
ComPtr<ID3D12Fence> uploadFence;
UINT64 nextUploadFenceValue = 1;
int lastPreviewBudgetFrame = -1;
int previewLoadsThisFrame = 0;
int lastMaintenanceFrame = -1;
@@ -560,16 +539,12 @@ bool RefreshAssetPreviewSourceFingerprint(
}
void ResetTexture(BuiltInTexture& texture) {
if (g_icons.backend && texture.cpuHandle.ptr != 0) {
g_icons.backend->FreeTextureDescriptor(texture.cpuHandle, texture.gpuHandle);
if (g_icons.backend != nullptr) {
g_icons.backend->ResetUploadedTexture(&texture);
return;
}
texture.texture.Reset();
texture.textureId = {};
texture.cpuHandle = {};
texture.gpuHandle = {};
texture.width = 0;
texture.height = 0;
texture = {};
}
void ResetAssetPreviewCache() {
@@ -579,48 +554,6 @@ void ResetAssetPreviewCache() {
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) {
if (!device || !commandQueue) {
return false;
}
ComPtr<ID3D12Fence> fence;
if (FAILED(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)))) {
return false;
}
HANDLE eventHandle = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if (!eventHandle) {
return false;
}
constexpr UINT64 kFenceValue = 1;
const HRESULT signalHr = commandQueue->Signal(fence.Get(), kFenceValue);
if (FAILED(signalHr)) {
CloseHandle(eventHandle);
return false;
}
if (fence->GetCompletedValue() < kFenceValue) {
if (FAILED(fence->SetEventOnCompletion(kFenceValue, eventHandle))) {
CloseHandle(eventHandle);
return false;
}
WaitForSingleObject(eventHandle, INFINITE);
}
CloseHandle(eventHandle);
return true;
}
bool DecodeTextureFromFile(
const std::filesystem::path& filePath,
LoadedTexturePixels& outTexturePixels) {
@@ -660,163 +593,6 @@ bool DecodeTextureFromFile(
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 = {};
textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
textureDesc.Alignment = 0;
textureDesc.Width = static_cast<UINT64>(texturePixels.width);
textureDesc.Height = static_cast<UINT>(texturePixels.height);
textureDesc.DepthOrArraySize = 1;
textureDesc.MipLevels = 1;
textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
textureDesc.SampleDesc.Count = 1;
textureDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
D3D12_HEAP_PROPERTIES defaultHeap = {};
defaultHeap.Type = D3D12_HEAP_TYPE_DEFAULT;
ComPtr<ID3D12Resource> textureResource;
if (FAILED(device->CreateCommittedResource(
&defaultHeap,
D3D12_HEAP_FLAG_NONE,
&textureDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(&textureResource)))) {
return false;
}
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint = {};
UINT numRows = 0;
UINT64 rowSizeInBytes = 0;
UINT64 uploadBufferSize = 0;
device->GetCopyableFootprints(&textureDesc, 0, 1, 0, &footprint, &numRows, &rowSizeInBytes, &uploadBufferSize);
D3D12_RESOURCE_DESC uploadDesc = {};
uploadDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
uploadDesc.Width = uploadBufferSize;
uploadDesc.Height = 1;
uploadDesc.DepthOrArraySize = 1;
uploadDesc.MipLevels = 1;
uploadDesc.SampleDesc.Count = 1;
uploadDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
D3D12_HEAP_PROPERTIES uploadHeap = {};
uploadHeap.Type = D3D12_HEAP_TYPE_UPLOAD;
ComPtr<ID3D12Resource> uploadResource;
if (FAILED(device->CreateCommittedResource(
&uploadHeap,
D3D12_HEAP_FLAG_NONE,
&uploadDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&uploadResource)))) {
return false;
}
std::uint8_t* mappedData = nullptr;
if (FAILED(uploadResource->Map(0, nullptr, reinterpret_cast<void**>(&mappedData)))) {
return false;
}
for (UINT row = 0; row < numRows; ++row) {
std::memcpy(
mappedData + footprint.Offset + static_cast<SIZE_T>(row) * footprint.Footprint.RowPitch,
texturePixels.rgbaPixels.data() + static_cast<size_t>(row) * srcRowPitch,
srcRowPitch);
}
uploadResource->Unmap(0, nullptr);
ComPtr<ID3D12CommandAllocator> commandAllocator;
ComPtr<ID3D12GraphicsCommandList> commandList;
if (FAILED(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&commandAllocator)))) {
return false;
}
if (FAILED(device->CreateCommandList(
0,
D3D12_COMMAND_LIST_TYPE_DIRECT,
commandAllocator.Get(),
nullptr,
IID_PPV_ARGS(&commandList)))) {
return false;
}
D3D12_TEXTURE_COPY_LOCATION dst = {};
dst.pResource = textureResource.Get();
dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
dst.SubresourceIndex = 0;
D3D12_TEXTURE_COPY_LOCATION src = {};
src.pResource = uploadResource.Get();
src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
src.PlacedFootprint = footprint;
commandList->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
D3D12_RESOURCE_BARRIER barrier = {};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Transition.pResource = textureResource.Get();
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
commandList->ResourceBarrier(1, &barrier);
if (FAILED(commandList->Close())) {
return false;
}
if (!EnsureUploadFence(device)) {
return false;
}
ResetTexture(outTexture);
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 = {};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = 1;
device->CreateShaderResourceView(textureResource.Get(), &srvDesc, outTexture.cpuHandle);
outTexture.texture = textureResource;
outTexture.textureId = (ImTextureID)(static_cast<intptr_t>(outTexture.gpuHandle.ptr));
outTexture.width = texturePixels.width;
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;
}
bool LoadTextureFromFile(
ImGuiBackendBridge& backend,
ID3D12Device* device,
@@ -829,13 +605,14 @@ bool LoadTextureFromFile(
return false;
}
return UploadTexturePixels(
backend,
return backend.UploadRgbaTexture(
device,
commandQueue,
texturePixels,
outTexture,
outPendingUpload);
texturePixels.rgbaPixels.data(),
texturePixels.width,
texturePixels.height,
&outTexture,
&outPendingUpload);
}
void PreviewWorkerMain() {
@@ -985,15 +762,13 @@ void DrainPreviewDecodeResults() {
}
void PollCompletedUploads() {
if (!g_icons.uploadFence) {
if (g_icons.backend == nullptr) {
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) {
if (preview.pendingUpload && g_icons.backend->IsTextureUploadComplete(*preview.pendingUpload)) {
preview.pendingUpload.reset();
if (preview.hasStaleTexture) {
ResetTexture(preview.texture);
@@ -1002,13 +777,7 @@ void PollCompletedUploads() {
}
}
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());
g_icons.backend->RemoveCompletedTextureUploads(&g_icons.pendingIconUploads);
}
void MaintainIconRuntimeState() {
@@ -1022,27 +791,12 @@ void MaintainIconRuntimeState() {
PollCompletedUploads();
}
ImVec2 ComputeFittedIconSize(const BuiltInTexture& texture, const ImVec2& min, const ImVec2& max) {
const float availableWidth = max.x - min.x;
const float availableHeight = max.y - min.y;
if (availableWidth <= 0.0f || availableHeight <= 0.0f || texture.width <= 0 || texture.height <= 0) {
return ImVec2(0.0f, 0.0f);
}
const float scale = (std::min)(
availableWidth / static_cast<float>(texture.width),
availableHeight / static_cast<float>(texture.height));
return ImVec2(
static_cast<float>(texture.width) * scale,
static_cast<float>(texture.height) * scale);
}
void DrawTextureIcon(ImDrawList* drawList, const BuiltInTexture& texture, const ImVec2& min, const ImVec2& max) {
if (!drawList || !texture.IsValid()) {
return;
}
const ImVec2 size = ComputeFittedIconSize(texture, min, max);
const ImVec2 size = ComputeFittedIconSize(texture.width, texture.height, min, max);
const float x = min.x + ((max.x - min.x) - size.x) * 0.5f;
const float y = min.y + ((max.y - min.y) - size.y) * 0.5f;
drawList->AddImage(texture.textureId, ImVec2(x, y), ImVec2(x + size.x, y + size.y));
@@ -1126,7 +880,7 @@ BuiltInIconState::CachedAssetPreview* GetOrCreateAssetPreview(
}
if (preview.texture.IsValid() && !preview.hasStaleTexture) {
if (preview.pendingUpload && preview.pendingUpload->fenceValue <= g_icons.uploadFence->GetCompletedValue()) {
if (preview.pendingUpload && g_icons.backend->IsTextureUploadComplete(*preview.pendingUpload)) {
preview.pendingUpload.reset();
}
return &preview;
@@ -1134,13 +888,14 @@ BuiltInIconState::CachedAssetPreview* GetOrCreateAssetPreview(
if (preview.decodedPixels && !preview.pendingUpload) {
std::unique_ptr<PreviewGpuUpload> pendingUpload;
if (!UploadTexturePixels(
*g_icons.backend,
if (!g_icons.backend->UploadRgbaTexture(
g_icons.device,
g_icons.commandQueue,
*preview.decodedPixels,
preview.texture,
pendingUpload)) {
preview.decodedPixels->rgbaPixels.data(),
preview.decodedPixels->width,
preview.decodedPixels->height,
&preview.texture,
&pendingUpload)) {
preview.decodedPixels.reset();
preview.loadFailed = true;
return &preview;
@@ -1233,8 +988,8 @@ void InitializeBuiltInIcons(
}
void ShutdownBuiltInIcons() {
if (g_icons.device && g_icons.commandQueue) {
WaitForQueueIdle(g_icons.device, g_icons.commandQueue);
if (g_icons.backend && g_icons.device && g_icons.commandQueue) {
g_icons.backend->WaitForQueueIdle(g_icons.device, g_icons.commandQueue);
}
StopPreviewWorkers();
ResetAssetPreviewCache();
@@ -1245,8 +1000,6 @@ void ShutdownBuiltInIcons() {
g_icons.backend = nullptr;
g_icons.device = nullptr;
g_icons.commandQueue = nullptr;
g_icons.uploadFence.Reset();
g_icons.nextUploadFenceValue = 1;
g_icons.lastMaintenanceFrame = -1;
g_icons.lastPreviewBudgetFrame = -1;
g_icons.previewLoadsThisFrame = 0;

View File

@@ -13,7 +13,12 @@
#include <imgui.h>
#include <imgui_impl_dx12.h>
#include <imgui_impl_win32.h>
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <memory>
#include <vector>
#include <wrl/client.h>
#include <windows.h>
#ifdef min
@@ -32,6 +37,26 @@ namespace UI {
class ImGuiBackendBridge {
public:
struct UploadedTexture {
ImTextureID textureId = {};
D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle = {};
D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle = {};
Microsoft::WRL::ComPtr<ID3D12Resource> texture;
int width = 0;
int height = 0;
bool IsValid() const {
return textureId != ImTextureID{} && texture != nullptr && width > 0 && height > 0;
}
};
struct PendingTextureUpload {
Microsoft::WRL::ComPtr<ID3D12Resource> uploadResource;
Microsoft::WRL::ComPtr<ID3D12CommandAllocator> commandAllocator;
Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList> commandList;
UINT64 fenceValue = 0;
};
static void EnableDpiAwareness() {
ImGui_ImplWin32_EnableDpiAwareness();
}
@@ -83,6 +108,8 @@ public:
m_srvDescriptorSize = 0;
m_srvCpuStart.ptr = 0;
m_srvGpuStart.ptr = 0;
m_uploadFence.Reset();
m_nextUploadFenceValue = 1;
m_initialized = false;
}
@@ -147,6 +174,255 @@ public:
FreeSrvDescriptorInternal(cpuHandle, gpuHandle);
}
void ResetUploadedTexture(UploadedTexture* texture) {
if (texture == nullptr) {
return;
}
if (texture->cpuHandle.ptr != 0) {
FreeTextureDescriptor(texture->cpuHandle, texture->gpuHandle);
}
texture->texture.Reset();
texture->textureId = {};
texture->cpuHandle = {};
texture->gpuHandle = {};
texture->width = 0;
texture->height = 0;
}
bool UploadRgbaTexture(
ID3D12Device* device,
ID3D12CommandQueue* commandQueue,
const std::uint8_t* rgbaPixels,
int width,
int height,
UploadedTexture* outTexture,
std::unique_ptr<PendingTextureUpload>* outPendingUpload) {
if (device == nullptr ||
commandQueue == nullptr ||
rgbaPixels == nullptr ||
width <= 0 ||
height <= 0 ||
outTexture == nullptr ||
outPendingUpload == nullptr) {
return false;
}
const UINT srcRowPitch = static_cast<UINT>(width * 4);
D3D12_RESOURCE_DESC textureDesc = {};
textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
textureDesc.Alignment = 0;
textureDesc.Width = static_cast<UINT64>(width);
textureDesc.Height = static_cast<UINT>(height);
textureDesc.DepthOrArraySize = 1;
textureDesc.MipLevels = 1;
textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
textureDesc.SampleDesc.Count = 1;
textureDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
D3D12_HEAP_PROPERTIES defaultHeap = {};
defaultHeap.Type = D3D12_HEAP_TYPE_DEFAULT;
Microsoft::WRL::ComPtr<ID3D12Resource> textureResource;
if (FAILED(device->CreateCommittedResource(
&defaultHeap,
D3D12_HEAP_FLAG_NONE,
&textureDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(&textureResource)))) {
return false;
}
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint = {};
UINT numRows = 0;
UINT64 rowSizeInBytes = 0;
UINT64 uploadBufferSize = 0;
device->GetCopyableFootprints(
&textureDesc,
0,
1,
0,
&footprint,
&numRows,
&rowSizeInBytes,
&uploadBufferSize);
D3D12_RESOURCE_DESC uploadDesc = {};
uploadDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
uploadDesc.Width = uploadBufferSize;
uploadDesc.Height = 1;
uploadDesc.DepthOrArraySize = 1;
uploadDesc.MipLevels = 1;
uploadDesc.SampleDesc.Count = 1;
uploadDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
D3D12_HEAP_PROPERTIES uploadHeap = {};
uploadHeap.Type = D3D12_HEAP_TYPE_UPLOAD;
Microsoft::WRL::ComPtr<ID3D12Resource> uploadResource;
if (FAILED(device->CreateCommittedResource(
&uploadHeap,
D3D12_HEAP_FLAG_NONE,
&uploadDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&uploadResource)))) {
return false;
}
std::uint8_t* mappedData = nullptr;
if (FAILED(uploadResource->Map(0, nullptr, reinterpret_cast<void**>(&mappedData)))) {
return false;
}
for (UINT row = 0; row < numRows; ++row) {
std::memcpy(
mappedData + footprint.Offset + static_cast<SIZE_T>(row) * footprint.Footprint.RowPitch,
rgbaPixels + static_cast<size_t>(row) * srcRowPitch,
srcRowPitch);
}
uploadResource->Unmap(0, nullptr);
Microsoft::WRL::ComPtr<ID3D12CommandAllocator> commandAllocator;
Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList> commandList;
if (FAILED(device->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT,
IID_PPV_ARGS(&commandAllocator)))) {
return false;
}
if (FAILED(device->CreateCommandList(
0,
D3D12_COMMAND_LIST_TYPE_DIRECT,
commandAllocator.Get(),
nullptr,
IID_PPV_ARGS(&commandList)))) {
return false;
}
D3D12_TEXTURE_COPY_LOCATION dst = {};
dst.pResource = textureResource.Get();
dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
dst.SubresourceIndex = 0;
D3D12_TEXTURE_COPY_LOCATION src = {};
src.pResource = uploadResource.Get();
src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
src.PlacedFootprint = footprint;
commandList->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
D3D12_RESOURCE_BARRIER barrier = {};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Transition.pResource = textureResource.Get();
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
commandList->ResourceBarrier(1, &barrier);
if (FAILED(commandList->Close())) {
return false;
}
if (!EnsureUploadFence(device)) {
return false;
}
UploadedTexture uploadedTexture = {};
AllocateTextureDescriptor(&uploadedTexture.cpuHandle, &uploadedTexture.gpuHandle);
if (uploadedTexture.cpuHandle.ptr == 0 || uploadedTexture.gpuHandle.ptr == 0) {
ResetUploadedTexture(&uploadedTexture);
return false;
}
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = 1;
device->CreateShaderResourceView(textureResource.Get(), &srvDesc, uploadedTexture.cpuHandle);
uploadedTexture.texture = textureResource;
uploadedTexture.textureId = (ImTextureID)(static_cast<intptr_t>(uploadedTexture.gpuHandle.ptr));
uploadedTexture.width = width;
uploadedTexture.height = height;
ID3D12CommandList* commandLists[] = { commandList.Get() };
commandQueue->ExecuteCommandLists(1, commandLists);
const UINT64 fenceValue = m_nextUploadFenceValue++;
if (FAILED(commandQueue->Signal(m_uploadFence.Get(), fenceValue))) {
ResetUploadedTexture(&uploadedTexture);
return false;
}
auto pendingUpload = std::make_unique<PendingTextureUpload>();
pendingUpload->uploadResource = std::move(uploadResource);
pendingUpload->commandAllocator = std::move(commandAllocator);
pendingUpload->commandList = std::move(commandList);
pendingUpload->fenceValue = fenceValue;
*outTexture = std::move(uploadedTexture);
*outPendingUpload = std::move(pendingUpload);
return true;
}
bool IsTextureUploadComplete(const PendingTextureUpload& upload) const {
return m_uploadFence != nullptr && m_uploadFence->GetCompletedValue() >= upload.fenceValue;
}
void RemoveCompletedTextureUploads(
std::vector<std::unique_ptr<PendingTextureUpload>>* uploads) const {
if (uploads == nullptr || m_uploadFence == nullptr) {
return;
}
const UINT64 completedFenceValue = m_uploadFence->GetCompletedValue();
uploads->erase(
std::remove_if(
uploads->begin(),
uploads->end(),
[completedFenceValue](const std::unique_ptr<PendingTextureUpload>& upload) {
return upload != nullptr && completedFenceValue >= upload->fenceValue;
}),
uploads->end());
}
bool WaitForQueueIdle(ID3D12Device* device, ID3D12CommandQueue* commandQueue) const {
if (device == nullptr || commandQueue == nullptr) {
return false;
}
Microsoft::WRL::ComPtr<ID3D12Fence> fence;
if (FAILED(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)))) {
return false;
}
HANDLE eventHandle = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if (eventHandle == nullptr) {
return false;
}
constexpr UINT64 kFenceValue = 1;
const HRESULT signalHr = commandQueue->Signal(fence.Get(), kFenceValue);
if (FAILED(signalHr)) {
CloseHandle(eventHandle);
return false;
}
if (fence->GetCompletedValue() < kFenceValue) {
if (FAILED(fence->SetEventOnCompletion(kFenceValue, eventHandle))) {
CloseHandle(eventHandle);
return false;
}
WaitForSingleObject(eventHandle, INFINITE);
}
CloseHandle(eventHandle);
return true;
}
static bool HandleWindowMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
return ImGui_ImplWin32_WndProcHandler(hwnd, msg, wParam, lParam) != 0;
}
@@ -205,12 +481,22 @@ private:
}
}
bool EnsureUploadFence(ID3D12Device* device) {
if (m_uploadFence) {
return true;
}
return SUCCEEDED(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_uploadFence)));
}
bool m_initialized = false;
ID3D12DescriptorHeap* m_srvHeap = nullptr;
UINT m_srvDescriptorSize = 0;
D3D12_CPU_DESCRIPTOR_HANDLE m_srvCpuStart = {};
D3D12_GPU_DESCRIPTOR_HANDLE m_srvGpuStart = {};
std::vector<bool> m_srvUsage;
Microsoft::WRL::ComPtr<ID3D12Fence> m_uploadFence;
UINT64 m_nextUploadFenceValue = 1;
};
} // namespace UI

View File

@@ -11,6 +11,7 @@ set(EDITOR_TEST_SOURCES
test_scene_viewport_picker.cpp
test_scene_viewport_overlay_renderer.cpp
test_viewport_host_surface_utils.cpp
test_builtin_icon_layout_utils.cpp
${CMAKE_SOURCE_DIR}/editor/src/Core/UndoManager.cpp
${CMAKE_SOURCE_DIR}/editor/src/Managers/SceneManager.cpp
${CMAKE_SOURCE_DIR}/editor/src/Managers/ProjectManager.cpp

View File

@@ -0,0 +1,40 @@
#include <gtest/gtest.h>
#include "UI/BuiltInIconLayoutUtils.h"
namespace {
using XCEngine::Editor::UI::ComputeFittedIconSize;
TEST(BuiltInIconLayoutUtilsTest, ReturnsZeroWhenTextureOrBoundsAreInvalid) {
EXPECT_EQ(
ComputeFittedIconSize(0, 64, ImVec2(0.0f, 0.0f), ImVec2(64.0f, 64.0f)).x,
0.0f);
EXPECT_EQ(
ComputeFittedIconSize(64, 64, ImVec2(10.0f, 0.0f), ImVec2(10.0f, 64.0f)).y,
0.0f);
}
TEST(BuiltInIconLayoutUtilsTest, FitsWidthLimitedRegions) {
const ImVec2 size = ComputeFittedIconSize(
256,
128,
ImVec2(0.0f, 0.0f),
ImVec2(64.0f, 64.0f));
EXPECT_FLOAT_EQ(size.x, 64.0f);
EXPECT_FLOAT_EQ(size.y, 32.0f);
}
TEST(BuiltInIconLayoutUtilsTest, FitsHeightLimitedRegions) {
const ImVec2 size = ComputeFittedIconSize(
128,
256,
ImVec2(0.0f, 0.0f),
ImVec2(80.0f, 40.0f));
EXPECT_FLOAT_EQ(size.x, 20.0f);
EXPECT_FLOAT_EQ(size.y, 40.0f);
}
} // namespace