어제 방송중 게임이 크래시했던 원인 찾았다.
CPU코드에서 크래시한게 아니고 D3D런타임이나 드라이버, GPU하드웨어 큐에서 예외가 발생한 것인데 D3D Debug Runtime으로 돌리던게 아니었기 때문에 관련 에러 메시지는 전혀 확인할 수 없었다. 물론 덤프를 떠도 소용없지. CPU코드는 아무 문제 없이 잘 돌고 있었으니까.하루종일 고민 하다가 저녁먹고 본격적으로 뜯어보기 시작했는데 다행히 원인을 찾을 수 있었다.
[원인]
- 최근에 이펙트의 반투명 처리를 위해 ABuffer를 이용한 OIT 코드를 DXR렌더러에 추가했다.
- 이건 원래 D3D11 렌더러에서 최초 구현했었고 이후 D3D12렌더러에 포팅한 코드였다. DXR렌더러는 D3D12기반이므로 아무런 수정 없이 DXR렌더러에도 집어넣었다.
- GPU메모리를 아끼기 위해 최초 ABuffer의 fragment-link pool사이즈는 화면의 픽셀 사이즈만큼만 잡는다.
알파블랜딩될 픽셀이 잔뜩 추가되면 fragment-link pool의 메모리가 곧 소진된다. 이때 compute shader에서는 처리할수 있는 만큼만 처리하고 부족한 fragment-link개수를 UAV로 전달된 FailCounter메모리에 저장해둔다. - CPU측 OIT코드에서는 주기적으로 FailCounter를 확인해서 fragment-link pool이 부족한것으로 판명되면 fragment-link pool의 사이즈를 키워서 다시 생성한다. 여기서 fragment-link pool로 사용하고 있는 D3DResource에 대해서 Release()를 호출하게 된다.
- 여기서 문제 발생. 현재 D3D12/DXR렌더러는 2-3프레임을 중첩해서 렌더링하고있다. 즉 GPU는 이전 fragment-link pool의 D3DResource를 참조하고 있는데 그 상태에서 해당 D3DResource가 즉시 삭제된 것이다. 이것이 D3D runtime을 크래시하게 한것.
- 즉 이 문제는 DXR이전에 D3D12렌더러에서도 가지고 있던 잠재적 버그였다. 그럼 왜 지금껏 문제가 된적이 없는가? 당초 D3D12렌더러는 미래를 위한 프로토타입이었지 그걸로 게임을 출시할 생각까진 없었다. 그래서 중첩 렌더링 코드는 테스트만 해보고-그다지 효율적이지 않다는 판단 하에-주석처리 해놓고 있었다. 그래서 D3D12렌더러에서는 Present() 호출 후에 정확하게 렌더링이 완료되기를 기다렸다. 따라서 이 문제가 발생하지 않는다.
DXR렌더러에서 1프레임이라도 성능을 높이려고 쥐어짜다보니 중첩 렌더링을 적극 도입하게 됐다. 각잡고 튜닝을 해보니 중첩렌더링 덕에 성능 향상이 실제로 있었다. 그리고 이로 인해서 OIT코드의 버그가 드러날 환경이 갖춰졌다.
최근까지도 DXR렌더러에선 OIT를 아예 사용하지 않고 있었기 때문에 이 문제를 확인할 수 없었지만 방송을 쉬고 있던 3주 사이에 DXR렌더러에 OIT기능을 넣었다. 그게 어제 방송때 터져버린것이다.
처음엔 두 달쯤 전에 작업한 DefReleaseManage문제라고 생각하고 코드를 뒤져봤는데 코드상으론 문제가 없었다.
그동안 테스트를 수 만번도 더 했을텐데 이제서 티가 난것이 이상했다. 이게 안그래도 멀티스레드로 돌아가고 해서 이쪽 버그였으면 그 동안 티가 안났을리 없거든.
방송때 크래시하던 장면이 녹화가 되어있었으므로 화면을 유심히보니 로켓이 터지는 이펙트가 2개 이상 겹쳐진 상태에서 캐릭터가 부활할때 크래시하더라.
부활 이펙트에 알파가 잔뜩 들어있거든. 로켓 폭발 이펙트 몇 개가 겹쳐져있을때 fragment-link pool의 용량을 거의 소모했고, 부활 이펙트 뜨면서 fragment-link pool이 완전히 바닥난거지. 그래서 CPU측 코드에선 Release()호출하고 재생성을 시도한거고. GPU는 다음 프레임을 렌더링 하느라 fragment-link pool을 참조하고 있는데 CPU측에서 Release()했으니 바로 크래시.
증상 재현도 되고 원인도 알았으니 차 한잔 마시면서 코드 고치면 된다. 내내 신경쓰였는데 이제 발뻗고 자겠다.