다음을 통해 공유


HLSL의 리소스 바인딩

이 항목에서는 Direct3D 12에서 HLSL(High Level Shader Language) 셰이더 모델 5.1 사용하는 몇 가지 특정 기능에 대해 설명합니다. 모든 Direct3D 12 하드웨어는 셰이더 모델 5.1을 지원하므로 이 모델에 대한 지원은 하드웨어 기능 수준에 따라 달라지지 않습니다.

리소스 종류 및 배열

셰이더 모델 5(SM5.0) 리소스 구문은 register 키워드를 사용하여 리소스에 대한 중요한 정보를 HLSL 컴파일러에 릴레이합니다. 예를 들어 다음 문은 슬롯 t3, t4, t5 및 t6에 바인딩된 4개의 텍스처 배열을 선언합니다. t3은 문에 나타나는 유일한 레지스터 슬롯이며, 단순히 4개 배열에서 첫 번째 슬롯입니다.

Texture2D<float4> tex1[4] : register(t3)

HLSL의 셰이더 모델 5.1(SM5.1) 리소스 구문은 기존 레지스터 리소스 구문을 기반으로 하므로 더 쉽게 포팅할 수 있습니다. HLSL의 Direct3D 12 리소스는 논리 레지스터 공간 내의 가상 레지스터에 바인딩됩니다.

  • t – 셰이더 리소스 뷰의 경우(SRV)
  • s – 샘플러용
  • u – UAV(순서가 지정되지 않은 액세스 뷰)의 경우
  • b – 상수 버퍼 뷰 (CBV)

셰이더를 참조하는 루트 서명은 선언된 레지스터 슬롯과 호환되어야 합니다. 예를 들어 루트 서명의 다음 부분은 t0~t98 슬롯이 있는 설명자 테이블을 설명하므로 t3부터 t6까지의 텍스처 슬롯 사용과 호환됩니다.

DescriptorTable( CBV(b1), SRV(t0,numDescriptors=99), CBV(b2) )

리소스 선언은 스칼라, 1D 배열 또는 다차원 배열일 수 있습니다.

Texture2D<float4> tex1 : register(t3,  space0)
Texture2D<float4> tex2[4] : register(t10)
Texture2D<float4> tex3[7][5][3] : register(t20, space1)

SM5.1은 SM5.0과 동일한 리소스 유형 및 요소 형식을 사용합니다. SM5.1 선언 제한은 더 유연하며 런타임/하드웨어 제한에 의해서만 제한됩니다. space 키워드는 선언된 변수가 바인딩되는 논리 레지스터 공간을 지정합니다. space 키워드를 생략하면 기본 공간 인덱스 0이 범위에 암시적으로 할당되므로 위의 tex2 범위는 space0상주합니다. register(t3, space0) register(t3, space1)충돌하거나 t3을 포함할 수 있는 다른 공간의 배열과 충돌하지 않습니다.

배열 리소스는 첫 번째 차원을 비워 두거나 0으로 지정하여 선언되는 바인딩되지 않은 크기를 가질 수 있습니다.

Texture2D<float4> tex1[] : register(t0)

일치하는 설명자 테이블은 다음과 같습니다.

DescriptorTable( CBV(b1), UAV(u0, numDescriptors = 4), SRV(t0, numDescriptors=unbounded) )

HLSL의 바인딩되지 않은 배열은 설명자 테이블의 numDescriptors 있는 고정 번호 집합과 일치하며 HLSL의 고정 크기는 설명자 테이블의 바인딩되지 않은 선언과 일치합니다.

바인딩되지 않은 크기를 포함하여 다차원 배열이 허용됩니다. 이러한 다차원 배열은 레지스터 공간에서 평면화됩니다.

Texture2D<float4> tex2[3000][10] : register(t0, space0); // t0-t29999 in space0
Texture2D<float4> tex3[0][5][3] : register(t5, space1)

리소스 범위의 별칭 지정은 허용되지 않습니다. 즉, 각 리소스 종류(t, s, u, b)에 대해 선언된 레지스터 범위는 겹치지 않아야 합니다. 여기에는 바인딩되지 않은 범위도 포함됩니다. 다른 레지스터 공간에 선언된 범위는 겹치지 않습니다. 알아두세요, 제한되지 않은 tex2(위쪽)은 space0에 있고, 제한되지 않은 tex3space1에 있어 서로 겹치지 않습니다.

