diff --git a/editor/src/UI/BuiltInIconLayoutUtils.h b/editor/src/UI/BuiltInIconLayoutUtils.h new file mode 100644 index 00000000..8596954f --- /dev/null +++ b/editor/src/UI/BuiltInIconLayoutUtils.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include + +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(textureWidth), + availableHeight / static_cast(textureHeight)); + return ImVec2( + static_cast(textureWidth) * scale, + static_cast(textureHeight) * scale); +} + +} // namespace UI +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/UI/BuiltInIcons.cpp b/editor/src/UI/BuiltInIcons.cpp index 71f0d058..beede77c 100644 --- a/editor/src/UI/BuiltInIcons.cpp +++ b/editor/src/UI/BuiltInIcons.cpp @@ -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 #include #include -#include #include @@ -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 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 rgbaPixels; @@ -50,13 +38,6 @@ struct LoadedTexturePixels { int height = 0; }; -struct PreviewGpuUpload { - ComPtr uploadResource; - ComPtr commandAllocator; - ComPtr 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 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 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& outPendingUpload) { - if (!device || !commandQueue || texturePixels.width <= 0 || texturePixels.height <= 0 || texturePixels.rgbaPixels.empty()) { - return false; - } - - const UINT srcRowPitch = static_cast(texturePixels.width * 4); - - D3D12_RESOURCE_DESC textureDesc = {}; - textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; - textureDesc.Alignment = 0; - textureDesc.Width = static_cast(texturePixels.width); - textureDesc.Height = static_cast(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 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 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(&mappedData)))) { - return false; - } - - for (UINT row = 0; row < numRows; ++row) { - std::memcpy( - mappedData + footprint.Offset + static_cast(row) * footprint.Footprint.RowPitch, - texturePixels.rgbaPixels.data() + static_cast(row) * srcRowPitch, - srcRowPitch); - } - uploadResource->Unmap(0, nullptr); - - ComPtr commandAllocator; - ComPtr 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(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(); - 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& 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(texture.width), - availableHeight / static_cast(texture.height)); - return ImVec2( - static_cast(texture.width) * scale, - static_cast(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 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; diff --git a/editor/src/UI/ImGuiBackendBridge.h b/editor/src/UI/ImGuiBackendBridge.h index a0df1e81..8e7720c5 100644 --- a/editor/src/UI/ImGuiBackendBridge.h +++ b/editor/src/UI/ImGuiBackendBridge.h @@ -13,7 +13,12 @@ #include #include #include +#include +#include +#include +#include #include +#include #include #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 texture; + int width = 0; + int height = 0; + + bool IsValid() const { + return textureId != ImTextureID{} && texture != nullptr && width > 0 && height > 0; + } + }; + + struct PendingTextureUpload { + Microsoft::WRL::ComPtr uploadResource; + Microsoft::WRL::ComPtr commandAllocator; + Microsoft::WRL::ComPtr 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* outPendingUpload) { + if (device == nullptr || + commandQueue == nullptr || + rgbaPixels == nullptr || + width <= 0 || + height <= 0 || + outTexture == nullptr || + outPendingUpload == nullptr) { + return false; + } + + const UINT srcRowPitch = static_cast(width * 4); + + D3D12_RESOURCE_DESC textureDesc = {}; + textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + textureDesc.Alignment = 0; + textureDesc.Width = static_cast(width); + textureDesc.Height = static_cast(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 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 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(&mappedData)))) { + return false; + } + + for (UINT row = 0; row < numRows; ++row) { + std::memcpy( + mappedData + footprint.Offset + static_cast(row) * footprint.Footprint.RowPitch, + rgbaPixels + static_cast(row) * srcRowPitch, + srcRowPitch); + } + uploadResource->Unmap(0, nullptr); + + Microsoft::WRL::ComPtr commandAllocator; + Microsoft::WRL::ComPtr 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(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(); + 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>* 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& 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 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 m_srvUsage; + Microsoft::WRL::ComPtr m_uploadFence; + UINT64 m_nextUploadFenceValue = 1; }; } // namespace UI diff --git a/tests/editor/CMakeLists.txt b/tests/editor/CMakeLists.txt index 157ee46c..f937d953 100644 --- a/tests/editor/CMakeLists.txt +++ b/tests/editor/CMakeLists.txt @@ -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 diff --git a/tests/editor/test_builtin_icon_layout_utils.cpp b/tests/editor/test_builtin_icon_layout_utils.cpp new file mode 100644 index 00000000..a48cdc8a --- /dev/null +++ b/tests/editor/test_builtin_icon_layout_utils.cpp @@ -0,0 +1,40 @@ +#include + +#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