From 162f1cc12eef0e77f2b9568d1d1452cc93893cf6 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Wed, 8 Apr 2026 16:09:15 +0800 Subject: [PATCH] engine: sync editor rendering and ui changes --- Assets.meta | 5 - Assets/XCUI.meta | 5 - CMakeLists.txt | 2 +- CMakePresets.json | 35 - MVS/Music fluctuations/source/CMakeLists.txt | 2 +- MVS/OpenGL/CMakeLists.txt | 2 +- MVS/VolumeRenderer/CMakeLists.txt | 2 +- MVS/ui/CMakeLists.txt | 4 +- editor/CMakeLists.txt | 1 + ..._gizmo.png => directional_light_gizmo.png} | Bin editor/resources/Icons/point_light_gizmo.png | Bin 0 -> 875 bytes editor/resources/Icons/spot_light_gizmo.png | Bin 0 -> 519 bytes .../infinite-grid/infinite-grid.ps.hlsl | 132 --- .../infinite-grid/infinite-grid.shader | 198 ++++- .../infinite-grid/infinite-grid.vs.hlsl | 26 - .../object-id-outline.ps.hlsl | 89 -- .../object-id-outline.shader | 29 - .../object-id-outline.vs.hlsl | 25 - editor/src/Commands/EntityCommands.h | 49 +- editor/src/UI/BuiltInIcons.cpp | 56 +- editor/src/UI/BuiltInIcons.h | 5 +- editor/src/UI/Widgets.h | 41 +- .../SceneViewportSelectionOutlinePass.cpp | 6 +- .../Viewport/SceneViewportEditorOverlayData.h | 5 +- .../src/Viewport/SceneViewportHudOverlay.cpp | 16 +- editor/src/Viewport/SceneViewportNavigation.h | 32 +- .../SceneViewportOverlayProviders.cpp | 214 ++++- .../SceneViewportOverlaySpriteResources.h | 22 +- .../src/Viewport/SceneViewportResourcePaths.h | 26 +- editor/src/panels/ViewportPanelContent.h | 13 +- engine/CMakeLists.txt | 4 +- .../builtin/shaders/object-id-outline.shader | 9 +- engine/include/XCEngine/Core/Math/Vector4.h | 1 + engine/include/XCEngine/Input/InputTypes.h | 2 +- .../XCEngine/RHI/D3D12/D3D12ResourceView.h | 1 + .../XCEngine/RHI/OpenGL/OpenGLPipelineState.h | 12 +- .../XCEngine/RHI/Vulkan/VulkanCommandList.h | 1 + .../Rendering/Builtin/BuiltinPassTypes.h | 18 + .../Materials/RenderMaterialStateUtils.h | 73 +- .../XCEngine/Resources/Material/Material.h | 11 - .../Resources/Material/MaterialRenderState.h | 59 +- .../include/XCEngine/Threading/TaskSystem.h | 5 +- engine/include/XCEngine/UI/DrawData.h | 68 ++ .../XCEngine/UI/Widgets/UIScrollModel.h | 32 + engine/src/Audio/WindowsAudioBackend.cpp | 43 +- engine/src/Components/MeshFilterComponent.cpp | 9 +- .../src/Components/MeshRendererComponent.cpp | 22 +- engine/src/RHI/D3D12/D3D12PipelineState.cpp | 69 ++ engine/src/RHI/OpenGL/OpenGLCommandList.cpp | 36 +- engine/src/RHI/OpenGL/OpenGLPipelineState.cpp | 55 +- engine/src/RHI/Vulkan/VulkanCommandList.cpp | 12 +- engine/src/RHI/Vulkan/VulkanPipelineState.cpp | 43 +- .../BuiltinColorScalePostProcessPass.cpp | 17 +- .../Passes/BuiltinFinalColorPass.cpp | 17 +- .../Passes/BuiltinInfiniteGridPass.cpp | 54 +- .../Passes/BuiltinObjectIdOutlinePass.cpp | 56 +- .../Passes/BuiltinObjectIdPassResources.cpp | 17 +- .../Pipelines/BuiltinForwardPipeline.cpp | 17 +- engine/src/Resources/Material/Material.cpp | 20 - .../Shader/Internal/ShaderAuthoringInternal.h | 15 + .../Shader/Internal/ShaderManifestLoader.cpp | 518 ------------ .../Shader/Internal/ShaderManifestLoader.h | 24 - .../src/UI/Runtime/UIScreenDocumentHost.cpp | 32 +- engine/src/UI/Widgets/UIScrollModel.cpp | 67 ++ project/.xceditor/imgui_layout.ini | 53 +- project/Assets.meta | 2 +- project/Assets/Materials.meta | 2 +- project/Assets/Models.meta | 2 +- project/Assets/Models/backpack.meta | 2 +- project/Assets/Models/backpack/ao.jpg.meta | 2 +- .../Assets/Models/backpack/backpack.mtl.meta | 2 +- .../Assets/Models/backpack/backpack.obj.meta | 2 +- .../Assets/Models/backpack/diffuse.jpg.meta | 2 +- .../Assets/Models/backpack/normal.png.meta | 2 +- .../Assets/Models/backpack/roughness.jpg.meta | 2 +- .../backpack/source_attribution.txt.meta | 2 +- .../Assets/Models/backpack/specular.jpg.meta | 2 +- project/Assets/New Material.mat | 2 +- project/Assets/New Material.mat.meta | 2 +- project/Assets/Scenes.meta | 2 +- project/Assets/Scenes/Backpack.xc | 56 +- project/Assets/Scenes/Backpack.xc.meta | 2 +- project/Assets/Scenes/Main.xc | 139 +++- project/Assets/Scenes/Main.xc.meta | 2 +- project/Assets/Scenes/NewFolder.meta | 2 +- project/Assets/Scripts.meta | 2 +- .../Assets/Scripts/ProjectScriptProbe.cs.meta | 2 +- project/Assets/Scripts/Textures.meta | 2 +- .../Assets/Scripts/Textures/Grass.png.meta | 2 +- .../Assets/Scripts/Textures/NewFolder.meta | 2 +- .../Scripts/Textures/PlayerController.cs.meta | 2 +- .../Assets/Scripts/Textures/Stone.png.meta | 2 +- project/Assets/Scripts/TickLogProbe.cs.meta | 2 +- scripts/Run-RendererPhaseRegression.ps1 | 156 ---- .../test_mesh_render_components.cpp | 66 +- tests/Core/Asset/test_resource_handle.cpp | 12 + tests/Core/UI/test_layout_engine.cpp | 130 --- tests/Core/UI/test_ui_core.cpp | 240 ------ .../test_ui_editor_collection_primitives.cpp | 84 -- tests/Core/UI/test_ui_editor_panel_chrome.cpp | 126 --- tests/Core/UI/test_ui_expansion_model.cpp | 45 - .../UI/test_ui_flat_hierarchy_helpers.cpp | 145 ---- tests/Core/UI/test_ui_input_dispatcher.cpp | 110 --- .../UI/test_ui_keyboard_navigation_model.cpp | 101 --- tests/Core/UI/test_ui_property_edit_model.cpp | 80 -- tests/Core/UI/test_ui_runtime.cpp | 75 ++ tests/Core/UI/test_ui_selection_model.cpp | 42 - tests/Core/UI/test_ui_text_editing.cpp | 59 -- .../Core/UI/test_ui_text_input_controller.cpp | 287 ------- .../RHI/OpenGL/unit/test_backend_specific.cpp | 124 +++ tests/RHI/OpenGL/unit/test_command_list.cpp | 28 + tests/RHI/OpenGL/unit/test_pipeline_state.cpp | 86 ++ tests/Resources/Material/test_material.cpp | 40 +- tests/Resources/Mesh/CMakeLists.txt | 1 + .../Mesh/test_builtin_primitive_mesh.cpp | 107 +++ tests/Resources/Shader/test_shader_loader.cpp | 9 + tests/Scene/test_scene_runtime.cpp | 104 +++ tests/UI/Core/integration/CMakeLists.txt | 2 + .../UI/Core/integration/render/CMakeLists.txt | 6 + .../draw_primitives_basic/CMakeLists.txt | 27 + .../render/draw_primitives_basic/main.cpp | 465 +++++++++++ .../integration/shared/src/NativeRenderer.cpp | 78 +- tests/UI/Core/unit/CMakeLists.txt | 2 + tests/UI/Core/unit/test_ui_draw_data.cpp | 82 ++ tests/UI/Core/unit/test_ui_scroll_model.cpp | 53 ++ tests/UI/Editor/integration/CMakeLists.txt | 5 + .../Editor/integration/shell/CMakeLists.txt | 3 + .../shell/color_field_basic/CMakeLists.txt | 31 + .../shell/color_field_basic/captures/.gitkeep | 1 + .../history/20260408_160549_1_startup.png | Bin 0 -> 117580 bytes .../color_field_basic/captures/latest.png | Bin 0 -> 117580 bytes .../shell/color_field_basic/main.cpp | 778 ++++++++++++++++++ .../shell/dock_host_basic/captures/.gitkeep | 1 - .../shell/text_field_basic/captures/.gitkeep | 1 + .../vector2_field_basic/captures/.gitkeep | 1 + .../vector3_field_basic/captures/.gitkeep | 1 + tests/UI/Editor/unit/CMakeLists.txt | 2 + .../unit/test_ui_editor_color_field.cpp | 140 ++++ ...test_ui_editor_color_field_interaction.cpp | 138 ++++ tests/UI/Editor/unit/test_ui_editor_theme.cpp | 53 ++ tests/core/Asset/test_resource_manager.cpp | 38 + tests/editor/test_action_routing.cpp | 193 +++-- ...est_scene_viewport_interaction_actions.cpp | 16 +- .../test_scene_viewport_interaction_frame.cpp | 8 +- .../editor/test_scene_viewport_navigation.cpp | 24 +- .../test_scene_viewport_overlay_providers.cpp | 97 ++- .../test_scene_viewport_overlay_renderer.cpp | 23 + ...cene_viewport_overlay_sprite_resources.cpp | 36 +- .../test_scene_viewport_shader_paths.cpp | 34 +- ...e_viewport_transform_gizmo_coordinator.cpp | 8 +- .../editor/test_viewport_object_id_picker.cpp | 12 +- .../test_viewport_render_flow_utils.cpp | 35 + tests/editor/test_viewport_render_targets.cpp | 12 +- 153 files changed, 4454 insertions(+), 2990 deletions(-) delete mode 100644 Assets.meta delete mode 100644 Assets/XCUI.meta delete mode 100644 CMakePresets.json rename editor/resources/Icons/{main_light_gizmo.png => directional_light_gizmo.png} (100%) create mode 100644 editor/resources/Icons/point_light_gizmo.png create mode 100644 editor/resources/Icons/spot_light_gizmo.png delete mode 100644 editor/resources/shaders/scene-viewport/infinite-grid/infinite-grid.ps.hlsl delete mode 100644 editor/resources/shaders/scene-viewport/infinite-grid/infinite-grid.vs.hlsl delete mode 100644 editor/resources/shaders/scene-viewport/object-id-outline/object-id-outline.ps.hlsl delete mode 100644 editor/resources/shaders/scene-viewport/object-id-outline/object-id-outline.shader delete mode 100644 editor/resources/shaders/scene-viewport/object-id-outline/object-id-outline.vs.hlsl create mode 100644 engine/include/XCEngine/UI/Widgets/UIScrollModel.h delete mode 100644 engine/src/Resources/Shader/Internal/ShaderManifestLoader.cpp delete mode 100644 engine/src/Resources/Shader/Internal/ShaderManifestLoader.h create mode 100644 engine/src/UI/Widgets/UIScrollModel.cpp delete mode 100644 scripts/Run-RendererPhaseRegression.ps1 delete mode 100644 tests/Core/UI/test_layout_engine.cpp delete mode 100644 tests/Core/UI/test_ui_core.cpp delete mode 100644 tests/Core/UI/test_ui_editor_collection_primitives.cpp delete mode 100644 tests/Core/UI/test_ui_editor_panel_chrome.cpp delete mode 100644 tests/Core/UI/test_ui_expansion_model.cpp delete mode 100644 tests/Core/UI/test_ui_flat_hierarchy_helpers.cpp delete mode 100644 tests/Core/UI/test_ui_input_dispatcher.cpp delete mode 100644 tests/Core/UI/test_ui_keyboard_navigation_model.cpp delete mode 100644 tests/Core/UI/test_ui_property_edit_model.cpp delete mode 100644 tests/Core/UI/test_ui_selection_model.cpp delete mode 100644 tests/Core/UI/test_ui_text_editing.cpp delete mode 100644 tests/Core/UI/test_ui_text_input_controller.cpp create mode 100644 tests/Resources/Mesh/test_builtin_primitive_mesh.cpp create mode 100644 tests/UI/Core/integration/render/CMakeLists.txt create mode 100644 tests/UI/Core/integration/render/draw_primitives_basic/CMakeLists.txt create mode 100644 tests/UI/Core/integration/render/draw_primitives_basic/main.cpp create mode 100644 tests/UI/Core/unit/test_ui_draw_data.cpp create mode 100644 tests/UI/Core/unit/test_ui_scroll_model.cpp create mode 100644 tests/UI/Editor/integration/shell/color_field_basic/CMakeLists.txt create mode 100644 tests/UI/Editor/integration/shell/color_field_basic/captures/.gitkeep create mode 100644 tests/UI/Editor/integration/shell/color_field_basic/captures/history/20260408_160549_1_startup.png create mode 100644 tests/UI/Editor/integration/shell/color_field_basic/captures/latest.png create mode 100644 tests/UI/Editor/integration/shell/color_field_basic/main.cpp create mode 100644 tests/UI/Editor/integration/shell/text_field_basic/captures/.gitkeep create mode 100644 tests/UI/Editor/integration/shell/vector2_field_basic/captures/.gitkeep create mode 100644 tests/UI/Editor/integration/shell/vector3_field_basic/captures/.gitkeep create mode 100644 tests/UI/Editor/unit/test_ui_editor_color_field.cpp create mode 100644 tests/UI/Editor/unit/test_ui_editor_color_field_interaction.cpp diff --git a/Assets.meta b/Assets.meta deleted file mode 100644 index 8357e494..00000000 --- a/Assets.meta +++ /dev/null @@ -1,5 +0,0 @@ -fileFormatVersion: 1 -guid: 60a2f7fbbea9b0ef86cefe4d4ea75578 -folderAsset: true -importer: FolderImporter -importerVersion: 5 diff --git a/Assets/XCUI.meta b/Assets/XCUI.meta deleted file mode 100644 index 264e260a..00000000 --- a/Assets/XCUI.meta +++ /dev/null @@ -1,5 +0,0 @@ -fileFormatVersion: 1 -guid: 6dd3f8fecfde5a3f1d52cb3a9e18ed53 -folderAsset: true -importer: FolderImporter -importerVersion: 5 diff --git a/CMakeLists.txt b/CMakeLists.txt index 55f9a955..9b81e3d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) enable_testing() option(XCENGINE_ENABLE_MONO_SCRIPTING "Build the Mono-based C# scripting runtime" ON) -option(XCENGINE_BUILD_NEW_EDITOR "Build the XCUI new_editor shell app" ON) +option(XCENGINE_BUILD_XCUI_EDITOR_APP "Build the XCUI editor shell app" ON) set( XCENGINE_MONO_ROOT_DIR "${CMAKE_SOURCE_DIR}/参考/Fermion/Fermion/external/mono" diff --git a/CMakePresets.json b/CMakePresets.json deleted file mode 100644 index 5e661dc0..00000000 --- a/CMakePresets.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "version": 3, - "cmakeMinimumRequired": { - "major": 3, - "minor": 21, - "patch": 0 - }, - "configurePresets": [ - { - "name": "vs2022-x64", - "displayName": "Visual Studio 2022 x64", - "generator": "Visual Studio 17 2022", - "binaryDir": "${sourceDir}/build" - } - ], - "buildPresets": [ - { - "name": "debug", - "displayName": "Build Debug", - "configurePreset": "vs2022-x64", - "configuration": "Debug" - } - ], - "testPresets": [ - { - "name": "debug", - "displayName": "Test Debug", - "configurePreset": "vs2022-x64", - "configuration": "Debug", - "output": { - "outputOnFailure": true - } - } - ] -} diff --git a/MVS/Music fluctuations/source/CMakeLists.txt b/MVS/Music fluctuations/source/CMakeLists.txt index d6d6b089..bdab6a23 100644 --- a/MVS/Music fluctuations/source/CMakeLists.txt +++ b/MVS/Music fluctuations/source/CMakeLists.txt @@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.12) project(Doom) #本工程支持的C++版本 -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) file(GLOB resources "../res") file(COPY ${resources} DESTINATION ${CMAKE_BINARY_DIR}) diff --git a/MVS/OpenGL/CMakeLists.txt b/MVS/OpenGL/CMakeLists.txt index f2411225..53d6f8b5 100644 --- a/MVS/OpenGL/CMakeLists.txt +++ b/MVS/OpenGL/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.12) project("XCRender") -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) include_directories(./package/include/) include_directories(./package/glm/) diff --git a/MVS/VolumeRenderer/CMakeLists.txt b/MVS/VolumeRenderer/CMakeLists.txt index 4d099822..3dce23ff 100644 --- a/MVS/VolumeRenderer/CMakeLists.txt +++ b/MVS/VolumeRenderer/CMakeLists.txt @@ -7,7 +7,7 @@ project(XCVolumeRenderer VERSION 1.0 LANGUAGES CXX) set(VCPKG_ROOT "D:/vcpkg") set(CMAKE_TOOLCHAIN_FILE "${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE STRING "") -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) diff --git a/MVS/ui/CMakeLists.txt b/MVS/ui/CMakeLists.txt index 4fdcecdb..b78753e6 100644 --- a/MVS/ui/CMakeLists.txt +++ b/MVS/ui/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.15) project(XCVolumeRendererUI2 VERSION 1.0 LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_definitions(-DUNICODE -D_UNICODE) @@ -63,4 +63,4 @@ target_link_libraries(${PROJECT_NAME} PRIVATE set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin" -) \ No newline at end of file +) diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 2a056797..6bf1833c 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -78,6 +78,7 @@ add_executable(${PROJECT_NAME} WIN32 src/panels/MenuBar.cpp src/panels/HierarchyPanel.cpp src/panels/SceneViewPanel.cpp + src/panels/MaterialInspectorMaterialStateIO.cpp src/Viewport/SceneViewportPicker.cpp src/Viewport/SceneViewportMoveGizmo.cpp src/Viewport/SceneViewportRotateGizmo.cpp diff --git a/editor/resources/Icons/main_light_gizmo.png b/editor/resources/Icons/directional_light_gizmo.png similarity index 100% rename from editor/resources/Icons/main_light_gizmo.png rename to editor/resources/Icons/directional_light_gizmo.png diff --git a/editor/resources/Icons/point_light_gizmo.png b/editor/resources/Icons/point_light_gizmo.png new file mode 100644 index 0000000000000000000000000000000000000000..8f33f2961b172fcc08cbbd12e95168c3c2774727 GIT binary patch literal 875 zcmV-x1C;!UP)kdg0009rNklq}E{7=F%mJF-S$hB2gU43=dWS_z35I0HYJS#*Jlg0fc7g8zbo^r`3fY5TqJ^Zwq;^Bx9fu%253?0bM1 z{ta77$K)}2OR1R+Yj!CW3I#%eRA^?yn_VUuA|T29^9%k4*#yF&B|BYzpk#Nt4lRL@ z^Uk|0^7hN?R?jrW_O*(lDBIUgySIA2ypFu}F3b6X0bUX3l^$vL)n@*U$3dW2JY(yt zJ<>j}R1pUZDmQC=vm7J}^<`cmI?kRxds=j4qKEp3d*o`vGZ}1KViAK9?7Xbj~ zpbTZ00|2#)M!SgygAHEf8@DlX`#MV}kAV~`Q*e1*Mg8oB48DGRes{+$#j23rW<0M>5&#UtUpMhZ&2K6Y6+zbl@| za#u+uEuJ{PD|a-$vB)`JWzs!OM6dOSQC=?!&}P_?D3sU3KNp)_qLfOzl014dh>h-% zK$HBbpIkC6!2ihN8}cli`eQ?Au77$<2S26kDE!(xlK3wu9GlCZ+*= z*gh$7`fr9+Y~vE&DL1q@Tv{fDi5FcMA0Be8_?=*Q2;)1hlz$X$>1Z5%?td=>fPlQI zV+9NTsCOdzYY``+NB4sV3u~6OVAO`C-idU(aOb^>W5u=FH-jdY}G3F$fMB9HjnwfV10F=KBB>Ql_juj zJund^jWp%QhP@CgO9tN&h5CGT#uQv@U0SeBdtd{5u`Q7#mDG6Rf)5OAO?z~)*BKe(H z?f-oEyVm_w*LzCMtTH5GNY75>){UXzSzMq$cMTYbS);)>Zkdg0005bNkl>%hOsH3$*b#fJ?jt;tsbk;8VALuG5SmNRXbP^SGQ4klc z5sYF%TiXgrRVqz;94wNSBsQ*kGUeoaznlA=b1(28e;_JQVO_wPfAYBIN4omKU7v<_ zrAOOKdp(iOtVTqX)gn7X9=rPL<0T@>kwzNjh)$MKV@p5S{VWraR(L3J=j$=9WX9>v zHb*_^`;{u|+%2{Mnow*nA4#OzEW2Pc|Psmo-Lglg_Yaqx3KX>DUn_jiIgCar~h;LDM-pg$%NU5Kaxn3FjH zR9%QfF)8S`G+i&3qvHVx1FoC!O?bcsVLTv5%Rzk0N2kyTOnTqaZ|O-dfX30OpAmeq z9GDnZ6;+uS21*ddUQGAKuk>;t;8zt@3HS>I1w{O6y4P?LTiP9NIXbhjEh1&OLTX!_ zS&*X+cWb-hAT6HX&)&Tp9hk@gAhorjBs&_`&kX?|k1MOlBV*a6= 1.0) { - discard; - } - } - - const float radialFade = - 1.0 - smoothstep(fadeDistance * 0.3, fadeDistance, length(worldPosition - cameraPosition)); - const float normalFade = smoothstep(0.0, 0.15, abs(rayDirection.y)); - const float fadeFactor = radialFade * normalFade; - if (fadeFactor < 1e-3) { - discard; - } - - const float2 worldPos2D = worldPosition.xz; - const GridLayer baseLayer = SampleGridLayer(worldPos2D, scale); - const GridLayer nextLayer = SampleGridLayer(worldPos2D, scale * 10.0); - const float minorGridIntensity = lerp(baseLayer.minor, nextLayer.minor, transitionBlend); - const float majorGridIntensity = lerp(baseLayer.major, nextLayer.major, transitionBlend); - float3 finalColor = float3(0.56, 0.56, 0.56); - float finalAlpha = max( - 0.13 * minorGridIntensity * fadeFactor, - 0.28 * majorGridIntensity * fadeFactor); - - const float2 worldDeriv = max(fwidth(worldPos2D), float2(1e-6, 1e-6)); - const float xAxisAlpha = AxisLineAA(worldPos2D.y, worldDeriv.y) * fadeFactor; - const float zAxisAlpha = AxisLineAA(worldPos2D.x, worldDeriv.x) * fadeFactor; - - const float axisAlpha = max(xAxisAlpha, zAxisAlpha); - finalAlpha = max(finalAlpha, 0.34 * saturate(axisAlpha)); - - if (finalAlpha < 1e-3) { - discard; - } - - PSOutput output; - output.color = float4(finalColor, finalAlpha); - output.depth = depth; - return output; -} diff --git a/editor/resources/shaders/scene-viewport/infinite-grid/infinite-grid.shader b/editor/resources/shaders/scene-viewport/infinite-grid/infinite-grid.shader index 967c9689..03eb1872 100644 --- a/editor/resources/shaders/scene-viewport/infinite-grid/infinite-grid.shader +++ b/editor/resources/shaders/scene-viewport/infinite-grid/infinite-grid.shader @@ -1,29 +1,177 @@ +Shader "Scene View Infinite Grid" { - "name": "Scene View Infinite Grid", - "passes": [ + HLSLINCLUDE + // XC_EDITOR_SCENE_VIEW_INFINITE_GRID_D3D12_SHARED + cbuffer GridConstants { - "name": "InfiniteGrid", - "tags": { - "LightMode": "InfiniteGrid" - }, - "variants": [ - { - "stage": "Vertex", - "backend": "D3D12", - "language": "HLSL", - "source": "infinite-grid.vs.hlsl", - "entryPoint": "MainVS", - "profile": "vs_5_0" - }, - { - "stage": "Fragment", - "backend": "D3D12", - "language": "HLSL", - "source": "infinite-grid.ps.hlsl", - "entryPoint": "MainPS", - "profile": "ps_5_0" - } - ] + float4x4 gViewProjectionMatrix; + float4 gCameraPositionAndScale; + float4 gCameraRightAndFade; + float4 gCameraUpAndTanHalfFov; + float4 gCameraForwardAndAspect; + float4 gViewportNearFar; + float4 gGridTransition; + }; + + struct VSOutput + { + float4 position : SV_POSITION; + }; + + VSOutput MainVS(uint vertexId : SV_VertexID) + { + // XC_EDITOR_SCENE_VIEW_INFINITE_GRID_D3D12_VS + static const float2 positions[3] = { + float2(-1.0, -1.0), + float2(-1.0, 3.0), + float2( 3.0, -1.0) + }; + + VSOutput output; + output.position = float4(positions[vertexId], 0.0, 1.0); + return output; + } + + float PristineGridLine(float2 uv) + { + float2 deriv = max(fwidth(uv), float2(1e-6, 1e-6)); + float2 uvMod = frac(uv); + float2 uvDist = min(uvMod, 1.0 - uvMod); + float2 distInPixels = uvDist / deriv; + float2 lineAlpha = 1.0 - smoothstep(0.0, 1.0, distInPixels); + float density = max(deriv.x, deriv.y); + float densityFade = 1.0 - smoothstep(0.5, 1.0, density); + return max(lineAlpha.x, lineAlpha.y) * densityFade; + } + + float AxisLineAA(float coord, float deriv) + { + float distInPixels = abs(coord) / max(deriv, 1e-6); + return 1.0 - smoothstep(0.0, 1.5, distInPixels); + } + + struct GridLayer + { + float minor; + float major; + }; + + GridLayer SampleGridLayer(float2 worldPos2D, float baseScale) + { + GridLayer layer; + const float2 gridCoord1 = worldPos2D / baseScale; + const float2 gridCoord10 = worldPos2D / (baseScale * 10.0); + const float grid1 = PristineGridLine(gridCoord1); + const float grid10 = PristineGridLine(gridCoord10); + const float2 deriv1 = fwidth(gridCoord1); + const float lodFactor = smoothstep(0.3, 0.6, max(deriv1.x, deriv1.y)); + + layer.major = max(grid10, grid1 * 0.35); + layer.minor = grid1 * (1.0 - lodFactor); + return layer; + } + + struct PSOutput + { + float4 color : SV_TARGET0; + float depth : SV_Depth; + }; + + PSOutput MainPS(VSOutput input) + { + // XC_EDITOR_SCENE_VIEW_INFINITE_GRID_D3D12_PS + const float2 viewportSize = max(gViewportNearFar.xy, float2(1.0, 1.0)); + const float scale = max(gCameraPositionAndScale.w, 1e-4); + const float fadeDistance = max(gCameraRightAndFade.w, scale * 10.0); + const float tanHalfFov = max(gCameraUpAndTanHalfFov.w, 1e-4); + const float aspect = max(gCameraForwardAndAspect.w, 1e-4); + const float transitionBlend = saturate(gGridTransition.x); + const float nearClip = gViewportNearFar.z; + const float sceneFarClip = gViewportNearFar.w; + + const float2 ndc = float2( + (input.position.x / viewportSize.x) * 2.0 - 1.0, + 1.0 - (input.position.y / viewportSize.y) * 2.0); + + const float3 cameraPosition = gCameraPositionAndScale.xyz; + const float3 rayDirection = normalize( + gCameraForwardAndAspect.xyz + + ndc.x * aspect * tanHalfFov * gCameraRightAndFade.xyz + + ndc.y * tanHalfFov * gCameraUpAndTanHalfFov.xyz); + + if (abs(rayDirection.y) < 1e-5) { + discard; + } + + const float t = -cameraPosition.y / rayDirection.y; + if (t <= nearClip) { + discard; + } + + const float3 worldPosition = cameraPosition + rayDirection * t; + float depth = 0.999999; + if (t < sceneFarClip) { + const float4 clipPosition = mul(gViewProjectionMatrix, float4(worldPosition, 1.0)); + if (clipPosition.w <= 1e-6) { + discard; + } + + depth = clipPosition.z / clipPosition.w; + if (depth <= 0.0 || depth >= 1.0) { + discard; + } + } + + const float radialFade = + 1.0 - smoothstep(fadeDistance * 0.3, fadeDistance, length(worldPosition - cameraPosition)); + const float normalFade = smoothstep(0.0, 0.15, abs(rayDirection.y)); + const float fadeFactor = radialFade * normalFade; + if (fadeFactor < 1e-3) { + discard; + } + + const float2 worldPos2D = worldPosition.xz; + const GridLayer baseLayer = SampleGridLayer(worldPos2D, scale); + const GridLayer nextLayer = SampleGridLayer(worldPos2D, scale * 10.0); + const float minorGridIntensity = lerp(baseLayer.minor, nextLayer.minor, transitionBlend); + const float majorGridIntensity = lerp(baseLayer.major, nextLayer.major, transitionBlend); + float3 finalColor = float3(0.56, 0.56, 0.56); + float finalAlpha = max( + 0.13 * minorGridIntensity * fadeFactor, + 0.28 * majorGridIntensity * fadeFactor); + + const float2 worldDeriv = max(fwidth(worldPos2D), float2(1e-6, 1e-6)); + const float xAxisAlpha = AxisLineAA(worldPos2D.y, worldDeriv.y) * fadeFactor; + const float zAxisAlpha = AxisLineAA(worldPos2D.x, worldDeriv.x) * fadeFactor; + + const float axisAlpha = max(xAxisAlpha, zAxisAlpha); + finalAlpha = max(finalAlpha, 0.34 * saturate(axisAlpha)); + + if (finalAlpha < 1e-3) { + discard; + } + + PSOutput output; + output.color = float4(finalColor, finalAlpha); + output.depth = depth; + return output; + } + ENDHLSL + SubShader + { + Pass + { + Name "InfiniteGrid" + Tags { "LightMode" = "InfiniteGrid" } + Cull Off + ZWrite Off + ZTest LEqual + Blend SrcAlpha OneMinusSrcAlpha, One OneMinusSrcAlpha + HLSLPROGRAM + #pragma target 4.5 + #pragma vertex MainVS + #pragma fragment MainPS + ENDHLSL + } } - ] } diff --git a/editor/resources/shaders/scene-viewport/infinite-grid/infinite-grid.vs.hlsl b/editor/resources/shaders/scene-viewport/infinite-grid/infinite-grid.vs.hlsl deleted file mode 100644 index b2297ad5..00000000 --- a/editor/resources/shaders/scene-viewport/infinite-grid/infinite-grid.vs.hlsl +++ /dev/null @@ -1,26 +0,0 @@ -// XC_EDITOR_SCENE_VIEW_INFINITE_GRID_D3D12_VS -cbuffer GridConstants : register(b0) { - float4x4 gViewProjectionMatrix; - float4 gCameraPositionAndScale; - float4 gCameraRightAndFade; - float4 gCameraUpAndTanHalfFov; - float4 gCameraForwardAndAspect; - float4 gViewportNearFar; - float4 gGridTransition; -}; - -struct VSOutput { - float4 position : SV_POSITION; -}; - -VSOutput MainVS(uint vertexId : SV_VertexID) { - static const float2 positions[3] = { - float2(-1.0, -1.0), - float2(-1.0, 3.0), - float2( 3.0, -1.0) - }; - - VSOutput output; - output.position = float4(positions[vertexId], 0.0, 1.0); - return output; -} diff --git a/editor/resources/shaders/scene-viewport/object-id-outline/object-id-outline.ps.hlsl b/editor/resources/shaders/scene-viewport/object-id-outline/object-id-outline.ps.hlsl deleted file mode 100644 index d1152563..00000000 --- a/editor/resources/shaders/scene-viewport/object-id-outline/object-id-outline.ps.hlsl +++ /dev/null @@ -1,89 +0,0 @@ -// XC_EDITOR_SCENE_VIEW_OBJECT_ID_OUTLINE_D3D12_PS -cbuffer OutlineConstants : register(b0) { - float4 gViewportSizeAndTexelSize; - float4 gOutlineColor; - float4 gSelectedInfo; - float4 gSelectedObjectColors[256]; -}; - -Texture2D gObjectIdTexture : register(t0); - -struct VSOutput { - float4 position : SV_POSITION; -}; - -int2 ClampPixelCoord(int2 pixelCoord) { - const int2 maxCoord = int2( - max((int)gViewportSizeAndTexelSize.x - 1, 0), - max((int)gViewportSizeAndTexelSize.y - 1, 0)); - return clamp(pixelCoord, int2(0, 0), maxCoord); -} - -float4 LoadObjectId(int2 pixelCoord) { - return gObjectIdTexture.Load(int3(ClampPixelCoord(pixelCoord), 0)); -} - -bool IsSelectedObject(float4 objectIdColor) { - if (objectIdColor.a <= 0.0) { - return false; - } - - const int selectedCount = min((int)gSelectedInfo.x, 256); - [loop] - for (int i = 0; i < selectedCount; ++i) { - const float4 selectedColor = gSelectedObjectColors[i]; - if (all(abs(objectIdColor - selectedColor) <= float4( - 0.0025, - 0.0025, - 0.0025, - 0.0025))) { - return true; - } - } - - return false; -} - -float4 MainPS(VSOutput input) : SV_TARGET { - const int2 pixelCoord = int2(input.position.xy); - const bool debugSelectionMask = gSelectedInfo.y > 0.5; - const bool centerSelected = IsSelectedObject(LoadObjectId(pixelCoord)); - - if (debugSelectionMask) { - return centerSelected ? float4(1.0, 1.0, 1.0, 1.0) : float4(0.0, 0.0, 0.0, 1.0); - } - - if (centerSelected) { - discard; - } - - const int outlineWidth = max((int)gSelectedInfo.z, 1); - float outline = 0.0; - [loop] - for (int y = -2; y <= 2; ++y) { - [loop] - for (int x = -2; x <= 2; ++x) { - if (x == 0 && y == 0) { - continue; - } - - const float distancePixels = length(float2((float)x, (float)y)); - if (distancePixels > outlineWidth) { - continue; - } - - if (!IsSelectedObject(LoadObjectId(pixelCoord + int2(x, y)))) { - continue; - } - - const float weight = saturate(1.0 - ((distancePixels - 1.0) / max((float)outlineWidth, 1.0))); - outline = max(outline, weight); - } - } - - if (outline <= 0.001) { - discard; - } - - return float4(gOutlineColor.rgb, gOutlineColor.a * outline); -} diff --git a/editor/resources/shaders/scene-viewport/object-id-outline/object-id-outline.shader b/editor/resources/shaders/scene-viewport/object-id-outline/object-id-outline.shader deleted file mode 100644 index 207a9648..00000000 --- a/editor/resources/shaders/scene-viewport/object-id-outline/object-id-outline.shader +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "Scene View Object Id Outline", - "passes": [ - { - "name": "ObjectIdOutline", - "tags": { - "LightMode": "ObjectIdOutline" - }, - "variants": [ - { - "stage": "Vertex", - "backend": "D3D12", - "language": "HLSL", - "source": "object-id-outline.vs.hlsl", - "entryPoint": "MainVS", - "profile": "vs_5_0" - }, - { - "stage": "Fragment", - "backend": "D3D12", - "language": "HLSL", - "source": "object-id-outline.ps.hlsl", - "entryPoint": "MainPS", - "profile": "ps_5_0" - } - ] - } - ] -} diff --git a/editor/resources/shaders/scene-viewport/object-id-outline/object-id-outline.vs.hlsl b/editor/resources/shaders/scene-viewport/object-id-outline/object-id-outline.vs.hlsl deleted file mode 100644 index 0a36bd32..00000000 --- a/editor/resources/shaders/scene-viewport/object-id-outline/object-id-outline.vs.hlsl +++ /dev/null @@ -1,25 +0,0 @@ -// XC_EDITOR_SCENE_VIEW_OBJECT_ID_OUTLINE_D3D12_VS -cbuffer OutlineConstants : register(b0) { - float4 gViewportSizeAndTexelSize; - float4 gOutlineColor; - float4 gSelectedInfo; - float4 gSelectedObjectColors[256]; -}; - -Texture2D gObjectIdTexture : register(t0); - -struct VSOutput { - float4 position : SV_POSITION; -}; - -VSOutput MainVS(uint vertexId : SV_VertexID) { - static const float2 positions[3] = { - float2(-1.0, -1.0), - float2(-1.0, 3.0), - float2( 3.0, -1.0) - }; - - VSOutput output; - output.position = float4(positions[vertexId], 0.0, 1.0); - return output; -} diff --git a/editor/src/Commands/EntityCommands.h b/editor/src/Commands/EntityCommands.h index 179b648d..94c9ffd4 100644 --- a/editor/src/Commands/EntityCommands.h +++ b/editor/src/Commands/EntityCommands.h @@ -72,17 +72,60 @@ inline ::XCEngine::Components::GameObject* CreateLightEntity( IEditorContext& context, ::XCEngine::Components::GameObject* parent = nullptr, const std::string& commandLabel = "Create Light", - const std::string& entityName = "Light") { + const std::string& entityName = "Light", + ::XCEngine::Components::LightType lightType = ::XCEngine::Components::LightType::Directional) { return CreateEntity( context, commandLabel, entityName, parent, - [](::XCEngine::Components::GameObject& entity, ISceneManager&) { - entity.AddComponent<::XCEngine::Components::LightComponent>(); + [lightType](::XCEngine::Components::GameObject& entity, ISceneManager&) { + auto* light = entity.AddComponent<::XCEngine::Components::LightComponent>(); + if (light != nullptr) { + light->SetLightType(lightType); + } }); } +inline ::XCEngine::Components::GameObject* CreateDirectionalLightEntity( + IEditorContext& context, + ::XCEngine::Components::GameObject* parent = nullptr, + const std::string& commandLabel = "Create Directional Light", + const std::string& entityName = "Directional Light") { + return CreateLightEntity( + context, + parent, + commandLabel, + entityName, + ::XCEngine::Components::LightType::Directional); +} + +inline ::XCEngine::Components::GameObject* CreatePointLightEntity( + IEditorContext& context, + ::XCEngine::Components::GameObject* parent = nullptr, + const std::string& commandLabel = "Create Point Light", + const std::string& entityName = "Point Light") { + return CreateLightEntity( + context, + parent, + commandLabel, + entityName, + ::XCEngine::Components::LightType::Point); +} + +inline ::XCEngine::Components::GameObject* CreateSpotLightEntity( + IEditorContext& context, + ::XCEngine::Components::GameObject* parent = nullptr, + const std::string& commandLabel = "Create Spot Light", + const std::string& entityName = "Spot Light") { + return CreateLightEntity( + context, + parent, + commandLabel, + entityName, + ::XCEngine::Components::LightType::Spot); +} + inline ::XCEngine::Components::GameObject* CreatePrimitiveEntity( IEditorContext& context, ::XCEngine::Resources::BuiltinPrimitiveType primitiveType, diff --git a/editor/src/UI/BuiltInIcons.cpp b/editor/src/UI/BuiltInIcons.cpp index e20d8c74..6f7f00bf 100644 --- a/editor/src/UI/BuiltInIcons.cpp +++ b/editor/src/UI/BuiltInIcons.cpp @@ -99,7 +99,9 @@ struct BuiltInIconState { BuiltInTexture gameObject; BuiltInTexture scene; BuiltInTexture cameraGizmo; - BuiltInTexture mainLightGizmo; + BuiltInTexture directionalLightGizmo; + BuiltInTexture pointLightGizmo; + BuiltInTexture spotLightGizmo; struct CachedAssetPreview { BuiltInTexture texture; std::unique_ptr decodedPixels; @@ -157,9 +159,19 @@ std::filesystem::path ResolveCameraGizmoIconPath() { return (exeDir / ".." / ".." / "resources" / "Icons" / "camera_gizmo.png").lexically_normal(); } -std::filesystem::path ResolveMainLightGizmoIconPath() { +std::filesystem::path ResolveDirectionalLightGizmoIconPath() { const std::filesystem::path exeDir(Platform::GetExecutableDirectoryUtf8()); - return (exeDir / ".." / ".." / "resources" / "Icons" / "main_light_gizmo.png").lexically_normal(); + return (exeDir / ".." / ".." / "resources" / "Icons" / "directional_light_gizmo.png").lexically_normal(); +} + +std::filesystem::path ResolvePointLightGizmoIconPath() { + const std::filesystem::path exeDir(Platform::GetExecutableDirectoryUtf8()); + return (exeDir / ".." / ".." / "resources" / "Icons" / "point_light_gizmo.png").lexically_normal(); +} + +std::filesystem::path ResolveSpotLightGizmoIconPath() { + const std::filesystem::path exeDir(Platform::GetExecutableDirectoryUtf8()); + return (exeDir / ".." / ".." / "resources" / "Icons" / "spot_light_gizmo.png").lexically_normal(); } std::string NormalizePathKey(const std::filesystem::path& path) { @@ -975,8 +987,12 @@ BuiltInTexture* ResolveEditorTexture(EditorTextureIconKind kind) { switch (kind) { case EditorTextureIconKind::CameraGizmo: return &g_icons.cameraGizmo; - case EditorTextureIconKind::MainLightGizmo: - return &g_icons.mainLightGizmo; + case EditorTextureIconKind::DirectionalLightGizmo: + return &g_icons.directionalLightGizmo; + case EditorTextureIconKind::PointLightGizmo: + return &g_icons.pointLightGizmo; + case EditorTextureIconKind::SpotLightGizmo: + return &g_icons.spotLightGizmo; default: return nullptr; } @@ -1025,8 +1041,30 @@ void InitializeBuiltInIcons( backend, device, commandQueue, - ResolveMainLightGizmoIconPath(), - g_icons.mainLightGizmo, + ResolveDirectionalLightGizmoIconPath(), + g_icons.directionalLightGizmo, + pendingUpload)) { + g_icons.pendingIconUploads.push_back(std::move(pendingUpload)); + } + + pendingUpload.reset(); + if (LoadTextureFromFile( + backend, + device, + commandQueue, + ResolvePointLightGizmoIconPath(), + g_icons.pointLightGizmo, + pendingUpload)) { + g_icons.pendingIconUploads.push_back(std::move(pendingUpload)); + } + + pendingUpload.reset(); + if (LoadTextureFromFile( + backend, + device, + commandQueue, + ResolveSpotLightGizmoIconPath(), + g_icons.spotLightGizmo, pendingUpload)) { g_icons.pendingIconUploads.push_back(std::move(pendingUpload)); } @@ -1043,7 +1081,9 @@ void ShutdownBuiltInIcons() { ResetTexture(g_icons.gameObject); ResetTexture(g_icons.scene); ResetTexture(g_icons.cameraGizmo); - ResetTexture(g_icons.mainLightGizmo); + ResetTexture(g_icons.directionalLightGizmo); + ResetTexture(g_icons.pointLightGizmo); + ResetTexture(g_icons.spotLightGizmo); g_icons.backend = nullptr; g_icons.device = nullptr; g_icons.commandQueue = nullptr; diff --git a/editor/src/UI/BuiltInIcons.h b/editor/src/UI/BuiltInIcons.h index 4e9a24a5..59e0ad1a 100644 --- a/editor/src/UI/BuiltInIcons.h +++ b/editor/src/UI/BuiltInIcons.h @@ -21,7 +21,10 @@ enum class AssetIconKind { enum class EditorTextureIconKind { CameraGizmo, - MainLightGizmo + DirectionalLightGizmo, + PointLightGizmo, + SpotLightGizmo, + MainLightGizmo = DirectionalLightGizmo }; void InitializeBuiltInIcons( diff --git a/editor/src/UI/Widgets.h b/editor/src/UI/Widgets.h index 25c085f0..3dad9805 100644 --- a/editor/src/UI/Widgets.h +++ b/editor/src/UI/Widgets.h @@ -14,8 +14,17 @@ namespace XCEngine { namespace Editor { namespace UI { +inline bool ShouldTracePopupSubmenuLabel(const char* label) { + if (!label) { + return false; + } + + const std::string text(label); + return text == "Create" || text == "3D Object"; +} + inline void TracePopupSubmenuIfNeeded(const char* label, const std::string& message) { - if (!label || std::string(label) != "Create") { + if (!ShouldTracePopupSubmenuLabel(label)) { return; } @@ -92,7 +101,7 @@ inline bool DrawMenuScope(const char* label, DrawContentFn&& drawContent) { } template -inline bool DrawPopupSubmenuScope(const char* label, DrawContentFn&& drawContent) { +inline bool DrawPopupSubmenuScope(const char* label, DrawContentFn&& drawContent, bool enabled = true) { if (!label || label[0] == '\0') { return false; } @@ -103,18 +112,24 @@ inline bool DrawPopupSubmenuScope(const char* label, DrawContentFn&& drawContent const ImVec2 rowPos = ImGui::GetCursorScreenPos(); const float rowHeight = labelSize.y; const float rowWidth = ImMax(ImGui::GetContentRegionAvail().x, 1.0f); - const bool popupOpen = ImGui::IsPopupOpen(popupId); + const bool popupOpen = enabled && ImGui::IsPopupOpen(popupId); + ImGuiSelectableFlags selectableFlags = ImGuiSelectableFlags_NoAutoClosePopups; + if (!enabled) { + selectableFlags |= ImGuiSelectableFlags_Disabled; + } if (ImGui::Selectable( "##PopupSubmenuRow", popupOpen, - ImGuiSelectableFlags_NoAutoClosePopups, + selectableFlags, ImVec2(rowWidth, rowHeight))) { - TracePopupSubmenuIfNeeded(label, "Hierarchy create submenu selectable clicked -> OpenPopup"); - ImGui::OpenPopup(popupId); + if (enabled) { + TracePopupSubmenuIfNeeded(label, "Hierarchy create submenu selectable clicked -> OpenPopup"); + ImGui::OpenPopup(popupId); + } } - const bool hovered = ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); + const bool hovered = enabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); if (hovered && !popupOpen) { TracePopupSubmenuIfNeeded(label, "Hierarchy create submenu hovered -> OpenPopup"); ImGui::OpenPopup(popupId); @@ -127,9 +142,10 @@ inline bool DrawPopupSubmenuScope(const char* label, DrawContentFn&& drawContent const float parentWindowRight = parentWindowPos.x + parentWindowSize.x; const float itemHeight = itemMax.y - itemMin.y; ImDrawList* drawList = ImGui::GetWindowDrawList(); + const ImU32 textColor = ImGui::GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled); drawList->AddText( ImVec2(itemMin.x + ImGui::GetStyle().FramePadding.x, itemMin.y + (itemHeight - labelSize.y) * 0.5f), - ImGui::GetColorU32(ImGuiCol_Text), + textColor, label); const float arrowExtent = PopupSubmenuArrowExtent(); @@ -139,7 +155,12 @@ inline bool DrawPopupSubmenuScope(const char* label, DrawContentFn&& drawContent ImVec2(arrowCenterX - arrowExtent * 0.30f, arrowCenterY - arrowExtent * 0.50f), ImVec2(arrowCenterX - arrowExtent * 0.30f, arrowCenterY + arrowExtent * 0.50f), ImVec2(arrowCenterX + arrowExtent * 0.50f, arrowCenterY), - ImGui::GetColorU32(ImGuiCol_Text)); + textColor); + + if (!enabled) { + ImGui::PopID(); + return false; + } ImGui::SetNextWindowPos( ImVec2(parentWindowRight + PopupSubmenuOpenOffsetX(), rowPos.y - PopupWindowPadding().y), @@ -149,7 +170,7 @@ inline bool DrawPopupSubmenuScope(const char* label, DrawContentFn&& drawContent ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings); - if (std::string(label) == "Create") { + if (ShouldTracePopupSubmenuLabel(label)) { static bool s_lastCreateOpen = false; if (open != s_lastCreateOpen) { TracePopupSubmenuIfNeeded( diff --git a/editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp b/editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp index e6e7ee05..9d0221ff 100644 --- a/editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp +++ b/editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp @@ -1,9 +1,5 @@ -#include - #include "Passes/SceneViewportSelectionOutlinePass.h" -#include "Viewport/SceneViewportShaderPaths.h" - namespace XCEngine { namespace Editor { @@ -45,7 +41,7 @@ private: } // namespace SceneViewportSelectionOutlinePassRenderer::SceneViewportSelectionOutlinePassRenderer() - : m_outlinePass(GetSceneViewportObjectIdOutlineShaderPath()) { + : m_outlinePass() { } void SceneViewportSelectionOutlinePassRenderer::Shutdown() { diff --git a/editor/src/Viewport/SceneViewportEditorOverlayData.h b/editor/src/Viewport/SceneViewportEditorOverlayData.h index 5a7b90f8..ebd441b9 100644 --- a/editor/src/Viewport/SceneViewportEditorOverlayData.h +++ b/editor/src/Viewport/SceneViewportEditorOverlayData.h @@ -20,7 +20,10 @@ enum class SceneViewportOverlayDepthMode : uint8_t { enum class SceneViewportOverlaySpriteTextureKind : uint8_t { Camera = 0, - Light = 1 + DirectionalLight = 1, + PointLight = 2, + SpotLight = 3, + Light = DirectionalLight }; enum class SceneViewportOverlayHandleKind : uint8_t { diff --git a/editor/src/Viewport/SceneViewportHudOverlay.cpp b/editor/src/Viewport/SceneViewportHudOverlay.cpp index 342e9d27..12cdbf09 100644 --- a/editor/src/Viewport/SceneViewportHudOverlay.cpp +++ b/editor/src/Viewport/SceneViewportHudOverlay.cpp @@ -29,12 +29,24 @@ bool DrawSceneViewportSpriteIcon( min, max, UI::EditorTextureIconKind::CameraGizmo); - case SceneViewportOverlaySpriteTextureKind::Light: + case SceneViewportOverlaySpriteTextureKind::DirectionalLight: return UI::DrawEditorTextureIcon( drawList, min, max, - UI::EditorTextureIconKind::MainLightGizmo); + UI::EditorTextureIconKind::DirectionalLightGizmo); + case SceneViewportOverlaySpriteTextureKind::PointLight: + return UI::DrawEditorTextureIcon( + drawList, + min, + max, + UI::EditorTextureIconKind::PointLightGizmo); + case SceneViewportOverlaySpriteTextureKind::SpotLight: + return UI::DrawEditorTextureIcon( + drawList, + min, + max, + UI::EditorTextureIconKind::SpotLightGizmo); default: return false; } diff --git a/editor/src/Viewport/SceneViewportNavigation.h b/editor/src/Viewport/SceneViewportNavigation.h index b58d328d..c81db1c0 100644 --- a/editor/src/Viewport/SceneViewportNavigation.h +++ b/editor/src/Viewport/SceneViewportNavigation.h @@ -105,6 +105,16 @@ inline bool ShouldBeginSceneViewportNavigationDrag( return hasInteractiveViewport && hovered && !activeDrag && !otherDrag && !gizmoActive && clicked; } +inline bool ShouldBeginSceneViewportHeldNavigationDrag( + bool hasInteractiveViewport, + bool hovered, + bool activeDrag, + bool otherDrag, + bool gizmoActive, + bool buttonDown) { + return hasInteractiveViewport && hovered && !activeDrag && !otherDrag && !gizmoActive && buttonDown; +} + inline bool IsSceneViewportPanDragButtonDown( const SceneViewportNavigationRequest& request, int button) { @@ -139,13 +149,21 @@ inline SceneViewportNavigationUpdate UpdateSceneViewportNavigationState( request.state.panDragging, request.gizmoActive, request.clickedRight); - update.beginMiddlePanDrag = ShouldBeginSceneViewportNavigationDrag( - request.hasInteractiveViewport, - request.viewportHovered, - request.state.panDragging, - request.state.lookDragging, - request.gizmoActive, - request.clickedMiddle); + update.beginMiddlePanDrag = + ShouldBeginSceneViewportNavigationDrag( + request.hasInteractiveViewport, + request.viewportHovered, + request.state.panDragging, + request.state.lookDragging, + request.gizmoActive, + request.clickedMiddle) || + ShouldBeginSceneViewportHeldNavigationDrag( + request.hasInteractiveViewport, + request.viewportHovered, + request.state.panDragging, + request.state.lookDragging, + request.gizmoActive, + request.middleMouseDown); update.beginPanDrag = update.beginLeftPanDrag || update.beginMiddlePanDrag; if (update.beginLookDrag) { diff --git a/editor/src/Viewport/SceneViewportOverlayProviders.cpp b/editor/src/Viewport/SceneViewportOverlayProviders.cpp index cc677102..46249641 100644 --- a/editor/src/Viewport/SceneViewportOverlayProviders.cpp +++ b/editor/src/Viewport/SceneViewportOverlayProviders.cpp @@ -73,6 +73,42 @@ void AppendWorldLine( line.depthMode = depthMode; } +constexpr Math::Color kSelectedLightHelperColor(1.0f, 0.92f, 0.24f, 1.0f); +constexpr float kSelectedLightHelperLineThickness = 1.8f; +constexpr size_t kSelectedLightHelperSegmentCount = 32u; + +void AppendWireCircle( + SceneViewportOverlayFrameData& frameData, + const Math::Vector3& center, + const Math::Vector3& basisA, + const Math::Vector3& basisB, + float radius, + const Math::Color& color, + float thicknessPixels, + SceneViewportOverlayDepthMode depthMode) { + const Math::Vector3 axisA = basisA.Normalized(); + const Math::Vector3 axisB = basisB.Normalized(); + if (radius <= Math::EPSILON || + axisA.SqrMagnitude() <= Math::EPSILON || + axisB.SqrMagnitude() <= Math::EPSILON) { + return; + } + + for (size_t segmentIndex = 0; segmentIndex < kSelectedLightHelperSegmentCount; ++segmentIndex) { + const float angle0 = + static_cast(segmentIndex) / static_cast(kSelectedLightHelperSegmentCount) * + Math::PI * 2.0f; + const float angle1 = + static_cast(segmentIndex + 1u) / static_cast(kSelectedLightHelperSegmentCount) * + Math::PI * 2.0f; + const Math::Vector3 p0 = + center + axisA * std::cos(angle0) * radius + axisB * std::sin(angle0) * radius; + const Math::Vector3 p1 = + center + axisA * std::cos(angle1) * radius + axisB * std::sin(angle1) * radius; + AppendWorldLine(frameData, p0, p1, color, thicknessPixels, depthMode); + } +} + void AppendWorldSprite( SceneViewportOverlayFrameData& frameData, const Math::Vector3& worldPosition, @@ -164,6 +200,20 @@ void AppendSceneIconOverlay( projectedPoint.ndcDepth); } +SceneViewportOverlaySpriteTextureKind ResolveLightSceneIconTextureKind( + const Components::LightComponent& light) { + switch (light.GetLightType()) { + case Components::LightType::Directional: + return SceneViewportOverlaySpriteTextureKind::DirectionalLight; + case Components::LightType::Point: + return SceneViewportOverlaySpriteTextureKind::PointLight; + case Components::LightType::Spot: + return SceneViewportOverlaySpriteTextureKind::SpotLight; + default: + return SceneViewportOverlaySpriteTextureKind::DirectionalLight; + } +} + void AppendCameraFrustumOverlay( SceneViewportOverlayFrameData& frameData, const Components::CameraComponent& camera, @@ -251,10 +301,10 @@ void AppendDirectionalLightOverlay( } const Math::Vector3 position = transform->GetPosition(); - const Math::Vector3 lightDirection = (transform->GetForward() * -1.0f).Normalized(); + const Math::Vector3 lightRayDirection = transform->GetForward().Normalized(); const Math::Vector3 right = transform->GetRight().Normalized(); const Math::Vector3 up = transform->GetUp().Normalized(); - if (lightDirection.SqrMagnitude() <= Math::EPSILON || + if (lightRayDirection.SqrMagnitude() <= Math::EPSILON || right.SqrMagnitude() <= Math::EPSILON || up.SqrMagnitude() <= Math::EPSILON) { return; @@ -265,8 +315,6 @@ void AppendDirectionalLightOverlay( return; } - constexpr Math::Color kDirectionalLightColor(1.0f, 0.92f, 0.24f, 1.0f); - constexpr float kLineThickness = 1.8f; constexpr size_t kRingSegmentCount = 32u; constexpr std::array kRayAngles = {{ 0.0f, @@ -281,7 +329,7 @@ void AppendDirectionalLightOverlay( const float ringOffset = worldUnitsPerPixel * 54.0f; const float innerRayRadius = ringRadius * 0.52f; const float rayLength = worldUnitsPerPixel * 96.0f; - const Math::Vector3 ringCenter = position + lightDirection * ringOffset; + const Math::Vector3 ringCenter = position + lightRayDirection * ringOffset; for (size_t segmentIndex = 0; segmentIndex < kRingSegmentCount; ++segmentIndex) { const float angle0 = @@ -296,8 +344,8 @@ void AppendDirectionalLightOverlay( frameData, p0, p1, - kDirectionalLightColor, - kLineThickness, + kSelectedLightHelperColor, + kSelectedLightHelperLineThickness, SceneViewportOverlayDepthMode::AlwaysOnTop); } @@ -305,15 +353,15 @@ void AppendDirectionalLightOverlay( frameData, position, ringCenter, - kDirectionalLightColor, - kLineThickness, + kSelectedLightHelperColor, + kSelectedLightHelperLineThickness, SceneViewportOverlayDepthMode::AlwaysOnTop); AppendWorldLine( frameData, ringCenter, - ringCenter + lightDirection * rayLength, - kDirectionalLightColor, - kLineThickness, + ringCenter + lightRayDirection * rayLength, + kSelectedLightHelperColor, + kSelectedLightHelperLineThickness, SceneViewportOverlayDepthMode::AlwaysOnTop); for (float angle : kRayAngles) { @@ -322,9 +370,119 @@ void AppendDirectionalLightOverlay( AppendWorldLine( frameData, rayStart, - rayStart + lightDirection * rayLength, - kDirectionalLightColor, - kLineThickness, + rayStart + lightRayDirection * rayLength, + kSelectedLightHelperColor, + kSelectedLightHelperLineThickness, + SceneViewportOverlayDepthMode::AlwaysOnTop); + } +} + +void AppendPointLightOverlay( + SceneViewportOverlayFrameData& frameData, + const Components::GameObject& gameObject, + const Components::LightComponent& light) { + const Components::TransformComponent* transform = gameObject.GetTransform(); + if (transform == nullptr) { + return; + } + + const Math::Vector3 position = transform->GetPosition(); + const Math::Vector3 right = transform->GetRight().Normalized(); + const Math::Vector3 up = transform->GetUp().Normalized(); + const Math::Vector3 forward = transform->GetForward().Normalized(); + if (right.SqrMagnitude() <= Math::EPSILON || + up.SqrMagnitude() <= Math::EPSILON || + forward.SqrMagnitude() <= Math::EPSILON) { + return; + } + + const float range = (std::max)(light.GetRange(), 0.001f); + AppendWireCircle( + frameData, + position, + right, + up, + range, + kSelectedLightHelperColor, + kSelectedLightHelperLineThickness, + SceneViewportOverlayDepthMode::AlwaysOnTop); + AppendWireCircle( + frameData, + position, + right, + forward, + range, + kSelectedLightHelperColor, + kSelectedLightHelperLineThickness, + SceneViewportOverlayDepthMode::AlwaysOnTop); + AppendWireCircle( + frameData, + position, + up, + forward, + range, + kSelectedLightHelperColor, + kSelectedLightHelperLineThickness, + SceneViewportOverlayDepthMode::AlwaysOnTop); +} + +void AppendSpotLightOverlay( + SceneViewportOverlayFrameData& frameData, + const Components::GameObject& gameObject, + const Components::LightComponent& light) { + const Components::TransformComponent* transform = gameObject.GetTransform(); + if (transform == nullptr) { + return; + } + + const Math::Vector3 position = transform->GetPosition(); + const Math::Vector3 forward = transform->GetForward().Normalized(); + const Math::Vector3 right = transform->GetRight().Normalized(); + const Math::Vector3 up = transform->GetUp().Normalized(); + if (forward.SqrMagnitude() <= Math::EPSILON || + right.SqrMagnitude() <= Math::EPSILON || + up.SqrMagnitude() <= Math::EPSILON) { + return; + } + + const float range = (std::max)(light.GetRange(), 0.001f); + const float halfAngleRadians = + std::clamp(light.GetSpotAngle(), 1.0f, 179.0f) * Math::DEG_TO_RAD * 0.5f; + const float coneRadius = std::tan(halfAngleRadians) * range; + const Math::Vector3 coneBaseCenter = position + forward * range; + + AppendWorldLine( + frameData, + position, + coneBaseCenter, + kSelectedLightHelperColor, + kSelectedLightHelperLineThickness, + SceneViewportOverlayDepthMode::AlwaysOnTop); + AppendWireCircle( + frameData, + coneBaseCenter, + right, + up, + coneRadius, + kSelectedLightHelperColor, + kSelectedLightHelperLineThickness, + SceneViewportOverlayDepthMode::AlwaysOnTop); + + static constexpr std::array kConeEdgeAngles = {{ + 0.0f, + Math::PI * 0.5f, + Math::PI, + Math::PI * 1.5f + }}; + for (float angle : kConeEdgeAngles) { + const Math::Vector3 coneEdgePoint = + coneBaseCenter + right * std::cos(angle) * coneRadius + up * std::sin(angle) * coneRadius; + AppendWorldLine( + frameData, + position, + coneEdgePoint, + kSelectedLightHelperColor, + kSelectedLightHelperLineThickness, SceneViewportOverlayDepthMode::AlwaysOnTop); } } @@ -421,7 +579,7 @@ public: context.viewportHeight, *gameObject, kLightIconSize, - SceneViewportOverlaySpriteTextureKind::Light); + ResolveLightSceneIconTextureKind(*light)); } for (uint64_t entityId : *context.selectedObjectIds) { @@ -435,17 +593,25 @@ public: } Components::LightComponent* light = gameObject->GetComponent(); - if (light == nullptr || - !light->IsEnabled() || - light->GetLightType() != Components::LightType::Directional) { + if (light == nullptr || !light->IsEnabled()) { continue; } - AppendDirectionalLightOverlay( - frameData, - *gameObject, - *context.overlay, - context.viewportHeight); + switch (light->GetLightType()) { + case Components::LightType::Directional: + AppendDirectionalLightOverlay( + frameData, + *gameObject, + *context.overlay, + context.viewportHeight); + break; + case Components::LightType::Point: + AppendPointLightOverlay(frameData, *gameObject, *light); + break; + case Components::LightType::Spot: + AppendSpotLightOverlay(frameData, *gameObject, *light); + break; + } } } }; diff --git a/editor/src/Viewport/SceneViewportOverlaySpriteResources.h b/editor/src/Viewport/SceneViewportOverlaySpriteResources.h index b5298ef9..6e6a4951 100644 --- a/editor/src/Viewport/SceneViewportOverlaySpriteResources.h +++ b/editor/src/Viewport/SceneViewportOverlaySpriteResources.h @@ -22,9 +22,11 @@ class RHITexture; namespace Editor { -inline constexpr std::array kSceneViewportOverlaySpriteTextureKinds = { +inline constexpr std::array kSceneViewportOverlaySpriteTextureKinds = { SceneViewportOverlaySpriteTextureKind::Camera, - SceneViewportOverlaySpriteTextureKind::Light + SceneViewportOverlaySpriteTextureKind::DirectionalLight, + SceneViewportOverlaySpriteTextureKind::PointLight, + SceneViewportOverlaySpriteTextureKind::SpotLight }; inline constexpr size_t kSceneViewportOverlaySpriteResourceCount = @@ -35,8 +37,12 @@ inline size_t GetSceneViewportOverlaySpriteResourceIndex( switch (textureKind) { case SceneViewportOverlaySpriteTextureKind::Camera: return 0u; - case SceneViewportOverlaySpriteTextureKind::Light: + case SceneViewportOverlaySpriteTextureKind::DirectionalLight: return 1u; + case SceneViewportOverlaySpriteTextureKind::PointLight: + return 2u; + case SceneViewportOverlaySpriteTextureKind::SpotLight: + return 3u; default: return 0u; } @@ -63,8 +69,14 @@ inline SceneViewportOverlaySpriteAssetSpec GetSceneViewportOverlaySpriteAssetSpe case SceneViewportOverlaySpriteTextureKind::Camera: spec.resourcePath = GetSceneViewportCameraGizmoIconPath(); break; - case SceneViewportOverlaySpriteTextureKind::Light: - spec.resourcePath = GetSceneViewportMainLightGizmoIconPath(); + case SceneViewportOverlaySpriteTextureKind::DirectionalLight: + spec.resourcePath = GetSceneViewportDirectionalLightGizmoIconPath(); + break; + case SceneViewportOverlaySpriteTextureKind::PointLight: + spec.resourcePath = GetSceneViewportPointLightGizmoIconPath(); + break; + case SceneViewportOverlaySpriteTextureKind::SpotLight: + spec.resourcePath = GetSceneViewportSpotLightGizmoIconPath(); break; default: break; diff --git a/editor/src/Viewport/SceneViewportResourcePaths.h b/editor/src/Viewport/SceneViewportResourcePaths.h index 8d2e561a..5ba23351 100644 --- a/editor/src/Viewport/SceneViewportResourcePaths.h +++ b/editor/src/Viewport/SceneViewportResourcePaths.h @@ -39,22 +39,28 @@ inline Containers::String GetSceneViewportInfiniteGridShaderPath() { "infinite-grid.shader"); } -inline Containers::String GetSceneViewportObjectIdOutlineShaderPath() { - return Detail::BuildSceneViewportEditorResourcePath( - std::filesystem::path("shaders") / - "scene-viewport" / - "object-id-outline" / - "object-id-outline.shader"); -} - inline Containers::String GetSceneViewportCameraGizmoIconPath() { return Detail::BuildSceneViewportEditorResourcePath( std::filesystem::path("Icons") / "camera_gizmo.png"); } -inline Containers::String GetSceneViewportMainLightGizmoIconPath() { +inline Containers::String GetSceneViewportDirectionalLightGizmoIconPath() { return Detail::BuildSceneViewportEditorResourcePath( - std::filesystem::path("Icons") / "main_light_gizmo.png"); + std::filesystem::path("Icons") / "directional_light_gizmo.png"); +} + +inline Containers::String GetSceneViewportPointLightGizmoIconPath() { + return Detail::BuildSceneViewportEditorResourcePath( + std::filesystem::path("Icons") / "point_light_gizmo.png"); +} + +inline Containers::String GetSceneViewportSpotLightGizmoIconPath() { + return Detail::BuildSceneViewportEditorResourcePath( + std::filesystem::path("Icons") / "spot_light_gizmo.png"); +} + +inline Containers::String GetSceneViewportMainLightGizmoIconPath() { + return GetSceneViewportDirectionalLightGizmoIconPath(); } } // namespace Editor diff --git a/editor/src/panels/ViewportPanelContent.h b/editor/src/panels/ViewportPanelContent.h index 21af2da9..fcb11fd3 100644 --- a/editor/src/panels/ViewportPanelContent.h +++ b/editor/src/panels/ViewportPanelContent.h @@ -77,10 +77,15 @@ inline void RenderViewportInteractionSurface( result.itemMin = ImGui::GetItemRectMin(); result.itemMax = ImGui::GetItemRectMax(); - result.hovered = ImGui::IsItemHovered(); - result.clickedLeft = result.hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left, false); - result.clickedRight = result.hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Right, false); - result.clickedMiddle = result.hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Middle, false); + // Use explicit viewport-rect hit testing instead of ImGui item hover state. Dear ImGui ties + // hovered items to click ownership, so a middle-button press that arrives before the panel + // has established hover can fail to start panning in the live docked editor even though the + // cursor is inside the viewport. + const bool hoveredForInput = ImGui::IsMouseHoveringRect(result.itemMin, result.itemMax, false); + result.hovered = hoveredForInput; + result.clickedLeft = hoveredForInput && ImGui::IsMouseClicked(ImGuiMouseButton_Left); + result.clickedRight = hoveredForInput && ImGui::IsMouseClicked(ImGuiMouseButton_Right); + result.clickedMiddle = hoveredForInput && ImGui::IsMouseClicked(ImGuiMouseButton_Middle); } inline ViewportPanelContentResult RenderViewportPanelContent( diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 4b2131b3..dcaa939d 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -394,8 +394,6 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/Shader/Internal/ShaderRuntimeBuildUtils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/Shader/Internal/ShaderAuthoringLoader.h ${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/Shader/Internal/ShaderAuthoringLoader.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/Shader/Internal/ShaderManifestLoader.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/Shader/Internal/ShaderManifestLoader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/Shader/Internal/ShaderArtifactLoader.h ${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/Shader/Internal/ShaderArtifactLoader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Resources/Shader/Internal/ShaderAuthoringInternal.h @@ -569,12 +567,14 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Widgets/UIKeyboardNavigationModel.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Widgets/UIPopupOverlayModel.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Widgets/UIPropertyEditModel.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Widgets/UIScrollModel.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Widgets/UISelectionModel.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Widgets/UITabStripModel.h ${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Widgets/UIExpansionModel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Widgets/UIKeyboardNavigationModel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Widgets/UIPopupOverlayModel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Widgets/UIPropertyEditModel.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Widgets/UIScrollModel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Widgets/UISelectionModel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Runtime/UIScreenTypes.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Runtime/UIScreenDocumentHost.h diff --git a/engine/assets/builtin/shaders/object-id-outline.shader b/engine/assets/builtin/shaders/object-id-outline.shader index 54ad2981..d199b6e1 100644 --- a/engine/assets/builtin/shaders/object-id-outline.shader +++ b/engine/assets/builtin/shaders/object-id-outline.shader @@ -46,7 +46,14 @@ Shader "Builtin Object Id Outline" bool IsSelectedObject(float4 objectIdColor) { - if (objectIdColor.a <= 0.0) { + // Object-id surfaces encode the low 32 bits across RGBA, so low-valued + // runtime ids legitimately have zero alpha. Only the all-zero clear + // color should be treated as "no object". + if (all(abs(objectIdColor) <= float4( + 0.0025, + 0.0025, + 0.0025, + 0.0025))) { return false; } diff --git a/engine/include/XCEngine/Core/Math/Vector4.h b/engine/include/XCEngine/Core/Math/Vector4.h index 0cd8caea..470d6646 100644 --- a/engine/include/XCEngine/Core/Math/Vector4.h +++ b/engine/include/XCEngine/Core/Math/Vector4.h @@ -1,6 +1,7 @@ #pragma once #include "Math.h" +#include "Vector3.h" namespace XCEngine { namespace Math { diff --git a/engine/include/XCEngine/Input/InputTypes.h b/engine/include/XCEngine/Input/InputTypes.h index 12cdd392..d2d929fa 100644 --- a/engine/include/XCEngine/Input/InputTypes.h +++ b/engine/include/XCEngine/Input/InputTypes.h @@ -22,7 +22,7 @@ enum class KeyCode : Core::uint8 { Up = 126, Down = 125, Left = 123, Right = 124, Home = 115, End = 119, PageUp = 116, PageDown = 121, - Delete = 51, Backspace = 51, + Delete = 117, Backspace = 51, Zero = 39, One = 30, Two = 31, Three = 32, Four = 33, Five = 34, Six = 35, Seven = 37, diff --git a/engine/include/XCEngine/RHI/D3D12/D3D12ResourceView.h b/engine/include/XCEngine/RHI/D3D12/D3D12ResourceView.h index e5914043..30401835 100644 --- a/engine/include/XCEngine/RHI/D3D12/D3D12ResourceView.h +++ b/engine/include/XCEngine/RHI/D3D12/D3D12ResourceView.h @@ -26,6 +26,7 @@ public: bool IsValid() const override; void SetViewType(ResourceViewType type) { m_viewType = type; } + void SetFormat(Format format) { m_format = format; } ResourceViewType GetViewType() const override { return m_viewType; } ResourceViewDimension GetDimension() const override { return m_dimension; } Format GetFormat() const override { return m_format; } diff --git a/engine/include/XCEngine/RHI/OpenGL/OpenGLPipelineState.h b/engine/include/XCEngine/RHI/OpenGL/OpenGLPipelineState.h index 3fcee3a8..a990faf1 100644 --- a/engine/include/XCEngine/RHI/OpenGL/OpenGLPipelineState.h +++ b/engine/include/XCEngine/RHI/OpenGL/OpenGLPipelineState.h @@ -19,10 +19,14 @@ struct OpenGLDepthStencilState { uint8_t stencilReadMask = 0xFF; uint8_t stencilWriteMask = 0xFF; int stencilRef = 0; - ComparisonFunc stencilFunc = ComparisonFunc::Always; - StencilOp stencilFailOp = StencilOp::Keep; - StencilOp stencilDepthFailOp = StencilOp::Keep; - StencilOp stencilDepthPassOp = StencilOp::Keep; + ComparisonFunc frontStencilFunc = ComparisonFunc::Always; + StencilOp frontStencilFailOp = StencilOp::Keep; + StencilOp frontStencilDepthFailOp = StencilOp::Keep; + StencilOp frontStencilDepthPassOp = StencilOp::Keep; + ComparisonFunc backStencilFunc = ComparisonFunc::Always; + StencilOp backStencilFailOp = StencilOp::Keep; + StencilOp backStencilDepthFailOp = StencilOp::Keep; + StencilOp backStencilDepthPassOp = StencilOp::Keep; }; struct OpenGLBlendState { diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanCommandList.h b/engine/include/XCEngine/RHI/Vulkan/VulkanCommandList.h index acceb499..7e955986 100644 --- a/engine/include/XCEngine/RHI/Vulkan/VulkanCommandList.h +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanCommandList.h @@ -87,6 +87,7 @@ private: bool m_renderPassActive = false; bool m_hasViewport = false; bool m_hasScissor = false; + uint32_t m_stencilRef = 0; VkViewport m_viewport = {}; VkRect2D m_scissor = {}; std::vector m_transientFramebuffers; diff --git a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassTypes.h b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassTypes.h index efe1def4..fbd77c4a 100644 --- a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassTypes.h +++ b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassTypes.h @@ -18,6 +18,9 @@ enum class BuiltinMaterialPass : Core::uint32 { DepthOnly, ShadowCaster, ObjectId, + Skybox, + PostProcess, + FinalColor, Forward = ForwardLit }; @@ -36,7 +39,12 @@ enum class BuiltinPassResourceSemantic : Core::uint8 { Material, Lighting, ShadowReceiver, + Environment, + PassConstants, BaseColorTexture, + SourceColorTexture, + SkyboxPanoramicTexture, + SkyboxTexture, ShadowMapTexture, LinearClampSampler, ShadowMapSampler @@ -60,7 +68,12 @@ struct BuiltinPassResourceBindingPlan { PassResourceBindingLocation material = {}; PassResourceBindingLocation lighting = {}; PassResourceBindingLocation shadowReceiver = {}; + PassResourceBindingLocation environment = {}; + PassResourceBindingLocation passConstants = {}; PassResourceBindingLocation baseColorTexture = {}; + PassResourceBindingLocation sourceColorTexture = {}; + PassResourceBindingLocation skyboxPanoramicTexture = {}; + PassResourceBindingLocation skyboxTexture = {}; PassResourceBindingLocation linearClampSampler = {}; PassResourceBindingLocation shadowMapTexture = {}; PassResourceBindingLocation shadowMapSampler = {}; @@ -85,8 +98,13 @@ struct BuiltinPassSetLayoutMetadata { bool usesMaterial = false; bool usesLighting = false; bool usesShadowReceiver = false; + bool usesEnvironment = false; + bool usesPassConstants = false; bool usesTexture = false; bool usesBaseColorTexture = false; + bool usesSourceColorTexture = false; + bool usesSkyboxPanoramicTexture = false; + bool usesSkyboxTexture = false; bool usesShadowMapTexture = false; bool usesSampler = false; bool usesLinearClampSampler = false; diff --git a/engine/include/XCEngine/Rendering/Materials/RenderMaterialStateUtils.h b/engine/include/XCEngine/Rendering/Materials/RenderMaterialStateUtils.h index 717e0f6d..c05e7133 100644 --- a/engine/include/XCEngine/Rendering/Materials/RenderMaterialStateUtils.h +++ b/engine/include/XCEngine/Rendering/Materials/RenderMaterialStateUtils.h @@ -1,5 +1,8 @@ #pragma once +#include + +#include #include #include #include @@ -97,6 +100,28 @@ inline RHI::BlendOp ToRHIBlendOp(Resources::MaterialBlendOp op) { } } +inline RHI::StencilOp ToRHIStencilOp(Resources::MaterialStencilOp op) { + switch (op) { + case Resources::MaterialStencilOp::Zero: + return RHI::StencilOp::Zero; + case Resources::MaterialStencilOp::Replace: + return RHI::StencilOp::Replace; + case Resources::MaterialStencilOp::IncrSat: + return RHI::StencilOp::IncrSat; + case Resources::MaterialStencilOp::DecrSat: + return RHI::StencilOp::DecrSat; + case Resources::MaterialStencilOp::Invert: + return RHI::StencilOp::Invert; + case Resources::MaterialStencilOp::IncrWrap: + return RHI::StencilOp::Incr; + case Resources::MaterialStencilOp::DecrWrap: + return RHI::StencilOp::Decr; + case Resources::MaterialStencilOp::Keep: + default: + return RHI::StencilOp::Keep; + } +} + inline Resources::MaterialRenderState ResolveEffectiveRenderState( const Resources::ShaderPass* shaderPass, const Resources::Material* material) { @@ -122,6 +147,8 @@ inline RHI::RasterizerDesc BuildRasterizerState(const Resources::MaterialRenderS desc.frontFace = static_cast(RHI::FrontFace::CounterClockwise); desc.depthClipEnable = true; desc.cullMode = static_cast(ToRHICullMode(renderState.cullMode)); + desc.depthBias = renderState.depthBiasUnits; + desc.slopeScaledDepthBias = renderState.depthBiasFactor; return desc; } @@ -145,7 +172,17 @@ inline RHI::DepthStencilStateDesc BuildDepthStencilState(const Resources::Materi desc.depthTestEnable = renderState.depthTestEnable; desc.depthWriteEnable = renderState.depthWriteEnable; desc.depthFunc = static_cast(ToRHIComparisonFunc(renderState.depthFunc)); - desc.stencilEnable = false; + desc.stencilEnable = renderState.stencil.enabled; + desc.stencilReadMask = renderState.stencil.readMask; + desc.stencilWriteMask = renderState.stencil.writeMask; + desc.front.failOp = static_cast(ToRHIStencilOp(renderState.stencil.front.failOp)); + desc.front.passOp = static_cast(ToRHIStencilOp(renderState.stencil.front.passOp)); + desc.front.depthFailOp = static_cast(ToRHIStencilOp(renderState.stencil.front.depthFailOp)); + desc.front.func = static_cast(ToRHIComparisonFunc(renderState.stencil.front.func)); + desc.back.failOp = static_cast(ToRHIStencilOp(renderState.stencil.back.failOp)); + desc.back.passOp = static_cast(ToRHIStencilOp(renderState.stencil.back.passOp)); + desc.back.depthFailOp = static_cast(ToRHIStencilOp(renderState.stencil.back.depthFailOp)); + desc.back.func = static_cast(ToRHIComparisonFunc(renderState.stencil.back.func)); return desc; } @@ -167,12 +204,32 @@ inline void ApplyResolvedRenderState( ApplyRenderState(ResolveEffectiveRenderState(shaderPass, material), pipelineDesc); } +inline void ApplyDynamicRenderState( + const Resources::MaterialRenderState& renderState, + RHI::RHICommandList& commandList) { + if (renderState.stencil.enabled) { + commandList.SetStencilRef(renderState.stencil.reference); + } +} + +inline Resources::MaterialRenderState BuildStaticPipelineRenderStateKey( + const Resources::MaterialRenderState& renderState) { + Resources::MaterialRenderState keyState = renderState; + keyState.stencil.reference = 0; + return keyState; +} + struct MaterialRenderStateHash { size_t operator()(const Resources::MaterialRenderState& state) const noexcept { size_t hash = 2166136261u; auto combine = [&hash](size_t value) { hash ^= value + 0x9e3779b9u + (hash << 6) + (hash >> 2); }; + auto combineFloat = [&combine](float value) { + Core::uint32 bits = 0; + std::memcpy(&bits, &value, sizeof(bits)); + combine(static_cast(bits)); + }; combine(static_cast(state.blendEnable)); combine(static_cast(state.srcBlend)); @@ -186,6 +243,20 @@ struct MaterialRenderStateHash { combine(static_cast(state.depthWriteEnable)); combine(static_cast(state.depthFunc)); combine(static_cast(state.cullMode)); + combineFloat(state.depthBiasFactor); + combine(static_cast(state.depthBiasUnits)); + combine(static_cast(state.stencil.enabled)); + combine(static_cast(state.stencil.readMask)); + combine(static_cast(state.stencil.writeMask)); + combine(static_cast(state.stencil.reference)); + combine(static_cast(state.stencil.front.failOp)); + combine(static_cast(state.stencil.front.passOp)); + combine(static_cast(state.stencil.front.depthFailOp)); + combine(static_cast(state.stencil.front.func)); + combine(static_cast(state.stencil.back.failOp)); + combine(static_cast(state.stencil.back.passOp)); + combine(static_cast(state.stencil.back.depthFailOp)); + combine(static_cast(state.stencil.back.func)); return hash; } }; diff --git a/engine/include/XCEngine/Resources/Material/Material.h b/engine/include/XCEngine/Resources/Material/Material.h index 2097493f..7883e8b7 100644 --- a/engine/include/XCEngine/Resources/Material/Material.h +++ b/engine/include/XCEngine/Resources/Material/Material.h @@ -102,16 +102,6 @@ public: bool HasRenderStateOverride() const { return m_hasRenderStateOverride; } void SetRenderStateOverrideEnabled(bool enabled); - // Legacy-only compatibility hint for older material assets that still - // select a builtin pass by serialized name. - void SetLegacyShaderPassHint(const Containers::String& shaderPass); - const Containers::String& GetLegacyShaderPassHint() const { return m_legacyShaderPassHint; } - bool HasLegacyShaderPassHint() const { return !m_legacyShaderPassHint.Empty(); } - void ClearLegacyShaderPassHint(); - - void SetShaderPass(const Containers::String& shaderPass); - const Containers::String& GetShaderPass() const { return GetLegacyShaderPassHint(); } - void SetTag(const Containers::String& name, const Containers::String& value); Containers::String GetTag(const Containers::String& name) const; bool HasTag(const Containers::String& name) const; @@ -186,7 +176,6 @@ private: Core::int32 m_renderQueue = static_cast(MaterialRenderQueue::Geometry); MaterialRenderState m_renderState; bool m_hasRenderStateOverride = false; - Containers::String m_legacyShaderPassHint; Containers::Array m_tags; ShaderKeywordSet m_keywordSet; Containers::HashMap m_properties; diff --git a/engine/include/XCEngine/Resources/Material/MaterialRenderState.h b/engine/include/XCEngine/Resources/Material/MaterialRenderState.h index 0f9417a7..383c4172 100644 --- a/engine/include/XCEngine/Resources/Material/MaterialRenderState.h +++ b/engine/include/XCEngine/Resources/Material/MaterialRenderState.h @@ -50,6 +50,57 @@ enum class MaterialBlendFactor : Core::uint8 { InvSrc1Alpha = 16 }; +enum class MaterialStencilOp : Core::uint8 { + Keep = 0, + Zero = 1, + Replace = 2, + IncrSat = 3, + DecrSat = 4, + Invert = 5, + IncrWrap = 6, + DecrWrap = 7 +}; + +struct MaterialStencilFaceState { + MaterialStencilOp failOp = MaterialStencilOp::Keep; + MaterialStencilOp passOp = MaterialStencilOp::Keep; + MaterialStencilOp depthFailOp = MaterialStencilOp::Keep; + MaterialComparisonFunc func = MaterialComparisonFunc::Always; + + bool operator==(const MaterialStencilFaceState& other) const { + return failOp == other.failOp && + passOp == other.passOp && + depthFailOp == other.depthFailOp && + func == other.func; + } + + bool operator!=(const MaterialStencilFaceState& other) const { + return !(*this == other); + } +}; + +struct MaterialStencilState { + bool enabled = false; + Core::uint8 readMask = 0xFF; + Core::uint8 writeMask = 0xFF; + Core::uint8 reference = 0; + MaterialStencilFaceState front = {}; + MaterialStencilFaceState back = {}; + + bool operator==(const MaterialStencilState& other) const { + return enabled == other.enabled && + readMask == other.readMask && + writeMask == other.writeMask && + reference == other.reference && + front == other.front && + back == other.back; + } + + bool operator!=(const MaterialStencilState& other) const { + return !(*this == other); + } +}; + struct MaterialRenderState { bool blendEnable = false; MaterialBlendFactor srcBlend = MaterialBlendFactor::One; @@ -65,6 +116,9 @@ struct MaterialRenderState { MaterialComparisonFunc depthFunc = MaterialComparisonFunc::Less; MaterialCullMode cullMode = MaterialCullMode::None; + float depthBiasFactor = 0.0f; + Core::int32 depthBiasUnits = 0; + MaterialStencilState stencil = {}; bool operator==(const MaterialRenderState& other) const { return blendEnable == other.blendEnable && @@ -78,7 +132,10 @@ struct MaterialRenderState { depthTestEnable == other.depthTestEnable && depthWriteEnable == other.depthWriteEnable && depthFunc == other.depthFunc && - cullMode == other.cullMode; + cullMode == other.cullMode && + depthBiasFactor == other.depthBiasFactor && + depthBiasUnits == other.depthBiasUnits && + stencil == other.stencil; } bool operator!=(const MaterialRenderState& other) const { diff --git a/engine/include/XCEngine/Threading/TaskSystem.h b/engine/include/XCEngine/Threading/TaskSystem.h index aae55400..00636b49 100644 --- a/engine/include/XCEngine/Threading/TaskSystem.h +++ b/engine/include/XCEngine/Threading/TaskSystem.h @@ -7,6 +7,7 @@ #include "Mutex.h" #include "SpinLock.h" #include +#include #include #include #include @@ -102,7 +103,9 @@ void TaskSystem::ParallelFor(int32_t start, int32_t end, Func&& func) { } for (auto& task : tasks) { - Submit(std::make_unique>>(std::move(task), TaskPriority::High)); + Submit(std::make_unique<::XCEngine::Threading::LambdaTask>>( + std::move(task), + TaskPriority::High)); } } diff --git a/engine/include/XCEngine/UI/DrawData.h b/engine/include/XCEngine/UI/DrawData.h index c6a03ad3..a09a9db7 100644 --- a/engine/include/XCEngine/UI/DrawData.h +++ b/engine/include/XCEngine/UI/DrawData.h @@ -29,12 +29,21 @@ struct UIColor { enum class UIDrawCommandType : std::uint8_t { FilledRect = 0, RectOutline, + FilledRectLinearGradient, + Line, + FilledCircle, + CircleOutline, Text, Image, PushClipRect, PopClipRect }; +enum class UILinearGradientDirection : std::uint8_t { + Horizontal = 0, + Vertical +}; + struct UIDrawCommand { UIDrawCommandType type = UIDrawCommandType::FilledRect; UIRect rect = {}; @@ -42,10 +51,13 @@ struct UIDrawCommand { UIPoint uvMin = {}; UIPoint uvMax = UIPoint(1.0f, 1.0f); UIColor color = {}; + UIColor secondaryColor = {}; float thickness = 1.0f; float rounding = 0.0f; + float radius = 0.0f; float fontSize = 0.0f; bool intersectWithCurrentClip = true; + UILinearGradientDirection gradientDirection = UILinearGradientDirection::Horizontal; UITextureHandle texture = {}; std::string text; }; @@ -107,6 +119,62 @@ public: return AddCommand(std::move(command)); } + UIDrawCommand& AddFilledRectLinearGradient( + const UIRect& rect, + const UIColor& startColor, + const UIColor& endColor, + UILinearGradientDirection direction = UILinearGradientDirection::Horizontal, + float rounding = 0.0f) { + UIDrawCommand command = {}; + command.type = UIDrawCommandType::FilledRectLinearGradient; + command.rect = rect; + command.color = startColor; + command.secondaryColor = endColor; + command.rounding = rounding; + command.gradientDirection = direction; + return AddCommand(std::move(command)); + } + + UIDrawCommand& AddLine( + const UIPoint& start, + const UIPoint& end, + const UIColor& color, + float thickness = 1.0f) { + UIDrawCommand command = {}; + command.type = UIDrawCommandType::Line; + command.position = start; + command.uvMin = end; + command.color = color; + command.thickness = thickness; + return AddCommand(std::move(command)); + } + + UIDrawCommand& AddFilledCircle( + const UIPoint& center, + float radius, + const UIColor& color) { + UIDrawCommand command = {}; + command.type = UIDrawCommandType::FilledCircle; + command.position = center; + command.radius = radius; + command.color = color; + return AddCommand(std::move(command)); + } + + UIDrawCommand& AddCircleOutline( + const UIPoint& center, + float radius, + const UIColor& color, + float thickness = 1.0f) { + UIDrawCommand command = {}; + command.type = UIDrawCommandType::CircleOutline; + command.position = center; + command.radius = radius; + command.color = color; + command.thickness = thickness; + return AddCommand(std::move(command)); + } + UIDrawCommand& AddText( const UIPoint& position, std::string text, diff --git a/engine/include/XCEngine/UI/Widgets/UIScrollModel.h b/engine/include/XCEngine/UI/Widgets/UIScrollModel.h new file mode 100644 index 00000000..962be328 --- /dev/null +++ b/engine/include/XCEngine/UI/Widgets/UIScrollModel.h @@ -0,0 +1,32 @@ +#pragma once + +namespace XCEngine { +namespace UI { +namespace Widgets { + +struct UIScrollWheelResult { + bool changed = false; + float overflow = 0.0f; + float offsetBefore = 0.0f; + float offsetAfter = 0.0f; +}; + +float ComputeUIScrollOverflow(float contentExtent, float viewportExtent); +float ClampUIScrollOffset(float offset, float contentExtent, float viewportExtent); +UIScrollWheelResult ApplyUIScrollWheel( + float offset, + float wheelDelta, + float contentExtent, + float viewportExtent, + float wheelStep = 48.0f, + float epsilon = 0.01f); +float EnsureUIScrollOffsetVisible( + float offset, + float itemStart, + float itemExtent, + float contentExtent, + float viewportExtent); + +} // namespace Widgets +} // namespace UI +} // namespace XCEngine diff --git a/engine/src/Audio/WindowsAudioBackend.cpp b/engine/src/Audio/WindowsAudioBackend.cpp index 2c5d768b..72f8d015 100644 --- a/engine/src/Audio/WindowsAudioBackend.cpp +++ b/engine/src/Audio/WindowsAudioBackend.cpp @@ -10,6 +10,45 @@ namespace XCEngine { namespace Audio { namespace WASAPI { +namespace { + +std::string ConvertWaveOutDeviceName(const TCHAR* deviceName) { + if (deviceName == nullptr) { + return {}; + } + +#if defined(UNICODE) || defined(_UNICODE) + const int requiredBytes = WideCharToMultiByte( + CP_UTF8, + 0, + deviceName, + -1, + nullptr, + 0, + nullptr, + nullptr); + if (requiredBytes <= 1) { + return {}; + } + + std::string utf8Name(static_cast(requiredBytes - 1), '\0'); + WideCharToMultiByte( + CP_UTF8, + 0, + deviceName, + -1, + utf8Name.data(), + requiredBytes, + nullptr, + nullptr); + return utf8Name; +#else + return std::string(deviceName); +#endif +} + +} // namespace + WASAPIBackend::WASAPIBackend() : m_audioBuffer1(BufferSize * 2) , m_audioBuffer2(BufferSize * 2) @@ -65,7 +104,7 @@ std::string WASAPIBackend::GetDeviceName() const { void WASAPIBackend::GetAvailableDevices(std::vector& devices) { devices.clear(); for (const auto& caps : m_waveOutCaps) { - devices.push_back(caps.szPname); + devices.push_back(ConvertWaveOutDeviceName(caps.szPname)); } } @@ -73,7 +112,7 @@ bool WASAPIBackend::SetDevice(const std::string& deviceName) { for (UINT i = 0; i < waveOutGetNumDevs(); ++i) { WAVEOUTCAPS caps; waveOutGetDevCaps(i, &caps, sizeof(WAVEOUTCAPS)); - if (deviceName == caps.szPname) { + if (deviceName == ConvertWaveOutDeviceName(caps.szPname)) { m_deviceName = deviceName; return true; } diff --git a/engine/src/Components/MeshFilterComponent.cpp b/engine/src/Components/MeshFilterComponent.cpp index 9f67a341..09ca759d 100644 --- a/engine/src/Components/MeshFilterComponent.cpp +++ b/engine/src/Components/MeshFilterComponent.cpp @@ -139,7 +139,7 @@ void MeshFilterComponent::Serialize(std::ostream& os) const { } os << "meshRef=" << EncodeAssetRef(meshRef) << ";"; - if (!meshRef.IsValid() && !m_meshPath.empty()) { + if (!meshRef.IsValid() && !m_meshPath.empty() && HasVirtualPathScheme(m_meshPath)) { os << "meshPath=" << m_meshPath << ";"; } } @@ -167,7 +167,7 @@ void MeshFilterComponent::Deserialize(std::istream& is) { const std::string key = token.substr(0, eqPos); const std::string value = token.substr(eqPos + 1); - if (key == "mesh" || key == "meshPath") { + if (key == "meshPath") { pendingMeshPath = value; } else if (key == "meshRef") { TryDecodeAssetRef(value, pendingMeshRef); @@ -203,12 +203,9 @@ void MeshFilterComponent::Deserialize(std::istream& is) { } else { m_meshPath = pendingMeshPath; } - } else if (!pendingMeshPath.empty()) { + } else if (!pendingMeshPath.empty() && HasVirtualPathScheme(pendingMeshPath)) { if (Resources::ResourceManager::Get().IsDeferredSceneLoadEnabled()) { m_meshPath = pendingMeshPath; - if (!Resources::ResourceManager::Get().TryGetAssetRef(m_meshPath.c_str(), Resources::ResourceType::Mesh, m_meshRef)) { - m_meshRef.Reset(); - } return; } diff --git a/engine/src/Components/MeshRendererComponent.cpp b/engine/src/Components/MeshRendererComponent.cpp index dc7bf817..8180d30d 100644 --- a/engine/src/Components/MeshRendererComponent.cpp +++ b/engine/src/Components/MeshRendererComponent.cpp @@ -240,7 +240,6 @@ void MeshRendererComponent::Serialize(std::ostream& os) const { serializedRefs.resize(slotCount); std::vector serializedPaths = m_materialPaths; serializedPaths.resize(slotCount); - std::vector fallbackPaths(slotCount); for (size_t i = 0; i < slotCount; ++i) { if (!serializedRefs[i].IsValid() && !serializedPaths[i].empty() && @@ -251,8 +250,8 @@ void MeshRendererComponent::Serialize(std::ostream& os) const { serializedRefs[i])) { } - if (!serializedRefs[i].IsValid()) { - fallbackPaths[i] = serializedPaths[i]; + if (serializedRefs[i].IsValid() || !HasVirtualPathScheme(serializedPaths[i])) { + serializedPaths[i].clear(); } } @@ -261,7 +260,7 @@ void MeshRendererComponent::Serialize(std::ostream& os) const { if (i > 0) { os << "|"; } - os << fallbackPaths[i]; + os << serializedPaths[i]; } os << ";"; os << "materialRefs="; @@ -298,7 +297,7 @@ void MeshRendererComponent::Deserialize(std::istream& is) { const std::string key = token.substr(0, eqPos); const std::string value = token.substr(eqPos + 1); - if (key == "materials" || key == "materialPaths") { + if (key == "materialPaths") { m_materialPaths = SplitMaterialPaths(value); m_materials.resize(m_materialPaths.size()); m_materialRefs.resize(m_materialPaths.size()); @@ -362,15 +361,12 @@ void MeshRendererComponent::Deserialize(std::istream& is) { } } + if (!HasVirtualPathScheme(m_materialPaths[i])) { + m_materialPaths[i].clear(); + continue; + } + if (Resources::ResourceManager::Get().IsDeferredSceneLoadEnabled()) { - if (!m_materialPaths[i].empty()) { - if (!m_materialRefs[i].IsValid() && - !Resources::ResourceManager::Get().TryGetAssetRef(m_materialPaths[i].c_str(), - Resources::ResourceType::Material, - m_materialRefs[i])) { - m_materialRefs[i].Reset(); - } - } continue; } diff --git a/engine/src/RHI/D3D12/D3D12PipelineState.cpp b/engine/src/RHI/D3D12/D3D12PipelineState.cpp index 8cb3e572..aafe2605 100644 --- a/engine/src/RHI/D3D12/D3D12PipelineState.cpp +++ b/engine/src/RHI/D3D12/D3D12PipelineState.cpp @@ -1,11 +1,76 @@ #include "XCEngine/RHI/D3D12/D3D12PipelineState.h" #include "XCEngine/RHI/D3D12/D3D12Shader.h" #include "XCEngine/Debug/Logger.h" +#include #include +#include namespace XCEngine { namespace RHI { +namespace { + +void LogD3D12InfoQueueMessages(ID3D12Device* device) { + if (device == nullptr) { + return; + } + + ComPtr infoQueue; + if (FAILED(device->QueryInterface(IID_PPV_ARGS(&infoQueue))) || infoQueue == nullptr) { + return; + } + + const UINT64 messageCount = infoQueue->GetNumStoredMessagesAllowedByRetrievalFilter(); + if (messageCount == 0) { + return; + } + + const UINT64 firstMessage = messageCount > 12 ? messageCount - 12 : 0; + for (UINT64 messageIndex = firstMessage; messageIndex < messageCount; ++messageIndex) { + SIZE_T messageLength = 0; + if (FAILED(infoQueue->GetMessage(messageIndex, nullptr, &messageLength)) || messageLength == 0) { + continue; + } + + std::vector storage(messageLength); + auto* message = reinterpret_cast(storage.data()); + if (FAILED(infoQueue->GetMessage(messageIndex, message, &messageLength)) || message == nullptr) { + continue; + } + + if (message->pDescription != nullptr && message->pDescription[0] != '\0') { + Debug::Logger::Get().Error(Debug::LogCategory::Rendering, message->pDescription); + } + } + + infoQueue->ClearStoredMessages(); +} + +void LogGraphicsPipelineFailureDetails( + ID3D12Device* device, + HRESULT pipelineCreateResult, + const D3D12_GRAPHICS_PIPELINE_STATE_DESC& desc) { + char details[512] = {}; + sprintf_s( + details, + "D3D12 PSO failure details: createHr=0x%08X removedReason=0x%08X rootSignature=%p rtCount=%u rtv0=0x%X dsv=0x%X sampleCount=%u vsBytes=%zu psBytes=%zu gsBytes=%zu", + static_cast(pipelineCreateResult), + static_cast(device != nullptr ? device->GetDeviceRemovedReason() : S_OK), + desc.pRootSignature, + desc.NumRenderTargets, + static_cast(desc.RTVFormats[0]), + static_cast(desc.DSVFormat), + desc.SampleDesc.Count, + static_cast(desc.VS.BytecodeLength), + static_cast(desc.PS.BytecodeLength), + static_cast(desc.GS.BytecodeLength)); + Debug::Logger::Get().Error(Debug::LogCategory::Rendering, details); + + LogD3D12InfoQueueMessages(device); +} + +} // namespace + D3D12PipelineState::D3D12PipelineState(ID3D12Device* device) : m_device(device), m_finalized(false) { m_renderTargetFormats[0] = static_cast(Format::R8G8B8A8_UNorm); @@ -176,6 +241,9 @@ bool D3D12PipelineState::CreateD3D12PSO() { desc.RasterizerState.DepthClipEnable = m_rasterizerDesc.depthClipEnable; desc.RasterizerState.MultisampleEnable = m_rasterizerDesc.multisampleEnable; desc.RasterizerState.AntialiasedLineEnable = m_rasterizerDesc.antialiasedLineEnable; + desc.RasterizerState.DepthBias = m_rasterizerDesc.depthBias; + desc.RasterizerState.DepthBiasClamp = m_rasterizerDesc.depthBiasClamp; + desc.RasterizerState.SlopeScaledDepthBias = m_rasterizerDesc.slopeScaledDepthBias; desc.BlendState.RenderTarget[0].BlendEnable = m_blendDesc.blendEnable; desc.BlendState.RenderTarget[0].SrcBlend = ToD3D12(static_cast(m_blendDesc.srcBlend)); @@ -216,6 +284,7 @@ bool D3D12PipelineState::CreateD3D12PSO() { char errorMessage[256] = {}; sprintf_s(errorMessage, "D3D12 CreateGraphicsPipelineState failed: hr=0x%08X", static_cast(hr)); Debug::Logger::Get().Error(Debug::LogCategory::Rendering, errorMessage); + LogGraphicsPipelineFailureDetails(m_device, hr, desc); return false; } diff --git a/engine/src/RHI/OpenGL/OpenGLCommandList.cpp b/engine/src/RHI/OpenGL/OpenGLCommandList.cpp index 7abd5934..517f32e5 100644 --- a/engine/src/RHI/OpenGL/OpenGLCommandList.cpp +++ b/engine/src/RHI/OpenGL/OpenGLCommandList.cpp @@ -319,17 +319,26 @@ void OpenGLCommandList::SetPrimitiveType(PrimitiveType type) { void OpenGLCommandList::Draw(PrimitiveType type, unsigned int vertexCount, unsigned int startVertex) { m_primitiveType = ToOpenGL(type); + if (m_currentVAO == 0) { + EnsureInternalVertexArrayBound(); + } glDrawArrays(m_primitiveType, startVertex, vertexCount); } void OpenGLCommandList::DrawInstanced(PrimitiveType type, unsigned int vertexCount, unsigned int instanceCount, unsigned int startVertex, unsigned int startInstance) { m_primitiveType = ToOpenGL(type); + if (m_currentVAO == 0) { + EnsureInternalVertexArrayBound(); + } glDrawArraysInstanced(m_primitiveType, startVertex, vertexCount, instanceCount); (void)startInstance; } void OpenGLCommandList::DrawIndexed(PrimitiveType type, unsigned int indexCount, unsigned int startIndex, int baseVertex) { m_primitiveType = ToOpenGL(type); + if (m_currentVAO == 0) { + EnsureInternalVertexArrayBound(); + } const uint64_t indexOffset = m_currentIndexOffset + static_cast(startIndex) * GetIndexTypeSize(m_currentIndexType); glDrawElements(m_primitiveType, indexCount, @@ -340,6 +349,9 @@ void OpenGLCommandList::DrawIndexed(PrimitiveType type, unsigned int indexCount, void OpenGLCommandList::DrawIndexedInstanced(PrimitiveType type, unsigned int indexCount, unsigned int instanceCount, unsigned int startIndex, int baseVertex, unsigned int startInstance) { m_primitiveType = ToOpenGL(type); + if (m_currentVAO == 0) { + EnsureInternalVertexArrayBound(); + } const uint64_t indexOffset = m_currentIndexOffset + static_cast(startIndex) * GetIndexTypeSize(m_currentIndexType); glDrawElementsInstanced(m_primitiveType, indexCount, @@ -1055,6 +1067,9 @@ void OpenGLCommandList::CopyResource(RHIResourceView* dst, RHIResourceView* src) } void OpenGLCommandList::Draw(uint32_t vertexCount, uint32_t instanceCount, uint32_t startVertex, uint32_t startInstance) { + if (m_currentVAO == 0) { + EnsureInternalVertexArrayBound(); + } glDrawArraysInstanced(m_primitiveType, static_cast(startVertex), static_cast(vertexCount), @@ -1063,6 +1078,9 @@ void OpenGLCommandList::Draw(uint32_t vertexCount, uint32_t instanceCount, uint3 } void OpenGLCommandList::DrawIndexed(uint32_t indexCount, uint32_t instanceCount, uint32_t startIndex, int32_t baseVertex, uint32_t startInstance) { + if (m_currentVAO == 0) { + EnsureInternalVertexArrayBound(); + } const uint64_t indexOffset = m_currentIndexOffset + static_cast(startIndex) * GetIndexTypeSize(m_currentIndexType); glDrawElementsInstanced(m_primitiveType, static_cast(indexCount), @@ -1078,7 +1096,23 @@ void OpenGLCommandList::SetPrimitiveTopology(PrimitiveTopology topology) { } void OpenGLCommandList::SetStencilRef(uint8_t ref) { - glStencilFunc(GL_FRONT_AND_BACK, ref, 0xFF); + if (m_currentPipelineState == nullptr) { + glStencilFuncSeparate(GL_FRONT, GL_ALWAYS, ref, 0xFF); + glStencilFuncSeparate(GL_BACK, GL_ALWAYS, ref, 0xFF); + return; + } + + const OpenGLDepthStencilState& state = m_currentPipelineState->GetOpenGLDepthStencilState(); + glStencilFuncSeparate( + GL_FRONT, + ToOpenGL(state.frontStencilFunc), + ref, + state.stencilReadMask); + glStencilFuncSeparate( + GL_BACK, + ToOpenGL(state.backStencilFunc), + ref, + state.stencilReadMask); } void OpenGLCommandList::SetBlendFactor(const float factor[4]) { diff --git a/engine/src/RHI/OpenGL/OpenGLPipelineState.cpp b/engine/src/RHI/OpenGL/OpenGLPipelineState.cpp index 2899ad3c..1c409e3f 100644 --- a/engine/src/RHI/OpenGL/OpenGLPipelineState.cpp +++ b/engine/src/RHI/OpenGL/OpenGLPipelineState.cpp @@ -28,7 +28,7 @@ void OpenGLPipelineState::SetRasterizerState(const RasterizerDesc& state) { m_glRasterizerState.scissorTestEnable = state.scissorTestEnable; m_glRasterizerState.multisampleEnable = state.multisampleEnable; m_glRasterizerState.polygonOffsetFactor = state.slopeScaledDepthBias; - m_glRasterizerState.polygonOffsetUnits = state.depthBiasClamp; + m_glRasterizerState.polygonOffsetUnits = static_cast(state.depthBias); } void OpenGLPipelineState::SetBlendState(const BlendDesc& state) { @@ -52,10 +52,14 @@ void OpenGLPipelineState::SetDepthStencilState(const DepthStencilStateDesc& stat m_glDepthStencilState.stencilEnable = state.stencilEnable; m_glDepthStencilState.stencilReadMask = state.stencilReadMask; m_glDepthStencilState.stencilWriteMask = state.stencilWriteMask; - m_glDepthStencilState.stencilFunc = static_cast(state.front.func); - m_glDepthStencilState.stencilFailOp = static_cast(state.front.failOp); - m_glDepthStencilState.stencilDepthFailOp = static_cast(state.front.depthFailOp); - m_glDepthStencilState.stencilDepthPassOp = static_cast(state.front.passOp); + m_glDepthStencilState.frontStencilFunc = static_cast(state.front.func); + m_glDepthStencilState.frontStencilFailOp = static_cast(state.front.failOp); + m_glDepthStencilState.frontStencilDepthFailOp = static_cast(state.front.depthFailOp); + m_glDepthStencilState.frontStencilDepthPassOp = static_cast(state.front.passOp); + m_glDepthStencilState.backStencilFunc = static_cast(state.back.func); + m_glDepthStencilState.backStencilFailOp = static_cast(state.back.failOp); + m_glDepthStencilState.backStencilDepthFailOp = static_cast(state.back.depthFailOp); + m_glDepthStencilState.backStencilDepthPassOp = static_cast(state.back.passOp); } void OpenGLPipelineState::SetTopology(uint32_t topologyType) { @@ -121,16 +125,31 @@ void OpenGLPipelineState::ApplyDepthStencil() { if (m_glDepthStencilState.stencilEnable) { glEnable(GL_STENCIL_TEST); - glStencilMask(m_glDepthStencilState.stencilWriteMask); - glStencilFunc( - ToOpenGL(m_glDepthStencilState.stencilFunc), + glStencilMaskSeparate(GL_FRONT, m_glDepthStencilState.stencilWriteMask); + glStencilMaskSeparate(GL_BACK, m_glDepthStencilState.stencilWriteMask); + glStencilFuncSeparate( + GL_FRONT, + ToOpenGL(m_glDepthStencilState.frontStencilFunc), m_glDepthStencilState.stencilRef, m_glDepthStencilState.stencilReadMask ); - glStencilOp( - ToOpenGL(m_glDepthStencilState.stencilFailOp), - ToOpenGL(m_glDepthStencilState.stencilDepthFailOp), - ToOpenGL(m_glDepthStencilState.stencilDepthPassOp) + glStencilOpSeparate( + GL_FRONT, + ToOpenGL(m_glDepthStencilState.frontStencilFailOp), + ToOpenGL(m_glDepthStencilState.frontStencilDepthFailOp), + ToOpenGL(m_glDepthStencilState.frontStencilDepthPassOp) + ); + glStencilFuncSeparate( + GL_BACK, + ToOpenGL(m_glDepthStencilState.backStencilFunc), + m_glDepthStencilState.stencilRef, + m_glDepthStencilState.stencilReadMask + ); + glStencilOpSeparate( + GL_BACK, + ToOpenGL(m_glDepthStencilState.backStencilFailOp), + ToOpenGL(m_glDepthStencilState.backStencilDepthFailOp), + ToOpenGL(m_glDepthStencilState.backStencilDepthPassOp) ); } else { glDisable(GL_STENCIL_TEST); @@ -177,6 +196,18 @@ void OpenGLPipelineState::ApplyRasterizer() { } else { glDisable(GL_MULTISAMPLE); } + + const bool polygonOffsetEnable = + m_glRasterizerState.polygonOffsetFactor != 0.0f || + m_glRasterizerState.polygonOffsetUnits != 0.0f; + if (polygonOffsetEnable) { + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset( + m_glRasterizerState.polygonOffsetFactor, + m_glRasterizerState.polygonOffsetUnits); + } else { + glDisable(GL_POLYGON_OFFSET_FILL); + } } void OpenGLPipelineState::ApplyViewport() { diff --git a/engine/src/RHI/Vulkan/VulkanCommandList.cpp b/engine/src/RHI/Vulkan/VulkanCommandList.cpp index 76ee691f..33ceb51a 100644 --- a/engine/src/RHI/Vulkan/VulkanCommandList.cpp +++ b/engine/src/RHI/Vulkan/VulkanCommandList.cpp @@ -305,6 +305,7 @@ void VulkanCommandList::Reset() { m_renderPassActive = false; m_hasViewport = false; m_hasScissor = false; + m_stencilRef = 0; VkCommandBufferBeginInfo beginInfo = {}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; @@ -586,7 +587,10 @@ void VulkanCommandList::SetRenderTargets(uint32_t count, RHIResourceView** rende } void VulkanCommandList::SetStencilRef(uint8_t ref) { - (void)ref; + m_stencilRef = ref; + if (m_renderPassActive && m_currentPipelineState != nullptr) { + vkCmdSetStencilReference(m_commandBuffer, VK_STENCIL_FRONT_AND_BACK, m_stencilRef); + } } void VulkanCommandList::SetBlendFactor(const float factor[4]) { @@ -643,6 +647,9 @@ void VulkanCommandList::Draw(uint32_t vertexCount, uint32_t instanceCount, uint3 } vkCmdBindPipeline(m_commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_currentPipelineState->GetPipeline()); + if (m_currentPipelineState->GetDepthStencilState().stencilEnable) { + vkCmdSetStencilReference(m_commandBuffer, VK_STENCIL_FRONT_AND_BACK, m_stencilRef); + } vkCmdDraw(m_commandBuffer, vertexCount, instanceCount, startVertex, startInstance); } @@ -652,6 +659,9 @@ void VulkanCommandList::DrawIndexed(uint32_t indexCount, uint32_t instanceCount, } vkCmdBindPipeline(m_commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_currentPipelineState->GetPipeline()); + if (m_currentPipelineState->GetDepthStencilState().stencilEnable) { + vkCmdSetStencilReference(m_commandBuffer, VK_STENCIL_FRONT_AND_BACK, m_stencilRef); + } vkCmdDrawIndexed(m_commandBuffer, indexCount, instanceCount, startIndex, baseVertex, startInstance); } diff --git a/engine/src/RHI/Vulkan/VulkanPipelineState.cpp b/engine/src/RHI/Vulkan/VulkanPipelineState.cpp index e4110688..66b3c4c5 100644 --- a/engine/src/RHI/Vulkan/VulkanPipelineState.cpp +++ b/engine/src/RHI/Vulkan/VulkanPipelineState.cpp @@ -34,6 +34,40 @@ VkShaderModule CreateShaderModule(VkDevice device, const std::vector& return module; } +VkStencilOp ToVulkanStencilOp(StencilOp op) { + switch (op) { + case StencilOp::Zero: + return VK_STENCIL_OP_ZERO; + case StencilOp::Replace: + return VK_STENCIL_OP_REPLACE; + case StencilOp::IncrSat: + return VK_STENCIL_OP_INCREMENT_AND_CLAMP; + case StencilOp::DecrSat: + return VK_STENCIL_OP_DECREMENT_AND_CLAMP; + case StencilOp::Invert: + return VK_STENCIL_OP_INVERT; + case StencilOp::Incr: + return VK_STENCIL_OP_INCREMENT_AND_WRAP; + case StencilOp::Decr: + return VK_STENCIL_OP_DECREMENT_AND_WRAP; + case StencilOp::Keep: + default: + return VK_STENCIL_OP_KEEP; + } +} + +VkStencilOpState BuildVulkanStencilOpState(const StencilOpDesc& desc) { + VkStencilOpState state = {}; + state.failOp = ToVulkanStencilOp(static_cast(desc.failOp)); + state.passOp = ToVulkanStencilOp(static_cast(desc.passOp)); + state.depthFailOp = ToVulkanStencilOp(static_cast(desc.depthFailOp)); + state.compareOp = ToVulkanCompareOp(static_cast(desc.func)); + state.compareMask = 0xFFu; + state.writeMask = 0xFFu; + state.reference = 0u; + return state; +} + } // namespace VulkanPipelineState::~VulkanPipelineState() { @@ -303,10 +337,17 @@ bool VulkanPipelineState::CreateGraphicsPipeline(const GraphicsPipelineDesc& des depthStencil.depthCompareOp = ToVulkanCompareOp(static_cast(m_depthStencilDesc.depthFunc)); depthStencil.depthBoundsTestEnable = m_depthStencilDesc.depthBoundsEnable ? VK_TRUE : VK_FALSE; depthStencil.stencilTestEnable = m_depthStencilDesc.stencilEnable ? VK_TRUE : VK_FALSE; + depthStencil.front = BuildVulkanStencilOpState(m_depthStencilDesc.front); + depthStencil.back = BuildVulkanStencilOpState(m_depthStencilDesc.back); + depthStencil.front.compareMask = m_depthStencilDesc.stencilReadMask; + depthStencil.front.writeMask = m_depthStencilDesc.stencilWriteMask; + depthStencil.back.compareMask = m_depthStencilDesc.stencilReadMask; + depthStencil.back.writeMask = m_depthStencilDesc.stencilWriteMask; VkDynamicState dynamicStates[] = { VK_DYNAMIC_STATE_VIEWPORT, - VK_DYNAMIC_STATE_SCISSOR + VK_DYNAMIC_STATE_SCISSOR, + VK_DYNAMIC_STATE_STENCIL_REFERENCE }; VkPipelineDynamicStateCreateInfo dynamicState = {}; dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; diff --git a/engine/src/Rendering/Passes/BuiltinColorScalePostProcessPass.cpp b/engine/src/Rendering/Passes/BuiltinColorScalePostProcessPass.cpp index ea2a1d76..56bd18e5 100644 --- a/engine/src/Rendering/Passes/BuiltinColorScalePostProcessPass.cpp +++ b/engine/src/Rendering/Passes/BuiltinColorScalePostProcessPass.cpp @@ -75,14 +75,27 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc( pipelineDesc.depthStencilState.depthWriteEnable = false; pipelineDesc.depthStencilState.depthFunc = static_cast(RHI::ComparisonFunc::Always); + const Resources::ShaderPass* shaderPass = shader.FindPass(passName); const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType); if (const Resources::ShaderStageVariant* vertexVariant = shader.FindVariant(passName, Resources::ShaderType::Vertex, backend)) { - ::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*vertexVariant, pipelineDesc.vertexShader); + if (shaderPass != nullptr) { + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant( + *shaderPass, + backend, + *vertexVariant, + pipelineDesc.vertexShader); + } } if (const Resources::ShaderStageVariant* fragmentVariant = shader.FindVariant(passName, Resources::ShaderType::Fragment, backend)) { - ::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*fragmentVariant, pipelineDesc.fragmentShader); + if (shaderPass != nullptr) { + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant( + *shaderPass, + backend, + *fragmentVariant, + pipelineDesc.fragmentShader); + } } return pipelineDesc; diff --git a/engine/src/Rendering/Passes/BuiltinFinalColorPass.cpp b/engine/src/Rendering/Passes/BuiltinFinalColorPass.cpp index 0b3eef48..134a8096 100644 --- a/engine/src/Rendering/Passes/BuiltinFinalColorPass.cpp +++ b/engine/src/Rendering/Passes/BuiltinFinalColorPass.cpp @@ -77,14 +77,27 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc( pipelineDesc.depthStencilState.depthWriteEnable = false; pipelineDesc.depthStencilState.depthFunc = static_cast(RHI::ComparisonFunc::Always); + const Resources::ShaderPass* shaderPass = shader.FindPass(passName); const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType); if (const Resources::ShaderStageVariant* vertexVariant = shader.FindVariant(passName, Resources::ShaderType::Vertex, backend)) { - ::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*vertexVariant, pipelineDesc.vertexShader); + if (shaderPass != nullptr) { + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant( + *shaderPass, + backend, + *vertexVariant, + pipelineDesc.vertexShader); + } } if (const Resources::ShaderStageVariant* fragmentVariant = shader.FindVariant(passName, Resources::ShaderType::Fragment, backend)) { - ::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*fragmentVariant, pipelineDesc.fragmentShader); + if (shaderPass != nullptr) { + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant( + *shaderPass, + backend, + *fragmentVariant, + pipelineDesc.fragmentShader); + } } return pipelineDesc; diff --git a/engine/src/Rendering/Passes/BuiltinInfiniteGridPass.cpp b/engine/src/Rendering/Passes/BuiltinInfiniteGridPass.cpp index e2dfe250..84986ead 100644 --- a/engine/src/Rendering/Passes/BuiltinInfiniteGridPass.cpp +++ b/engine/src/Rendering/Passes/BuiltinInfiniteGridPass.cpp @@ -7,6 +7,7 @@ #include #include +#include "Rendering/Materials/RenderMaterialStateUtils.h" #include "Rendering/Detail/ShaderVariantUtils.h" #include @@ -332,35 +333,50 @@ bool BuiltinInfiniteGridPass::CreateResources(const RenderContext& renderContext pipelineDesc.depthStencilFormat = static_cast(RHI::Format::D24_UNorm_S8_UInt); pipelineDesc.sampleCount = 1; - pipelineDesc.rasterizerState.fillMode = static_cast(RHI::FillMode::Solid); - pipelineDesc.rasterizerState.cullMode = static_cast(RHI::CullMode::None); - pipelineDesc.rasterizerState.frontFace = static_cast(RHI::FrontFace::CounterClockwise); - pipelineDesc.rasterizerState.depthClipEnable = true; - - pipelineDesc.blendState.blendEnable = true; - pipelineDesc.blendState.srcBlend = static_cast(RHI::BlendFactor::SrcAlpha); - pipelineDesc.blendState.dstBlend = static_cast(RHI::BlendFactor::InvSrcAlpha); - pipelineDesc.blendState.srcBlendAlpha = static_cast(RHI::BlendFactor::One); - pipelineDesc.blendState.dstBlendAlpha = static_cast(RHI::BlendFactor::InvSrcAlpha); - pipelineDesc.blendState.blendOp = static_cast(RHI::BlendOp::Add); - pipelineDesc.blendState.blendOpAlpha = static_cast(RHI::BlendOp::Add); - pipelineDesc.blendState.colorWriteMask = 0xF; - - pipelineDesc.depthStencilState.depthTestEnable = true; - pipelineDesc.depthStencilState.depthWriteEnable = false; - pipelineDesc.depthStencilState.depthFunc = static_cast(RHI::ComparisonFunc::LessEqual); + if (infiniteGridPass->hasFixedFunctionState) { + ::XCEngine::Rendering::ApplyRenderState(infiniteGridPass->fixedFunctionState, pipelineDesc); + } else { + Resources::MaterialRenderState fallbackState = {}; + fallbackState.cullMode = Resources::MaterialCullMode::None; + fallbackState.depthWriteEnable = false; + fallbackState.depthTestEnable = true; + fallbackState.depthFunc = Resources::MaterialComparisonFunc::LessEqual; + fallbackState.blendEnable = true; + fallbackState.srcBlend = Resources::MaterialBlendFactor::SrcAlpha; + fallbackState.dstBlend = Resources::MaterialBlendFactor::InvSrcAlpha; + fallbackState.srcBlendAlpha = Resources::MaterialBlendFactor::One; + fallbackState.dstBlendAlpha = Resources::MaterialBlendFactor::InvSrcAlpha; + fallbackState.blendOp = Resources::MaterialBlendOp::Add; + fallbackState.blendOpAlpha = Resources::MaterialBlendOp::Add; + fallbackState.colorWriteMask = static_cast(RHI::ColorWriteMask::All); + ::XCEngine::Rendering::ApplyRenderState(fallbackState, pipelineDesc); + } if (const Resources::ShaderStageVariant* vertexVariant = m_builtinInfiniteGridShader->FindVariant( infiniteGridPass->name, Resources::ShaderType::Vertex, backend)) { - ::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*vertexVariant, pipelineDesc.vertexShader); + const Resources::ShaderPass* shaderPass = m_builtinInfiniteGridShader->FindPass(infiniteGridPass->name); + if (shaderPass != nullptr) { + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant( + *shaderPass, + backend, + *vertexVariant, + pipelineDesc.vertexShader); + } } if (const Resources::ShaderStageVariant* fragmentVariant = m_builtinInfiniteGridShader->FindVariant( infiniteGridPass->name, Resources::ShaderType::Fragment, backend)) { - ::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*fragmentVariant, pipelineDesc.fragmentShader); + const Resources::ShaderPass* shaderPass = m_builtinInfiniteGridShader->FindPass(infiniteGridPass->name); + if (shaderPass != nullptr) { + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant( + *shaderPass, + backend, + *fragmentVariant, + pipelineDesc.fragmentShader); + } } m_pipelineState = m_device->CreatePipelineState(pipelineDesc); diff --git a/engine/src/Rendering/Passes/BuiltinObjectIdOutlinePass.cpp b/engine/src/Rendering/Passes/BuiltinObjectIdOutlinePass.cpp index 7e3f0314..436e8523 100644 --- a/engine/src/Rendering/Passes/BuiltinObjectIdOutlinePass.cpp +++ b/engine/src/Rendering/Passes/BuiltinObjectIdOutlinePass.cpp @@ -4,8 +4,10 @@ #include "Debug/Logger.h" #include "Rendering/Picking/ObjectIdCodec.h" #include "Rendering/Detail/ShaderVariantUtils.h" +#include "Rendering/Materials/RenderMaterialStateUtils.h" #include "RHI/RHICommandList.h" #include "RHI/RHIDevice.h" +#include #include #include @@ -52,32 +54,46 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc( pipelineDesc.depthStencilFormat = static_cast(RHI::Format::Unknown); pipelineDesc.sampleCount = 1; - pipelineDesc.rasterizerState.fillMode = static_cast(RHI::FillMode::Solid); - pipelineDesc.rasterizerState.cullMode = static_cast(RHI::CullMode::None); - pipelineDesc.rasterizerState.frontFace = static_cast(RHI::FrontFace::CounterClockwise); - pipelineDesc.rasterizerState.depthClipEnable = true; - - pipelineDesc.blendState.blendEnable = true; - pipelineDesc.blendState.srcBlend = static_cast(RHI::BlendFactor::SrcAlpha); - pipelineDesc.blendState.dstBlend = static_cast(RHI::BlendFactor::InvSrcAlpha); - pipelineDesc.blendState.srcBlendAlpha = static_cast(RHI::BlendFactor::One); - pipelineDesc.blendState.dstBlendAlpha = static_cast(RHI::BlendFactor::InvSrcAlpha); - pipelineDesc.blendState.blendOp = static_cast(RHI::BlendOp::Add); - pipelineDesc.blendState.blendOpAlpha = static_cast(RHI::BlendOp::Add); - pipelineDesc.blendState.colorWriteMask = static_cast(RHI::ColorWriteMask::All); - - pipelineDesc.depthStencilState.depthTestEnable = false; - pipelineDesc.depthStencilState.depthWriteEnable = false; - pipelineDesc.depthStencilState.depthFunc = static_cast(RHI::ComparisonFunc::Always); + const Resources::ShaderPass* shaderPass = shader.FindPass(passName); + if (shaderPass != nullptr && shaderPass->hasFixedFunctionState) { + ::XCEngine::Rendering::ApplyRenderState(shaderPass->fixedFunctionState, pipelineDesc); + } else { + Resources::MaterialRenderState fallbackState = {}; + fallbackState.cullMode = Resources::MaterialCullMode::None; + fallbackState.depthWriteEnable = false; + fallbackState.depthTestEnable = false; + fallbackState.depthFunc = Resources::MaterialComparisonFunc::Always; + fallbackState.blendEnable = true; + fallbackState.srcBlend = Resources::MaterialBlendFactor::SrcAlpha; + fallbackState.dstBlend = Resources::MaterialBlendFactor::InvSrcAlpha; + fallbackState.srcBlendAlpha = Resources::MaterialBlendFactor::One; + fallbackState.dstBlendAlpha = Resources::MaterialBlendFactor::InvSrcAlpha; + fallbackState.blendOp = Resources::MaterialBlendOp::Add; + fallbackState.blendOpAlpha = Resources::MaterialBlendOp::Add; + fallbackState.colorWriteMask = static_cast(RHI::ColorWriteMask::All); + ::XCEngine::Rendering::ApplyRenderState(fallbackState, pipelineDesc); + } const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType); if (const Resources::ShaderStageVariant* vertexVariant = shader.FindVariant(passName, Resources::ShaderType::Vertex, backend)) { - ::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*vertexVariant, pipelineDesc.vertexShader); + if (shaderPass != nullptr) { + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant( + *shaderPass, + backend, + *vertexVariant, + pipelineDesc.vertexShader); + } } if (const Resources::ShaderStageVariant* fragmentVariant = shader.FindVariant(passName, Resources::ShaderType::Fragment, backend)) { - ::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*fragmentVariant, pipelineDesc.fragmentShader); + if (shaderPass != nullptr) { + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant( + *shaderPass, + backend, + *fragmentVariant, + pipelineDesc.fragmentShader); + } } return pipelineDesc; @@ -86,7 +102,7 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc( } // namespace BuiltinObjectIdOutlinePass::BuiltinObjectIdOutlinePass(Containers::String shaderPath) - : m_shaderPath(std::move(shaderPath)) { + : m_shaderPath(shaderPath.Empty() ? Resources::GetBuiltinObjectIdOutlineShaderPath() : std::move(shaderPath)) { ResetState(); } diff --git a/engine/src/Rendering/Passes/BuiltinObjectIdPassResources.cpp b/engine/src/Rendering/Passes/BuiltinObjectIdPassResources.cpp index d0db32f1..d6e8b381 100644 --- a/engine/src/Rendering/Passes/BuiltinObjectIdPassResources.cpp +++ b/engine/src/Rendering/Passes/BuiltinObjectIdPassResources.cpp @@ -77,14 +77,27 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc( pipelineDesc.depthStencilState.depthWriteEnable = true; pipelineDesc.depthStencilState.depthFunc = static_cast(RHI::ComparisonFunc::LessEqual); + const Resources::ShaderPass* shaderPass = shader.FindPass(passName); const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType); if (const Resources::ShaderStageVariant* vertexVariant = shader.FindVariant(passName, Resources::ShaderType::Vertex, backend)) { - ::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*vertexVariant, pipelineDesc.vertexShader); + if (shaderPass != nullptr) { + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant( + *shaderPass, + backend, + *vertexVariant, + pipelineDesc.vertexShader); + } } if (const Resources::ShaderStageVariant* fragmentVariant = shader.FindVariant(passName, Resources::ShaderType::Fragment, backend)) { - ::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*fragmentVariant, pipelineDesc.fragmentShader); + if (shaderPass != nullptr) { + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant( + *shaderPass, + backend, + *fragmentVariant, + pipelineDesc.fragmentShader); + } } return pipelineDesc; diff --git a/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp b/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp index 981f7382..0e9eee3e 100644 --- a/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp +++ b/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp @@ -146,14 +146,27 @@ RHI::GraphicsPipelineDesc CreateSkyboxPipelineDesc( pipelineDesc.depthStencilState.depthWriteEnable = false; pipelineDesc.depthStencilState.depthFunc = static_cast(RHI::ComparisonFunc::LessEqual); + const Resources::ShaderPass* shaderPass = shader.FindPass(passName); const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType); if (const Resources::ShaderStageVariant* vertexVariant = shader.FindVariant(passName, Resources::ShaderType::Vertex, backend)) { - ::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*vertexVariant, pipelineDesc.vertexShader); + if (shaderPass != nullptr) { + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant( + *shaderPass, + backend, + *vertexVariant, + pipelineDesc.vertexShader); + } } if (const Resources::ShaderStageVariant* fragmentVariant = shader.FindVariant(passName, Resources::ShaderType::Fragment, backend)) { - ::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*fragmentVariant, pipelineDesc.fragmentShader); + if (shaderPass != nullptr) { + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant( + *shaderPass, + backend, + *fragmentVariant, + pipelineDesc.fragmentShader); + } } return pipelineDesc; diff --git a/engine/src/Resources/Material/Material.cpp b/engine/src/Resources/Material/Material.cpp index 82547167..033ac46d 100644 --- a/engine/src/Resources/Material/Material.cpp +++ b/engine/src/Resources/Material/Material.cpp @@ -324,7 +324,6 @@ void Material::Release() { m_renderQueue = static_cast(MaterialRenderQueue::Geometry); m_renderState = MaterialRenderState(); m_hasRenderStateOverride = false; - m_legacyShaderPassHint.Clear(); m_tags.Clear(); m_keywordSet.enabledKeywords.Clear(); m_properties.Clear(); @@ -363,24 +362,6 @@ void Material::SetRenderStateOverrideEnabled(bool enabled) { MarkChanged(false); } -void Material::SetLegacyShaderPassHint(const Containers::String& shaderPass) { - m_legacyShaderPassHint = shaderPass; - MarkChanged(false); -} - -void Material::ClearLegacyShaderPassHint() { - if (m_legacyShaderPassHint.Empty()) { - return; - } - - m_legacyShaderPassHint.Clear(); - MarkChanged(false); -} - -void Material::SetShaderPass(const Containers::String& shaderPass) { - SetLegacyShaderPassHint(shaderPass); -} - void Material::SetTag(const Containers::String& name, const Containers::String& value) { for (MaterialTagEntry& tag : m_tags) { if (tag.name == name) { @@ -1120,7 +1101,6 @@ void Material::UpdateMemorySize() { m_memorySize = m_constantBufferData.Size() + m_constantLayout.Size() * sizeof(MaterialConstantFieldDesc) + sizeof(MaterialRenderState) + - m_legacyShaderPassHint.Length() + m_tags.Size() * sizeof(MaterialTagEntry) + m_keywordSet.enabledKeywords.Size() * sizeof(Containers::String) + m_textureBindings.Size() * sizeof(MaterialTextureBinding) + diff --git a/engine/src/Resources/Shader/Internal/ShaderAuthoringInternal.h b/engine/src/Resources/Shader/Internal/ShaderAuthoringInternal.h index 54bfb049..f502c56c 100644 --- a/engine/src/Resources/Shader/Internal/ShaderAuthoringInternal.h +++ b/engine/src/Resources/Shader/Internal/ShaderAuthoringInternal.h @@ -42,10 +42,25 @@ bool TryParseAuthoringBoolDirectiveToken(const std::string& token, bool& outValu bool TryParseAuthoringCullMode(const std::string& token, MaterialCullMode& outMode); bool TryParseAuthoringComparisonFunc(const std::string& token, MaterialComparisonFunc& outFunc); bool TryParseAuthoringBlendFactor(const std::string& token, MaterialBlendFactor& outFactor); +bool TryParseAuthoringBlendOp(const std::string& token, MaterialBlendOp& outOp); +bool TryParseAuthoringStencilOp(const std::string& token, MaterialStencilOp& outOp); bool TryParseAuthoringColorMask(const std::string& token, Core::uint8& outMask); bool TryParseAuthoringBlendDirective( const std::vector& tokens, MaterialRenderState& outState); +bool TryParseAuthoringBlendOpDirective( + const std::vector& tokens, + MaterialRenderState& outState); +bool TryParseAuthoringOffsetDirective( + const std::vector& tokens, + MaterialRenderState& outState); +bool TryParseAuthoringStencilDirective( + const std::vector& tokens, + MaterialStencilState& outState); +bool TryParseAuthoringUsePassReference( + const Containers::String& reference, + Containers::String& outShaderName, + Containers::String& outPassName); void SetOrReplaceAuthoringTag( std::vector& tags, diff --git a/engine/src/Resources/Shader/Internal/ShaderManifestLoader.cpp b/engine/src/Resources/Shader/Internal/ShaderManifestLoader.cpp deleted file mode 100644 index 1f3f2a33..00000000 --- a/engine/src/Resources/Shader/Internal/ShaderManifestLoader.cpp +++ /dev/null @@ -1,518 +0,0 @@ -#include "ShaderManifestLoader.h" - -#include "ShaderFileUtils.h" -#include "ShaderRuntimeBuildUtils.h" -#include "../ShaderSourceUtils.h" - -#include -#include -#include -#include -#include -#include - -namespace XCEngine { -namespace Resources { - -namespace { - -bool FindValueStart(const std::string& json, const char* key, size_t& valuePos) { - const std::string token = std::string("\"") + key + "\""; - const size_t keyPos = json.find(token); - if (keyPos == std::string::npos) { - return false; - } - - const size_t colonPos = json.find(':', keyPos + token.length()); - if (colonPos == std::string::npos) { - return false; - } - - valuePos = SkipWhitespace(json, colonPos + 1); - return valuePos < json.size(); -} - -bool TryParseStringValue(const std::string& json, const char* key, Containers::String& outValue) { - size_t valuePos = 0; - if (!FindValueStart(json, key, valuePos)) { - return false; - } - - return ParseQuotedString(json, valuePos, outValue); -} - -bool TryExtractDelimitedValue( - const std::string& json, - const char* key, - char openChar, - char closeChar, - std::string& outValue) { - size_t valuePos = 0; - if (!FindValueStart(json, key, valuePos) || valuePos >= json.size() || json[valuePos] != openChar) { - return false; - } - - bool inString = false; - bool escaped = false; - int depth = 0; - - for (size_t pos = valuePos; pos < json.size(); ++pos) { - const char ch = json[pos]; - if (escaped) { - escaped = false; - continue; - } - - if (ch == '\\') { - escaped = true; - continue; - } - - if (ch == '"') { - inString = !inString; - continue; - } - - if (inString) { - continue; - } - - if (ch == openChar) { - ++depth; - } else if (ch == closeChar) { - --depth; - if (depth == 0) { - outValue = json.substr(valuePos, pos - valuePos + 1); - return true; - } - } - } - - return false; -} - -bool TryExtractObject(const std::string& json, const char* key, std::string& outObject) { - return TryExtractDelimitedValue(json, key, '{', '}', outObject); -} - -bool TryExtractArray(const std::string& json, const char* key, std::string& outArray) { - return TryExtractDelimitedValue(json, key, '[', ']', outArray); -} - -bool TryParseStringMapObject( - const std::string& objectText, - const std::function& onEntry) { - if (!onEntry || objectText.empty() || objectText.front() != '{' || objectText.back() != '}') { - return false; - } - - size_t pos = 1; - while (pos < objectText.size()) { - pos = SkipWhitespace(objectText, pos); - if (pos >= objectText.size()) { - return false; - } - - if (objectText[pos] == '}') { - return true; - } - - Containers::String key; - if (!ParseQuotedString(objectText, pos, key, &pos)) { - return false; - } - - pos = SkipWhitespace(objectText, pos); - if (pos >= objectText.size() || objectText[pos] != ':') { - return false; - } - - pos = SkipWhitespace(objectText, pos + 1); - Containers::String value; - if (!ParseQuotedString(objectText, pos, value, &pos)) { - return false; - } - - onEntry(key, value); - - pos = SkipWhitespace(objectText, pos); - if (pos >= objectText.size()) { - return false; - } - - if (objectText[pos] == ',') { - ++pos; - continue; - } - - if (objectText[pos] == '}') { - return true; - } - - return false; - } - - return false; -} - -bool SplitTopLevelArrayElements(const std::string& arrayText, std::vector& outElements) { - outElements.clear(); - if (arrayText.size() < 2 || arrayText.front() != '[' || arrayText.back() != ']') { - return false; - } - - bool inString = false; - bool escaped = false; - int objectDepth = 0; - int arrayDepth = 0; - size_t elementStart = std::string::npos; - - for (size_t pos = 1; pos + 1 < arrayText.size(); ++pos) { - const char ch = arrayText[pos]; - if (escaped) { - escaped = false; - continue; - } - - if (ch == '\\') { - escaped = true; - continue; - } - - if (ch == '"') { - if (elementStart == std::string::npos) { - elementStart = pos; - } - inString = !inString; - continue; - } - - if (inString) { - continue; - } - - if (std::isspace(static_cast(ch)) != 0) { - continue; - } - - if (elementStart == std::string::npos) { - elementStart = pos; - } - - if (ch == '{') { - ++objectDepth; - } else if (ch == '}') { - --objectDepth; - } else if (ch == '[') { - ++arrayDepth; - } else if (ch == ']') { - --arrayDepth; - } else if (ch == ',' && objectDepth == 0 && arrayDepth == 0) { - outElements.push_back(TrimCopy(arrayText.substr(elementStart, pos - elementStart))); - elementStart = std::string::npos; - } - } - - if (elementStart != std::string::npos) { - outElements.push_back(TrimCopy(arrayText.substr(elementStart, arrayText.size() - 1 - elementStart))); - } - - return true; -} - -bool TryParseShaderKeywordsArray( - const std::string& arrayText, - ShaderKeywordSet& outKeywordSet) { - outKeywordSet = {}; - - std::vector keywordElements; - if (!SplitTopLevelArrayElements(arrayText, keywordElements)) { - return false; - } - - for (const std::string& keywordElement : keywordElements) { - if (keywordElement.empty()) { - continue; - } - - Containers::String keyword; - size_t nextPos = 0; - if (!ParseQuotedString(keywordElement, 0, keyword, &nextPos)) { - return false; - } - - if (SkipWhitespace(keywordElement, nextPos) != keywordElement.size()) { - return false; - } - - outKeywordSet.enabledKeywords.PushBack(keyword); - } - - NormalizeShaderKeywordSetInPlace(outKeywordSet); - return true; -} - -bool TryParseUnsignedValue(const std::string& json, const char* key, Core::uint32& outValue) { - size_t valuePos = 0; - if (!FindValueStart(json, key, valuePos)) { - return false; - } - - size_t endPos = valuePos; - while (endPos < json.size() && std::isdigit(static_cast(json[endPos])) != 0) { - ++endPos; - } - - if (endPos == valuePos) { - return false; - } - - try { - outValue = static_cast(std::stoul(json.substr(valuePos, endPos - valuePos))); - return true; - } catch (...) { - return false; - } -} - -} // namespace - -bool LooksLikeShaderManifest(const std::string& sourceText) { - const size_t firstContentPos = SkipWhitespace(sourceText, 0); - return firstContentPos < sourceText.size() && - sourceText[firstContentPos] == '{' && - sourceText.find("\"passes\"") != std::string::npos; -} - -bool CollectShaderManifestDependencyPaths( - const Containers::String& path, - const std::string& jsonText, - Containers::Array& outDependencies) { - outDependencies.Clear(); - - std::string passesArray; - if (!TryExtractArray(jsonText, "passes", passesArray)) { - return false; - } - - std::vector passObjects; - if (!SplitTopLevelArrayElements(passesArray, passObjects)) { - return false; - } - - std::unordered_set seenPaths; - for (const std::string& passObject : passObjects) { - std::string variantsArray; - if (!TryExtractArray(passObject, "variants", variantsArray)) { - return false; - } - - std::vector variantObjects; - if (!SplitTopLevelArrayElements(variantsArray, variantObjects)) { - return false; - } - - for (const std::string& variantObject : variantObjects) { - Containers::String sourcePath; - if (!TryParseStringValue(variantObject, "source", sourcePath) && - !TryParseStringValue(variantObject, "sourcePath", sourcePath)) { - continue; - } - - const Containers::String resolvedPath = ResolveShaderDependencyPath(sourcePath, path); - const std::string key = ToStdString(resolvedPath); - if (!key.empty() && seenPaths.insert(key).second) { - outDependencies.PushBack(resolvedPath); - } - } - } - - return true; -} - -LoadResult LoadShaderManifest( - const Containers::String& path, - const std::string& jsonText) { - std::string passesArray; - if (!TryExtractArray(jsonText, "passes", passesArray)) { - return LoadResult("Shader manifest is missing a valid passes array: " + path); - } - - std::vector passObjects; - if (!SplitTopLevelArrayElements(passesArray, passObjects) || passObjects.empty()) { - return LoadResult("Shader manifest does not contain any pass objects: " + path); - } - - auto shader = std::make_unique(); - IResource::ConstructParams params; - params.path = path; - params.guid = ResourceGUID::Generate(path); - - Containers::String manifestName; - if (TryParseStringValue(jsonText, "name", manifestName) && !manifestName.Empty()) { - params.name = manifestName; - } else { - const std::filesystem::path shaderPath(path.CStr()); - const std::string stem = shaderPath.stem().generic_string(); - params.name = stem.empty() ? path : Containers::String(stem.c_str()); - } - - shader->Initialize(params); - - Containers::String fallback; - if (TryParseStringValue(jsonText, "fallback", fallback)) { - shader->SetFallback(fallback); - } - - std::string propertiesArray; - if (TryExtractArray(jsonText, "properties", propertiesArray)) { - std::vector propertyObjects; - if (!SplitTopLevelArrayElements(propertiesArray, propertyObjects)) { - return LoadResult("Shader manifest properties array could not be parsed: " + path); - } - - for (const std::string& propertyObject : propertyObjects) { - ShaderPropertyDesc property = {}; - if (!TryParseStringValue(propertyObject, "name", property.name) || property.name.Empty()) { - return LoadResult("Shader manifest property is missing a valid name: " + path); - } - - Containers::String propertyTypeName; - if (!TryParseStringValue(propertyObject, "type", propertyTypeName) || - !TryParseShaderPropertyType(propertyTypeName, property.type)) { - return LoadResult("Shader manifest property has an invalid type: " + path); - } - - if (!TryParseStringValue(propertyObject, "displayName", property.displayName)) { - property.displayName = property.name; - } - - if (!TryParseStringValue(propertyObject, "defaultValue", property.defaultValue)) { - TryParseStringValue(propertyObject, "default", property.defaultValue); - } - - TryParseStringValue(propertyObject, "semantic", property.semantic); - shader->AddProperty(property); - } - } - - for (const std::string& passObject : passObjects) { - Containers::String passName; - if (!TryParseStringValue(passObject, "name", passName) || passName.Empty()) { - return LoadResult("Shader manifest pass is missing a valid name: " + path); - } - - std::string tagsObject; - if (TryExtractObject(passObject, "tags", tagsObject)) { - if (!TryParseStringMapObject( - tagsObject, - [shaderPtr = shader.get(), &passName](const Containers::String& key, const Containers::String& value) { - shaderPtr->SetPassTag(passName, key, value); - })) { - return LoadResult("Shader manifest pass tags could not be parsed: " + path); - } - } - - std::string resourcesArray; - if (TryExtractArray(passObject, "resources", resourcesArray)) { - std::vector resourceObjects; - if (!SplitTopLevelArrayElements(resourcesArray, resourceObjects)) { - return LoadResult("Shader manifest pass resources could not be parsed: " + path); - } - - for (const std::string& resourceObject : resourceObjects) { - ShaderResourceBindingDesc resourceBinding = {}; - if (!TryParseStringValue(resourceObject, "name", resourceBinding.name) || - resourceBinding.name.Empty()) { - return LoadResult("Shader manifest pass resource is missing a valid name: " + path); - } - - Containers::String resourceTypeName; - if (!TryParseStringValue(resourceObject, "type", resourceTypeName) || - !TryParseShaderResourceType(resourceTypeName, resourceBinding.type)) { - return LoadResult("Shader manifest pass resource has an invalid type: " + path); - } - - if (!TryParseUnsignedValue(resourceObject, "set", resourceBinding.set)) { - return LoadResult("Shader manifest pass resource is missing a valid set: " + path); - } - if (!TryParseUnsignedValue(resourceObject, "binding", resourceBinding.binding)) { - return LoadResult("Shader manifest pass resource is missing a valid binding: " + path); - } - - TryParseStringValue(resourceObject, "semantic", resourceBinding.semantic); - shader->AddPassResourceBinding(passName, resourceBinding); - } - } - - std::string variantsArray; - if (!TryExtractArray(passObject, "variants", variantsArray)) { - return LoadResult("Shader manifest pass is missing variants: " + path); - } - - std::vector variantObjects; - if (!SplitTopLevelArrayElements(variantsArray, variantObjects) || variantObjects.empty()) { - return LoadResult("Shader manifest pass does not contain any variants: " + path); - } - - for (const std::string& variantObject : variantObjects) { - ShaderStageVariant variant = {}; - - Containers::String stageName; - if (!TryParseStringValue(variantObject, "stage", stageName) || - !TryParseShaderType(stageName, variant.stage)) { - return LoadResult("Shader manifest variant has an invalid stage: " + path); - } - - Containers::String backendName; - if (!TryParseStringValue(variantObject, "backend", backendName) || - !TryParseShaderBackend(backendName, variant.backend)) { - return LoadResult("Shader manifest variant has an invalid backend: " + path); - } - - Containers::String languageName; - if (!TryParseStringValue(variantObject, "language", languageName) || - !TryParseShaderLanguage(languageName, variant.language)) { - return LoadResult("Shader manifest variant has an invalid language: " + path); - } - - Containers::String sourceCode; - if (TryParseStringValue(variantObject, "sourceCode", sourceCode)) { - variant.sourceCode = sourceCode; - } else { - Containers::String sourcePath; - if (!TryParseStringValue(variantObject, "source", sourcePath) && - !TryParseStringValue(variantObject, "sourcePath", sourcePath)) { - return LoadResult("Shader manifest variant is missing source/sourceCode: " + path); - } - - const Containers::String resolvedSourcePath = ResolveShaderDependencyPath(sourcePath, path); - if (!ReadShaderTextFile(resolvedSourcePath, variant.sourceCode)) { - return LoadResult("Failed to read shader variant source: " + resolvedSourcePath); - } - } - - if (!TryParseStringValue(variantObject, "entryPoint", variant.entryPoint)) { - variant.entryPoint = GetDefaultEntryPoint(variant.language, variant.stage); - } - - if (!TryParseStringValue(variantObject, "profile", variant.profile)) { - variant.profile = GetDefaultProfile(variant.language, variant.backend, variant.stage); - } - - std::string keywordsArray; - if (TryExtractArray(variantObject, "keywords", keywordsArray) && - !TryParseShaderKeywordsArray(keywordsArray, variant.requiredKeywords)) { - return LoadResult("Shader manifest variant keywords could not be parsed: " + path); - } - - shader->AddPassVariant(passName, variant); - } - } - - shader->m_memorySize = CalculateShaderMemorySize(*shader); - return LoadResult(shader.release()); -} - -} // namespace Resources -} // namespace XCEngine diff --git a/engine/src/Resources/Shader/Internal/ShaderManifestLoader.h b/engine/src/Resources/Shader/Internal/ShaderManifestLoader.h deleted file mode 100644 index 9212687d..00000000 --- a/engine/src/Resources/Shader/Internal/ShaderManifestLoader.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -namespace XCEngine { -namespace Resources { - -bool LooksLikeShaderManifest(const std::string& sourceText); - -bool CollectShaderManifestDependencyPaths( - const Containers::String& path, - const std::string& jsonText, - Containers::Array& outDependencies); - -LoadResult LoadShaderManifest( - const Containers::String& path, - const std::string& jsonText); - -} // namespace Resources -} // namespace XCEngine diff --git a/engine/src/UI/Runtime/UIScreenDocumentHost.cpp b/engine/src/UI/Runtime/UIScreenDocumentHost.cpp index c39da0e5..59486c62 100644 --- a/engine/src/UI/Runtime/UIScreenDocumentHost.cpp +++ b/engine/src/UI/Runtime/UIScreenDocumentHost.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -985,14 +986,6 @@ std::string BuildNodeStateKeySegment( return ToStdString(source.tagName) + "#" + std::to_string(siblingIndex); } -float ComputeScrollOverflow(float contentExtent, float viewportExtent) { - return (std::max)(0.0f, contentExtent - viewportExtent); -} - -float ClampScrollOffset(float offset, float contentExtent, float viewportExtent) { - return (std::max)(0.0f, (std::min)(offset, ComputeScrollOverflow(contentExtent, viewportExtent))); -} - bool RectContainsPoint(const UIRect& rect, const UIPoint& point) { return point.x >= rect.x && point.x <= rect.x + rect.width && @@ -1885,7 +1878,7 @@ void ArrangeNode( const auto found = verticalScrollOffsets.find(node.stateKey); const float requestedOffset = found != verticalScrollOffsets.end() ? found->second : 0.0f; node.scrollViewportRect = contentRect; - node.scrollOffsetY = ClampScrollOffset( + node.scrollOffsetY = Widgets::ClampUIScrollOffset( requestedOffset, node.contentDesiredSize.height, contentRect.height); @@ -1932,7 +1925,7 @@ RuntimeLayoutNode* FindDeepestScrollTarget( return nullptr; } - if (ComputeScrollOverflow(node.contentDesiredSize.height, node.scrollViewportRect.height) <= 0.0f) { + if (Widgets::ComputeUIScrollOverflow(node.contentDesiredSize.height, node.scrollViewportRect.height) <= 0.0f) { return nullptr; } @@ -2001,7 +1994,7 @@ bool ApplyScrollWheelEvent( scrollDebugSnapshot.lastTargetStateKey = hoveredScrollView->stateKey; scrollDebugSnapshot.lastViewportRect = hoveredScrollView->scrollViewportRect; - scrollDebugSnapshot.lastOverflow = ComputeScrollOverflow( + scrollDebugSnapshot.lastOverflow = Widgets::ComputeUIScrollOverflow( hoveredScrollView->contentDesiredSize.height, hoveredScrollView->scrollViewportRect.height); @@ -2022,18 +2015,18 @@ bool ApplyScrollWheelEvent( : target->scrollOffsetY; scrollDebugSnapshot.lastOffsetBefore = oldOffset; - const float scrollUnits = event.wheelDelta / 120.0f; - const float nextOffset = ClampScrollOffset( - oldOffset - scrollUnits * 48.0f, + const Widgets::UIScrollWheelResult scrollResult = Widgets::ApplyUIScrollWheel( + oldOffset, + event.wheelDelta, target->contentDesiredSize.height, target->scrollViewportRect.height); - scrollDebugSnapshot.lastOffsetAfter = nextOffset; - if (std::fabs(nextOffset - oldOffset) <= 0.01f) { + scrollDebugSnapshot.lastOffsetAfter = scrollResult.offsetAfter; + if (!scrollResult.changed) { scrollDebugSnapshot.lastResult = "Scroll delta clamped to current offset"; return false; } - verticalScrollOffsets[target->stateKey] = nextOffset; + verticalScrollOffsets[target->stateKey] = scrollResult.offsetAfter; ++scrollDebugSnapshot.handledWheelEventCount; scrollDebugSnapshot.lastResult = "Handled"; return true; @@ -2545,7 +2538,8 @@ void SyncScrollOffsets( const RuntimeLayoutNode& node, std::unordered_map& verticalScrollOffsets) { if (node.isScrollView) { - const float overflow = ComputeScrollOverflow(node.contentDesiredSize.height, node.scrollViewportRect.height); + const float overflow = + Widgets::ComputeUIScrollOverflow(node.contentDesiredSize.height, node.scrollViewportRect.height); if (overflow <= 0.0f || node.scrollOffsetY <= 0.0f) { verticalScrollOffsets.erase(node.stateKey); } else { @@ -3009,7 +3003,7 @@ UIScreenFrameResult UIDocumentScreenHost::BuildFrame( if (const RuntimeLayoutNode* primaryScrollView = FindFirstScrollView(root); primaryScrollView != nullptr) { m_scrollDebugSnapshot.primaryTargetStateKey = primaryScrollView->stateKey; m_scrollDebugSnapshot.primaryViewportRect = primaryScrollView->scrollViewportRect; - m_scrollDebugSnapshot.primaryOverflow = ComputeScrollOverflow( + m_scrollDebugSnapshot.primaryOverflow = Widgets::ComputeUIScrollOverflow( primaryScrollView->contentDesiredSize.height, primaryScrollView->scrollViewportRect.height); } else { diff --git a/engine/src/UI/Widgets/UIScrollModel.cpp b/engine/src/UI/Widgets/UIScrollModel.cpp new file mode 100644 index 00000000..04e2352f --- /dev/null +++ b/engine/src/UI/Widgets/UIScrollModel.cpp @@ -0,0 +1,67 @@ +#include + +#include +#include + +namespace XCEngine { +namespace UI { +namespace Widgets { + +float ComputeUIScrollOverflow(float contentExtent, float viewportExtent) { + return (std::max)(0.0f, contentExtent - viewportExtent); +} + +float ClampUIScrollOffset(float offset, float contentExtent, float viewportExtent) { + return (std::max)(0.0f, (std::min)(offset, ComputeUIScrollOverflow(contentExtent, viewportExtent))); +} + +UIScrollWheelResult ApplyUIScrollWheel( + float offset, + float wheelDelta, + float contentExtent, + float viewportExtent, + float wheelStep, + float epsilon) { + UIScrollWheelResult result = {}; + result.offsetBefore = ClampUIScrollOffset(offset, contentExtent, viewportExtent); + result.overflow = ComputeUIScrollOverflow(contentExtent, viewportExtent); + result.offsetAfter = result.offsetBefore; + if (result.overflow <= 0.0f || std::fabs(wheelDelta) <= epsilon || wheelStep <= 0.0f) { + return result; + } + + const float scrollUnits = wheelDelta / 120.0f; + result.offsetAfter = ClampUIScrollOffset( + result.offsetBefore - scrollUnits * wheelStep, + contentExtent, + viewportExtent); + result.changed = std::fabs(result.offsetAfter - result.offsetBefore) > epsilon; + return result; +} + +float EnsureUIScrollOffsetVisible( + float offset, + float itemStart, + float itemExtent, + float contentExtent, + float viewportExtent) { + const float clampedOffset = ClampUIScrollOffset(offset, contentExtent, viewportExtent); + if (viewportExtent <= 0.0f || itemExtent <= 0.0f) { + return clampedOffset; + } + + const float itemEnd = itemStart + itemExtent; + const float viewportEnd = clampedOffset + viewportExtent; + float nextOffset = clampedOffset; + if (itemStart < clampedOffset) { + nextOffset = itemStart; + } else if (itemEnd > viewportEnd) { + nextOffset = itemEnd - viewportExtent; + } + + return ClampUIScrollOffset(nextOffset, contentExtent, viewportExtent); +} + +} // namespace Widgets +} // namespace UI +} // namespace XCEngine diff --git a/project/.xceditor/imgui_layout.ini b/project/.xceditor/imgui_layout.ini index ac1bee5c..19f0d20a 100644 --- a/project/.xceditor/imgui_layout.ini +++ b/project/.xceditor/imgui_layout.ini @@ -1,42 +1,42 @@ [Window][Hierarchy] -Pos=0,65 -Size=435,735 +Pos=0,59 +Size=189,1122 Collapsed=0 DockId=0x00000003 [Window][Scene] -Pos=437,65 -Size=1311,735 +Pos=191,59 +Size=2099,1122 Collapsed=0 -DockId=0x00000004 +DockId=0x00000005 [Window][Game] -Pos=437,65 -Size=1311,735 +Pos=191,59 +Size=2099,1122 Collapsed=0 -DockId=0x00000004 +DockId=0x00000005 [Window][Inspector] -Pos=1750,65 -Size=810,1286 +Pos=2292,59 +Size=268,1122 Collapsed=0 DockId=0x00000006 [Window][Console] -Pos=0,802 -Size=1748,549 +Pos=0,1183 +Size=2560,168 Collapsed=0 DockId=0x00000002 [Window][Project] -Pos=0,802 -Size=1748,549 +Pos=0,1183 +Size=2560,168 Collapsed=0 DockId=0x00000002 [Window][MainDockspace] -Pos=0,65 -Size=2560,1286 +Pos=0,59 +Size=2560,1292 Collapsed=0 [Window][Debug##Default] @@ -53,12 +53,17 @@ Pos=1060,620 Size=440,110 Collapsed=0 -[Docking][Data] -DockSpace ID=0xA11B73D6 Window=0x1C358F53 Pos=0,65 Size=2560,1286 Split=X - DockNode ID=0x00000005 Parent=0xA11B73D6 SizeRef=1748,640 Split=Y - DockNode ID=0x00000001 Parent=0x00000005 SizeRef=1262,735 Split=X - DockNode ID=0x00000003 Parent=0x00000001 SizeRef=435,503 NoTabBar=1 NoWindowMenuButton=1 NoCloseButton=1 Selected=0xBABDAE5E - DockNode ID=0x00000004 Parent=0x00000001 SizeRef=1311,503 CentralNode=1 NoTabBar=1 NoWindowMenuButton=1 NoCloseButton=1 Selected=0xE601B12F - DockNode ID=0x00000002 Parent=0x00000005 SizeRef=1262,549 NoTabBar=1 NoWindowMenuButton=1 NoCloseButton=1 Selected=0xEA83D666 - DockNode ID=0x00000006 Parent=0xA11B73D6 SizeRef=810,640 NoTabBar=1 NoWindowMenuButton=1 NoCloseButton=1 Selected=0x36DC96AB +[Window][XCUI Demo] +Pos=506,204 +Size=501,40 +Collapsed=0 + +[Docking][Data] +DockSpace ID=0xA11B73D6 Window=0x1C358F53 Pos=0,59 Size=2560,1292 Split=Y + DockNode ID=0x00000001 Parent=0xA11B73D6 SizeRef=1262,503 Split=X + DockNode ID=0x00000003 Parent=0x00000001 SizeRef=189,503 NoTabBar=1 NoWindowMenuButton=1 NoCloseButton=1 Selected=0xBABDAE5E + DockNode ID=0x00000004 Parent=0x00000001 SizeRef=1071,503 Split=X + DockNode ID=0x00000005 Parent=0x00000004 SizeRef=801,503 CentralNode=1 NoTabBar=1 NoWindowMenuButton=1 NoCloseButton=1 Selected=0xE601B12F + DockNode ID=0x00000006 Parent=0x00000004 SizeRef=268,503 NoTabBar=1 NoWindowMenuButton=1 NoCloseButton=1 Selected=0x36DC96AB + DockNode ID=0x00000002 Parent=0xA11B73D6 SizeRef=1262,168 NoTabBar=1 NoWindowMenuButton=1 NoCloseButton=1 Selected=0xEA83D666 diff --git a/project/Assets.meta b/project/Assets.meta index a88a52ca..286b1813 100644 --- a/project/Assets.meta +++ b/project/Assets.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: 96666a186a717b84e90236ea2978d91f folderAsset: true importer: FolderImporter -importerVersion: 3 +importerVersion: 7 diff --git a/project/Assets/Materials.meta b/project/Assets/Materials.meta index 1d6285cf..98aee2fd 100644 --- a/project/Assets/Materials.meta +++ b/project/Assets/Materials.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: 9b99cbc7c30f5a7bbf6e5cc49690e2e7 folderAsset: true importer: FolderImporter -importerVersion: 3 +importerVersion: 7 diff --git a/project/Assets/Models.meta b/project/Assets/Models.meta index 89458d7e..28fc701e 100644 --- a/project/Assets/Models.meta +++ b/project/Assets/Models.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: 7ad7534fed3a73dfeb84739d0d3aef92 folderAsset: true importer: FolderImporter -importerVersion: 3 +importerVersion: 7 diff --git a/project/Assets/Models/backpack.meta b/project/Assets/Models/backpack.meta index 799cb2a2..ce5629c1 100644 --- a/project/Assets/Models/backpack.meta +++ b/project/Assets/Models/backpack.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: ea210ba93a7a93260ae13bc84508bbe3 folderAsset: true importer: FolderImporter -importerVersion: 3 +importerVersion: 7 diff --git a/project/Assets/Models/backpack/ao.jpg.meta b/project/Assets/Models/backpack/ao.jpg.meta index fce16249..da6b779f 100644 --- a/project/Assets/Models/backpack/ao.jpg.meta +++ b/project/Assets/Models/backpack/ao.jpg.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: 7eedc507fd8709abb2b19af32a52ab21 folderAsset: false importer: TextureImporter -importerVersion: 3 +importerVersion: 7 diff --git a/project/Assets/Models/backpack/backpack.mtl.meta b/project/Assets/Models/backpack/backpack.mtl.meta index 12ad07cd..53d5d26b 100644 --- a/project/Assets/Models/backpack/backpack.mtl.meta +++ b/project/Assets/Models/backpack/backpack.mtl.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: 5efa3f1aa5ce5981cbbefe56c8f97128 folderAsset: false importer: DefaultImporter -importerVersion: 3 +importerVersion: 7 diff --git a/project/Assets/Models/backpack/backpack.obj.meta b/project/Assets/Models/backpack/backpack.obj.meta index 6c5201f8..bfb08f2c 100644 --- a/project/Assets/Models/backpack/backpack.obj.meta +++ b/project/Assets/Models/backpack/backpack.obj.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: 17cce01b8a084a5cdf1c79382a46c31f folderAsset: false importer: ModelImporter -importerVersion: 3 +importerVersion: 7 diff --git a/project/Assets/Models/backpack/diffuse.jpg.meta b/project/Assets/Models/backpack/diffuse.jpg.meta index 41204e92..3fdf1454 100644 --- a/project/Assets/Models/backpack/diffuse.jpg.meta +++ b/project/Assets/Models/backpack/diffuse.jpg.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: 003ee132378579f4bfc59b39cf8e8d51 folderAsset: false importer: TextureImporter -importerVersion: 3 +importerVersion: 7 diff --git a/project/Assets/Models/backpack/normal.png.meta b/project/Assets/Models/backpack/normal.png.meta index 91f8a22c..400f231a 100644 --- a/project/Assets/Models/backpack/normal.png.meta +++ b/project/Assets/Models/backpack/normal.png.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: e94d89b26d6e920e2e73cb96d3beddbd folderAsset: false importer: TextureImporter -importerVersion: 3 +importerVersion: 7 diff --git a/project/Assets/Models/backpack/roughness.jpg.meta b/project/Assets/Models/backpack/roughness.jpg.meta index c3e8e037..1464c61a 100644 --- a/project/Assets/Models/backpack/roughness.jpg.meta +++ b/project/Assets/Models/backpack/roughness.jpg.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: c98fc6424b69b1a41adc91eabc2e82bd folderAsset: false importer: TextureImporter -importerVersion: 3 +importerVersion: 7 diff --git a/project/Assets/Models/backpack/source_attribution.txt.meta b/project/Assets/Models/backpack/source_attribution.txt.meta index ce3e9161..37baccbf 100644 --- a/project/Assets/Models/backpack/source_attribution.txt.meta +++ b/project/Assets/Models/backpack/source_attribution.txt.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: 26aa3d5b0d4e0320a5286c0cc5a76c46 folderAsset: false importer: DefaultImporter -importerVersion: 3 +importerVersion: 7 diff --git a/project/Assets/Models/backpack/specular.jpg.meta b/project/Assets/Models/backpack/specular.jpg.meta index a2d7f381..7b07b1c3 100644 --- a/project/Assets/Models/backpack/specular.jpg.meta +++ b/project/Assets/Models/backpack/specular.jpg.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: bac31a87fc5da61d66e2de6841990bb1 folderAsset: false importer: TextureImporter -importerVersion: 3 +importerVersion: 7 diff --git a/project/Assets/New Material.mat b/project/Assets/New Material.mat index c7c79595..2610291d 100644 --- a/project/Assets/New Material.mat +++ b/project/Assets/New Material.mat @@ -2,7 +2,7 @@ "shader": "builtin://shaders/unlit", "renderQueue": "geometry", "properties": { - "_BaseColor": [1, 1, 1, 1] + "_BaseColor": [0.850877, 0.365728, 0.365728, 1] }, "textures": { "_MainTex": "Assets/Models/backpack/diffuse.jpg" diff --git a/project/Assets/New Material.mat.meta b/project/Assets/New Material.mat.meta index 275f3666..fb20a981 100644 --- a/project/Assets/New Material.mat.meta +++ b/project/Assets/New Material.mat.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: 9a608ceb121dae614903368e56e8e049 folderAsset: false importer: MaterialImporter -importerVersion: 5 +importerVersion: 7 diff --git a/project/Assets/Scenes.meta b/project/Assets/Scenes.meta index ff24e7a2..195281cb 100644 --- a/project/Assets/Scenes.meta +++ b/project/Assets/Scenes.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: 95bf8c50b9011ec0375cd185053068c9 folderAsset: true importer: FolderImporter -importerVersion: 3 +importerVersion: 7 diff --git a/project/Assets/Scenes/Backpack.xc b/project/Assets/Scenes/Backpack.xc index d51e55bf..3f0c8a76 100644 --- a/project/Assets/Scenes/Backpack.xc +++ b/project/Assets/Scenes/Backpack.xc @@ -6,30 +6,23 @@ gameobject_begin id=1 uuid=11806343893082442755 name=Main Camera +tag=Untagged active=1 +layer=0 parent=0 transform=position=1.24194,0,-5.54416;rotation=0,0,0,1;scale=1,1,1; -component=Camera;projection=0;fov=60.7089;orthoSize=5;near=0.001;far=102.9;depth=1.6;primary=1;clearMode=0;stackType=0;cullingMask=4294967295;viewportRect=0,0,1,1;clearColor=0,0,0,1; -gameobject_end - -gameobject_begin -id=20 -uuid=8649195733451829943 -name=GameObject -active=1 -parent=0 -transform=position=-3.68003,0.160159,0.360709;rotation=0,0,0,1;scale=1,1,1; -component=MeshFilter;meshRef=17cce01b8a084a5cdf1c79382a46c31f,1,2; -component=MeshRenderer;materialPaths=;materialRefs=;castShadows=1;receiveShadows=1;renderLayer=0; +component=Camera;projection=0;fov=60.7089;orthoSize=5;near=0.001;far=102.9;depth=1.6;primary=1;clearMode=0;stackType=0;cullingMask=4294967295;viewportRect=0,0,1,1;clearColor=0,0,0,1;skyboxEnabled=0;skyboxMaterialPath=;skyboxMaterialRef=;skyboxTopColor=0.18,0.36,0.74,1;skyboxHorizonColor=0.78,0.84,0.92,1;skyboxBottomColor=0.92,0.93,0.95,1;finalColorOverrideOutputTransferEnabled=0;finalColorOverrideOutputTransferMode=0;finalColorOverrideExposureModeEnabled=0;finalColorOverrideExposureMode=0;finalColorOverrideExposureValueEnabled=0;finalColorOverrideExposureValue=1;finalColorOverrideToneMappingModeEnabled=0;finalColorOverrideToneMappingMode=0;finalColorOverrideScaleEnabled=0;finalColorOverrideScale=1,1,1,1;postProcessPassCount=0; gameobject_end gameobject_begin id=29 uuid=16627428025886468363 name=Light +tag=Untagged active=1 +layer=0 parent=0 -transform=position=0,3.00762,3.94704;rotation=0.394142,-0.000640735,-0.0613786,0.916998;scale=1,1,1; +transform=position=0,3.00762,4.6342;rotation=0.930636,0.196101,-0.052872,0.304409;scale=1,1,1; component=Light;type=0;color=1,1,1,1;intensity=1;range=10;spotAngle=30;shadows=0; gameobject_end @@ -37,7 +30,9 @@ gameobject_begin id=75 uuid=5125863825480013902 name=Cube +tag=Untagged active=1 +layer=0 parent=0 transform=position=5.97411,0,0;rotation=0,0,0,1;scale=1,1,1; component=MeshFilter;meshRef=;meshPath=builtin://meshes/cube; @@ -45,12 +40,39 @@ component=MeshRenderer;materialPaths=builtin://materials/default-primitive;mater gameobject_end gameobject_begin -id=79 -uuid=14087659263782399666 -name=GameObject +id=128 +uuid=9183622432480476442 +name=Light +tag=Untagged active=1 +layer=0 parent=0 -transform=position=2.32896,0,-0.076026;rotation=0,0,0,1;scale=1,1,1; +transform=position=1.04812,1.32087,6.70265;rotation=0.295208,0,0,0.955433;scale=1,1,1; +component=Light;type=1;color=1,0.614035,0.991319,1;intensity=11.5;range=10;spotAngle=30;shadows=0; +gameobject_end + +gameobject_begin +id=129 +uuid=2162319538989844123 +name=Plane +tag=Untagged +active=1 +layer=0 +parent=0 +transform=position=0,-2.16998,0;rotation=0,0,0,1;scale=1,1,1; +component=MeshFilter;meshRef=;meshPath=builtin://meshes/plane; +component=MeshRenderer;materialPaths=builtin://materials/default-primitive;materialRefs=;castShadows=1;receiveShadows=1;renderLayer=0; +gameobject_end + +gameobject_begin +id=141 +uuid=11103014809698991785 +name=GameObject +tag=Untagged +active=1 +layer=0 +parent=0 +transform=position=0,0,-1.77895;rotation=0,0,0,1;scale=1,1,-1.7; component=MeshFilter;meshRef=17cce01b8a084a5cdf1c79382a46c31f,1,2; component=MeshRenderer;materialPaths=;materialRefs=;castShadows=1;receiveShadows=1;renderLayer=0; gameobject_end diff --git a/project/Assets/Scenes/Backpack.xc.meta b/project/Assets/Scenes/Backpack.xc.meta index 721a12a0..40e65d1b 100644 --- a/project/Assets/Scenes/Backpack.xc.meta +++ b/project/Assets/Scenes/Backpack.xc.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: 97d8a1008ece4eab2930a9caf3c11f54 folderAsset: false importer: DefaultImporter -importerVersion: 3 +importerVersion: 7 diff --git a/project/Assets/Scenes/Main.xc b/project/Assets/Scenes/Main.xc index 664d18af..3c9c494d 100644 --- a/project/Assets/Scenes/Main.xc +++ b/project/Assets/Scenes/Main.xc @@ -6,50 +6,157 @@ gameobject_begin id=64 uuid=5744812053316050663 name=Camera +tag=Untagged active=1 +layer=0 parent=0 -transform=position=-0.352908,0,-3.0611;rotation=0.111657,0.15678,0.890189,-0.412937;scale=1,1,1; -component=Camera;projection=0;fov=60;orthoSize=5;near=1.43;far=1000;depth=0;primary=1;clearMode=0;stackType=0;cullingMask=4294967295;viewportRect=0,0,1,1;clearColor=0,0,0,0.552941; +transform=position=1.40968,2.17144,-6.14578;rotation=0.0850599,0,0,0.996376;scale=1,1,1; +component=Camera;projection=0;fov=60;orthoSize=5;near=1.43;far=1000;depth=0;primary=1;clearMode=0;stackType=0;cullingMask=4294967295;viewportRect=0,0,1,1;clearColor=0,0,0,0.552941;skyboxEnabled=1;skyboxMaterialPath=;skyboxMaterialRef=;skyboxTopColor=0.269544,0.501514,0.991228,1;skyboxHorizonColor=0.570175,0.570175,0.570175,1;skyboxBottomColor=0.141582,0.228891,0.403509,1;finalColorOverrideOutputTransferEnabled=0;finalColorOverrideOutputTransferMode=0;finalColorOverrideExposureModeEnabled=0;finalColorOverrideExposureMode=0;finalColorOverrideExposureValueEnabled=0;finalColorOverrideExposureValue=1;finalColorOverrideToneMappingModeEnabled=0;finalColorOverrideToneMappingMode=0;finalColorOverrideScaleEnabled=0;finalColorOverrideScale=1,1,1,1;postProcessPassCount=0; gameobject_end gameobject_begin id=66 uuid=18270436544925411184 name=Light +tag=Untagged active=1 +layer=0 parent=0 -transform=position=0,3.3245,0;rotation=-0.12466,0.596834,-0.0945426,0.786963;scale=1,1,1; -component=Light;type=0;color=1,1,1,1;intensity=1;range=10;spotAngle=30;shadows=0; +transform=position=5.87107,3.04462,0.305848;rotation=0.717254,0.587471,-0.0433283,0.372219;scale=1,1,1; +component=Light;type=0;color=1,1,1,1;intensity=0.2;range=10;spotAngle=30;shadows=0; gameobject_end gameobject_begin id=87 uuid=9751737136539126565 name=GameObject +tag=Untagged active=1 +layer=0 parent=0 transform=position=0,0,0;rotation=-0.345665,0.580459,-0.0801843,-0.732908;scale=1,1,1; gameobject_end -gameobject_begin -id=89 -uuid=3783751572903533469 -name=Sphere -active=1 -parent=0 -transform=position=-0.0171053,0,-0.2493;rotation=0.514887,0.648149,-0.409349,0.383703;scale=1,1,1; -component=MeshFilter;meshRef=;meshPath=builtin://meshes/sphere; -component=MeshRenderer;materialPaths=builtin://materials/default-primitive;materialRefs=;castShadows=1;receiveShadows=1;renderLayer=0; -component=ScriptComponent;scriptComponentUUID=14953097718051232299;assembly=GameScripts;namespace=ProjectScripts;class=TickLogProbe;fields=; -gameobject_end - gameobject_begin id=91 uuid=12782062714694956708 name=GameObject +tag=Untagged active=1 +layer=0 parent=0 transform=position=0,0,0;rotation=0,0,0,1;scale=1,1,1; component=ScriptComponent;scriptComponentUUID=13495247810881627157;assembly=GameScripts;namespace=;class=;fields=; gameobject_end +gameobject_begin +id=113 +uuid=15803379621740129390 +name=Plane +tag=Untagged +active=1 +layer=0 +parent=0 +transform=position=0,0,0;rotation=0,0,0,1;scale=1,1,1; +component=MeshFilter;meshRef=;meshPath=builtin://meshes/plane; +component=MeshRenderer;materialPaths=builtin://materials/default-primitive;materialRefs=;castShadows=1;receiveShadows=1;renderLayer=0; +gameobject_end +gameobject_begin +id=126 +uuid=12456668029646601964 +name=Cube +tag=Untagged +active=1 +layer=0 +parent=113 +transform=position=2.91271,0.679955,2.94568;rotation=-0.288105,0,0,0.957599;scale=1,1,1; +component=MeshFilter;meshRef=;meshPath=builtin://meshes/cube; +component=MeshRenderer;materialPaths=builtin://materials/default-primitive;materialRefs=;castShadows=1;receiveShadows=1;renderLayer=0; +gameobject_end +gameobject_begin +id=115 +uuid=6890014658586654960 +name=Cube +tag=Untagged +active=1 +layer=0 +parent=113 +transform=position=-1.28823,0.525337,1.45296;rotation=-0.0763061,0.299091,-0.0189699,0.95098;scale=1,1,1; +component=MeshFilter;meshRef=;meshPath=builtin://meshes/cube; +component=MeshRenderer;materialPaths=builtin://materials/default-primitive;materialRefs=;castShadows=1;receiveShadows=1;renderLayer=0; +gameobject_end +gameobject_begin +id=130 +uuid=16092524035934325911 +name=Cube +tag=Untagged +active=1 +layer=0 +parent=113 +transform=position=2.6382,0.525337,-3.07507;rotation=-0.0763061,0.299091,-0.0189699,0.95098;scale=1,1,1; +component=MeshFilter;meshRef=;meshPath=builtin://meshes/cube; +component=MeshRenderer;materialPaths=;materialRefs=9a608ceb121dae614903368e56e8e049,1,3;castShadows=1;receiveShadows=1;renderLayer=0; +gameobject_end + +gameobject_begin +id=117 +uuid=6470155182778634572 +name=Light +tag=Untagged +active=1 +layer=0 +parent=0 +transform=position=0.484716,0.961334,2.89361;rotation=0,0,0,1;scale=1,1,1; +component=Light;type=1;color=1,0,0.0287113,1;intensity=1;range=10;spotAngle=30;shadows=0; +gameobject_end + +gameobject_begin +id=119 +uuid=11541908339887058565 +name=Light +tag=Untagged +active=1 +layer=0 +parent=0 +transform=position=2.94791,0.869651,-2.61555;rotation=0,0,0,1;scale=1,1,1; +component=Light;type=1;color=0,1,0.318094,1;intensity=1;range=10;spotAngle=30;shadows=0; +gameobject_end + +gameobject_begin +id=120 +uuid=6386081964352732701 +name=Light +tag=Untagged +active=1 +layer=0 +parent=0 +transform=position=-1.55514,1.18028,-0.698302;rotation=0,0,0,1;scale=1,1,1; +component=Light;type=1;color=0,0.141337,1,1;intensity=1;range=10;spotAngle=30;shadows=0; +gameobject_end + +gameobject_begin +id=132 +uuid=4498534432540015846 +name=Cube +tag=Untagged +active=1 +layer=0 +parent=0 +transform=position=-1.08156,0.874702,-3.56394;rotation=0,0,0,1;scale=1,1,1; +component=MeshFilter;meshRef=;meshPath=builtin://meshes/cube; +component=MeshRenderer;materialPaths=builtin://materials/default-primitive;materialRefs=;castShadows=1;receiveShadows=1;renderLayer=0; +gameobject_end + +gameobject_begin +id=133 +uuid=17783067283336270389 +name=Capsule +tag=Untagged +active=1 +layer=0 +parent=0 +transform=position=2.8096,1.4917,0;rotation=0,0,0,1;scale=1,1,1; +component=MeshFilter;meshRef=;meshPath=builtin://meshes/capsule; +component=MeshRenderer;materialPaths=builtin://materials/default-primitive;materialRefs=;castShadows=1;receiveShadows=1;renderLayer=0; +gameobject_end + diff --git a/project/Assets/Scenes/Main.xc.meta b/project/Assets/Scenes/Main.xc.meta index 6910524f..b98783ce 100644 --- a/project/Assets/Scenes/Main.xc.meta +++ b/project/Assets/Scenes/Main.xc.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: d291a8a36c1395b2199e6e32abfa7b84 folderAsset: false importer: DefaultImporter -importerVersion: 3 +importerVersion: 7 diff --git a/project/Assets/Scenes/NewFolder.meta b/project/Assets/Scenes/NewFolder.meta index cae0f440..607de032 100644 --- a/project/Assets/Scenes/NewFolder.meta +++ b/project/Assets/Scenes/NewFolder.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: f8af940001366ccd64b1b8bb5e071cdb folderAsset: true importer: FolderImporter -importerVersion: 3 +importerVersion: 7 diff --git a/project/Assets/Scripts.meta b/project/Assets/Scripts.meta index fa7e0cbf..d7eded1d 100644 --- a/project/Assets/Scripts.meta +++ b/project/Assets/Scripts.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: e63714c47b794207e7310ca83f50537b folderAsset: true importer: FolderImporter -importerVersion: 3 +importerVersion: 7 diff --git a/project/Assets/Scripts/ProjectScriptProbe.cs.meta b/project/Assets/Scripts/ProjectScriptProbe.cs.meta index 672fa5da..efd54cbe 100644 --- a/project/Assets/Scripts/ProjectScriptProbe.cs.meta +++ b/project/Assets/Scripts/ProjectScriptProbe.cs.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: 6607cf0bad154aa86768fd90d630d84f folderAsset: false importer: DefaultImporter -importerVersion: 3 +importerVersion: 7 diff --git a/project/Assets/Scripts/Textures.meta b/project/Assets/Scripts/Textures.meta index 27adce6e..d6be5674 100644 --- a/project/Assets/Scripts/Textures.meta +++ b/project/Assets/Scripts/Textures.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: ec851a44fdf97e9e2d7464b259d4e5b8 folderAsset: true importer: FolderImporter -importerVersion: 3 +importerVersion: 7 diff --git a/project/Assets/Scripts/Textures/Grass.png.meta b/project/Assets/Scripts/Textures/Grass.png.meta index 4533f88f..8e24e924 100644 --- a/project/Assets/Scripts/Textures/Grass.png.meta +++ b/project/Assets/Scripts/Textures/Grass.png.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: 09908ff9ea86f69e5951b763ec5a65e4 folderAsset: false importer: TextureImporter -importerVersion: 3 +importerVersion: 7 diff --git a/project/Assets/Scripts/Textures/NewFolder.meta b/project/Assets/Scripts/Textures/NewFolder.meta index 8064af1e..10f538b3 100644 --- a/project/Assets/Scripts/Textures/NewFolder.meta +++ b/project/Assets/Scripts/Textures/NewFolder.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: 907d8d9c76e51c3ff41daf2c272bbc2e folderAsset: true importer: FolderImporter -importerVersion: 3 +importerVersion: 7 diff --git a/project/Assets/Scripts/Textures/PlayerController.cs.meta b/project/Assets/Scripts/Textures/PlayerController.cs.meta index 60c85c21..dda067cb 100644 --- a/project/Assets/Scripts/Textures/PlayerController.cs.meta +++ b/project/Assets/Scripts/Textures/PlayerController.cs.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: 3292abd66fd3c24f29b432371a671609 folderAsset: false importer: DefaultImporter -importerVersion: 3 +importerVersion: 7 diff --git a/project/Assets/Scripts/Textures/Stone.png.meta b/project/Assets/Scripts/Textures/Stone.png.meta index 8ff2331b..47f5b67b 100644 --- a/project/Assets/Scripts/Textures/Stone.png.meta +++ b/project/Assets/Scripts/Textures/Stone.png.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: 4ebde4198801fc49d0d13e3ef7f65633 folderAsset: false importer: TextureImporter -importerVersion: 3 +importerVersion: 7 diff --git a/project/Assets/Scripts/TickLogProbe.cs.meta b/project/Assets/Scripts/TickLogProbe.cs.meta index bd89660a..79820ab9 100644 --- a/project/Assets/Scripts/TickLogProbe.cs.meta +++ b/project/Assets/Scripts/TickLogProbe.cs.meta @@ -2,4 +2,4 @@ fileFormatVersion: 1 guid: b5bf687835df60784ce27f81737e95c4 folderAsset: false importer: DefaultImporter -importerVersion: 3 +importerVersion: 7 diff --git a/scripts/Run-RendererPhaseRegression.ps1 b/scripts/Run-RendererPhaseRegression.ps1 deleted file mode 100644 index 092f3889..00000000 --- a/scripts/Run-RendererPhaseRegression.ps1 +++ /dev/null @@ -1,156 +0,0 @@ -[CmdletBinding()] -param( - [string]$BuildDir = "build", - [string]$Config = "Debug", - [string]$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path, - [int]$SmokeSeconds = 5, - [switch]$SkipBuild, - [switch]$SkipSmoke, - [switch]$CleanBuild -) - -Set-StrictMode -Version Latest -$ErrorActionPreference = "Stop" - -function Invoke-Step { - param( - [Parameter(Mandatory = $true)] - [string]$Label, - [Parameter(Mandatory = $true)] - [scriptblock]$Action - ) - - Write-Host "==> $Label" - & $Action -} - -function Invoke-Native { - param( - [Parameter(Mandatory = $true)] - [string]$FilePath, - [Parameter()] - [string[]]$Arguments = @(), - [string]$WorkingDirectory = $RepoRoot - ) - - Push-Location $WorkingDirectory - try { - & $FilePath @Arguments - if ($LASTEXITCODE -ne 0) { - throw ("Command failed with exit code {0}: {1} {2}" -f $LASTEXITCODE, $FilePath, ($Arguments -join ' ')) - } - } - finally { - Pop-Location - } -} - -function Get-ExePath { - param( - [Parameter(Mandatory = $true)] - [string[]]$Parts - ) - - $path = $BuildDir - foreach ($part in $Parts) { - $path = Join-Path $path $part - } - - $resolved = if ([System.IO.Path]::IsPathRooted($path)) { - $path - } - else { - Join-Path $RepoRoot $path - } - if (-not (Test-Path $resolved)) { - throw "Expected executable does not exist: $resolved" - } - - return $resolved -} - -$RepoRoot = [System.IO.Path]::GetFullPath($RepoRoot) -$BuildDir = if ([System.IO.Path]::IsPathRooted($BuildDir)) { - [System.IO.Path]::GetFullPath($BuildDir) -} -else { - [System.IO.Path]::GetFullPath((Join-Path $RepoRoot $BuildDir)) -} - -$msbuildArguments = @( - "--", "/m:1", - "/p:UseMultiToolTask=false", - "/p:CL_MPCount=1" -) - -if (-not $SkipBuild) { - if ($CleanBuild) { - Invoke-Step "Clean rebuild XCEngine" { - Invoke-Native -FilePath "cmake" -Arguments (@( - "--build", $BuildDir, - "--config", $Config, - "--target", "XCEngine", - "--clean-first" - ) + $msbuildArguments) - } - } - - Invoke-Step "Build rendering regression targets" { - Invoke-Native -FilePath "cmake" -Arguments (@( - "--build", $BuildDir, - "--config", $Config, - "--target", "rendering_phase_regression_build" - ) + $msbuildArguments) - } -} - -$executables = @( - (Get-ExePath -Parts @("tests", "Rendering", "unit", $Config, "rendering_unit_tests.exe")), - (Get-ExePath -Parts @("tests", "Rendering", "integration", "textured_quad_scene", $Config, "rendering_integration_textured_quad_scene.exe")), - (Get-ExePath -Parts @("tests", "Rendering", "integration", "backpack_scene", $Config, "rendering_integration_backpack_scene.exe")), - (Get-ExePath -Parts @("tests", "Rendering", "integration", "backpack_lit_scene", $Config, "rendering_integration_backpack_lit_scene.exe")), - (Get-ExePath -Parts @("tests", "Rendering", "integration", "camera_stack_scene", $Config, "rendering_integration_camera_stack_scene.exe")), - (Get-ExePath -Parts @("tests", "Rendering", "integration", "transparent_material_scene", $Config, "rendering_integration_transparent_material_scene.exe")), - (Get-ExePath -Parts @("tests", "Rendering", "integration", "cull_material_scene", $Config, "rendering_integration_cull_material_scene.exe")), - (Get-ExePath -Parts @("tests", "Rendering", "integration", "depth_sort_scene", $Config, "rendering_integration_depth_sort_scene.exe")), - (Get-ExePath -Parts @("tests", "Rendering", "integration", "material_state_scene", $Config, "rendering_integration_material_state_scene.exe")), - (Get-ExePath -Parts @("tests", "Rendering", "integration", "offscreen_scene", $Config, "rendering_integration_offscreen_scene.exe")), - (Get-ExePath -Parts @("tests", "Editor", $Config, "editor_tests.exe")) -) - -foreach ($executable in $executables) { - $label = [System.IO.Path]::GetFileName($executable) - $workingDirectory = Split-Path -Parent $executable - Invoke-Step "Run $label" { - Invoke-Native -FilePath $executable -WorkingDirectory $workingDirectory - } -} - -if (-not $SkipSmoke) { - $editorExe = Join-Path $RepoRoot "editor\bin\$Config\XCEngine.exe" - if (-not (Test-Path $editorExe)) { - throw "Expected editor executable does not exist: $editorExe" - } - - Invoke-Step "Smoke launch XCEditor ($SmokeSeconds seconds)" { - $process = Start-Process -FilePath $editorExe -WorkingDirectory $RepoRoot -PassThru - try { - Start-Sleep -Seconds $SmokeSeconds - if ($process.HasExited) { - if ($process.ExitCode -ne 0) { - throw "XCEditor exited early with code $($process.ExitCode)" - } - } - else { - Stop-Process -Id $process.Id -Force - } - } - finally { - if (-not $process.HasExited) { - Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue - } - } - } -} - -Write-Host "Renderer phase regression passed." diff --git a/tests/Components/test_mesh_render_components.cpp b/tests/Components/test_mesh_render_components.cpp index 37d39f11..02b9dbc1 100644 --- a/tests/Components/test_mesh_render_components.cpp +++ b/tests/Components/test_mesh_render_components.cpp @@ -106,35 +106,30 @@ TEST(MeshFilterComponent_Test, SetMeshCachesResourceAndPath) { TEST(MeshFilterComponent_Test, SerializeAndDeserializePreservesPath) { MeshFilterComponent source; - Mesh* mesh = CreateTestMesh("Quad", "Meshes/serialized.mesh"); - source.SetMesh(mesh); + source.SetMeshPath("builtin://meshes/cube"); std::stringstream stream; source.Serialize(stream); const std::string serialized = stream.str(); EXPECT_NE(serialized.find("meshRef="), std::string::npos); - EXPECT_NE(serialized.find("meshPath=Meshes/serialized.mesh;"), std::string::npos); - EXPECT_EQ(serialized.find("mesh=Meshes/serialized.mesh;"), std::string::npos); + EXPECT_NE(serialized.find("meshPath=builtin://meshes/cube;"), std::string::npos); MeshFilterComponent target; std::stringstream deserializeStream(serialized); target.Deserialize(deserializeStream); - EXPECT_EQ(target.GetMeshPath(), "Meshes/serialized.mesh"); - EXPECT_EQ(target.GetMesh(), nullptr); + EXPECT_EQ(target.GetMeshPath(), "builtin://meshes/cube"); + ASSERT_NE(target.GetMesh(), nullptr); EXPECT_FALSE(target.GetMeshAssetRef().IsValid()); - - source.ClearMesh(); - delete mesh; } -TEST(MeshFilterComponent_Test, DeserializeSupportsLegacyMeshKey) { +TEST(MeshFilterComponent_Test, DeserializeIgnoresPlainMeshPathWithoutAssetRef) { MeshFilterComponent target; - std::stringstream stream("mesh=Meshes/legacy.mesh;meshRef=;"); + std::stringstream stream("meshPath=Meshes/legacy.mesh;meshRef=;"); target.Deserialize(stream); - EXPECT_EQ(target.GetMeshPath(), "Meshes/legacy.mesh"); + EXPECT_TRUE(target.GetMeshPath().empty()); EXPECT_EQ(target.GetMesh(), nullptr); EXPECT_FALSE(target.GetMeshAssetRef().IsValid()); } @@ -165,16 +160,16 @@ TEST(MeshFilterComponent_Test, DeferredSceneDeserializeLoadsMeshAsyncByPath) { { ResourceManager::ScopedDeferredSceneLoad deferredLoadScope; EXPECT_TRUE(manager.IsDeferredSceneLoadEnabled()); - std::stringstream stream("mesh=Meshes/async.mesh;meshRef=;"); + std::stringstream stream("meshPath=test://meshes/async.mesh;meshRef=;"); target.Deserialize(stream); } - EXPECT_EQ(target.GetMeshPath(), "Meshes/async.mesh"); + EXPECT_EQ(target.GetMeshPath(), "test://meshes/async.mesh"); EXPECT_EQ(target.GetMesh(), nullptr); EXPECT_GT(manager.GetAsyncPendingCount(), pendingBeforeDeserialize); ASSERT_TRUE(PumpAsyncLoadsUntilIdle(manager)); ASSERT_NE(target.GetMesh(), nullptr); - EXPECT_EQ(target.GetMeshPath(), "Meshes/async.mesh"); + EXPECT_EQ(target.GetMeshPath(), "test://meshes/async.mesh"); EXPECT_EQ(target.GetMesh()->GetVertexCount(), 3u); manager.RegisterLoader(originalLoader); @@ -209,10 +204,8 @@ TEST(MeshRendererComponent_Test, SetMaterialsKeepsSlotsAndFlags) { TEST(MeshRendererComponent_Test, SerializeAndDeserializePreservesMaterialPathsAndSettings) { MeshRendererComponent source; - Material* material0 = CreateTestMaterial("M0", "Materials/serialized0.mat"); - Material* material1 = CreateTestMaterial("M1", "Materials/serialized1.mat"); - source.SetMaterial(0, material0); - source.SetMaterial(1, material1); + source.SetMaterialPath(0, "builtin://materials/default-primitive"); + source.SetMaterialPath(1, "builtin://materials/default-primitive"); source.SetCastShadows(false); source.SetReceiveShadows(true); source.SetRenderLayer(3); @@ -221,33 +214,27 @@ TEST(MeshRendererComponent_Test, SerializeAndDeserializePreservesMaterialPathsAn source.Serialize(stream); const std::string serialized = stream.str(); EXPECT_NE( - serialized.find("materialPaths=Materials/serialized0.mat|Materials/serialized1.mat;"), + serialized.find("materialPaths=builtin://materials/default-primitive|builtin://materials/default-primitive;"), std::string::npos); EXPECT_NE(serialized.find("materialRefs=|;"), std::string::npos); - EXPECT_EQ(serialized.find("materials="), std::string::npos); MeshRendererComponent target; std::stringstream deserializeStream(serialized); target.Deserialize(deserializeStream); ASSERT_EQ(target.GetMaterialCount(), 2u); - EXPECT_EQ(target.GetMaterial(0), nullptr); - EXPECT_EQ(target.GetMaterial(1), nullptr); - EXPECT_EQ(target.GetMaterialPaths()[0], "Materials/serialized0.mat"); - EXPECT_EQ(target.GetMaterialPaths()[1], "Materials/serialized1.mat"); + ASSERT_NE(target.GetMaterial(0), nullptr); + ASSERT_NE(target.GetMaterial(1), nullptr); + EXPECT_EQ(target.GetMaterialPaths()[0], "builtin://materials/default-primitive"); + EXPECT_EQ(target.GetMaterialPaths()[1], "builtin://materials/default-primitive"); EXPECT_FALSE(target.GetCastShadows()); EXPECT_TRUE(target.GetReceiveShadows()); EXPECT_EQ(target.GetRenderLayer(), 3u); - - source.ClearMaterials(); - delete material0; - delete material1; } TEST(MeshRendererComponent_Test, SerializeAndDeserializePreservesTrailingEmptyMaterialSlots) { MeshRendererComponent source; - Material* material0 = CreateTestMaterial("M0", "Materials/serialized0.mat"); - source.SetMaterial(0, material0); + source.SetMaterialPath(0, "builtin://materials/default-primitive"); source.SetMaterialPath(1, ""); source.SetCastShadows(false); source.SetReceiveShadows(true); @@ -256,25 +243,21 @@ TEST(MeshRendererComponent_Test, SerializeAndDeserializePreservesTrailingEmptyMa std::stringstream stream; source.Serialize(stream); const std::string serialized = stream.str(); - EXPECT_NE(serialized.find("materialPaths=Materials/serialized0.mat|;"), std::string::npos); + EXPECT_NE(serialized.find("materialPaths=builtin://materials/default-primitive|;"), std::string::npos); EXPECT_NE(serialized.find("materialRefs=|;"), std::string::npos); - EXPECT_EQ(serialized.find("materials="), std::string::npos); MeshRendererComponent target; std::stringstream deserializeStream(serialized); target.Deserialize(deserializeStream); ASSERT_EQ(target.GetMaterialCount(), 2u); - EXPECT_EQ(target.GetMaterial(0), nullptr); + ASSERT_NE(target.GetMaterial(0), nullptr); EXPECT_EQ(target.GetMaterial(1), nullptr); - EXPECT_EQ(target.GetMaterialPath(0), "Materials/serialized0.mat"); + EXPECT_EQ(target.GetMaterialPath(0), "builtin://materials/default-primitive"); EXPECT_EQ(target.GetMaterialPath(1), ""); EXPECT_FALSE(target.GetCastShadows()); EXPECT_TRUE(target.GetReceiveShadows()); EXPECT_EQ(target.GetRenderLayer(), 9u); - - source.ClearMaterials(); - delete material0; } TEST(MeshRendererComponent_Test, SetMaterialPathPreservesPathWithoutLoadedResource) { @@ -293,15 +276,15 @@ TEST(MeshRendererComponent_Test, SetMaterialPathPreservesPathWithoutLoadedResour EXPECT_EQ(component.GetMaterial(1), nullptr); } -TEST(MeshRendererComponent_Test, DeserializeSupportsLegacyMaterialsKey) { +TEST(MeshRendererComponent_Test, DeserializeIgnoresPlainMaterialPathsWithoutAssetRefs) { MeshRendererComponent target; std::stringstream stream( - "materials=Materials/legacy0.mat|;materialRefs=|;castShadows=0;receiveShadows=1;renderLayer=5;"); + "materialPaths=Materials/legacy0.mat|;materialRefs=|;castShadows=0;receiveShadows=1;renderLayer=5;"); target.Deserialize(stream); ASSERT_EQ(target.GetMaterialCount(), 2u); - EXPECT_EQ(target.GetMaterialPath(0), "Materials/legacy0.mat"); + EXPECT_EQ(target.GetMaterialPath(0), ""); EXPECT_EQ(target.GetMaterialPath(1), ""); EXPECT_EQ(target.GetMaterial(0), nullptr); EXPECT_EQ(target.GetMaterial(1), nullptr); @@ -351,7 +334,6 @@ TEST(MeshRendererComponent_Test, SerializeAndDeserializeLoadsProjectMaterialByAs EXPECT_NE(serialized.find("materialPaths=;"), std::string::npos); EXPECT_NE(serialized.find("materialRefs="), std::string::npos); EXPECT_EQ(serialized.find("materialRefs=;"), std::string::npos); - EXPECT_EQ(serialized.find("materials="), std::string::npos); std::stringstream deserializeStream(serialized); MeshRendererComponent target; diff --git a/tests/Core/Asset/test_resource_handle.cpp b/tests/Core/Asset/test_resource_handle.cpp index 6d04f359..193ec9ed 100644 --- a/tests/Core/Asset/test_resource_handle.cpp +++ b/tests/Core/Asset/test_resource_handle.cpp @@ -191,4 +191,16 @@ TEST(ResourceHandle, EqualityOperators) { handle2.Reset(); } +TEST(ResourceHandle, ResetDoesNotDereferenceDestroyedResourcePointer) { + TestResource* resource = new TestResource(); + resource->Initialize({ "Test", "test.png", ResourceGUID(321), 100 }); + + ResourceHandle handle(resource); + delete resource; + + handle.Reset(); + EXPECT_EQ(handle.Get(), nullptr); + EXPECT_EQ(handle.GetGUID().value, 0u); +} + } // namespace diff --git a/tests/Core/UI/test_layout_engine.cpp b/tests/Core/UI/test_layout_engine.cpp deleted file mode 100644 index 12d0b218..00000000 --- a/tests/Core/UI/test_layout_engine.cpp +++ /dev/null @@ -1,130 +0,0 @@ -#include - -#include - -namespace { - -using XCEngine::UI::UIRect; -using XCEngine::UI::UISize; -using XCEngine::UI::Layout::ArrangeOverlayLayout; -using XCEngine::UI::Layout::ArrangeStackLayout; -using XCEngine::UI::Layout::MeasureOverlayLayout; -using XCEngine::UI::Layout::MeasureStackLayout; -using XCEngine::UI::Layout::UILayoutAlignment; -using XCEngine::UI::Layout::UILayoutAxis; -using XCEngine::UI::Layout::UILayoutConstraints; -using XCEngine::UI::Layout::UILayoutItem; -using XCEngine::UI::Layout::UILayoutLength; -using XCEngine::UI::Layout::UILayoutThickness; -using XCEngine::UI::Layout::UIOverlayLayoutOptions; -using XCEngine::UI::Layout::UIStackLayoutOptions; - -void ExpectRect( - const UIRect& rect, - float x, - float y, - float width, - float height) { - EXPECT_FLOAT_EQ(rect.x, x); - EXPECT_FLOAT_EQ(rect.y, y); - EXPECT_FLOAT_EQ(rect.width, width); - EXPECT_FLOAT_EQ(rect.height, height); -} - -} // namespace - -TEST(UI_Layout, MeasureHorizontalStackAccumulatesSpacingPaddingAndCrossExtent) { - UIStackLayoutOptions options = {}; - options.axis = UILayoutAxis::Horizontal; - options.spacing = 5.0f; - options.padding = UILayoutThickness::Symmetric(10.0f, 6.0f); - - std::vector items(2); - items[0].desiredContentSize = UISize(40.0f, 20.0f); - items[1].desiredContentSize = UISize(60.0f, 30.0f); - - const auto result = MeasureStackLayout(options, items); - - EXPECT_FLOAT_EQ(result.desiredSize.width, 125.0f); - EXPECT_FLOAT_EQ(result.desiredSize.height, 42.0f); -} - -TEST(UI_Layout, ArrangeHorizontalStackDistributesRemainingSpaceToStretchChildren) { - UIStackLayoutOptions options = {}; - options.axis = UILayoutAxis::Horizontal; - options.spacing = 5.0f; - options.padding = UILayoutThickness::Uniform(10.0f); - - std::vector items(3); - items[0].width = UILayoutLength::Pixels(100.0f); - items[0].desiredContentSize = UISize(10.0f, 20.0f); - - items[1].width = UILayoutLength::Stretch(1.0f); - items[1].desiredContentSize = UISize(30.0f, 20.0f); - - items[2].width = UILayoutLength::Pixels(50.0f); - items[2].desiredContentSize = UISize(10.0f, 20.0f); - - const auto result = ArrangeStackLayout(options, items, UIRect(0.0f, 0.0f, 300.0f, 80.0f)); - - ExpectRect(result.children[0].arrangedRect, 10.0f, 10.0f, 100.0f, 20.0f); - ExpectRect(result.children[1].arrangedRect, 115.0f, 10.0f, 120.0f, 20.0f); - ExpectRect(result.children[2].arrangedRect, 240.0f, 10.0f, 50.0f, 20.0f); -} - -TEST(UI_Layout, ArrangeVerticalStackSupportsCrossAxisStretch) { - UIStackLayoutOptions options = {}; - options.axis = UILayoutAxis::Vertical; - options.spacing = 4.0f; - options.padding = UILayoutThickness::Symmetric(8.0f, 6.0f); - - std::vector items(2); - items[0].desiredContentSize = UISize(40.0f, 10.0f); - items[0].horizontalAlignment = UILayoutAlignment::Stretch; - items[1].desiredContentSize = UISize(60.0f, 20.0f); - - const auto result = ArrangeStackLayout(options, items, UIRect(0.0f, 0.0f, 200.0f, 100.0f)); - - ExpectRect(result.children[0].arrangedRect, 8.0f, 6.0f, 184.0f, 10.0f); - ExpectRect(result.children[1].arrangedRect, 8.0f, 20.0f, 60.0f, 20.0f); -} - -TEST(UI_Layout, ArrangeOverlaySupportsCenterAndStretch) { - UIOverlayLayoutOptions options = {}; - options.padding = UILayoutThickness::Uniform(10.0f); - - std::vector items(2); - items[0].desiredContentSize = UISize(40.0f, 20.0f); - items[0].horizontalAlignment = UILayoutAlignment::Center; - items[0].verticalAlignment = UILayoutAlignment::Center; - - items[1].desiredContentSize = UISize(10.0f, 10.0f); - items[1].width = UILayoutLength::Stretch(); - items[1].height = UILayoutLength::Stretch(); - items[1].margin = UILayoutThickness::Uniform(5.0f); - - const auto result = ArrangeOverlayLayout(options, items, UIRect(0.0f, 0.0f, 100.0f, 60.0f)); - - ExpectRect(result.children[0].arrangedRect, 30.0f, 20.0f, 40.0f, 20.0f); - ExpectRect(result.children[1].arrangedRect, 15.0f, 15.0f, 70.0f, 30.0f); -} - -TEST(UI_Layout, MeasureOverlayRespectsItemMinMaxAndAvailableConstraints) { - UIOverlayLayoutOptions options = {}; - - std::vector items(1); - items[0].width = UILayoutLength::Pixels(500.0f); - items[0].desiredContentSize = UISize(10.0f, 10.0f); - items[0].minSize = UISize(0.0f, 50.0f); - items[0].maxSize = UISize(200.0f, 120.0f); - - const auto result = MeasureOverlayLayout( - options, - items, - UILayoutConstraints::Bounded(150.0f, 100.0f)); - - EXPECT_FLOAT_EQ(result.children[0].measuredSize.width, 150.0f); - EXPECT_FLOAT_EQ(result.children[0].measuredSize.height, 50.0f); - EXPECT_FLOAT_EQ(result.desiredSize.width, 150.0f); - EXPECT_FLOAT_EQ(result.desiredSize.height, 50.0f); -} diff --git a/tests/Core/UI/test_ui_core.cpp b/tests/Core/UI/test_ui_core.cpp deleted file mode 100644 index 3fc54814..00000000 --- a/tests/Core/UI/test_ui_core.cpp +++ /dev/null @@ -1,240 +0,0 @@ -#include - -#include -#include -#include - -namespace { - -using XCEngine::UI::HasAnyDirtyFlags; -using XCEngine::UI::IUIViewModel; -using XCEngine::UI::RevisionedViewModelBase; -using XCEngine::UI::UIBuildContext; -using XCEngine::UI::UIBuildElementDesc; -using XCEngine::UI::UIContext; -using XCEngine::UI::UIDirtyFlags; -using XCEngine::UI::UIElementChangeKind; -using XCEngine::UI::UIElementId; -using XCEngine::UI::UIElementNode; -using XCEngine::UI::UIElementTree; -using XCEngine::Resources::UIDocumentKind; -using XCEngine::Resources::UIDocumentModel; -using XCEngine::Resources::UISchema; -using XCEngine::Resources::UIView; - -class TestViewModel : public RevisionedViewModelBase { -public: - void Touch() { - MarkViewModelChanged(); - } -}; - -UIBuildElementDesc MakeElement( - UIElementId id, - const char* typeName, - std::uint64_t localStateRevision = 0, - const IUIViewModel* viewModel = nullptr, - std::uint64_t structuralRevision = 0) { - UIBuildElementDesc desc = {}; - desc.id = id; - desc.typeName = typeName; - desc.localStateRevision = localStateRevision; - desc.viewModel = viewModel; - desc.structuralRevision = structuralRevision; - return desc; -} - -void BuildBasicTree(UIBuildContext& buildContext) { - auto root = buildContext.PushElement(MakeElement(1, "Root")); - EXPECT_TRUE(static_cast(root)); - EXPECT_TRUE(buildContext.AddLeaf(MakeElement(2, "Label"))); - auto panel = buildContext.PushElement(MakeElement(3, "Panel")); - EXPECT_TRUE(static_cast(panel)); - EXPECT_TRUE(buildContext.AddLeaf(MakeElement(4, "Button"))); -} - -const UIElementNode& RequireNode(const UIElementTree& tree, UIElementId id) { - const UIElementNode* node = tree.FindNode(id); - EXPECT_NE(node, nullptr); - return *node; -} - -TEST(UICoreTest, RebuildCreatesStableParentChildTree) { - UIContext context = {}; - const auto result = context.Rebuild(BuildBasicTree); - - EXPECT_TRUE(result.succeeded); - EXPECT_TRUE(result.treeChanged); - EXPECT_EQ(result.generation, 1u); - EXPECT_EQ(context.GetElementTree().GetRootId(), 1u); - EXPECT_EQ(context.GetElementTree().GetNodeCount(), 4u); - EXPECT_TRUE(result.HasChange(1)); - EXPECT_TRUE(result.HasChange(4)); - - const UIElementNode& root = RequireNode(context.GetElementTree(), 1); - ASSERT_EQ(root.childIds.size(), 2u); - EXPECT_EQ(root.childIds[0], 2u); - EXPECT_EQ(root.childIds[1], 3u); - EXPECT_EQ(root.depth, 0u); - - const UIElementNode& panel = RequireNode(context.GetElementTree(), 3); - ASSERT_EQ(panel.childIds.size(), 1u); - EXPECT_EQ(panel.childIds[0], 4u); - EXPECT_EQ(panel.depth, 1u); - - ASSERT_EQ(result.dirtyRootIds.size(), 1u); - EXPECT_EQ(result.dirtyRootIds[0], 1u); -} - -TEST(UICoreTest, RebuildSkipsUnchangedTreeAfterDirtyFlagsAreCleared) { - UIContext context = {}; - const auto initial = context.Rebuild(BuildBasicTree); - ASSERT_TRUE(initial.succeeded); - context.GetElementTree().ClearAllDirtyFlags(); - - const auto result = context.Rebuild(BuildBasicTree); - - EXPECT_TRUE(result.succeeded); - EXPECT_FALSE(result.treeChanged); - EXPECT_TRUE(result.changes.empty()); - EXPECT_TRUE(result.dirtyRootIds.empty()); -} - -TEST(UICoreTest, LocalStateChangeOnlyInvalidatesTheChangedLeaf) { - UIContext context = {}; - ASSERT_TRUE(context.Rebuild(BuildBasicTree).succeeded); - context.GetElementTree().ClearAllDirtyFlags(); - - const auto result = context.Rebuild([](UIBuildContext& buildContext) { - auto root = buildContext.PushElement(MakeElement(1, "Root")); - EXPECT_TRUE(static_cast(root)); - EXPECT_TRUE(buildContext.AddLeaf(MakeElement(2, "Label", 1))); - auto panel = buildContext.PushElement(MakeElement(3, "Panel")); - EXPECT_TRUE(static_cast(panel)); - EXPECT_TRUE(buildContext.AddLeaf(MakeElement(4, "Button"))); - }); - - EXPECT_TRUE(result.succeeded); - EXPECT_TRUE(result.treeChanged); - ASSERT_EQ(result.changes.size(), 1u); - ASSERT_NE(result.FindChange(2), nullptr); - EXPECT_EQ(result.FindChange(2)->kind, UIElementChangeKind::Updated); - EXPECT_TRUE(HasAnyDirtyFlags(result.FindChange(2)->dirtyFlags, UIDirtyFlags::LocalState)); - EXPECT_TRUE(HasAnyDirtyFlags(result.FindChange(2)->dirtyFlags, UIDirtyFlags::Paint)); - - ASSERT_EQ(result.dirtyRootIds.size(), 1u); - EXPECT_EQ(result.dirtyRootIds[0], 2u); - - const UIElementNode& leaf = RequireNode(context.GetElementTree(), 2); - EXPECT_TRUE(HasAnyDirtyFlags(leaf.dirtyFlags, UIDirtyFlags::LocalState)); - EXPECT_FALSE(RequireNode(context.GetElementTree(), 1).IsDirty()); -} - -TEST(UICoreTest, ViewModelRevisionChangeInvalidatesBoundElement) { - TestViewModel viewModel = {}; - UIContext context = {}; - ASSERT_TRUE(context.Rebuild([&](UIBuildContext& buildContext) { - auto root = buildContext.PushElement(MakeElement(1, "Root")); - EXPECT_TRUE(static_cast(root)); - EXPECT_TRUE(buildContext.AddLeaf(MakeElement(2, "Inspector", 0, &viewModel))); - }).succeeded); - context.GetElementTree().ClearAllDirtyFlags(); - - viewModel.Touch(); - const auto result = context.Rebuild([&](UIBuildContext& buildContext) { - auto root = buildContext.PushElement(MakeElement(1, "Root")); - EXPECT_TRUE(static_cast(root)); - EXPECT_TRUE(buildContext.AddLeaf(MakeElement(2, "Inspector", 0, &viewModel))); - }); - - EXPECT_TRUE(result.succeeded); - ASSERT_NE(result.FindChange(2), nullptr); - EXPECT_TRUE(HasAnyDirtyFlags(result.FindChange(2)->dirtyFlags, UIDirtyFlags::ViewModel)); - EXPECT_TRUE(HasAnyDirtyFlags(result.FindChange(2)->dirtyFlags, UIDirtyFlags::Paint)); - ASSERT_EQ(result.dirtyRootIds.size(), 1u); - EXPECT_EQ(result.dirtyRootIds[0], 2u); -} - -TEST(UICoreTest, StructuralChangesBubbleLayoutInvalidationToAncestors) { - UIContext context = {}; - ASSERT_TRUE(context.Rebuild([](UIBuildContext& buildContext) { - auto root = buildContext.PushElement(MakeElement(1, "Root")); - EXPECT_TRUE(static_cast(root)); - auto panel = buildContext.PushElement(MakeElement(2, "Panel")); - EXPECT_TRUE(static_cast(panel)); - EXPECT_TRUE(buildContext.AddLeaf(MakeElement(3, "Text"))); - }).succeeded); - context.GetElementTree().ClearAllDirtyFlags(); - - const auto result = context.Rebuild([](UIBuildContext& buildContext) { - auto root = buildContext.PushElement(MakeElement(1, "Root")); - EXPECT_TRUE(static_cast(root)); - auto panel = buildContext.PushElement(MakeElement(2, "Panel")); - EXPECT_TRUE(static_cast(panel)); - EXPECT_TRUE(buildContext.AddLeaf(MakeElement(3, "Text"))); - EXPECT_TRUE(buildContext.AddLeaf(MakeElement(4, "Icon"))); - }); - - EXPECT_TRUE(result.succeeded); - EXPECT_TRUE(result.HasChange(4)); - ASSERT_NE(result.FindChange(2), nullptr); - EXPECT_TRUE(HasAnyDirtyFlags(result.FindChange(2)->dirtyFlags, UIDirtyFlags::Structure)); - - const UIElementNode& root = RequireNode(context.GetElementTree(), 1); - const UIElementNode& panel = RequireNode(context.GetElementTree(), 2); - EXPECT_TRUE(HasAnyDirtyFlags(root.dirtyFlags, UIDirtyFlags::Layout)); - EXPECT_TRUE(HasAnyDirtyFlags(panel.dirtyFlags, UIDirtyFlags::Structure)); - - ASSERT_EQ(result.dirtyRootIds.size(), 1u); - EXPECT_EQ(result.dirtyRootIds[0], 1u); -} - -TEST(UICoreTest, RebuildFailsWhenElementScopesRemainOpen) { - UIContext context = {}; - const auto result = context.Rebuild([](UIBuildContext& buildContext) { - EXPECT_TRUE(buildContext.BeginElement(MakeElement(1, "Root"))); - }); - - EXPECT_FALSE(result.succeeded); - EXPECT_FALSE(result.errorMessage.empty()); - EXPECT_EQ(context.GetElementTree().GetNodeCount(), 0u); -} - -TEST(UICoreTest, UIDocumentResourcesAcceptMovedDocumentModels) { - UIDocumentModel viewDocument = {}; - viewDocument.kind = UIDocumentKind::View; - viewDocument.sourcePath = "Assets/UI/Test.xcui"; - viewDocument.displayName = "TestView"; - viewDocument.rootNode.tagName = "View"; - viewDocument.valid = true; - - UIView view = {}; - XCEngine::Resources::IResource::ConstructParams params = {}; - params.name = "TestView"; - params.path = viewDocument.sourcePath; - params.guid = XCEngine::Resources::ResourceGUID::Generate(params.path); - view.Initialize(params); - view.SetDocumentModel(std::move(viewDocument)); - EXPECT_EQ(view.GetRootNode().tagName, "View"); - EXPECT_EQ(view.GetSourcePath(), "Assets/UI/Test.xcui"); - - UIDocumentModel schemaDocument = {}; - schemaDocument.kind = UIDocumentKind::Schema; - schemaDocument.sourcePath = "Assets/UI/Test.xcschema"; - schemaDocument.displayName = "TestSchema"; - schemaDocument.rootNode.tagName = "Schema"; - schemaDocument.schemaDefinition.name = "TestSchema"; - schemaDocument.schemaDefinition.valid = true; - schemaDocument.valid = true; - - UISchema schema = {}; - params.name = "TestSchema"; - params.path = schemaDocument.sourcePath; - params.guid = XCEngine::Resources::ResourceGUID::Generate(params.path); - schema.Initialize(params); - schema.SetDocumentModel(std::move(schemaDocument)); - EXPECT_TRUE(schema.GetSchemaDefinition().valid); - EXPECT_EQ(schema.GetSchemaDefinition().name, "TestSchema"); -} - -} // namespace diff --git a/tests/Core/UI/test_ui_editor_collection_primitives.cpp b/tests/Core/UI/test_ui_editor_collection_primitives.cpp deleted file mode 100644 index 757d4cba..00000000 --- a/tests/Core/UI/test_ui_editor_collection_primitives.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include - -#include -#include -#include - -namespace { - -namespace Style = XCEngine::UI::Style; -namespace UIWidgets = XCEngine::UI::Widgets; - -Style::UITheme BuildEditorPrimitiveTheme() { - Style::UIThemeDefinition definition = {}; - definition.SetToken("space.cardInset", Style::UIStyleValue(14.0f)); - definition.SetToken("size.treeItemHeight", Style::UIStyleValue(30.0f)); - definition.SetToken("size.listItemHeight", Style::UIStyleValue(64.0f)); - definition.SetToken("size.fieldRowHeight", Style::UIStyleValue(36.0f)); - definition.SetToken("size.propertySectionHeight", Style::UIStyleValue(156.0f)); - definition.SetToken("size.treeIndent", Style::UIStyleValue(20.0f)); - return Style::BuildTheme(definition); -} - -TEST(UIEditorCollectionPrimitivesTest, ClassifyAndFlagsMatchEditorCollectionTags) { - using Kind = UIWidgets::UIEditorCollectionPrimitiveKind; - - EXPECT_EQ(UIWidgets::ClassifyUIEditorCollectionPrimitive("ScrollView"), Kind::ScrollView); - EXPECT_EQ(UIWidgets::ClassifyUIEditorCollectionPrimitive("TreeView"), Kind::TreeView); - EXPECT_EQ(UIWidgets::ClassifyUIEditorCollectionPrimitive("TreeItem"), Kind::TreeItem); - EXPECT_EQ(UIWidgets::ClassifyUIEditorCollectionPrimitive("ListView"), Kind::ListView); - EXPECT_EQ(UIWidgets::ClassifyUIEditorCollectionPrimitive("ListItem"), Kind::ListItem); - EXPECT_EQ(UIWidgets::ClassifyUIEditorCollectionPrimitive("PropertySection"), Kind::PropertySection); - EXPECT_EQ(UIWidgets::ClassifyUIEditorCollectionPrimitive("FieldRow"), Kind::FieldRow); - EXPECT_EQ(UIWidgets::ClassifyUIEditorCollectionPrimitive("Column"), Kind::None); - - EXPECT_TRUE(UIWidgets::IsUIEditorCollectionPrimitiveContainer(Kind::ScrollView)); - EXPECT_TRUE(UIWidgets::IsUIEditorCollectionPrimitiveContainer(Kind::TreeView)); - EXPECT_TRUE(UIWidgets::IsUIEditorCollectionPrimitiveContainer(Kind::ListView)); - EXPECT_TRUE(UIWidgets::IsUIEditorCollectionPrimitiveContainer(Kind::PropertySection)); - EXPECT_FALSE(UIWidgets::IsUIEditorCollectionPrimitiveContainer(Kind::TreeItem)); - - EXPECT_TRUE(UIWidgets::UsesUIEditorCollectionPrimitiveColumnLayout(Kind::TreeView)); - EXPECT_TRUE(UIWidgets::UsesUIEditorCollectionPrimitiveColumnLayout(Kind::ListView)); - EXPECT_TRUE(UIWidgets::UsesUIEditorCollectionPrimitiveColumnLayout(Kind::PropertySection)); - EXPECT_FALSE(UIWidgets::UsesUIEditorCollectionPrimitiveColumnLayout(Kind::ScrollView)); - - EXPECT_TRUE(UIWidgets::DoesUIEditorCollectionPrimitiveClipChildren(Kind::ScrollView)); - EXPECT_TRUE(UIWidgets::DoesUIEditorCollectionPrimitiveClipChildren(Kind::TreeView)); - EXPECT_TRUE(UIWidgets::DoesUIEditorCollectionPrimitiveClipChildren(Kind::ListView)); - EXPECT_FALSE(UIWidgets::DoesUIEditorCollectionPrimitiveClipChildren(Kind::PropertySection)); - - EXPECT_TRUE(UIWidgets::IsUIEditorCollectionPrimitiveHoverable(Kind::TreeItem)); - EXPECT_TRUE(UIWidgets::IsUIEditorCollectionPrimitiveHoverable(Kind::ListItem)); - EXPECT_TRUE(UIWidgets::IsUIEditorCollectionPrimitiveHoverable(Kind::PropertySection)); - EXPECT_TRUE(UIWidgets::IsUIEditorCollectionPrimitiveHoverable(Kind::FieldRow)); - EXPECT_FALSE(UIWidgets::IsUIEditorCollectionPrimitiveHoverable(Kind::TreeView)); -} - -TEST(UIEditorCollectionPrimitivesTest, ResolveMetricsUseThemeTokensAndFallbacks) { - using Kind = UIWidgets::UIEditorCollectionPrimitiveKind; - - const Style::UITheme themed = BuildEditorPrimitiveTheme(); - const Style::UITheme fallback = Style::UITheme(); - - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::TreeView, themed), 14.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::ListView, themed), 14.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::PropertySection, themed), 14.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::ScrollView, themed), 0.0f); - - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::TreeItem, themed), 30.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::ListItem, themed), 64.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::FieldRow, themed), 36.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::PropertySection, themed), 156.0f); - - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::TreeItem, fallback), 28.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::ListItem, fallback), 60.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::FieldRow, fallback), 32.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::PropertySection, fallback), 148.0f); - - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveIndent(Kind::TreeItem, themed, 2.0f), 40.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveIndent(Kind::TreeItem, fallback, 2.0f), 36.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveIndent(Kind::ListItem, themed, 2.0f), 0.0f); -} - -} // namespace diff --git a/tests/Core/UI/test_ui_editor_panel_chrome.cpp b/tests/Core/UI/test_ui_editor_panel_chrome.cpp deleted file mode 100644 index c63d9986..00000000 --- a/tests/Core/UI/test_ui_editor_panel_chrome.cpp +++ /dev/null @@ -1,126 +0,0 @@ -#include - -#include -#include - -namespace { - -using XCEngine::UI::UIColor; -using XCEngine::UI::UIDrawCommandType; -using XCEngine::UI::UIDrawList; -using XCEngine::UI::UIRect; -using XCEngine::UI::Widgets::AppendUIEditorPanelChromeBackground; -using XCEngine::UI::Widgets::AppendUIEditorPanelChromeForeground; -using XCEngine::UI::Widgets::BuildUIEditorPanelChromeHeaderRect; -using XCEngine::UI::Widgets::ResolveUIEditorPanelChromeBorderColor; -using XCEngine::UI::Widgets::ResolveUIEditorPanelChromeBorderThickness; -using XCEngine::UI::Widgets::UIEditorPanelChromePalette; -using XCEngine::UI::Widgets::UIEditorPanelChromeState; -using XCEngine::UI::Widgets::UIEditorPanelChromeText; - -void ExpectColorEq( - const UIColor& actual, - const UIColor& expected) { - EXPECT_FLOAT_EQ(actual.r, expected.r); - EXPECT_FLOAT_EQ(actual.g, expected.g); - EXPECT_FLOAT_EQ(actual.b, expected.b); - EXPECT_FLOAT_EQ(actual.a, expected.a); -} - -TEST(UIEditorPanelChromeTest, HeaderRectAndBorderPolicyMatchNativeShellCardChrome) { - const UIRect panelRect(100.0f, 200.0f, 320.0f, 180.0f); - const UIEditorPanelChromePalette palette = {}; - - const auto headerRect = BuildUIEditorPanelChromeHeaderRect(panelRect); - EXPECT_FLOAT_EQ(headerRect.x, 100.0f); - EXPECT_FLOAT_EQ(headerRect.y, 200.0f); - EXPECT_FLOAT_EQ(headerRect.width, 320.0f); - EXPECT_FLOAT_EQ(headerRect.height, 42.0f); - - ExpectColorEq( - ResolveUIEditorPanelChromeBorderColor(UIEditorPanelChromeState(), palette), - palette.borderColor); - ExpectColorEq( - ResolveUIEditorPanelChromeBorderColor(UIEditorPanelChromeState{ false, true }, palette), - palette.hoveredAccentColor); - ExpectColorEq( - ResolveUIEditorPanelChromeBorderColor(UIEditorPanelChromeState{ true, true }, palette), - palette.accentColor); - - EXPECT_FLOAT_EQ(ResolveUIEditorPanelChromeBorderThickness(UIEditorPanelChromeState()), 1.0f); - EXPECT_FLOAT_EQ(ResolveUIEditorPanelChromeBorderThickness(UIEditorPanelChromeState{ true, false }), 2.0f); -} - -TEST(UIEditorPanelChromeTest, BackgroundAppendEmitsSurfaceOutlineAndHeaderFill) { - UIDrawList drawList("PanelChrome"); - const UIRect panelRect(40.0f, 60.0f, 400.0f, 280.0f); - const UIEditorPanelChromeState state{ true, false }; - const UIEditorPanelChromePalette palette = {}; - - AppendUIEditorPanelChromeBackground(drawList, panelRect, state, palette); - - ASSERT_EQ(drawList.GetCommandCount(), 3u); - const auto& commands = drawList.GetCommands(); - EXPECT_EQ(commands[0].type, UIDrawCommandType::FilledRect); - EXPECT_EQ(commands[1].type, UIDrawCommandType::RectOutline); - EXPECT_EQ(commands[2].type, UIDrawCommandType::FilledRect); - - EXPECT_FLOAT_EQ(commands[0].rect.x, 40.0f); - EXPECT_FLOAT_EQ(commands[0].rounding, 18.0f); - ExpectColorEq(commands[0].color, palette.surfaceColor); - - EXPECT_FLOAT_EQ(commands[1].thickness, 2.0f); - EXPECT_FLOAT_EQ(commands[1].rounding, 18.0f); - ExpectColorEq(commands[1].color, palette.accentColor); - - EXPECT_FLOAT_EQ(commands[2].rect.height, 42.0f); - EXPECT_FLOAT_EQ(commands[2].rounding, 18.0f); - ExpectColorEq(commands[2].color, palette.headerColor); -} - -TEST(UIEditorPanelChromeTest, ForegroundAppendPlacesTitleSubtitleAndFooterAtCurrentOffsets) { - UIDrawList drawList("PanelChromeText"); - const UIRect panelRect(100.0f, 200.0f, 320.0f, 180.0f); - const UIEditorPanelChromePalette palette = {}; - const UIEditorPanelChromeText text{ - "XCUI Demo", - "native queued offscreen surface", - "Active | 42 elements | 9 cmds" - }; - - AppendUIEditorPanelChromeForeground(drawList, panelRect, text, palette); - - ASSERT_EQ(drawList.GetCommandCount(), 3u); - const auto& commands = drawList.GetCommands(); - EXPECT_EQ(commands[0].type, UIDrawCommandType::Text); - EXPECT_EQ(commands[1].type, UIDrawCommandType::Text); - EXPECT_EQ(commands[2].type, UIDrawCommandType::Text); - - EXPECT_EQ(commands[0].text, "XCUI Demo"); - EXPECT_FLOAT_EQ(commands[0].position.x, 116.0f); - EXPECT_FLOAT_EQ(commands[0].position.y, 212.0f); - ExpectColorEq(commands[0].color, palette.textPrimary); - - EXPECT_EQ(commands[1].text, "native queued offscreen surface"); - EXPECT_FLOAT_EQ(commands[1].position.x, 116.0f); - EXPECT_FLOAT_EQ(commands[1].position.y, 228.0f); - ExpectColorEq(commands[1].color, palette.textSecondary); - - EXPECT_EQ(commands[2].text, "Active | 42 elements | 9 cmds"); - EXPECT_FLOAT_EQ(commands[2].position.x, 116.0f); - EXPECT_FLOAT_EQ(commands[2].position.y, 362.0f); - ExpectColorEq(commands[2].color, palette.textMuted); -} - -TEST(UIEditorPanelChromeTest, ForegroundAppendSkipsEmptyStrings) { - UIDrawList drawList("PanelChromeEmptyText"); - - AppendUIEditorPanelChromeForeground( - drawList, - UIRect(0.0f, 0.0f, 320.0f, 180.0f), - UIEditorPanelChromeText{}); - - EXPECT_EQ(drawList.GetCommandCount(), 0u); -} - -} // namespace diff --git a/tests/Core/UI/test_ui_expansion_model.cpp b/tests/Core/UI/test_ui_expansion_model.cpp deleted file mode 100644 index 2128a4db..00000000 --- a/tests/Core/UI/test_ui_expansion_model.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include - -#include - -namespace { - -using XCEngine::UI::Widgets::UIExpansionModel; - -TEST(UIExpansionModelTest, ExpandCollapseAndClearTrackExpandedItems) { - UIExpansionModel expansion = {}; - - EXPECT_FALSE(expansion.HasExpandedItems()); - EXPECT_EQ(expansion.GetExpandedCount(), 0u); - - EXPECT_TRUE(expansion.Expand("treeAssetsRoot")); - EXPECT_TRUE(expansion.IsExpanded("treeAssetsRoot")); - EXPECT_TRUE(expansion.HasExpandedItems()); - EXPECT_EQ(expansion.GetExpandedCount(), 1u); - - EXPECT_FALSE(expansion.Expand("treeAssetsRoot")); - EXPECT_TRUE(expansion.Collapse("treeAssetsRoot")); - EXPECT_FALSE(expansion.IsExpanded("treeAssetsRoot")); - EXPECT_EQ(expansion.GetExpandedCount(), 0u); - EXPECT_FALSE(expansion.Collapse("treeAssetsRoot")); - EXPECT_FALSE(expansion.Clear()); -} - -TEST(UIExpansionModelTest, SetAndToggleExpandedReplaceStateForMatchingItem) { - UIExpansionModel expansion = {}; - - EXPECT_TRUE(expansion.SetExpanded("inspectorTransform", true)); - EXPECT_TRUE(expansion.IsExpanded("inspectorTransform")); - EXPECT_EQ(expansion.GetExpandedCount(), 1u); - - EXPECT_FALSE(expansion.SetExpanded("inspectorTransform", true)); - EXPECT_TRUE(expansion.ToggleExpanded("inspectorTransform")); - EXPECT_FALSE(expansion.IsExpanded("inspectorTransform")); - - EXPECT_TRUE(expansion.ToggleExpanded("inspectorMesh")); - EXPECT_TRUE(expansion.IsExpanded("inspectorMesh")); - EXPECT_TRUE(expansion.SetExpanded("inspectorMesh", false)); - EXPECT_FALSE(expansion.IsExpanded("inspectorMesh")); -} - -} // namespace diff --git a/tests/Core/UI/test_ui_flat_hierarchy_helpers.cpp b/tests/Core/UI/test_ui_flat_hierarchy_helpers.cpp deleted file mode 100644 index 86dd97c8..00000000 --- a/tests/Core/UI/test_ui_flat_hierarchy_helpers.cpp +++ /dev/null @@ -1,145 +0,0 @@ -#include - -#include - -#include -#include -#include - -namespace { - -using XCEngine::UI::Widgets::kInvalidUIFlatHierarchyItemOffset; -using XCEngine::UI::Widgets::UIFlatHierarchyFindFirstVisibleChildOffset; -using XCEngine::UI::Widgets::UIFlatHierarchyFindParentOffset; -using XCEngine::UI::Widgets::UIFlatHierarchyHasChildren; -using XCEngine::UI::Widgets::UIFlatHierarchyIsVisible; - -struct FlatHierarchyItem { - float depth = 0.0f; -}; - -TEST(UIFlatHierarchyHelpersTest, HasChildrenAndParentResolutionTrackIndentedBranches) { - const std::array items = {{ - { 0.0f }, - { 1.0f }, - { 2.0f }, - { 1.0f }, - { 0.0f }, - }}; - const std::vector itemIndices = { 0u, 1u, 2u, 3u, 4u }; - - const auto resolveDepth = [&items](std::size_t itemIndex) { - return items[itemIndex].depth; - }; - - EXPECT_TRUE(UIFlatHierarchyHasChildren(itemIndices, 0u, resolveDepth)); - EXPECT_TRUE(UIFlatHierarchyHasChildren(itemIndices, 1u, resolveDepth)); - EXPECT_FALSE(UIFlatHierarchyHasChildren(itemIndices, 2u, resolveDepth)); - EXPECT_FALSE(UIFlatHierarchyHasChildren(itemIndices, 3u, resolveDepth)); - EXPECT_FALSE(UIFlatHierarchyHasChildren(itemIndices, 4u, resolveDepth)); - - EXPECT_EQ( - UIFlatHierarchyFindParentOffset(itemIndices, 0u, resolveDepth), - kInvalidUIFlatHierarchyItemOffset); - EXPECT_EQ(UIFlatHierarchyFindParentOffset(itemIndices, 1u, resolveDepth), 0u); - EXPECT_EQ(UIFlatHierarchyFindParentOffset(itemIndices, 2u, resolveDepth), 1u); - EXPECT_EQ(UIFlatHierarchyFindParentOffset(itemIndices, 3u, resolveDepth), 0u); - EXPECT_EQ( - UIFlatHierarchyFindParentOffset(itemIndices, 99u, resolveDepth), - kInvalidUIFlatHierarchyItemOffset); -} - -TEST(UIFlatHierarchyHelpersTest, VisibilityFollowsCollapsedAncestorStateAcrossDepthTransitions) { - const std::array items = {{ - { 0.0f }, - { 1.0f }, - { 2.0f }, - { 1.0f }, - }}; - const std::vector itemIndices = { 0u, 1u, 2u, 3u }; - - const auto resolveDepth = [&items](std::size_t itemIndex) { - return items[itemIndex].depth; - }; - - std::unordered_set expandedItems = { 0u }; - const auto isExpanded = [&expandedItems](std::size_t itemIndex) { - return expandedItems.contains(itemIndex); - }; - - EXPECT_TRUE(UIFlatHierarchyIsVisible(itemIndices, 0u, resolveDepth, isExpanded)); - EXPECT_TRUE(UIFlatHierarchyIsVisible(itemIndices, 1u, resolveDepth, isExpanded)); - EXPECT_FALSE(UIFlatHierarchyIsVisible(itemIndices, 2u, resolveDepth, isExpanded)); - EXPECT_TRUE(UIFlatHierarchyIsVisible(itemIndices, 3u, resolveDepth, isExpanded)); - - expandedItems.insert(1u); - EXPECT_TRUE(UIFlatHierarchyIsVisible(itemIndices, 2u, resolveDepth, isExpanded)); - - expandedItems.clear(); - EXPECT_FALSE(UIFlatHierarchyIsVisible(itemIndices, 1u, resolveDepth, isExpanded)); - EXPECT_FALSE(UIFlatHierarchyIsVisible(itemIndices, 2u, resolveDepth, isExpanded)); -} - -TEST(UIFlatHierarchyHelpersTest, FirstVisibleChildSkipsCollapsedDescendantsUntilExpanded) { - const std::array items = {{ - { 0.0f }, - { 1.0f }, - { 2.0f }, - { 1.0f }, - }}; - const std::vector itemIndices = { 0u, 1u, 2u, 3u }; - - const auto resolveDepth = [&items](std::size_t itemIndex) { - return items[itemIndex].depth; - }; - - std::unordered_set expandedItems = { 0u }; - const auto isExpanded = [&expandedItems](std::size_t itemIndex) { - return expandedItems.contains(itemIndex); - }; - const auto isVisible = [&itemIndices, &resolveDepth, &isExpanded](std::size_t itemIndex) { - for (std::size_t itemOffset = 0; itemOffset < itemIndices.size(); ++itemOffset) { - if (itemIndices[itemOffset] == itemIndex) { - return UIFlatHierarchyIsVisible(itemIndices, itemOffset, resolveDepth, isExpanded); - } - } - return false; - }; - - EXPECT_EQ( - UIFlatHierarchyFindFirstVisibleChildOffset(itemIndices, 0u, resolveDepth, isVisible), - 1u); - EXPECT_EQ( - UIFlatHierarchyFindFirstVisibleChildOffset(itemIndices, 1u, resolveDepth, isVisible), - kInvalidUIFlatHierarchyItemOffset); - - expandedItems.insert(1u); - EXPECT_EQ( - UIFlatHierarchyFindFirstVisibleChildOffset(itemIndices, 1u, resolveDepth, isVisible), - 2u); -} - -TEST(UIFlatHierarchyHelpersTest, NegativeDepthsClampToRootsForHierarchyQueries) { - const std::array items = {{ - { -3.0f }, - { 1.0f }, - { -1.0f }, - }}; - const std::vector itemIndices = { 0u, 1u, 2u }; - - const auto resolveDepth = [&items](std::size_t itemIndex) { - return items[itemIndex].depth; - }; - const auto isExpanded = [](std::size_t itemIndex) { - return itemIndex == 0u; - }; - - EXPECT_TRUE(UIFlatHierarchyHasChildren(itemIndices, 0u, resolveDepth)); - EXPECT_EQ(UIFlatHierarchyFindParentOffset(itemIndices, 1u, resolveDepth), 0u); - EXPECT_EQ( - UIFlatHierarchyFindParentOffset(itemIndices, 2u, resolveDepth), - kInvalidUIFlatHierarchyItemOffset); - EXPECT_TRUE(UIFlatHierarchyIsVisible(itemIndices, 2u, resolveDepth, isExpanded)); -} - -} // namespace diff --git a/tests/Core/UI/test_ui_input_dispatcher.cpp b/tests/Core/UI/test_ui_input_dispatcher.cpp deleted file mode 100644 index d0fc5698..00000000 --- a/tests/Core/UI/test_ui_input_dispatcher.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include - -#include - -#include -#include - -namespace { - -using XCEngine::UI::UIInputDispatchRequest; -using XCEngine::UI::UIInputDispatcher; -using XCEngine::UI::UIInputEvent; -using XCEngine::UI::UIInputEventType; -using XCEngine::UI::UIInputPath; -using XCEngine::UI::UIPointerButton; - -UIInputEvent MakePointerEvent( - UIInputEventType type, - UIPointerButton button = UIPointerButton::None) { - UIInputEvent event = {}; - event.type = type; - event.pointerButton = button; - return event; -} - -} // namespace - -TEST(UIInputDispatcherTest, PointerDownTransfersFocusAndStartsActivePath) { - UIInputDispatcher dispatcher{}; - const UIInputPath hoveredPath = { 10u, 20u, 30u }; - std::vector routedRequests = {}; - - const auto summary = dispatcher.Dispatch( - MakePointerEvent(UIInputEventType::PointerButtonDown, UIPointerButton::Left), - hoveredPath, - [&](const UIInputDispatchRequest& request) { - routedRequests.push_back(request); - return XCEngine::UI::UIInputDispatchDecision{}; - }); - - EXPECT_TRUE(summary.focusChange.Changed()); - EXPECT_EQ(summary.focusChange.previousPath, UIInputPath()); - EXPECT_EQ(summary.focusChange.currentPath, hoveredPath); - EXPECT_EQ(dispatcher.GetFocusController().GetFocusedPath(), hoveredPath); - EXPECT_EQ(dispatcher.GetFocusController().GetActivePath(), hoveredPath); - ASSERT_FALSE(routedRequests.empty()); - EXPECT_EQ(summary.routing.plan.targetPath, hoveredPath); - EXPECT_EQ(summary.routing.plan.targetKind, XCEngine::UI::UIInputTargetKind::Hovered); - const auto targetIt = std::find_if( - routedRequests.begin(), - routedRequests.end(), - [](const UIInputDispatchRequest& request) { - return request.isTargetElement; - }); - ASSERT_NE(targetIt, routedRequests.end()); - EXPECT_EQ(targetIt->elementId, hoveredPath.Target()); -} - -TEST(UIInputDispatcherTest, PointerCaptureOverridesHoveredRouteForPointerEvents) { - UIInputDispatcher dispatcher{}; - const UIInputPath hoveredPath = { 41u, 42u }; - const UIInputPath capturePath = { 7u, 8u, 9u }; - dispatcher.GetFocusController().SetPointerCapturePath(capturePath); - - const auto summary = dispatcher.Dispatch( - MakePointerEvent(UIInputEventType::PointerMove), - hoveredPath, - [](const UIInputDispatchRequest&) { - return XCEngine::UI::UIInputDispatchDecision{}; - }); - - EXPECT_EQ(summary.routing.plan.targetKind, XCEngine::UI::UIInputTargetKind::Captured); - EXPECT_EQ(summary.routing.plan.targetPath, capturePath); -} - -TEST(UIInputDispatcherTest, PointerButtonUpClearsActivePathAfterDispatch) { - UIInputDispatcher dispatcher{}; - const UIInputPath activePath = { 2u, 4u, 6u }; - dispatcher.GetFocusController().SetActivePath(activePath); - - const auto summary = dispatcher.Dispatch( - MakePointerEvent(UIInputEventType::PointerButtonUp, UIPointerButton::Left), - {}, - [](const UIInputDispatchRequest&) { - return XCEngine::UI::UIInputDispatchDecision{}; - }); - - EXPECT_FALSE(summary.routing.handled); - EXPECT_FALSE(dispatcher.GetFocusController().HasActivePath()); -} - -TEST(UIInputDispatcherTest, KeyboardEventsRouteToFocusedPath) { - UIInputDispatcher dispatcher{}; - const UIInputPath focusedPath = { 101u, 202u }; - dispatcher.GetFocusController().SetFocusedPath(focusedPath); - - UIInputEvent event = {}; - event.type = UIInputEventType::KeyDown; - event.keyCode = 'F'; - - const auto summary = dispatcher.Dispatch( - event, - {}, - [](const UIInputDispatchRequest&) { - return XCEngine::UI::UIInputDispatchDecision{}; - }); - - EXPECT_EQ(summary.routing.plan.targetKind, XCEngine::UI::UIInputTargetKind::Focused); - EXPECT_EQ(summary.routing.plan.targetPath, focusedPath); -} diff --git a/tests/Core/UI/test_ui_keyboard_navigation_model.cpp b/tests/Core/UI/test_ui_keyboard_navigation_model.cpp deleted file mode 100644 index 41c81fd7..00000000 --- a/tests/Core/UI/test_ui_keyboard_navigation_model.cpp +++ /dev/null @@ -1,101 +0,0 @@ -#include - -#include - -namespace { - -using XCEngine::UI::Widgets::UIKeyboardNavigationModel; - -TEST(UIKeyboardNavigationModelTest, EmptyModelStartsWithoutCurrentIndexOrAnchor) { - UIKeyboardNavigationModel navigation = {}; - - EXPECT_EQ(navigation.GetItemCount(), 0u); - EXPECT_FALSE(navigation.HasCurrentIndex()); - EXPECT_EQ(navigation.GetCurrentIndex(), UIKeyboardNavigationModel::InvalidIndex); - EXPECT_FALSE(navigation.HasSelectionAnchor()); - EXPECT_EQ(navigation.GetSelectionAnchorIndex(), UIKeyboardNavigationModel::InvalidIndex); - EXPECT_FALSE(navigation.MoveNext()); - EXPECT_FALSE(navigation.MovePrevious()); - EXPECT_FALSE(navigation.MoveHome()); - EXPECT_FALSE(navigation.MoveEnd()); -} - -TEST(UIKeyboardNavigationModelTest, SetCurrentIndexAndDirectionalMovesTrackCurrentIndexAndAnchor) { - UIKeyboardNavigationModel navigation = {}; - ASSERT_TRUE(navigation.SetItemCount(4u)); - - EXPECT_TRUE(navigation.SetCurrentIndex(1u)); - EXPECT_EQ(navigation.GetCurrentIndex(), 1u); - EXPECT_EQ(navigation.GetSelectionAnchorIndex(), 1u); - - EXPECT_TRUE(navigation.MoveNext()); - EXPECT_EQ(navigation.GetCurrentIndex(), 2u); - EXPECT_EQ(navigation.GetSelectionAnchorIndex(), 2u); - - EXPECT_TRUE(navigation.MoveEnd()); - EXPECT_EQ(navigation.GetCurrentIndex(), 3u); - EXPECT_EQ(navigation.GetSelectionAnchorIndex(), 3u); - EXPECT_FALSE(navigation.MoveNext()); - - EXPECT_TRUE(navigation.MoveHome()); - EXPECT_EQ(navigation.GetCurrentIndex(), 0u); - EXPECT_EQ(navigation.GetSelectionAnchorIndex(), 0u); - EXPECT_FALSE(navigation.MovePrevious()); -} - -TEST(UIKeyboardNavigationModelTest, MovePreviousAndEndSeedNavigationWhenCurrentIndexIsUnset) { - UIKeyboardNavigationModel navigation = {}; - ASSERT_TRUE(navigation.SetItemCount(5u)); - - EXPECT_TRUE(navigation.MovePrevious()); - EXPECT_EQ(navigation.GetCurrentIndex(), 4u); - EXPECT_EQ(navigation.GetSelectionAnchorIndex(), 4u); - - EXPECT_TRUE(navigation.ClearCurrentIndex()); - EXPECT_TRUE(navigation.ClearSelectionAnchor()); - EXPECT_FALSE(navigation.HasCurrentIndex()); - EXPECT_FALSE(navigation.HasSelectionAnchor()); - - EXPECT_TRUE(navigation.MoveEnd()); - EXPECT_EQ(navigation.GetCurrentIndex(), 4u); - EXPECT_EQ(navigation.GetSelectionAnchorIndex(), 4u); -} - -TEST(UIKeyboardNavigationModelTest, ExplicitAnchorCanBePreservedUntilNavigationCollapsesIt) { - UIKeyboardNavigationModel navigation = {}; - ASSERT_TRUE(navigation.SetItemCount(6u)); - - EXPECT_TRUE(navigation.SetSelectionAnchorIndex(1u)); - EXPECT_TRUE(navigation.SetCurrentIndex(4u, false)); - EXPECT_EQ(navigation.GetCurrentIndex(), 4u); - EXPECT_EQ(navigation.GetSelectionAnchorIndex(), 1u); - - EXPECT_TRUE(navigation.MovePrevious()); - EXPECT_EQ(navigation.GetCurrentIndex(), 3u); - EXPECT_EQ(navigation.GetSelectionAnchorIndex(), 3u); -} - -TEST(UIKeyboardNavigationModelTest, ItemCountChangesClampCurrentIndexAndSelectionAnchor) { - UIKeyboardNavigationModel navigation = {}; - ASSERT_TRUE(navigation.SetItemCount(5u)); - ASSERT_TRUE(navigation.SetSelectionAnchorIndex(3u)); - ASSERT_TRUE(navigation.SetCurrentIndex(4u, false)); - - EXPECT_TRUE(navigation.SetItemCount(4u)); - EXPECT_EQ(navigation.GetCurrentIndex(), 3u); - EXPECT_EQ(navigation.GetSelectionAnchorIndex(), 3u); - - EXPECT_FALSE(navigation.SetCurrentIndex(3u, false)); - EXPECT_TRUE(navigation.SetSelectionAnchorIndex(2u)); - EXPECT_TRUE(navigation.SetItemCount(2u)); - EXPECT_EQ(navigation.GetCurrentIndex(), 1u); - EXPECT_EQ(navigation.GetSelectionAnchorIndex(), 1u); - - EXPECT_TRUE(navigation.SetItemCount(0u)); - EXPECT_FALSE(navigation.HasCurrentIndex()); - EXPECT_EQ(navigation.GetCurrentIndex(), UIKeyboardNavigationModel::InvalidIndex); - EXPECT_FALSE(navigation.HasSelectionAnchor()); - EXPECT_EQ(navigation.GetSelectionAnchorIndex(), UIKeyboardNavigationModel::InvalidIndex); -} - -} // namespace diff --git a/tests/Core/UI/test_ui_property_edit_model.cpp b/tests/Core/UI/test_ui_property_edit_model.cpp deleted file mode 100644 index 4206469d..00000000 --- a/tests/Core/UI/test_ui_property_edit_model.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include - -#include - -namespace { - -using XCEngine::UI::Widgets::UIPropertyEditModel; - -TEST(UIPropertyEditModelTest, BeginEditTracksActiveFieldAndInitialValue) { - UIPropertyEditModel model = {}; - - EXPECT_FALSE(model.HasActiveEdit()); - EXPECT_TRUE(model.GetActiveFieldId().empty()); - EXPECT_TRUE(model.GetStagedValue().empty()); - EXPECT_FALSE(model.IsDirty()); - - EXPECT_FALSE(model.BeginEdit("", "12.0")); - EXPECT_TRUE(model.BeginEdit("transform.position.x", "12.0")); - EXPECT_TRUE(model.HasActiveEdit()); - EXPECT_EQ(model.GetActiveFieldId(), "transform.position.x"); - EXPECT_EQ(model.GetStagedValue(), "12.0"); - EXPECT_FALSE(model.IsDirty()); - - EXPECT_FALSE(model.BeginEdit("transform.position.x", "12.0")); -} - -TEST(UIPropertyEditModelTest, UpdateStagedValueTracksDirtyAgainstBaseline) { - UIPropertyEditModel model = {}; - - EXPECT_FALSE(model.UpdateStagedValue("3.5")); - ASSERT_TRUE(model.BeginEdit("light.intensity", "1.0")); - - EXPECT_TRUE(model.UpdateStagedValue("3.5")); - EXPECT_EQ(model.GetStagedValue(), "3.5"); - EXPECT_TRUE(model.IsDirty()); - - EXPECT_FALSE(model.UpdateStagedValue("3.5")); - EXPECT_TRUE(model.UpdateStagedValue("1.0")); - EXPECT_EQ(model.GetStagedValue(), "1.0"); - EXPECT_FALSE(model.IsDirty()); -} - -TEST(UIPropertyEditModelTest, CommitEditReturnsPayloadAndClearsState) { - UIPropertyEditModel model = {}; - ASSERT_TRUE(model.BeginEdit("material.albedo", "#ffffff")); - ASSERT_TRUE(model.UpdateStagedValue("#ffcc00")); - - std::string committedFieldId = {}; - std::string committedValue = {}; - EXPECT_TRUE(model.CommitEdit(&committedFieldId, &committedValue)); - EXPECT_EQ(committedFieldId, "material.albedo"); - EXPECT_EQ(committedValue, "#ffcc00"); - - EXPECT_FALSE(model.HasActiveEdit()); - EXPECT_TRUE(model.GetActiveFieldId().empty()); - EXPECT_TRUE(model.GetStagedValue().empty()); - EXPECT_FALSE(model.IsDirty()); - EXPECT_FALSE(model.CommitEdit(&committedFieldId, &committedValue)); -} - -TEST(UIPropertyEditModelTest, CancelEditDropsStagedChangesAndResetsSession) { - UIPropertyEditModel model = {}; - ASSERT_TRUE(model.BeginEdit("camera.fov", "60")); - ASSERT_TRUE(model.UpdateStagedValue("75")); - ASSERT_TRUE(model.IsDirty()); - - EXPECT_TRUE(model.CancelEdit()); - EXPECT_FALSE(model.HasActiveEdit()); - EXPECT_TRUE(model.GetActiveFieldId().empty()); - EXPECT_TRUE(model.GetStagedValue().empty()); - EXPECT_FALSE(model.IsDirty()); - EXPECT_FALSE(model.CancelEdit()); - - EXPECT_TRUE(model.BeginEdit("camera.nearClip", "0.3")); - EXPECT_EQ(model.GetActiveFieldId(), "camera.nearClip"); - EXPECT_EQ(model.GetStagedValue(), "0.3"); - EXPECT_FALSE(model.IsDirty()); -} - -} // namespace diff --git a/tests/Core/UI/test_ui_runtime.cpp b/tests/Core/UI/test_ui_runtime.cpp index 2054305e..fa589fb2 100644 --- a/tests/Core/UI/test_ui_runtime.cpp +++ b/tests/Core/UI/test_ui_runtime.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -22,6 +23,7 @@ using XCEngine::UI::Runtime::UISceneRuntimeContext; using XCEngine::UI::Runtime::UIDocumentScreenHost; using XCEngine::UI::Runtime::UIScreenStackController; using XCEngine::UI::Runtime::UISystem; +using XCEngine::Input::KeyCode; namespace fs = std::filesystem; @@ -515,6 +517,79 @@ TEST(UIRuntimeTest, DocumentHostTracksHoverFocusAndPointerCaptureAcrossFrames) { EXPECT_NE(afterRelease.hoveredStateKey.find("/input-route"), std::string::npos); } +TEST(UIRuntimeTest, DocumentHostTraversesKeyboardFocusAndKeyboardActivationAcrossFrames) { + TempFileScope viewFile( + "xcui_runtime_keyboard_focus", + ".xcui", + "\n" + " \n" + "