checkpoint(new_editor): native d3d12 ui path

Key node 1: move main-window UI presentation onto the D3D12 render loop with native UI renderer, text system, and texture host.

Key node 2: wire frame timing/FPS display, window runtime, swapchain presentation, and native screenshot capture around the new path.

Key node 3: carry editor shell/workspace/viewport/panel interaction updates needed by the new renderer and detached window flow.

Key node 4: pump async resource loads and scene bridge follow-up needed for scene content visibility in new_editor.
This commit is contained in:
2026-04-21 20:49:18 +08:00
parent a779b04dba
commit cb8233ce72
53 changed files with 5330 additions and 858 deletions

View File

@@ -108,6 +108,7 @@ struct UIInputEvent {
UIInputModifiers modifiers = {};
bool repeat = false;
bool synthetic = false;
bool doubleClick = false;
};
} // namespace UI

View File

@@ -140,8 +140,14 @@ void D3D12PipelineState::SetInputLayout(const InputLayoutDesc& layout) {
desc.Format = ToD3D12(static_cast<Format>(elem.format));
desc.InputSlot = elem.inputSlot;
desc.AlignedByteOffset = elem.alignedByteOffset;
desc.InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA;
desc.InstanceDataStepRate = 0;
desc.InputSlotClass =
elem.inputSlotClass != 0u
? D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA
: D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA;
desc.InstanceDataStepRate =
elem.inputSlotClass != 0u
? (elem.instanceDataStepRate != 0u ? elem.instanceDataStepRate : 1u)
: 0u;
m_inputElements.push_back(desc);
}
}

View File

@@ -273,9 +273,14 @@ bool VulkanPipelineState::CreateGraphicsPipeline(const GraphicsPipelineDesc& des
}
std::map<uint32_t, uint32_t> strideBySlot;
std::map<uint32_t, VkVertexInputRate> inputRateBySlot;
for (const InputElementDesc& element : m_inputLayoutDesc.elements) {
const uint32_t attributeSize = GetFormatSize(static_cast<Format>(element.format));
strideBySlot[element.inputSlot] = (std::max)(strideBySlot[element.inputSlot], element.alignedByteOffset + attributeSize);
inputRateBySlot[element.inputSlot] =
element.inputSlotClass != 0u
? VK_VERTEX_INPUT_RATE_INSTANCE
: VK_VERTEX_INPUT_RATE_VERTEX;
}
std::vector<VkVertexInputBindingDescription> bindings;
@@ -284,7 +289,11 @@ bool VulkanPipelineState::CreateGraphicsPipeline(const GraphicsPipelineDesc& des
VkVertexInputBindingDescription binding = {};
binding.binding = entry.first;
binding.stride = entry.second;
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
const auto rateFound = inputRateBySlot.find(entry.first);
binding.inputRate =
rateFound != inputRateBySlot.end()
? rateFound->second
: VK_VERTEX_INPUT_RATE_VERTEX;
bindings.push_back(binding);
}

View File

