新增调试绘制体系:DebugDraw 服务 + 物理调试 pass + 脚本侧力学线

通用图元(线/箭头四翼头/球/胶囊/盒)瞬时+持久双缓冲聚合;
物理 pass 画刚体速度/重力/接触反力;角色 collider 与力学
线归位(引擎只画资产、脚本画行为);线宽按设备上限钳制
This commit is contained in:
2026-10-08 17:02:27 +08:00
parent ff955b9e63
commit 36b1fc0566
10 changed files with 903 additions and 44 deletions
@@ -64,9 +64,11 @@ namespace Engine::Passes
FrameSceneData& frame,
RG::TextureHandle hdrColor,
RG::TextureHandle depth,
UniformBuffer& lineVB, UniformBuffer& triVB)
UniformBuffer& lineVB, UniformBuffer& triVB,
UniformBuffer& debugVB)
: m_Device(device), m_Descriptors(descriptors), m_Frame(frame),
m_HDRColor(hdrColor), m_Depth(depth), m_LineVB(lineVB), m_TriVB(triVB)
m_HDRColor(hdrColor), m_Depth(depth), m_LineVB(lineVB), m_TriVB(triVB),
m_DebugVB(debugVB)
{
// 无描述符 set:viewProj 走 push constant(shader 侧声明一致)
m_Layout = descriptors.CreatePipelineLayout(
@@ -98,7 +100,8 @@ namespace Engine::Passes
}
void PhysicsDebugPass::PrepareFrame(u32 frameSlot,
const PhysicsDebugFrame& src)
const PhysicsDebugFrame& src,
const Debug::DebugFrame* dbg)
{
// 预算截断:物理调试不应打满(打满说明该收敛可视化了)
const u32 lineVerts = std::min<u32>(
@@ -115,18 +118,40 @@ namespace Engine::Passes
// fence 已等(Renderer::Render 的 WaitFence 之后调用),
// 写-读竞争关闭——与 UBO/SSBO 上传同一窗口语义
m_Spans[frameSlot].clear();
if (lineVerts > 0) {
m_LineVB.Update(frameSlot, src.lines.data(),
static_cast<u64>(lineVerts) *
sizeof(PhysicsDebugVertex));
m_Spans[frameSlot].push_back({false, 1.0f, true, 0, lineVerts});
}
if (triVerts > 0) {
m_TriVB.Update(frameSlot, src.triangles.data(),
static_cast<u64>(triVerts) *
sizeof(PhysicsDebugVertex));
m_Spans[frameSlot].push_back({true, 1.0f, true, 0, triVerts});
}
m_LineCounts[frameSlot] = lineVerts;
m_TriCounts[frameSlot] = triVerts;
// 通用调试源(独立 ring VB,与物理源隔离):批次按收集侧的
// (拓扑, 线宽, 深度) 原样展开为绘制段;超预算部分截断
if (dbg != nullptr && !dbg->Empty() && !dbg->verts.empty()) {
const u32 total = static_cast<u32>(dbg->verts.size());
const u32 n = std::min<u32>(total, Debug::kMaxVerts);
m_DebugVB.Update(frameSlot, dbg->verts.data(),
static_cast<u64>(n) * sizeof(Debug::Vertex));
u32 consumed = 0;
for (const Debug::Batch& b : dbg->batches) {
if (consumed >= n) break;
const u32 take = std::min<u32>(b.count, n - consumed);
if (take > 0) {
m_Spans[frameSlot].push_back(
{b.tris, b.width, b.depthTest, consumed, take});
}
consumed += take;
}
}
}
void PhysicsDebugPass::Register(RG::RenderGraph& rg)
@@ -134,7 +159,10 @@ namespace Engine::Passes
rg.AddPass("PhysicsDebug", [this](RG::RenderGraphContext& ctx) {
const u32 lineCount = m_LineCounts[ctx.frame.frameSlot];
const u32 triCount = m_TriCounts[ctx.frame.frameSlot];
if (lineCount == 0 && triCount == 0) return; // 空帧零开销
const std::vector<Span>& spans = m_Spans[ctx.frame.frameSlot];
if (lineCount == 0 && triCount == 0 && spans.empty()) {
return; // 空帧零开销
}
ZoneScopedN("PhysicsDebug");
const vk::CommandBuffer cmd = ctx.cmd;
@@ -236,6 +264,29 @@ namespace Engine::Passes
cmd.draw(triCount, 1, 0, 0);
}
// 通用调试源批次:逐批动态状态(拓扑/线宽/深度测试)。
// 线宽 >1 需 wideLines 特性,不支持时钳回 1.0(VUID-07937)
if (!spans.empty()) {
const std::array<vk::Buffer, 1> bufs{m_DebugVB.Get(ctx.frame.frameSlot)};
const std::array<vk::DeviceSize, 1> offs{0};
cmd.bindVertexBuffers(0, bufs, offs);
for (const Span& s : spans) {
cmd.setPrimitiveTopology(
s.tris ? vk::PrimitiveTopology::eTriangleList
: vk::PrimitiveTopology::eLineList);
// 线宽单位 = 像素,设备范围 [1.0, lineWidthRange[1]]:
// 低于下限的值(如 0.18)是校验错误,驱动按 1px 处
// 理——必须钳进范围;wideLines 不支持则恒 1px
cmd.setLineWidth(m_Device.IsWideLinesSupported()
? std::clamp(s.width, 1.0f,
m_Device.MaxLineWidth())
: 1.0f);
cmd.setDepthTestEnable(s.depthTest ? VK_TRUE : VK_FALSE);
cmd.draw(s.count, 1, s.first, 0);
}
cmd.setLineWidth(1.0f); // 恢复默认,防状态外泄
}
cmd.endRendering();
})
// Write use(WAW 链):保证排在 Forward/ForwardTrans 之后、