배열로 선언된 리소스에 액세스하는 것은 인덱싱하는 것만큼 간단합니다.

Texture2D<float4> tex1[400] : register(t3);
sampler samp[7] : register(s0);
tex1[myMaterialID].Sample(samp[samplerID], texCoords);

인덱스(위 코드의myMaterialIDsamplerID)의 사용에 대한 중요한 기본 제한은 웨이브내에서 다를 수 없다는 것입니다. 인스턴싱 횟수에 따라 인덱스를 변경하는 것도 변동으로 간주됩니다.

인덱스 변경이 필요한 경우 인덱스 NonUniformResourceIndex 한정자를 지정합니다. 예를 들면 다음과 같습니다.

tex1[NonUniformResourceIndex(myMaterialID)].Sample(samp[NonUniformResourceIndex(samplerID)], texCoords);

일부 하드웨어에서 이 한정자를 사용하면 수정(스레드 간 포함)을 적용하는 추가 코드가 생성되지만 성능은 약간 저하됩니다. 이 한정자 없이 인덱스가 변경되고 그리기/디스패치 내에서 인덱스가 변경되면 결과가 정의되지 않습니다.

설명자 배열 및 텍스처 배열

텍스처 배열은 DirectX 10부터 사용할 수 있습니다. 텍스처 배열에는 하나의 설명자가 필요하지만 모든 배열 조각은 동일한 형식, 너비, 높이 및 밉 수를 공유해야 합니다. 또한 배열은 가상 주소 공간에서 연속 범위를 차지해야 합니다. 다음 코드는 셰이더에서 텍스처 배열에 액세스하는 예제를 보여 줍니다.

Texture2DArray<float4> myTex2DArray : register(t0); // t0
float3 myCoord(1.0f,1.4f,2.2f); // 2.2f is array index (rounded to int)
color = myTex2DArray.Sample(mySampler, myCoord);

텍스처 배열에서 인덱스는 NonUniformResourceIndex같은 한정자 없이 자유롭게 변경할 수 있습니다.

해당하는 설명자 배열은 다음과 같습니다.

Texture2D<float4> myArrayOfTex2D[] : register(t0); // t0+
float2 myCoord(1.0f, 1.4f);
color = myArrayOfTex2D[2].Sample(mySampler,myCoord); // 2 is index

배열 인덱스에 부적절하게 사용된 부동 소수점 숫자는 myArrayOfTex2D[2]으로 대체됩니다. 또한 설명자 배열은 차원에 더 많은 유연성을 제공합니다. 이 예시에서는 유형 Texture2D이 변할 수 없지만, 형식, 너비, 높이, 그리고 밉 개수는 각 설명자에 따라 변할 수 있습니다.

텍스처 배열의 설명자 배열을 사용하는 것은 합법적입니다.

Texture2DArray<float4> myArrayOfTex2DArrays[2] : register(t0);

구조체 배열을 선언하는 것은 합법적이지 않습니다. 설명자를 포함하는 각 구조체는 지원되지 않습니다. 예를 들어 다음 코드는 지원되지 않습니다.

struct myStruct {
    Texture2D                    a; 
    Texture2D                    b;
    ConstantBuffer<myConstants>  c;
};
myStruct foo[10000] : register(....);

이렇게 하면 메모리 레이아웃 abcabcabc....이 허용될 수 있었겠지만, 이것은 프로그래밍 언어의 제한사항으로 지원되지 않습니다. 지원되는 방법 중 하나는 다음과 같습니다. 이 경우 메모리 레이아웃은 aaa.... bbb... ccc....

Texture2D                     a[10000] : register(t0);
Texture2D                     b[10000] : register(t10000);
ConstantBuffer<myConstants>   c[10000] : register(b0);

abcabcabc.... 메모리 레이아웃을 구현하려면 myStruct 구조를 사용하지 않고 설명자 테이블을 사용합니다.

리소스 별칭

