为什么在着色器中顺序很重要?

时间:2018-11-16 20:44:03

标签: c# c++ directx-11 hlsl sharpdx

快速笔记

此问题带有C++标签,因为DirectX中使用C++的开发人员比C#中的更多。我不认为这个问题与任何一种语言都直接相关,而是与所使用的类型(据我所知完全相同)或DirectX本身及其如何编译着色器有关。如果在C++工作的人知道更好,更具描述性的答案,那么我宁愿选择这个答案而不是自己的答案。我了解两种语言,但主要使用C#


概述

HLSL着色器中,设置常量缓冲区时遇到了一个非常奇怪的问题。有问题的原始常量缓冲区设置如下:

cbuffer ObjectBuffer : register(b0) {
    float4x4 WorldViewProjection;
    float4x4 World;
    float4x4 WorldInverseTranspose;
}

cbuffer ViewBuffer : register(b1) {
    DirectionalLight Light;
    float3 CameraPosition;
    float3 CameraUp;
    float2 RenderTargetSize;
}

如果我交换b0b1寄存器,则渲染不再起作用( e1 )。如果我不理会这些寄存器,并再次在WorldWorldViewProjection之间交换顺序,则渲染不再起作用( e2 )。但是,只需将ViewBuffer文件中的ObjectBuffer移动到HLSL上方而不进行其他修改,就可以了。

现在,我希望寄存器的放置非常重要,并且第一个寄存器b0需要该缓冲区中给出的三个属性,并且我知道HLSL常量缓冲区必须位于16个字节中大块。但是,这让我有些疑问。


问题

考虑到HLSL期望常量缓冲区为16字节块的事实;

  • 为什么 e2 中的排序如此重要?

float4x4类型与Matrix类型实际上不是数组一样吗?

[ 0, 0, 0, 0 ] = 16 bytes
[ 0, 0, 0, 0 ] = 16 bytes
[ 0, 0, 0, 0 ] = 16 bytes
[ 0, 0, 0, 0 ] = 16 bytes
[    TOTAL   ] = 64 bytes

由于float本身是4个字节,因此这意味着float4是16个字节,因此float4x4是64个字节。那么,如果大小保持不变,为什么顺序很重要?

  • 在这种情况下,为什么必须将ObjectBuffer分配给b0而不是其他任何b寄存器?

1 个答案:

答案 0 :(得分:0)

快速笔记

我目前正在对问题进行进一步的分析,以便可以给出更详细和准确的答案。我将更新问题和答案,以在发现更多细节时尽可能多地反映出准确性。


基本答案

上述问题的确切问题(发布时尚不知道)是HLSL缓冲区与它们的C#表示形式不匹配;因此,变量的重新排序导致着色器失败。但是,我仍然不确定为什么类型相同。我在沿途中学到了其他一些东西,希望得到答案,因此决定将其发布在这里。


为什么要重要

经过一些进一步的研究和测试,我仍然不确定100%都是相同类型的背后原因。总体而言,我认为这可能是由于cbuffer中期望的类型和struct中类型的顺序造成的。在这种情况下,如果您的cbuffer首先期望bool,然后期望float,则重新排列会导致问题。

cbuffer MaterialBuffer : register(b0) {
    bool HasTexture;
    float SpecularPower;
    float4 Ambient;
    ...
}
// Won't work.
public struct MaterialBuffer {
    public float SpecularPower;
    public Vector2 padding2;
    public bool HasTexture;
    private bool padding0;
    private short padding1;
    public Color4 Ambient;
    ...
}
// Works.
public struct MaterialBuffer {
    public bool HasTexture;
    private bool padding0;
    private short padding1;
    public float SpecularPower;
    public Vector2 padding2;
    public Color4 Ambient;
    ...
}

我投入了一些研究工作来测试类型的字节大小的差异,但这似乎并没有太大改变,但是我将在这里发布常见基本类型的发现:

1 Byte  : bool, sbyte, byte
2 Bytes : short, ushort
4 Bytes : int, uint, float
8 Bytes : long, ulong, double
16 Bytes: decimal

您必须意识到用于构造更复杂类型的基本类型。假设您有一个Vector2,该属性带有X属性和一个Y属性。如果这些由float类型表示,则在下一个属性之前需要8字节的填充,除非您有其他帮助达到16字节的填充。但是,如果这些类型由double类型或decimal类型表示,则大小会有所不同,您需要注意这一点。


注册分配

我能够解决注册问题;设置缓冲区时,它也对应于C#端。设置缓冲区时,需要为这些缓冲区分配索引,并且HLSL应该使用相同的索引。

// Buffer declarations in HLSL.
cbuffer ViewBuffer : register(b0)
cbuffer CameraBuffer : register(b1);
cbuffer MaterialBuffer : register(b2);

// Buffer assignments in C#.
context.VertexShader.SetConstantBuffer(0, viewBuffer);
context.VertexShader.SetConstantBuffer(1, cameraBuffer);
context.VertexShader.SetConstantBuffer(2, materialBuffer);

由于将缓冲区分配给正确的寄存器,因此上面的代码将按预期工作。但是,例如,如果将相机的缓冲区更改为8,则必须将cbuffer分配给寄存器b8才能正常工作。出于确切的原因,下面的代码不起作用。

cbuffer CameraBuffer : register(b1)
context.VertexShader.SetConstantBuffer(8, cameraBuffer);