Files
HelloVulkan/Engine/src/Renderer/Passes/PhysicsDebugPass.cpp
T
atdunbg 36b1fc0566 新增调试绘制体系:DebugDraw 服务 + 物理调试 pass + 脚本侧力学线
通用图元(线/箭头四翼头/球/胶囊/盒)瞬时+持久双缓冲聚合;
物理 pass 画刚体速度/重力/接触反力;角色 collider 与力学
线归位(引擎只画资产、脚本画行为);线宽按设备上限钳制
2026-10-08 17:02:27 +08:00

299 lines
14 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// PhysicsDebugPass.cpp:pass 装配(shader object + ring VB 上传)与录制。
//
#include <Engine/Renderer/Passes/PhysicsDebugPass.h>
#include <Engine/Physics/PhysicsDebugTypes.h>
#include <Engine/Vulkan/Device.h>
#include <Engine/Vulkan/DescriptorManager.h>
#include <Engine/Vulkan/ShaderManager.h>
#include <Engine/Core/Log.h>
#include <glm/glm.hpp>
#include <tracy/Tracy.hpp>
#include <array>
#include <cstddef>
namespace Engine::Passes
{
namespace
{
// 顶点布局:location0 = pos(vec3),location1 = color(vec4)
//(setVertexInputEXT 走 2EXT 结构,与 Vertex.h 同一套)
vk::VertexInputBindingDescription2EXT MakeBinding()
{
vk::VertexInputBindingDescription2EXT b;
b.setBinding(0)
.setStride(sizeof(PhysicsDebugVertex))
.setInputRate(vk::VertexInputRate::eVertex)
.setDivisor(1); // 0 需 zeroDivisor feature(VUID-04798)
return b;
}
std::array<vk::VertexInputAttributeDescription2EXT, 2> MakeAttributes()
{
std::array<vk::VertexInputAttributeDescription2EXT, 2> a;
a[0].setLocation(0).setBinding(0)
.setFormat(vk::Format::eR32G32B32Sfloat)
.setOffset(offsetof(PhysicsDebugVertex, pos));
a[1].setLocation(1).setBinding(0)
.setFormat(vk::Format::eR32G32B32A32Sfloat)
.setOffset(offsetof(PhysicsDebugVertex, color));
return a;
}
// 直写方程:(ONE, ZERO) → dst = src,数学上等价于无混合
vk::ColorBlendEquationEXT MakeDirectEquation()
{
vk::ColorBlendEquationEXT eq;
eq.setSrcColorBlendFactor(vk::BlendFactor::eOne)
.setDstColorBlendFactor(vk::BlendFactor::eZero)
.setColorBlendOp(vk::BlendOp::eAdd)
.setSrcAlphaBlendFactor(vk::BlendFactor::eOne)
.setDstAlphaBlendFactor(vk::BlendFactor::eZero)
.setAlphaBlendOp(vk::BlendOp::eAdd);
return eq;
}
} // namespace
PhysicsDebugPass::PhysicsDebugPass(Device& device, MemoryAllocator& /*allocator*/,
ShaderManager& shaders,
DescriptorManager& descriptors,
FrameSceneData& frame,
RG::TextureHandle hdrColor,
RG::TextureHandle depth,
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_DebugVB(debugVB)
{
// 无描述符 set:viewProj 走 push constant(shader 侧声明一致)
m_Layout = descriptors.CreatePipelineLayout(
std::vector<vk::DescriptorSetLayout>{}, sizeof(glm::mat4));
const std::string shaderDir = "shaders/debug/";
auto vert = shaders.CreateFromFile(
shaderDir + "debug_line.vert.spv",
vk::ShaderStageFlagBits::eVertex,
std::vector<vk::DescriptorSetLayout>{},
sizeof(glm::mat4));
auto frag = shaders.CreateFromFile(
shaderDir + "debug_line.frag.spv",
vk::ShaderStageFlagBits::eFragment,
std::vector<vk::DescriptorSetLayout>{},
sizeof(glm::mat4));
m_Vert = std::move(vert);
m_Frag = std::move(frag);
if (!m_Vert || !m_Frag) {
ENGINE_FATAL("PhysicsDebugPass: failed to create shader objects");
}
}
PhysicsDebugPass::~PhysicsDebugPass()
{
// shader 随 ShaderRef 引用计数自动销毁,此处只清其余原生句柄
m_Device.GetDevice().destroyPipelineLayout(m_Layout);
}
void PhysicsDebugPass::PrepareFrame(u32 frameSlot,
const PhysicsDebugFrame& src,
const Debug::DebugFrame* dbg)
{
// 预算截断:物理调试不应打满(打满说明该收敛可视化了)
const u32 lineVerts = std::min<u32>(
static_cast<u32>(src.lines.size()), kMaxLineVerts);
const u32 triVerts = std::min<u32>(
static_cast<u32>(src.triangles.size()), kMaxTriVerts);
if ((src.lines.size() > kMaxLineVerts ||
src.triangles.size() > kMaxTriVerts) && !m_TruncationWarned) {
m_TruncationWarned = true;
ENGINE_WARN("PhysicsDebugPass: vertex budget exceeded "
"(lines {} / tris {}), clamped", src.lines.size(),
src.triangles.size());
}
// 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)
{
rg.AddPass("PhysicsDebug", [this](RG::RenderGraphContext& ctx) {
const u32 lineCount = m_LineCounts[ctx.frame.frameSlot];
const u32 triCount = m_TriCounts[ctx.frame.frameSlot];
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;
// HDR:Load 承接 Forward 结果叠加;深度:Load 承接不透明深度
// 做测试(只测不写),DontCare——下帧 Forward 重新 Clear
vk::RenderingAttachmentInfo color;
color.setImageView(ctx.View(m_HDRColor))
.setImageLayout(vk::ImageLayout::eColorAttachmentOptimal)
.setLoadOp(vk::AttachmentLoadOp::eLoad)
.setStoreOp(vk::AttachmentStoreOp::eStore);
vk::RenderingAttachmentInfo depth;
depth.setImageView(ctx.View(m_Depth))
.setImageLayout(vk::ImageLayout::eDepthAttachmentOptimal)
.setLoadOp(vk::AttachmentLoadOp::eLoad)
.setStoreOp(vk::AttachmentStoreOp::eDontCare);
vk::RenderingInfo renderingInfo;
renderingInfo.setRenderArea({{0, 0}, ctx.extent})
.setLayerCount(1)
.setColorAttachments(color)
.setPDepthAttachment(&depth);
cmd.beginRendering(renderingInfo);
const std::array<vk::ShaderStageFlagBits, 2> stages = {
vk::ShaderStageFlagBits::eVertex,
vk::ShaderStageFlagBits::eFragment
};
const std::array<vk::ShaderEXT, 2> shaders = {m_Vert.GetHandle(), m_Frag.GetHandle()};
cmd.bindShadersEXT(stages, shaders);
// layout 的 push range 是 vertex|fragment(CreatePipelineLayout 默认),
// vkCmdPushConstants 的 stageFlags 必须覆盖 range 全部 stage(VUID-01796)
cmd.pushConstants<glm::mat4>(
m_Layout,
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
0, m_Frame.ubo.proj * m_Frame.ubo.view);
// ---- 动态状态(shader object 无管线对象,全量显式设置)----
cmd.setRasterizerDiscardEnable(VK_FALSE);
cmd.setDepthTestEnable(VK_TRUE);
cmd.setDepthCompareOp(vk::CompareOp::eLessOrEqual);
cmd.setDepthWriteEnable(VK_FALSE); // 调试图元不污染场景深度
cmd.setDepthBoundsTestEnable(VK_FALSE);
cmd.setStencilTestEnable(VK_FALSE);
cmd.setDepthBiasEnable(VK_FALSE);
cmd.setPolygonModeEXT(vk::PolygonMode::eFill);
cmd.setCullMode(vk::CullModeFlagBits::eNone);
cmd.setFrontFace(vk::FrontFace::eCounterClockwise);
cmd.setPrimitiveRestartEnable(VK_FALSE);
const vk::VertexInputBindingDescription2EXT binding = MakeBinding();
const std::array<vk::VertexInputAttributeDescription2EXT, 2>
attributes = MakeAttributes();
cmd.setVertexInputEXT(binding, attributes);
cmd.setRasterizationSamplesEXT(vk::SampleCountFlagBits::e1);
constexpr vk::SampleMask sampleMask = 0xFFFFFFFF;
cmd.setSampleMaskEXT(vk::SampleCountFlagBits::e1, sampleMask);
cmd.setAlphaToCoverageEnableEXT(VK_FALSE);
cmd.setAlphaToOneEnableEXT(VK_FALSE);
const vk::Viewport viewport(
0.0f, 0.0f, static_cast<float>(ctx.extent.width),
static_cast<float>(ctx.extent.height), 0.0f, 1.0f);
cmd.setViewportWithCount(viewport);
cmd.setScissorWithCount(vk::Rect2D({0, 0}, ctx.extent));
// 无混合直写(ArrayProxyNoTemporaries:具名局部变量传入)
constexpr vk::Bool32 blendEnable = VK_FALSE;
const vk::ColorBlendEquationEXT direct = MakeDirectEquation();
constexpr vk::ColorComponentFlags colorWriteMask =
vk::ColorComponentFlagBits::eR |
vk::ColorComponentFlagBits::eG |
vk::ColorComponentFlagBits::eB |
vk::ColorComponentFlagBits::eA;
cmd.setColorBlendEnableEXT(0, blendEnable);
cmd.setColorBlendEquationEXT(0, direct);
cmd.setColorWriteMaskEXT(0, colorWriteMask);
// 线段与三角形各一条流:换流 = 换 VB 绑定 + 拓扑
cmd.setLineWidth(1.0f); // 线拓扑必须显式设置(VUID-08617)
if (lineCount > 0) {
cmd.setPrimitiveTopology(vk::PrimitiveTopology::eLineList);
const std::array<vk::Buffer, 1> bufs{m_LineVB.Get(ctx.frame.frameSlot)};
const std::array<vk::DeviceSize, 1> offs{0};
cmd.bindVertexBuffers(0, bufs, offs);
cmd.draw(lineCount, 1, 0, 0);
}
if (triCount > 0) {
cmd.setPrimitiveTopology(vk::PrimitiveTopology::eTriangleList);
const std::array<vk::Buffer, 1> bufs{m_TriVB.Get(ctx.frame.frameSlot)};
const std::array<vk::DeviceSize, 1> offs{0};
cmd.bindVertexBuffers(0, bufs, offs);
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 之后、
// TAA(Read HDRColor)之前——调试图元进入 TAA/Bloom/Tonemap 主链路
.Write(m_HDRColor, RG::ResourceUse::ColorAttachment)
.Write(m_Depth, RG::ResourceUse::DepthAttachment);
}
} // namespace Engine::Passes