HLSL 셰이더에 지정된 리소스 범위는 논리적 범위입니다. 루트 서명 메커니즘을 통해 런타임 시 구체적인 힙 범위에 바인딩됩니다. 일반적으로 논리적 범위는 다른 힙 범위와 겹치지 않는 힙 범위에 매핑됩니다. 그러나 루트 서명 메커니즘을 사용하면 호환되는 형식의 힙 범위를 별칭(겹침)할 수 있습니다. 예를 들어 위의 예제에서 tex2tex3 범위는 HLSL 프로그램에서 텍스처를 별칭 지정하는 효과가 있는 동일한(또는 겹치는) 힙 범위에 매핑될 수 있습니다. 이러한 에일리어싱을 원하는 경우, 셰이더는 D3D10_SHADER_RESOURCES_MAY_ALIAS 옵션으로 컴파일되어야 하며, 이는 FXC(Effect-Compiler Tool)에서 /res_may_alias 옵션을 사용하여 설정됩니다. 이 옵션을 사용하면 리소스가 별칭일 수 있다는 가정 하에 특정 부하/저장소 최적화를 방지하여 컴파일러가 올바른 코드를 생성할 수 있습니다.

발산 및 미분

SM5.1은 리소스 인덱스에 제한을 두지 않습니다. 즉, tex2[idx].Sample(…) - 인덱스 idx는 리터럴 상수, cbuffer 상수 또는 보간된 값일 수 있습니다. 프로그래밍 모델은 뛰어난 유연성을 제공하지만 다음 사항에 유의해야 할 문제가 있습니다.

  • 인덱스가 쿼드 간에 분산되면 LOD와 같은 하드웨어 계산 파생 수량 및 파생 수량이 정의되지 않을 수 있습니다. HLSL 컴파일러는 이 경우 경고를 발생시키기 위해 최선을 다하지만 셰이더가 컴파일되는 것을 방지하지는 않습니다. 이 동작은 서로 다른 제어 흐름에서 파생을 계산하는 것과 유사합니다.
  • 리소스 인덱스가 서로 다른 경우 하드웨어가 여러 리소스에 대한 작업을 수행해야 하므로 균일한 인덱스의 경우와 비교하여 성능이 저하됩니다. 서로 다를 수 있는 리소스 인덱스는 HLSL 코드에서 NonUniformResourceIndex 함수로 표시되어야 합니다. 그렇지 않으면 결과가 정의되지 않습니다.

픽셀 셰이더의 UAV

SM5.1은 SM5.0의 경우와 마찬가지로 UAV 범위에 제약 조건을 픽셀 셰이더로 적용하지 않습니다.

상수 버퍼

개발자가 상수 버퍼를 인덱싱할 수 있도록 SM5.1 상수 버퍼(cbuffer) 구문이 SM5.0에서 변경되었습니다. 인덱싱 가능한 상수 버퍼를 사용하도록 설정하기 위해 SM5.1에는 ConstantBuffer "template" 구문이 도입되었습니다.

struct Foo
{
    float4 a;
    int2 b;
};
ConstantBuffer<Foo> myCB1[2][3] : register(b2, space1);
ConstantBuffer<Foo> myCB2 : register(b0, space1);

앞의 코드는 Foo 형식의 크기 6인 상수 버퍼 변수 myCB1와 스칼라인 상수 버퍼 변수 myCB2를 선언합니다. 이제 셰이더에서 상수 버퍼 변수를 다음과 같이 인덱싱할 수 있습니다.

myCB1[i][j].a.xyzw
myCB2.b.yy

'a' 및 'b' 필드는 전역 변수가 아니라 필드로 처리되어야 합니다. 이전 버전과의 호환성을 위해 SM5.1은 스칼라 cbuffer에 대한 이전 cbuffer 개념을 지원합니다. 다음 문은 SM5.0에서와 같이 'a' 및 'b' 전역 읽기 전용 변수를 만듭니다. 그러나 이러한 이전 스타일의 cbuffer는 인덱싱할 수 없습니다.

cbuffer : register(b1)
{
    float4 a;
    int2 b;
};

현재 셰이더 컴파일러는 사용자 정의 구조에 대해서만 ConstantBuffer 템플릿을 지원합니다.

