TECHARTNOMAD | MAZELINE.TECH

TECH.ART.FLOW.IO

[번역] 고급 API 성능: 셰이더

jplee 2026. 7. 9. 11:44

이 글은 NVIDIA GPU에서 셰이더를 다룰 때 도움이 되는 실전 최적화 팁을 정리한 내용입니다. 애플리케이션에서 높고 안정적인 프레임 속도를 유지하고 싶다면 고급 API 성능 팁 시리즈도 함께 참고해 보세요.

셰이더는 그래픽 프로그래밍에서 빼놓을 수 없는 핵심 요소입니다. GPU 위에서 실행되며 버텍스, 픽셀, 기타 렌더링 데이터를 처리하고, 최종 화면이 어떻게 그려질지를 결정하는 여러 단계를 제어합니다.

이 글에서는 다음 셰이더 유형별로 권장 사항과 피해야 할 패턴을 정리합니다.

  • 일반 셰이더
  • 컴퓨트 셰이더
  • 픽셀 셰이더
  • 버텍스 셰이더
  • 지오메트리, 도메인, 헐 셰이더

일반 셰이더

먼저 모든 셰이더 유형에 공통으로 적용되는 기본 최적화 원칙부터 살펴보겠습니다.

권장 사항

  • 워프 내에서 서로 다른 주소를 읽는 상수 버퍼 뷰(CBV)나 즉시 상수 버퍼(ICB) 접근은 피하는 것이 좋습니다.
    • 상수 버퍼는 워프의 모든 스레드가 같은 데이터를 균일하게 읽을 때 가장 효율적입니다. 스레드마다 서로 다른 데이터를 읽어야 한다면 셰이더 리소스 뷰(SRV)를 사용하는 편이 더 적합합니다.
    • SRV가 CBV보다 더 나은 선택이 되는 대표적인 예는 다음과 같습니다.
      • 본 또는 스키닝 데이터
      • 미리 계산해 둔 난수 테이블 같은 룩업 테이블
  • 버퍼와 그룹 공유 메모리를 아끼기 위해 수동 비트 패킹을 활용하세요. 데이터 구조를 패킹할 때는 각 필드가 가질 수 있는 값의 범위를 먼저 확인하고, 그 범위를 담을 수 있는 가장 작은 데이터 타입을 선택하는 것이 좋습니다.
  • 런타임 동작 힌트를 활용해 제어 흐름을 최적화하세요.
    • 가능하다면 DXC에서는 -all-resources-bound 컴파일 플래그를, FXC에서는 D3DCOMPILE_ALL_RESOURCES_BOUND를 활성화하세요. 이렇게 하면 드라이버가 더 공격적인 최적화를 적용할 수 있습니다.
    • [FLATTEN]과 [BRANCH] 키워드는 상황에 맞게 전략적으로 사용하세요.
      • 조건부 분기는 컴파일러가 텍스처 페치처럼 지연 시간이 긴 명령을 미리 끌어올리는 것을 막을 수 있습니다.
      • [FLATTEN]을 사용하면 컴파일러가 조건식을 평가하기 전에 필요한 로드 작업을 먼저 시작할 수 있어, 대기 시간을 줄이는 데 도움이 됩니다.
  • 정적 데이터와 정적 디스크립터에는 Root Signature 1.1을 사용하세요. 셰이더 최적화에 더 유리한 정보를 드라이버에 제공할 수 있습니다.
  • 레지스터 사용량은 가능한 한 줄이세요. 레지스터를 너무 많이 사용하면 점유율이 낮아지고, 경우에 따라 레지스터 값이 메모리로 밀려나는 스필링이 발생할 수 있습니다.
  • 단일 채널 텍스처에서 2×2 쿼드 값을 읽어야 한다면 게더 명령을 사용하세요.
    • 연속적인 샘플 명령을 여러 번 호출하는 방식에 비해 지연 시간을 거의 4배까지 줄일 수 있습니다.
  • 원시 버퍼보다는 구조화된 버퍼를 우선적으로 고려하세요.
    • 구조화된 버퍼는 정렬 요구사항이 더 명확하기 때문에, 로드 명령을 더 효율적으로 스케줄링하기 쉽습니다.
  • 물리 시뮬레이션이나 디노이저처럼 수학 연산이 많은 셰이더에서는 exp, log, sin, cos, sqrt 같은 초월 함수 사용 비용을 주의해야 합니다. 필요하다면 수치 근사식이나 미리 계산해 둔 룩업 테이블을 사용하는 방법도 고려해 보세요.
  • 다음과 같은 경우에는 포인트 필터링을 사용하면 TEX 유닛 처리 속도 측면에서 유리할 수 있습니다.
    • 포인트 필터링만으로도 충분히 정확한 결과를 얻을 수 있는 저해상도 텍스처
    • 네이티브 해상도로 직접 접근하는 텍스처

권장하지 않음

  • 반정밀도 부동소수점이 항상 전체 정밀도보다 빠르다고 가정하지 마세요. 반대로 전체 정밀도가 항상 더 낫다고 단정하는 것도 좋지 않습니다.
    • NVIDIA Ampere GPU에서는 FP32와 FP16 명령의 효율이 거의 동일합니다. 오히려 두 형식 사이를 변환하는 과정에서 불필요한 오버헤드가 생길 수 있습니다.
    • 반면 NVIDIA Turing GPU에서는 FP16 연산을 FP32보다 2배 빠른 속도로 처리할 수 있습니다. 따라서 대상 GPU 아키텍처를 기준으로 판단해야 합니다.

컴퓨트 셰이더

