Add semantic pixel asserts for lighting scenes

This commit is contained in:
2026-04-05 16:46:47 +08:00
parent cda4a57756
commit 54a699aa26
11 changed files with 282 additions and 96 deletions

View File

@@ -42,6 +42,10 @@ Old `editor` replacement is explicitly out of scope for this phase.
- ImGui factory entry points now live only in `LegacyImGuiHostInterop.h` - ImGui factory entry points now live only in `LegacyImGuiHostInterop.h`
- `XCUIEditorFontSetup.h` no longer exposes `ImFont` / `ImFontAtlas`; the public API is now a current-context font bootstrap helper - `XCUIEditorFontSetup.h` no longer exposes `ImFont` / `ImFontAtlas`; the public API is now a current-context font bootstrap helper
- `XCUIStandaloneTextAtlasProvider.h` no longer depends on the ImGui-oriented font bootstrap header - `XCUIStandaloneTextAtlasProvider.h` no longer depends on the ImGui-oriented font bootstrap header
- The generic shell command/state boundary is now narrower as well:
- `XCUIShellChromeState` no longer carries the legacy host demo-window toggle or command id on the generic shell-state surface
- `Application.h` no longer exposes that legacy demo command through generic `ShellCommandIds`, `ShellCommandBindings`, or `RegisterShellViewCommands(...)`
- the legacy demo window toggle now lives as a compatibility-only command inside `ApplicationLegacyImGui.cpp`
- Old `editor` replacement remains deferred; all active execution still stays inside XCUI shared code and `new_editor`. - Old `editor` replacement remains deferred; all active execution still stays inside XCUI shared code and `new_editor`.
## Three-Layer Status ## Three-Layer Status
@@ -120,7 +124,7 @@ Current gap:
- `XCUIDemoRuntime` now bridges pointer activation, text-edit commands, and shortcut-triggered commands through a unified command path, and `DrainPendingCommandIds()` now preserves mixed pointer/text/shortcut ordering. - `XCUIDemoRuntime` now bridges pointer activation, text-edit commands, and shortcut-triggered commands through a unified command path, and `DrainPendingCommandIds()` now preserves mixed pointer/text/shortcut ordering.
- `new_editor` now also has a pure `XCUIShellChromeState` model covering panel visibility, hosted-preview mode, and shell-level view toggles without depending on ImGui, `Application`, or the old editor. - `new_editor` now also has a pure `XCUIShellChromeState` model covering panel visibility, hosted-preview mode, and shell-level view toggles without depending on ImGui, `Application`, or the old editor.
- `XCUIShellChromeState` hosted-preview mode naming is now backend-neutral (`HostedPresenter` / `NativeOffscreen`) instead of encoding `ImGui` into the XCUI shell model. - `XCUIShellChromeState` hosted-preview mode naming is now backend-neutral (`HostedPresenter` / `NativeOffscreen`) instead of encoding `ImGui` into the XCUI shell model.
- The shell chrome view-toggle model no longer encodes the legacy Dear ImGui demo window as a generic `ImGuiDemoWindow` concept; that compatibility-only toggle is now named `LegacyHostDemoWindow` / `new_editor.view.legacy_host_demo` at the editor-layer seam. - The shell chrome view-toggle model no longer carries the legacy host demo window at all on the generic XCUI seam; that command now lives only inside the compatibility-only legacy host implementation.
- `new_editor` now also has a concrete `NativeWindowUICompositor` path and native-focused compositor tests, so the window compositor seam is no longer ImGui-only. - `new_editor` now also has a concrete `NativeWindowUICompositor` path and native-focused compositor tests, so the window compositor seam is no longer ImGui-only.
- `Application` now also has a native XCUI shell path that: - `Application` now also has a native XCUI shell path that:
- becomes the default `new_editor` startup path - becomes the default `new_editor` startup path

View File