호환성을 위해 HLSL 컴파일러는 space0선언된 범위에 대한 리소스 레지스터를 자동으로 할당할 수 있습니다. register 절에서 'space'를 생략하면 기본 space0 사용됩니다. 컴파일러는 첫 번째 홀 맞춤 추론을 사용하여 레지스터를 할당합니다. 할당은 리플렉션 API를 통해 검색할 수 있습니다. 이 API는 공간의 공백 필드를 추가하도록 확장되었으며, BindPoint 필드는 리소스 레지스터 범위의 하한을 나타냅니다.

SM5.1의 바이트 코드 변경 내용

SM5.1은 리소스 레지스터가 명령에서 선언되고 참조되는 방식을 변경합니다. 이 구문에는 그룹 공유 메모리 레지스터에 대해 수행되는 방식과 유사한 레지스터 "변수"를 선언하는 작업이 포함됩니다.

Texture2D<float4> tex0          : register(t5,  space0);
Texture2D<float4> tex1[][5][3]  : register(t10, space0);
Texture2D<float4> tex2[8]       : register(t0,  space1);
SamplerState samp0              : register(s5, space0);

float4 main(float4 coord : COORD) : SV_TARGET
{
    float4 r = coord;
    r += tex0.Sample(samp0, r.xy);
    r += tex2[r.x].Sample(samp0, r.xy);
    r += tex1[r.x][r.y][r.z].Sample(samp0, r.xy);
    return r;
}

이렇게 하면 분해되어 다음과 같이 됩니다.

// Resource Bindings:
//
// Name                                 Type  Format         Dim    ID   HLSL Bind     Count
// ------------------------------ ---------- ------- ----------- -----   --------- ---------
// samp0                             sampler      NA          NA     S0    a5            1
// tex0                              texture  float4          2d     T0    t5            1
// tex1[0][5][3]                     texture  float4          2d     T1   t10        unbounded
// tex2[8]                           texture  float4          2d     T2    t0.space1     8
//
//
//
// Input signature:
//
// Name                 Index   Mask Register SysValue  Format   Used
// -------------------- ----- ------ -------- -------- ------- ------
// COORD                    0   xyzw        0     NONE   float   xyzw
//
//
// Output signature:
//
// Name                 Index   Mask Register SysValue  Format   Used
// -------------------- ----- ------ -------- -------- ------- ------
// SV_TARGET                0   xyzw        0   TARGET   float   xyzw
//
ps_5_1
dcl_globalFlags refactoringAllowed
dcl_sampler s0[5:5], mode_default, space=0
dcl_resource_texture2d (float,float,float,float) t0[5:5], space=0
dcl_resource_texture2d (float,float,float,float) t1[10:*], space=0
dcl_resource_texture2d (float,float,float,float) t2[0:7], space=1
dcl_input_ps linear v0.xyzw
dcl_output o0.xyzw
dcl_temps 2
sample r0.xyzw, v0.xyxx, t0[0].xyzw, s0[5]
add r0.xyzw, r0.xyzw, v0.xyzw
ftou r1.x, r0.x
sample r1.xyzw, r0.xyxx, t2[r1.x + 0].xyzw, s0[5]
add r0.xyzw, r0.xyzw, r1.xyzw
ftou r1.xyz, r0.zyxz
imul null, r1.yz, r1.zzyz, l(0, 15, 3, 0)
iadd r1.y, r1.z, r1.y
iadd r1.x, r1.x, r1.y
sample r1.xyzw, r0.xyxx, t1[r1.x + 10].xyzw, s0[5]
add o0.xyzw, r0.xyzw, r1.xyzw
ret
// Approximately 12 instruction slots are used.

이제 각 셰이더 리소스 범위에 셰이더 바이트 코드에 고유한 ID(이름)가 있습니다. 예를 들어 tex1(t10) 텍스처 배열은 셰이더 바이트 코드에서 'T1'이 됩니다. 각 리소스 범위에 고유한 ID를 제공하면 다음 두 가지를 수행할 수 있습니다.

  • 명령에서 인덱싱되는 리소스 범위(dcl_resource_texture2d 참조)를 명확하게 식별합니다(샘플 명령 참조).
  • 선언에 특성 집합 연결(예: 요소 형식, 스트라이드 크기, 래스터 작업 모드 등)

