refactor: formalize scene viewport object-id picking
This commit is contained in:
71
docs/plan/Renderer阶段收口补充_ObjectIdPicking正式化.md
Normal file
71
docs/plan/Renderer阶段收口补充_ObjectIdPicking正式化.md
Normal file
@@ -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。
|
||||||
@@ -4,7 +4,6 @@
|
|||||||
#include "Core/ISceneManager.h"
|
#include "Core/ISceneManager.h"
|
||||||
#include "Core/ISelectionManager.h"
|
#include "Core/ISelectionManager.h"
|
||||||
#include "IViewportHostService.h"
|
#include "IViewportHostService.h"
|
||||||
#include "SceneViewportPicker.h"
|
|
||||||
#include "SceneViewportCameraController.h"
|
#include "SceneViewportCameraController.h"
|
||||||
#include "ViewportHostRenderFlowUtils.h"
|
#include "ViewportHostRenderFlowUtils.h"
|
||||||
#include "ViewportHostRenderTargets.h"
|
#include "ViewportHostRenderTargets.h"
|
||||||
@@ -159,27 +158,20 @@ public:
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Components::Scene* scene = context.GetSceneManager().GetScene();
|
if (context.GetSceneManager().GetScene() == nullptr) {
|
||||||
if (scene == nullptr) {
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewportEntry& entry = GetEntry(EditorViewportKind::Scene);
|
ViewportEntry& entry = GetEntry(EditorViewportKind::Scene);
|
||||||
uint64_t objectIdEntity = 0;
|
const ViewportObjectIdPickResult objectIdPick =
|
||||||
if (TryPickSceneViewEntityWithObjectId(
|
PickSceneViewEntityWithObjectId(
|
||||||
entry,
|
entry,
|
||||||
viewportSize,
|
viewportSize,
|
||||||
viewportMousePosition,
|
viewportMousePosition);
|
||||||
objectIdEntity)) {
|
if (objectIdPick.status == ViewportObjectIdPickStatus::ReadbackFailed) {
|
||||||
return objectIdEntity;
|
SetViewportStatusIfEmpty(entry.statusText, "Scene object id readback failed");
|
||||||
}
|
}
|
||||||
|
return objectIdPick.entityId;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlignSceneViewToOrientationAxis(SceneViewportOrientationAxis axis) override {
|
void AlignSceneViewToOrientationAxis(SceneViewportOrientationAxis axis) override {
|
||||||
@@ -576,14 +568,12 @@ private:
|
|||||||
targets.hasValidObjectIdFrame = false;
|
targets.hasValidObjectIdFrame = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TryPickSceneViewEntityWithObjectId(
|
ViewportObjectIdPickResult PickSceneViewEntityWithObjectId(
|
||||||
ViewportEntry& entry,
|
ViewportEntry& entry,
|
||||||
const ImVec2& viewportSize,
|
const ImVec2& viewportSize,
|
||||||
const ImVec2& viewportMousePosition,
|
const ImVec2& viewportMousePosition) {
|
||||||
uint64_t& outEntityId) {
|
|
||||||
if (m_device == nullptr) {
|
if (m_device == nullptr) {
|
||||||
outEntityId = 0;
|
return {};
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewportObjectIdPickContext pickContext = {};
|
ViewportObjectIdPickContext pickContext = {};
|
||||||
@@ -596,7 +586,7 @@ private:
|
|||||||
pickContext.viewportSize = viewportSize;
|
pickContext.viewportSize = viewportSize;
|
||||||
pickContext.viewportMousePosition = viewportMousePosition;
|
pickContext.viewportMousePosition = viewportMousePosition;
|
||||||
|
|
||||||
return TryPickViewportObjectIdEntity(
|
return PickViewportObjectIdEntity(
|
||||||
pickContext,
|
pickContext,
|
||||||
[this](const ViewportObjectIdReadbackRequest& request, std::array<uint8_t, 4>& outRgba) {
|
[this](const ViewportObjectIdReadbackRequest& request, std::array<uint8_t, 4>& outRgba) {
|
||||||
return m_device != nullptr &&
|
return m_device != nullptr &&
|
||||||
@@ -607,8 +597,7 @@ private:
|
|||||||
request.pixelX,
|
request.pixelX,
|
||||||
request.pixelY,
|
request.pixelY,
|
||||||
outRgba);
|
outRgba);
|
||||||
},
|
});
|
||||||
outEntityId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UI::ImGuiBackendBridge* m_backend = nullptr;
|
UI::ImGuiBackendBridge* m_backend = nullptr;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Editor {
|
namespace Editor {
|
||||||
@@ -32,6 +33,21 @@ struct ViewportObjectIdReadbackRequest {
|
|||||||
uint32_t pixelY = 0;
|
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) {
|
inline bool CanPickViewportObjectId(const ViewportObjectIdPickContext& context) {
|
||||||
return context.commandQueue != nullptr &&
|
return context.commandQueue != nullptr &&
|
||||||
context.texture != nullptr &&
|
context.texture != nullptr &&
|
||||||
@@ -67,28 +83,40 @@ inline bool BuildViewportObjectIdReadbackRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename ReadPixelFn>
|
template <typename ReadPixelFn>
|
||||||
bool TryPickViewportObjectIdEntity(
|
ViewportObjectIdPickResult PickViewportObjectIdEntity(
|
||||||
const ViewportObjectIdPickContext& context,
|
const ViewportObjectIdPickContext& context,
|
||||||
ReadPixelFn&& readPixel,
|
ReadPixelFn&& readPixel) {
|
||||||
uint64_t& outEntityId) {
|
ViewportObjectIdPickResult result = {};
|
||||||
outEntityId = 0;
|
|
||||||
|
|
||||||
ViewportObjectIdReadbackRequest request = {};
|
ViewportObjectIdReadbackRequest request = {};
|
||||||
if (!BuildViewportObjectIdReadbackRequest(context, request)) {
|
if (!BuildViewportObjectIdReadbackRequest(context, request)) {
|
||||||
return false;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::array<uint8_t, 4> rgba = {};
|
std::array<uint8_t, 4> rgba = {};
|
||||||
if (!readPixel(request, rgba)) {
|
if (!readPixel(request, rgba)) {
|
||||||
return false;
|
result.status = ViewportObjectIdPickStatus::ReadbackFailed;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
outEntityId = static_cast<uint64_t>(Rendering::DecodeObjectIdFromColor(
|
result.status = ViewportObjectIdPickStatus::Success;
|
||||||
|
result.entityId = static_cast<uint64_t>(Rendering::DecodeObjectIdFromColor(
|
||||||
rgba[0],
|
rgba[0],
|
||||||
rgba[1],
|
rgba[1],
|
||||||
rgba[2],
|
rgba[2],
|
||||||
rgba[3]));
|
rgba[3]));
|
||||||
return true;
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ReadPixelFn>
|
||||||
|
bool TryPickViewportObjectIdEntity(
|
||||||
|
const ViewportObjectIdPickContext& context,
|
||||||
|
ReadPixelFn&& readPixel,
|
||||||
|
uint64_t& outEntityId) {
|
||||||
|
const ViewportObjectIdPickResult result =
|
||||||
|
PickViewportObjectIdEntity(context, std::forward<ReadPixelFn>(readPixel));
|
||||||
|
outEntityId = result.entityId;
|
||||||
|
return result.HasResolvedSample();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Editor
|
} // namespace Editor
|
||||||
|
|||||||
Reference in New Issue
Block a user