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:
@@ -108,6 +108,7 @@ struct UIInputEvent {
|
||||
UIInputModifiers modifiers = {};
|
||||
bool repeat = false;
|
||||
bool synthetic = false;
|
||||
bool doubleClick = false;
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
2492
new_editor/app/Rendering/D3D12/D3D12UiRenderer.cpp
Normal file
2492
new_editor/app/Rendering/D3D12/D3D12UiRenderer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
246
new_editor/app/Rendering/D3D12/D3D12UiRenderer.h
Normal file
246
new_editor/app/Rendering/D3D12/D3D12UiRenderer.h
Normal 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
|
||||
752
new_editor/app/Rendering/D3D12/D3D12UiTextSystem.cpp
Normal file
752
new_editor/app/Rendering/D3D12/D3D12UiTextSystem.cpp
Normal 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
|
||||
85
new_editor/app/Rendering/D3D12/D3D12UiTextSystem.h
Normal file
85
new_editor/app/Rendering/D3D12/D3D12UiTextSystem.h
Normal 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
|
||||
676
new_editor/app/Rendering/D3D12/D3D12UiTextureHost.cpp
Normal file
676
new_editor/app/Rendering/D3D12/D3D12UiTextureHost.cpp
Normal 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
|
||||
113
new_editor/app/Rendering/D3D12/D3D12UiTextureHost.h
Normal file
113
new_editor/app/Rendering/D3D12/D3D12UiTextureHost.h
Normal 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
|
||||
@@ -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(
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = {};
|
||||
};
|
||||
|
||||
|
||||
@@ -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.";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = {});
|
||||
|
||||
|
||||
@@ -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 = {},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user