diff --git a/CMakeLists.txt b/CMakeLists.txt index d67a0370..0cf7dcd8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,15 +1,20 @@ cmake_minimum_required(VERSION 3.15) -if(MSVC) - if(POLICY CMP0141) - cmake_policy(SET CMP0141 NEW) - endif() - set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT - "$<$:Embedded>") +if(POLICY CMP0141) + cmake_policy(SET CMP0141 NEW) endif() project(XCEngine) +if(MSVC) + set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT + "$<$:Embedded>") + add_compile_options("$<$:/MP>") + if(CMAKE_GENERATOR MATCHES "Visual Studio") + set(CMAKE_VS_GLOBALS "UseMultiToolTask=true") + endif() +endif() + set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -43,9 +48,18 @@ set( CACHE PATH "Path to the bundled Mono distribution used by the scripting runtime") +if(EXISTS "${CMAKE_SOURCE_DIR}/engine/third_party/mono/binary/mscorlib.dll") + set( + XCENGINE_MONO_ROOT_DIR + "${CMAKE_SOURCE_DIR}/engine/third_party/mono" + CACHE PATH + "Path to the bundled Mono distribution used by the scripting runtime" + FORCE) +endif() + add_subdirectory(engine) +add_subdirectory(managed) add_subdirectory(editor) add_subdirectory(new_editor) -add_subdirectory(managed) add_subdirectory(mvs/RenderDoc) add_subdirectory(tests) diff --git a/docs/plan/Renderer_C++层第六阶段计划_RenderGraphBuilder与NativePass正式化_2026-04-15.md b/docs/plan/Renderer_C++层第六阶段计划_RenderGraphBuilder与NativePass正式化_2026-04-15.md new file mode 100644 index 00000000..b226d40d --- /dev/null +++ b/docs/plan/Renderer_C++层第六阶段计划_RenderGraphBuilder与NativePass正式化_2026-04-15.md @@ -0,0 +1,190 @@ +# Renderer C++层第六阶段计划:RenderGraph Builder 与 Native Pass 正式化 + +日期:`2026-04-15` + +## 1. 阶段定位 + +第五阶段已经完成: + +- `SceneRenderer -> RenderPipelineHost -> CameraRenderer` 的主链职责切开 +- `ScriptableRenderPipelineHost` 已经成为默认 native 宿主 +- `BuiltinForward` 已经被 host 复用,而不是继续长在 `CameraRenderer` +- C# 侧已经有最小 `RenderPipelineAsset / GraphicsSettings` 选择入口 + +下一阶段不再继续堆 SRP 表面 API,而是回头把 C++ 里的 RenderGraph 组织层做实。 + +现在的问题不是“有没有 RenderGraph”,而是“RenderGraph 还没有成为渲染层唯一正式的组织语言”。 + +## 2. 当前核心问题 + +### 2.1 graph core 已有,但 renderer authoring 还不正式 + +当前已经有: + +- `RenderGraph` +- `CameraFramePlan` +- `BuiltinForward` 内部 graph 录制 +- feature / fullscreen / final color 若干 graph 录制路径 + +但现在仍然缺: + +- 稳定的 pass builder 录制接口 +- 统一的 frame data / blackboard / graph context +- 资源声明、导入、导出、跨 pass 依赖的正式边界 + +结果就是: + +- builtin renderer 仍然夹杂较多“知道内部细节的 graph 录制代码” +- 后续要做 deferred、lightmap、custom renderer feature 时,容易继续散着长 +- C# SRP 想组织 pass 时,没有稳定 native 承接点 + +### 2.2 现在的 builtin pass 更像功能块,不像正式 graph node + +当前很多 pass 还停留在: + +- 按功能拆出来了 +- 能被 builtin forward 调 +- 但还没有统一的“声明资源 -> 录制 graph -> 执行 payload”模型 + +这会导致后续每加一个新特性,都容易再复制一套组织方式。 + +### 2.3 SRP 已有入口,但还没有可扩展的 native renderer contract + +第五阶段做的是: + +- 先把 host 立起来 +- 让 managed 侧能选 pipeline +- 默认仍回落到 builtin forward + +这一步是必要的,但还不是最终可扩展状态。 + +如果现在直接往 C# 侧暴露更多 renderer / feature API,会把当前 native 的不稳定组织方式直接固化出去。 + +## 3. 本阶段目标 + +这一阶段结束后,要达到的状态是: + +`RenderPipelineHost / ScriptableRenderPipelineHost` +`-> Native Renderer Contract` +`-> RenderGraph Builder` +`-> Compiled RenderGraph` +`-> Execute` + +也就是: + +- host 只负责组织 renderer 与 frame 级上下文 +- renderer 通过统一 builder 录制 graph +- pass 通过统一 contract 声明输入输出资源 +- builtin forward、post process、shadow、object id、editor overlay 都走同一种 graph authoring 方式 + +## 4. 本阶段明确要做的事 + +### 4.1 建立正式的 RenderGraph 录制上下文 + +新增或收口统一概念: + +- `RenderGraphBuilderContext` +- `RenderGraphFrameData` 或等价 blackboard +- `RendererFeatureContext` +- `PassRecordContext` + +目标: + +- renderer 不直接到处自己拼资源名字和跨阶段状态 +- graph 录制代码读写统一的 frame 数据 +- camera/frame 级公共数据有固定入口 + +### 4.2 正式化 native pass contract + +把现有 builtin pass 整理成统一模型: + +- pass 声明输入资源 +- pass 声明输出资源 +- pass 录制 render/compute callback +- pass 执行期只消费编译后的 graph 结果 + +至少先把这几类 pass 收进统一 contract: + +- main scene color/depth +- shadow map +- object id +- fullscreen post process +- final color resolve / present 前 copy + +### 4.3 收口资源导入导出与命名 + +重点不是做花哨 API,而是避免 graph 资源边界继续散: + +- camera color/depth +- intermediate post-process chain +- shadow atlases / shadow maps +- object id / selection outline 相关纹理 +- editor viewport 附加 surface + +阶段目标是让“哪些资源是 imported,哪些是 transient,哪些跨 renderer 共享”全部有明确归属。 + +### 4.4 让 BuiltinForward 真正成为标准 renderer 实现 + +本阶段完成后,`BuiltinForward` 应该只是: + +- 一个标准 native renderer +- 使用标准 pass contract +- 通过标准 RenderGraph builder 录制 + +而不是继续保留特殊地位。 + +### 4.5 给下阶段 SRP/URP-like 层预留稳定承接点 + +这个阶段暂时不扩写完整 C# renderer feature API。 + +但必须把 native 边界准备好,让下阶段可以安全承接: + +- managed renderer asset 配置下发 +- renderer feature 列表 +- renderer pass 描述 +- future deferred renderer + +## 5. 本阶段明确不做 + +- 不做完整 deferred pipeline +- 不做完整 URP-like builtin package +- 不做大规模 C# renderer feature authoring API +- 不做 lightmap、probe、GI 全链路 +- 不做 HDRP 风格多路径并行体系 + +这些东西都应该建立在“RenderGraph 组织层正式化”之后。 + +## 6. 推荐执行顺序 + +1. 先整理 frame data / graph builder / pass record context +2. 再把 main scene、fullscreen、final color 三条主链统一到新 contract +3. 再把 shadow / object id / editor viewport 附加 pass 接入同一模型 +4. 再清理 builtin forward 内部遗留 special case +5. 最后补齐 renderer/host 级测试,并验证 editor 主链 + +## 7. 验收标准 + +满足以下条件即可认为第六阶段收口: + +- `BuiltinForward` 已完全通过统一 RenderGraph builder 录制 +- fullscreen / final color / object id / shadow 不再各自维护不同 graph 组织方式 +- host、renderer、pass 三层职责清晰 +- 新增一个 native pass 不需要再改 `CameraRenderer` 主调度逻辑 +- `rendering_unit_tests` 新增覆盖 renderer contract 与 graph 录制边界 +- `scripting_tests`、`editor_tests`、`XCEditor` smoke 继续稳定 + +## 8. 第六阶段结束后的下一步 + +第六阶段结束后,才进入真正面向用户的下一阶段: + +`SRP/URP-like package v1` + +那时再做才是对的: + +- C# renderer asset 配置对象 +- renderer feature 列表 +- forward renderer data +- deferred renderer 原型 +- 用户级自定义渲染管线入口 + +顺序不能反。 diff --git a/docs/plan/Renderer_C++层第五阶段计划_SRPHost_v1骨架与BuiltinForward复用_2026-04-15.md b/docs/plan/used/Renderer_C++层第五阶段计划_SRPHost_v1骨架与BuiltinForward复用_2026-04-15.md similarity index 100% rename from docs/plan/Renderer_C++层第五阶段计划_SRPHost_v1骨架与BuiltinForward复用_2026-04-15.md rename to docs/plan/used/Renderer_C++层第五阶段计划_SRPHost_v1骨架与BuiltinForward复用_2026-04-15.md diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 94618b59..4f04c75c 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -56,6 +56,18 @@ endif() file(TO_CMAKE_PATH "${XCENGINE_ROOT_DIR}" XCENGINE_ROOT_DIR_CMAKE) file(TO_CMAKE_PATH "${XCENGINE_MONO_ROOT_DIR}" XCENGINE_MONO_ROOT_DIR_CMAKE) +function(xcengine_attach_recursive_build_target target dependency_target) + add_custom_command(TARGET ${target} PRE_BUILD + COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} + --config $ + --target ${dependency_target} + -- + /m:1 + /p:BuildProjectReferences=false + VERBATIM + COMMENT "Building ${dependency_target} before ${target}") +endfunction() + set(IMGUI_SOURCES ${imgui_SOURCE_DIR}/imgui.cpp ${imgui_SOURCE_DIR}/imgui_demo.cpp @@ -129,6 +141,11 @@ if(XCENGINE_ENABLE_MONO_SCRIPTING) XCENGINE_ENABLE_MONO_SCRIPTING XCENGINE_EDITOR_REPO_ROOT="${XCENGINE_ROOT_DIR_CMAKE}" XCENGINE_EDITOR_MONO_ROOT_DIR="${XCENGINE_MONO_ROOT_DIR_CMAKE}") + + if(TARGET xcengine_project_managed_assemblies) + add_dependencies(${PROJECT_NAME} xcengine_project_managed_assemblies) + xcengine_attach_recursive_build_target(${PROJECT_NAME} xcengine_project_managed_assemblies) + endif() endif() if(MSVC) diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 37516dfd..ac07e6fc 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -534,6 +534,7 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Features/BuiltinGaussianSplatPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Features/BuiltinVolumetricPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Pipelines/ScriptableRenderPipelineHost.h ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Execution/CameraFramePlan.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Execution/CameraRenderer.cpp @@ -595,6 +596,7 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Shadow/DirectionalShadowRuntime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Execution/SceneRenderer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/Internal/BuiltinForwardSceneSetup.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineFrame.cpp diff --git a/engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h b/engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h new file mode 100644 index 00000000..4dd9f7e7 --- /dev/null +++ b/engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h @@ -0,0 +1,54 @@ +#pragma once + +#include + +#include +#include + +namespace XCEngine { +namespace Rendering { +namespace Pipelines { + +struct ManagedRenderPipelineAssetDescriptor { + std::string assemblyName; + std::string namespaceName; + std::string className; + + bool IsValid() const { + return !assemblyName.empty() && !className.empty(); + } + + std::string GetFullName() const { + return namespaceName.empty() + ? className + : namespaceName + "." + className; + } +}; + +class ManagedScriptableRenderPipelineAsset final : public RenderPipelineAsset { +public: + explicit ManagedScriptableRenderPipelineAsset( + ManagedRenderPipelineAssetDescriptor descriptor); + + const ManagedRenderPipelineAssetDescriptor& GetDescriptor() const { + return m_descriptor; + } + + std::unique_ptr CreatePipeline() const override; + FinalColorSettings GetDefaultFinalColorSettings() const override; + +private: + ManagedRenderPipelineAssetDescriptor m_descriptor; + ScriptableRenderPipelineHostAsset m_fallbackAsset; +}; + +void SetManagedRenderPipelineAssetDescriptor( + const ManagedRenderPipelineAssetDescriptor& descriptor); +void ClearManagedRenderPipelineAssetDescriptor(); +ManagedRenderPipelineAssetDescriptor GetManagedRenderPipelineAssetDescriptor(); +std::shared_ptr +CreateManagedOrDefaultScriptableRenderPipelineAsset(); + +} // namespace Pipelines +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/src/Rendering/Execution/CameraRenderer.cpp b/engine/src/Rendering/Execution/CameraRenderer.cpp index 1de91997..3c774a2f 100644 --- a/engine/src/Rendering/Execution/CameraRenderer.cpp +++ b/engine/src/Rendering/Execution/CameraRenderer.cpp @@ -7,6 +7,7 @@ #include "Rendering/Passes/BuiltinObjectIdPass.h" #include "Rendering/Passes/BuiltinShadowCasterPass.h" #include "Rendering/Pipelines/BuiltinForwardPipeline.h" +#include "Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h" #include "Rendering/Pipelines/ScriptableRenderPipelineHost.h" #include "Rendering/RenderPipelineAsset.h" #include "Rendering/RenderSurface.h" @@ -19,9 +20,7 @@ namespace Rendering { namespace { std::shared_ptr CreateDefaultPipelineAsset() { - static const std::shared_ptr s_defaultPipelineAsset = - std::make_shared(); - return s_defaultPipelineAsset; + return Pipelines::CreateManagedOrDefaultScriptableRenderPipelineAsset(); } std::unique_ptr CreateDefaultDepthOnlyPass() { diff --git a/engine/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp b/engine/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp new file mode 100644 index 00000000..35a1e330 --- /dev/null +++ b/engine/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp @@ -0,0 +1,57 @@ +#include "Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h" + +#include + +namespace XCEngine { +namespace Rendering { +namespace Pipelines { + +namespace { + +ManagedRenderPipelineAssetDescriptor& GetManagedRenderPipelineAssetDescriptorStorage() { + static ManagedRenderPipelineAssetDescriptor s_descriptor = {}; + return s_descriptor; +} + +} // namespace + +ManagedScriptableRenderPipelineAsset::ManagedScriptableRenderPipelineAsset( + ManagedRenderPipelineAssetDescriptor descriptor) + : m_descriptor(std::move(descriptor)) { +} + +std::unique_ptr ManagedScriptableRenderPipelineAsset::CreatePipeline() const { + return m_fallbackAsset.CreatePipeline(); +} + +FinalColorSettings ManagedScriptableRenderPipelineAsset::GetDefaultFinalColorSettings() const { + return m_fallbackAsset.GetDefaultFinalColorSettings(); +} + +void SetManagedRenderPipelineAssetDescriptor( + const ManagedRenderPipelineAssetDescriptor& descriptor) { + GetManagedRenderPipelineAssetDescriptorStorage() = descriptor; +} + +void ClearManagedRenderPipelineAssetDescriptor() { + GetManagedRenderPipelineAssetDescriptorStorage() = {}; +} + +ManagedRenderPipelineAssetDescriptor GetManagedRenderPipelineAssetDescriptor() { + return GetManagedRenderPipelineAssetDescriptorStorage(); +} + +std::shared_ptr +CreateManagedOrDefaultScriptableRenderPipelineAsset() { + const ManagedRenderPipelineAssetDescriptor descriptor = + GetManagedRenderPipelineAssetDescriptor(); + if (descriptor.IsValid()) { + return std::make_shared(descriptor); + } + + return std::make_shared(); +} + +} // namespace Pipelines +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp index 58406a22..c586e475 100644 --- a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp +++ b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp @@ -8,6 +8,7 @@ #include "Components/TransformComponent.h" #include "Debug/Logger.h" #include "Input/InputManager.h" +#include "Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h" #include "Scene/Scene.h" #include "Scripting/ScriptComponent.h" #include "Scripting/ScriptEngine.h" @@ -92,6 +93,16 @@ std::string SafeString(const char* value) { return value ? std::string(value) : std::string(); } +std::string TrimAssemblyName(const std::string& assemblyName) { + constexpr const char* kDllSuffix = ".dll"; + if (assemblyName.size() >= 4u && + assemblyName.substr(assemblyName.size() - 4u) == kDllSuffix) { + return assemblyName.substr(0u, assemblyName.size() - 4u); + } + + return assemblyName; +} + bool IsMonoClassOrSubclass(MonoClass* monoClass, MonoClass* potentialBaseClass) { if (!monoClass || !potentialBaseClass) { return false; @@ -1522,6 +1533,47 @@ void InternalCall_MeshRenderer_SetRenderLayer(uint64_t gameObjectUUID, int32_t v component->SetRenderLayer(static_cast(std::max(value, 0))); } +void InternalCall_Rendering_SetRenderPipelineAssetType(MonoReflectionType* assetType) { + if (assetType == nullptr) { + Rendering::Pipelines::ClearManagedRenderPipelineAssetDescriptor(); + return; + } + + MonoType* monoType = mono_reflection_type_get_type(assetType); + MonoClass* monoClass = + monoType != nullptr ? mono_class_from_mono_type(monoType) : nullptr; + if (monoClass == nullptr) { + Rendering::Pipelines::ClearManagedRenderPipelineAssetDescriptor(); + return; + } + + MonoImage* image = mono_class_get_image(monoClass); + Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = {}; + descriptor.assemblyName = + TrimAssemblyName(SafeString(image != nullptr ? mono_image_get_name(image) : nullptr)); + descriptor.namespaceName = SafeString(mono_class_get_namespace(monoClass)); + descriptor.className = SafeString(mono_class_get_name(monoClass)); + if (!descriptor.IsValid()) { + Rendering::Pipelines::ClearManagedRenderPipelineAssetDescriptor(); + return; + } + + Rendering::Pipelines::SetManagedRenderPipelineAssetDescriptor(descriptor); +} + +MonoString* InternalCall_Rendering_GetRenderPipelineAssetTypeName() { + const Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = + Rendering::Pipelines::GetManagedRenderPipelineAssetDescriptor(); + if (!descriptor.IsValid()) { + return mono_string_new(mono_domain_get(), ""); + } + + std::string assemblyQualifiedName = descriptor.GetFullName(); + assemblyQualifiedName += ", "; + assemblyQualifiedName += descriptor.assemblyName; + return mono_string_new(mono_domain_get(), assemblyQualifiedName.c_str()); +} + void RegisterInternalCalls() { if (GetInternalCallRegistrationState()) { return; @@ -1629,6 +1681,8 @@ void RegisterInternalCalls() { mono_add_internal_call("XCEngine.InternalCalls::MeshRenderer_SetReceiveShadows", reinterpret_cast(&InternalCall_MeshRenderer_SetReceiveShadows)); mono_add_internal_call("XCEngine.InternalCalls::MeshRenderer_GetRenderLayer", reinterpret_cast(&InternalCall_MeshRenderer_GetRenderLayer)); mono_add_internal_call("XCEngine.InternalCalls::MeshRenderer_SetRenderLayer", reinterpret_cast(&InternalCall_MeshRenderer_SetRenderLayer)); + mono_add_internal_call("XCEngine.InternalCalls::Rendering_SetRenderPipelineAssetType", reinterpret_cast(&InternalCall_Rendering_SetRenderPipelineAssetType)); + mono_add_internal_call("XCEngine.InternalCalls::Rendering_GetRenderPipelineAssetTypeName", reinterpret_cast(&InternalCall_Rendering_GetRenderPipelineAssetTypeName)); GetInternalCallRegistrationState() = true; } diff --git a/managed/CMakeLists.txt b/managed/CMakeLists.txt index 9061ce96..da9ee4b5 100644 --- a/managed/CMakeLists.txt +++ b/managed/CMakeLists.txt @@ -103,6 +103,7 @@ set(XCENGINE_SCRIPT_CORE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Camera.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Component.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Debug.cs + ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/GraphicsSettings.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/GameObject.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Input.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/InternalCalls.cs @@ -113,6 +114,7 @@ set(XCENGINE_SCRIPT_CORE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/MonoBehaviour.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Object.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Quaternion.cs + ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/RenderPipelineAsset.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/SerializeField.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Space.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Time.cs @@ -137,6 +139,7 @@ set(XCENGINE_GAME_SCRIPT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/MeshComponentProbe.cs ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/MeshRendererEdgeCaseProbe.cs ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/ObjectApiProbe.cs + ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/RenderPipelineApiProbe.cs ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/SerializeFieldProbe.cs ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/TagLayerProbe.cs ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/TickLogProbe.cs diff --git a/managed/GameScripts/RenderPipelineApiProbe.cs b/managed/GameScripts/RenderPipelineApiProbe.cs new file mode 100644 index 00000000..3fec38f6 --- /dev/null +++ b/managed/GameScripts/RenderPipelineApiProbe.cs @@ -0,0 +1,29 @@ +using XCEngine; + +namespace Gameplay +{ + public sealed class RenderPipelineApiProbeAsset : RenderPipelineAsset + { + } + + public sealed class RenderPipelineApiProbe : MonoBehaviour + { + public bool InitialTypeWasNull; + public bool SelectionRoundTripSucceeded; + public string SelectedPipelineTypeName = string.Empty; + + public void Start() + { + InitialTypeWasNull = GraphicsSettings.renderPipelineAssetType == null; + + GraphicsSettings.renderPipelineAssetType = + typeof(RenderPipelineApiProbeAsset); + System.Type selectedType = GraphicsSettings.renderPipelineAssetType; + SelectionRoundTripSucceeded = + selectedType == typeof(RenderPipelineApiProbeAsset); + SelectedPipelineTypeName = selectedType != null + ? selectedType.FullName ?? string.Empty + : string.Empty; + } + } +} diff --git a/managed/XCEngine.ScriptCore/GraphicsSettings.cs b/managed/XCEngine.ScriptCore/GraphicsSettings.cs new file mode 100644 index 00000000..181776a7 --- /dev/null +++ b/managed/XCEngine.ScriptCore/GraphicsSettings.cs @@ -0,0 +1,34 @@ +using System; + +namespace XCEngine +{ + public static class GraphicsSettings + { + public static Type renderPipelineAssetType + { + get + { + string assemblyQualifiedName = + InternalCalls.Rendering_GetRenderPipelineAssetTypeName(); + if (string.IsNullOrEmpty(assemblyQualifiedName)) + { + return null; + } + + return Type.GetType(assemblyQualifiedName, throwOnError: false); + } + set + { + if (value != null && + !typeof(RenderPipelineAsset).IsAssignableFrom(value)) + { + throw new ArgumentException( + "GraphicsSettings.renderPipelineAssetType must derive from RenderPipelineAsset.", + nameof(value)); + } + + InternalCalls.Rendering_SetRenderPipelineAssetType(value); + } + } + } +} diff --git a/managed/XCEngine.ScriptCore/InternalCalls.cs b/managed/XCEngine.ScriptCore/InternalCalls.cs index 4d471513..bb932b6c 100644 --- a/managed/XCEngine.ScriptCore/InternalCalls.cs +++ b/managed/XCEngine.ScriptCore/InternalCalls.cs @@ -310,5 +310,11 @@ namespace XCEngine [MethodImpl(MethodImplOptions.InternalCall)] internal static extern void MeshRenderer_SetRenderLayer(ulong gameObjectUUID, int value); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern void Rendering_SetRenderPipelineAssetType(Type assetType); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern string Rendering_GetRenderPipelineAssetTypeName(); } } diff --git a/managed/XCEngine.ScriptCore/RenderPipelineAsset.cs b/managed/XCEngine.ScriptCore/RenderPipelineAsset.cs new file mode 100644 index 00000000..b4fe885c --- /dev/null +++ b/managed/XCEngine.ScriptCore/RenderPipelineAsset.cs @@ -0,0 +1,9 @@ +namespace XCEngine +{ + public abstract class RenderPipelineAsset : Object + { + protected RenderPipelineAsset() + { + } + } +} diff --git a/tests/Rendering/unit/test_camera_scene_renderer.cpp b/tests/Rendering/unit/test_camera_scene_renderer.cpp index 0cc37587..2c45be45 100644 --- a/tests/Rendering/unit/test_camera_scene_renderer.cpp +++ b/tests/Rendering/unit/test_camera_scene_renderer.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -3717,6 +3718,29 @@ TEST(ScriptableRenderPipelineHostAsset_Test, CreatesHostFromRendererAssetAndForw EXPECT_EQ(assetState->createCalls, 1); } +TEST(CameraRenderer_Test, DefaultPipelineAssetUsesManagedSelectionWhenPresent) { + const Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = { + "GameScripts", + "Gameplay", + "ManagedRenderPipelineProbeAsset" + }; + Pipelines::SetManagedRenderPipelineAssetDescriptor(descriptor); + + CameraRenderer renderer; + auto* asset = + dynamic_cast( + renderer.GetPipelineAsset()); + ASSERT_NE(asset, nullptr); + EXPECT_EQ(asset->GetDescriptor().assemblyName, "GameScripts"); + EXPECT_EQ(asset->GetDescriptor().namespaceName, "Gameplay"); + EXPECT_EQ(asset->GetDescriptor().className, "ManagedRenderPipelineProbeAsset"); + EXPECT_NE( + dynamic_cast(renderer.GetPipeline()), + nullptr); + + Pipelines::ClearManagedRenderPipelineAssetDescriptor(); +} + TEST(SceneRenderer_Test, CreatesPipelineInstancesFromPipelineAssetsAndShutsDownReplacedPipelines) { Scene scene("SceneRendererAssetScene"); diff --git a/tests/editor/CMakeLists.txt b/tests/editor/CMakeLists.txt index 4586225a..6a14b386 100644 --- a/tests/editor/CMakeLists.txt +++ b/tests/editor/CMakeLists.txt @@ -2,6 +2,18 @@ cmake_minimum_required(VERSION 3.15) project(XCEngine_EditorTests) +function(xcengine_attach_recursive_build_target target dependency_target) + add_custom_command(TARGET ${target} PRE_BUILD + COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} + --config $ + --target ${dependency_target} + -- + /m:1 + /p:BuildProjectReferences=false + VERBATIM + COMMENT "Building ${dependency_target} before ${target}") +endfunction() + set(EDITOR_TEST_SOURCES test_action_routing.cpp test_application_asset_cache_stub.cpp @@ -146,6 +158,7 @@ add_custom_command(TARGET nahida_preview_regenerator POST_BUILD if(XCENGINE_ENABLE_MONO_SCRIPTING AND TARGET xcengine_managed_assemblies) add_dependencies(editor_tests xcengine_managed_assemblies) + xcengine_attach_recursive_build_target(editor_tests xcengine_managed_assemblies) file(TO_CMAKE_PATH "${XCENGINE_MANAGED_OUTPUT_DIR}" XCENGINE_MANAGED_OUTPUT_DIR_CMAKE) file(TO_CMAKE_PATH "${XCENGINE_SCRIPT_CORE_DLL}" XCENGINE_SCRIPT_CORE_DLL_CMAKE) file(TO_CMAKE_PATH "${XCENGINE_GAME_SCRIPTS_DLL}" XCENGINE_GAME_SCRIPTS_DLL_CMAKE) diff --git a/tests/scripting/CMakeLists.txt b/tests/scripting/CMakeLists.txt index 19686082..907374bd 100644 --- a/tests/scripting/CMakeLists.txt +++ b/tests/scripting/CMakeLists.txt @@ -2,6 +2,18 @@ cmake_minimum_required(VERSION 3.15) project(XCEngine_ScriptingTests) +function(xcengine_attach_recursive_build_target target dependency_target) + add_custom_command(TARGET ${target} PRE_BUILD + COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} + --config $ + --target ${dependency_target} + -- + /m:1 + /p:BuildProjectReferences=false + VERBATIM + COMMENT "Building ${dependency_target} before ${target}") +endfunction() + set(SCRIPTING_TEST_SOURCES test_script_field_storage.cpp test_script_component.cpp @@ -40,6 +52,7 @@ target_include_directories(scripting_tests PRIVATE if(TARGET xcengine_managed_assemblies) add_dependencies(scripting_tests xcengine_managed_assemblies) + xcengine_attach_recursive_build_target(scripting_tests xcengine_managed_assemblies) file(TO_CMAKE_PATH "${XCENGINE_MANAGED_OUTPUT_DIR}" XCENGINE_MANAGED_OUTPUT_DIR_CMAKE) file(TO_CMAKE_PATH "${XCENGINE_SCRIPT_CORE_DLL}" XCENGINE_SCRIPT_CORE_DLL_CMAKE) @@ -54,6 +67,9 @@ endif() if(TARGET xcengine_test_project_managed_assemblies) add_dependencies(scripting_tests xcengine_test_project_managed_assemblies) + xcengine_attach_recursive_build_target( + scripting_tests + xcengine_test_project_managed_assemblies) file( TO_CMAKE_PATH diff --git a/tests/scripting/test_mono_script_runtime.cpp b/tests/scripting/test_mono_script_runtime.cpp index df68d139..f6b8f249 100644 --- a/tests/scripting/test_mono_script_runtime.cpp +++ b/tests/scripting/test_mono_script_runtime.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -91,6 +92,7 @@ protected: engine->OnRuntimeStop(); engine->SetRuntimeFixedDeltaTime(ScriptEngine::DefaultFixedDeltaTime); XCEngine::Input::InputManager::Get().Shutdown(); + XCEngine::Rendering::Pipelines::ClearManagedRenderPipelineAssetDescriptor(); runtime = std::make_unique(CreateMonoSettings()); ASSERT_TRUE(runtime->Initialize()) << runtime->GetLastError(); @@ -102,6 +104,7 @@ protected: engine->OnRuntimeStop(); engine->SetRuntime(nullptr); XCEngine::Input::InputManager::Get().Shutdown(); + XCEngine::Rendering::Pipelines::ClearManagedRenderPipelineAssetDescriptor(); runtime.reset(); scene.reset(); } @@ -262,6 +265,38 @@ TEST_F(MonoScriptRuntimeTest, ClassFieldMetadataListsConcreteComponentReferenceF EXPECT_EQ(fields, expected); } +TEST_F(MonoScriptRuntimeTest, ManagedGraphicsSettingsRoundTripsRenderPipelineAssetSelection) { + Scene* runtimeScene = CreateScene("ManagedRenderPipelineSelectionScene"); + GameObject* scriptObject = runtimeScene->CreateGameObject("RenderPipelineProbe"); + ScriptComponent* script = + AddScript(scriptObject, "Gameplay", "RenderPipelineApiProbe"); + ASSERT_NE(script, nullptr); + + engine->OnRuntimeStart(runtimeScene); + engine->OnUpdate(0.016f); + + const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = + XCEngine::Rendering::Pipelines::GetManagedRenderPipelineAssetDescriptor(); + EXPECT_EQ(descriptor.assemblyName, "GameScripts"); + EXPECT_EQ(descriptor.namespaceName, "Gameplay"); + EXPECT_EQ(descriptor.className, "RenderPipelineApiProbeAsset"); + + bool selectionRoundTripSucceeded = false; + std::string selectedPipelineTypeName; + EXPECT_TRUE(runtime->TryGetFieldValue( + script, + "SelectionRoundTripSucceeded", + selectionRoundTripSucceeded)); + EXPECT_TRUE(selectionRoundTripSucceeded); + EXPECT_TRUE(runtime->TryGetFieldValue( + script, + "SelectedPipelineTypeName", + selectedPipelineTypeName)); + EXPECT_EQ( + selectedPipelineTypeName, + "Gameplay.RenderPipelineApiProbeAsset"); +} + TEST_F(MonoScriptRuntimeTest, ClassFieldDefaultValueQueryReturnsNullComponentReferences) { std::vector fields;