返回

D3D Feature Level 11_0 升级 12_0:SM5.0 到 SM6.x 性能提升

windows

D3D Feature Level 11_0 升级到 12_0,以及 SM5.0 到 SM6.x:能否不改代码就提升效率?

很多时候,升级开发工具或API版本会让人有些纠结。Copilot 建议升级到更新的技术,这通常是个好建议,但我还不确定,像把 D3D Feature Level 从 11_0 换到 12_0,以及 Shader Model 从 5.0 换到 6.x,在不修改 C++ 和着色器代码的情况下,到底能不能真正提升效率。我的计划是未来肯定会重构代码以支持SM6等,但我想先知道这种仅仅更新“版本号”的做法是否能先稍微“爽一下”。

问题的根源:Feature Level 和 Shader Model 的差异

要搞清楚这个问题,我们需要先明白 Feature Level 和 Shader Model 的不同之处,以及升级后可能带来的影响。

1. D3D Feature Level

Feature Level 代表了硬件和驱动程序支持的 DirectX 功能的集合。 从 D3D_FEATURE_LEVEL_11_0 升级到 D3D_FEATURE_LEVEL_12_0 意味着你可以使用Direct3D 12中提供的所有功能,例如:

  • 更低的 CPU 开销: Direct3D 12 重新设计了 API,以减少驱动程序层的开销。即便你不显式使用这些新特性,某些内部优化也可能会起作用, 从而略微改善 CPU 方面的表现。
  • 资源绑定模型的变化: Direct3D 12 引入了新的资源绑定模型 (Descriptor Heaps, Root Signatures)。 虽然不直接修改代码不会利用这些新模型, 但是, 新的驱动程序可能会为这种较新的绑定模型提供某种内部优化。
  • 并行性: D3D12更强调了多线程的应用, 其在指令提交上有较大的优化。即便程序保持不变,底层运行方式上依旧有所改变。

但是, 如果你的代码 完全 没有利用 Direct3D 12 的特性, 你就相当于还把 Direct3D 12 当 Direct3D 11 用. 这样的"升级"带来的改进可能会非常有限, 甚至难以察觉。

2. Shader Model

Shader Model (SM) 定义了着色器程序(例如像素着色器、顶点着色器)可以使用的指令集和功能。从 SM5.0 升级到 SM6.x 意味着:

  • 更丰富的指令集: SM6.x 提供了一些新的着色器指令,例如 Wave Intrinsics。如果你的着色器没有用这些新指令, 那单纯升级 Shader Model 版本不会 带来任何性能提升。
  • 编译器优化: 即使你不使用新的着色器指令, 新的 Shader Model 对应的编译器可能会针对现有的着色器代码生成更优化的指令。 但这依赖于编译器的实现,且优化效果可能很小,甚至有时还会劣化(概率很小,但是依旧存在)。

总之, 除非编译器优化做得极好, 或者驱动程序针对 D3D12 Feature Level 有一些显著的底层改进, 否则, 仅仅升级 Feature Level 和 Shader Model 而不改代码, 通常不会带来 明显 的性能提升。

解决方案与详细步骤

尽管可能性不高,下面还是提供几种升级方案,并且评估可能性与风险:

1. 只升级 Feature Level 到 12_0

  • 原理: 让应用程序使用 Direct3D 12 的运行时和驱动,但保持着色器模型不变。

  • 步骤:

    1. 在创建 Direct3D 设备时, 指定 Feature Level 为 D3D_FEATURE_LEVEL_12_0。 如果用的是老的创建方法D3D11CreateDevice,则将该方法替换掉, 并保证最后可以创建D3D12设备即可。
    2. 确保你的项目设置正确链接到 Direct3D 12 的库。
    3. 着色器编译保持不变 (继续使用 SM5.0)。
    4. 其余的代码逻辑, 完全不做改变
  • 代码示例 (创建设备部分):

    // 假设已经正确设置了所需的 COM 对象等...
    D3D_FEATURE_LEVEL featureLevels[] = {
        D3D_FEATURE_LEVEL_12_0,
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0
    };
    
    ComPtr<ID3D12Device> device;
     HRESULT hr = D3D12CreateDevice(
            nullptr, // Use default adapter.
            D3D_FEATURE_LEVEL_12_0,
            IID_PPV_ARGS(&device)
            );
    if (FAILED(hr))
     {
    // 处理错误,设备不支持等等...
    }
    
    
  • 可能性和风险评估:

    • 可能有非常小的 CPU 开销降低, 如果底层做了优化。
    • 如果原本就有GPU瓶颈,那这种方式毫无作用。
    • 该种升级改动最小, 几乎不存在风险。
    • 不会破坏现有功能。

