6.5 KiB
VulkanPipelineState
命名空间: XCEngine::RHI
类型: class
头文件: XCEngine/RHI/Vulkan/VulkanPipelineState.h
描述: Vulkan 后端里的 RHIPipelineState 实现,负责创建 graphics / compute pipeline,并保存与之相关的 layout、render target 格式和固定功能状态。
概览
VulkanPipelineState 是当前 Vulkan 后端里 draw / dispatch 能否真正执行的关键对象。
它承担两类职责:
- 保存抽象层的 pipeline 描述,例如输入布局、光栅化、混合、深度模板、拓扑和 render target 格式
- 在合适时机创建原生
VkPipeline
但它不是一个完全成熟、完全统一的 PSO 系统。当前 graphics 和 compute 路径的成熟度并不一致,这一点文档必须明确写出来。
生命周期
常见用法有两种:
图形管线
- 构造
VulkanPipelineState Initialize(VulkanDevice*, const GraphicsPipelineDesc&)- 录制阶段把它传给 VulkanCommandList 的
SetPipelineState() - 不再使用时
Shutdown()
计算管线
- 初始化 pipeline state,让它至少拥有 pipeline layout
SetComputeShader(RHIShader*)- 在录制
Dispatch()前由EnsureValid()延迟创建 compute pipeline
当前实现的真实行为
graphics 与 compute 的差异
按当前实现:
Initialize()面向的是GraphicsPipelineDesc- 如果
vertexShader和fragmentShader都没有有效 payload,Initialize()仍可能把对象标记为m_isConfigured = true - graphics 路径要求:
renderTargetCount == 1- 第一项 render target format 有效
- vertex shader 和 fragment shader 都存在
- compute 路径不是在
Initialize()里直接创建,而是走SetComputeShader()+EnsureValid()+CreateComputePipeline()
因此当前类名虽然叫 PipelineState,但本质上是“图形优先、计算补充”的实现。
Pipeline Layout
EnsurePipelineLayout(desc) 有两种行为:
- 如果
desc.pipelineLayout非空,直接借用外部VulkanPipelineLayout - 否则创建一个空的
VkPipelineLayout
这解释了为什么 compute 路径能先有 pipeline layout,再延迟绑定 compute shader。
Graphics Pipeline 创建
CreateGraphicsPipeline() 当前会:
- 用
CompileVulkanShader(...)把desc.vertexShader/desc.fragmentShader编译成 SPIR-V - 临时创建两个
VkShaderModule - 根据 render target / depth format 构造一个内部
VkRenderPass - 再据此创建 graphics pipeline
需要注意的是,当前这个 graphics pipeline 会自己生成一个内部 VkRenderPass。这和一些成熟引擎里把 render pass / framebuffer compatibility 单独抽出来统一管理的做法相比,还处于更简单直接的阶段。
Compute Pipeline 创建
CreateComputePipeline() 当前要求:
m_device有效m_pipelineLayout有效m_computeShader非空m_computeShader实际上是有效的VulkanShader
创建时会从 VulkanShader 里拿 VkShaderModule 与 entry point,然后调用 vkCreateComputePipelines()。
IsValid() 与真实 native pipeline
这是当前实现里最容易误解的一点:
IsValid()返回的是m_isConfigured- 它不等价于“
m_pipeline != VK_NULL_HANDLE一定成立”
尤其在 compute 路径里,pipeline 可能要等 EnsureValid() 之后才真正创建。因此把 IsValid() 理解成“配置状态可用”更准确,而不是“原生对象已经存在”。
其他行为
Bind()/Unbind()当前都是 no-opGetHash()目前只用了拓扑和第一个 render target format 做轻量 hashHasDepthStencilAttachment()只按m_depthStencilFormat是否为Unknown判断
线程语义
当前实现没有内部锁。更安全的工程约束是:
- pipeline state 构建与修改在单线程完成
- 构建完成后再交给录制线程使用
- 不要在录制过程中并发修改同一个 pipeline state
所有权与资源管理
VulkanPipelineState持有VkPipeline- 可能持有自建的
VkPipelineLayout - graphics 路径还会持有内部创建的
VkRenderPass - 如果借用了外部
VulkanPipelineLayout,则不会在自身Shutdown()中销毁它
设计取向
当前实现非常典型地体现了“先让 pipeline 概念在跨后端 RHI 里工作起来,再逐步收敛成更严谨的 Vulkan 语义”:
- 优点是 graphics / compute 基本链路已经能跑
- 代价是状态有效性、render pass 归属和 hash 策略仍较简化
这类设计在商业引擎重构早期很常见。真正成熟后,通常会继续把 pipeline cache、render pass 兼容性、shader reflection 和 descriptor layout 规则再往前推一层。
当前限制
- graphics 路径当前只支持一个 render target
- compute pipeline 为延迟创建模型
IsValid()不严格等价于 native pipeline 已创建Bind()/Unbind()无实际行为GetHash()很简化,不能视为完整 pipeline cache key
主要公开方法
bool Initialize(VulkanDevice* device, const GraphicsPipelineDesc& desc)void SetInputLayout(const InputLayoutDesc& layout)void SetRasterizerState(const RasterizerDesc& state)void SetBlendState(const BlendDesc& state)void SetDepthStencilState(const DepthStencilStateDesc& state)void SetTopology(uint32_t topologyType)void SetRenderTargetFormats(uint32_t count, const uint32_t* formats, uint32_t depthFormat)void SetSampleCount(uint32_t count)void SetComputeShader(RHIShader* shader)PipelineStateHash GetHash() constRHIShader* GetComputeShader() constbool HasComputeShader() constbool IsValid() constvoid EnsureValid()void Shutdown()void Bind()void Unbind()void* GetNativeHandle()PipelineType GetType() const
相关测试与使用线索
tests/RHI/unit/test_vulkan_graphics.cpp覆盖了 Vulkan graphics pipeline 与 compute dispatch 的真实创建路径tests/RHI/unit/test_compute.cpp会把 compute shader、pipeline layout 和 descriptor set 接起来验证计算路径tests/RHI/unit/test_command_list.cpp会通过抽象命令列表接口驱动 pipeline 绑定与 dispatch