| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- vulkan
- scalar
- forward
- hzb
- Graphics
- Study
- SGPR
- RayTracing
- ShadowMap
- GPU
- wave
- optimization
- deferred
- rendering
- DirectX12
- DX12
- GPU Driven Rendering
- texture
- scattering
- atmospheric
- unrealengine
- 번역
- ue4
- SIMD
- Shadow
- UE5
- VGPR
- shader
- Wavefront
- Nanite
- Today
- Total
RenderLog
[번역]A Better Way to Scalarize a Shader 본문
개인 공부용으로 번역한 거라 잘못 번역된 내용이 있을 수 있습니다.
또한 원작자의 동의 없이 올려서 언제든 글이 내려갈 수 있습니다.
출처 : https://asawicki.info/news_1735_a_better_way_to_scalarize_a_shader
이 글은 고급 주제 입니다. 당신이 쉐이더를 쓰는 것과 low level의 작동을 알고 있고(vector vs scalar 레지스터들 같은) 스칼라화를 통해 어떻게 최적화 하는지 안다고 가정합니다. 모든 것의 시작은 텍스쳐나 버퍼 디스크립션 배열 요소를 동적인 인덱스 값으로 얻어야 하는 것에서 출발합니다 - 픽셀마다 다를 수 있습니다. 이것은 bindless-style 렌더링 혹은 여러 텍스쳐 레이어의 블랜딩에 유용합니다. HLSL 쉐이더에서 적절히 돌아가게 만들기 위해서, 우리는 pseudo-함수 NonUniformResourceIndex로 인덱스 연산을 감싸야합다. 나의 이전 글인 “Direct3D 12 - Watch out for non-uniform resource index!”를 보세요.
Texture2D g_Textures[] : register(t1); ... return g_Textures[NonUniformResourceIndex(textureIndex)].Load(pos);
대부분, 이것은 충분합니다. 드라이버의 마법으로 적절하게 동작하게 할 것입니다. 그러나 만약 textureIndex에 의존하는 당신의 로직이 단일 Load 나 SampleGrad 보다 더 복잡하다면, 즉 당신이 여러 텍스처를 샘플링 하거나 계산한다면(이것을 MyDynamicTextureIndexing 라 부릅시다), 이것은 아마 반복문과 HLSL Shader Model 6.0의 wave function을 사용하여 쉐이더를 직접 스칼라화 하는 것이 이득일 수 있습니다.)
Francesco Cifariello Ciardi의 “Intro to GPU Scalarization” (번역링크) 글 2번째 파트와 Michał Drobot의 “Improved Culling for Tiled and Clustered Rendering”에서 어떻게 스칼라화를 하는지 배웠습니다. 두 글은 아래와 같은 구현을 제안 하였습니다.
HLSL 코드:
// WORKING, TRADITIONAL
float4 color = float4(0.0, 0.0, 0.0, 0.0);
uint currThreadIndex = WaveGetLaneIndex();
uint2 currThreadMask = uint2(
currThreadIndex < 32 ? 1u << currThreadIndex : 0,
currThreadIndex < 32 ? 0 : 1u << (currThreadIndex - 32));
uint2 activeThreadsMask = WaveActiveBallot(true).xy;
while(any(currThreadMask & activeThreadsMask) != 0)
{
uint scalarTextureIndex = WaveReadLaneFirst(textureIndex);
uint2 scalarTextureIndexThreadMask = WaveActiveBallot(scalarTextureIndex == textureIndex).xy;
activeThreadsMask &= ~scalarTextureIndexThreadMask;
[branch]
if(scalarTextureIndex == textureIndex)
{
color = MyDynamicTextureIndexing(textureIndex);
}
}
return color;
활성 스레드들의 비트마스크와 연관되어 있습니다. 내가 이 코드를 처음 봤을 때, 왜 이 코드가 필요하지? 반복문을 계속 실행하고 싶은 스레드의 마스크들은 이미 쉐이더 컴파일러에 의해 유지됩니다. 현재 스레드의 textureIndex로 완료 했을 때 반복문에서 그냥 break;를 할수 없을까?!.그래서 작은 코드 조각을 썼습니다:
// BAD, CRASHES
float4 color = float4(0.0, 0.0, 0.0, 0.0);
while(true)
{
uint scalarTextureIndex = WaveReadLaneFirst(textureIndex);
[branch]
if(scalarTextureIndex == textureIndex)
{
color = MyDynamicTextureIndexing(textureIndex);
break;
}
}
return color;...그리고 내 GPU에 크래시가 발생했습니다. 먼저 나는 쉬이더 캄파일러에 버그가 있을 수 있다고 생각했습니다, 그러나 나는 helper lanes의 이슈를 언급한 footnote [2] in part 2 of the scalarization tutorial (번역링크) 를 떠올렸습니다. 쉐이더가 SIMT 방식으로 실행되었을 때, 개별 스레드(lanes)는 활성 혹은 비활성일 것입니다. 활성 lanes는 그들을 작업을 합니다. 삼각형의 경계에 있기 때문에 모든 lanes들을 사용하게 하는데 충분한 픽셀이 없을 때나 임시적으로 꺼져있는 경우(즉, if 문으로 인해 몇몇 스레드는 조건문 내로 들어가고 싶지 않는 경우)는 비활성 lanes는 처음부터 비활성일 것입니다. 그러나 픽셀 쉐이더에서 helper lanes라고 하는 3번째 종류의 lanes가 있습니다. 이것들은 Full 2x2 Quads가 코드를 항상 실행할 수 있게 하기 위해서 비활성 lanes 대신에 사용 됩니다, 이것은 도함수 ddx/ddy를 계산하기 위해서 필요합니다, 또한 옳바른 mip level을 계산하기위해서 텍스쳐 샘플링 할 때 명시적으로 수행됩니다. Helper lane은 코드를 실행합니다(활성 lane 처럼), 그러나 그것의 결과를 렌더타겟에 전달하진 않습니다(비활성 lane 처럼).
결과적으로, helper lanes는 wave functions에 기여하지 않습니다 - 그들은 비활성 lanes처럼 작동합니다. 당신은 여기서 문제를 이미 발견했나요? 위의 반복문의 내부에서, helper lane은 wave 범위내에서의 활성 lanes와 다르게 그들의 textureIndex를 가질 수 있습니다. 스칼라 방식에서 그것은 자신이 처리될 차례를 얻을 수 없기 때문에, 무한루프에 빠져들게되며, GPU 크래시를 유발합니다 (TDR)!
그리고 내 생각에: 내가 전체 루프 전에 Helper lane을 그냥 한번 비활성화 한다면? 그래서 아래의 쉐이더가 떠올랐습니다. 그것은 잘 동작하는 것 처럼 보입니다. 나는 그것이 시작할때 한번 스레드 비트마스크 연산을 하여서 더 적은 변수들을 GPU 레지스터에 저장하고 더 적은 연산을 매 반복문에서 계산하기 때문에 첫번째 해결책보다 더 낫다고 생각합니다. 이제 나는 내 아이디어에서 내가 보지 못한 잘 못된 것이 있는지? 또는 내가 스칼라화 쉐이더에 더 좋은 방법을 만들었는지? 를 생각해보고 있습니다.
// WORKING, NEW
float4 color = float4(0.0, 0.0, 0.0, 0.0);
uint currThreadIndex = WaveGetLaneIndex();
uint2 currThreadMask = uint2(
currThreadIndex < 32 ? 1u << currThreadIndex : 0,
currThreadIndex < 32 ? 0 : 1u << (currThreadIndex - 32));
uint2 activeThreadsMask = WaveActiveBallot(true).xy;
[branch]
if(any((currThreadMask & activeThreadsMask) != 0))
{
while(true)
{
uint scalarTextureIndex = WaveReadLaneFirst(textureIndex);
[branch]
if(scalarTextureIndex == textureIndex)
{
color = MyDynamicTextureIndexing(textureIndex);
break;
}
}
}
return color;
UPDATE 2020-10-28: 내 트윗에 이 주제에 관한 가치있는 댓글들이 있습니다. 그것을 확인 하는 것을 추천 합니다.
'Graphics > 참고자료' 카테고리의 다른 글
| [번역]Real-Time Computation of Dynamic Irradiance Environment Maps (0) | 2020.11.26 |
|---|---|
| [번역]WHAT IS SHADER OCCUPANCY AND WHY DO WE CARE ABOUT IT? (0) | 2020.11.13 |
| [번역] Effective Water Simulation from Physical Models (0) | 2020.09.22 |
| [번역]Real-Time Atmospheric Scattering (0) | 2020.08.22 |
| [번역] INTRO TO GPU SCALARIZATION – PART 2 -SCALARIZE ALL THE LIGHTS (0) | 2020.08.08 |