Lobsters

When adding a fractional part to a number fixes your shader

숫자에 소수점을 더하니 셰이더가 고쳐졌다 — FXC 최적화 버그 추적기

저자가 만든 Voronoi 셰이더가 RTX 4070 Windows PC에서만 애니메이션이 끊기는 현상을 일주일간 추적한 디버깅 기록입니다. 원인은 GPU 드라이버가 아니라 ANGLE을 거쳐 DirectX 11의 FXC 컴파일러가 O3 최적화 과정에서 fract() 호출을 floor()로 잘못 최적화한 것이었으며, 리터럴 값을 398.0에서 398.1로 바꾸는 것만으로 회피할 수 있었습니다.

본문

뮤직비디오 배경으로 쓸 Voronoi 셰이더를 만들던 저자는 어느 날 다른 컴퓨터에서 작업하다가 애니메이션이 특정 시점마다 '딱딱 끊기는(stutter)' 현상을 발견합니다. 셰이더 자체는 단순합니다. 공간을 타일로 나누고 각 타일 안에 해시 기반 노이즈로 Voronoi 중심점을 생성한 뒤, 각 픽셀에서 가장 가까운 중심점까지의 거리로 셀을 결정하고, UV 워핑과 7색 팔레트 조회로 색을 입히는 구조입니다. 문제는 노이즈 입력을 만드는 vec2 v = o * 398.0 + vec2(iTime * 1.3, iTime * 1.4) 부분에서 발생했습니다.

다섯 대의 기기, 한 대만 이상하다

저자는 다섯 대의 기기에서 테스트했습니다. Intel 내장 GPU를 쓴 Linux 노트북 두 대, Google Pixel 9 Pro, RTX 2070이 달린 Linux PC는 모두 정상이었고, 유일하게 RTX 4070이 달린 Windows PC에서만 문제가 재현됐습니다. 처음에는 Nvidia 40xx 드라이버의 fract() 미스컴파일을 의심했고, LLM의 제안에 따라 fract(x)를 수학적으로 동일한 x - floor(x)로 바꾸자 실제로 문제가 사라졌습니다. 그러나 LLM들이 만든 최소 재현 셰이더는 전부 실패했고, fract가 특정 입력에서 쓰레기 값을 낸다는 가설은 성립하지 않았습니다. 진짜 단서는 따로 있었습니다. 노이즈 입력에 곱하는 스칼라를 398.0에서 398.1처럼 소수부가 있는 값으로만 바꿔도, 심지어 fract를 그대로 둔 채로도 문제가 사라진 것입니다.

RenderDoc으로 브라우저 프레임 캡처

저자는 RenderDoc으로 Chromium의 GPU 자식 프로세스를 캡처해 컴파일된 셰이더를 비교했습니다. 그런데 디스어셈블리가 ps_5_0, 즉 DirectX 11 포맷이었습니다. WebGL이 OpenGL ES로 돌아야 한다고 생각했지만, Windows의 Chrome과 Firefox는 ANGLE을 통해 GLSL을 HLSL로 트랜스파일한 뒤 DirectX로 렌더링하고 있었던 것입니다. --use-angle=vulkan으로 실행하면 ANGLE이 GLSL을 SPIR-V로 변환하는데, 이 경우 스터터가 재현되지 않았습니다. 이어서 --enable-angle-features=dumpTranslatedShaders로 ANGLE이 생성한 HLSL을 덤프해 확인했지만 HLSL은 GLSL과 거의 동일했고, 트랜스파일 단계의 문제도 아니었습니다.

범인은 FXC의 O3 최적화

네이티브 DirectX 11 래퍼에서 수동으로 변환한 HLSL로 버그를 재현한 저자는 최적화 플래그를 바꿔가며 테스트했습니다. D3DCOMPILE_SKIP_OPTIMIZATION에서는 정상, O3에서는 버그가 재현됐습니다. 디스어셈블리를 비교하니 차이가 명확했습니다. 최적화 없이는 floor와 frac 호출이 그대로 남는 반면, O3에서는 r3.xz = floor(r3.xz)처럼 값을 자기 자신의 floor로 재대입해 소수부를 통째로 버리는 코드가 생성됐습니다. FXC가 정수 리터럴 398.0이 곱해진 경로를 '소수부가 없는 값'으로 추론해 frac를 최적화로 제거한 것이며, 이 때문에 노이즈의 보간 가중치가 부드럽게 변하지 않고 순간이동하듯 튀면서 Voronoi 중심점이 끊긴 것입니다. 398.1을 곱하면 컴파일러가 그런 추론을 하지 못해 frac가 살아남습니다. FXC는 비공개 컴파일러라 내부를 확인할 수는 없었고, 저자는 최소 재현 셰이더를 아직 만들지 못했다며 그래픽스 프로그래머들의 후속 조사를 부탁하며 글을 마칩니다.

Lobsters 반응

댓글이 없습니다.

원문: CrociDB / 번역·요약: Trawling