📌 1. 전체 구조를 이해하자: Dispatch와 Thread Group

Dispatch(5, 3, 2)
→ 총 30개의 Thread Group이 생성된다.
→ 각 Group은 우리가 numthreads(10, 8, 3)으로 지정한 대로 240개의 쓰레드를 포함한다.

즉, 총 30 × 240 = 7200개의 쓰레드가 동시에 동작할 수 있다는 말이다.

  • 각각의 작은 박스는 Thread Group
  • 그 안에 있는 작은 셀은 Thread(= 병사 한 명)

🧠 2. SV 시스템 값(System Values)의 정체

✅ SV_GroupID

내가 속한 그룹이 전체 그룹 중에서 몇 번째인지 3차원 좌표로 알려준다.

예: SV_GroupID = (2,1,0) → x=2, y=1, z=0 번째 그룹


✅ SV_GroupThreadID

하나의 그룹 내에서, 내가 어떤 위치의 쓰레드인지를 알려준다.

예: SV_GroupThreadID = (7,5,0)
→ 해당 그룹 내부에서 x=7, y=5, z=0 위치에 있는 쓰레드


✅ SV_DispatchThreadID

전체 그룹과 쓰레드를 포함한 절대 위치 ID
"전장 전체에서 나의 절대 좌표"

계산 공식:

SV_DispatchThreadID = SV_GroupID * numthreads + SV_GroupThreadID

예:

= (2,1,0) * (10,8,3) + (7,5,0)
= (2*10, 1*8, 0*3) + (7,5,0)
= (27,13,0)

✅ SV_GroupIndex

SV_GroupThreadID를 1차원 인덱스로 flatten(평탄화)한 값
그룹 내부에서 "몇 번째 쓰레드인가?"

계산 공식:

SV_GroupIndex = z * (x * y) + y * x + x
= SV_GroupThreadID.z * (numthreads.x * numthreads.y) +
  SV_GroupThreadID.y * numthreads.x +
  SV_GroupThreadID.x

예:
SV_GroupThreadID = (7,5,0), numthreads = (10,8,3)
SV_GroupIndex = 0 * 80 + 5 * 10 + 7 = 57


🔥 3. 왜 이게 중요한가?

이런 시스템 값은 데이터 분할의 기준이 된다.

예시 상황:

  • 이미지 필터링: 1 픽셀씩 병렬 연산
  • 행렬 곱셈: 각 셀 계산을 다른 쓰레드가 담당
  • 애니메이션 보간: 프레임 간 변화 계산

👉 어느 쓰레드가 어떤 데이터를 처리할지 "SV 값"을 기준으로 분배하면 된다.
→ 즉, 쓰레드마다 처리할 데이터 인덱스를 이걸로 계산하면 된다!


💡 실전 예제 1: Output 전용 RWByteAddressBuffer 사용

[numthreads(10, 8, 3)]
void CS(ComputeInput input)
{
    uint index = input.groupIndex;
    uint outAddress = index * 10 * 4; // 40 바이트

    Output.Store3(outAddress + 0, input.groupID);
    Output.Store3(outAddress + 12, input.groupThreadID);
    Output.Store3(outAddress + 24, input.dispatchThreadID);
    Output.Store(outAddress + 36, input.groupIndex);
}

🎯 핵심 포인트

  • outAddress = index * 구조체 크기
  • 주소 기반 접근이기 때문에 바이트 단위로 계산 필요
  • 쓰레드 수가 많아져도 각자의 메모리 공간만 쓴다면 충돌 없음 (병렬 안정성 확보)

🧪 실전 예제 2: Input + Output을 함께 사용하는 구조

ByteAddressBuffer Input;
RWByteAddressBuffer Output;

[numthreads(10,8,3)]
void CS(ComputeInput input)
{
    uint index = input.groupID.x * (10 * 8 * 3) + input.groupIndex;
    uint outAddress = index * 11 * 4; // 구조체 크기: 44바이트
    uint inAddress = index * 4;

    float value = asfloat(Input.Load(inAddress));

    Output.Store3(outAddress + 0, input.groupID);
    Output.Store3(outAddress + 12, input.groupThreadID);
    Output.Store3(outAddress + 24, input.dispatchThreadID);
    Output.Store(outAddress + 36, input.groupIndex);
    Output.Store(outAddress + 40, asuint(value));
}

→ 이렇게 하면 랜덤 값이 들어간 Input을 읽어서 Output에 기록하는 구조로 연산할 수 있다.


📋 결과 분석 방법

  1. CSV 파일로 로그 저장
  2. 엑셀에서 GroupID(X) 필드를 정렬해보면
    • 그룹 0의 쓰레드 240개 → 다음 240개는 그룹 1
    • GroupIndex는 0~239 사이 반복
  3. Value는 Input에서 전달된 랜덤 값이 각 쓰레드별로 기록됨

🤔 왜 numthreads를 꼭 3차원으로 쓸까?

  • 예를 들어 이미지 필터 작업은 2D 연산에 특화
  • 1D로 관리할 수도 있지만 복잡한 인덱스 계산 필요
  • SV_GroupThreadID.x, .y, .z만 가지고도 공간상 위치 파악 가능
    → 그래서 x,y,z로 나누면 더 직관적이고, 데이터 구조와 잘 맞음

⚠️ 제한 사항

  • numthreads(x,y,z)에서 총합은 1024 이하로 제한됨
    → 너무 많은 쓰레드는 그룹을 나눠서 Dispatch해야 함

✅ SV 값의 핵심

시스템 값설명
SV_GroupID내가 속한 그룹의 ID
SV_GroupThreadID그룹 내 쓰레드 위치
SV_DispatchThreadID전체 전역 위치
SV_GroupIndex그룹 내 1D 인덱스 (플랫)

이 값들을 잘 활용하면 복잡한 데이터 분할, 병렬 처리, 최적화된 GPU 연산이 가능하다.


profile
李家네_공부방

0개의 댓글