[viz] Remove Capabilities::supports_surfaceless
Chromium의 디스플레이 컴포지터(viz)에서 의미를 잃은 capability 플래그 supports_surfaceless를 제거하고, 같은 사실을 표현하는 renderer_allocates_images로 통일한 코드 정리 작업입니다.
문제 설명
OutputSurface::Capabilities에는 같은 사실을 서로 다른 계층의 언어로 말하는 플래그가 두 개 있었습니다.
supports_surfaceless: "GL이 그릴 기본 프레임버퍼(FBO 0)가 없다" (EGL/GL 계층)renderer_allocates_images: "SkiaRenderer가 root render pass 버퍼를 직접 소유한다" (viz 계층)
기본 프레임버퍼가 없으면 렌더러가 직접 버퍼를 할당해 오버레이 plane으로 제출해야 하므로 두 조건은 같은 상황을 가리킵니다.
문제점 1. 이름이 실제 동작과 다름
주석은 "Whether the gpu supports surfaceless surface" 라고 쓰여 있어, GPU 드라이버에 확장 지원 여부를 조회하는 값처럼 보입니다.
하지만 output_presenter_gl.cc의 실제 코드는 조건문 없이 true를 대입하는 한 줄뿐입니다. 조회하는 코드가 없습니다. 이름은 "GPU 능력"을 말하는데 실제로는 "이 코드 경로를 쓰고 있다"는 표시였습니다.
문제점 2.두 값이 실제로 어긋나는 지점이 있음
Windows의 SkiaOutputDeviceDComp는 renderer_allocates_images만 켜고 supports_surfaceless는 켜지 않습니다. 여기서는 두 값이 다릅니다.
지금은 문제가 드러나지 않습니다. 이 플래그를 읽는 여섯 군데가 모두 !IS_WIN이나 IS_APPLE/IS_OZONE/IS_ANDROID 같은 플랫폼 가드 안에 있어서, Windows에서는 실행 자체가 되지 않기 때문입니다. 누군가 가드 없이 renderer_allocates_images 대신 supports_surfaceless를 읽는 코드를 새로 추가하면, Windows에서 컴파일 에러도 크래시도 없이 잘못된 분기를 타게 됩니다.
문제점 3. 이미 죽어 있던 코드
skia_output_device_gl.cc는 GLSurface에게 surfaceless인지 물어 이 플래그를 채우고 있었습니다. 그런데 같은 호출 흐름의 skia_output_surface_impl_on_gpu.cc에 "onscreen GLSurface는 절대 surfaceless가 아니다"라고 단언하는 DCHECK가 있었습니다. 즉 이 대입은 항상 false를 넣는 코드였고, 2019년에 달린 TODO 주석과 함께 방치돼 있었습니다.
배경
2022년 12월 CL 4062650이 surfaceless 프레젠테이션을 gl::GLSurface에서 gl::Presenter라는 별도 타입으로 분리하면서 "이 GLSurface가 surfaceless인가?"라는 런타임 질문 자체가 무의미해졌습니다. 이 플래그는 그때부터 사실상 죽어 있었고, Issue:40224327의 Capabilities 정리 시리즈에서 마지막으로 남은 항목이었습니다.
해결 내용
1. 두 플래그가 실제로 같은 값인지 확인
supports_surfaceless가 true가 되는 경로는 OutputPresenterGL과 OutputPresenterFuchsia 두 곳뿐인데, 이 둘의 InitializeCapabilities()를 호출하는 곳은 SkiaOutputDeviceBufferQueue 생성자 하나였습니다. 그리고 같은 생성자가 이미 renderer_allocates_images를 켜고 있었습니다. 즉, 한쪽이 참이면 다른 쪽도 반드시 참인 구조였습니다.
반대편에서는 SkiaOutputDeviceGL이 GLSurface에게 surfaceless인지 물어 이 플래그를 채우고 있었는데, 같은 파일 계열의 SkiaOutputSurfaceImplOnGpu가 "onscreen GLSurface는 절대 surfaceless가 아니다"라고 DCHECK로 단언하고 있었습니다. 그래서 이 대입은 항상 거짓이었음을 확인했습니다.
2. 두 플래그의 갈라지는 지점을 따라 안전성 확인
Windows의 SkiaOutputDeviceDComp는 renderer_allocates_images만 켜고 supports_surfaceless는 켜지 않아 두 값이 다릅니다. 이 비대칭이 문제가 되는지 확인해보았고 읽는 여섯 군데가 모두 !IS_WIN 또는 IS_APPLE/IS_OZONE/IS_ANDROID 가드 안에 있어 Windows에서는 애초에 실행되지 않았습니다.
또한, 플래그를 세팅하는 세 곳이 Windows에서 실행 가능한지도 확인했습니다. SkiaOutputDeviceGL의 대입은 IS_ANDROID 가드 안에 있고, OutputPresenterFuchsia는 Fuchsia 전용 빌드이며, OutputPresenterGL은 생성 지점 세 곳이 전부 Windows를 배제하는 분기 안에 있었습니다. 즉 Windows에서 이 플래그는 세팅 코드에 도달 자체를 하지 않아 항상 기본값 false 였고 교체로 인해 관측 가능한 동작 변화가 발생하지 않음을 확인했습니다.
3. 작업하며 까다로웠던 상황에 대한 도식화
image_transport_surface_android.cc에서 SurfacelessEGL을 반환하는 경로를 파악하는데 문제가 있었습니다.
설정한 전제는 "onscreen GLSurface는 절대 surfaceless가 아니다"였습니다. 이게 참이어야 SkiaOutputDeviceGL이 세팅하던 supports_surfaceless가 항상 false이고, 따라서 삭제해도 안전함을 인지했습니다. 그런데 Android 코드에서 이 전제를 깨뜨리는 분기(image_transport_surface_android.cc:89)를 발견했습니다.
ANGLE은 플랫폼별 그래픽 API(Windows→D3D11, macOS→Metal, Linux→Vulkan 등)로 번역해주는 오픈소스입니다. 그중 Null백엔드는 GL 호출을 받아들이고 검증만 한 뒤 아무것도 그리지 않는 테스트용 모드인데, 그릴 대상이 없으니 화면용 GLSurface를 만드는 함수가 SurfacelessEGL을 반환합니다.
문제는 이 함수가 onscreen 경로라는 점이었습니다. 그대로라면 IsSurfaceless()가 true가 되어 supports_surfaceless가 켜지고, 오버레이 프로세서 선택이 달라집니다. 즉 이 CL이 순수 리팩터링이 아니라 동작을 바꾸는 변경이 될 수 있기에 유의하여 조건 2개를 검증하였습니다. 이 분기에 들어가려면 두 조건이 동시에 성립해야 합니다.
조건1: ANGLE Null 백엔드가 켜져 있어야 한다
조건2: viz가 이 함수(화면용 GLSurface 생성)를 호출해야 한다각 조건을 누가 만족시키는지 따로 찾아봤습니다. 조건 1를 켜는 코드는 GL 명령 자체를 검사하는 unittest와 fuzzer 다섯 곳뿐이었습니다. viz를 아예 만들지 않고 있었습니다. 반대로, 조건 2를 부르는 코드는 viz 내부 에만 존재했습니다. ANGLE Null이 켜지는 상황에서는 viz가 등장하지 않고 viz가 이 함수를 부를 때는 ANGLE Null이 꺼져 있습니다. 두 조건의 교집합이 없어 이 분기는 실행될 수 없음을 파악했습니다.
따라서 이 변경이 순수 리팩토링 작업임을 확인한 상황에서 CL을 작성할 수 있었습니다.
테스트 방법
1. 단위 테스트 및 빌드
빌드방식은 작성을 생략하고 단위테스트를 진행했던 방법만 기재하겠습니다.
autoninja -C out/Default viz_unittests
./out/Default/viz_unittests결과: SUCCESS: all tests passed (2,364 / 2,364)
2. 정적 검사
git cl format
git cl presubmit 3. 검증하지 못한 영역 (09/05 시점)
제 환경인 macOS에서는 10개 수정 지점 중 6개만 컴파일됩니다. IS_OZONE / IS_ANDROID / IS_FUCHSIA 가드 안의 4곳은 CQ 트라이봇에서만 검증 가능할 것 같습니다.
배운 점
기술적 학습
- viz 렌더링 파이프라인: CompositorFrame → DrawQuad → render pass → 화면 출력까지의 흐름
- root render pass와 non-root의 차이와 onScreenBuffer와 offScreenBuffer를 통해 화면이 바뀌게 되는 상황 파악
- 오버레이 plane의 원리: 디스플레이 컨트롤러가 여러 버퍼를 하드웨어로 합성하면 GPU 개입 없이 비디오를 표시할 수 있고, 그러려면 렌더러가 버퍼를 소유해야 한다는 인과관계
- Chromium에서 "surface"가 최소 4가지 의미(viz Surface, gl::GLSurface, OS 네이티브 surface, viz::OutputSurface)로 쓰인다는 점
참고 자료
- Issue:358041224 — Remove OutputSurface::Capabilities::supports_surfaceless
- Issue:40224327 — OutputSurface::Capabilities 정리 umbrella bug
- CL:4062650 — Use new Presenter class in viz for surfaceless presentation