feat: ImGui docking Editor + 显示链/帧同步/输入语义重构

- App/editor: Hierarchy/Inspector/Viewport/Stats 四面板 + DockBuilder 默认布局,main.cpp 瘦身为场景搭建与生命周期
- 显示链: Tonemap→PresentTarget 经 ImGui::Image 呈现(零色偏),backbuffer 仅承载 ImGui;Direct/Viewport 双显示模式 + 面板分辨率上报
- 帧首同步 BeginFrameSync: resize/vsync/尺寸/模式意图统一帧首落定(修描述符悬垂);修复 vsync 意图未落 swapchain 导致的每帧重建交换链
- 输入: Editor 每帧判定键盘/鼠标归属(打字让位、Viewport hover/focus 放行、Fullscreen 全放行)
- 材质: roughness 独立槽位 + 非 glTF 金属/粗糙贴图映射;透明队列 depthWrite=FALSE + translucent 独立 shader + scratch 清帧
- FBX: 非蒙皮网格烘焙节点链全局变换(Z-up 旋转/单位换算落顶点)
- Hierarchy 行 ImGui ID 以 UUID 作用域(重名不冲突、改名不丢选中)
This commit is contained in:
2026-09-30 00:24:53 +08:00
parent b25fa29fe5
commit 20f0035baa
57 changed files with 3067 additions and 622 deletions
@@ -0,0 +1,72 @@
//
// Created by atdunbg on 2026/9/29.
//
// BlitPass:显示模式适配器(kDisplayDirect 专用)。
// Direct 模式:vkCmdBlitImage 把 PresentTarget(tonemap 输出)拷进
// backbuffer,ImGui 随后 loadOp=Load 叠加 —— 场景铺满窗口的游戏形态。
// Viewport(Editor)模式:空转,backbuffer 由 ImGui 清屏独占,场景
// 经 ImGui::Image 呈现。
//
// 用 vkCmdBlitImage 而非着色器:同格式全屏拷贝零 shader 成本(声明
// TransferSrc/TransferDst use,layout/barrier 由 RG 推导)。Blit 不在
// rendering scope 内,直接录在 cmd 上,合法。
//
// 注:Blit 只在 Direct 模式运行,此时 PresentTarget 与 backbuffer 同为
// 目标尺寸(1:1),src/dst offset 均取本 pass 渲染区域即可。
//
#ifndef HELLOVULKAN_BLITPASS_H
#define HELLOVULKAN_BLITPASS_H
#include <Engine/Renderer/Passes/RenderPass.h>
#include <Engine/Renderer/SceneTypes.h>
#include <array>
namespace Engine::Passes {
class BlitPass final : public RenderPass {
public:
BlitPass(FrameSceneData& frame, RG::TextureHandle src,
RG::TextureHandle dst)
: m_Frame(frame), m_Src(src), m_Dst(dst) {}
void Register(RG::RenderGraph& rg) override
{
rg.AddPass("Blit", [this](RG::RenderGraphContext& ctx) {
if (m_Frame.displayMode != kDisplayDirect) return; // Editor 模式空转
vk::ImageBlit region;
region.setSrcSubresource({vk::ImageAspectFlagBits::eColor, 0, 0, 1})
.setDstSubresource({vk::ImageAspectFlagBits::eColor, 0, 0, 1});
// src/dst 各按自己附件的物理尺寸取 offset(Direct 模式下
// 二者同为窗口尺寸;尺寸解耦时此写法也各自正确)
const vk::Extent2D srcExt = ctx.Extent(m_Src);
const vk::Extent2D dstExt = ctx.Extent(m_Dst);
const std::array<vk::Offset3D, 2> srcSpan = {
vk::Offset3D{0, 0, 0},
vk::Offset3D{static_cast<i32>(srcExt.width),
static_cast<i32>(srcExt.height), 1}};
const std::array<vk::Offset3D, 2> dstSpan = {
vk::Offset3D{0, 0, 0},
vk::Offset3D{static_cast<i32>(dstExt.width),
static_cast<i32>(dstExt.height), 1}};
region.setSrcOffsets(srcSpan).setDstOffsets(dstSpan);
ctx.cmd.blitImage(
ctx.Image(m_Src), vk::ImageLayout::eTransferSrcOptimal,
ctx.Image(m_Dst), vk::ImageLayout::eTransferDstOptimal,
{region}, vk::Filter::eLinear);
})
.Read(m_Src, RG::ResourceUse::TransferSrc)
.Write(m_Dst, RG::ResourceUse::TransferDst);
}
private:
FrameSceneData& m_Frame; // displayMode(帧契约)
RG::TextureHandle m_Src; // PresentTarget(tonemap 输出)
RG::TextureHandle m_Dst; // backbuffer
};
} // namespace Engine::Passes
#endif //HELLOVULKAN_BLITPASS_H
@@ -28,6 +28,12 @@ namespace Engine::Passes {
void Register(RG::RenderGraph& rg) override;
private:
// 两段渲染共享的命令录制(shader/描述符绑定 + 几何/光栅/视口状态)。
// translucent=true 绑定 gl_FragDepth=1.0 的透明 frag 变体;
// 深度写与混合状态由各渲染实例作为初始态设置,见 ForwardPass.cpp
void RecordSharedState(const RG::RenderGraphContext& ctx,
vk::CommandBuffer cmd, bool translucent) const;
SceneGpu& m_Gpu;
FrameSceneData& m_Frame;
RG::TextureHandle m_HDRColor;
@@ -56,6 +56,7 @@ namespace Engine {
kDebugIrradiance, // IBL 漫反射辐照度(32² 低频)
kDebugPrefilter, // IBL pre-filter(纵向 = mip/roughness 分级)
kDebugBrdfLut, // BRDF 积分 LUT(RG 双通道)
kDebugTransparent, // 仅透明队列(Skybox/Forward 跳过绘制,看透明段内容)
kDebugViewCount
};
@@ -11,6 +11,7 @@
#define HELLOVULKAN_IMGUIPASS_H
#include <Engine/Renderer/Passes/RenderPass.h>
#include <Engine/Renderer/SceneTypes.h> // FrameSceneData(displayMode 帧契约)
namespace Engine {
@@ -21,15 +22,22 @@ namespace Engine {
class ImGuiPass final : public RenderPass {
public:
// 二级指针:Renderer::SetImGuiLayer 在 Register 之后才可能被调
//(EnableImGui 时机由派生类决定),执行期解引用取最新值
ImGuiPass(ImGuiLayer** layer, RG::TextureHandle backbuffer)
: m_Layer(layer), m_Backbuffer(backbuffer) {}
//(EnableImGui 时机由派生类决定),执行期解引用取最新值。
// presentTarget:ImGui::Image 采样的场景纹理(tonemap 输出)。
// loadOp 按 displayMode 分支:Direct = Load(blit 已铺场景),
// Viewport = Clear(唯一写入者,清"桌面底色")
ImGuiPass(ImGuiLayer** layer, FrameSceneData& frame,
RG::TextureHandle backbuffer, RG::TextureHandle presentTarget)
: m_Layer(layer), m_Frame(frame), m_Backbuffer(backbuffer),
m_PresentTarget(presentTarget) {}
void Register(RG::RenderGraph& rg) override;
private:
ImGuiLayer** m_Layer;
FrameSceneData& m_Frame; // displayMode(帧契约)
RG::TextureHandle m_Backbuffer;
RG::TextureHandle m_PresentTarget;
};
} // namespace Passes
@@ -25,22 +25,36 @@ namespace Engine {
class ShaderManager;
class UniformBuffer;
class Cubemap;
class Buffer;
class MemoryAllocator;
namespace Passes {
class SceneGpu {
public:
SceneGpu(Device& device, DescriptorManager& descriptors,
SceneGpu(Device& device, MemoryAllocator& allocator,
DescriptorManager& descriptors,
ShaderManager& shaders,
UniformBuffer& ubo, UniformBuffer& objectBuffer,
UniformBuffer& prevObjectBuffer);
UniformBuffer& prevObjectBuffer,
UniformBuffer& skinOffsetBuffer,
UniformBuffer& skinPaletteBuffer);
~SceneGpu();
SceneGpu(const SceneGpu&) = delete;
SceneGpu& operator=(const SceneGpu&) = delete;
// ---- MDI 间接绘制 ----
// CPU 打包的命令/count 落进本槽位 ring(Renderer 在 fence
// 等待后调用——与 UBO/SSBO 上传同一安全窗口)
void UploadIndirect(u32 frameSlot, const FrameSceneData& frame);
// 一次组绘制:bind 共享 megabuffer + drawIndexedIndirectCount
void DrawIndirectGroup(vk::CommandBuffer cmd, u32 frameSlot,
u32 viewIndex, const IndirectGroup& g);
// ---- set 布局(shader 契约,改绑定必须同步 shaders/)----
vk::DescriptorSetLayout uboSetLayout; // Set0 = {UBO, ObjectBuffer, PrevObjectBuffer}
vk::DescriptorSetLayout uboSetLayout; // Set0 = {UBO, ObjectBuffer, PrevObjectBuffer,
// SkinOffsets, SkinPalettes}
vk::DescriptorSetLayout textureSetLayout; // Set1 = {材质 4 槽 + 参数 UBO}
vk::DescriptorSetLayout shadowSetLayout; // Set2 = {级联 0/1/2 阴影图, irradiance cube}
vk::PipelineLayout forwardLayout; // 3 sets,无 push(GPUDriven 经 SSBO)
@@ -54,7 +68,16 @@ namespace Engine {
//(省 shader 出界分支)
vk::Sampler shadowSampler;
// ---- MDI per-slot ring ----
// 布局:[main | cascade0 | cascade1 | cascade2],每区
// kMaxInstances 条命令预算(每实例至多 1 条命令,必不越界)。
// count buffer 同分区(每组一个 u32,下标 = 组首命令下标)
std::array<Scope<Buffer>, kMaxFramesInFlight> indirectCmdBuffers;
std::array<Scope<Buffer>, kMaxFramesInFlight> indirectCountBuffers;
vk::ShaderEXT forwardVert{}, forwardFrag{};
// 透明变体:同 simple_shader + gl_FragDepth=1.0(见 frag 注释)
vk::ShaderEXT translucentFrag{};
vk::ShaderEXT shadowVert{}, shadowFrag{};
// RG 重编译后重绑阴影图(物理镜像按槽位各一份)
@@ -66,6 +89,7 @@ namespace Engine {
private:
Device& m_Device;
MemoryAllocator& m_Allocator;
DescriptorManager& m_Descriptors;
};
@@ -1,9 +1,10 @@
//
// Created by atdunbg on 2026/9/23.
//
// TonemapPass:HDR(+bloom)→ 显示空间,全屏三角写 Backbuffer。
// TonemapPass:HDR(+bloom)→ 显示空间,全屏三角写 PresentTarget。
// 显示变换在此显式建模(对齐 UE/Unity:HDR SceneColor → tonemapper → UI)。
// Backbuffer 从此只接收"显示空间"颜色:tonemap 直接写、ImGui 叠加。
// PresentTarget 是 offscreen 可采样纹理:由 ImGui::Image 呈现(输出位置
// 可自由摆放),backbuffer 不再接收场景画面。
//
#ifndef HELLOVULKAN_TONEMAPPASS_H
@@ -28,7 +29,8 @@ namespace Engine {
PostProcessParams& params,
FrameSceneData& frame,
RG::TextureHandle hdrColor, RG::TextureHandle bloomA,
RG::TextureHandle temporalDiff, RG::TextureHandle backbuffer,
RG::TextureHandle temporalDiff,
RG::TextureHandle presentTarget,
RG::TextureHandle rawHdr, RG::TextureHandle velocity,
RG::TextureHandle shadowMap0, RG::TextureHandle shadowMap1,
RG::TextureHandle shadowMap2);
@@ -46,7 +48,7 @@ namespace Engine {
RG::TextureHandle m_HDR;
RG::TextureHandle m_A;
RG::TextureHandle m_TemporalDiff;
RG::TextureHandle m_Backbuffer;
RG::TextureHandle m_Target; // PresentTarget(offscreen,ImGui 采样呈现)
// ---- 调试视图源(binding 3-7,Register 时声明读防止剔除)----
// 注:TAAColor 视图直接用 binding0(主输入即 TAA 输出),无需重复绑
RG::TextureHandle m_RawHDR; // Forward 直出(TAA 前)