범위의 ID는 HLSL 하한 선언과 관련이 없습니다.

리플렉션 리소스 바인딩(맨 위에 나열됨) 및 셰이더 선언 명령(dcl_*)의 순서는 HLSL 변수와 바이트 코드 ID 간의 대응을 식별하는 데 도움이 됩니다.

SM5.1의 각 선언 명령은 3D 피연산자를 사용하여 범위 ID, 하한 및 상한을 정의합니다. 레지스터 공간을 지정하기 위해 추가 토큰이 내보내집니다. 다른 토큰은 범위의 추가 속성을 전달하기 위해 내보내질 수 있습니다. 예를 들어 cbuffer 또는 구조적 버퍼 선언 명령은 cbuffer 또는 구조체의 크기를 내보냅니다. 인코딩의 정확한 세부 정보는 d3d12TokenizedProgramFormat.h 및 D3D10ShaderBinary::CShaderCodeParser찾을 수 있습니다.

SM5.1 지침은 명령의 일부로 추가 리소스 피연산자 정보를 내보내지 않습니다(SM5.0에서와 같이). 이 정보는 이제 선언 지침에 있습니다. SM5.0에서는 리소스 인덱싱을 할 때, 인덱싱이 선언과의 연관성을 모호하게 만들었기 때문에, 리소스 속성을 확장된 오퍼코드 토큰에 설명해야 했습니다. SM5.1에서 각 ID(예: 't1')는 필요한 리소스 정보를 설명하는 단일 선언과 명확하게 연결됩니다. 따라서 리소스 정보를 설명하는 지침에 사용되는 확장된 opcode 토큰은 더 이상 내보내지지 않습니다.

선언이 아닌 명령에서 샘플러, SRV 및 UAV에 대한 리소스 피연산자는 2D 피연산자입니다. 첫 번째 인덱스는 범위 ID를 지정하는 리터럴 상수입니다. 두 번째 인덱스는 인덱스의 선형화된 값을 나타냅니다. 값은 루트 서명과 더 잘 상관 관계를 지정하고 인덱스 조정의 드라이버 컴파일러 부담을 줄이기 위해 해당 레지스터 공간의 시작 부분(논리 범위의 시작 부분을 기준으로 하지 않음)을 기준으로 계산됩니다.

CBV의 리소스 피연산자는 범위의 리터럴 ID, 상수 버퍼의 인덱스, 상수 버퍼의 특정 인스턴스로 오프셋을 포함하는 3D 피연산자입니다.

예제 HLSL 선언

HLSL 프로그램은 루트 서명에 대해 아무것도 알 필요가 없습니다. 가상 "레지스터" 바인딩 공간에 바인딩을 할당하거나, SRV의 경우 t#, UV의 경우 u#, CBV의 경우 b#, 샘플러의 경우 s#을 할당하거나 컴파일러에 의존하여 할당을 선택하고 나중에 셰이더 리플렉션을 사용하여 결과 매핑을 쿼리할 수 있습니다. 루트 서명은 설명자 테이블, 루트 설명자 및 루트 상수를 이 가상 레지스터 공간에 매핑합니다.

다음은 HLSL 셰이더에 있을 수 있는 몇 가지 선언 예제입니다. 루트 서명 또는 설명자 테이블에 대한 참조는 없습니다.

Texture2D foo[5] : register(t2);
Buffer bar : register(t7);
RWBuffer dataLog : register(u1);

Sampler samp : register(s0);

struct Data
{
    UINT index;
    float4 color;
};
ConstantBuffer<Data> myData : register(b0);

Texture2D terrain[] : register(t8); // Unbounded array
Texture2D misc[] : register(t0,space1); // Another unbounded array 
                                        // space1 avoids overlap with above t#

struct MoreData
{
    float4x4 xform;
};
ConstantBuffer<MoreData> myMoreData : register(b1);

struct Stuff
{
    float2 factor;
    UINT drawID;
};
ConstantBuffer<Stuff> myStuff[][3][8]  : register(b2, space3)