구글 버그를 찾고 이슈를 제기했다 (feat. GPU 가속 렌더링)


블로그의 Card 형식 UI에 hover 애니메이션을 줬는데
뭔가 오류가 발생했다.
1. hover시 layout이 깨지는 현상
card에 hover시 translateY(-8px)
이런식으로 줬는데,
transition시 미세하게 div
크기가 늘어났다가 줄어든다.

도대체 왜일까? 분석을 해보았다.
div
크기가 변하는 시점이 transform 시작, transform 종료였다.
우선, transform을 주어 GPU 가속이 일어나기 때문에
CPU <-> GPU 레이어로 이동 시점(하드웨어 가속 시점)에서 문제가 발생하는 것 같았다.
그래서 사전에 레이어를 따로 분리시키면(승격시키면) 해결되지 않을까 싶어
will-change: transform
을 주었다.

위와 같이 카드별 레이어가 분리되었고, div
크기가 변하지 않고 transform
이 잘 동작했다.
2. 문제 파헤치기
그런데 뭔가 계속 찜찜함이 남았다.
지금껏 이런적이 없었는데 왜..?
수없이 transform translate를 사용해보면서 이런 적이 없었다.
문제의 정확한 원인을 찾고 싶어 좀 더 파헤쳐 보기로 했다.
그래서 will-change
적용 전/후로 개발자 도구의 Performance
탭을 확인해보았다.
-
will-change 적용 전
-
will-change 적용 후
위와 같이 will-change를 적용 후에는 Layout(Reflow) 과정이 일어나지 않는다.
will-change
속성 자체가 사전에 레이아웃을 미리 계산해 놓으니 당연한 것이지만
유일한 차이가 Layout
이므로
그렇다면 문제의 원인이 하드웨어 가속시 Layout
가 다시 일어나면서 생기는 것인가?
이것도 아니다. 이게 원인이라면 transform이 끝나는 시점에서도 Layout이 발생해야 하는데
발생하지 않았다.
3. 문제 재현
일단 혹시라도 내가 코드를 잘못 짠 것인지 해서 tailwind play에서 문제를 재현해보았다.

재현이 됐다. 그런데 이번엔 줄어들었다가 늘어난다.
뭐지? 싶어서 브라우저 width를 조정해보았다.
그런데 이번에는

늘어났다가 줄어든다.
또 어떤 경우는 크기 변화가 없다.
Box width를 확인해보니,
Box width | 크기 |
---|---|
줄어드는 경우 | 108.33px |
변화가 없는 경우 | 108px |
커지는 경우 | 108.67px |
처럼 요소 크기가 소숫점이고, 하드웨어 가속이 일어날 때 크기 변화가 생겼다.
그래서 픽셀이 반올림되나? 싶었지만, 변한 크기를 정확히 측정할 수 없었다.
정말 난해했다.
또한 브라우저/기기별 테스트도 진행했다.
Browser | 렌더링 엔진 | OS | 문제 발생 여부 |
---|---|---|---|
Chrome | Blink | window/mac/linux | O |
Safari | Webkit | window/mac/linux | X |
Firefox | Gecko | window/mac/linux | X |
위처럼 크로미움 기반의 브라우저에서는 모두 문제가 재현됐다.
4. 중간 정리
지금까지 정리하자면, chromium 기반 브라우저에서
요소 크기가 소숫점 단위이고, 합성 레이어로 승격되면
요소 크기가 바뀐다.
이다.
5. 1차 가설
일단 소숫점이 0.5 미만이면 줄어들고, 0.5보다 크면 커지는 것을 봐서는
요소 크기를 정수 픽셀로 재계산하는 것이라고 가설을 세웠다.
그래서 정확한 크기 차이를 알기 위해
일단 확실히 변하는지 getBoundingClientRect
을 이용해 크기 측정을 해보았다.

1번째는 초기 상태, 2번째는 hovering
상태에서 측정했으나,
사진과 같이 width
와 height
가 동일했다.
왜??? 분명 크기가 달랐는데?
내 눈이 이상한지 의심도 했다.
얼마 전 건강검진에서 양쪽 시력이 분명 1.5가 나왔었지만
일단 분명 육안상으로는 크기가 변하는데, "합성 레이어의 크기는 가져오지 못 하는 건가?"싶어