@@ -607,7 +607,9 @@ if(MSVC)
target_compile_options(XCEngine PRIVATE target_compile_options(XCEngine PRIVATE
/FS /FS
$<$<CONFIG:Debug,RelWithDebInfo>:/Z7>) $<$<CONFIG:Debug,RelWithDebInfo>:/Z7>)
target_link_libraries(XCEngine PUBLIC delayimp) target_link_libraries(XCEngine PUBLIC
delayimp
d3dcompiler)
target_link_options(XCEngine INTERFACE "/DELAYLOAD:assimp-vc143-mt.dll") target_link_options(XCEngine INTERFACE "/DELAYLOAD:assimp-vc143-mt.dll")
set_target_properties(XCEngine PROPERTIES set_target_properties(XCEngine PROPERTIES
MSVC_DEBUG_INFORMATION_FORMAT "$<$<CONFIG:Debug,RelWithDebInfo>:Embedded>" MSVC_DEBUG_INFORMATION_FORMAT "$<$<CONFIG:Debug,RelWithDebInfo>:Embedded>"

View File

@@ -60,8 +60,6 @@ public:
::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::ToggleXCUIDemoPanel; ::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::ToggleXCUIDemoPanel;
static constexpr const char* ToggleXCUILayoutLabPanel = static constexpr const char* ToggleXCUILayoutLabPanel =
::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::ToggleXCUILayoutLabPanel; ::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::ToggleXCUILayoutLabPanel;
static constexpr const char* ToggleLegacyHostDemoWindow =
::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::ToggleLegacyHostDemoWindow;
static constexpr const char* ToggleNativeBackdrop = static constexpr const char* ToggleNativeBackdrop =
::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::ToggleNativeBackdrop; ::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::ToggleNativeBackdrop;
static constexpr const char* TogglePulseAccent = static constexpr const char* TogglePulseAccent =
@@ -81,8 +79,6 @@ public:
std::function<void(bool)> setXCUIDemoPanelVisible = {}; std::function<void(bool)> setXCUIDemoPanelVisible = {};
std::function<bool()> getXCUILayoutLabPanelVisible = {}; std::function<bool()> getXCUILayoutLabPanelVisible = {};
std::function<void(bool)> setXCUILayoutLabPanelVisible = {}; std::function<void(bool)> setXCUILayoutLabPanelVisible = {};
std::function<bool()> getLegacyHostDemoWindowVisible = {};
std::function<void(bool)> setLegacyHostDemoWindowVisible = {};
std::function<bool()> getNativeBackdropVisible = {}; std::function<bool()> getNativeBackdropVisible = {};
std::function<void(bool)> setNativeBackdropVisible = {}; std::function<void(bool)> setNativeBackdropVisible = {};
std::function<bool()> getPulseAccentEnabled = {}; std::function<bool()> getPulseAccentEnabled = {};
@@ -213,15 +209,6 @@ public:
ctrlOnly, ctrlOnly,
true, true,
false } }); false } });
bindToggleCommand(
ShellCommandIds::ToggleLegacyHostDemoWindow,
bindings.getLegacyHostDemoWindowVisible,
bindings.setLegacyHostDemoWindowVisible,
{ XCUIEditorCommandAccelerator{
static_cast<std::int32_t>(KeyCode::Three),
ctrlOnly,
true,
false } });
bindToggleCommand( bindToggleCommand(
ShellCommandIds::ToggleNativeBackdrop, ShellCommandIds::ToggleNativeBackdrop,
bindings.getNativeBackdropVisible, bindings.getNativeBackdropVisible,
@@ -469,6 +456,7 @@ private:
::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost m_nativeDemoCanvasHost; ::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost m_nativeDemoCanvasHost;
::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost m_nativeLayoutCanvasHost; ::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost m_nativeLayoutCanvasHost;
ShellPanelId m_nativeActivePanel = ShellPanelId::XCUIDemo; ShellPanelId m_nativeActivePanel = ShellPanelId::XCUIDemo;
bool m_legacyHostDemoWindowVisible = false;
bool m_nativeDemoReloadSucceeded = false; bool m_nativeDemoReloadSucceeded = false;
bool m_nativeLayoutReloadSucceeded = false; bool m_nativeLayoutReloadSucceeded = false;
::XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime m_nativeOverlayRuntime; ::XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime m_nativeOverlayRuntime;

View File

@@ -15,6 +15,7 @@ namespace NewEditor {
namespace { namespace {
constexpr float kLegacyHostClearColor[4] = { 0.08f, 0.09f, 0.11f, 1.0f }; constexpr float kLegacyHostClearColor[4] = { 0.08f, 0.09f, 0.11f, 1.0f };
constexpr const char* kLegacyHostDemoWindowCommandId = "new_editor.view.legacy_host_demo";
std::uint64_t MakeLegacyHostFrameTimestampNanoseconds() { std::uint64_t MakeLegacyHostFrameTimestampNanoseconds() {
return static_cast<std::uint64_t>( return static_cast<std::uint64_t>(
@@ -65,6 +66,37 @@ const char* GetHostedPreviewStateLabel(
return "idle"; return "idle";
} }
void RegisterLegacyHostDemoWindowCommand(
::XCEngine::Editor::XCUIBackend::XCUIEditorCommandRouter& router,
const std::function<bool()>& getter,
const std::function<void(bool)>& setter) {
if (!getter || !setter) {
return;
}
using ::XCEngine::Editor::XCUIBackend::XCUIEditorCommandAccelerator;
using ::XCEngine::Editor::XCUIBackend::XCUIEditorCommandDefinition;
using ::XCEngine::Input::KeyCode;
XCUIEditorCommandDefinition definition = {};
definition.commandId = kLegacyHostDemoWindowCommandId;
definition.isEnabled = [getter, setter]() {
return static_cast<bool>(getter) && static_cast<bool>(setter);
};
definition.invoke = [getter, setter]() {
setter(!getter());
};
definition.accelerators = {
XCUIEditorCommandAccelerator{
static_cast<std::int32_t>(KeyCode::Three),
{ false, true, false, false },
true,
false,
},
};
router.RegisterCommand(definition);
}
} // namespace } // namespace
Application::Application() = default; Application::Application() = default;
@@ -155,12 +187,6 @@ void Application::ConfigureShellCommandRouter() {
m_layoutLabPanel->SetVisible(visible); m_layoutLabPanel->SetVisible(visible);
} }
}; };
bindings.getLegacyHostDemoWindowVisible = [this]() {
return IsShellViewToggleEnabled(ShellViewToggleId::LegacyHostDemoWindow);
};
bindings.setLegacyHostDemoWindowVisible = [this](bool visible) {
SetShellViewToggleEnabled(ShellViewToggleId::LegacyHostDemoWindow, visible);
};
bindings.getNativeBackdropVisible = [this]() { bindings.getNativeBackdropVisible = [this]() {
return IsShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop); return IsShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop);
}; };
@@ -204,6 +230,10 @@ void Application::ConfigureShellCommandRouter() {
bindings.onHostedPreviewModeChanged = [this]() { ConfigureHostedPreviewPresenters(); }; bindings.onHostedPreviewModeChanged = [this]() { ConfigureHostedPreviewPresenters(); };
Application::RegisterShellViewCommands(m_shellCommandRouter, bindings); Application::RegisterShellViewCommands(m_shellCommandRouter, bindings);
RegisterLegacyHostDemoWindowCommand(
m_shellCommandRouter,
[this]() { return m_legacyHostDemoWindowVisible; },
[this](bool visible) { m_legacyHostDemoWindowVisible = visible; });
} }
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta
@@ -308,9 +338,9 @@ void Application::RenderLegacyImGuiUiFrame() {
m_layoutLabPanel->RenderIfVisible(); m_layoutLabPanel->RenderIfVisible();
} }
bool showLegacyHostDemoWindow = IsShellViewToggleEnabled(ShellViewToggleId::LegacyHostDemoWindow); bool showLegacyHostDemoWindow = m_legacyHostDemoWindowVisible;
if (::XCEngine::Editor::XCUIBackend::RenderLegacyImGuiDemoWindow(showLegacyHostDemoWindow)) { if (::XCEngine::Editor::XCUIBackend::RenderLegacyImGuiDemoWindow(showLegacyHostDemoWindow)) {
SetShellViewToggleEnabled(ShellViewToggleId::LegacyHostDemoWindow, showLegacyHostDemoWindow); m_legacyHostDemoWindowVisible = showLegacyHostDemoWindow;
} }
SyncShellChromePanelStateFromPanels(); SyncShellChromePanelStateFromPanels();
@@ -371,8 +401,8 @@ void Application::RenderShellChrome() {
drawCommandMenuItem( drawCommandMenuItem(
"Legacy Host Demo", "Legacy Host Demo",
"Ctrl+3", "Ctrl+3",
IsShellViewToggleEnabled(ShellViewToggleId::LegacyHostDemoWindow), m_legacyHostDemoWindowVisible,
ShellCommandIds::ToggleLegacyHostDemoWindow); kLegacyHostDemoWindowCommandId);
ImGui::Separator(); ImGui::Separator();
drawCommandMenuItem( drawCommandMenuItem(
"Native Backdrop", "Native Backdrop",

View File

@@ -13,7 +13,6 @@ constexpr std::size_t ToIndex(XCUIShellPanelId panelId) {
constexpr std::string_view kViewMenuLabel = "View"; constexpr std::string_view kViewMenuLabel = "View";
constexpr std::string_view kXCUIDemoShortcut = "Ctrl+1"; constexpr std::string_view kXCUIDemoShortcut = "Ctrl+1";
constexpr std::string_view kXCUILayoutLabShortcut = "Ctrl+2"; constexpr std::string_view kXCUILayoutLabShortcut = "Ctrl+2";
constexpr std::string_view kLegacyHostDemoShortcut = "Ctrl+3";
constexpr std::string_view kNativeBackdropShortcut = "Ctrl+Shift+B"; constexpr std::string_view kNativeBackdropShortcut = "Ctrl+Shift+B";
constexpr std::string_view kPulseAccentShortcut = "Ctrl+Shift+P"; constexpr std::string_view kPulseAccentShortcut = "Ctrl+Shift+P";
constexpr std::string_view kNativeXCUIOverlayShortcut = "Ctrl+Shift+O"; constexpr std::string_view kNativeXCUIOverlayShortcut = "Ctrl+Shift+O";
@@ -164,8 +163,6 @@ bool XCUIShellChromeState::ToggleHostedPreviewMode(XCUIShellPanelId panelId) {
bool XCUIShellChromeState::GetViewToggle(XCUIShellViewToggleId toggleId) const { bool XCUIShellChromeState::GetViewToggle(XCUIShellViewToggleId toggleId) const {
switch (toggleId) { switch (toggleId) {
case XCUIShellViewToggleId::LegacyHostDemoWindow:
return m_viewToggles.legacyHostDemoWindowVisible;
case XCUIShellViewToggleId::NativeBackdrop: case XCUIShellViewToggleId::NativeBackdrop:
return m_viewToggles.nativeBackdropVisible; return m_viewToggles.nativeBackdropVisible;
case XCUIShellViewToggleId::PulseAccent: case XCUIShellViewToggleId::PulseAccent:
@@ -183,9 +180,6 @@ bool XCUIShellChromeState::GetViewToggle(XCUIShellViewToggleId toggleId) const {
bool XCUIShellChromeState::SetViewToggle(XCUIShellViewToggleId toggleId, bool enabled) { bool XCUIShellChromeState::SetViewToggle(XCUIShellViewToggleId toggleId, bool enabled) {
bool* target = nullptr; bool* target = nullptr;
switch (toggleId) { switch (toggleId) {
case XCUIShellViewToggleId::LegacyHostDemoWindow:
target = &m_viewToggles.legacyHostDemoWindowVisible;
break;
case XCUIShellViewToggleId::NativeBackdrop: case XCUIShellViewToggleId::NativeBackdrop:
target = &m_viewToggles.nativeBackdropVisible; target = &m_viewToggles.nativeBackdropVisible;
break; break;
@@ -227,9 +221,6 @@ bool XCUIShellChromeState::InvokeCommand(std::string_view commandId) {
if (commandId == XCUIShellChromeCommandIds::ToggleXCUILayoutLabPanel) { if (commandId == XCUIShellChromeCommandIds::ToggleXCUILayoutLabPanel) {
return TogglePanelVisible(XCUIShellPanelId::XCUILayoutLab); return TogglePanelVisible(XCUIShellPanelId::XCUILayoutLab);
} }
if (commandId == XCUIShellChromeCommandIds::ToggleLegacyHostDemoWindow) {
return ToggleViewToggle(XCUIShellViewToggleId::LegacyHostDemoWindow);
}
if (commandId == XCUIShellChromeCommandIds::ToggleNativeBackdrop) { if (commandId == XCUIShellChromeCommandIds::ToggleNativeBackdrop) {
return ToggleViewToggle(XCUIShellViewToggleId::NativeBackdrop); return ToggleViewToggle(XCUIShellViewToggleId::NativeBackdrop);
} }
@@ -288,15 +279,6 @@ bool XCUIShellChromeState::TryGetCommandDescriptor(
return true; return true;
} }
if (commandId == GetViewToggleCommandId(XCUIShellViewToggleId::LegacyHostDemoWindow)) {
outDescriptor.label = "Legacy Host Demo";
outDescriptor.shortcut = kLegacyHostDemoShortcut;
outDescriptor.commandId = commandId;
outDescriptor.checkable = true;
outDescriptor.checked = GetViewToggle(XCUIShellViewToggleId::LegacyHostDemoWindow);
outDescriptor.enabled = true;
return true;
}
if (commandId == GetViewToggleCommandId(XCUIShellViewToggleId::NativeBackdrop)) { if (commandId == GetViewToggleCommandId(XCUIShellViewToggleId::NativeBackdrop)) {
outDescriptor.label = "Native Backdrop"; outDescriptor.label = "Native Backdrop";
outDescriptor.shortcut = kNativeBackdropShortcut; outDescriptor.shortcut = kNativeBackdropShortcut;
@@ -358,7 +340,7 @@ bool XCUIShellChromeState::TryGetCommandDescriptor(
XCUIShellMenuDescriptor XCUIShellChromeState::BuildViewMenuDescriptor() const { XCUIShellMenuDescriptor XCUIShellChromeState::BuildViewMenuDescriptor() const {
XCUIShellMenuDescriptor descriptor = {}; XCUIShellMenuDescriptor descriptor = {};
descriptor.label = kViewMenuLabel; descriptor.label = kViewMenuLabel;
descriptor.items.reserve(10u); descriptor.items.reserve(9u);
const auto appendCommandItem = [this, &descriptor](std::string_view commandId) { const auto appendCommandItem = [this, &descriptor](std::string_view commandId) {
XCUIShellCommandDescriptor commandDescriptor = {}; XCUIShellCommandDescriptor commandDescriptor = {};
@@ -374,7 +356,6 @@ XCUIShellMenuDescriptor XCUIShellChromeState::BuildViewMenuDescriptor() const {
appendCommandItem(GetPanelVisibilityCommandId(XCUIShellPanelId::XCUIDemo)); appendCommandItem(GetPanelVisibilityCommandId(XCUIShellPanelId::XCUIDemo));
appendCommandItem(GetPanelVisibilityCommandId(XCUIShellPanelId::XCUILayoutLab)); appendCommandItem(GetPanelVisibilityCommandId(XCUIShellPanelId::XCUILayoutLab));
appendCommandItem(GetViewToggleCommandId(XCUIShellViewToggleId::LegacyHostDemoWindow));
descriptor.items.push_back({ XCUIShellMenuItemKind::Separator, {} }); descriptor.items.push_back({ XCUIShellMenuItemKind::Separator, {} });
@@ -414,8 +395,6 @@ std::string_view XCUIShellChromeState::GetPanelPreviewModeCommandId(XCUIShellPan
std::string_view XCUIShellChromeState::GetViewToggleCommandId(XCUIShellViewToggleId toggleId) { std::string_view XCUIShellChromeState::GetViewToggleCommandId(XCUIShellViewToggleId toggleId) {
switch (toggleId) { switch (toggleId) {
case XCUIShellViewToggleId::LegacyHostDemoWindow:
return XCUIShellChromeCommandIds::ToggleLegacyHostDemoWindow;
case XCUIShellViewToggleId::NativeBackdrop: case XCUIShellViewToggleId::NativeBackdrop:
return XCUIShellChromeCommandIds::ToggleNativeBackdrop; return XCUIShellChromeCommandIds::ToggleNativeBackdrop;
case XCUIShellViewToggleId::PulseAccent: case XCUIShellViewToggleId::PulseAccent:

View File

@@ -16,8 +16,7 @@ enum class XCUIShellPanelId : std::uint8_t {
}; };
enum class XCUIShellViewToggleId : std::uint8_t { enum class XCUIShellViewToggleId : std::uint8_t {
LegacyHostDemoWindow = 0, NativeBackdrop = 0,
NativeBackdrop,
PulseAccent, PulseAccent,
NativeXCUIOverlay, NativeXCUIOverlay,
HostedPreviewHud, HostedPreviewHud,
@@ -46,7 +45,6 @@ struct XCUIShellPanelChromeState {
}; };
struct XCUIShellViewToggleState { struct XCUIShellViewToggleState {
bool legacyHostDemoWindowVisible = false;
bool nativeBackdropVisible = true; bool nativeBackdropVisible = true;
bool pulseAccentEnabled = true; bool pulseAccentEnabled = true;
bool nativeXCUIOverlayVisible = true; bool nativeXCUIOverlayVisible = true;
@@ -56,7 +54,6 @@ struct XCUIShellViewToggleState {
struct XCUIShellChromeCommandIds { struct XCUIShellChromeCommandIds {
static constexpr const char* ToggleXCUIDemoPanel = "new_editor.view.xcui_demo"; static constexpr const char* ToggleXCUIDemoPanel = "new_editor.view.xcui_demo";
static constexpr const char* ToggleXCUILayoutLabPanel = "new_editor.view.xcui_layout_lab"; static constexpr const char* ToggleXCUILayoutLabPanel = "new_editor.view.xcui_layout_lab";
static constexpr const char* ToggleLegacyHostDemoWindow = "new_editor.view.legacy_host_demo";
static constexpr const char* ToggleNativeBackdrop = "new_editor.view.native_backdrop"; static constexpr const char* ToggleNativeBackdrop = "new_editor.view.native_backdrop";
static constexpr const char* TogglePulseAccent = "new_editor.view.pulse_accent"; static constexpr const char* TogglePulseAccent = "new_editor.view.pulse_accent";
static constexpr const char* ToggleNativeXCUIOverlay = "new_editor.view.native_xcui_overlay"; static constexpr const char* ToggleNativeXCUIOverlay = "new_editor.view.native_xcui_overlay";

View File

@@ -71,12 +71,6 @@ struct ShellCommandHarness {
bindings.setXCUILayoutLabPanelVisible = [this](bool visible) { bindings.setXCUILayoutLabPanelVisible = [this](bool visible) {
panels[ToPanelIndex(Application::ShellPanelId::XCUILayoutLab)].visible = visible; panels[ToPanelIndex(Application::ShellPanelId::XCUILayoutLab)].visible = visible;
}; };
bindings.getLegacyHostDemoWindowVisible = [this]() {
return viewToggles.legacyHostDemoWindowVisible;
};
bindings.setLegacyHostDemoWindowVisible = [this](bool visible) {
viewToggles.legacyHostDemoWindowVisible = visible;
};
bindings.getNativeBackdropVisible = [this]() { bindings.getNativeBackdropVisible = [this]() {
return viewToggles.nativeBackdropVisible; return viewToggles.nativeBackdropVisible;
}; };
@@ -134,7 +128,6 @@ TEST(ApplicationShellCommandBindingsTest, RegisterShellViewCommandsInvokesBoundT
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleXCUIDemoPanel)); EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleXCUIDemoPanel));
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleXCUILayoutLabPanel)); EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleXCUILayoutLabPanel));
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleLegacyHostDemoWindow));
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleNativeBackdrop)); EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleNativeBackdrop));
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::TogglePulseAccent)); EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::TogglePulseAccent));
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleNativeXCUIOverlay)); EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleNativeXCUIOverlay));
@@ -144,8 +137,6 @@ TEST(ApplicationShellCommandBindingsTest, RegisterShellViewCommandsInvokesBoundT
EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleXCUIDemoPanel)); EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleXCUIDemoPanel));
EXPECT_FALSE(harness.Panel(Application::ShellPanelId::XCUIDemo).visible); EXPECT_FALSE(harness.Panel(Application::ShellPanelId::XCUIDemo).visible);
EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleLegacyHostDemoWindow));
EXPECT_TRUE(harness.viewToggles.legacyHostDemoWindowVisible);
EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeBackdrop)); EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeBackdrop));
EXPECT_FALSE(harness.viewToggles.nativeBackdropVisible); EXPECT_FALSE(harness.viewToggles.nativeBackdropVisible);
EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeXCUIOverlay)); EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeXCUIOverlay));

