2026-04-21 20:49:18 +08:00
|
|
|
#include "D3D12UiTextureHost.h"
|
|
|
|
|
|
|
|
|
|
#include <XCEngine/RHI/D3D12/D3D12CommandList.h>
|
|
|
|
|
#include <XCEngine/RHI/D3D12/D3D12Enums.h>
|
|
|
|
|
#include <XCEngine/RHI/D3D12/D3D12Texture.h>
|
|
|
|
|
#include <XCEngine/RHI/RHIDevice.h>
|
|
|
|
|
#include <XCEngine/RHI/RHIEnums.h>
|
|
|
|
|
#include <XCEngine/RHI/RHITypes.h>
|
2026-04-22 00:19:19 +08:00
|
|
|
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
|
2026-04-21 20:49:18 +08:00
|
|
|
|
|
|
|
|
#include <cstdio>
|
|
|
|
|
#include <limits>
|
2026-04-22 00:19:19 +08:00
|
|
|
#include <sstream>
|
2026-04-21 20:49:18 +08:00
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
|
|
namespace XCEngine::UI::Editor::Host {
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
std::string HrToString(const char* operation, HRESULT hr) {
|
|
|
|
|
char buffer[128] = {};
|
|
|
|
|
sprintf_s(buffer, "%s failed with hr=0x%08X.", operation, static_cast<unsigned int>(hr));
|
|
|
|
|
return buffer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constexpr std::uint32_t kTextureBytesPerPixel = 4u;
|
|
|
|
|
|
|
|
|
|
std::uint32_t AlignTextureRowPitch(std::uint32_t rowPitch) {
|
|
|
|
|
constexpr std::uint32_t alignment = D3D12_TEXTURE_DATA_PITCH_ALIGNMENT;
|
|
|
|
|
return (rowPitch + alignment - 1u) & ~(alignment - 1u);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
bool D3D12UiTextureHost::Initialize(D3D12WindowRenderer& windowRenderer) {
|
|
|
|
|
Shutdown();
|
|
|
|
|
|
|
|
|
|
if (windowRenderer.GetRHIDevice() == nullptr) {
|
|
|
|
|
m_lastError = "Initialize requires an initialized D3D12 window renderer.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_windowRenderer = &windowRenderer;
|
|
|
|
|
m_lastError.clear();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void D3D12UiTextureHost::BeginFrame(std::uint32_t frameSlot) {
|
|
|
|
|
if (frameSlot >= m_frameUploadBuffers.size()) {
|
|
|
|
|
m_hasActiveFrameSlot = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 00:19:19 +08:00
|
|
|
if (!m_retiredTextures[frameSlot].empty()) {
|
|
|
|
|
std::ostringstream stream = {};
|
|
|
|
|
stream << "D3D12UiTextureHost::BeginFrame reclaim frameSlot=" << frameSlot
|
|
|
|
|
<< " retiredCount=" << m_retiredTextures[frameSlot].size();
|
|
|
|
|
AppendUIEditorRuntimeTrace("window-close", stream.str());
|
|
|
|
|
}
|
|
|
|
|
DestroyQueuedRetiredTextures(frameSlot);
|
2026-04-21 20:49:18 +08:00
|
|
|
m_frameUploadBuffers[frameSlot].clear();
|
|
|
|
|
m_activeFrameSlot = frameSlot;
|
|
|
|
|
m_hasActiveFrameSlot = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void D3D12UiTextureHost::Shutdown() {
|
2026-04-22 00:19:19 +08:00
|
|
|
std::size_t retiredTextureCount = 0u;
|
|
|
|
|
for (const auto& retiredTextures : m_retiredTextures) {
|
|
|
|
|
retiredTextureCount += retiredTextures.size();
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
std::ostringstream stream = {};
|
|
|
|
|
stream << "D3D12UiTextureHost::Shutdown begin liveTextures=" << m_liveTextures.size()
|
|
|
|
|
<< " retiredTextures=" << retiredTextureCount
|
|
|
|
|
<< " activeFrameSlot=" << m_activeFrameSlot
|
|
|
|
|
<< " hasActiveFrameSlot=" << (m_hasActiveFrameSlot ? 1 : 0);
|
|
|
|
|
AppendUIEditorRuntimeTrace("window-close", stream.str());
|
|
|
|
|
}
|
|
|
|
|
if (m_windowRenderer != nullptr) {
|
|
|
|
|
m_windowRenderer->WaitForGpuIdle();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 20:49:18 +08:00
|
|
|
for (auto& entry : m_liveTextures) {
|
|
|
|
|
if (entry.second != nullptr) {
|
|
|
|
|
DestroyTextureResource(*entry.second);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
m_liveTextures.clear();
|
2026-04-22 00:19:19 +08:00
|
|
|
for (auto& retiredTextures : m_retiredTextures) {
|
|
|
|
|
for (auto& textureResource : retiredTextures) {
|
|
|
|
|
if (textureResource != nullptr) {
|
|
|
|
|
DestroyTextureResource(*textureResource);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
retiredTextures.clear();
|
|
|
|
|
}
|
2026-04-21 20:49:18 +08:00
|
|
|
for (auto& uploadBuffers : m_frameUploadBuffers) {
|
|
|
|
|
uploadBuffers.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_wicFactory.Reset();
|
|
|
|
|
if (m_wicComInitialized) {
|
|
|
|
|
CoUninitialize();
|
|
|
|
|
m_wicComInitialized = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_windowRenderer = nullptr;
|
|
|
|
|
m_activeFrameSlot = 0u;
|
|
|
|
|
m_hasActiveFrameSlot = false;
|
|
|
|
|
m_lastError.clear();
|
2026-04-22 00:19:19 +08:00
|
|
|
AppendUIEditorRuntimeTrace("window-close", "D3D12UiTextureHost::Shutdown end");
|
2026-04-21 20:49:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool D3D12UiTextureHost::IsInitialized() const {
|
|
|
|
|
return m_windowRenderer != nullptr &&
|
|
|
|
|
m_windowRenderer->GetRHIDevice() != nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::string& D3D12UiTextureHost::GetLastError() const {
|
|
|
|
|
return m_lastError;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool D3D12UiTextureHost::EnsureWicFactory(std::string& outError) {
|
|
|
|
|
outError.clear();
|
|
|
|
|
if (m_wicFactory != nullptr) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const HRESULT initHr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
|
|
|
|
if (FAILED(initHr) && initHr != RPC_E_CHANGED_MODE) {
|
|
|
|
|
outError = HrToString("CoInitializeEx", initHr);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (SUCCEEDED(initHr)) {
|
|
|
|
|
m_wicComInitialized = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const HRESULT factoryHr = CoCreateInstance(
|
|
|
|
|
CLSID_WICImagingFactory,
|
|
|
|
|
nullptr,
|
|
|
|
|
CLSCTX_INPROC_SERVER,
|
|
|
|
|
IID_PPV_ARGS(m_wicFactory.ReleaseAndGetAddressOf()));
|
|
|
|
|
if (FAILED(factoryHr)) {
|
|
|
|
|
outError = HrToString("CoCreateInstance(CLSID_WICImagingFactory)", factoryHr);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool D3D12UiTextureHost::DecodeTextureFile(
|
|
|
|
|
const std::filesystem::path& path,
|
|
|
|
|
std::vector<std::uint8_t>& outPixels,
|
|
|
|
|
std::uint32_t& outWidth,
|
|
|
|
|
std::uint32_t& outHeight,
|
|
|
|
|
std::string& outError) {
|
|
|
|
|
outPixels.clear();
|
|
|
|
|
outWidth = 0u;
|
|
|
|
|
outHeight = 0u;
|
|
|
|
|
outError.clear();
|
|
|
|
|
|
|
|
|
|
if (!EnsureWicFactory(outError)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Microsoft::WRL::ComPtr<IWICBitmapDecoder> decoder = {};
|
|
|
|
|
const HRESULT hr = m_wicFactory->CreateDecoderFromFilename(
|
|
|
|
|
path.wstring().c_str(),
|
|
|
|
|
nullptr,
|
|
|
|
|
GENERIC_READ,
|
|
|
|
|
WICDecodeMetadataCacheOnLoad,
|
|
|
|
|
decoder.ReleaseAndGetAddressOf());
|
|
|
|
|
if (FAILED(hr) || decoder == nullptr) {
|
|
|
|
|
outError = HrToString("IWICImagingFactory::CreateDecoderFromFilename", hr);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Microsoft::WRL::ComPtr<IWICBitmapFrameDecode> frame = {};
|
|
|
|
|
const HRESULT frameHr = decoder->GetFrame(0u, frame.ReleaseAndGetAddressOf());
|
|
|
|
|
if (FAILED(frameHr) || frame == nullptr) {
|
|
|
|
|
outError = HrToString("IWICBitmapDecoder::GetFrame", frameHr);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return DecodeTextureFrame(*frame.Get(), outPixels, outWidth, outHeight, outError);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool D3D12UiTextureHost::DecodeTextureMemory(
|
|
|
|
|
const std::uint8_t* data,
|
|
|
|
|
std::size_t size,
|
|
|
|
|
std::vector<std::uint8_t>& outPixels,
|
|
|
|
|
std::uint32_t& outWidth,
|
|
|
|
|
std::uint32_t& outHeight,
|
|
|
|
|
std::string& outError) {
|
|
|
|
|
outPixels.clear();
|
|
|
|
|
outWidth = 0u;
|
|
|
|
|
outHeight = 0u;
|
|
|
|
|
outError.clear();
|
|
|
|
|
|
|
|
|
|
if (data == nullptr || size == 0u) {
|
|
|
|
|
outError = "DecodeTextureMemory rejected an empty image payload.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (size > static_cast<std::size_t>((std::numeric_limits<DWORD>::max)())) {
|
|
|
|
|
outError = "DecodeTextureMemory payload exceeds WIC stream limits.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!EnsureWicFactory(outError)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Microsoft::WRL::ComPtr<IWICStream> stream = {};
|
|
|
|
|
HRESULT hr = m_wicFactory->CreateStream(stream.ReleaseAndGetAddressOf());
|
|
|
|
|
if (FAILED(hr) || stream == nullptr) {
|
|
|
|
|
outError = HrToString("IWICImagingFactory::CreateStream", hr);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hr = stream->InitializeFromMemory(
|
|
|
|
|
const_cast<BYTE*>(reinterpret_cast<const BYTE*>(data)),
|
|
|
|
|
static_cast<DWORD>(size));
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
|
outError = HrToString("IWICStream::InitializeFromMemory", hr);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Microsoft::WRL::ComPtr<IWICBitmapDecoder> decoder = {};
|
|
|
|
|
hr = m_wicFactory->CreateDecoderFromStream(
|
|
|
|
|
stream.Get(),
|
|
|
|
|
nullptr,
|
|
|
|
|
WICDecodeMetadataCacheOnLoad,
|
|
|
|
|
decoder.ReleaseAndGetAddressOf());
|
|
|
|
|
if (FAILED(hr) || decoder == nullptr) {
|
|
|
|
|
outError = HrToString("IWICImagingFactory::CreateDecoderFromStream", hr);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Microsoft::WRL::ComPtr<IWICBitmapFrameDecode> frame = {};
|
|
|
|
|
hr = decoder->GetFrame(0u, frame.ReleaseAndGetAddressOf());
|
|
|
|
|
if (FAILED(hr) || frame == nullptr) {
|
|
|
|
|
outError = HrToString("IWICBitmapDecoder::GetFrame", hr);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return DecodeTextureFrame(*frame.Get(), outPixels, outWidth, outHeight, outError);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool D3D12UiTextureHost::DecodeTextureFrame(
|
|
|
|
|
IWICBitmapSource& source,
|
|
|
|
|
std::vector<std::uint8_t>& outPixels,
|
|
|
|
|
std::uint32_t& outWidth,
|
|
|
|
|
std::uint32_t& outHeight,
|
|
|
|
|
std::string& outError) {
|
|
|
|
|
outPixels.clear();
|
|
|
|
|
outWidth = 0u;
|
|
|
|
|
outHeight = 0u;
|
|
|
|
|
outError.clear();
|
|
|
|
|
|
|
|
|
|
Microsoft::WRL::ComPtr<IWICFormatConverter> converter = {};
|
|
|
|
|
HRESULT hr = m_wicFactory->CreateFormatConverter(converter.ReleaseAndGetAddressOf());
|
|
|
|
|
if (FAILED(hr) || converter == nullptr) {
|
|
|
|
|
outError = HrToString("IWICImagingFactory::CreateFormatConverter", hr);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hr = converter->Initialize(
|
|
|
|
|
&source,
|
|
|
|
|
GUID_WICPixelFormat32bppRGBA,
|
|
|
|
|
WICBitmapDitherTypeNone,
|
|
|
|
|
nullptr,
|
|
|
|
|
0.0f,
|
|
|
|
|
WICBitmapPaletteTypeCustom);
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
|
outError = HrToString("IWICFormatConverter::Initialize", hr);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UINT width = 0u;
|
|
|
|
|
UINT height = 0u;
|
|
|
|
|
hr = converter->GetSize(&width, &height);
|
|
|
|
|
if (FAILED(hr) || width == 0u || height == 0u) {
|
|
|
|
|
outError = HrToString("IWICBitmapSource::GetSize", hr);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<std::uint8_t> pixels(
|
|
|
|
|
static_cast<std::size_t>(width) * static_cast<std::size_t>(height) * 4u);
|
|
|
|
|
hr = converter->CopyPixels(
|
|
|
|
|
nullptr,
|
|
|
|
|
width * 4u,
|
|
|
|
|
static_cast<UINT>(pixels.size()),
|
|
|
|
|
pixels.data());
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
|
outError = HrToString("IWICBitmapSource::CopyPixels", hr);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
outPixels = std::move(pixels);
|
|
|
|
|
outWidth = static_cast<std::uint32_t>(width);
|
|
|
|
|
outHeight = static_cast<std::uint32_t>(height);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool D3D12UiTextureHost::CreateTextureResource(
|
|
|
|
|
const std::uint8_t* rgbaPixels,
|
|
|
|
|
std::uint32_t width,
|
|
|
|
|
std::uint32_t height,
|
|
|
|
|
::XCEngine::UI::UITextureHandle& outTexture,
|
|
|
|
|
std::string& outError) {
|
|
|
|
|
ReleaseTexture(outTexture);
|
|
|
|
|
outTexture = {};
|
|
|
|
|
outError.clear();
|
|
|
|
|
|
|
|
|
|
if (!IsInitialized()) {
|
|
|
|
|
outError = "CreateTextureResource requires an initialized D3D12 UI texture host.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (rgbaPixels == nullptr || width == 0u || height == 0u) {
|
|
|
|
|
outError = "CreateTextureResource rejected an empty RGBA payload.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
::XCEngine::RHI::TextureDesc textureDesc = {};
|
|
|
|
|
textureDesc.width = width;
|
|
|
|
|
textureDesc.height = height;
|
|
|
|
|
textureDesc.depth = 1u;
|
|
|
|
|
textureDesc.mipLevels = 1u;
|
|
|
|
|
textureDesc.arraySize = 1u;
|
|
|
|
|
textureDesc.format = static_cast<std::uint32_t>(::XCEngine::RHI::Format::R8G8B8A8_UNorm);
|
|
|
|
|
textureDesc.textureType =
|
|
|
|
|
static_cast<std::uint32_t>(::XCEngine::RHI::TextureType::Texture2D);
|
|
|
|
|
textureDesc.sampleCount = 1u;
|
|
|
|
|
textureDesc.sampleQuality = 0u;
|
|
|
|
|
textureDesc.flags = 0u;
|
|
|
|
|
|
|
|
|
|
const std::size_t pixelBytes =
|
|
|
|
|
static_cast<std::size_t>(width) * static_cast<std::size_t>(height) * 4u;
|
|
|
|
|
::XCEngine::RHI::RHITexture* texture = nullptr;
|
|
|
|
|
Microsoft::WRL::ComPtr<ID3D12Resource> uploadBuffer = {};
|
|
|
|
|
if (m_hasActiveFrameSlot && m_windowRenderer->GetDevice() != nullptr) {
|
|
|
|
|
const ::XCEngine::Rendering::RenderContext renderContext =
|
|
|
|
|
m_windowRenderer->GetRenderContext();
|
|
|
|
|
auto* d3d12CommandList =
|
|
|
|
|
renderContext.commandList != nullptr
|
|
|
|
|
? static_cast<::XCEngine::RHI::D3D12CommandList*>(renderContext.commandList)
|
|
|
|
|
: nullptr;
|
|
|
|
|
if (d3d12CommandList != nullptr && d3d12CommandList->GetCommandList() != nullptr) {
|
|
|
|
|
auto* d3d12Texture = new ::XCEngine::RHI::D3D12Texture();
|
|
|
|
|
D3D12_RESOURCE_DESC nativeTextureDesc = {};
|
|
|
|
|
nativeTextureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
|
|
|
|
nativeTextureDesc.Alignment = 0;
|
|
|
|
|
nativeTextureDesc.Width = width;
|
|
|
|
|
nativeTextureDesc.Height = height;
|
|
|
|
|
nativeTextureDesc.DepthOrArraySize = 1u;
|
|
|
|
|
nativeTextureDesc.MipLevels = 1u;
|
|
|
|
|
nativeTextureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
|
|
|
nativeTextureDesc.SampleDesc.Count = 1u;
|
|
|
|
|
nativeTextureDesc.SampleDesc.Quality = 0u;
|
|
|
|
|
nativeTextureDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
|
|
|
|
|
nativeTextureDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
|
|
|
|
|
if (d3d12Texture->InitializeFromData(
|
|
|
|
|
m_windowRenderer->GetDevice(),
|
|
|
|
|
d3d12CommandList->GetCommandList(),
|
|
|
|
|
nativeTextureDesc,
|
|
|
|
|
::XCEngine::RHI::TextureType::Texture2D,
|
|
|
|
|
rgbaPixels,
|
|
|
|
|
pixelBytes,
|
|
|
|
|
width * 4u,
|
|
|
|
|
&uploadBuffer)) {
|
|
|
|
|
texture = d3d12Texture;
|
|
|
|
|
} else {
|
|
|
|
|
delete d3d12Texture;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (texture == nullptr) {
|
|
|
|
|
texture = m_windowRenderer->GetRHIDevice()->CreateTexture(
|
|
|
|
|
textureDesc,
|
|
|
|
|
rgbaPixels,
|
|
|
|
|
pixelBytes,
|
|
|
|
|
width * 4u);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (texture == nullptr) {
|
|
|
|
|
outError = "Failed to create the GPU texture.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle = {};
|
|
|
|
|
D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle = {};
|
|
|
|
|
if (!m_windowRenderer->GetTextureDescriptorAllocator().CreateTextureDescriptor(
|
|
|
|
|
texture,
|
|
|
|
|
&cpuHandle,
|
|
|
|
|
&gpuHandle) ||
|
|
|
|
|
cpuHandle.ptr == 0u ||
|
|
|
|
|
gpuHandle.ptr == 0u) {
|
|
|
|
|
texture->Shutdown();
|
|
|
|
|
delete texture;
|
|
|
|
|
outError = "Failed to allocate the shader resource descriptor for the GPU texture.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto textureResource = std::make_unique<TextureResource>();
|
|
|
|
|
textureResource->texture = texture;
|
|
|
|
|
textureResource->cpuHandle = cpuHandle;
|
|
|
|
|
textureResource->gpuHandle = gpuHandle;
|
|
|
|
|
textureResource->width = width;
|
|
|
|
|
textureResource->height = height;
|
|
|
|
|
if (uploadBuffer != nullptr &&
|
|
|
|
|
m_hasActiveFrameSlot &&
|
|
|
|
|
m_activeFrameSlot < m_frameUploadBuffers.size()) {
|
|
|
|
|
m_frameUploadBuffers[m_activeFrameSlot].push_back(std::move(uploadBuffer));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::uintptr_t resourceKey =
|
|
|
|
|
reinterpret_cast<std::uintptr_t>(textureResource.get());
|
|
|
|
|
outTexture.nativeHandle = static_cast<std::uintptr_t>(gpuHandle.ptr);
|
|
|
|
|
outTexture.width = width;
|
|
|
|
|
outTexture.height = height;
|
|
|
|
|
outTexture.kind = ::XCEngine::UI::UITextureHandleKind::ShaderResourceView;
|
|
|
|
|
outTexture.resourceHandle = resourceKey;
|
|
|
|
|
m_liveTextures.emplace(resourceKey, std::move(textureResource));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 00:19:19 +08:00
|
|
|
void D3D12UiTextureHost::DestroyQueuedRetiredTextures(std::uint32_t frameSlot) {
|
|
|
|
|
if (frameSlot >= m_retiredTextures.size()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (auto& textureResource : m_retiredTextures[frameSlot]) {
|
|
|
|
|
if (textureResource != nullptr) {
|
|
|
|
|
DestroyTextureResource(*textureResource);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
m_retiredTextures[frameSlot].clear();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 20:49:18 +08:00
|
|
|
D3D12UiTextureHost::TextureResource* D3D12UiTextureHost::ResolveTextureResource(
|
|
|
|
|
const ::XCEngine::UI::UITextureHandle& texture) const {
|
|
|
|
|
if (!texture.IsValid() || texture.resourceHandle == 0u) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto found = m_liveTextures.find(texture.resourceHandle);
|
|
|
|
|
if (found == m_liveTextures.end() || found->second == nullptr) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return found->second.get();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void D3D12UiTextureHost::DestroyTextureResource(TextureResource& textureResource) {
|
|
|
|
|
if (m_windowRenderer != nullptr &&
|
|
|
|
|
textureResource.cpuHandle.ptr != 0u &&
|
|
|
|
|
textureResource.gpuHandle.ptr != 0u) {
|
|
|
|
|
m_windowRenderer->GetTextureDescriptorAllocator().Free(
|
|
|
|
|
textureResource.cpuHandle,
|
|
|
|
|
textureResource.gpuHandle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (textureResource.texture != nullptr) {
|
|
|
|
|
textureResource.texture->Shutdown();
|
|
|
|
|
delete textureResource.texture;
|
|
|
|
|
textureResource.texture = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
textureResource.cpuHandle = {};
|
|
|
|
|
textureResource.gpuHandle = {};
|
|
|
|
|
textureResource.width = 0u;
|
|
|
|
|
textureResource.height = 0u;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 00:19:19 +08:00
|
|
|
void D3D12UiTextureHost::RetireTextureResource(
|
|
|
|
|
std::unique_ptr<TextureResource> textureResource) {
|
|
|
|
|
if (textureResource == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_hasActiveFrameSlot && m_activeFrameSlot < m_retiredTextures.size()) {
|
|
|
|
|
m_retiredTextures[m_activeFrameSlot].push_back(std::move(textureResource));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AppendUIEditorRuntimeTrace(
|
|
|
|
|
"window-close",
|
|
|
|
|
"D3D12UiTextureHost::RetireTextureResource forcing WaitForGpuIdle for immediate destruction");
|
|
|
|
|
if (m_windowRenderer != nullptr) {
|
|
|
|
|
m_windowRenderer->WaitForGpuIdle();
|
|
|
|
|
}
|
|
|
|
|
DestroyTextureResource(*textureResource);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 20:49:18 +08:00
|
|
|
bool D3D12UiTextureHost::UpdateTextureRegionRgba(
|
|
|
|
|
const ::XCEngine::UI::UITextureHandle& texture,
|
|
|
|
|
std::uint32_t dstX,
|
|
|
|
|
std::uint32_t dstY,
|
|
|
|
|
const std::uint8_t* rgbaPixels,
|
|
|
|
|
std::uint32_t width,
|
|
|
|
|
std::uint32_t height,
|
|
|
|
|
std::uint32_t rowPitch,
|
|
|
|
|
std::string& outError) {
|
|
|
|
|
outError.clear();
|
|
|
|
|
|
|
|
|
|
if (!IsInitialized()) {
|
|
|
|
|
outError = "UpdateTextureRegionRgba requires an initialized D3D12 UI texture host.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!m_hasActiveFrameSlot || m_activeFrameSlot >= m_frameUploadBuffers.size()) {
|
|
|
|
|
outError = "UpdateTextureRegionRgba requires an active frame slot.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (rgbaPixels == nullptr || width == 0u || height == 0u) {
|
|
|
|
|
outError = "UpdateTextureRegionRgba rejected an empty RGBA payload.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TextureResource* textureResource = ResolveTextureResource(texture);
|
|
|
|
|
if (textureResource == nullptr || textureResource->texture == nullptr) {
|
|
|
|
|
outError = "UpdateTextureRegionRgba could not resolve the destination texture.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dstX + width > textureResource->width || dstY + height > textureResource->height) {
|
|
|
|
|
outError = "UpdateTextureRegionRgba target rectangle exceeds texture bounds.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ::XCEngine::Rendering::RenderContext renderContext = m_windowRenderer->GetRenderContext();
|
|
|
|
|
auto* d3d12CommandList =
|
|
|
|
|
renderContext.commandList != nullptr
|
|
|
|
|
? static_cast<::XCEngine::RHI::D3D12CommandList*>(renderContext.commandList)
|
|
|
|
|
: nullptr;
|
|
|
|
|
if (d3d12CommandList == nullptr || d3d12CommandList->GetCommandList() == nullptr) {
|
|
|
|
|
outError = "UpdateTextureRegionRgba requires an active D3D12 command list.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto* d3d12Texture =
|
|
|
|
|
dynamic_cast<::XCEngine::RHI::D3D12Texture*>(textureResource->texture);
|
|
|
|
|
if (d3d12Texture == nullptr || d3d12Texture->GetResource() == nullptr) {
|
|
|
|
|
outError = "UpdateTextureRegionRgba requires a native D3D12 destination texture.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::uint32_t resolvedRowPitch =
|
|
|
|
|
rowPitch > 0u ? rowPitch : width * kTextureBytesPerPixel;
|
|
|
|
|
if (resolvedRowPitch < width * kTextureBytesPerPixel) {
|
|
|
|
|
outError = "UpdateTextureRegionRgba row pitch is smaller than the texture row width.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::uint32_t alignedRowPitch = AlignTextureRowPitch(width * kTextureBytesPerPixel);
|
|
|
|
|
const std::uint64_t uploadBytes =
|
|
|
|
|
static_cast<std::uint64_t>(alignedRowPitch) * static_cast<std::uint64_t>(height);
|
|
|
|
|
|
|
|
|
|
D3D12_HEAP_PROPERTIES uploadHeapProperties = {};
|
|
|
|
|
uploadHeapProperties.Type = D3D12_HEAP_TYPE_UPLOAD;
|
|
|
|
|
|
|
|
|
|
D3D12_RESOURCE_DESC uploadDesc = {};
|
|
|
|
|
uploadDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
|
|
|
|
|
uploadDesc.Alignment = 0;
|
|
|
|
|
uploadDesc.Width = uploadBytes;
|
|
|
|
|
uploadDesc.Height = 1;
|
|
|
|
|
uploadDesc.DepthOrArraySize = 1;
|
|
|
|
|
uploadDesc.MipLevels = 1;
|
|
|
|
|
uploadDesc.Format = DXGI_FORMAT_UNKNOWN;
|
|
|
|
|
uploadDesc.SampleDesc.Count = 1;
|
|
|
|
|
uploadDesc.SampleDesc.Quality = 0;
|
|
|
|
|
uploadDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
|
|
|
|
|
uploadDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
|
|
|
|
|
|
|
|
|
|
Microsoft::WRL::ComPtr<ID3D12Resource> uploadBuffer = {};
|
|
|
|
|
HRESULT hr = m_windowRenderer->GetDevice()->CreateCommittedResource(
|
|
|
|
|
&uploadHeapProperties,
|
|
|
|
|
D3D12_HEAP_FLAG_NONE,
|
|
|
|
|
&uploadDesc,
|
|
|
|
|
D3D12_RESOURCE_STATE_GENERIC_READ,
|
|
|
|
|
nullptr,
|
|
|
|
|
IID_PPV_ARGS(uploadBuffer.ReleaseAndGetAddressOf()));
|
|
|
|
|
if (FAILED(hr) || uploadBuffer == nullptr) {
|
|
|
|
|
outError = HrToString("ID3D12Device::CreateCommittedResource(upload)", hr);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::uint8_t* mappedData = nullptr;
|
|
|
|
|
hr = uploadBuffer->Map(0u, nullptr, reinterpret_cast<void**>(&mappedData));
|
|
|
|
|
if (FAILED(hr) || mappedData == nullptr) {
|
|
|
|
|
outError = HrToString("ID3D12Resource::Map(upload)", hr);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (std::uint32_t row = 0u; row < height; ++row) {
|
|
|
|
|
std::memcpy(
|
|
|
|
|
mappedData + static_cast<std::size_t>(row) * alignedRowPitch,
|
|
|
|
|
rgbaPixels + static_cast<std::size_t>(row) * resolvedRowPitch,
|
|
|
|
|
static_cast<std::size_t>(width) * kTextureBytesPerPixel);
|
|
|
|
|
}
|
|
|
|
|
uploadBuffer->Unmap(0u, nullptr);
|
|
|
|
|
|
|
|
|
|
ID3D12GraphicsCommandList* nativeCommandList = d3d12CommandList->GetCommandList();
|
|
|
|
|
const ::XCEngine::RHI::ResourceStates originalState = d3d12Texture->GetState();
|
|
|
|
|
if (originalState != ::XCEngine::RHI::ResourceStates::CopyDst) {
|
|
|
|
|
D3D12_RESOURCE_BARRIER toCopyBarrier = {};
|
|
|
|
|
toCopyBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
|
|
|
|
|
toCopyBarrier.Transition.pResource = d3d12Texture->GetResource();
|
|
|
|
|
toCopyBarrier.Transition.StateBefore = ::XCEngine::RHI::ToD3D12(originalState);
|
|
|
|
|
toCopyBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST;
|
|
|
|
|
toCopyBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
|
|
|
|
|
nativeCommandList->ResourceBarrier(1u, &toCopyBarrier);
|
|
|
|
|
d3d12Texture->SetState(::XCEngine::RHI::ResourceStates::CopyDst);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
D3D12_TEXTURE_COPY_LOCATION dstLocation = {};
|
|
|
|
|
dstLocation.pResource = d3d12Texture->GetResource();
|
|
|
|
|
dstLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
|
|
|
|
|
dstLocation.SubresourceIndex = 0u;
|
|
|
|
|
|
|
|
|
|
D3D12_TEXTURE_COPY_LOCATION srcLocation = {};
|
|
|
|
|
srcLocation.pResource = uploadBuffer.Get();
|
|
|
|
|
srcLocation.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
|
|
|
|
|
srcLocation.PlacedFootprint.Offset = 0u;
|
|
|
|
|
srcLocation.PlacedFootprint.Footprint.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
|
|
|
srcLocation.PlacedFootprint.Footprint.Width = width;
|
|
|
|
|
srcLocation.PlacedFootprint.Footprint.Height = height;
|
|
|
|
|
srcLocation.PlacedFootprint.Footprint.Depth = 1u;
|
|
|
|
|
srcLocation.PlacedFootprint.Footprint.RowPitch = alignedRowPitch;
|
|
|
|
|
|
|
|
|
|
D3D12_BOX srcBox = {};
|
|
|
|
|
srcBox.left = 0u;
|
|
|
|
|
srcBox.top = 0u;
|
|
|
|
|
srcBox.front = 0u;
|
|
|
|
|
srcBox.right = width;
|
|
|
|
|
srcBox.bottom = height;
|
|
|
|
|
srcBox.back = 1u;
|
|
|
|
|
nativeCommandList->CopyTextureRegion(
|
|
|
|
|
&dstLocation,
|
|
|
|
|
dstX,
|
|
|
|
|
dstY,
|
|
|
|
|
0u,
|
|
|
|
|
&srcLocation,
|
|
|
|
|
&srcBox);
|
|
|
|
|
|
|
|
|
|
if (originalState != ::XCEngine::RHI::ResourceStates::CopyDst) {
|
|
|
|
|
D3D12_RESOURCE_BARRIER restoreBarrier = {};
|
|
|
|
|
restoreBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
|
|
|
|
|
restoreBarrier.Transition.pResource = d3d12Texture->GetResource();
|
|
|
|
|
restoreBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
|
|
|
|
|
restoreBarrier.Transition.StateAfter = ::XCEngine::RHI::ToD3D12(originalState);
|
|
|
|
|
restoreBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
|
|
|
|
|
nativeCommandList->ResourceBarrier(1u, &restoreBarrier);
|
|
|
|
|
d3d12Texture->SetState(originalState);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_frameUploadBuffers[m_activeFrameSlot].push_back(std::move(uploadBuffer));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool D3D12UiTextureHost::LoadTextureFromFile(
|
|
|
|
|
const std::filesystem::path& path,
|
|
|
|
|
::XCEngine::UI::UITextureHandle& outTexture,
|
|
|
|
|
std::string& outError) {
|
|
|
|
|
std::vector<std::uint8_t> pixels = {};
|
|
|
|
|
std::uint32_t width = 0u;
|
|
|
|
|
std::uint32_t height = 0u;
|
|
|
|
|
if (!DecodeTextureFile(path, pixels, width, height, outError)) {
|
|
|
|
|
outTexture = {};
|
|
|
|
|
m_lastError = outError;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bool created = CreateTextureResource(
|
|
|
|
|
pixels.data(),
|
|
|
|
|
width,
|
|
|
|
|
height,
|
|
|
|
|
outTexture,
|
|
|
|
|
outError);
|
|
|
|
|
m_lastError = created ? std::string() : outError;
|
|
|
|
|
return created;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool D3D12UiTextureHost::LoadTextureFromMemory(
|
|
|
|
|
const std::uint8_t* data,
|
|
|
|
|
std::size_t size,
|
|
|
|
|
::XCEngine::UI::UITextureHandle& outTexture,
|
|
|
|
|
std::string& outError) {
|
|
|
|
|
std::vector<std::uint8_t> pixels = {};
|
|
|
|
|
std::uint32_t width = 0u;
|
|
|
|
|
std::uint32_t height = 0u;
|
|
|
|
|
if (!DecodeTextureMemory(data, size, pixels, width, height, outError)) {
|
|
|
|
|
outTexture = {};
|
|
|
|
|
m_lastError = outError;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bool created = CreateTextureResource(
|
|
|
|
|
pixels.data(),
|
|
|
|
|
width,
|
|
|
|
|
height,
|
|
|
|
|
outTexture,
|
|
|
|
|
outError);
|
|
|
|
|
m_lastError = created ? std::string() : outError;
|
|
|
|
|
return created;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool D3D12UiTextureHost::LoadTextureFromRgba(
|
|
|
|
|
const std::uint8_t* rgbaPixels,
|
|
|
|
|
std::uint32_t width,
|
|
|
|
|
std::uint32_t height,
|
|
|
|
|
::XCEngine::UI::UITextureHandle& outTexture,
|
|
|
|
|
std::string& outError) {
|
|
|
|
|
const bool created = CreateTextureResource(
|
|
|
|
|
rgbaPixels,
|
|
|
|
|
width,
|
|
|
|
|
height,
|
|
|
|
|
outTexture,
|
|
|
|
|
outError);
|
|
|
|
|
m_lastError = created ? std::string() : outError;
|
|
|
|
|
return created;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void D3D12UiTextureHost::ReleaseTexture(::XCEngine::UI::UITextureHandle& texture) {
|
|
|
|
|
if (!texture.IsValid()) {
|
|
|
|
|
texture = {};
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto found = m_liveTextures.find(texture.resourceHandle);
|
|
|
|
|
if (found != m_liveTextures.end() && found->second != nullptr) {
|
2026-04-22 00:19:19 +08:00
|
|
|
std::unique_ptr<TextureResource> retiredTexture = std::move(found->second);
|
2026-04-21 20:49:18 +08:00
|
|
|
m_liveTextures.erase(found);
|
2026-04-22 00:19:19 +08:00
|
|
|
RetireTextureResource(std::move(retiredTexture));
|
2026-04-21 20:49:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
texture = {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace XCEngine::UI::Editor::Host
|