From a5d2058818c35598cd2c1640bec4f4e2de9aa5f9 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Thu, 2 Apr 2026 15:23:25 +0800 Subject: [PATCH] refactor: formalize scene viewport object-id picking --- ...derer阶段收口补充_ObjectIdPicking正式化.md | 71 +++++++++++++++++++ editor/src/Viewport/ViewportHostService.h | 35 ++++----- editor/src/Viewport/ViewportObjectIdPicker.h | 44 +++++++++--- 3 files changed, 119 insertions(+), 31 deletions(-) create mode 100644 docs/plan/Renderer阶段收口补充_ObjectIdPicking正式化.md diff --git a/docs/plan/Renderer阶段收口补充_ObjectIdPicking正式化.md b/docs/plan/Renderer阶段收口补充_ObjectIdPicking正式化.md new file mode 100644 index 00000000..abce5b74 --- /dev/null +++ b/docs/plan/Renderer阶段收口补充_ObjectIdPicking正式化.md @@ -0,0 +1,71 @@ +# Renderer 阶段收口补充:Object ID Picking 正式化 + +日期:`2026-04-02` + +## 1. 这次补充收口解决什么 + +本次补充只收一件事: + +- `SceneView` 选中主链路正式切到 `GPU object-id` + +本次明确不做: + +- render graph +- renderer 内更完整的多 pass 调度 +- game/runtime 通用 picking 服务 + +原因很简单:这些属于下一阶段架构演进,不应该继续污染当前阶段的收口边界。 + +## 2. 本次收口后的正式行为 + +当前 `SceneView` 选中行为统一定义为: + +1. 场景渲染时生成 `object-id` 纹理 +2. 鼠标点击时读取对应像素 +3. 颜色解码为实体 ID +4. `0` 视为“未选中任何对象”,但这仍然是一次成功的 GPU 采样 + +关键变化: + +- editor 不再把 `CPU ray picking` 作为 `SceneView` 点击选中的静默回退主链路 +- `CPU ray picking` 继续保留为独立几何工具能力,不再承担当前正式选中流程 +- `object-id` 读取失败会被显式标记为 readback failure,而不是与“没有有效帧”混在一起 + +## 3. 为什么这才算收口 + +之前的问题不是没有 `object-id pass`,而是“主路径”和“兜底路径”的语义不够硬: + +- 成功采样 +- 无有效 object-id 帧 +- GPU 读回失败 + +这三种状态以前没有被清晰区分。 + +现在已经收紧为显式结果类型: + +- `Unavailable` +- `Success` +- `ReadbackFailed` + +这意味着: + +- renderer/editor 的 `object-id` 交互已经形成可测试契约 +- `0 id` 与“采样失败”不再混淆 +- 后续若要继续升级成异步 readback、共享 picking 服务,也有稳定边界可接 + +## 4. 当前阶段完成后的边界 + +到这里,当前阶段可以正式视为完成: + +- editor viewport 宿主链路已打通 +- renderer 的 builtin post-process 已形成稳定接口 +- `SceneView` 选中正式以 GPU object-id 为主链路 +- 回归测试已覆盖 object-id 读回状态语义 + +下一阶段真正该做的是: + +- renderer 内正式 render graph / pass graph +- 更完整的 renderer-owned picking 服务 +- editor / runtime shared picking contract + +而不是继续在这个阶段里反复修补 viewport host。 diff --git a/editor/src/Viewport/ViewportHostService.h b/editor/src/Viewport/ViewportHostService.h index 3efdde77..8ac0f8c3 100644 --- a/editor/src/Viewport/ViewportHostService.h +++ b/editor/src/Viewport/ViewportHostService.h @@ -4,7 +4,6 @@ #include "Core/ISceneManager.h" #include "Core/ISelectionManager.h" #include "IViewportHostService.h" -#include "SceneViewportPicker.h" #include "SceneViewportCameraController.h" #include "ViewportHostRenderFlowUtils.h" #include "ViewportHostRenderTargets.h" @@ -159,27 +158,20 @@ public: return 0; } - const Components::Scene* scene = context.GetSceneManager().GetScene(); - if (scene == nullptr) { + if (context.GetSceneManager().GetScene() == nullptr) { return 0; } ViewportEntry& entry = GetEntry(EditorViewportKind::Scene); - uint64_t objectIdEntity = 0; - if (TryPickSceneViewEntityWithObjectId( + const ViewportObjectIdPickResult objectIdPick = + PickSceneViewEntityWithObjectId( entry, viewportSize, - viewportMousePosition, - objectIdEntity)) { - return objectIdEntity; + viewportMousePosition); + if (objectIdPick.status == ViewportObjectIdPickStatus::ReadbackFailed) { + SetViewportStatusIfEmpty(entry.statusText, "Scene object id readback failed"); } - - SceneViewportPickRequest request = {}; - request.scene = scene; - request.overlay = GetSceneViewOverlayData(); - request.viewportSize = Math::Vector2(viewportSize.x, viewportSize.y); - request.viewportPosition = Math::Vector2(viewportMousePosition.x, viewportMousePosition.y); - return PickSceneViewportEntity(request).entityId; + return objectIdPick.entityId; } void AlignSceneViewToOrientationAxis(SceneViewportOrientationAxis axis) override { @@ -576,14 +568,12 @@ private: targets.hasValidObjectIdFrame = false; } - bool TryPickSceneViewEntityWithObjectId( + ViewportObjectIdPickResult PickSceneViewEntityWithObjectId( ViewportEntry& entry, const ImVec2& viewportSize, - const ImVec2& viewportMousePosition, - uint64_t& outEntityId) { + const ImVec2& viewportMousePosition) { if (m_device == nullptr) { - outEntityId = 0; - return false; + return {}; } ViewportObjectIdPickContext pickContext = {}; @@ -596,7 +586,7 @@ private: pickContext.viewportSize = viewportSize; pickContext.viewportMousePosition = viewportMousePosition; - return TryPickViewportObjectIdEntity( + return PickViewportObjectIdEntity( pickContext, [this](const ViewportObjectIdReadbackRequest& request, std::array& outRgba) { return m_device != nullptr && @@ -607,8 +597,7 @@ private: request.pixelX, request.pixelY, outRgba); - }, - outEntityId); + }); } UI::ImGuiBackendBridge* m_backend = nullptr; diff --git a/editor/src/Viewport/ViewportObjectIdPicker.h b/editor/src/Viewport/ViewportObjectIdPicker.h index 4f497449..230a8a0b 100644 --- a/editor/src/Viewport/ViewportObjectIdPicker.h +++ b/editor/src/Viewport/ViewportObjectIdPicker.h @@ -9,6 +9,7 @@ #include #include +#include namespace XCEngine { namespace Editor { @@ -32,6 +33,21 @@ struct ViewportObjectIdReadbackRequest { uint32_t pixelY = 0; }; +enum class ViewportObjectIdPickStatus : uint8_t { + Unavailable = 0, + Success, + ReadbackFailed +}; + +struct ViewportObjectIdPickResult { + ViewportObjectIdPickStatus status = ViewportObjectIdPickStatus::Unavailable; + uint64_t entityId = 0; + + bool HasResolvedSample() const { + return status == ViewportObjectIdPickStatus::Success; + } +}; + inline bool CanPickViewportObjectId(const ViewportObjectIdPickContext& context) { return context.commandQueue != nullptr && context.texture != nullptr && @@ -67,28 +83,40 @@ inline bool BuildViewportObjectIdReadbackRequest( } template -bool TryPickViewportObjectIdEntity( +ViewportObjectIdPickResult PickViewportObjectIdEntity( const ViewportObjectIdPickContext& context, - ReadPixelFn&& readPixel, - uint64_t& outEntityId) { - outEntityId = 0; + ReadPixelFn&& readPixel) { + ViewportObjectIdPickResult result = {}; ViewportObjectIdReadbackRequest request = {}; if (!BuildViewportObjectIdReadbackRequest(context, request)) { - return false; + return result; } std::array rgba = {}; if (!readPixel(request, rgba)) { - return false; + result.status = ViewportObjectIdPickStatus::ReadbackFailed; + return result; } - outEntityId = static_cast(Rendering::DecodeObjectIdFromColor( + result.status = ViewportObjectIdPickStatus::Success; + result.entityId = static_cast(Rendering::DecodeObjectIdFromColor( rgba[0], rgba[1], rgba[2], rgba[3])); - return true; + return result; +} + +template +bool TryPickViewportObjectIdEntity( + const ViewportObjectIdPickContext& context, + ReadPixelFn&& readPixel, + uint64_t& outEntityId) { + const ViewportObjectIdPickResult result = + PickViewportObjectIdEntity(context, std::forward(readPixel)); + outEntityId = result.entityId; + return result.HasResolvedSample(); } } // namespace Editor