컴퓨트 셰이더는 렌더링 전용 단계를 넘어, 데이터 처리, 시뮬레이션, 머신 러닝 등 다양한 범용 연산에 사용됩니다.

권장 사항

  • 스레드 간 통신이 필요하다면 그룹 공유 메모리보다 웨이브 내장 함수를 먼저 고려하세요.
    • 웨이브 내장 함수는 명시적인 스레드 동기화를 줄일 수 있어 코드와 실행 흐름이 더 단순해집니다.
    • SM 6.0부터 HLSL은 벤더별 확장 없이도 워프 전체에 적용되는 웨이브 내장 함수를 기본으로 지원합니다. 벤더 전용 API는 정말 필요한 경우에만 사용하는 편이 좋습니다. 자세한 내용은 HLSL의 GPU 내장 함수 활용을 참고하세요.
    • 원자적 연산이 많은 경우에는 웨이브 명령을 사용해 워프 단위로 연산을 합치면 원자적 처리량을 개선할 수 있습니다.
  • 전체 화면 컴퓨트 패스에서는 스레드 그룹 ID 스위즐링을 사용해 캐시 지역성을 높이세요. L1과 L2 캐시 히트율을 개선하는 데 도움이 됩니다.
  • 전체 화면 패스의 스레드 그룹 크기는 먼저 2~8개 워프 정도에서 시작해 보세요. 예를 들어 8×8×1 또는 16×16×1 같은 구성이 좋은 출발점이 될 수 있습니다. 이후에는 셰이더를 실제로 프로파일링하면서 크기를 조정하는 것이 가장 안전합니다.

권장하지 않음

  • 플랫폼과 GPU 아키텍처가 달라졌을 때 확장하기 어려운 스레드 그룹 크기를 고정적으로 설계하지 마세요.
    • Vulkan은 특수화 상수를 통해 파이프라인 생성 시점에 차원을 설정할 수 있지만, HLSL은 컴파일 시점의 스레드 그룹 크기를 요구합니다. 이 차이를 염두에 두고 설계해야 합니다.
  • 스레드 그룹 실행 자체에도 지연 시간이 있다는 점을 무시하지 마세요.
    • 조기 종료 조건이 자주 발생하는 컴퓨트 셰이더라면, 전체 스레드 그룹 실행 횟수를 줄이기 위해 더 큰 스레드 그룹 크기를 사용하는 편이 나을 수 있습니다.

픽셀 셰이더

픽셀 셰이더, 또는 프래그먼트 셰이더는 픽셀 단위로 색상과 효과를 계산하는 단계입니다.

권장 사항

  • 픽셀 셰이더 내부에서 직접 깊이 테스트를 구현하기보다는, 가능하면 깊이 경계 테스트 또는 스텐실 및 깊이 테스트를 활용하세요.
  • 깊이 및 스텐실 테스트는 16×16 래스터 타일 단위에서 시작해 개별 픽셀까지 빠르게 폐기할 수 있습니다. 가장 좋은 결과를 얻으려면 Early-Z가 동작하도록 유지하는 것이 중요합니다.
  • Early-Z를 비활성화할 수 있는 패턴에는 특히 주의하세요.
    • 조건부 z-쓰기, 클립, 폐기
      • 이런 경우에는 대안으로 널 블렌드 연산을 사용하는 방법도 검토해 볼 수 있습니다.
    • 픽셀 셰이더에서 직접 깊이 쓰기
    • UAV 리소스에 쓰기
  • 전체 화면 패스에서 워프 간 지연 시간 편차가 크다면, 픽셀 셰이더 대신 컴퓨트 셰이더로 옮기는 것도 고려해 보세요.

권장하지 않음

  • 래스터 순서 뷰(ROV) 기법을 과도하게 사용하지 마세요.
    • 순서를 보장하는 기능은 편리하지만, 그만큼 성능 비용이 따릅니다.
    • 고급 블렌딩 연산이나 원자적 연산 같은 다른 대안과 성능을 비교한 뒤 선택하는 것이 좋습니다.

버텍스 셰이더

버텍스 셰이더는 정점 단위로 데이터를 처리하고, 이후 렌더링 단계로 넘길 정보를 준비합니다.

권장 사항

  • 압축된 버텍스 포맷을 사용하세요. 메모리 대역폭을 줄이고 캐시 효율을 높이는 데 도움이 됩니다.
  • 스키닝 데이터에는 CBV보다 SRV를 사용하는 편이 좋습니다. 이렇게 하면 다이버전트 CBV 읽기에서 발생할 수 있는 비효율을 피할 수 있습니다.

지오메트리, 도메인 및 헐 셰이더

지오메트리, 도메인, 헐 셰이더는 테셀레이션을 통해 동적 표면과 객체를 만들고, 지오메트리를 제어하거나 평가하는 데 사용됩니다.

권장 사항

  • 가능하다면 기존 지오메트리, 도메인, 헐 셰이더 대신 NVIDIA Turing에서 지원하는 메시 셰이딩 기능을 사용하는 것이 좋습니다.
  • 다음 조건을 만족하면 빠른 지오메트리 경로를 활성화할 수 있습니다.
    • 고정 토폴로지: 정점 수가 변하지 않음
    • 고정 프리미티브 타입: 입력과 출력의 프리미티브 타입이 동일함
    • 불변 정점별 속성: 입력 정점 속성을 출력으로 그대로 복사함
    • 가변 프리미티브별 속성: 프래그먼트 셰이더로 넘길 프리미티브 단위 값을 하나 계산함. 예를 들어 삼각형 면적이 여기에 해당합니다.

감사의 말

Ryan Prescott, Ana Mihut, Katherine Sun, Ivan Fedorov에게 감사드립니다.