자로 재봤다..
잘못 본 게 아니었다. 실체 측정 사이즈로 1mm도 안 되지만 분명 변했다.
정확한 크기를 측정하기 위해 전/후 캡쳐 후
픽셀 단위로 확인해보았다.

element 크기 비율로 근삿값을 계산했을 때,
0.몇 픽셀 단위로 크기가 변하며, element 크기가 정수 픽셀로 변한다.
그렇게 나는 0.몇 픽셀도 구분하는 눈을 가지게 되었다.
브라우저에서 소숫점 단위 픽셀을 렌더링하는 것을 sub-pixel rendering
이라고 한다.
오랜 서칭 끝에 현재 상황과 가장 유사한 현상을 찾았다.
https://keithclark.co.uk/articles/gpu-text-rendering-in-webkit위는 텍스트 관련 anti aliasing
과 관련된 서브픽셀 렌더링
문제이다.
그러나, 내 문제는 anti aliasing 수준이 아니었다.
6. 가설 구체화
그렇게 도달한 가설이
하드웨어 가속시 **합성 레이어(GPU 레이어)**에서 크기 **정수 픽셀로 조정(내림, 반올림)**한다는 것이었다.

일단 Layer 탭에서 Layer Size는 전부 정수 사이즈로 되는 것을 확인할 수 있다.
문제는?
309.34px
이면 내림하여 309px
가 돼야 한다고 생각했다.
하지만 310px
로 Layer 사이즈가 측정됐다.
실제 렌더링 사이즈는 내림, 반올림을 하더라도 합성 Layer 사이즈는 잘림을 방지하기 위해 여유롭게 전부 올림처리를 하는 것인가?
일단 어느 정도 생각을 정리하고, 테스트를 더 해보았다.
7. 문제 재현2
현재 tailwind를 쓰고 있었기에, css로도 재현을 해보았다.
그런데 css로 재현했을 때는 아무 문제가 없었다.
"도대체 왜 tailwind랑 css랑 결과 차이가 날까?" 싶어
tailwind에서 변환된 css를 살펴보았다.
어?

-translate-y-2
가 transform: translateY(-0.5rem)
이 아니었다.
csstransform: translate(0, 0.5rem) rotate(0) skewX(0) skewY(0) scaleX(0) scaleY(0);
이렇게 변환됐다.
근데 그렇다 해도 문제가 없어야 하는 거 아닌가? 싶었지만,
일단 문제가 있으니..
테스트를 더 해봤다.
css | 문제 발생 여부 |
---|---|
translate | X |
rotate | O |
skew | O |
scale(0) | X |
scale | O |
translate + rotate
만 해도 문제가 생겼다.
이건 도대체 무슨 이유일까 싶어
수많은 공식문서를 찾아보았다.
transform에 어떤 것이 들어가도 행렬 계산을 통해 matrix
함수로 변환되기 때문에
rotate
, skew
, scale
이 포함되어도 문제가 없어야 했다.
이제 정말 답이 없었다.
translate만 사용해도 하드웨어 가속이 발생하는데,
translate만 사용했을 때는 문제가 없고,
rotate/skew/scale이 포함되면 문제가 생긴다는 것.
즉, 하드웨어 가속때문만은 아니란 것이었다.
CSS로도 translate + rotate(0)을 주면 문제가 재현됐다.
8. chromium 이슈 제기
요소 크기가 변하는 것이 서브픽셀 관련된 문제라는 것은 이해했다.
그런데 하드웨어 가속 중에서도 **(rotate/skew/scale)**이 들어갔을 때만 발생한다?
이건 그냥 답이 없었다.
그래서 크로미움에 이슈를 제기했다.
https://issues.chromium.org/issues/389341980답변은 다음과 같다.

하하 구글 크롬 버그였다!
9. 결론
결론은 chromium 기반 브라우저에서
요소 크기가 소숫점 단위이고,
rotate/skew/scale이 포함되었을 때, 합성 레이어로 승격되면
요소 크기가 바뀌는 현상이 있었고,
버그였다.
이 현상이 어떤 상황에서 발생하는지, 원인이 뭔지를 규명하는 과정이 너무 힘들었다.
버그였기에 원인을 명확히 규명하지는 못 했지만, 많은 분석과 테스트를 통해
문제를 정리할 수 있었고, 이슈 제기를 할 수 있었다.
10. (추가)타 사이트 동일 문제
추가로 퍼플렉시티에서도 동일한 버그가 발생한다!

빨리 고쳐줬으면 좋겠다.