2. 升级 Feature Level 和 Shader Model (但不修改着色器代码)

  • 原理: 同时升级到 Direct3D 12 和 SM6.x,但 修改或重写现有的着色器代码。 理论上让新的编译器有机会优化老的着色器代码。

  • 步骤:

    1. 执行方案 1 中的所有步骤, 将 Feature Level 设置为 D3D_FEATURE_LEVEL_12_0

    2. 在编译着色器时,将目标 Shader Model 指定为 SM6.x (例如,ps_6_0, vs_6_0 等)。例如你用的是.fx文件来组织着色器, 可以直接用 fxc.exe工具:

      fxc.exe /T ps_6_0 /E MainPS /Fo PixelShader.cso PixelShader.fx
      fxc.exe /T vs_6_0 /E MainVS /Fo VertexShader.cso VertexShader.fx
      
      

    如果用的是HLSL源文件,编译步骤通常和构建过程结合在一起, 在Visual Studio项目设置中配置好.

  • 代码示例 (HLSL 编译部分, 如果在构建时进行):

    你不需要显式写什么"代码"来改编译目标。 只需在你选择的编译工具(FXC,DXC, VS自带的构建流程等)中,更改 target profile 设置,从5_0改为6_0,或 6_1, 6_2 等即可。

  • 可能性和风险评估:
    * 极小可能性有略微提升(新的编译器做的额外优化),但是也存在极小可能性导致劣化.

    • 由于没有显式利用新的指令或功能,改进空间非常非常有限.
    • 改动也不大, 只需要改一个编译器选项, 基本没有什么额外负担.
      • 存在轻微的风险:新编译器产生的着色器字节码可能和老驱动程序有兼容性问题 (极罕见,但可能)。最好在多种硬件和驱动配置下测试.

3. 只升级Shader Model(但使用 D3D_FEATURE_LEVEL_11_0)

这种做法完全没有意义。跳过。因为即便你的Shader能跑在6.x,你的Feature Level依旧会卡住上限.

4. 进阶: 保留核心逻辑,逐渐引入 D3D12 特性

  • 原理: 在现有代码框架的基础上, 逐步引入一些 D3D12 的特性, 进行大规模重构。 例如,先从改进资源绑定入手。

  • 步骤 (以改进资源绑定为例):

    1. 保留大部分代码: 大部分的渲染逻辑不变。

    2. 创建 Root Signature: 为常用的着色器组合创建 Root Signature。这部分有点繁琐,但是对于后期扩展性能是必要的.

       // 举例: 一个简单的 Root Signature,只有一个 Constant Buffer
       CD3DX12_ROOT_PARAMETER1 rootParameters[1];
       rootParameters[0].InitAsConstantBufferView(0); // b0
      
       CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc;
       rootSignatureDesc.Init_1_1(_countof(rootParameters), rootParameters, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
      
      ComPtr<ID3DBlob> signature;
      ComPtr<ID3DBlob> error;
       D3D12SerializeVersionedRootSignature(&rootSignatureDesc, &signature, &error);
      // 检查error
      
      ComPtr<ID3D12RootSignature> rootSignature;
      
      device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&rootSignature));
      
      
    3. 创建 Descriptor Heap: 分配存放资源符(例如 Constant Buffer View, Shader Resource View)的 Descriptor Heap。

    4. 修改绑定资源的代码: 不再使用旧的 PSSetConstantBuffers 等函数, 改为使用 SetGraphicsRootDescriptorTableSetGraphicsRootConstantBufferView 等函数, 通过 Root Signature 来绑定资源。这要求你根据前面定义的 Root Signature 来更新你的C++代码。

        //... 获取 Constant Buffer 的 GPU 虚拟地址 ...
      
       commandList->SetGraphicsRootSignature(rootSignature.Get());
      
      //假设constant buffer view已经在heap里面创建了
      
        commandList->SetGraphicsRootConstantBufferView(0, constantBufferGpuAddress); // 绑定到 Root Parameter 0 (在上面的Root Signature中定义为 b0)
      
    5. 更新着色器: 虽然我们不想大改 shader 代码, 但还是要稍微调整一下以适应 Root Signature。
      例如把:

      cbuffer MyConstantBuffer : register(b0)
      

    {
    float4x4 g_WorldViewProj;
    };
    中的`register(b0)` 这一部分移除(register定义改为在 Root Signature 中),变为:hlsl
    cbuffer MyConstantBuffer
    {
    float4x4 g_WorldViewProj;
    };

    ```
    
  • 可能性和风险评估

    • 相比前两个, 这个改进可能会 有意义一些。 但是请注意,依旧要彻底改为异步等才能实现12的全部实力.
    • 可以逐渐迁移,相对稳妥。
    • 要学习一些新的 Direct3D 12 概念和 API.
    • 需要有一定的代码改动量。

总结

一句话概括:在不重构的前提下,单靠升级 Feature Level 和 Shader Model 很难 获得可观的性能提升。 如果你的瓶颈主要在 CPU 端,且驱动和系统针对D3D12有些默认的内部优化, 可能 会看到非常微弱的提升。 如果你有GPU瓶颈, 或者不充分利用DX12特性, 那升级后基本没区别。

真正的性能飞跃,还是需要根据 Direct3D 12 的特点来重构或优化代码, 例如, 使用多线程命令列表, 优化资源绑定, 使用新的着色器模型特性(例如, Wave Intrinsics) 等。

建议:稳扎稳打,逐渐过渡,并且做好充分的性能测试.