View File

@@ -17,7 +17,6 @@ TEST(XCUIShellChromeStateTest, DefaultsMatchCurrentShellChromeConfiguration) {
XCUIShellChromeState state = {}; XCUIShellChromeState state = {};
const auto& viewToggles = state.GetViewToggles(); const auto& viewToggles = state.GetViewToggles();
EXPECT_FALSE(viewToggles.legacyHostDemoWindowVisible);
EXPECT_TRUE(viewToggles.nativeBackdropVisible); EXPECT_TRUE(viewToggles.nativeBackdropVisible);
EXPECT_TRUE(viewToggles.pulseAccentEnabled); EXPECT_TRUE(viewToggles.pulseAccentEnabled);
EXPECT_TRUE(viewToggles.nativeXCUIOverlayVisible); EXPECT_TRUE(viewToggles.nativeXCUIOverlayVisible);
@@ -118,10 +117,6 @@ TEST(XCUIShellChromeStateTest, HostedPreviewStateSeparatesEnablementFromRequeste
TEST(XCUIShellChromeStateTest, ViewToggleMutatorsOnlyFlipRequestedFlags) { TEST(XCUIShellChromeStateTest, ViewToggleMutatorsOnlyFlipRequestedFlags) {
XCUIShellChromeState state = {}; XCUIShellChromeState state = {};
EXPECT_TRUE(state.SetViewToggle(XCUIShellViewToggleId::LegacyHostDemoWindow, true));
EXPECT_TRUE(state.GetViewToggle(XCUIShellViewToggleId::LegacyHostDemoWindow));
EXPECT_FALSE(state.SetViewToggle(XCUIShellViewToggleId::LegacyHostDemoWindow, true));
EXPECT_TRUE(state.ToggleViewToggle(XCUIShellViewToggleId::HostedPreviewHud)); EXPECT_TRUE(state.ToggleViewToggle(XCUIShellViewToggleId::HostedPreviewHud));
EXPECT_FALSE(state.GetViewToggle(XCUIShellViewToggleId::HostedPreviewHud)); EXPECT_FALSE(state.GetViewToggle(XCUIShellViewToggleId::HostedPreviewHud));
EXPECT_TRUE(state.GetViewToggle(XCUIShellViewToggleId::NativeBackdrop)); EXPECT_TRUE(state.GetViewToggle(XCUIShellViewToggleId::NativeBackdrop));
@@ -190,7 +185,7 @@ TEST(XCUIShellChromeStateTest, ViewMenuDescriptorMatchesCurrentApplicationOrderi
const auto menu = state.BuildViewMenuDescriptor(); const auto menu = state.BuildViewMenuDescriptor();
ASSERT_EQ(menu.label, "View"); ASSERT_EQ(menu.label, "View");
ASSERT_EQ(menu.items.size(), 10u); ASSERT_EQ(menu.items.size(), 9u);
ASSERT_EQ(menu.items[0].kind, XCUIShellMenuItemKind::Command); ASSERT_EQ(menu.items[0].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[0].command.label, "XCUI Demo"); EXPECT_EQ(menu.items[0].command.label, "XCUI Demo");
@@ -203,42 +198,37 @@ TEST(XCUIShellChromeStateTest, ViewMenuDescriptorMatchesCurrentApplicationOrderi
EXPECT_EQ(menu.items[1].command.shortcut, "Ctrl+2"); EXPECT_EQ(menu.items[1].command.shortcut, "Ctrl+2");
EXPECT_EQ(menu.items[1].command.commandId, XCUIShellChromeCommandIds::ToggleXCUILayoutLabPanel); EXPECT_EQ(menu.items[1].command.commandId, XCUIShellChromeCommandIds::ToggleXCUILayoutLabPanel);
ASSERT_EQ(menu.items[2].kind, XCUIShellMenuItemKind::Command); EXPECT_EQ(menu.items[2].kind, XCUIShellMenuItemKind::Separator);
EXPECT_EQ(menu.items[2].command.label, "Legacy Host Demo");
EXPECT_EQ(menu.items[2].command.shortcut, "Ctrl+3");
EXPECT_EQ(menu.items[2].command.commandId, XCUIShellChromeCommandIds::ToggleLegacyHostDemoWindow);
EXPECT_EQ(menu.items[3].kind, XCUIShellMenuItemKind::Separator); ASSERT_EQ(menu.items[3].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[3].command.label, "Native Backdrop");
EXPECT_EQ(menu.items[3].command.shortcut, "Ctrl+Shift+B");
EXPECT_EQ(menu.items[3].command.commandId, XCUIShellChromeCommandIds::ToggleNativeBackdrop);
ASSERT_EQ(menu.items[4].kind, XCUIShellMenuItemKind::Command); ASSERT_EQ(menu.items[4].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[4].command.label, "Native Backdrop"); EXPECT_EQ(menu.items[4].command.label, "Pulse Accent");
EXPECT_EQ(menu.items[4].command.shortcut, "Ctrl+Shift+B"); EXPECT_EQ(menu.items[4].command.shortcut, "Ctrl+Shift+P");
EXPECT_EQ(menu.items[4].command.commandId, XCUIShellChromeCommandIds::ToggleNativeBackdrop); EXPECT_EQ(menu.items[4].command.commandId, XCUIShellChromeCommandIds::TogglePulseAccent);
ASSERT_EQ(menu.items[5].kind, XCUIShellMenuItemKind::Command); ASSERT_EQ(menu.items[5].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[5].command.label, "Pulse Accent"); EXPECT_EQ(menu.items[5].command.label, "Native XCUI Overlay");
EXPECT_EQ(menu.items[5].command.shortcut, "Ctrl+Shift+P"); EXPECT_EQ(menu.items[5].command.shortcut, "Ctrl+Shift+O");
EXPECT_EQ(menu.items[5].command.commandId, XCUIShellChromeCommandIds::TogglePulseAccent); EXPECT_EQ(menu.items[5].command.commandId, XCUIShellChromeCommandIds::ToggleNativeXCUIOverlay);
ASSERT_EQ(menu.items[6].kind, XCUIShellMenuItemKind::Command); ASSERT_EQ(menu.items[6].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[6].command.label, "Native XCUI Overlay"); EXPECT_EQ(menu.items[6].command.label, "Hosted Preview HUD");
EXPECT_EQ(menu.items[6].command.shortcut, "Ctrl+Shift+O"); EXPECT_EQ(menu.items[6].command.shortcut, "Ctrl+Shift+H");
EXPECT_EQ(menu.items[6].command.commandId, XCUIShellChromeCommandIds::ToggleNativeXCUIOverlay); EXPECT_EQ(menu.items[6].command.commandId, XCUIShellChromeCommandIds::ToggleHostedPreviewHud);
ASSERT_EQ(menu.items[7].kind, XCUIShellMenuItemKind::Command); ASSERT_EQ(menu.items[7].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[7].command.label, "Hosted Preview HUD"); EXPECT_EQ(menu.items[7].command.label, "Native Demo Panel Preview");
EXPECT_EQ(menu.items[7].command.shortcut, "Ctrl+Shift+H"); EXPECT_EQ(menu.items[7].command.shortcut, "Ctrl+Alt+1");
EXPECT_EQ(menu.items[7].command.commandId, XCUIShellChromeCommandIds::ToggleHostedPreviewHud); EXPECT_EQ(menu.items[7].command.commandId, XCUIShellChromeCommandIds::ToggleNativeDemoPanelPreview);
ASSERT_EQ(menu.items[8].kind, XCUIShellMenuItemKind::Command); ASSERT_EQ(menu.items[8].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[8].command.label, "Native Demo Panel Preview"); EXPECT_EQ(menu.items[8].command.label, "Native Layout Lab Preview");
EXPECT_EQ(menu.items[8].command.shortcut, "Ctrl+Alt+1"); EXPECT_EQ(menu.items[8].command.shortcut, "Ctrl+Alt+2");
EXPECT_EQ(menu.items[8].command.commandId, XCUIShellChromeCommandIds::ToggleNativeDemoPanelPreview); EXPECT_EQ(menu.items[8].command.commandId, XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview);
ASSERT_EQ(menu.items[9].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[9].command.label, "Native Layout Lab Preview");
EXPECT_EQ(menu.items[9].command.shortcut, "Ctrl+Alt+2");
EXPECT_EQ(menu.items[9].command.commandId, XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview);
} }
TEST(XCUIShellChromeStateTest, ViewMenuDescriptorCheckedStateTracksShellStateChanges) { TEST(XCUIShellChromeStateTest, ViewMenuDescriptorCheckedStateTracksShellStateChanges) {
@@ -254,9 +244,9 @@ TEST(XCUIShellChromeStateTest, ViewMenuDescriptorCheckedStateTracksShellStateCha
const auto menu = state.BuildViewMenuDescriptor(); const auto menu = state.BuildViewMenuDescriptor();
EXPECT_FALSE(menu.items[0].command.checked); EXPECT_FALSE(menu.items[0].command.checked);
EXPECT_FALSE(menu.items[6].command.checked);
EXPECT_FALSE(menu.items[7].command.checked); EXPECT_FALSE(menu.items[7].command.checked);
EXPECT_FALSE(menu.items[8].command.checked); EXPECT_TRUE(menu.items[8].command.checked);
EXPECT_TRUE(menu.items[9].command.checked);
} }
TEST(XCUIShellChromeStateTest, PanelCommandIdHelpersMatchCurrentShellCommands) { TEST(XCUIShellChromeStateTest, PanelCommandIdHelpersMatchCurrentShellCommands) {
@@ -275,9 +265,6 @@ TEST(XCUIShellChromeStateTest, PanelCommandIdHelpersMatchCurrentShellCommands) {
} }
TEST(XCUIShellChromeStateTest, ViewToggleCommandIdHelpersMatchCurrentShellCommands) { TEST(XCUIShellChromeStateTest, ViewToggleCommandIdHelpersMatchCurrentShellCommands) {
EXPECT_EQ(
XCUIShellChromeState::GetViewToggleCommandId(XCUIShellViewToggleId::LegacyHostDemoWindow),
XCUIShellChromeCommandIds::ToggleLegacyHostDemoWindow);
EXPECT_EQ( EXPECT_EQ(
XCUIShellChromeState::GetViewToggleCommandId(XCUIShellViewToggleId::NativeBackdrop), XCUIShellChromeState::GetViewToggleCommandId(XCUIShellViewToggleId::NativeBackdrop),
XCUIShellChromeCommandIds::ToggleNativeBackdrop); XCUIShellChromeCommandIds::ToggleNativeBackdrop);

View File

@@ -0,0 +1,176 @@
#pragma once
#include <gtest/gtest.h>
#include <algorithm>
#include <array>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <string>
#include <vector>
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
namespace RenderingIntegrationTestUtils {
struct PpmImage {
uint32_t width = 0;
uint32_t height = 0;
std::vector<uint8_t> rgb;
std::array<uint8_t, 3> GetPixel(uint32_t x, uint32_t y) const {
const size_t index = (static_cast<size_t>(y) * width + x) * 3u;
return { rgb[index + 0], rgb[index + 1], rgb[index + 2] };
}
};
inline std::filesystem::path GetExecutableDirectory() {
char exePath[MAX_PATH] = {};
const DWORD length = GetModuleFileNameA(nullptr, exePath, MAX_PATH);
if (length == 0 || length >= MAX_PATH) {
return std::filesystem::current_path();
}
return std::filesystem::path(exePath).parent_path();
}
inline std::filesystem::path ResolveRuntimePath(const char* path) {
std::filesystem::path resolved(path);
if (resolved.is_absolute()) {
return resolved;
}
return GetExecutableDirectory() / resolved;
}
inline std::string ReadNextPpmToken(std::istream& stream) {
std::string token;
while (stream >> token) {
if (!token.empty() && token[0] == '#') {
std::string ignored;
std::getline(stream, ignored);
continue;
}
return token;
}
return {};
}
inline PpmImage LoadPpmImage(const char* path) {
const std::filesystem::path resolvedPath = ResolveRuntimePath(path);
std::ifstream file(resolvedPath, std::ios::binary);
EXPECT_TRUE(file.is_open()) << resolvedPath.string();
PpmImage image;
if (!file.is_open()) {
return image;
}
const std::string magic = ReadNextPpmToken(file);
EXPECT_EQ(magic, "P6");
if (magic != "P6") {
return image;
}
const std::string widthToken = ReadNextPpmToken(file);
const std::string heightToken = ReadNextPpmToken(file);
const std::string maxValueToken = ReadNextPpmToken(file);
EXPECT_FALSE(widthToken.empty());
EXPECT_FALSE(heightToken.empty());
EXPECT_FALSE(maxValueToken.empty());
if (widthToken.empty() || heightToken.empty() || maxValueToken.empty()) {
return image;
}
image.width = static_cast<uint32_t>(std::stoul(widthToken));
image.height = static_cast<uint32_t>(std::stoul(heightToken));
EXPECT_EQ(std::stoul(maxValueToken), 255u);
file.get();
image.rgb.resize(static_cast<size_t>(image.width) * image.height * 3u);
file.read(reinterpret_cast<char*>(image.rgb.data()), static_cast<std::streamsize>(image.rgb.size()));
EXPECT_EQ(file.gcount(), static_cast<std::streamsize>(image.rgb.size()));
return image;
}
inline int PixelLuminance(const std::array<uint8_t, 3>& pixel) {
return static_cast<int>(pixel[0]) + static_cast<int>(pixel[1]) + static_cast<int>(pixel[2]);
}
inline void AssertPixelInBounds(const PpmImage& image, uint32_t x, uint32_t y, const char* label) {
ASSERT_LT(x, image.width) << label;
ASSERT_LT(y, image.height) << label;
}
inline void ExpectPixelNear(
const PpmImage& image,
uint32_t x,
uint32_t y,
const std::array<uint8_t, 3>& expected,
uint8_t tolerance,
const char* label) {
AssertPixelInBounds(image, x, y, label);
const std::array<uint8_t, 3> actual = image.GetPixel(x, y);
for (size_t channel = 0; channel < actual.size(); ++channel) {
const int delta = std::abs(static_cast<int>(actual[channel]) - static_cast<int>(expected[channel]));
EXPECT_LE(delta, static_cast<int>(tolerance))
<< label << " @ (" << x << ", " << y << ") channel=" << channel;
}
}
inline void ExpectPixelLuminanceAtLeast(
const PpmImage& image,
uint32_t x,
uint32_t y,
int minLuminance,
const char* label) {
AssertPixelInBounds(image, x, y, label);
const std::array<uint8_t, 3> actual = image.GetPixel(x, y);
EXPECT_GE(PixelLuminance(actual), minLuminance) << label << " @ (" << x << ", " << y << ")";
}
inline void ExpectPixelLuminanceAtMost(
const PpmImage& image,
uint32_t x,
uint32_t y,
int maxLuminance,
const char* label) {
AssertPixelInBounds(image, x, y, label);
const std::array<uint8_t, 3> actual = image.GetPixel(x, y);
EXPECT_LE(PixelLuminance(actual), maxLuminance) << label << " @ (" << x << ", " << y << ")";
}
inline void ExpectPixelChannelDominates(
const PpmImage& image,
uint32_t x,
uint32_t y,
size_t dominantChannel,
int minMargin,
const char* label) {
AssertPixelInBounds(image, x, y, label);
ASSERT_LT(dominantChannel, static_cast<size_t>(3)) << label;
const std::array<uint8_t, 3> actual = image.GetPixel(x, y);
int maxOtherChannel = 0;
for (size_t channel = 0; channel < actual.size(); ++channel) {
if (channel == dominantChannel) {
continue;
}
maxOtherChannel = std::max(maxOtherChannel, static_cast<int>(actual[channel]));
}
EXPECT_GE(static_cast<int>(actual[dominantChannel]) - maxOtherChannel, minMargin)
<< label << " @ (" << x << ", " << y << ")";
}
} // namespace RenderingIntegrationTestUtils

View File

@@ -3,6 +3,7 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include "../RenderingIntegrationImageAssert.h"
#include "../RenderingIntegrationMain.h" #include "../RenderingIntegrationMain.h"
#include <XCEngine/Components/CameraComponent.h> #include <XCEngine/Components/CameraComponent.h>
@@ -45,6 +46,18 @@ constexpr const char* kVulkanScreenshot = "multi_light_scene_vulkan.ppm";
constexpr uint32_t kFrameWidth = 1280; constexpr uint32_t kFrameWidth = 1280;
constexpr uint32_t kFrameHeight = 720; constexpr uint32_t kFrameHeight = 720;
void ExpectMultiLightKeyPixels(const RenderingIntegrationTestUtils::PpmImage& image) {
using namespace RenderingIntegrationTestUtils;
ExpectPixelLuminanceAtMost(image, 320, 320, 24, "background remains dark");
ExpectPixelLuminanceAtLeast(image, 625, 285, 540, "left point-light highlight");
ExpectPixelChannelDominates(image, 625, 285, 0, 80, "left point-light highlight stays warm");
ExpectPixelLuminanceAtLeast(image, 640, 315, 470, "center cube receives main light");
ExpectPixelChannelDominates(image, 640, 315, 0, 40, "center cube keeps warm directional tint");
ExpectPixelLuminanceAtLeast(image, 758, 311, 300, "right cube receives cool fill");
ExpectPixelChannelDominates(image, 758, 311, 2, 20, "right cube keeps cool fill tint");
}
void AppendQuadFace( void AppendQuadFace(
std::vector<StaticMeshVertex>& vertices, std::vector<StaticMeshVertex>& vertices,
std::vector<uint32_t>& indices, std::vector<uint32_t>& indices,
@@ -459,6 +472,8 @@ TEST_P(MultiLightSceneTest, RenderMultiLightScene) {
if (frameCount >= targetFrameCount) { if (frameCount >= targetFrameCount) {
commandQueue->WaitForIdle(); commandQueue->WaitForIdle();
ASSERT_TRUE(TakeScreenshot(screenshotFilename)); ASSERT_TRUE(TakeScreenshot(screenshotFilename));
const auto screenshotImage = RenderingIntegrationTestUtils::LoadPpmImage(screenshotFilename);
ExpectMultiLightKeyPixels(screenshotImage);
ASSERT_TRUE(CompareWithGoldenTemplate(screenshotFilename, "GT.ppm", static_cast<float>(comparisonThreshold))); ASSERT_TRUE(CompareWithGoldenTemplate(screenshotFilename, "GT.ppm", static_cast<float>(comparisonThreshold)));
break; break;
} }

View File

@@ -3,6 +3,7 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include "../RenderingIntegrationImageAssert.h"
#include "../RenderingIntegrationMain.h" #include "../RenderingIntegrationMain.h"
#include <XCEngine/Components/CameraComponent.h> #include <XCEngine/Components/CameraComponent.h>
@@ -45,6 +46,20 @@ constexpr const char* kVulkanScreenshot = "spot_light_scene_vulkan.ppm";
constexpr uint32_t kFrameWidth = 1280; constexpr uint32_t kFrameWidth = 1280;
constexpr uint32_t kFrameHeight = 720; constexpr uint32_t kFrameHeight = 720;
void ExpectSpotLightKeyPixels(const RenderingIntegrationTestUtils::PpmImage& image) {
using namespace RenderingIntegrationTestUtils;
ExpectPixelLuminanceAtMost(image, 320, 320, 18, "background remains unlit");
ExpectPixelLuminanceAtLeast(image, 640, 320, 700, "spot hot area stays bright");
ExpectPixelNear(image, 640, 320, { 255, 255, 203 }, 2, "spot hot area color");
ExpectPixelLuminanceAtLeast(image, 676, 289, 560, "upper cone receives spotlight");
ExpectPixelChannelDominates(image, 676, 289, 0, 40, "upper cone keeps warm tint");
ExpectPixelLuminanceAtLeast(image, 700, 376, 540, "ground inside cone stays lit");
ExpectPixelChannelDominates(image, 700, 376, 0, 30, "ground inside cone stays warm");
ExpectPixelLuminanceAtMost(image, 760, 320, 210, "edge cube outside cone stays dark");
ExpectPixelLuminanceAtMost(image, 640, 406, 160, "ground outside cone stays dark");
}
void AppendQuadFace( void AppendQuadFace(
std::vector<StaticMeshVertex>& vertices, std::vector<StaticMeshVertex>& vertices,
std::vector<uint32_t>& indices, std::vector<uint32_t>& indices,
@@ -420,6 +435,8 @@ TEST_P(SpotLightSceneTest, RenderSpotLightScene) {
if (frameCount >= targetFrameCount) { if (frameCount >= targetFrameCount) {
commandQueue->WaitForIdle(); commandQueue->WaitForIdle();
ASSERT_TRUE(TakeScreenshot(screenshotFilename)); ASSERT_TRUE(TakeScreenshot(screenshotFilename));
const auto screenshotImage = RenderingIntegrationTestUtils::LoadPpmImage(screenshotFilename);
ExpectSpotLightKeyPixels(screenshotImage);
ASSERT_TRUE(CompareWithGoldenTemplate(screenshotFilename, "GT.ppm", static_cast<float>(comparisonThreshold))); ASSERT_TRUE(CompareWithGoldenTemplate(screenshotFilename, "GT.ppm", static_cast<float>(comparisonThreshold)));
break; break;
} }