@@ -168,6 +168,9 @@ set(XCUI_EDITOR_HOST_PLATFORM_SOURCES
set(XCUI_EDITOR_HOST_RENDERING_SOURCES
app/Rendering/Native/AutoScreenshot.cpp
app/Rendering/D3D12/D3D12HostDevice.cpp
app/Rendering/D3D12/D3D12UiRenderer.cpp
app/Rendering/D3D12/D3D12UiTextSystem.cpp
app/Rendering/D3D12/D3D12UiTextureHost.cpp
app/Rendering/D3D12/D3D12ShaderResourceDescriptorAllocator.cpp
app/Rendering/D3D12/D3D12WindowInteropContext.cpp
app/Rendering/D3D12/D3D12WindowRenderer.cpp

View File

@@ -228,6 +228,7 @@ int Application::Run(HINSTANCE hInstance, int nCmdShow) {
}
if (m_windowManager != nullptr) {
::XCEngine::Resources::ResourceManager::Get().UpdateAsyncLoads();
m_windowManager->DestroyClosedWindows();
if (!m_windowManager->HasWindows()) {
break;

View File

@@ -2,6 +2,7 @@
#include "Composition/EditorShellAssetBuilder.h"
#include "Scene/EditorSceneRuntime.h"
#include "Composition/EditorPanelIds.h"
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
#include <sstream>
#include <utility>
@@ -11,6 +12,7 @@ namespace {
using ::XCEngine::UI::Editor::BuildEditorShellShortcutManager;
using ::XCEngine::UI::Editor::UIEditorWorkspacePanelPresentationModel;
using ::XCEngine::UI::Editor::AppendUIEditorRuntimeTrace;
std::string ComposeStatusText(
std::string_view status,
@@ -41,8 +43,14 @@ UIEditorWorkspacePanelPresentationModel* FindMutablePresentation(
} // namespace
bool EditorContext::Initialize(const std::filesystem::path& repoRoot) {
AppendUIEditorRuntimeTrace("startup", "EditorContext::Initialize begin");
m_shellAsset = BuildEditorApplicationShellAsset(repoRoot);
AppendUIEditorRuntimeTrace("startup", "BuildEditorApplicationShellAsset complete");
m_shellValidation = ValidateEditorShellAsset(m_shellAsset);
AppendUIEditorRuntimeTrace(
"startup",
std::string("ValidateEditorShellAsset complete valid=") +
(m_shellValidation.IsValid() ? "1" : "0"));
if (!m_shellValidation.IsValid()) {
return false;
}
@@ -53,10 +61,14 @@ bool EditorContext::Initialize(const std::filesystem::path& repoRoot) {
m_commandFocusService = {};
m_selectionService = {};
m_projectRuntime = {};
AppendUIEditorRuntimeTrace("startup", "EditorProjectRuntime::Initialize begin");
m_projectRuntime.Initialize(repoRoot);
AppendUIEditorRuntimeTrace("startup", "EditorProjectRuntime::Initialize end");
m_projectRuntime.BindSelectionService(&m_selectionService);
m_sceneRuntime = {};
AppendUIEditorRuntimeTrace("startup", "EditorSceneRuntime::Initialize begin");
m_sceneRuntime.Initialize(m_session.projectRoot);
AppendUIEditorRuntimeTrace("startup", "EditorSceneRuntime::Initialize end");
m_sceneRuntime.BindSelectionService(&m_selectionService);
ResetEditorColorPickerToolState(m_colorPickerToolState);
SyncSessionFromSelectionService();
@@ -69,6 +81,7 @@ bool EditorContext::Initialize(const std::filesystem::path& repoRoot) {
m_shellServices.commandDispatcher = &m_shortcutManager.GetCommandDispatcher();
m_shellServices.shortcutManager = &m_shortcutManager;
SetReadyStatus();
AppendUIEditorRuntimeTrace("startup", "EditorContext::Initialize end");
return true;
}

View File

@@ -49,7 +49,6 @@ void EditorShellRuntime::Initialize(
m_projectPanel.SetBuiltInIcons(&m_builtInIcons);
m_projectPanel.SetTextMeasurer(&textMeasurer);
m_hierarchyPanel.Initialize();
m_projectPanel.Initialize(repoRoot);
}
void EditorShellRuntime::AttachViewportWindowRenderer(Ports::ViewportRenderPort& renderer) {
@@ -257,36 +256,120 @@ void AppendShellPopups(
namespace XCEngine::UI::Editor::App {
using ::XCEngine::UI::UIDrawList;
using ::XCEngine::UI::UIDrawData;
template<typename AppendFn>
void AppendDrawPacket(
UIDrawData& drawData,
std::string debugName,
AppendFn&& appendFn) {
UIDrawList drawList(std::move(debugName));
appendFn(drawList);
if (!drawList.Empty()) {
drawData.AddDrawList(std::move(drawList));
}
}
void EditorShellRuntime::RenderRequestedViewports(
const ::XCEngine::Rendering::RenderContext& renderContext) {
m_viewportHostService.RenderRequestedViewports(renderContext);
}
void EditorShellRuntime::Append(UIDrawList& drawList) const {
void EditorShellRuntime::Append(UIDrawData& drawData) const {
const auto& metrics = ResolveUIEditorShellInteractionMetrics();
const auto& palette = ResolveUIEditorShellInteractionPalette();
const UIEditorShellComposeModel shellComposeModel =
BuildShellComposeModelFromFrame(m_shellFrame);
AppendUIEditorShellCompose(
drawList,
m_shellFrame.shellFrame,
shellComposeModel,
m_shellInteractionState.composeState,
palette.shellPalette,
metrics.shellMetrics,
&m_builtInIcons);
m_consolePanel.Append(drawList);
m_colorPickerPanel.Append(drawList);
m_hierarchyPanel.Append(drawList);
m_inspectorPanel.Append(drawList);
m_projectPanel.Append(drawList);
m_sceneViewportFeature.Append(drawList);
AppendUIEditorShellComposeOverlay(
drawList,
m_shellFrame.shellFrame,
palette.shellPalette,
metrics.shellMetrics);
AppendShellPopups(drawList, m_shellFrame, palette, metrics);
AppendDrawPacket(
drawData,
"XCEditorShell.Compose.Base",
[&](UIDrawList& drawList) {
AppendUIEditorShellComposeBase(
drawList,
m_shellFrame.shellFrame,
shellComposeModel,
m_shellInteractionState.composeState,
palette.shellPalette,
metrics.shellMetrics,
&m_builtInIcons);
});
AppendDrawPacket(
drawData,
"XCEditorShell.Compose.Workspace",
[&](UIDrawList& drawList) {
AppendUIEditorWorkspaceCompose(
drawList,
m_shellFrame.workspaceInteractionFrame.composeFrame,
palette.shellPalette.dockHostPalette,
metrics.shellMetrics.dockHostMetrics,
palette.shellPalette.viewportPalette,
metrics.shellMetrics.viewportMetrics,
UIEditorWorkspaceComposeAppendOptions{
true,
false
});
});
AppendDrawPacket(
drawData,
"XCEditorShell.Compose.ViewportTextures",
[&](UIDrawList& drawList) {
AppendUIEditorWorkspaceComposeViewportTextures(
drawList,
m_shellFrame.workspaceInteractionFrame.composeFrame,
palette.shellPalette.viewportPalette);
});
AppendDrawPacket(
drawData,
"XCEditorShell.Compose.StatusBar",
[&](UIDrawList& drawList) {
AppendUIEditorShellComposeStatusBar(
drawList,
m_shellFrame.shellFrame,
shellComposeModel,
m_shellInteractionState.composeState,
palette.shellPalette,
metrics.shellMetrics);
});
AppendDrawPacket(
drawData,
"XCEditorPanel.Console",
[&](UIDrawList& drawList) { m_consolePanel.Append(drawList); });
AppendDrawPacket(
drawData,
"XCEditorPanel.ColorPicker",
[&](UIDrawList& drawList) { m_colorPickerPanel.Append(drawList); });
AppendDrawPacket(
drawData,
"XCEditorPanel.Hierarchy",
[&](UIDrawList& drawList) { m_hierarchyPanel.Append(drawList); });
AppendDrawPacket(
drawData,
"XCEditorPanel.Inspector",
[&](UIDrawList& drawList) { m_inspectorPanel.Append(drawList); });
AppendDrawPacket(
drawData,
"XCEditorPanel.Project",
[&](UIDrawList& drawList) { m_projectPanel.Append(drawList); });
AppendDrawPacket(
drawData,
"XCEditorPanel.SceneOverlay",
[&](UIDrawList& drawList) { m_sceneViewportFeature.Append(drawList); });
AppendDrawPacket(
drawData,
"XCEditorShell.Compose.Overlay",
[&](UIDrawList& drawList) {
AppendUIEditorShellComposeOverlay(
drawList,
m_shellFrame.workspaceInteractionFrame.composeFrame,
palette.shellPalette,
metrics.shellMetrics);
});
AppendDrawPacket(
drawData,
"XCEditorShell.Popups",
[&](UIDrawList& drawList) {
AppendShellPopups(drawList, m_shellFrame, palette, metrics);
});
}
} // namespace XCEngine::UI::Editor::App
@@ -664,7 +747,6 @@ void ApplyViewportFramesToShellFrame(
};
applyToViewportFrames(shellFrame.workspaceInteractionFrame.composeFrame.viewportFrames);
applyToViewportFrames(shellFrame.shellFrame.workspaceFrame.viewportFrames);
}
} // namespace XCEngine::UI::Editor::App

View File

@@ -70,7 +70,7 @@ public:
float detachedWindowChromeHeight = 0.0f);
void RenderRequestedViewports(
const ::XCEngine::Rendering::RenderContext& renderContext);
void Append(::XCEngine::UI::UIDrawList& drawList) const;
void Append(::XCEngine::UI::UIDrawData& drawData) const;
const UIEditorShellInteractionFrame& GetShellFrame() const;
const UIEditorShellInteractionState& GetShellInteractionState() const;

View File

@@ -1,5 +1,6 @@
#include "ProjectBrowserModel.h"
#include <filesystem>
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
#include <string>
#include <string_view>
#include <vector>
@@ -89,6 +90,10 @@ std::u8string BuildU8String(std::string_view value) {
return result;
}
void TraceProjectBrowser(std::string message) {
::XCEngine::UI::Editor::AppendUIEditorRuntimeTrace("startup", std::move(message));
}
} // namespace
std::string ToLowerCopy(std::string value) {
@@ -550,14 +555,31 @@ std::string RemapMovedItemId(
void ProjectBrowserModel::Initialize(const std::filesystem::path& repoRoot) {
m_assetsRootPath = (repoRoot / "project/Assets").lexically_normal();
TraceProjectBrowser("ProjectBrowserModel::Initialize assetsRoot=" + PathToUtf8String(m_assetsRootPath));
std::error_code errorCode = {};
if (!std::filesystem::exists(m_assetsRootPath, errorCode)) {
std::filesystem::create_directories(m_assetsRootPath / "Scenes", errorCode);
}
if (errorCode) {
TraceProjectBrowser("ProjectBrowserModel::Initialize filesystem error=" + errorCode.message());
}
Refresh();
TraceProjectBrowser(
"ProjectBrowserModel::Initialize complete folders=" +
std::to_string(m_folderEntries.size()) +
" assets=" +
std::to_string(m_assetEntries.size()));
}
void ProjectBrowserModel::SetFolderIcon(const ::XCEngine::UI::UITextureHandle& icon) {
if (m_folderIcon.nativeHandle == icon.nativeHandle &&
m_folderIcon.width == icon.width &&
m_folderIcon.height == icon.height &&
m_folderIcon.kind == icon.kind &&
m_folderIcon.resourceHandle == icon.resourceHandle) {
return;
}
m_folderIcon = icon;
if (!m_assetsRootPath.empty()) {
RefreshFolderTree();
@@ -565,9 +587,17 @@ void ProjectBrowserModel::SetFolderIcon(const ::XCEngine::UI::UITextureHandle& i
}
void ProjectBrowserModel::Refresh() {
TraceProjectBrowser("ProjectBrowserModel::Refresh begin");
RefreshFolderTree();
EnsureValidCurrentFolder();
RefreshAssetList();
TraceProjectBrowser(
"ProjectBrowserModel::Refresh end currentFolder=" +
m_currentFolderId +
" folders=" +
std::to_string(m_folderEntries.size()) +
" assets=" +
std::to_string(m_assetEntries.size()));
}
bool ProjectBrowserModel::Empty() const {
@@ -1186,6 +1216,7 @@ namespace XCEngine::UI::Editor::App {
void ProjectBrowserModel::RefreshAssetList() {
TraceProjectBrowser("ProjectBrowserModel::RefreshAssetList begin");
EnsureValidCurrentFolder();
m_assetEntries.clear();
@@ -1239,6 +1270,12 @@ void ProjectBrowserModel::RefreshAssetList() {
CanPreviewItem(entry.path(), assetEntry.kind, assetEntry.directory);
m_assetEntries.push_back(std::move(assetEntry));
}
TraceProjectBrowser(
"ProjectBrowserModel::RefreshAssetList end currentFolder=" +
m_currentFolderId +
" assets=" +
std::to_string(m_assetEntries.size()));
}
} // namespace XCEngine::UI::Editor::App
@@ -1247,6 +1284,7 @@ namespace XCEngine::UI::Editor::App {
void ProjectBrowserModel::RefreshFolderTree() {
TraceProjectBrowser("ProjectBrowserModel::RefreshFolderTree begin");
m_folderEntries.clear();
m_treeItems.clear();
@@ -1280,6 +1318,9 @@ void ProjectBrowserModel::RefreshFolderTree() {
};
appendFolderRecursive(appendFolderRecursive, m_assetsRootPath, 0u);
TraceProjectBrowser(
"ProjectBrowserModel::RefreshFolderTree end folders=" +
std::to_string(m_folderEntries.size()));
}
std::vector<std::string> ProjectBrowserModel::CollectCurrentFolderAncestorIds() const {

View File

@@ -317,6 +317,10 @@ void ProjectPanel::Initialize(const std::filesystem::path& repoRoot) {
}
void ProjectPanel::SetProjectRuntime(EditorProjectRuntime* projectRuntime) {
if (m_projectRuntime == projectRuntime) {
return;
}
m_projectRuntime = projectRuntime;
if (m_projectRuntime != nullptr && m_icons != nullptr) {
m_projectRuntime->SetFolderIcon(ResolveFolderIcon(m_icons));

View File

@@ -613,20 +613,22 @@ EditorWindowFrameTransferRequests EditorWindow::RenderFrame(
const UIRect workspaceBounds = ResolveWorkspaceBounds(width, height);
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("XCEditorShell");
drawList.AddFilledRect(
UIDrawList& backgroundDrawList = drawData.EmplaceDrawList("XCEditorWindow.Surface");
backgroundDrawList.AddFilledRect(
UIRect(0.0f, 0.0f, width, height),
kShellSurfaceColor);
EditorWindowFrameTransferRequests transferRequests = {};
if (editorContext.IsValid()) {
transferRequests =
RenderRuntimeFrame(editorContext, globalTabDragActive, workspaceBounds, drawList);
RenderRuntimeFrame(editorContext, globalTabDragActive, workspaceBounds, drawData);
} else {
m_frameOrchestrator->AppendInvalidFrame(editorContext, drawList);
UIDrawList& invalidDrawList = drawData.EmplaceDrawList("XCEditorWindow.Invalid");
m_frameOrchestrator->AppendInvalidFrame(editorContext, invalidDrawList);
}
AppendBorderlessWindowChrome(drawList, width);
UIDrawList& windowChromeDrawList = drawData.EmplaceDrawList("XCEditorWindow.Chrome");
AppendBorderlessWindowChrome(windowChromeDrawList, width);
const Host::D3D12WindowRenderLoopPresentResult presentResult = m_runtime->Present(drawData);
if (!presentResult.warning.empty()) {
@@ -677,7 +679,7 @@ EditorWindowFrameTransferRequests EditorWindow::RenderRuntimeFrame(
EditorContext& editorContext,
bool globalTabDragActive,
const UIRect& workspaceBounds,
UIDrawList& drawList) {
UIDrawData& drawData) {
SyncShellCapturedPointerButtonsFromSystemState();
std::vector<UIInputEvent> frameEvents = m_inputController->TakePendingEvents();
const bool useDetachedTitleBarTabStrip = ShouldUseDetachedTitleBarTabStrip();
@@ -685,13 +687,13 @@ EditorWindowFrameTransferRequests EditorWindow::RenderRuntimeFrame(
m_frameOrchestrator->UpdateAndAppend(
editorContext,
*m_runtime,
workspaceBounds,
frameEvents,
workspaceBounds,
frameEvents,
m_runtime->BuildCaptureStatusText(),
m_state->window.primary,
globalTabDragActive,
useDetachedTitleBarTabStrip,
drawList);
useDetachedTitleBarTabStrip,
drawData);
ApplyShellRuntimePointerCapture();
ApplyCurrentCursor();

View File

@@ -18,6 +18,7 @@
namespace XCEngine::UI {
class UIDrawData;
class UIDrawList;
struct UIPoint;
@@ -215,7 +216,7 @@ private:
EditorContext& editorContext,
bool globalTabDragActive,
const ::XCEngine::UI::UIRect& workspaceBounds,
::XCEngine::UI::UIDrawList& drawList);
::XCEngine::UI::UIDrawData& drawData);
bool IsPointerInsideClientArea() const;
LPCWSTR ResolveCurrentCursorResource() const;
float GetDpiScale() const;

View File

@@ -818,7 +818,7 @@ void EditorWindowChromeController::AppendChrome(
kBorderlessTitleBarFontSize);
if (!frameRateText.empty()) {
const float frameRateTextWidth =
window.m_runtime->GetRenderer().MeasureTextWidth(
window.m_runtime->GetTextMeasurer().MeasureTextWidth(
UIEditorTextMeasureRequest{
frameRateText,
kBorderlessTitleBarFontSize });

View File

@@ -13,6 +13,7 @@
namespace XCEngine::UI::Editor::App {
using namespace EditorWindowSupport;
using ::XCEngine::UI::UIDrawData;
using ::XCEngine::UI::UIDrawList;
using ::XCEngine::UI::UIInputEvent;
using ::XCEngine::UI::UIInputEventType;
@@ -64,12 +65,12 @@ EditorWindowFrameTransferRequests EditorWindowFrameOrchestrator::UpdateAndAppend
EditorContext& editorContext,
EditorWindowRuntimeController& runtimeController,
const ::XCEngine::UI::UIRect& workspaceBounds,
const std::vector<UIInputEvent>& frameEvents,
std::string_view captureStatusText,
bool primary,
bool globalTabDragActive,
bool useDetachedTitleBarTabStrip,
UIDrawList& drawList) const {
const std::vector<UIInputEvent>& frameEvents,
std::string_view captureStatusText,
bool primary,
bool globalTabDragActive,
bool useDetachedTitleBarTabStrip,
UIDrawData& drawData) const {
LogInputTrace(editorContext, runtimeController, frameEvents);
const Host::D3D12WindowRenderLoopFrameContext frameContext = runtimeController.BeginFrame();
@@ -77,7 +78,7 @@ EditorWindowFrameTransferRequests EditorWindowFrameOrchestrator::UpdateAndAppend
LogRuntimeTrace("viewport", frameContext.warning);
}
editorContext.AttachTextMeasurer(runtimeController.GetRenderer());
editorContext.AttachTextMeasurer(runtimeController.GetTextMeasurer());
UIEditorWorkspaceController& workspaceController =
runtimeController.GetMutableWorkspaceController();
EditorShellRuntime& shellRuntime = runtimeController.GetShellRuntime();
@@ -120,7 +121,7 @@ EditorWindowFrameTransferRequests EditorWindowFrameOrchestrator::UpdateAndAppend
LogRuntimeTrace(entry.channel, entry.message);
}
shellRuntime.Append(drawList);
shellRuntime.Append(drawData);
if (frameContext.canRenderViewports) {
shellRuntime.RenderRequestedViewports(frameContext.renderContext);
}

View File

@@ -11,6 +11,7 @@
namespace XCEngine::UI {
class UIDrawList;
class UIDrawData;
struct UIRect;
struct UIInputEvent;
@@ -50,7 +51,7 @@ public:
bool primary,
bool globalTabDragActive,
bool useDetachedTitleBarTabStrip,
::XCEngine::UI::UIDrawList& drawList) const;
::XCEngine::UI::UIDrawData& drawData) const;
void AppendInvalidFrame(
EditorContext& editorContext,
::XCEngine::UI::UIDrawList& drawList) const;

View File

@@ -8,6 +8,7 @@
#include <algorithm>
#include <chrono>
#include <cstdio>
#include <cmath>
#include <utility>
namespace XCEngine::UI::Editor::App {
@@ -18,6 +19,7 @@ using namespace EditorWindowSupport;
namespace {
constexpr float kFrameTimeSmoothingFactor = 0.12f;
constexpr float kFrameStatsDisplayRefreshIntervalSeconds = 0.25f;
}
@@ -73,14 +75,17 @@ void EditorWindowRuntimeController::ClearExternalDockHostDropPreview() {
void EditorWindowRuntimeController::SetDpiScale(float dpiScale) {
m_renderer.SetDpiScale(dpiScale);
m_textSystem.SetDpiScale(dpiScale);
m_uiRenderer.SetDpiScale(dpiScale);
}
Host::NativeRenderer& EditorWindowRuntimeController::GetRenderer() {
return m_renderer;
::XCEngine::UI::Editor::UIEditorTextMeasurer& EditorWindowRuntimeController::GetTextMeasurer() {
return m_textSystem;
}
const Host::NativeRenderer& EditorWindowRuntimeController::GetRenderer() const {
return m_renderer;
const ::XCEngine::UI::Editor::UIEditorTextMeasurer&
EditorWindowRuntimeController::GetTextMeasurer() const {
return m_textSystem;
}
const ::XCEngine::UI::UITextureHandle& EditorWindowRuntimeController::GetTitleBarLogoIcon() const {
@@ -98,36 +103,53 @@ bool EditorWindowRuntimeController::Initialize(
return false;
}
if (!m_renderer.Initialize(hwnd)) {
LogRuntimeTrace("app", "renderer initialization failed");
return false;
}
RECT clientRect = {};
GetClientRect(hwnd, &clientRect);
const int clientWidth = (std::max)(clientRect.right - clientRect.left, 1L);
const int clientHeight = (std::max)(clientRect.bottom - clientRect.top, 1L);
if (!m_windowRenderer.Initialize(hwnd, clientWidth, clientHeight)) {
LogRuntimeTrace("app", "d3d12 window renderer initialization failed");
m_renderer.Shutdown();
return false;
}
const Host::D3D12WindowRenderLoopAttachResult attachResult =
m_windowRenderLoop.Attach(m_renderer, m_windowRenderer);
if (!attachResult.interopWarning.empty()) {
LogRuntimeTrace("app", attachResult.interopWarning);
if (!m_textureHost.Initialize(m_windowRenderer)) {
LogRuntimeTrace("app", "d3d12 ui texture host initialization failed");
m_windowRenderer.Shutdown();
return false;
}
editorContext.AttachTextMeasurer(m_renderer);
m_shellRuntime.Initialize(repoRoot, m_renderer, m_renderer);
if (!m_textSystem.Initialize()) {
LogRuntimeTrace("app", "d3d12 ui text system initialization failed");
m_textureHost.Shutdown();
m_windowRenderer.Shutdown();
return false;
}
m_textSystem.SetDpiScale(m_renderer.GetDpiScale());
if (!m_uiRenderer.Initialize(m_windowRenderer, m_textureHost, m_textSystem)) {
LogRuntimeTrace("app", "d3d12 ui renderer initialization failed");
m_textSystem.Shutdown();
m_textureHost.Shutdown();
m_windowRenderer.Shutdown();
return false;
}
m_uiRenderer.SetDpiScale(m_renderer.GetDpiScale());
const Host::D3D12WindowRenderLoopAttachResult attachResult =
m_windowRenderLoop.Attach(m_uiRenderer, m_windowRenderer);
if (!attachResult.warning.empty()) {
LogRuntimeTrace("app", attachResult.warning);
}
editorContext.AttachTextMeasurer(m_textSystem);
m_shellRuntime.Initialize(repoRoot, m_textureHost, m_textSystem);
m_shellRuntime.AttachViewportWindowRenderer(m_windowRenderer);
m_shellRuntime.SetViewportSurfacePresentationEnabled(
attachResult.hasViewportSurfacePresentation);
std::string titleBarLogoError = {};
if (!LoadEmbeddedPngTexture(
m_renderer,
m_textureHost,
IDR_PNG_LOGO_ICON,
m_titleBarLogoIcon,
titleBarLogoError)) {
@@ -160,8 +182,11 @@ void EditorWindowRuntimeController::Shutdown() {
ResetFrameTiming();
m_autoScreenshot.Shutdown();
m_shellRuntime.Shutdown();
m_renderer.ReleaseTexture(m_titleBarLogoIcon);
m_windowRenderLoop.Detach();
m_uiRenderer.Shutdown();
m_textSystem.Shutdown();
m_textureHost.ReleaseTexture(m_titleBarLogoIcon);
m_textureHost.Shutdown();
m_windowRenderer.Shutdown();
m_renderer.Shutdown();
}
@@ -185,10 +210,6 @@ bool EditorWindowRuntimeController::ApplyResize(UINT width, UINT height) {
LogRuntimeTrace("present", resizeResult.windowRendererWarning);
}
if (!resizeResult.interopWarning.empty()) {
LogRuntimeTrace("present", resizeResult.interopWarning);
}
return resizeResult.hasViewportSurfacePresentation;
}
@@ -209,6 +230,7 @@ void EditorWindowRuntimeController::CaptureIfRequested(
bool framePresented) {
m_autoScreenshot.CaptureIfRequested(
m_renderer,
m_windowRenderer,
drawData,
pixelWidth,
pixelHeight,
@@ -236,26 +258,17 @@ std::string EditorWindowRuntimeController::BuildCaptureStatusText() const {
}
std::string EditorWindowRuntimeController::BuildFrameRateText() const {
if (m_displayFps <= 0.0f || m_displayFrameTimeMs <= 0.0f) {
return {};
}
char buffer[48] = {};
std::snprintf(
buffer,
sizeof(buffer),
"FPS %.1f | %.2f ms",
m_displayFps,
m_displayFrameTimeMs);
return buffer;
return m_frameRateText;
}
void EditorWindowRuntimeController::ResetFrameTiming() {
m_lastFrameTime = {};
m_hasLastFrameTime = false;
m_smoothedDeltaTimeSeconds = 0.0f;
m_frameStatsDisplayAccumulatorSeconds = 0.0f;
m_displayFps = 0.0f;
m_displayFrameTimeMs = 0.0f;
m_frameRateText.clear();
}
void EditorWindowRuntimeController::UpdateFrameTiming() {
@@ -283,6 +296,33 @@ void EditorWindowRuntimeController::UpdateFrameTiming() {
m_displayFps = m_smoothedDeltaTimeSeconds > 0.0f
? 1.0f / m_smoothedDeltaTimeSeconds
: 0.0f;
m_frameStatsDisplayAccumulatorSeconds += deltaTime;
if (m_frameRateText.empty() ||
m_frameStatsDisplayAccumulatorSeconds >= kFrameStatsDisplayRefreshIntervalSeconds) {
RefreshDisplayedFrameStats();
m_frameStatsDisplayAccumulatorSeconds = 0.0f;
}
}
void EditorWindowRuntimeController::RefreshDisplayedFrameStats() {
if (m_displayFps <= 0.0f || m_displayFrameTimeMs <= 0.0f) {
m_frameRateText.clear();
return;
}
const int roundedFps = (std::max)(0, static_cast<int>(std::lround(m_displayFps)));
const int roundedFrameTimeMs =
(std::max)(0, static_cast<int>(std::lround(m_displayFrameTimeMs)));
char buffer[48] = {};
std::snprintf(
buffer,
sizeof(buffer),
"FPS %3d | %2d ms",
roundedFps,
roundedFrameTimeMs);
m_frameRateText = buffer;
}
} // namespace XCEngine::UI::Editor::App

View File

@@ -6,6 +6,9 @@
#include "Composition/EditorShellRuntime.h"
#include <Rendering/D3D12/D3D12UiRenderer.h>
#include <Rendering/D3D12/D3D12UiTextSystem.h>
#include <Rendering/D3D12/D3D12UiTextureHost.h>
#include <Rendering/D3D12/D3D12WindowRenderLoop.h>
#include <Rendering/D3D12/D3D12WindowRenderer.h>
#include <Rendering/Native/AutoScreenshot.h>
@@ -63,8 +66,8 @@ public:
void ClearExternalDockHostDropPreview();
void SetDpiScale(float dpiScale);
Host::NativeRenderer& GetRenderer();
const Host::NativeRenderer& GetRenderer() const;
::XCEngine::UI::Editor::UIEditorTextMeasurer& GetTextMeasurer();
const ::XCEngine::UI::Editor::UIEditorTextMeasurer& GetTextMeasurer() const;
const ::XCEngine::UI::UITextureHandle& GetTitleBarLogoIcon() const;
bool Initialize(
@@ -93,9 +96,13 @@ public:
private:
void ResetFrameTiming();
void UpdateFrameTiming();
void RefreshDisplayedFrameStats();
Host::NativeRenderer m_renderer = {};
Host::D3D12WindowRenderer m_windowRenderer = {};
Host::D3D12UiTextureHost m_textureHost = {};
Host::D3D12UiTextSystem m_textSystem = {};
Host::D3D12UiRenderer m_uiRenderer = {};
Host::D3D12WindowRenderLoop m_windowRenderLoop = {};
Host::AutoScreenshotController m_autoScreenshot = {};
::XCEngine::UI::UITextureHandle m_titleBarLogoIcon = {};
@@ -104,8 +111,10 @@ private:
std::chrono::steady_clock::time_point m_lastFrameTime = {};
bool m_hasLastFrameTime = false;
float m_smoothedDeltaTimeSeconds = 0.0f;
float m_frameStatsDisplayAccumulatorSeconds = 0.0f;
float m_displayFps = 0.0f;
float m_displayFrameTimeMs = 0.0f;
std::string m_frameRateText = {};
bool m_ready = false;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,246 @@
#pragma once
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include "D3D12UiTextSystem.h"
#include "D3D12UiTextureHost.h"
#include <Rendering/D3D12/D3D12WindowRenderer.h>
#include <XCEngine/Rendering/RenderContext.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/RHI/RHIDescriptorPool.h>
#include <XCEngine/RHI/RHIDescriptorSet.h>
#include <XCEngine/RHI/RHIBuffer.h>
#include <XCEngine/RHI/RHIPipelineLayout.h>
#include <XCEngine/RHI/RHIPipelineState.h>
#include <XCEngine/RHI/RHIResourceView.h>
#include <XCEngine/RHI/RHISampler.h>
#include <XCEngine/UI/DrawData.h>
#include <array>
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>
namespace XCEngine::UI::Editor::Host {
class D3D12UiRenderer {
public:
enum class UiBatchKind : std::uint8_t {
LegacyIndexed = 0u,
QuadInstanced
};
struct UiVertex {
float position[2] = {};
float uv[2] = {};
float color[4] = {};
};
struct UiQuadInstance {
float origin[2] = {};
float axisX[2] = {};
float axisY[2] = {};
float uvMin[2] = {};
float uvMax[2] = {};
float color[4] = {};
float secondaryColor[4] = {};
float params[4] = {};
};
struct UiBatch {
UiBatchKind kind = UiBatchKind::LegacyIndexed;
std::uint32_t firstElement = 0u;
std::uint32_t elementCount = 0u;
D3D12_GPU_DESCRIPTOR_HANDLE textureHandle = {};
::XCEngine::RHI::Rect scissorRect = {};
};
bool Initialize(
D3D12WindowRenderer& windowRenderer,
D3D12UiTextureHost& textureHost,
D3D12UiTextSystem& textSystem);
void Shutdown();
void SetDpiScale(float dpiScale);
float GetDpiScale() const;
const std::string& GetLastError() const;
bool Render(
const ::XCEngine::UI::UIDrawData& drawData,
const ::XCEngine::Rendering::RenderContext& renderContext,
const ::XCEngine::Rendering::RenderSurface& surface);
private:
struct FrameResources {
::XCEngine::RHI::RHIBuffer* legacyVertexBuffer = nullptr;
::XCEngine::RHI::RHIResourceView* legacyVertexBufferView = nullptr;
::XCEngine::RHI::RHIBuffer* legacyIndexBuffer = nullptr;
::XCEngine::RHI::RHIResourceView* legacyIndexBufferView = nullptr;
::XCEngine::RHI::RHIBuffer* quadInstanceBuffer = nullptr;
::XCEngine::RHI::RHIResourceView* quadInstanceBufferView = nullptr;
std::uint64_t legacyVertexCapacityBytes = 0u;
std::uint64_t legacyIndexCapacityBytes = 0u;
std::uint64_t quadInstanceCapacityBytes = 0u;
};
struct GlyphAtlasKey {
std::uintptr_t fontFaceKey = 0u;
std::uint16_t glyphIndex = 0u;
int fontEmSizeTenths = 0;
bool isSideways = false;
bool operator==(const GlyphAtlasKey& other) const;
};
struct GlyphAtlasKeyHash {
std::size_t operator()(const GlyphAtlasKey& key) const;
};
struct GlyphAtlasEntry {
std::size_t pageIndex = 0u;
std::uint32_t pageX = 0u;
std::uint32_t pageY = 0u;
std::uint32_t width = 0u;
std::uint32_t height = 0u;
LONG boundsLeft = 0;
LONG boundsTop = 0;
LONG boundsRight = 0;
LONG boundsBottom = 0;
D3D12_GPU_DESCRIPTOR_HANDLE textureHandle = {};
bool hasPixels = false;
};
struct GlyphAtlasPage {
::XCEngine::UI::UITextureHandle texture = {};
std::uint32_t width = 0u;
std::uint32_t height = 0u;
std::uint32_t cursorX = 0u;
std::uint32_t cursorY = 0u;
std::uint32_t rowHeight = 0u;
};
struct TextRunCacheKey {
std::string text = {};
int fontSizeTenths = 0;
int dpiScaleMilli = 0;
bool operator==(const TextRunCacheKey& other) const;
};
struct TextRunCacheKeyHash {
std::size_t operator()(const TextRunCacheKey& key) const;
};
struct GlyphQuadPlan {
float left = 0.0f;
float top = 0.0f;
float right = 0.0f;
float bottom = 0.0f;
float uvMinX = 0.0f;
float uvMinY = 0.0f;
float uvMaxX = 0.0f;
float uvMaxY = 0.0f;
D3D12_GPU_DESCRIPTOR_HANDLE textureHandle = {};
};
struct CachedTextRun {
std::vector<GlyphQuadPlan> glyphs = {};
};
struct CompiledDrawListKey {
std::uint64_t contentHash = 0u;
std::uint32_t renderWidth = 0u;
std::uint32_t renderHeight = 0u;
int dpiScaleMilli = 0;
bool operator==(const CompiledDrawListKey& other) const;
};
struct CompiledDrawListKeyHash {
std::size_t operator()(const CompiledDrawListKey& key) const;
};
struct CompiledDrawList {
std::vector<UiVertex> vertices = {};
std::vector<std::uint32_t> indices = {};
std::vector<UiQuadInstance> quadInstances = {};
std::vector<UiBatch> batches = {};
std::uint64_t lastUsedFrame = 0u;
};
bool EnsureInitialized(
const ::XCEngine::Rendering::RenderContext& renderContext,
const ::XCEngine::Rendering::RenderSurface& surface);
bool CreateResources(
const ::XCEngine::Rendering::RenderContext& renderContext,
const ::XCEngine::Rendering::RenderSurface& surface);
void DestroyResources();
bool EnsureFrameBufferCapacity(
std::uint32_t frameSlot,
std::size_t legacyVertexBytes,
std::size_t legacyIndexBytes,
std::size_t quadInstanceBytes);
void DestroyFrameResources(FrameResources& frameResources);
bool EnsureWhiteTexture();
bool EnsureGlyphAtlasPage();
bool AllocateGlyphAtlasRegion(
std::uint32_t width,
std::uint32_t height,
std::size_t& outPageIndex,
std::uint32_t& outPageX,
std::uint32_t& outPageY);
const GlyphAtlasEntry* ResolveGlyphAtlasEntry(
const D3D12UiTextSystem::ShapedGlyph& glyph);
const CachedTextRun* ResolveTextRun(
std::string_view text,
float fontSize);
void ReleaseGlyphAtlas();
void ReleaseTextRunCache();
void ReleaseCompiledDrawListCache();
bool BuildDrawBatches(
const ::XCEngine::UI::UIDrawData& drawData,
const ::XCEngine::Rendering::RenderSurface& surface,
std::vector<UiVertex>& outLegacyVertices,
std::vector<std::uint32_t>& outLegacyIndices,
std::vector<UiQuadInstance>& outQuadInstances,
std::vector<UiBatch>& outBatches);
D3D12WindowRenderer* m_windowRenderer = nullptr;
D3D12UiTextureHost* m_textureHost = nullptr;
D3D12UiTextSystem* m_textSystem = nullptr;
::XCEngine::RHI::RHIDevice* m_device = nullptr;
::XCEngine::RHI::RHIType m_backendType = ::XCEngine::RHI::RHIType::D3D12;
::XCEngine::RHI::RHIPipelineLayout* m_pipelineLayout = nullptr;
::XCEngine::RHI::RHIPipelineState* m_legacyPipelineState = nullptr;
::XCEngine::RHI::RHIPipelineState* m_quadPipelineState = nullptr;
::XCEngine::RHI::RHIDescriptorPool* m_constantPool = nullptr;
::XCEngine::RHI::RHIDescriptorSet* m_constantSet = nullptr;
::XCEngine::RHI::RHIDescriptorPool* m_samplerPool = nullptr;
::XCEngine::RHI::RHIDescriptorSet* m_samplerSet = nullptr;
::XCEngine::RHI::RHISampler* m_sampler = nullptr;
D3D12_GPU_DESCRIPTOR_HANDLE m_samplerGpuHandle = {};
::XCEngine::UI::UITextureHandle m_whiteTexture = {};
::XCEngine::RHI::RHIBuffer* m_quadVertexBuffer = nullptr;
::XCEngine::RHI::RHIResourceView* m_quadVertexBufferView = nullptr;
::XCEngine::RHI::RHIBuffer* m_quadIndexBuffer = nullptr;
::XCEngine::RHI::RHIResourceView* m_quadIndexBufferView = nullptr;
std::array<FrameResources, D3D12WindowRenderer::kFrameContextCount> m_frameResources = {};
std::vector<GlyphAtlasPage> m_glyphAtlasPages = {};
std::unordered_map<GlyphAtlasKey, GlyphAtlasEntry, GlyphAtlasKeyHash> m_glyphAtlas = {};
std::unordered_map<TextRunCacheKey, CachedTextRun, TextRunCacheKeyHash> m_textRunCache = {};
std::unordered_map<CompiledDrawListKey, CompiledDrawList, CompiledDrawListKeyHash>
m_compiledDrawListCache = {};
std::uint64_t m_compiledDrawListFrameCounter = 0u;
::XCEngine::RHI::Format m_renderTargetFormat = ::XCEngine::RHI::Format::Unknown;
std::uint32_t m_renderTargetSampleCount = 1u;
std::uint32_t m_renderTargetSampleQuality = 0u;
float m_dpiScale = 1.0f;
std::string m_lastError = {};
};
} // namespace XCEngine::UI::Editor::Host

View File

@@ -0,0 +1,752 @@
#include "D3D12UiTextSystem.h"
#include <algorithm>
#include <atomic>
#include <cmath>
#include <cstdio>
namespace XCEngine::UI::Editor::Host {
namespace {
constexpr float kDefaultFontSize = 16.0f;
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;
}
float ClampDpiScale(float dpiScale) {
return dpiScale > 0.0f ? dpiScale : 1.0f;
}
float ResolveFontSize(float fontSize) {
return fontSize > 0.0f ? fontSize : kDefaultFontSize;
}
struct GlyphRunRecord {
Microsoft::WRL::ComPtr<IDWriteFontFace> fontFace = {};
std::vector<UINT16> glyphIndices = {};
std::vector<FLOAT> glyphAdvances = {};
std::vector<DWRITE_GLYPH_OFFSET> glyphOffsets = {};
FLOAT fontEmSize = 0.0f;
FLOAT baselineOriginX = 0.0f;
FLOAT baselineOriginY = 0.0f;
DWRITE_MEASURING_MODE measuringMode = DWRITE_MEASURING_MODE_NATURAL;
BOOL isSideways = FALSE;
UINT32 bidiLevel = 0u;
};
class GlyphRunCollector final : public IDWriteTextRenderer {
public:
explicit GlyphRunCollector(float pixelsPerDip)
: m_pixelsPerDip(ClampDpiScale(pixelsPerDip)) {
}
const std::vector<GlyphRunRecord>& GetGlyphRuns() const {
return m_glyphRuns;
}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** object) override {
if (object == nullptr) {
return E_INVALIDARG;
}
*object = nullptr;
if (riid == __uuidof(IUnknown) ||
riid == __uuidof(IDWritePixelSnapping) ||
riid == __uuidof(IDWriteTextRenderer)) {
*object = static_cast<IDWriteTextRenderer*>(this);
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
ULONG STDMETHODCALLTYPE AddRef() override {
return ++m_refCount;
}
ULONG STDMETHODCALLTYPE Release() override {
const ULONG refCount = --m_refCount;
if (refCount == 0u) {
delete this;
}
return refCount;
}
HRESULT STDMETHODCALLTYPE IsPixelSnappingDisabled(
void*,
BOOL* isDisabled) override {
if (isDisabled == nullptr) {
return E_INVALIDARG;
}
*isDisabled = FALSE;
return S_OK;
}
HRESULT STDMETHODCALLTYPE GetCurrentTransform(
void*,
DWRITE_MATRIX* transform) override {
if (transform == nullptr) {
return E_INVALIDARG;
}
*transform = DWRITE_MATRIX{ 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f };
return S_OK;
}
HRESULT STDMETHODCALLTYPE GetPixelsPerDip(
void*,
FLOAT* pixelsPerDip) override {
if (pixelsPerDip == nullptr) {
return E_INVALIDARG;
}
*pixelsPerDip = m_pixelsPerDip;
return S_OK;
}
HRESULT STDMETHODCALLTYPE DrawGlyphRun(
void*,
FLOAT baselineOriginX,
FLOAT baselineOriginY,
DWRITE_MEASURING_MODE measuringMode,
const DWRITE_GLYPH_RUN* glyphRun,
const DWRITE_GLYPH_RUN_DESCRIPTION*,
IUnknown*) override {
if (glyphRun == nullptr ||
glyphRun->fontFace == nullptr ||
glyphRun->glyphCount == 0u ||
glyphRun->glyphIndices == nullptr) {
return S_OK;
}
GlyphRunRecord record = {};
record.fontFace = glyphRun->fontFace;
record.glyphIndices.assign(
glyphRun->glyphIndices,
glyphRun->glyphIndices + glyphRun->glyphCount);
record.glyphAdvances.resize(glyphRun->glyphCount, 0.0f);
record.glyphOffsets.resize(glyphRun->glyphCount, DWRITE_GLYPH_OFFSET{});
if (glyphRun->glyphAdvances != nullptr) {
std::copy(
glyphRun->glyphAdvances,
glyphRun->glyphAdvances + glyphRun->glyphCount,
record.glyphAdvances.begin());
}
if (glyphRun->glyphOffsets != nullptr) {
std::copy(
glyphRun->glyphOffsets,
glyphRun->glyphOffsets + glyphRun->glyphCount,
record.glyphOffsets.begin());
}
record.fontEmSize = glyphRun->fontEmSize;
record.baselineOriginX = baselineOriginX;
record.baselineOriginY = baselineOriginY;
record.measuringMode = measuringMode;
record.isSideways = glyphRun->isSideways;
record.bidiLevel = glyphRun->bidiLevel;
m_glyphRuns.push_back(std::move(record));
return S_OK;
}
HRESULT STDMETHODCALLTYPE DrawUnderline(
void*,
FLOAT,
FLOAT,
const DWRITE_UNDERLINE*,
IUnknown*) override {
return S_OK;
}
HRESULT STDMETHODCALLTYPE DrawStrikethrough(
void*,
FLOAT,
FLOAT,
const DWRITE_STRIKETHROUGH*,
IUnknown*) override {
return S_OK;
}
HRESULT STDMETHODCALLTYPE DrawInlineObject(
void*,
FLOAT,
FLOAT,
IDWriteInlineObject*,
BOOL,
BOOL,
IUnknown*) override {
return S_OK;
}
private:
std::atomic<ULONG> m_refCount = 1u;
float m_pixelsPerDip = 1.0f;
std::vector<GlyphRunRecord> m_glyphRuns = {};
};
void CompositeCoverage(
std::vector<std::uint8_t>& rgbaPixels,
UINT targetWidth,
UINT targetHeight,
const RECT& bounds,
const std::uint8_t* alphaTexture,
std::size_t textureStride,
std::size_t channelCount) {
if (alphaTexture == nullptr || channelCount == 0u) {
return;
}
const LONG width = bounds.right - bounds.left;
const LONG height = bounds.bottom - bounds.top;
if (width <= 0 || height <= 0) {
return;
}
for (LONG row = 0; row < height; ++row) {
const LONG dstY = bounds.top + row;
if (dstY < 0 || dstY >= static_cast<LONG>(targetHeight)) {
continue;
}
for (LONG column = 0; column < width; ++column) {
const LONG dstX = bounds.left + column;
if (dstX < 0 || dstX >= static_cast<LONG>(targetWidth)) {
continue;
}
const std::size_t srcOffset =
static_cast<std::size_t>(row) * textureStride +
static_cast<std::size_t>(column) * channelCount;
std::uint8_t coverage = 0u;
for (std::size_t channel = 0u; channel < channelCount; ++channel) {
coverage = (std::max)(coverage, alphaTexture[srcOffset + channel]);
}
if (coverage == 0u) {
continue;
}
const std::size_t dstOffset =
(static_cast<std::size_t>(dstY) * static_cast<std::size_t>(targetWidth) +
static_cast<std::size_t>(dstX)) *
4u;
std::uint8_t& dstAlpha = rgbaPixels[dstOffset + 3u];
const std::uint32_t blendedAlpha =
static_cast<std::uint32_t>(dstAlpha) +
((255u - static_cast<std::uint32_t>(dstAlpha)) *
static_cast<std::uint32_t>(coverage) +
127u) /
255u;
dstAlpha = static_cast<std::uint8_t>((std::min)(blendedAlpha, 255u));
rgbaPixels[dstOffset + 0u] = 255u;
rgbaPixels[dstOffset + 1u] = 255u;
rgbaPixels[dstOffset + 2u] = 255u;
}
}
}
} // namespace
bool D3D12UiTextSystem::Initialize() {
Shutdown();
const HRESULT hr = DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory),
reinterpret_cast<IUnknown**>(m_dwriteFactory.ReleaseAndGetAddressOf()));
if (FAILED(hr)) {
const std::string error = HrToString("DWriteCreateFactory", hr);
Shutdown();
m_lastError = error;
return false;
}
m_lastError.clear();
return true;
}
void D3D12UiTextSystem::Shutdown() {
m_textFormats.clear();
m_dwriteFactory.Reset();
m_lastError.clear();
}
void D3D12UiTextSystem::SetDpiScale(float dpiScale) {
m_dpiScale = ClampDpiScale(dpiScale);
}
float D3D12UiTextSystem::GetDpiScale() const {
return m_dpiScale;
}
const std::string& D3D12UiTextSystem::GetLastError() const {
return m_lastError;
}
bool D3D12UiTextSystem::ShapeTextRun(
std::string_view text,
float fontSize,
ShapedTextRun& outRun,
std::string& outError) const {
outRun = {};
outError.clear();
if (!m_dwriteFactory) {
outError = "ShapeTextRun requires an initialized DirectWrite factory.";
return false;
}
if (text.empty()) {
return true;
}
const std::wstring wideText = Utf8ToWide(text);
if (wideText.empty()) {
outError = "ShapeTextRun could not convert UTF-8 text to UTF-16.";
return false;
}
const float dpiScale = ClampDpiScale(m_dpiScale);
const float scaledFontSize = ResolveFontSize(fontSize) * dpiScale;
IDWriteTextFormat* textFormat = GetTextFormat(scaledFontSize);
if (textFormat == nullptr) {
outError = "ShapeTextRun could not resolve the DirectWrite text format.";
return false;
}
Microsoft::WRL::ComPtr<IDWriteTextLayout> textLayout = {};
HRESULT hr = m_dwriteFactory->CreateTextLayout(
wideText.c_str(),
static_cast<UINT32>(wideText.size()),
textFormat,
4096.0f,
scaledFontSize * 2.0f,
textLayout.ReleaseAndGetAddressOf());
if (FAILED(hr) || textLayout == nullptr) {
outError = HrToString("IDWriteFactory::CreateTextLayout", hr);
return false;
}
DWRITE_TEXT_METRICS textMetrics = {};
hr = textLayout->GetMetrics(&textMetrics);
if (FAILED(hr)) {
outError = HrToString("IDWriteTextLayout::GetMetrics", hr);
return false;
}
outRun.width = textMetrics.widthIncludingTrailingWhitespace;
outRun.height = (std::max)(textMetrics.height, scaledFontSize * 1.6f);
GlyphRunCollector* collector = new GlyphRunCollector(dpiScale);
hr = textLayout->Draw(
nullptr,
collector,
0.0f,
0.0f);
const std::vector<GlyphRunRecord> glyphRuns = collector->GetGlyphRuns();
collector->Release();
if (FAILED(hr)) {
outError = HrToString("IDWriteTextLayout::Draw", hr);
return false;
}
for (const GlyphRunRecord& record : glyphRuns) {
if (record.fontFace == nullptr || record.glyphIndices.empty()) {
continue;
}
float advanceCursor = 0.0f;
for (std::size_t glyphIndex = 0u; glyphIndex < record.glyphIndices.size(); ++glyphIndex) {
const DWRITE_GLYPH_OFFSET glyphOffset =
glyphIndex < record.glyphOffsets.size()
? record.glyphOffsets[glyphIndex]
: DWRITE_GLYPH_OFFSET{};
ShapedGlyph shapedGlyph = {};
shapedGlyph.fontFace = record.fontFace;
shapedGlyph.glyphIndex = record.glyphIndices[glyphIndex];
shapedGlyph.fontEmSize = record.fontEmSize;
shapedGlyph.originX =
record.baselineOriginX +
advanceCursor +
glyphOffset.advanceOffset;
shapedGlyph.originY =
record.baselineOriginY +
glyphOffset.ascenderOffset;
shapedGlyph.measuringMode = record.measuringMode;
shapedGlyph.isSideways = record.isSideways != FALSE;
outRun.glyphs.push_back(std::move(shapedGlyph));
if (glyphIndex < record.glyphAdvances.size()) {
advanceCursor += record.glyphAdvances[glyphIndex];
}
}
}
return true;
}
bool D3D12UiTextSystem::RasterizeGlyph(
const ShapedGlyph& glyph,
RasterizedGlyph& outGlyph,
std::string& outError) const {
outGlyph = {};
outError.clear();
if (!m_dwriteFactory) {
outError = "RasterizeGlyph requires an initialized DirectWrite factory.";
return false;
}
if (glyph.fontFace == nullptr) {
outError = "RasterizeGlyph requires a valid font face.";
return false;
}
const UINT16 glyphIndices[] = { glyph.glyphIndex };
const FLOAT glyphAdvances[] = { 0.0f };
const DWRITE_GLYPH_OFFSET glyphOffsets[] = { DWRITE_GLYPH_OFFSET{} };
DWRITE_GLYPH_RUN glyphRun = {};
glyphRun.fontFace = glyph.fontFace.Get();
glyphRun.fontEmSize = glyph.fontEmSize;
glyphRun.glyphCount = 1u;
glyphRun.glyphIndices = glyphIndices;
glyphRun.glyphAdvances = glyphAdvances;
glyphRun.glyphOffsets = glyphOffsets;
glyphRun.isSideways = glyph.isSideways ? TRUE : FALSE;
glyphRun.bidiLevel = 0u;
Microsoft::WRL::ComPtr<IDWriteGlyphRunAnalysis> analysis = {};
const float dpiScale = ClampDpiScale(m_dpiScale);
HRESULT hr = m_dwriteFactory->CreateGlyphRunAnalysis(
&glyphRun,
dpiScale,
nullptr,
DWRITE_RENDERING_MODE_NATURAL,
glyph.measuringMode,
0.0f,
0.0f,
analysis.ReleaseAndGetAddressOf());
if (FAILED(hr) || analysis == nullptr) {
outError = HrToString("IDWriteFactory::CreateGlyphRunAnalysis", hr);
return false;
}
RECT bounds = {};
hr = analysis->GetAlphaTextureBounds(DWRITE_TEXTURE_CLEARTYPE_3x1, &bounds);
if (FAILED(hr)) {
outError = HrToString("IDWriteGlyphRunAnalysis::GetAlphaTextureBounds", hr);
return false;
}
outGlyph.boundsLeft = bounds.left;
outGlyph.boundsTop = bounds.top;
outGlyph.boundsRight = bounds.right;
outGlyph.boundsBottom = bounds.bottom;
const LONG boundsWidth = bounds.right - bounds.left;
const LONG boundsHeight = bounds.bottom - bounds.top;
if (boundsWidth <= 0 || boundsHeight <= 0) {
return true;
}
std::vector<std::uint8_t> alphaTexture(
static_cast<std::size_t>(boundsWidth) * static_cast<std::size_t>(boundsHeight) * 3u);
hr = analysis->CreateAlphaTexture(
DWRITE_TEXTURE_CLEARTYPE_3x1,
&bounds,
alphaTexture.data(),
static_cast<UINT32>(alphaTexture.size()));
if (FAILED(hr)) {
outError = HrToString("IDWriteGlyphRunAnalysis::CreateAlphaTexture", hr);
return false;
}
outGlyph.width = static_cast<UINT>(boundsWidth);
outGlyph.height = static_cast<UINT>(boundsHeight);
outGlyph.rgbaPixels.assign(
static_cast<std::size_t>(outGlyph.width) * static_cast<std::size_t>(outGlyph.height) * 4u,
0u);
CompositeCoverage(
outGlyph.rgbaPixels,
outGlyph.width,
outGlyph.height,
RECT{ 0, 0, boundsWidth, boundsHeight },
alphaTexture.data(),
static_cast<std::size_t>(boundsWidth) * 3u,
3u);
return true;
}
float D3D12UiTextSystem::MeasureTextWidth(
const ::XCEngine::UI::Editor::UIEditorTextMeasureRequest& request) const {
if (!m_dwriteFactory || request.text.empty()) {
return 0.0f;
}
const std::wstring text = Utf8ToWide(request.text);
if (text.empty()) {
return 0.0f;
}
const float dpiScale = ClampDpiScale(m_dpiScale);
const float scaledFontSize = ResolveFontSize(request.fontSize) * dpiScale;
IDWriteTextFormat* textFormat = GetTextFormat(scaledFontSize);
if (textFormat == nullptr) {
return 0.0f;
}
Microsoft::WRL::ComPtr<IDWriteTextLayout> textLayout = {};
HRESULT hr = m_dwriteFactory->CreateTextLayout(
text.c_str(),
static_cast<UINT32>(text.size()),
textFormat,
4096.0f,
scaledFontSize * 2.0f,
textLayout.ReleaseAndGetAddressOf());
if (FAILED(hr) || textLayout == nullptr) {
return 0.0f;
}
DWRITE_TEXT_METRICS textMetrics = {};
hr = textLayout->GetMetrics(&textMetrics);
if (FAILED(hr)) {
return 0.0f;
}
DWRITE_OVERHANG_METRICS overhangMetrics = {};
float width = textMetrics.widthIncludingTrailingWhitespace;
if (SUCCEEDED(textLayout->GetOverhangMetrics(&overhangMetrics))) {
width += (std::max)(overhangMetrics.left, 0.0f);
width += (std::max)(overhangMetrics.right, 0.0f);
}
return std::ceil(width) / dpiScale;
}
bool D3D12UiTextSystem::RasterizeTextMask(
std::string_view text,
float fontSize,
std::vector<std::uint8_t>& outRgbaPixels,
UINT& outWidth,
UINT& outHeight,
std::string& outError) {
outRgbaPixels.clear();
outWidth = 0u;
outHeight = 0u;
outError.clear();
if (!m_dwriteFactory) {
outError = "RasterizeTextMask requires an initialized DirectWrite factory.";
return false;
}
if (text.empty()) {
outError = "RasterizeTextMask rejected an empty text payload.";
return false;
}
const std::wstring wideText = Utf8ToWide(text);
if (wideText.empty()) {
outError = "RasterizeTextMask could not convert UTF-8 text to UTF-16.";
return false;
}
const float dpiScale = ClampDpiScale(m_dpiScale);
const float scaledFontSize = ResolveFontSize(fontSize) * dpiScale;
IDWriteTextFormat* textFormat = GetTextFormat(scaledFontSize);
if (textFormat == nullptr) {
outError = "RasterizeTextMask could not resolve the DirectWrite text format.";
return false;
}
Microsoft::WRL::ComPtr<IDWriteTextLayout> textLayout = {};
HRESULT hr = m_dwriteFactory->CreateTextLayout(
wideText.c_str(),
static_cast<UINT32>(wideText.size()),
textFormat,
4096.0f,
scaledFontSize * 2.0f,
textLayout.ReleaseAndGetAddressOf());
if (FAILED(hr) || textLayout == nullptr) {
outError = HrToString("IDWriteFactory::CreateTextLayout", hr);
return false;
}
DWRITE_TEXT_METRICS textMetrics = {};
hr = textLayout->GetMetrics(&textMetrics);
if (FAILED(hr)) {
outError = HrToString("IDWriteTextLayout::GetMetrics", hr);
return false;
}
DWRITE_OVERHANG_METRICS overhangMetrics = {};
textLayout->GetOverhangMetrics(&overhangMetrics);
const float leftPad = (std::max)(overhangMetrics.left, 0.0f);
const float topPad = (std::max)(overhangMetrics.top, 0.0f);
const float rightPad = (std::max)(overhangMetrics.right, 0.0f);
const float bottomPad = (std::max)(overhangMetrics.bottom, 0.0f);
outWidth = (std::max)(
1u,
static_cast<UINT>(std::ceil(
textMetrics.widthIncludingTrailingWhitespace + leftPad + rightPad)));
outHeight = (std::max)(
1u,
static_cast<UINT>(std::ceil(
(std::max)(textMetrics.height, scaledFontSize * 1.6f) + topPad + bottomPad)));
outRgbaPixels.assign(
static_cast<std::size_t>(outWidth) * static_cast<std::size_t>(outHeight) * 4u,
0u);
GlyphRunCollector* collector = new GlyphRunCollector(dpiScale);
hr = textLayout->Draw(
nullptr,
collector,
leftPad,
topPad);
const std::vector<GlyphRunRecord> glyphRuns = collector->GetGlyphRuns();
collector->Release();
if (FAILED(hr)) {
outError = HrToString("IDWriteTextLayout::Draw", hr);
return false;
}
for (const GlyphRunRecord& record : glyphRuns) {
if (record.fontFace == nullptr || record.glyphIndices.empty()) {
continue;
}
DWRITE_GLYPH_RUN glyphRun = {};
glyphRun.fontFace = record.fontFace.Get();
glyphRun.fontEmSize = record.fontEmSize;
glyphRun.glyphCount = static_cast<UINT32>(record.glyphIndices.size());
glyphRun.glyphIndices = record.glyphIndices.data();
glyphRun.glyphAdvances = record.glyphAdvances.data();
glyphRun.glyphOffsets =
record.glyphOffsets.empty() ? nullptr : record.glyphOffsets.data();
glyphRun.isSideways = record.isSideways;
glyphRun.bidiLevel = record.bidiLevel;
Microsoft::WRL::ComPtr<IDWriteGlyphRunAnalysis> analysis = {};
hr = m_dwriteFactory->CreateGlyphRunAnalysis(
&glyphRun,
dpiScale,
nullptr,
DWRITE_RENDERING_MODE_NATURAL,
record.measuringMode,
record.baselineOriginX,
record.baselineOriginY,
analysis.ReleaseAndGetAddressOf());
if (FAILED(hr) || analysis == nullptr) {
outError = HrToString("IDWriteFactory::CreateGlyphRunAnalysis", hr);
return false;
}
RECT bounds = {};
hr = analysis->GetAlphaTextureBounds(DWRITE_TEXTURE_CLEARTYPE_3x1, &bounds);
if (FAILED(hr)) {
outError = HrToString("IDWriteGlyphRunAnalysis::GetAlphaTextureBounds", hr);
return false;
}
const LONG boundsWidth = bounds.right - bounds.left;
const LONG boundsHeight = bounds.bottom - bounds.top;
if (boundsWidth <= 0 || boundsHeight <= 0) {
continue;
}
std::vector<std::uint8_t> alphaTexture(
static_cast<std::size_t>(boundsWidth) * static_cast<std::size_t>(boundsHeight) * 3u);
hr = analysis->CreateAlphaTexture(
DWRITE_TEXTURE_CLEARTYPE_3x1,
&bounds,
alphaTexture.data(),
static_cast<UINT32>(alphaTexture.size()));
if (FAILED(hr)) {
outError = HrToString("IDWriteGlyphRunAnalysis::CreateAlphaTexture", hr);
return false;
}
CompositeCoverage(
outRgbaPixels,
outWidth,
outHeight,
bounds,
alphaTexture.data(),
static_cast<std::size_t>(boundsWidth) * 3u,
3u);
}
return true;
}
IDWriteTextFormat* D3D12UiTextSystem::GetTextFormat(float fontSize) const {
if (!m_dwriteFactory) {
return nullptr;
}
const float resolvedFontSize = ResolveFontSize(fontSize);
const int key = static_cast<int>(std::lround(resolvedFontSize * 10.0f));
const auto found = m_textFormats.find(key);
if (found != m_textFormats.end()) {
return found->second.Get();
}
Microsoft::WRL::ComPtr<IDWriteTextFormat> textFormat = {};
const HRESULT hr = m_dwriteFactory->CreateTextFormat(
L"Segoe UI",
nullptr,
DWRITE_FONT_WEIGHT_REGULAR,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
resolvedFontSize,
L"",
textFormat.ReleaseAndGetAddressOf());
if (FAILED(hr) || textFormat == nullptr) {
return nullptr;
}
textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);
textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR);
textFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
IDWriteTextFormat* result = textFormat.Get();
m_textFormats.emplace(key, std::move(textFormat));
return result;
}
std::wstring D3D12UiTextSystem::Utf8ToWide(std::string_view text) {
if (text.empty()) {
return {};
}
const int sizeNeeded = MultiByteToWideChar(
CP_UTF8,
0,
text.data(),
static_cast<int>(text.size()),
nullptr,
0);
if (sizeNeeded <= 0) {
return {};
}
std::wstring wideText(static_cast<std::size_t>(sizeNeeded), L'\0');
MultiByteToWideChar(
CP_UTF8,
0,
text.data(),
static_cast<int>(text.size()),
wideText.data(),
sizeNeeded);
return wideText;
}
} // namespace XCEngine::UI::Editor::Host

View File

@@ -0,0 +1,85 @@
#pragma once
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <XCEditor/Foundation/UIEditorTextMeasurement.h>
#include <dwrite.h>
#include <windows.h>
#include <wrl/client.h>
#include <cstdint>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
namespace XCEngine::UI::Editor::Host {
class D3D12UiTextSystem final : public ::XCEngine::UI::Editor::UIEditorTextMeasurer {
public:
struct ShapedGlyph {
Microsoft::WRL::ComPtr<IDWriteFontFace> fontFace = {};
std::uint16_t glyphIndex = 0u;
float fontEmSize = 0.0f;
float originX = 0.0f;
float originY = 0.0f;
DWRITE_MEASURING_MODE measuringMode = DWRITE_MEASURING_MODE_NATURAL;
bool isSideways = false;
};
struct ShapedTextRun {
std::vector<ShapedGlyph> glyphs = {};
float width = 0.0f;
float height = 0.0f;
};
struct RasterizedGlyph {
std::vector<std::uint8_t> rgbaPixels = {};
UINT width = 0u;
UINT height = 0u;
LONG boundsLeft = 0;
LONG boundsTop = 0;
LONG boundsRight = 0;
LONG boundsBottom = 0;
};
bool Initialize();
void Shutdown();
void SetDpiScale(float dpiScale);
float GetDpiScale() const;
const std::string& GetLastError() const;
float MeasureTextWidth(
const ::XCEngine::UI::Editor::UIEditorTextMeasureRequest& request) const override;
bool ShapeTextRun(
std::string_view text,
float fontSize,
ShapedTextRun& outRun,
std::string& outError) const;
bool RasterizeGlyph(
const ShapedGlyph& glyph,
RasterizedGlyph& outGlyph,
std::string& outError) const;
bool RasterizeTextMask(
std::string_view text,
float fontSize,
std::vector<std::uint8_t>& outRgbaPixels,
UINT& outWidth,
UINT& outHeight,
std::string& outError);
private:
IDWriteTextFormat* GetTextFormat(float fontSize) const;
static std::wstring Utf8ToWide(std::string_view text);
Microsoft::WRL::ComPtr<IDWriteFactory> m_dwriteFactory = {};
mutable std::unordered_map<int, Microsoft::WRL::ComPtr<IDWriteTextFormat>> m_textFormats = {};
float m_dpiScale = 1.0f;
std::string m_lastError = {};
};
} // namespace XCEngine::UI::Editor::Host

View File

@@ -0,0 +1,676 @@
#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>
#include <cstdio>
#include <limits>
#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;
}
m_frameUploadBuffers[frameSlot].clear();
m_activeFrameSlot = frameSlot;
m_hasActiveFrameSlot = true;
}
void D3D12UiTextureHost::Shutdown() {
for (auto& entry : m_liveTextures) {
if (entry.second != nullptr) {
DestroyTextureResource(*entry.second);
}
}
m_liveTextures.clear();
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();
}
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;
}
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;
}
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) {
DestroyTextureResource(*found->second);
m_liveTextures.erase(found);
}
texture = {};
}
} // namespace XCEngine::UI::Editor::Host

View File

@@ -0,0 +1,113 @@
#pragma once
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include "Ports/TexturePort.h"
#include <Rendering/D3D12/D3D12WindowRenderer.h>
#include <XCEngine/RHI/RHITexture.h>
#include <wincodec.h>
#include <wrl/client.h>
#include <array>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
namespace XCEngine::UI::Editor::Host {
class D3D12UiTextureHost final : public Ports::TexturePort {
public:
bool Initialize(D3D12WindowRenderer& windowRenderer);
void Shutdown();
bool IsInitialized() const;
const std::string& GetLastError() const;
void BeginFrame(std::uint32_t frameSlot);
bool LoadTextureFromFile(
const std::filesystem::path& path,
::XCEngine::UI::UITextureHandle& outTexture,
std::string& outError) override;
bool LoadTextureFromMemory(
const std::uint8_t* data,
std::size_t size,
::XCEngine::UI::UITextureHandle& outTexture,
std::string& outError) override;
bool LoadTextureFromRgba(
const std::uint8_t* rgbaPixels,
std::uint32_t width,
std::uint32_t height,
::XCEngine::UI::UITextureHandle& outTexture,
std::string& outError) override;
bool 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);
void ReleaseTexture(::XCEngine::UI::UITextureHandle& texture) override;
private:
struct TextureResource {
::XCEngine::RHI::RHITexture* texture = nullptr;
D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle = {};
D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle = {};
std::uint32_t width = 0u;
std::uint32_t height = 0u;
};
bool EnsureWicFactory(std::string& outError);
bool DecodeTextureFile(
const std::filesystem::path& path,
std::vector<std::uint8_t>& outPixels,
std::uint32_t& outWidth,
std::uint32_t& outHeight,
std::string& outError);
bool 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);
bool DecodeTextureFrame(
IWICBitmapSource& source,
std::vector<std::uint8_t>& outPixels,
std::uint32_t& outWidth,
std::uint32_t& outHeight,
std::string& outError);
bool CreateTextureResource(
const std::uint8_t* rgbaPixels,
std::uint32_t width,
std::uint32_t height,
::XCEngine::UI::UITextureHandle& outTexture,
std::string& outError);
TextureResource* ResolveTextureResource(const ::XCEngine::UI::UITextureHandle& texture) const;
void DestroyTextureResource(TextureResource& textureResource);
D3D12WindowRenderer* m_windowRenderer = nullptr;
Microsoft::WRL::ComPtr<IWICImagingFactory> m_wicFactory = {};
bool m_wicComInitialized = false;
std::array<
std::vector<Microsoft::WRL::ComPtr<ID3D12Resource>>,
D3D12WindowRenderer::kFrameContextCount>
m_frameUploadBuffers = {};
std::uint32_t m_activeFrameSlot = 0u;
bool m_hasActiveFrameSlot = false;
std::unordered_map<std::uintptr_t, std::unique_ptr<TextureResource>> m_liveTextures = {};
std::string m_lastError = {};
};
} // namespace XCEngine::UI::Editor::Host

View File

@@ -61,15 +61,7 @@ bool D3D12WindowInteropContext::Attach(
}
m_d2dFactory = &d2dFactory;
if (!EnsureInterop()) {
return false;
}
if (HasBackBufferTargets()) {
return true;
}
return RebuildBackBufferTargets();
return EnsureInterop();
}
void D3D12WindowInteropContext::Detach() {
@@ -95,10 +87,6 @@ ID2D1DeviceContext* D3D12WindowInteropContext::GetD2DDeviceContext() const {
return m_d2dDeviceContext.Get();
}
ID2D1SolidColorBrush* D3D12WindowInteropContext::GetInteropBrush() const {
return m_interopBrush.Get();
}
const std::string& D3D12WindowInteropContext::GetLastError() const {
return m_lastError;
}
@@ -113,8 +101,7 @@ bool D3D12WindowInteropContext::EnsureInterop() {
return false;
}
if (m_d3d11On12Device != nullptr &&
m_d2dDeviceContext != nullptr &&
m_interopBrush != nullptr) {
m_d2dDeviceContext != nullptr) {
return true;
}
@@ -207,15 +194,6 @@ bool D3D12WindowInteropContext::EnsureInterop() {
return false;
}
hr = m_d2dDeviceContext->CreateSolidColorBrush(
D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f),
m_interopBrush.ReleaseAndGetAddressOf());
if (FAILED(hr) || m_interopBrush == nullptr) {
m_lastError = HrToInteropString("ID2D1DeviceContext::CreateSolidColorBrush", hr);
ReleaseInteropState();
return false;
}
m_d2dDeviceContext->SetDpi(96.0f, 96.0f);
m_d2dDeviceContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
m_lastError.clear();
@@ -223,134 +201,19 @@ bool D3D12WindowInteropContext::EnsureInterop() {
}
void D3D12WindowInteropContext::ReleaseInteropState() {
ReleaseBackBufferTargets();
m_interopBrush.Reset();
m_d2dDeviceContext.Reset();
m_d2dDevice.Reset();
m_d3d11On12Device.Reset();
m_d3d11DeviceContext.Reset();
m_d3d11Device.Reset();
}
void D3D12WindowInteropContext::ReleaseBackBufferTargets() {
ClearSourceTextures();
if (m_d2dDeviceContext != nullptr) {
m_d2dDeviceContext->SetTarget(nullptr);
}
if (m_d3d11DeviceContext != nullptr) {
m_d3d11DeviceContext->ClearState();
}
m_backBufferTargets.clear();
if (m_d2dDeviceContext != nullptr) {
D2D1_TAG firstTag = 0u;
D2D1_TAG secondTag = 0u;
m_d2dDeviceContext->Flush(&firstTag, &secondTag);
}
if (m_d3d11DeviceContext != nullptr) {
m_d3d11DeviceContext->Flush();
}
}
bool D3D12WindowInteropContext::RebuildBackBufferTargets() {
m_backBufferTargets.clear();
if (m_windowRenderer == nullptr || m_d3d11On12Device == nullptr || m_d2dDeviceContext == nullptr) {
return false;
}
const std::uint32_t backBufferCount = m_windowRenderer->GetBackBufferCount();
m_backBufferTargets.resize(backBufferCount);
for (std::uint32_t index = 0u; index < backBufferCount; ++index) {
const ::XCEngine::RHI::D3D12Texture* backBufferTexture =
m_windowRenderer->GetBackBufferTexture(index);
if (backBufferTexture == nullptr || backBufferTexture->GetResource() == nullptr) {
m_lastError = "Failed to resolve a D3D12 swap chain back buffer.";
m_backBufferTargets.clear();
return false;
}
D3D11_RESOURCE_FLAGS resourceFlags = {};
resourceFlags.BindFlags = D3D11_BIND_RENDER_TARGET;
HRESULT hr = m_d3d11On12Device->CreateWrappedResource(
backBufferTexture->GetResource(),
&resourceFlags,
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_PRESENT,
IID_PPV_ARGS(m_backBufferTargets[index].wrappedResource.ReleaseAndGetAddressOf()));
if (FAILED(hr) || m_backBufferTargets[index].wrappedResource == nullptr) {
m_lastError = HrToInteropString("ID3D11On12Device::CreateWrappedResource(backbuffer)", hr);
m_backBufferTargets.clear();
return false;
}
Microsoft::WRL::ComPtr<IDXGISurface> dxgiSurface = {};
hr = m_backBufferTargets[index].wrappedResource.As(&dxgiSurface);
if (FAILED(hr) || dxgiSurface == nullptr) {
m_lastError = HrToInteropString("ID3D11Resource::QueryInterface(IDXGISurface)", hr);
m_backBufferTargets.clear();
return false;
}
const D2D1_BITMAP_PROPERTIES1 bitmapProperties =
BuildD2DBitmapProperties(
backBufferTexture->GetDesc().Format,
D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW);
hr = m_d2dDeviceContext->CreateBitmapFromDxgiSurface(
dxgiSurface.Get(),
&bitmapProperties,
m_backBufferTargets[index].targetBitmap.ReleaseAndGetAddressOf());
if (FAILED(hr) || m_backBufferTargets[index].targetBitmap == nullptr) {
m_lastError = HrToInteropString(
"ID2D1DeviceContext::CreateBitmapFromDxgiSurface(backbuffer)",
hr);
m_backBufferTargets.clear();
return false;
}
}
m_lastError.clear();
return true;
}
bool D3D12WindowInteropContext::HasAttachedWindowRenderer() const {
return m_windowRenderer != nullptr &&
m_d3d11On12Device != nullptr &&
m_d2dDeviceContext != nullptr &&
!m_backBufferTargets.empty();
}
bool D3D12WindowInteropContext::HasBackBufferTargets() const {
return !m_backBufferTargets.empty();
}
void D3D12WindowInteropContext::BuildAcquiredResources(
std::uint32_t backBufferIndex,
std::vector<ID3D11Resource*>& outResources) const {
outResources.clear();
ID3D11Resource* backBufferResource = GetWrappedBackBufferResource(backBufferIndex);
if (backBufferResource != nullptr) {
outResources.push_back(backBufferResource);
}
for (const SourceTextureResource& resource : m_activeSourceTextures) {
if (resource.wrappedResource != nullptr) {
outResources.push_back(resource.wrappedResource.Get());
}
}
}
ID3D11Resource* D3D12WindowInteropContext::GetWrappedBackBufferResource(std::uint32_t index) const {
return index < m_backBufferTargets.size() ? m_backBufferTargets[index].wrappedResource.Get() : nullptr;
}
ID2D1Bitmap1* D3D12WindowInteropContext::GetBackBufferTargetBitmap(std::uint32_t index) const {
return index < m_backBufferTargets.size() ? m_backBufferTargets[index].targetBitmap.Get() : nullptr;
}
std::uint32_t D3D12WindowInteropContext::GetCurrentBackBufferIndex() const {
return m_windowRenderer != nullptr && m_windowRenderer->GetSwapChain() != nullptr
? m_windowRenderer->GetSwapChain()->GetCurrentBackBufferIndex()
: 0u;
m_d2dDeviceContext.Reset();
m_d2dDevice.Reset();
m_d3d11On12Device.Reset();
m_d3d11DeviceContext.Reset();
m_d3d11Device.Reset();
}
bool D3D12WindowInteropContext::PrepareSourceTextures(

View File

@@ -24,10 +24,6 @@ class D3D12WindowInteropContext {
public:
bool Attach(D3D12WindowRenderer& windowRenderer, ID2D1Factory1& d2dFactory);
void Detach();
void ReleaseBackBufferTargets();
bool RebuildBackBufferTargets();
bool HasAttachedWindowRenderer() const;
bool HasBackBufferTargets() const;
bool PrepareSourceTextures(const ::XCEngine::UI::UIDrawData& drawData);
void ClearSourceTextures();
bool ResolveInteropBitmap(
@@ -38,21 +34,9 @@ public:
ID3D11On12Device* GetD3D11On12Device() const;
ID3D11DeviceContext* GetD3D11DeviceContext() const;
ID2D1DeviceContext* GetD2DDeviceContext() const;
ID2D1SolidColorBrush* GetInteropBrush() const;
void BuildAcquiredResources(
std::uint32_t backBufferIndex,
std::vector<ID3D11Resource*>& outResources) const;
ID3D11Resource* GetWrappedBackBufferResource(std::uint32_t index) const;
ID2D1Bitmap1* GetBackBufferTargetBitmap(std::uint32_t index) const;
std::uint32_t GetCurrentBackBufferIndex() const;
const std::string& GetLastError() const;
private:
struct BackBufferTarget {
Microsoft::WRL::ComPtr<ID3D11Resource> wrappedResource = {};
Microsoft::WRL::ComPtr<ID2D1Bitmap1> targetBitmap = {};
};
struct SourceTextureResource {
std::uintptr_t key = 0u;
Microsoft::WRL::ComPtr<ID3D11Resource> wrappedResource = {};
@@ -69,8 +53,6 @@ private:
Microsoft::WRL::ComPtr<ID3D11On12Device> m_d3d11On12Device = {};
Microsoft::WRL::ComPtr<ID2D1Device> m_d2dDevice = {};
Microsoft::WRL::ComPtr<ID2D1DeviceContext> m_d2dDeviceContext = {};
Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> m_interopBrush = {};
std::vector<BackBufferTarget> m_backBufferTargets = {};
std::vector<SourceTextureResource> m_activeSourceTextures = {};
std::unordered_map<std::uintptr_t, Microsoft::WRL::ComPtr<ID2D1Bitmap1>> m_activeBitmaps = {};
std::string m_lastError = {};

View File

@@ -3,28 +3,22 @@
namespace XCEngine::UI::Editor::Host {
D3D12WindowRenderLoopAttachResult D3D12WindowRenderLoop::Attach(
NativeRenderer& uiRenderer,
D3D12UiRenderer& uiRenderer,
D3D12WindowRenderer& windowRenderer) {
m_uiRenderer = &uiRenderer;
m_windowRenderer = &windowRenderer;
D3D12WindowRenderLoopAttachResult result = {};
result.hasViewportSurfacePresentation = m_uiRenderer->AttachWindowRenderer(*m_windowRenderer);
result.hasViewportSurfacePresentation =
m_windowRenderer->GetSwapChain() != nullptr &&
m_windowRenderer->GetRHIDevice() != nullptr;
if (!result.hasViewportSurfacePresentation) {
const std::string& interopError = m_uiRenderer->GetLastRenderError();
result.interopWarning = interopError.empty()
? "native renderer d3d12 interop unavailable; falling back to hwnd renderer."
: "native renderer d3d12 interop unavailable; falling back to hwnd renderer: " +
interopError;
result.warning = "window render loop attach requires an initialized D3D12 window renderer.";
}
return result;
}
void D3D12WindowRenderLoop::Detach() {
if (m_uiRenderer != nullptr) {
m_uiRenderer->DetachWindowRenderer();
}
m_uiRenderer = nullptr;
m_windowRenderer = nullptr;
}
@@ -51,16 +45,10 @@ D3D12WindowRenderLoopFrameContext D3D12WindowRenderLoop::BeginFrame() const {
D3D12WindowRenderLoopResizeResult D3D12WindowRenderLoop::ApplyResize(UINT width, UINT height) {
D3D12WindowRenderLoopResizeResult result = {};
if (m_uiRenderer == nullptr || m_windowRenderer == nullptr) {
result.interopWarning = "window render loop is detached.";
result.windowRendererWarning = "window render loop is detached.";
return result;
}
m_uiRenderer->Resize(width, height);
const bool hadViewportSurfacePresentation = m_uiRenderer->HasAttachedWindowRenderer();
if (hadViewportSurfacePresentation) {
m_uiRenderer->ReleaseWindowRendererBackBufferTargets();
}
const bool resizedWindowRenderer =
m_windowRenderer->Resize(static_cast<int>(width), static_cast<int>(height));
if (!resizedWindowRenderer || !m_windowRenderer->GetLastError().empty()) {
@@ -70,36 +58,10 @@ D3D12WindowRenderLoopResizeResult D3D12WindowRenderLoop::ApplyResize(UINT width,
: "window renderer resize warning: " + resizeError;
}
if (!resizedWindowRenderer) {
if (hadViewportSurfacePresentation) {
result.hasViewportSurfacePresentation =
m_uiRenderer->RebuildWindowRendererBackBufferTargets();
if (!result.hasViewportSurfacePresentation) {
const D3D12WindowRenderLoopAttachResult attachResult =
Attach(*m_uiRenderer, *m_windowRenderer);
result.hasViewportSurfacePresentation = attachResult.hasViewportSurfacePresentation;
result.interopWarning = attachResult.interopWarning;
}
}
return result;
}
if (hadViewportSurfacePresentation) {
result.hasViewportSurfacePresentation =
m_uiRenderer->RebuildWindowRendererBackBufferTargets();
if (!result.hasViewportSurfacePresentation) {
const D3D12WindowRenderLoopAttachResult attachResult =
Attach(*m_uiRenderer, *m_windowRenderer);
result.hasViewportSurfacePresentation = attachResult.hasViewportSurfacePresentation;
result.interopWarning = attachResult.interopWarning;
}
return result;
}
const D3D12WindowRenderLoopAttachResult attachResult =
Attach(*m_uiRenderer, *m_windowRenderer);
result.hasViewportSurfacePresentation = attachResult.hasViewportSurfacePresentation;
result.interopWarning = attachResult.interopWarning;
result.hasViewportSurfacePresentation =
resizedWindowRenderer &&
m_windowRenderer->GetSwapChain() != nullptr &&
m_windowRenderer->GetCurrentRenderSurface() != nullptr;
return result;
}
@@ -111,31 +73,77 @@ D3D12WindowRenderLoopPresentResult D3D12WindowRenderLoop::Present(
return result;
}
if (HasViewportSurfacePresentation()) {
result.framePresented = m_uiRenderer->RenderToWindowRenderer(drawData);
if (!result.framePresented) {
const std::string& composeError = m_uiRenderer->GetLastRenderError();
result.warning = composeError.empty()
? "d3d12 window composition failed, falling back to hwnd renderer."
: "d3d12 window composition failed, falling back to hwnd renderer: " +
composeError;
}
if (!HasViewportSurfacePresentation()) {
result.warning = "window render loop has no active D3D12 presentation path.";
return result;
}
if (!result.framePresented) {
result.framePresented = m_uiRenderer->Render(drawData);
if (!result.framePresented && result.warning.empty()) {
result.warning = m_uiRenderer->GetLastRenderError();
}
if (!m_windowRenderer->PrepareCurrentBackBufferForUiRender()) {
const std::string& error = m_windowRenderer->GetLastError();
result.warning = error.empty()
? "failed to prepare the current back buffer for UI rendering."
: error;
return result;
}
const ::XCEngine::Rendering::RenderContext renderContext =
m_windowRenderer->GetRenderContext();
const ::XCEngine::Rendering::RenderSurface* surface =
m_windowRenderer->GetCurrentRenderSurface();
if (surface == nullptr) {
result.warning = "window render loop could not resolve the current back buffer surface.";
return result;
}
if (!m_uiRenderer->Render(drawData, renderContext, *surface)) {
const std::string& error = m_uiRenderer->GetLastError();
result.warning = error.empty()
? "d3d12 ui renderer failed to render the current frame."
: error;
return result;
}
if (!m_windowRenderer->FinalizeCurrentBackBufferForPresent()) {
const std::string& error = m_windowRenderer->GetLastError();
result.warning = error.empty()
? "failed to finalize the current back buffer for present."
: error;
return result;
}
if (!m_windowRenderer->SubmitFrame()) {
const std::string& error = m_windowRenderer->GetLastError();
result.warning = error.empty()
? "failed to submit the current D3D12 frame."
: error;
return result;
}
if (!m_windowRenderer->SignalFrameCompletion()) {
const std::string& error = m_windowRenderer->GetLastError();
result.warning = error.empty()
? "failed to signal current frame completion."
: error;
return result;
}
if (!m_windowRenderer->PresentFrame()) {
const std::string& error = m_windowRenderer->GetLastError();
result.warning = error.empty()
? "failed to present the swap chain."
: error;
return result;
}
result.framePresented = true;
return result;
}
bool D3D12WindowRenderLoop::HasViewportSurfacePresentation() const {
return m_uiRenderer != nullptr &&
m_windowRenderer != nullptr &&
m_uiRenderer->HasAttachedWindowRenderer();
m_windowRenderer->GetSwapChain() != nullptr &&
m_windowRenderer->GetCurrentRenderSurface() != nullptr;
}
} // namespace XCEngine::UI::Editor::Host

View File

@@ -1,7 +1,7 @@
#pragma once
#include <Rendering/D3D12/D3D12UiRenderer.h>
#include <Rendering/D3D12/D3D12WindowRenderer.h>
#include <Rendering/Native/NativeRenderer.h>
#include <XCEngine/UI/DrawData.h>
@@ -11,7 +11,7 @@ namespace XCEngine::UI::Editor::Host {
struct D3D12WindowRenderLoopAttachResult {
bool hasViewportSurfacePresentation = false;
std::string interopWarning = {};
std::string warning = {};
};
struct D3D12WindowRenderLoopFrameContext {
@@ -23,7 +23,6 @@ struct D3D12WindowRenderLoopFrameContext {
struct D3D12WindowRenderLoopResizeResult {
bool hasViewportSurfacePresentation = false;
std::string windowRendererWarning = {};
std::string interopWarning = {};
};
struct D3D12WindowRenderLoopPresentResult {
@@ -34,7 +33,7 @@ struct D3D12WindowRenderLoopPresentResult {
class D3D12WindowRenderLoop {
public:
D3D12WindowRenderLoopAttachResult Attach(
NativeRenderer& uiRenderer,
D3D12UiRenderer& uiRenderer,
D3D12WindowRenderer& windowRenderer);
void Detach();
@@ -46,7 +45,7 @@ public:
bool HasViewportSurfacePresentation() const;
private:
NativeRenderer* m_uiRenderer = nullptr;
D3D12UiRenderer* m_uiRenderer = nullptr;
D3D12WindowRenderer* m_windowRenderer = nullptr;
};

View File

@@ -27,8 +27,8 @@ bool D3D12WindowRenderer::Initialize(HWND hwnd, int width, int height) {
auto* device = m_hostDevice.GetRHIDevice();
if (device == nullptr ||
!m_viewportTextureAllocator.Initialize(*device)) {
m_lastError = "Failed to initialize the viewport texture allocator.";
!m_textureAllocator.Initialize(*device, 512u)) {
m_lastError = "Failed to initialize the UI texture descriptor allocator.";
Shutdown();
return false;
}
@@ -38,8 +38,8 @@ bool D3D12WindowRenderer::Initialize(HWND hwnd, int width, int height) {
}
void D3D12WindowRenderer::Shutdown() {
m_viewportTextureCpuHandles.clear();
m_viewportTextureAllocator.Shutdown();
m_textureCpuHandles.clear();
m_textureAllocator.Shutdown();
m_presenter.Shutdown();
m_hostDevice.Shutdown();
m_activeFrameSlot = 0u;
@@ -84,35 +84,36 @@ bool D3D12WindowRenderer::BeginFrame() {
return true;
}
bool D3D12WindowRenderer::PreparePresentSurface() {
bool D3D12WindowRenderer::PrepareCurrentBackBufferForUiRender() {
::XCEngine::Rendering::RenderContext renderContext = GetRenderContext();
if (!renderContext.IsValid() || renderContext.commandList == nullptr) {
m_lastError = "PreparePresentSurface requires a valid render context.";
m_lastError = "PrepareCurrentBackBufferForUiRender requires a valid render context.";
return false;
}
const bool prepared = m_presenter.PreparePresentSurface(renderContext);
const bool prepared = m_presenter.PrepareCurrentBackBufferForRender(renderContext);
m_lastError = prepared ? std::string() : m_presenter.GetLastError();
return prepared;
}
bool D3D12WindowRenderer::SubmitFrame(bool presentSwapChain) {
if (presentSwapChain && m_presenter.GetSwapChain() == nullptr) {
m_lastError = "SubmitFrame requested present without a swap chain.";
bool D3D12WindowRenderer::FinalizeCurrentBackBufferForPresent() {
::XCEngine::Rendering::RenderContext renderContext = GetRenderContext();
if (!renderContext.IsValid() || renderContext.commandList == nullptr) {
m_lastError = "FinalizeCurrentBackBufferForPresent requires a valid render context.";
return false;
}
const bool finalized = m_presenter.FinalizeCurrentBackBufferForPresent(renderContext);
m_lastError = finalized ? std::string() : m_presenter.GetLastError();
return finalized;
}
bool D3D12WindowRenderer::SubmitFrame() {
if (!m_hostDevice.SubmitFrame(m_activeFrameSlot)) {
m_lastError = m_hostDevice.GetLastError();
return false;
}
if (presentSwapChain) {
if (!m_presenter.PresentFrame()) {
m_lastError = m_presenter.GetLastError();
return false;
}
}
m_lastError.clear();
return true;
}
@@ -157,13 +158,13 @@ bool D3D12WindowRenderer::CreateViewportTextureHandle(
outTexture = {};
if (width == 0u ||
height == 0u ||
!m_viewportTextureAllocator.IsInitialized()) {
!m_textureAllocator.IsInitialized()) {
return false;
}
D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle = {};
D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle = {};
if (!m_viewportTextureAllocator.CreateTextureDescriptor(
if (!m_textureAllocator.CreateTextureDescriptor(
&texture,
&cpuHandle,
&gpuHandle) ||
@@ -177,7 +178,7 @@ bool D3D12WindowRenderer::CreateViewportTextureHandle(
outTexture.height = height;
outTexture.kind = ::XCEngine::UI::UITextureHandleKind::ShaderResourceView;
outTexture.resourceHandle = reinterpret_cast<std::uintptr_t>(&texture);
m_viewportTextureCpuHandles[outTexture.nativeHandle] = cpuHandle;
m_textureCpuHandles[outTexture.nativeHandle] = cpuHandle;
return true;
}
@@ -188,12 +189,12 @@ void D3D12WindowRenderer::ReleaseViewportTextureHandle(
return;
}
const auto found = m_viewportTextureCpuHandles.find(texture.nativeHandle);
if (found != m_viewportTextureCpuHandles.end()) {
const auto found = m_textureCpuHandles.find(texture.nativeHandle);
if (found != m_textureCpuHandles.end()) {
D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle = {};
gpuHandle.ptr = static_cast<UINT64>(texture.nativeHandle);
m_viewportTextureAllocator.Free(found->second, gpuHandle);
m_viewportTextureCpuHandles.erase(found);
m_textureAllocator.Free(found->second, gpuHandle);
m_textureCpuHandles.erase(found);
}
texture = {};
@@ -219,8 +220,22 @@ std::uint32_t D3D12WindowRenderer::GetBackBufferCount() const {
return m_presenter.GetBackBufferCount();
}
std::uint32_t D3D12WindowRenderer::GetActiveFrameSlot() const {
return m_activeFrameSlot;
}
::XCEngine::Rendering::RenderContext D3D12WindowRenderer::GetRenderContext() const {
return m_hostDevice.GetRenderContext(m_activeFrameSlot);
}
D3D12ShaderResourceDescriptorAllocator&
D3D12WindowRenderer::GetTextureDescriptorAllocator() {
return m_textureAllocator;
}
const D3D12ShaderResourceDescriptorAllocator&
D3D12WindowRenderer::GetTextureDescriptorAllocator() const {
return m_textureAllocator;
}
} // namespace XCEngine::UI::Editor::Host

View File

@@ -32,8 +32,9 @@ public:
void Shutdown();
bool Resize(int width, int height);
bool BeginFrame();
bool PreparePresentSurface();
bool SubmitFrame(bool presentSwapChain);
bool PrepareCurrentBackBufferForUiRender();
bool FinalizeCurrentBackBufferForPresent();
bool SubmitFrame();
bool SignalFrameCompletion();
bool PresentFrame();
@@ -53,16 +54,19 @@ public:
const ::XCEngine::RHI::D3D12Texture* GetCurrentBackBufferTexture() const;
const ::XCEngine::RHI::D3D12Texture* GetBackBufferTexture(std::uint32_t index) const;
std::uint32_t GetBackBufferCount() const;
std::uint32_t GetActiveFrameSlot() const;
::XCEngine::Rendering::RenderContext GetRenderContext() const;
D3D12ShaderResourceDescriptorAllocator& GetTextureDescriptorAllocator();
const D3D12ShaderResourceDescriptorAllocator& GetTextureDescriptorAllocator() const;
private:
D3D12HostDevice m_hostDevice = {};
D3D12WindowSwapChainPresenter m_presenter = {};
D3D12ShaderResourceDescriptorAllocator m_viewportTextureAllocator = {};
D3D12ShaderResourceDescriptorAllocator m_textureAllocator = {};
std::uint32_t m_activeFrameSlot = 0u;
std::uint32_t m_nextFrameSlot = 0u;
std::uint32_t m_activeBackBufferIndex = 0u;
std::unordered_map<std::uintptr_t, D3D12_CPU_DESCRIPTOR_HANDLE> m_viewportTextureCpuHandles = {};
std::unordered_map<std::uintptr_t, D3D12_CPU_DESCRIPTOR_HANDLE> m_textureCpuHandles = {};
std::string m_lastError = {};
};

View File

@@ -240,15 +240,15 @@ bool D3D12WindowSwapChainPresenter::RecreateBackBufferViews() {
return true;
}
bool D3D12WindowSwapChainPresenter::PreparePresentSurface(
bool D3D12WindowSwapChainPresenter::PrepareCurrentBackBufferForRender(
const ::XCEngine::Rendering::RenderContext& renderContext) {
if (!renderContext.IsValid() || renderContext.commandList == nullptr) {
m_lastError = "PreparePresentSurface requires a valid render context.";
m_lastError = "PrepareCurrentBackBufferForRender requires a valid render context.";
return false;
}
if (m_swapChain == nullptr) {
m_lastError = "PreparePresentSurface requires an initialized swap chain.";
m_lastError = "PrepareCurrentBackBufferForRender requires an initialized swap chain.";
return false;
}
@@ -256,7 +256,7 @@ bool D3D12WindowSwapChainPresenter::PreparePresentSurface(
if (backBufferIndex >= m_backBufferViews.size() ||
m_backBufferViews[backBufferIndex] == nullptr) {
std::ostringstream error = {};
error << "PreparePresentSurface could not find the current swap chain RTV. index="
error << "PrepareCurrentBackBufferForRender could not find the current swap chain RTV. index="
<< backBufferIndex
<< " rtvCount="
<< m_backBufferViews.size();
@@ -272,6 +272,38 @@ bool D3D12WindowSwapChainPresenter::PreparePresentSurface(
return true;
}
bool D3D12WindowSwapChainPresenter::FinalizeCurrentBackBufferForPresent(
const ::XCEngine::Rendering::RenderContext& renderContext) {
if (!renderContext.IsValid() || renderContext.commandList == nullptr) {
m_lastError = "FinalizeCurrentBackBufferForPresent requires a valid render context.";
return false;
}
if (m_swapChain == nullptr) {
m_lastError = "FinalizeCurrentBackBufferForPresent requires an initialized swap chain.";
return false;
}
const std::uint32_t backBufferIndex = m_swapChain->GetCurrentBackBufferIndex();
if (backBufferIndex >= m_backBufferViews.size() ||
m_backBufferViews[backBufferIndex] == nullptr) {
std::ostringstream error = {};
error << "FinalizeCurrentBackBufferForPresent could not find the current swap chain RTV. index="
<< backBufferIndex
<< " rtvCount="
<< m_backBufferViews.size();
m_lastError = error.str();
return false;
}
renderContext.commandList->TransitionBarrier(
m_backBufferViews[backBufferIndex],
::XCEngine::RHI::ResourceStates::RenderTarget,
::XCEngine::RHI::ResourceStates::Present);
m_lastError.clear();
return true;
}
bool D3D12WindowSwapChainPresenter::PresentFrame() {
if (m_swapChain == nullptr) {
m_lastError = "PresentFrame requires an initialized swap chain.";

View File

@@ -28,7 +28,10 @@ public:
bool Initialize(D3D12HostDevice& hostDevice, HWND hwnd, int width, int height);
void Shutdown();
bool Resize(int width, int height);
bool PreparePresentSurface(const ::XCEngine::Rendering::RenderContext& renderContext);
bool PrepareCurrentBackBufferForRender(
const ::XCEngine::Rendering::RenderContext& renderContext);
bool FinalizeCurrentBackBufferForPresent(
const ::XCEngine::Rendering::RenderContext& renderContext);
bool PresentFrame();
const std::string& GetLastError() const;

View File

@@ -51,6 +51,7 @@ void AutoScreenshotController::RequestCapture(std::string reason) {
void AutoScreenshotController::CaptureIfRequested(
NativeRenderer& renderer,
D3D12WindowRenderer& windowRenderer,
const ::XCEngine::UI::UIDrawData& drawData,
unsigned int width,
unsigned int height,
@@ -82,7 +83,13 @@ void AutoScreenshotController::CaptureIfRequested(
std::string captureError = {};
const std::filesystem::path historyPath = BuildHistoryCapturePath(m_pendingReason);
if (!renderer.CaptureToPng(drawData, width, height, historyPath, captureError)) {
if (!renderer.CaptureToPng(
&windowRenderer,
drawData,
width,
height,
historyPath,
captureError)) {
m_lastCaptureError = std::move(captureError);
m_lastCaptureSummary = "AutoShot failed";
m_capturePending = false;

View File

@@ -14,6 +14,7 @@
namespace XCEngine::UI::Editor::Host {
class NativeRenderer;
class D3D12WindowRenderer;
class AutoScreenshotController {
public:
@@ -23,6 +24,7 @@ public:
void RequestCapture(std::string reason);
void CaptureIfRequested(
NativeRenderer& renderer,
D3D12WindowRenderer& windowRenderer,
const ::XCEngine::UI::UIDrawData& drawData,
unsigned int width,
unsigned int height,

View File

@@ -18,27 +18,9 @@ bool NativeRenderer::Initialize(HWND hwnd) {
}
m_hwnd = hwnd;
D2D1_FACTORY_OPTIONS factoryOptions = {};
#ifdef _DEBUG
factoryOptions.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif
HRESULT hr = D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
__uuidof(ID2D1Factory1),
&factoryOptions,
reinterpret_cast<void**>(m_d2dFactory.ReleaseAndGetAddressOf()));
if (FAILED(hr)) {
m_lastRenderError = HrToString("D2D1CreateFactory", hr);
Shutdown();
return false;
}
hr = DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory),
reinterpret_cast<IUnknown**>(m_dwriteFactory.ReleaseAndGetAddressOf()));
if (FAILED(hr)) {
m_lastRenderError = HrToString("DWriteCreateFactory", hr);
std::string error = {};
if (!EnsureCoreFactories(error)) {
m_lastRenderError = error;
Shutdown();
return false;
}
@@ -48,7 +30,7 @@ bool NativeRenderer::Initialize(HWND hwnd) {
}
void NativeRenderer::Shutdown() {
DetachWindowRenderer();
m_windowInterop.Detach();
while (!m_liveTextures.empty()) {
auto it = m_liveTextures.begin();
delete *it;
@@ -141,121 +123,42 @@ bool NativeRenderer::Render(const ::XCEngine::UI::UIDrawData& drawData) {
return true;
}
bool NativeRenderer::RenderToWindowRenderer(const ::XCEngine::UI::UIDrawData& drawData) {
if (!EnsureWindowRendererInterop()) {
if (m_lastRenderError.empty()) {
m_lastRenderError = "Window renderer interop is not available.";
bool NativeRenderer::EnsureCoreFactories(std::string& outError) {
outError.clear();
if (m_d2dFactory != nullptr && m_dwriteFactory != nullptr) {
return true;
}
if (m_d2dFactory == nullptr) {
D2D1_FACTORY_OPTIONS factoryOptions = {};
#ifdef _DEBUG
factoryOptions.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif
const HRESULT factoryHr = D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
__uuidof(ID2D1Factory1),
&factoryOptions,
reinterpret_cast<void**>(m_d2dFactory.ReleaseAndGetAddressOf()));
if (FAILED(factoryHr) || m_d2dFactory == nullptr) {
outError = HrToString("D2D1CreateFactory", factoryHr);
m_d2dFactory.Reset();
return false;
}
return false;
}
if (!m_windowInterop.HasBackBufferTargets() &&
!m_windowInterop.RebuildBackBufferTargets()) {
if (m_lastRenderError.empty()) {
m_lastRenderError = "Window renderer back buffer interop targets are unavailable.";
if (m_dwriteFactory == nullptr) {
const HRESULT writeHr = DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory),
reinterpret_cast<IUnknown**>(m_dwriteFactory.ReleaseAndGetAddressOf()));
if (FAILED(writeHr) || m_dwriteFactory == nullptr) {
outError = HrToString("DWriteCreateFactory", writeHr);
m_dwriteFactory.Reset();
return false;
}
return false;
}
ID3D11On12Device* d3d11On12Device = m_windowInterop.GetD3D11On12Device();
ID3D11DeviceContext* d3d11DeviceContext = m_windowInterop.GetD3D11DeviceContext();
ID2D1DeviceContext* d2dDeviceContext = m_windowInterop.GetD2DDeviceContext();
ID2D1SolidColorBrush* interopBrush = m_windowInterop.GetInteropBrush();
if (d3d11On12Device == nullptr ||
d3d11DeviceContext == nullptr ||
d2dDeviceContext == nullptr ||
interopBrush == nullptr) {
m_lastRenderError = "Window renderer interop resources are incomplete.";
return false;
}
const std::uint32_t backBufferIndex = m_windowInterop.GetCurrentBackBufferIndex();
if (m_windowInterop.GetWrappedBackBufferResource(backBufferIndex) == nullptr ||
m_windowInterop.GetBackBufferTargetBitmap(backBufferIndex) == nullptr) {
m_lastRenderError = "Back buffer interop target index is out of range.";
return false;
}
if (!m_windowRenderer->PreparePresentSurface()) {
m_lastRenderError = "Failed to prepare the D3D12 present surface: " +
m_windowRenderer->GetLastError();
return false;
}
if (!m_windowRenderer->SubmitFrame(false)) {
m_lastRenderError = "Failed to submit the D3D12 frame before UI composition.";
return false;
}
if (!m_windowInterop.PrepareSourceTextures(drawData)) {
ID3D11Resource* backBufferResource =
m_windowInterop.GetWrappedBackBufferResource(backBufferIndex);
if (backBufferResource != nullptr) {
d3d11On12Device->AcquireWrappedResources(&backBufferResource, 1u);
d3d11On12Device->ReleaseWrappedResources(&backBufferResource, 1u);
}
d3d11DeviceContext->Flush();
m_windowInterop.ClearSourceTextures();
const bool signaled = m_windowRenderer->SignalFrameCompletion();
ReleaseWindowRendererInterop();
if (!signaled) {
m_lastRenderError =
"Failed to signal D3D12 frame completion after interop preparation failed.";
}
return false;
}
std::vector<ID3D11Resource*> acquiredResources = {};
m_windowInterop.BuildAcquiredResources(backBufferIndex, acquiredResources);
if (acquiredResources.empty()) {
m_lastRenderError = "No wrapped interop resources were prepared for UI composition.";
return false;
}
d3d11On12Device->AcquireWrappedResources(
acquiredResources.data(),
static_cast<UINT>(acquiredResources.size()));
d2dDeviceContext->SetTarget(m_windowInterop.GetBackBufferTargetBitmap(backBufferIndex));
const bool rendered = RenderToTarget(*d2dDeviceContext, *interopBrush, drawData);
const HRESULT hr = d2dDeviceContext->EndDraw();
d3d11On12Device->ReleaseWrappedResources(
acquiredResources.data(),
static_cast<UINT>(acquiredResources.size()));
d3d11DeviceContext->Flush();
d2dDeviceContext->SetTarget(nullptr);
m_windowInterop.ClearSourceTextures();
if (!rendered || FAILED(hr)) {
m_lastRenderError = FAILED(hr)
? HrToString("ID2D1DeviceContext::EndDraw", hr)
: "RenderToTarget failed during D3D11On12 composition.";
const bool signaled = m_windowRenderer->SignalFrameCompletion();
if (hr == D2DERR_RECREATE_TARGET) {
ReleaseWindowRendererBackBufferTargets();
} else {
ReleaseWindowRendererInterop();
}
if (!signaled) {
m_lastRenderError =
"Failed to signal D3D12 frame completion after UI composition failed.";
}
return false;
}
if (!m_windowRenderer->SignalFrameCompletion()) {
m_lastRenderError = "Failed to signal D3D12 frame completion after UI composition.";
ReleaseWindowRendererInterop();
return false;
}
if (!m_windowRenderer->PresentFrame()) {
m_lastRenderError = "Failed to present the D3D12 swap chain.";
ReleaseWindowRendererInterop();
return false;
}
m_lastRenderError.clear();
return true;
}
@@ -688,52 +591,6 @@ namespace XCEngine::UI::Editor::Host {
using namespace NativeRendererHelpers;
float NativeRenderer::MeasureTextWidth(
const ::XCEngine::UI::Editor::UIEditorTextMeasureRequest& request) const {
if (!m_dwriteFactory || request.text.empty()) {
return 0.0f;
}
const std::wstring text = Utf8ToWide(request.text);
if (text.empty()) {
return 0.0f;
}
const float dpiScale = ClampDpiScale(m_dpiScale);
const float scaledFontSize = ResolveFontSize(request.fontSize) * dpiScale;
IDWriteTextFormat* textFormat = GetTextFormat(scaledFontSize);
if (textFormat == nullptr) {
return 0.0f;
}
Microsoft::WRL::ComPtr<IDWriteTextLayout> textLayout;
HRESULT hr = m_dwriteFactory->CreateTextLayout(
text.c_str(),
static_cast<UINT32>(text.size()),
textFormat,
4096.0f,
scaledFontSize * 2.0f,
textLayout.ReleaseAndGetAddressOf());
if (FAILED(hr) || !textLayout) {
return 0.0f;
}
DWRITE_TEXT_METRICS textMetrics = {};
hr = textLayout->GetMetrics(&textMetrics);
if (FAILED(hr)) {
return 0.0f;
}
DWRITE_OVERHANG_METRICS overhangMetrics = {};
float width = textMetrics.widthIncludingTrailingWhitespace;
if (SUCCEEDED(textLayout->GetOverhangMetrics(&overhangMetrics))) {
width += (std::max)(overhangMetrics.left, 0.0f);
width += (std::max)(overhangMetrics.right, 0.0f);
}
return std::ceil(width) / dpiScale;
}
IDWriteTextFormat* NativeRenderer::GetTextFormat(float fontSize) const {
if (!m_dwriteFactory) {
return nullptr;
@@ -1121,78 +978,8 @@ namespace XCEngine::UI::Editor::Host {
using namespace NativeRendererHelpers;
bool NativeRenderer::AttachWindowRenderer(D3D12WindowRenderer& windowRenderer) {
if (m_windowRenderer != &windowRenderer) {
ReleaseWindowRendererInterop();
m_windowRenderer = &windowRenderer;
}
if (!EnsureWindowRendererInterop()) {
return false;
}
DiscardRenderTarget();
if (m_windowInterop.HasBackBufferTargets()) {
return true;
}
return m_windowInterop.RebuildBackBufferTargets();
}
void NativeRenderer::DetachWindowRenderer() {
ReleaseWindowRendererInterop();
m_windowRenderer = nullptr;
}
void NativeRenderer::ReleaseWindowRendererBackBufferTargets() {
m_windowInterop.ReleaseBackBufferTargets();
}
bool NativeRenderer::RebuildWindowRendererBackBufferTargets() {
if (!EnsureWindowRendererInterop()) {
return false;
}
DiscardRenderTarget();
ReleaseWindowRendererBackBufferTargets();
return m_windowInterop.RebuildBackBufferTargets();
}
bool NativeRenderer::HasAttachedWindowRenderer() const {
return m_windowInterop.HasAttachedWindowRenderer();
}
bool NativeRenderer::EnsureWindowRendererInterop() {
if (m_windowRenderer == nullptr) {
m_lastRenderError = "EnsureWindowRendererInterop requires an attached D3D12 window renderer.";
return false;
}
if (m_d2dFactory == nullptr || m_dwriteFactory == nullptr) {
m_lastRenderError = "EnsureWindowRendererInterop requires initialized D2D and DWrite factories.";
return false;
}
const bool attached = m_windowInterop.Attach(*m_windowRenderer, *m_d2dFactory.Get());
if (!attached) {
m_lastRenderError = m_windowInterop.GetLastError();
} else {
m_lastRenderError.clear();
}
return attached;
}
void NativeRenderer::ReleaseWindowRendererInterop() {
m_windowInterop.Detach();
}
} // namespace XCEngine::UI::Editor::Host
namespace XCEngine::UI::Editor::Host {
using namespace NativeRendererHelpers;
bool NativeRenderer::CaptureToPng(
D3D12WindowRenderer* windowRenderer,
const ::XCEngine::UI::UIDrawData& drawData,
UINT width,
UINT height,
@@ -1204,8 +991,7 @@ bool NativeRenderer::CaptureToPng(
return false;
}
if (!m_d2dFactory || !m_dwriteFactory) {
outError = "CaptureToPng requires an initialized NativeRenderer.";
if (!EnsureCoreFactories(outError)) {
return false;
}
@@ -1213,10 +999,34 @@ bool NativeRenderer::CaptureToPng(
return false;
}
std::vector<::XCEngine::UI::UITextureHandle> interopTextures = {};
CollectInteropTextureHandles(drawData, interopTextures);
const bool requiresInterop = !interopTextures.empty();
if (requiresInterop) {
if (windowRenderer == nullptr) {
outError =
"CaptureToPng requires a D3D12 window renderer to resolve GPU UI textures.";
return false;
}
if (!m_windowInterop.Attach(*windowRenderer, *m_d2dFactory.Get())) {
outError = m_windowInterop.GetLastError();
return false;
}
if (!m_windowInterop.PrepareSourceTextures(drawData)) {
outError = m_windowInterop.GetLastError();
m_windowInterop.Detach();
return false;
}
}
std::error_code errorCode = {};
std::filesystem::create_directories(outputPath.parent_path(), errorCode);
if (errorCode) {
outError = "Failed to create screenshot directory: " + outputPath.parent_path().string();
if (requiresInterop) {
m_windowInterop.ClearSourceTextures();
m_windowInterop.Detach();
}
return false;
}
@@ -1245,6 +1055,10 @@ bool NativeRenderer::CaptureToPng(
offscreenRenderTarget.ReleaseAndGetAddressOf());
if (FAILED(hr)) {
outError = HrToString("ID2D1Factory::CreateWicBitmapRenderTarget", hr);
if (requiresInterop) {
m_windowInterop.ClearSourceTextures();
m_windowInterop.Detach();
}
return false;
}
@@ -1254,11 +1068,19 @@ bool NativeRenderer::CaptureToPng(
offscreenBrush.ReleaseAndGetAddressOf());
if (FAILED(hr)) {
outError = HrToString("ID2D1RenderTarget::CreateSolidColorBrush", hr);
if (requiresInterop) {
m_windowInterop.ClearSourceTextures();
m_windowInterop.Detach();
}
return false;
}
const bool rendered = RenderToTarget(*offscreenRenderTarget.Get(), *offscreenBrush.Get(), drawData);
hr = offscreenRenderTarget->EndDraw();
if (requiresInterop) {
m_windowInterop.ClearSourceTextures();
m_windowInterop.Detach();
}
if (!rendered || FAILED(hr)) {
outError = HrToString("ID2D1RenderTarget::EndDraw", hr);
return false;

View File

@@ -7,9 +7,6 @@
#include "Ports/TexturePort.h"
#include <Rendering/D3D12/D3D12WindowInteropContext.h>
#include <Rendering/D3D12/D3D12WindowRenderer.h>
#include <XCEditor/Foundation/UIEditorTextMeasurement.h>
#include <XCEngine/UI/DrawData.h>
@@ -31,22 +28,16 @@
namespace XCEngine::UI::Editor::Host {
class NativeRenderer
: public Ports::TexturePort
, public ::XCEngine::UI::Editor::UIEditorTextMeasurer {
class D3D12WindowRenderer;
class NativeRenderer : public Ports::TexturePort {
public:
bool Initialize(HWND hwnd);
void Shutdown();
void SetDpiScale(float dpiScale);
float GetDpiScale() const;
void Resize(UINT width, UINT height);
bool AttachWindowRenderer(D3D12WindowRenderer& windowRenderer);
void DetachWindowRenderer();
void ReleaseWindowRendererBackBufferTargets();
bool RebuildWindowRendererBackBufferTargets();
bool HasAttachedWindowRenderer() const;
bool Render(const ::XCEngine::UI::UIDrawData& drawData);
bool RenderToWindowRenderer(const ::XCEngine::UI::UIDrawData& drawData);
const std::string& GetLastRenderError() const;
bool LoadTextureFromFile(
const std::filesystem::path& path,
@@ -64,9 +55,8 @@ public:
::XCEngine::UI::UITextureHandle& outTexture,
std::string& outError) override;
void ReleaseTexture(::XCEngine::UI::UITextureHandle& texture) override;
float MeasureTextWidth(
const ::XCEngine::UI::Editor::UIEditorTextMeasureRequest& request) const override;
bool CaptureToPng(
D3D12WindowRenderer* windowRenderer,
const ::XCEngine::UI::UIDrawData& drawData,
UINT width,
UINT height,
@@ -74,6 +64,7 @@ public:
std::string& outError);
private:
bool EnsureCoreFactories(std::string& outError);
struct NativeTextureResource {
std::vector<std::uint8_t> pixels = {};
Microsoft::WRL::ComPtr<ID2D1Bitmap> cachedBitmap = {};
@@ -83,11 +74,9 @@ private:
};
bool EnsureRenderTarget();
bool EnsureWindowRendererInterop();
bool EnsureWicFactory(std::string& outError);
void DiscardRenderTarget();
bool CreateDeviceResources();
void ReleaseWindowRendererInterop();
void InvalidateCachedTextureBitmaps(const ID2D1RenderTarget* renderTarget);
bool RenderToTarget(
ID2D1RenderTarget& renderTarget,
@@ -159,7 +148,6 @@ private:
static std::wstring Utf8ToWide(std::string_view text);
HWND m_hwnd = nullptr;
D3D12WindowRenderer* m_windowRenderer = nullptr;
Microsoft::WRL::ComPtr<ID2D1Factory1> m_d2dFactory;
Microsoft::WRL::ComPtr<IDWriteFactory> m_dwriteFactory;
Microsoft::WRL::ComPtr<IWICImagingFactory> m_wicFactory;

View File

@@ -1,5 +1,6 @@
#include "Scene/EditorSceneBridge.h"
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
#include <XCEngine/Components/ComponentFactoryRegistry.h>
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/TransformComponent.h>
@@ -28,6 +29,10 @@ using ::XCEngine::Components::SceneManager;
using ::XCEngine::Components::TransformComponent;
using ::XCEngine::Resources::ResourceManager;
void TraceSceneStartup(std::string message) {
::XCEngine::UI::Editor::AppendUIEditorRuntimeTrace("startup", std::move(message));
}
struct ClipboardNode {
std::string name = {};
std::string transformPayload = {};
@@ -145,16 +150,19 @@ bool WouldCreateCycle(
EditorStartupSceneResult EnsureEditorStartupScene(
const std::filesystem::path& projectRoot) {
EditorStartupSceneResult result = {};
TraceSceneStartup("EnsureEditorStartupScene begin projectRoot=" + projectRoot.string());
if (projectRoot.empty()) {
return result;
}
ResourceManager::Get().SetResourceRoot(projectRoot.string().c_str());
TraceSceneStartup("ResourceManager::SetResourceRoot complete");
if (Scene* activeScene = ResolvePrimaryScene();
activeScene != nullptr) {
result.ready = true;
result.sceneName = activeScene->GetName();
TraceSceneStartup("EnsureEditorStartupScene reused active scene=" + result.sceneName);
return result;
}
@@ -164,7 +172,13 @@ EditorStartupSceneResult EnsureEditorStartupScene(
if (std::filesystem::exists(startupScenePath) &&
std::filesystem::is_regular_file(startupScenePath)) {
sceneManager.LoadScene(startupScenePath.string());
TraceSceneStartup("SceneManager::LoadScene begin path=" + startupScenePath.string());
{
ResourceManager::ScopedDeferredSceneLoad deferredSceneLoad(
ResourceManager::Get());
sceneManager.LoadScene(startupScenePath.string());
}
TraceSceneStartup("SceneManager::LoadScene end");
Scene* loadedScene = sceneManager.GetScene(startupScenePath.stem().string());
if (loadedScene == nullptr) {
loadedScene = ResolvePrimaryScene();
@@ -177,6 +191,7 @@ EditorStartupSceneResult EnsureEditorStartupScene(
result.loadedFromDisk = true;
result.scenePath = startupScenePath;
result.sceneName = loadedScene->GetName();
TraceSceneStartup("EnsureEditorStartupScene loaded scene=" + result.sceneName);
return result;
}
}
@@ -186,8 +201,12 @@ EditorStartupSceneResult EnsureEditorStartupScene(
sceneManager.SetActiveScene(scene);
result.ready = true;
result.sceneName = scene->GetName();
TraceSceneStartup("EnsureEditorStartupScene created scene=" + result.sceneName);
}
TraceSceneStartup(
std::string("EnsureEditorStartupScene end ready=") +
(result.ready ? "1" : "0"));
return result;
}
@@ -208,7 +227,11 @@ bool OpenEditorSceneAsset(const std::filesystem::path& scenePath) {
}
SceneManager& sceneManager = SceneManager::Get();
sceneManager.LoadScene(scenePath.string());
{
ResourceManager::ScopedDeferredSceneLoad deferredSceneLoad(
ResourceManager::Get());
sceneManager.LoadScene(scenePath.string());
}
Scene* loadedScene = sceneManager.GetScene(scenePath.stem().string());
if (loadedScene == nullptr) {
loadedScene = ResolvePrimaryScene();

View File

@@ -70,6 +70,10 @@ UIEditorPanelContentHostRequest ResolveUIEditorPanelContentHostRequest(
const Widgets::UIEditorDockHostLayout& dockHostLayout,
const UIEditorPanelRegistry& panelRegistry);
UIEditorPanelContentHostFrame BuildUIEditorPanelContentHostFrame(
const UIEditorPanelContentHostRequest& request,
const UIEditorPanelRegistry& panelRegistry);
UIEditorPanelContentHostFrame UpdateUIEditorPanelContentHost(
UIEditorPanelContentHostState& state,
const UIEditorPanelContentHostRequest& request,

View File

@@ -100,12 +100,10 @@ struct UIEditorShellComposeLayout {
struct UIEditorShellComposeRequest {
UIEditorShellComposeLayout layout = {};
UIEditorWorkspaceComposeRequest workspaceRequest = {};
};
struct UIEditorShellComposeFrame {
UIEditorShellComposeLayout layout = {};
UIEditorWorkspaceComposeFrame workspaceFrame = {};
};
UIEditorShellComposeLayout BuildUIEditorShellComposeLayout(
@@ -123,12 +121,7 @@ UIEditorShellComposeLayout BuildUIEditorShellComposeLayout(
UIEditorShellComposeRequest ResolveUIEditorShellComposeRequest(
const ::XCEngine::UI::UIRect& bounds,
const UIEditorPanelRegistry& panelRegistry,
const UIEditorWorkspaceModel& workspace,
const UIEditorWorkspaceSession& session,
const UIEditorShellComposeModel& model,
const Widgets::UIEditorDockHostState& dockHostState = {},
const UIEditorShellComposeState& state = {},
const UIEditorShellComposeMetrics& metrics = {});
UIEditorShellComposeFrame UpdateUIEditorShellCompose(
@@ -145,15 +138,33 @@ UIEditorShellComposeFrame UpdateUIEditorShellCompose(
void AppendUIEditorShellCompose(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorShellComposeFrame& frame,
const UIEditorWorkspaceComposeFrame& workspaceFrame,
const UIEditorShellComposeModel& model,
const UIEditorShellComposeState& state,
const UIEditorShellComposePalette& palette = {},
const UIEditorShellComposeMetrics& metrics = {},
const App::BuiltInIcons* builtInIcons = nullptr);
void AppendUIEditorShellComposeOverlay(
void AppendUIEditorShellComposeBase(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorShellComposeFrame& frame,
const UIEditorShellComposeModel& model,
const UIEditorShellComposeState& state,
const UIEditorShellComposePalette& palette = {},
const UIEditorShellComposeMetrics& metrics = {},
const App::BuiltInIcons* builtInIcons = nullptr);
void AppendUIEditorShellComposeStatusBar(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorShellComposeFrame& frame,
const UIEditorShellComposeModel& model,
const UIEditorShellComposeState& state,
const UIEditorShellComposePalette& palette = {},
const UIEditorShellComposeMetrics& metrics = {});
void AppendUIEditorShellComposeOverlay(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorWorkspaceComposeFrame& workspaceFrame,
const UIEditorShellComposePalette& palette = {},
const UIEditorShellComposeMetrics& metrics = {});

View File

@@ -143,7 +143,6 @@ UIEditorShellInteractionModel ResolveUIEditorShellInteractionModel(
UIEditorShellInteractionRequest ResolveUIEditorShellInteractionRequest(
const ::XCEngine::UI::UIRect& bounds,
const UIEditorWorkspaceController& controller,
const UIEditorShellInteractionModel& model,
const UIEditorShellInteractionState& state = {},
const UIEditorShellInteractionMetrics& metrics = {},

View File

@@ -47,6 +47,13 @@ UIEditorViewportShellRequest ResolveUIEditorViewportShellRequest(
const UIEditorViewportShellSpec& spec,
const Widgets::UIEditorViewportSlotMetrics& metrics = {});
UIEditorViewportShellFrame UpdateUIEditorViewportShell(
UIEditorViewportShellState& state,
const UIEditorViewportShellRequest& request,
const UIEditorViewportShellModel& model,
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
const UIEditorViewportInputBridgeRequest& inputRequest = {});
UIEditorViewportShellFrame UpdateUIEditorViewportShell(
UIEditorViewportShellState& state,
const ::XCEngine::UI::UIRect& bounds,

View File

@@ -153,6 +153,10 @@ struct UIEditorViewportSlotHitTarget {
std::size_t index = UIEditorViewportSlotInvalidIndex;
};
struct UIEditorViewportSlotForegroundAppendOptions {
bool includeSurfaceTexture = true;
};
float ResolveUIEditorViewportSlotDesiredToolWidth(
const UIEditorViewportSlotToolItem& item,
const UIEditorViewportSlotMetrics& metrics = {});
@@ -187,7 +191,14 @@ void AppendUIEditorViewportSlotForeground(
const std::vector<UIEditorStatusBarSegment>& statusSegments,
const UIEditorViewportSlotState& state,
const UIEditorViewportSlotPalette& palette = {},
const UIEditorViewportSlotMetrics& metrics = {});
const UIEditorViewportSlotMetrics& metrics = {},
const UIEditorViewportSlotForegroundAppendOptions& options = {});
void AppendUIEditorViewportSlotSurfaceTexture(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorViewportSlotLayout& layout,
const UIEditorViewportSlotFrame& frame,
const UIEditorViewportSlotPalette& palette = {});
void AppendUIEditorViewportSlot(
::XCEngine::UI::UIDrawList& drawList,

View File

@@ -58,6 +58,7 @@ struct UIEditorWorkspaceComposeFrame {
struct UIEditorWorkspaceComposeAppendOptions {
bool deferDockPreviewOverlay = false;
bool includeViewportTextures = true;
};
const UIEditorWorkspacePanelPresentationModel* FindUIEditorWorkspacePanelPresentationModel(
@@ -76,6 +77,12 @@ const UIEditorWorkspaceViewportComposeFrame* FindUIEditorWorkspaceViewportPresen
const UIEditorWorkspaceComposeFrame& frame,
std::string_view panelId);
UIEditorWorkspaceComposeRequest ResolveUIEditorWorkspaceComposeRequest(
const Widgets::UIEditorDockHostLayout& dockHostLayout,
const UIEditorPanelRegistry& panelRegistry,
const std::vector<UIEditorWorkspacePanelPresentationModel>& presentations,
const Widgets::UIEditorViewportSlotMetrics& viewportMetrics = {});
UIEditorWorkspaceComposeRequest ResolveUIEditorWorkspaceComposeRequest(
const ::XCEngine::UI::UIRect& bounds,
const UIEditorPanelRegistry& panelRegistry,
@@ -86,6 +93,14 @@ UIEditorWorkspaceComposeRequest ResolveUIEditorWorkspaceComposeRequest(
const Widgets::UIEditorDockHostMetrics& dockHostMetrics = {},
const Widgets::UIEditorViewportSlotMetrics& viewportMetrics = {});
UIEditorWorkspaceComposeFrame UpdateUIEditorWorkspaceCompose(
UIEditorWorkspaceComposeState& state,
const UIEditorWorkspaceComposeRequest& request,
const UIEditorPanelRegistry& panelRegistry,
const std::vector<UIEditorWorkspacePanelPresentationModel>& presentations,
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
const UIEditorWorkspaceInputOwner* inputOwner = nullptr);
UIEditorWorkspaceComposeFrame UpdateUIEditorWorkspaceCompose(
UIEditorWorkspaceComposeState& state,
const ::XCEngine::UI::UIRect& bounds,
@@ -117,4 +132,9 @@ void AppendUIEditorWorkspaceComposeOverlay(
const Widgets::UIEditorDockHostPalette& dockHostPalette = {},
const Widgets::UIEditorDockHostMetrics& dockHostMetrics = {});
void AppendUIEditorWorkspaceComposeViewportTextures(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorWorkspaceComposeFrame& frame,
const Widgets::UIEditorViewportSlotPalette& viewportPalette = {});
} // namespace XCEngine::UI::Editor

View File

@@ -83,7 +83,7 @@ UIEditorDockHostTabStripInteractionEntry& FindOrCreateTabStripInteractionEntry(
return entry;
}
void PruneTabStripInteractionEntries(
bool PruneTabStripInteractionEntries(
UIEditorDockHostInteractionState& state,
const Widgets::UIEditorDockHostLayout& layout) {
const auto isVisibleNodeId = [&layout](std::string_view nodeId) {
@@ -95,6 +95,8 @@ void PruneTabStripInteractionEntries(
}) != layout.tabStacks.end();
};
bool changed = false;
const std::size_t previousInteractionCount = state.tabStripInteractions.size();
state.tabStripInteractions.erase(
std::remove_if(
state.tabStripInteractions.begin(),
@@ -103,7 +105,11 @@ void PruneTabStripInteractionEntries(
return !isVisibleNodeId(entry.nodeId);
}),
state.tabStripInteractions.end());
changed = changed ||
previousInteractionCount != state.tabStripInteractions.size();
const std::size_t previousVisualStateCount =
state.dockHostState.tabStripStates.size();
state.dockHostState.tabStripStates.erase(
std::remove_if(
state.dockHostState.tabStripStates.begin(),
@@ -112,13 +118,18 @@ void PruneTabStripInteractionEntries(
return !isVisibleNodeId(entry.nodeId);
}),
state.dockHostState.tabStripStates.end());
changed = changed ||
previousVisualStateCount != state.dockHostState.tabStripStates.size();
if (!state.activeTabDragNodeId.empty() &&
!isVisibleNodeId(state.activeTabDragNodeId)) {
state.activeTabDragNodeId.clear();
state.activeTabDragPanelId.clear();
state.dockHostState.dropPreview = {};
changed = true;
}
return changed;
}
void SyncDockHostTabStripVisualStates(UIEditorDockHostInteractionState& state) {

View File

@@ -35,7 +35,7 @@ UIEditorWorkspaceCommandResult DispatchPanelCommand(
UIEditorDockHostTabStripInteractionEntry& FindOrCreateTabStripInteractionEntry(
UIEditorDockHostInteractionState& state,
std::string_view nodeId);
void PruneTabStripInteractionEntries(
bool PruneTabStripInteractionEntries(
UIEditorDockHostInteractionState& state,
const Widgets::UIEditorDockHostLayout& layout);
void SyncDockHostTabStripVisualStates(UIEditorDockHostInteractionState& state);

View File

@@ -41,15 +41,16 @@ Widgets::UIEditorDockHostLayout RebuildDockHostLayout(
controller.GetSession(),
state.dockHostState,
metrics);
Internal::PruneTabStripInteractionEntries(state, layout);
Internal::SyncDockHostTabStripVisualStates(state);
layout = BuildUIEditorDockHostLayout(
bounds,
controller.GetPanelRegistry(),
controller.GetWorkspace(),
controller.GetSession(),
state.dockHostState,
metrics);
if (Internal::PruneTabStripInteractionEntries(state, layout)) {
Internal::SyncDockHostTabStripVisualStates(state);
layout = BuildUIEditorDockHostLayout(
bounds,
controller.GetPanelRegistry(),
controller.GetWorkspace(),
controller.GetSession(),
state.dockHostState,
metrics);
}
Internal::SyncHoverTarget(state, layout);
return layout;
}
@@ -126,6 +127,30 @@ bool HasMeaningfulDockHostResult(
!result.detachedPanelId.empty();
}
bool ShouldRebuildDockHostLayoutForNextEvent(
const UIEditorDockHostInteractionResult& result) {
if (result.layoutChanged) {
return true;
}
if (!result.commandExecuted) {
return false;
}
switch (result.commandResult.kind) {
case UIEditorWorkspaceCommandKind::ActivatePanel:
return false;
case UIEditorWorkspaceCommandKind::OpenPanel:
case UIEditorWorkspaceCommandKind::ClosePanel:
case UIEditorWorkspaceCommandKind::ShowPanel:
case UIEditorWorkspaceCommandKind::HidePanel:
case UIEditorWorkspaceCommandKind::ResetWorkspace:
return true;
}
return true;
}
UIEditorDockHostInteractionResult BeginEventResult(
const DockHostEventContext& context) {
UIEditorDockHostInteractionResult result = {};
@@ -538,7 +563,11 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction(
break;
}
layout = RebuildDockHostLayout(state, controller, bounds, metrics);
if (ShouldRebuildDockHostLayoutForNextEvent(eventResult)) {
layout = RebuildDockHostLayout(state, controller, bounds, metrics);
} else {
Internal::SyncHoverTarget(state, layout);
}
if (eventResult.hitTarget.kind == UIEditorDockHostHitTargetKind::None) {
eventResult.hitTarget = state.dockHostState.hoveredTarget;
}

View File

@@ -34,10 +34,10 @@ bool SupportsExternalHosting(const UIEditorPanelDescriptor& descriptor) {
return IsUIEditorPanelPresentationExternallyHosted(descriptor.presentationKind);
}
UIEditorPanelContentHostPanelState* FindMutablePanelState(
UIEditorPanelContentHostState& state,
const UIEditorPanelContentHostPanelState* FindPanelState(
const std::vector<UIEditorPanelContentHostPanelState>& panelStates,
std::string_view panelId) {
for (UIEditorPanelContentHostPanelState& panelState : state.panelStates) {
for (const UIEditorPanelContentHostPanelState& panelState : panelStates) {
if (panelState.panelId == panelId) {
return &panelState;
}
@@ -46,21 +46,32 @@ UIEditorPanelContentHostPanelState* FindMutablePanelState(
return nullptr;
}
UIEditorPanelContentHostPanelState& EnsurePanelState(
UIEditorPanelContentHostState& state,
std::string_view panelId,
UIEditorPanelPresentationKind kind) {
if (UIEditorPanelContentHostPanelState* existing =
FindMutablePanelState(state, panelId)) {
existing->kind = kind;
return *existing;
std::vector<UIEditorPanelContentHostPanelState> BuildPanelStates(
const UIEditorPanelContentHostRequest& request,
const UIEditorPanelRegistry& panelRegistry) {
std::vector<UIEditorPanelContentHostPanelState> panelStates = {};
panelStates.reserve(panelRegistry.panels.size());
for (const UIEditorPanelDescriptor& descriptor : panelRegistry.panels) {
if (!SupportsExternalHosting(descriptor)) {
continue;
}
UIEditorPanelContentHostPanelState panelState = {};
panelState.panelId = descriptor.panelId;
panelState.kind = descriptor.presentationKind;
if (const UIEditorPanelContentHostMountRequest* mountRequest =
FindUIEditorPanelContentHostMountRequest(request, descriptor.panelId);
mountRequest != nullptr) {
panelState.mounted = true;
panelState.kind = mountRequest->kind;
panelState.bounds = mountRequest->bounds;
}
panelStates.push_back(std::move(panelState));
}
UIEditorPanelContentHostPanelState panelState = {};
panelState.panelId = std::string(panelId);
panelState.kind = kind;
state.panelStates.push_back(std::move(panelState));
return state.panelStates.back();
return panelStates;
}
} // namespace
@@ -145,42 +156,26 @@ UIEditorPanelContentHostRequest ResolveUIEditorPanelContentHostRequest(
return request;
}
UIEditorPanelContentHostFrame BuildUIEditorPanelContentHostFrame(
const UIEditorPanelContentHostRequest& request,
const UIEditorPanelRegistry& panelRegistry) {
UIEditorPanelContentHostFrame frame = {};
frame.panelStates = BuildPanelStates(request, panelRegistry);
return frame;
}
UIEditorPanelContentHostFrame UpdateUIEditorPanelContentHost(
UIEditorPanelContentHostState& state,
const UIEditorPanelContentHostRequest& request,
const UIEditorPanelRegistry& panelRegistry) {
UIEditorPanelContentHostFrame frame = {};
std::unordered_set<std::string> supportedPanelIds = {};
for (const UIEditorPanelDescriptor& descriptor : panelRegistry.panels) {
if (!SupportsExternalHosting(descriptor)) {
continue;
}
supportedPanelIds.insert(descriptor.panelId);
EnsurePanelState(state, descriptor.panelId, descriptor.presentationKind);
}
state.panelStates.erase(
std::remove_if(
state.panelStates.begin(),
state.panelStates.end(),
[&](const UIEditorPanelContentHostPanelState& panelState) {
return !supportedPanelIds.contains(panelState.panelId);
}),
state.panelStates.end());
for (UIEditorPanelContentHostPanelState& panelState : state.panelStates) {
const UIEditorPanelContentHostMountRequest* mountRequest =
FindUIEditorPanelContentHostMountRequest(request, panelState.panelId);
const bool wasMounted = panelState.mounted;
const ::XCEngine::UI::UIRect previousBounds = panelState.bounds;
panelState.mounted = mountRequest != nullptr;
panelState.bounds = mountRequest != nullptr ? mountRequest->bounds : ::XCEngine::UI::UIRect{};
if (mountRequest != nullptr) {
panelState.kind = mountRequest->kind;
}
frame.panelStates = BuildPanelStates(request, panelRegistry);
for (const UIEditorPanelContentHostPanelState& panelState : frame.panelStates) {
const UIEditorPanelContentHostPanelState* previousState =
FindPanelState(state.panelStates, panelState.panelId);
const bool wasMounted = previousState != nullptr && previousState->mounted;
const ::XCEngine::UI::UIRect previousBounds =
previousState != nullptr ? previousState->bounds : ::XCEngine::UI::UIRect{};
if (!wasMounted && panelState.mounted) {
frame.events.push_back({
@@ -207,7 +202,7 @@ UIEditorPanelContentHostFrame UpdateUIEditorPanelContentHost(
}
}
frame.panelStates = state.panelStates;
state.panelStates = frame.panelStates;
return frame;
}

View File

@@ -48,7 +48,6 @@ bool ShouldRefreshResolvedShellModel(
const UIEditorShellInteractionResult& result);
BuildRequestOutput BuildRequest(
const ::XCEngine::UI::UIRect& bounds,
const UIEditorWorkspaceController& controller,
const UIEditorShellInteractionModel& model,
const UIEditorShellInteractionState& state,
const UIEditorShellInteractionMetrics& metrics,

View File

@@ -119,7 +119,6 @@ void AppendUIEditorShellToolbar(
return;
}
const std::size_t buttonCount = (std::min)(buttons.size(), layout.buttonRects.size());
for (std::size_t index = 0; index < buttonCount; ++index) {
const UIRect& buttonRect = layout.buttonRects[index];
@@ -242,12 +241,7 @@ UIEditorShellComposeLayout BuildUIEditorShellComposeLayout(
UIEditorShellComposeRequest ResolveUIEditorShellComposeRequest(
const UIRect& bounds,
const UIEditorPanelRegistry& panelRegistry,
const UIEditorWorkspaceModel& workspace,
const UIEditorWorkspaceSession& session,
const UIEditorShellComposeModel& model,
const Widgets::UIEditorDockHostState& dockHostState,
const UIEditorShellComposeState& state,
const UIEditorShellComposeMetrics& metrics) {
UIEditorShellComposeRequest request = {};
request.layout = BuildUIEditorShellComposeLayout(
@@ -256,28 +250,6 @@ UIEditorShellComposeRequest ResolveUIEditorShellComposeRequest(
model.toolbarButtons,
model.statusSegments,
metrics);
request.workspaceRequest = ResolveUIEditorWorkspaceComposeRequest(
request.layout.workspaceRect,
panelRegistry,
workspace,
session,
model.workspacePresentations,
dockHostState,
metrics.dockHostMetrics,
metrics.viewportMetrics);
request.layout.menuBarLayout = BuildUIEditorMenuBarLayout(
request.layout.menuBarRect,
model.menuBarItems,
metrics.menuBarMetrics);
request.layout.toolbarLayout = BuildUIEditorShellToolbarLayout(
request.layout.toolbarRect,
model.toolbarButtons,
metrics.toolbarMetrics);
request.layout.statusBarLayout = BuildUIEditorStatusBarLayout(
request.layout.statusBarRect,
model.statusSegments,
metrics.statusBarMetrics);
(void)state;
return request;
}
@@ -298,7 +270,7 @@ UIEditorShellComposeFrame UpdateUIEditorShellCompose(
model.toolbarButtons,
model.statusSegments,
metrics);
frame.workspaceFrame = UpdateUIEditorWorkspaceCompose(
(void)UpdateUIEditorWorkspaceCompose(
state.workspaceState,
frame.layout.workspaceRect,
panelRegistry,
@@ -309,22 +281,46 @@ UIEditorShellComposeFrame UpdateUIEditorShellCompose(
dockHostState,
metrics.dockHostMetrics,
metrics.viewportMetrics);
frame.layout.menuBarLayout = BuildUIEditorMenuBarLayout(
frame.layout.menuBarRect,
model.menuBarItems,
metrics.menuBarMetrics);
frame.layout.toolbarLayout = BuildUIEditorShellToolbarLayout(
frame.layout.toolbarRect,
model.toolbarButtons,
metrics.toolbarMetrics);
frame.layout.statusBarLayout = BuildUIEditorStatusBarLayout(
frame.layout.statusBarRect,
model.statusSegments,
metrics.statusBarMetrics);
return frame;
}
void AppendUIEditorShellCompose(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorShellComposeFrame& frame,
const UIEditorWorkspaceComposeFrame& workspaceFrame,
const UIEditorShellComposeModel& model,
const UIEditorShellComposeState& state,
const UIEditorShellComposePalette& palette,
const UIEditorShellComposeMetrics& metrics,
const App::BuiltInIcons* builtInIcons) {
AppendUIEditorShellComposeBase(
drawList,
frame,
model,
state,
palette,
metrics,
builtInIcons);
AppendUIEditorWorkspaceCompose(
drawList,
workspaceFrame,
palette.dockHostPalette,
metrics.dockHostMetrics,
palette.viewportPalette,
metrics.viewportMetrics,
UIEditorWorkspaceComposeAppendOptions{ true });
AppendUIEditorShellComposeStatusBar(
drawList,
frame,
model,
state,
palette,
metrics);
}
void AppendUIEditorShellComposeBase(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorShellComposeFrame& frame,
const UIEditorShellComposeModel& model,
@@ -364,16 +360,15 @@ void AppendUIEditorShellCompose(
palette.toolbarPalette,
metrics.toolbarMetrics,
builtInIcons);
}
AppendUIEditorWorkspaceCompose(
drawList,
frame.workspaceFrame,
palette.dockHostPalette,
metrics.dockHostMetrics,
palette.viewportPalette,
metrics.viewportMetrics,
UIEditorWorkspaceComposeAppendOptions{ true });
void AppendUIEditorShellComposeStatusBar(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorShellComposeFrame& frame,
const UIEditorShellComposeModel& model,
const UIEditorShellComposeState& state,
const UIEditorShellComposePalette& palette,
const UIEditorShellComposeMetrics& metrics) {
AppendUIEditorStatusBarBackground(
drawList,
frame.layout.statusBarLayout,
@@ -392,12 +387,12 @@ void AppendUIEditorShellCompose(
void AppendUIEditorShellComposeOverlay(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorShellComposeFrame& frame,
const UIEditorWorkspaceComposeFrame& workspaceFrame,
const UIEditorShellComposePalette& palette,
const UIEditorShellComposeMetrics& metrics) {
AppendUIEditorWorkspaceComposeOverlay(
drawList,
frame.workspaceFrame,
workspaceFrame,
palette.dockHostPalette,
metrics.dockHostMetrics);
}

View File

@@ -248,7 +248,6 @@ bool ShouldRefreshResolvedShellModel(
BuildRequestOutput BuildRequest(
const UIRect& bounds,
const UIEditorWorkspaceController& controller,
const UIEditorShellInteractionModel& model,
const UIEditorShellInteractionState& state,
const UIEditorShellInteractionMetrics& metrics,
@@ -264,12 +263,7 @@ BuildRequestOutput BuildRequest(
BuildShellComposeModel(model, request.menuBarItems);
request.shellRequest = ResolveUIEditorShellComposeRequest(
bounds,
controller.GetPanelRegistry(),
controller.GetWorkspace(),
controller.GetSession(),
shellModel,
state.workspaceInteractionState.dockHostInteractionState.dockHostState,
state.composeState,
metrics.shellMetrics);
request.menuButtons.reserve(request.menuBarItems.size());
@@ -515,14 +509,12 @@ UIEditorShellInteractionModel ResolveUIEditorShellInteractionModel(
UIEditorShellInteractionRequest ResolveUIEditorShellInteractionRequest(
const UIRect& bounds,
const UIEditorWorkspaceController& controller,
const UIEditorShellInteractionModel& model,
const UIEditorShellInteractionState& state,
const UIEditorShellInteractionMetrics& metrics,
const UIEditorShellInteractionServices& services) {
return Internal::BuildRequest(
bounds,
controller,
model,
state,
metrics,
@@ -540,7 +532,6 @@ UIEditorShellInteractionRequest ResolveUIEditorShellInteractionRequest(
ResolveUIEditorShellInteractionModel(controller, definition, services);
return ResolveUIEditorShellInteractionRequest(
bounds,
controller,
model,
state,
metrics,
@@ -560,7 +551,6 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction(
Internal::BuildRequestOutput requestBuild = Internal::BuildRequest(
bounds,
controller,
model,
state,
metrics,
@@ -575,7 +565,6 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction(
requestBuild = Internal::BuildRequest(
bounds,
controller,
model,
state,
metrics,
@@ -727,11 +716,12 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction(
interactionResult = std::move(eventResult);
}
if (interactionResult.menuMutation.changed || state.menuSession.HasOpenMenu()) {
if (state.menuSession.HasOpenMenu()) {
menuModalDuringFrame = true;
}
if (eventResult.menuMutation.changed) {
request = Internal::BuildRequest(
bounds,
controller,
model,
state,
metrics,
@@ -753,14 +743,6 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction(
metrics.shellMetrics.dockHostMetrics);
state.composeState.workspaceState = state.workspaceInteractionState.composeState;
request = Internal::BuildRequest(
bounds,
controller,
model,
state,
metrics,
services).request;
const Internal::RequestHit finalHit = Internal::HitTestRequest(
request,
state.pointerPosition,
@@ -770,7 +752,6 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction(
UIEditorShellInteractionFrame frame = {};
frame.request = request;
frame.shellFrame.layout = request.shellRequest.layout;
frame.shellFrame.workspaceFrame = workspaceInteractionFrame.composeFrame;
frame.workspaceInteractionFrame = std::move(workspaceInteractionFrame);
frame.popupFrames = Internal::BuildPopupFrames(
frame.request,
@@ -855,6 +836,7 @@ void AppendUIEditorShellInteraction(
AppendUIEditorShellCompose(
drawList,
frame.shellFrame,
frame.workspaceInteractionFrame.composeFrame,
shellModel,
state.composeState,
palette.shellPalette,

View File

@@ -65,14 +65,13 @@ UIEditorViewportShellRequest ResolveUIEditorViewportShellRequest(
UIEditorViewportShellFrame UpdateUIEditorViewportShell(
UIEditorViewportShellState& state,
const ::XCEngine::UI::UIRect& bounds,
const UIEditorViewportShellRequest& request,
const UIEditorViewportShellModel& model,
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
const Widgets::UIEditorViewportSlotMetrics& metrics,
const UIEditorViewportInputBridgeRequest& inputRequest) {
UIEditorViewportShellFrame frame = {};
frame.slotLayout = BuildViewportShellLayout(bounds, model.spec, model.frame, metrics);
frame.requestedViewportSize = frame.slotLayout.requestedSurfaceSize;
frame.slotLayout = request.slotLayout;
frame.requestedViewportSize = request.requestedViewportSize;
frame.inputFrame = UpdateUIEditorViewportInputBridge(
state.inputBridgeState,
frame.slotLayout.bounds,
@@ -88,4 +87,21 @@ UIEditorViewportShellFrame UpdateUIEditorViewportShell(
return frame;
}
UIEditorViewportShellFrame UpdateUIEditorViewportShell(
UIEditorViewportShellState& state,
const ::XCEngine::UI::UIRect& bounds,
const UIEditorViewportShellModel& model,
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
const Widgets::UIEditorViewportSlotMetrics& metrics,
const UIEditorViewportInputBridgeRequest& inputRequest) {
const UIEditorViewportShellRequest request =
ResolveUIEditorViewportShellRequest(bounds, model.spec, metrics);
return UpdateUIEditorViewportShell(
state,
request,
model,
inputEvents,
inputRequest);
}
} // namespace XCEngine::UI::Editor

View File

@@ -372,7 +372,8 @@ void AppendUIEditorViewportSlotForeground(
const std::vector<UIEditorStatusBarSegment>& statusSegments,
const UIEditorViewportSlotState& state,
const UIEditorViewportSlotPalette& palette,
const UIEditorViewportSlotMetrics& metrics) {
const UIEditorViewportSlotMetrics& metrics,
const UIEditorViewportSlotForegroundAppendOptions& options) {
if (layout.hasTopBar) {
if (!chrome.title.empty()) {
drawList.AddText(
@@ -405,7 +406,9 @@ void AppendUIEditorViewportSlotForeground(
}
}
if (frame.hasTexture && frame.texture.IsValid()) {
if (options.includeSurfaceTexture &&
frame.hasTexture &&
frame.texture.IsValid()) {
drawList.AddImage(layout.textureRect, frame.texture, palette.imageTint);
}
@@ -419,6 +422,18 @@ void AppendUIEditorViewportSlotForeground(
}
}
void AppendUIEditorViewportSlotSurfaceTexture(
UIDrawList& drawList,
const UIEditorViewportSlotLayout& layout,
const UIEditorViewportSlotFrame& frame,
const UIEditorViewportSlotPalette& palette) {
if (!frame.hasTexture || !frame.texture.IsValid()) {
return;
}
drawList.AddImage(layout.textureRect, frame.texture, palette.imageTint);
}
void AppendUIEditorViewportSlot(
UIDrawList& drawList,
const UIRect& bounds,
@@ -454,7 +469,8 @@ void AppendUIEditorViewportSlot(
statusSegments,
state,
palette,
metrics);
metrics,
{});
}
} // namespace XCEngine::UI::Editor::Widgets

View File

@@ -144,25 +144,16 @@ const UIEditorWorkspaceViewportComposeFrame* FindUIEditorWorkspaceViewportPresen
}
UIEditorWorkspaceComposeRequest ResolveUIEditorWorkspaceComposeRequest(
const ::XCEngine::UI::UIRect& bounds,
const Widgets::UIEditorDockHostLayout& dockHostLayout,
const UIEditorPanelRegistry& panelRegistry,
const UIEditorWorkspaceModel& workspace,
const UIEditorWorkspaceSession& session,
const std::vector<UIEditorWorkspacePanelPresentationModel>& presentations,
const Widgets::UIEditorDockHostState& dockHostState,
const Widgets::UIEditorDockHostMetrics& dockHostMetrics,
const Widgets::UIEditorViewportSlotMetrics& viewportMetrics) {
UIEditorWorkspaceComposeRequest request = {};
request.dockHostLayout = BuildUIEditorDockHostLayout(
bounds,
panelRegistry,
workspace,
session,
dockHostState,
dockHostMetrics);
request.dockHostLayout = dockHostLayout;
request.contentHostRequest = ResolveUIEditorPanelContentHostRequest(
request.dockHostLayout,
panelRegistry);
request.viewportRequests.reserve(request.contentHostRequest.mountRequests.size());
for (const UIEditorPanelContentHostMountRequest& mountRequest :
request.contentHostRequest.mountRequests) {
@@ -190,6 +181,82 @@ UIEditorWorkspaceComposeRequest ResolveUIEditorWorkspaceComposeRequest(
return request;
}
UIEditorWorkspaceComposeRequest ResolveUIEditorWorkspaceComposeRequest(
const ::XCEngine::UI::UIRect& bounds,
const UIEditorPanelRegistry& panelRegistry,
const UIEditorWorkspaceModel& workspace,
const UIEditorWorkspaceSession& session,
const std::vector<UIEditorWorkspacePanelPresentationModel>& presentations,
const Widgets::UIEditorDockHostState& dockHostState,
const Widgets::UIEditorDockHostMetrics& dockHostMetrics,
const Widgets::UIEditorViewportSlotMetrics& viewportMetrics) {
const Widgets::UIEditorDockHostLayout dockHostLayout = BuildUIEditorDockHostLayout(
bounds,
panelRegistry,
workspace,
session,
dockHostState,
dockHostMetrics);
return ResolveUIEditorWorkspaceComposeRequest(
dockHostLayout,
panelRegistry,
presentations,
viewportMetrics);
}
UIEditorWorkspaceComposeFrame UpdateUIEditorWorkspaceCompose(
UIEditorWorkspaceComposeState& state,
const UIEditorWorkspaceComposeRequest& request,
const UIEditorPanelRegistry& panelRegistry,
const std::vector<UIEditorWorkspacePanelPresentationModel>& presentations,
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
const UIEditorWorkspaceInputOwner* inputOwner) {
UIEditorWorkspaceComposeFrame frame = {};
frame.dockHostLayout = request.dockHostLayout;
frame.contentHostFrame = UpdateUIEditorPanelContentHost(
state.contentHostState,
request.contentHostRequest,
panelRegistry);
TrimObsoleteViewportPresentationStates(state, panelRegistry, presentations);
frame.viewportFrames.reserve(request.viewportRequests.size());
for (const UIEditorWorkspacePanelPresentationModel& presentation : presentations) {
if (!SupportsExternalViewportPresentation(panelRegistry, presentation)) {
continue;
}
const UIEditorWorkspaceViewportComposeRequest* viewportRequest =
FindUIEditorWorkspaceViewportPresentationRequest(request, presentation.panelId);
if (viewportRequest == nullptr) {
ResetHiddenViewportPresentationState(state, presentation.panelId);
continue;
}
UIEditorWorkspacePanelPresentationState& panelState =
EnsurePanelState(state, presentation.panelId);
UIEditorWorkspaceViewportComposeFrame viewportFrame = {};
viewportFrame.panelId = presentation.panelId;
viewportFrame.bounds = viewportRequest->bounds;
viewportFrame.viewportShellModel = presentation.viewportShellModel;
UIEditorViewportInputBridgeRequest inputRequest = {};
if (inputOwner != nullptr) {
inputRequest.focusMode = UIEditorViewportInputBridgeFocusMode::External;
inputRequest.focused =
IsUIEditorWorkspaceViewportInputOwner(*inputOwner, presentation.panelId);
}
viewportFrame.viewportShellFrame = UpdateUIEditorViewportShell(
panelState.viewportShellState,
viewportRequest->viewportShellRequest,
presentation.viewportShellModel,
inputEvents,
inputRequest);
frame.viewportFrames.push_back(std::move(viewportFrame));
}
return frame;
}
UIEditorWorkspaceComposeFrame UpdateUIEditorWorkspaceCompose(
UIEditorWorkspaceComposeState& state,
const ::XCEngine::UI::UIRect& bounds,
@@ -202,60 +269,22 @@ UIEditorWorkspaceComposeFrame UpdateUIEditorWorkspaceCompose(
const Widgets::UIEditorDockHostMetrics& dockHostMetrics,
const Widgets::UIEditorViewportSlotMetrics& viewportMetrics,
const UIEditorWorkspaceInputOwner* inputOwner) {
UIEditorWorkspaceComposeFrame frame = {};
frame.dockHostLayout = BuildUIEditorDockHostLayout(
const UIEditorWorkspaceComposeRequest request = ResolveUIEditorWorkspaceComposeRequest(
bounds,
panelRegistry,
workspace,
session,
presentations,
dockHostState,
dockHostMetrics);
const UIEditorPanelContentHostRequest contentHostRequest =
ResolveUIEditorPanelContentHostRequest(
frame.dockHostLayout,
panelRegistry);
frame.contentHostFrame = UpdateUIEditorPanelContentHost(
state.contentHostState,
contentHostRequest,
panelRegistry);
TrimObsoleteViewportPresentationStates(state, panelRegistry, presentations);
for (const UIEditorWorkspacePanelPresentationModel& presentation : presentations) {
if (!SupportsExternalViewportPresentation(panelRegistry, presentation)) {
continue;
}
const UIEditorPanelContentHostPanelState* contentHostPanelState =
FindUIEditorPanelContentHostPanelState(frame.contentHostFrame, presentation.panelId);
if (contentHostPanelState == nullptr || !contentHostPanelState->mounted) {
ResetHiddenViewportPresentationState(state, presentation.panelId);
continue;
}
UIEditorWorkspacePanelPresentationState& panelState =
EnsurePanelState(state, presentation.panelId);
UIEditorWorkspaceViewportComposeFrame viewportFrame = {};
viewportFrame.panelId = presentation.panelId;
viewportFrame.bounds = contentHostPanelState->bounds;
viewportFrame.viewportShellModel = presentation.viewportShellModel;
UIEditorViewportInputBridgeRequest inputRequest = {};
if (inputOwner != nullptr) {
inputRequest.focusMode = UIEditorViewportInputBridgeFocusMode::External;
inputRequest.focused =
IsUIEditorWorkspaceViewportInputOwner(*inputOwner, presentation.panelId);
}
viewportFrame.viewportShellFrame = UpdateUIEditorViewportShell(
panelState.viewportShellState,
contentHostPanelState->bounds,
presentation.viewportShellModel,
inputEvents,
viewportMetrics,
inputRequest);
frame.viewportFrames.push_back(std::move(viewportFrame));
}
return frame;
dockHostMetrics,
viewportMetrics);
return UpdateUIEditorWorkspaceCompose(
state,
request,
panelRegistry,
presentations,
inputEvents,
inputOwner);
}
std::vector<std::string> CollectUIEditorWorkspaceComposeExternalBodyPanelIds(
@@ -295,7 +324,10 @@ void AppendUIEditorWorkspaceCompose(
viewportFrame.viewportShellModel.spec.statusSegments,
viewportFrame.viewportShellFrame.slotState,
viewportPalette,
viewportMetrics);
viewportMetrics,
Widgets::UIEditorViewportSlotForegroundAppendOptions{
options.includeViewportTextures
});
}
UIEditorDockHostForegroundOptions foregroundOptions = {};
@@ -322,4 +354,17 @@ void AppendUIEditorWorkspaceComposeOverlay(
dockHostMetrics);
}
void AppendUIEditorWorkspaceComposeViewportTextures(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorWorkspaceComposeFrame& frame,
const Widgets::UIEditorViewportSlotPalette& viewportPalette) {
for (const UIEditorWorkspaceViewportComposeFrame& viewportFrame : frame.viewportFrames) {
Widgets::AppendUIEditorViewportSlotSurfaceTexture(
drawList,
viewportFrame.viewportShellFrame.slotLayout,
viewportFrame.viewportShellModel.frame,
viewportPalette);
}
}
} // namespace XCEngine::UI::Editor

View File

@@ -74,47 +74,53 @@ UIEditorWorkspaceInteractionFrame UpdateUIEditorWorkspaceInteraction(
inputEvents,
dockHostMetrics);
const UIEditorWorkspaceComposeFrame previewComposeFrame = UpdateUIEditorWorkspaceCompose(
state.composeState,
bounds,
controller.GetPanelRegistry(),
controller.GetWorkspace(),
controller.GetSession(),
model.workspacePresentations,
{},
state.dockHostInteractionState.dockHostState,
dockHostMetrics,
viewportMetrics);
const UIEditorWorkspaceComposeRequest previewRequest =
ResolveUIEditorWorkspaceComposeRequest(
frame.dockHostFrame.layout,
controller.GetPanelRegistry(),
model.workspacePresentations,
viewportMetrics);
const UIEditorPanelContentHostFrame previewContentHostFrame =
BuildUIEditorPanelContentHostFrame(
previewRequest.contentHostRequest,
controller.GetPanelRegistry());
state.inputOwner = ResolveNextInputOwner(
state.inputOwner,
previewComposeFrame.dockHostLayout,
previewComposeFrame.contentHostFrame,
previewRequest.dockHostLayout,
previewContentHostFrame,
inputEvents);
frame.inputOwner = state.inputOwner;
frame.inputOwnerChanged = !AreUIEditorWorkspaceInputOwnersEquivalent(
frame.previousInputOwner,
frame.inputOwner);
state.dockHostInteractionState.dockHostState.focused =
const bool dockFocused =
IsUIEditorWorkspaceDockHostInputOwner(frame.inputOwner);
frame.dockHostFrame.layout = Widgets::BuildUIEditorDockHostLayout(
bounds,
controller.GetPanelRegistry(),
controller.GetWorkspace(),
controller.GetSession(),
state.dockHostInteractionState.dockHostState,
dockHostMetrics);
frame.dockHostFrame.focused = state.dockHostInteractionState.dockHostState.focused;
const bool dockFocusChanged =
state.dockHostInteractionState.dockHostState.focused != dockFocused;
state.dockHostInteractionState.dockHostState.focused = dockFocused;
UIEditorWorkspaceComposeRequest composeRequest = previewRequest;
// Dock focus only changes visual state; hosted content mounts and viewport
// bounds remain stable for this frame.
if (dockFocusChanged) {
composeRequest.dockHostLayout = Widgets::BuildUIEditorDockHostLayout(
bounds,
controller.GetPanelRegistry(),
controller.GetWorkspace(),
controller.GetSession(),
state.dockHostInteractionState.dockHostState,
dockHostMetrics);
}
frame.dockHostFrame.layout = composeRequest.dockHostLayout;
frame.dockHostFrame.focused = dockFocused;
frame.composeFrame = UpdateUIEditorWorkspaceCompose(
state.composeState,
bounds,
composeRequest,
controller.GetPanelRegistry(),
controller.GetWorkspace(),
controller.GetSession(),
model.workspacePresentations,
inputEvents,
state.dockHostInteractionState.dockHostState,
dockHostMetrics,
viewportMetrics,
&frame.inputOwner);
frame.result.dockHostResult = frame.dockHostFrame.result;