refactor: move built-in icon upload into imgui backend
This commit is contained in:
32
editor/src/UI/BuiltInIconLayoutUtils.h
Normal file
32
editor/src/UI/BuiltInIconLayoutUtils.h
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
40
tests/editor/test_builtin_icon_layout_utils.cpp
Normal file
40
tests/editor/test_builtin_icon_layout_utils.cpp
Normal 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
|
||||
Reference in New Issue
Block a user