This commit is contained in:
2025-10-13 22:55:54 +09:00
parent 146a861a2e
commit a41983ff65
11 changed files with 832 additions and 132 deletions

388
vav2/notes/gemini-anal1.txt Normal file
View File

@@ -0,0 +1,388 @@
✦ 알겠습니다. Vav2Player에서 화면이 검게 나오는 문제를 진단해 보겠습니다. 이 문제는 비디오 처리 파이프라인의 여러 단계에서 발생할 수 있습니다.
단계별로 나누어 원인을 좁혀가겠습니다.
진단 계획:
1. `VavCore` 디코딩 확인: VavCore가 비디오 파일을 성공적으로 열고, 디코더를 선택하고, 비디오 프레임을 디코딩하여 Vav2Player로 전달하는지
확인합니다.
2. `Vav2Player` 렌더링 확인: Vav2Player가 VavCore로부터 받은 프레임 데이터를 D3D12 텍스처로 올바르게 변환하고, 셰이더를 통해 색상 변환(YUV to
RGB)을 수행한 후 화면에 정상적으로 렌더링하는지 확인합니다.
예상 원인:
* 디코딩 실패: 적절한 디코더를 찾지 못하거나, 파일이 손상되었거나, 디코딩 과정에서 오류가 발생하여 유효한 프레임이 생성되지 않음.
* 데이터 전달 오류: 디코딩된 프레임 데이터가 VavCore에서 Vav2Player로 전달되지 않거나, 데이터 포인터가 유효하지 않음.
* 렌더링 파이프라인 문제:
* D3D12 리소스(텍스처) 생성 실패.
* CPU-GPU 데이터 복사 실패.
* YUV to RGB 변환 픽셀 셰이더 오류 (예: 항상 검은색을 출력).
* 렌더링 상태 또는 파이프라인 설정 오류.
첫 번째 단계: `VavCore`와 `Vav2Player`의 연동 지점 확인
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ x SearchText {"path":"D:\\Project\\video-av1\\vav2","pattern":"IVideoDecoder"} │
│ │
│ Path validation failed: Attempted path "D:\Project\video-av1\vav2" resolves outside the allowed workspace directories: │
│ D:\Project\video-av1\vav2\platforms\android │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
먼저 Vav2Player 코드에서 VavCore의 핵심 인터페이스인 IVideoDecoder 또는 VideoDecoderFactory를 사용하는 부분을 찾아, 디코딩 요청 및 결과 수신
과정에 로그를 추가하여 문제를 확인해 보겠습니다.
관련 파일을 찾기 위해 프로젝트 전체에서 IVideoDecoder를 검색하겠습니다.
✦ 경로 문제로 이전 명령이 실패했습니다. 현재 작업 디렉토리(D:\Project\video-av1\vav2\platforms\android)를 기준으로 다시 시도하겠습니다.
사용자께서 언급하신 vav2player는 WinUI3 애플리케이션이 아닌, 현재 경로에 있는 Android 애플리케이션(applications\vav2player)으로 보입니다.
따라서 Android 환경에 맞춰 진단을 진행하겠습니다.
Android 앱의 렌더링 파이프라인을 이해하기 위해, 관련 설계 문서를 먼저 읽어보겠습니다. 이 문서는 검은 화면 문제의 원인을 파악하는 데 중요한
단서가 될 수 있습니다.
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ✓ ReadFile \Android_GPU_Surface_Pipeline_Design.md │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
✦ 디자인 문서를 잘 읽어보았습니다. 문서에 따르면, 이 앱은 MediaCodec 디코더가 GPU 메모리(Vulkan Image)에 직접 프레임을 출력하고, Vulkan 렌더러가
이 메모리를 복사 없이 바로 화면에 그리는 'Zero-Copy' 파이프라인을 목표로 합니다.
핵심 진단 포인트:
이 방식은 매우 효율적이지만, 한 단계라도 실패하면 화면이 검게 나올 수밖에 없는 구조입니다. CPU를 이용한 예비 경로(Fallback)가 의도적으로
배제되었기 때문입니다.
문서에 명시된 몇 가지 잠재적인 실패 지점이 있습니다.
1. 하드웨어/드라이버 미지원: 기기가 MediaCodec에서 Vulkan 표면으로 직접 디코딩하는 것을 지원하지 않을 수 있습니다.
2. 초기화 실패: Vulkan 디바이스를 VavCore에 등록하는 과정(vavcore_set_vulkan_device)이 실패했을 수 있습니다.
3. 잘못된 프레임 타입: VavCore가 디코딩은 했지만, 예상된 Vulkan 표면 타입이 아닌 다른 포맷(예: CPU 메모리 버퍼)으로 프레임을 반환했을 수
있습니다.
4. Vulkan 렌더링 오류: RenderVulkanImage 함수에서 전달받은 Vulkan 이미지를 화면에 그리는 데 실패했을 수 있습니다.
다음 단계: 로그 확인을 위한 소스 코드 탐색
가장 확실한 방법은 문서에 언급된 오류 로그가 실제 로그캣(logcat)에 출력되는지 확인하는 것입니다. 그전에, 해당 로직을 담고 있는 핵심 소스 파일인
vavcore_vulkan_bridge.cpp가 어디에 있는지 찾아보겠습니다. 이 파일의 내용을 보면 현재 구현 상태를 더 정확히 파악할 수 있습니다.
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ✓ FindFiles '**/vavcore_vulkan_bridge.cpp' within . │
│ │
│ Found 1 matching file(s) │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
✦ 좋습니다. vavcore_vulkan_bridge.cpp 파일의 위치를 찾았습니다. 이제 이 파일의 내용을 읽어서 디자인 문서에 기술된 대로 오류 처리 및 로깅 로직이
실제로 구현되어 있는지 확인하겠습니다.
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ✓ ReadFile ...ons\main\cpp\vavcore_vulkan_bridge.cpp │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
✦ vavcore_vulkan_bridge.cpp 파일의 내용을 확인했습니다. 소스 코드는 이전에 검토한 디자인 문서와 정확히 일치하며, 파이프라인의 각 단계마다 상세한
로그를 기록하고 있습니다.
이제 검은 화면의 원인은 다음 중 하나일 가능성이 매우 높으며, 이는 Android의 logcat을 통해 확인할 수 있습니다.
가장 유력한 시나리오:
1. `GPU SURFACE NOT SUPPORTED` 오류: LoadVideoFile 함수에서 이 로그가 발견된다면, 현재 기기의 하드웨어나 드라이버가 VavCore가 요구하는
제로-카피(zero-copy) 디코딩을 지원하지 않는다는 의미입니다. 이것이 가장 가능성 있는 원인입니다.
2. `UNSUPPORTED SURFACE TYPE` 오류: ConvertVavCoreFrameToVulkan 함수에서 이 로그가 나온다면, 디코더는 생성되었지만 예상과 다른 종류의 비디오
프레임(예: CPU 메모리)을 반환하고 있다는 뜻입니다.
3. `Failed to render GPU surface frame` 오류: ProcessNextFrame 함수에서 이 로그가 보인다면, 디코딩은 성공했지만 Vulkan 렌더러가 프레임을
화면에 그리는 과정에서 실패했다는 의미입니다.
4. `Failed to register Vulkan device` 오류: InitializeVulkanRenderer 함수에서 이 로그가 보인다면, 초기화 단계에서 Vulkan과 VavCore 연동에
실패한 것입니다.
사용자께 요청:
이제 실제 기기에서 어떤 오류가 발생하는지 확인해야 합니다.
1. Android 기기를 PC에 연결하고, vav2player 앱을 실행하여 검은 화면이 나타나도록 하십시오.
2. 그 상태에서 터미널 또는 명령 프롬프트를 열고 아래 명령어를 실행하여 로그를 캡처해 주십시오. 이 명령어는 이번 문제와 관련된 세 가지 주요
태그(VavCoreVulkanBridge, VulkanRenderer, VavCore)의 로그만 필터링합니다.
1 adb logcat -s VavCoreVulkanBridge VulkanRenderer VavCore
3. 위 명령어를 실행한 후 출력되는 로그를 복사하여 여기에 붙여넣어 주십시오. 로그를 분석하여 정확한 원인을 진단하겠습니다.
> D:\Project\video-av1\time.log 파일로 테스트 로그를 기록해두었다. 로그를 분석해보고 원인을 찾아봐줘.
✦ 알겠습니다. D:\Project\video-av1\time.log 에 저장된 로그를 분석해달라고 하셨군요.
하지만 보안상의 이유로 제 작업 공간은 D:\Project\video-av1\vav2\platforms\android 디렉토리로 제한되어 있어, 상위 폴더에 있는 파일을 직접
읽을 수 없습니다.
번거로우시겠지만, D:\Project\video-av1\time.log 파일의 전체 내용을 복사하여 여기에 붙여넣어 주시겠어요? 로그 내용을 받으면 즉시 분석하여
문제의 원인을 찾아드리겠습니다.
> D:\Project\video-av1\vav2\platforms\android\time.log 로 옮겨두었다
✦ 알겠습니다. 파일을 작업 공간으로 옮겨주셔서 감사합니다. 이제 로그 파일을 읽고 분석하여 원인을 찾아보겠습니다.
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ✓ ReadFile time.log │
│ │
│ Read lines 1-2000 of 5603 from time.log │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
✦ 로그 파일을 성공적으로 읽고 분석했습니다.
로그 분석 결과:
놀랍게도, 로그 자체는 오류 없이 전체 파이프라인이 성공적으로 실행되고 있음을 보여줍니다.
1. 디코더 초기화 성공: c2.qti.av1.decoder (Qualcomm AV1 하드웨어 디코더)가 성공적으로 로드되고, Vulkan 표면(GPU 메모리) 출력을 지원하는 것으로
확인됩니다.
1 I/VavCoreVulkanBridge: GPU SURFACE SUPPORT VERIFIED
2 I/VavCoreVulkanBridge: Zero-copy MediaCodec → Vulkan pipeline ENABLED
2. 프레임 디코딩 성공: vavcore_decode_to_surface가 호출되어 프레임을 성공적으로 디코딩하고, surface_type: 7 (VULKAN_IMAGE)을 반환합니다. 이는
디코더가 GPU 메모리에 직접 프레임을 쓰고 있음을 의미합니다.
1 I/VavCore-MediaCodec: DecodeToSurface [Vulkan]: Frame 1 decoded via ImageReader → VkImage
3. Vulkan 렌더링 성공: 디코딩된 VkImage가 VulkanRenderer로 전달되고, 렌더러는 vkQueuePresentKHR 호출에 성공하며 프레임을 화면에 표시했다고
로그를 남깁니다.
1 I/VulkanRenderer: Frame presented successfully to screen
2 I/VulkanRenderer: RenderVulkanImage completed successfully
이 로그대로라면 비디오가 정상적으로 보여야 합니다. 하지만 화면이 검게 나온다는 것은, API 호출은 성공했지만 처리 과정의 데이터에 근본적인
문제가 있음을 시사합니다.
---
결정적 단서 및 원인 진단
로그에서 결정적인 단서를 찾았습니다.
1 I/VavCore-SurfaceManager: AHardwareBuffer Vulkan properties:
2 I/VavCore-SurfaceManager: format: 0
3 I/VavCore-SurfaceManager: CRITICAL FIX: Overriding VK_FORMAT_UNDEFINED to VK_FORMAT_G8_B8R8_2PLANE_420_UNORM (NV12)
이것이 문제의 핵심 원인입니다.
1. MediaCodec 디코더가 AHardwareBuffer(하드웨어 버퍼)에 프레임을 출력했습니다.
2. VavCore가 이 하드웨어 버퍼의 Vulkan 속성을 질의(vkGetAndroidHardwareBufferPropertiesANDROID)했을 때, Vulkan 드라이버가 이미지의 픽셀 포맷을
알려주지 않고 format: 0 (VK_FORMAT_UNDEFINED)을 반환했습니다.
3. 코드에서는 이 문제를 해결하기 위해 포맷을 VK_FORMAT_G8_B8R8_2PLANE_420_UNORM (일반적인 NV12 포맷)으로 강제로 가정하고 있습니다. (CRITICAL
FIX 로그)
결론:
Vulkan 드라이버가 픽셀 포맷 정보를 제대로 제공하지 않고 있으며, 코드에서는 이를 NV12 포맷일 것이라고 "추측"하고 있습니다. 하지만 실제
디코더가 출력한 포맷이 NV12가 아니거나, 드라이버와의 호환성 문제로 인해 데이터에 접근할 수 없는 상태일 가능성이 매우 높습니다.
결과적으로 Vulkan 렌더러는 유효하지 않거나 잘못된 형식의 데이터를 가지고 YUV->RGB 색상 변환을 시도하게 되고, 그 결과는 검은색 화면으로
나타나는 것입니다. 모든 API 호출이 성공했음에도 불구하고 내용물 없는 '빈 껍데기'만 렌더링되고 있는 상황입니다.
다음 단계:
이 문제는 VavCore의 VavCore-SurfaceManager가 MediaCodec의 출력 버퍼(AHardwareBuffer)를 처리하는 방식과 Vulkan 드라이버의 특정 동작 방식
사이의 호환성 문제입니다.
이 문제를 해결하려면 AHardwareBuffer의 실제 포맷을 알아내거나, ImageReader를 설정할 때 특정 포맷을 강제해야 합니다. 관련 코드를 좀 더 자세히
살펴보겠습니다. VavCore-SurfaceManager 관련 소스 파일을 찾아보겠습니다.
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ✓ FindFiles '**/VavCore-SurfaceManager.*' within . │
│ │
│ No files found │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
✦ VavCore-SurfaceManager라는 이름의 파일을 직접 찾는 데는 실패했습니다. 로그 태그와 실제 파일명이 다를 수 있습니다.
대신, 로그에 기록된 "VavCore-SurfaceManager"라는 문자열이 포함된 소스 파일을 직접 검색하여 이 로직이 어느 파일에 구현되어 있는지
찾아보겠습니다.
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ✓ SearchText 'VavCore-SurfaceManager' in **/*.{cpp,h} within ./ │
│ │
│ No matches found │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
✦ 검색 결과가 나오지 않는 것이 이상하네요. 로그에는 분명히 VavCore-SurfaceManager 태그가 존재하는데 소스 코드에서 해당 문자열을 찾을 수 없다는
것은, 이 로직이 현재 우리가 보고 있는 vav2player 애플리케이션 소스 코드 바깥에 존재할 가능성을 시사합니다.
가장 유력한 시나리오는 VavCore가 별도의 라이브러리로 빌드되고, vav2player는 이 라이브러리를 가져다 사용만 하는 구조라는 것입니다. 즉, 문제의
핵심인 VavCore-SurfaceManager의 소스 코드는 이 디렉토리에 없고, 미리 빌드된 libVavCore.so 파일 안에 포함되어 있을 것입니다.
가설 변경 및 새로운 접근:
문제의 원인이 VK_FORMAT_UNDEFINED를 NV12로 강제 오버라이드하는 부분이라는 점은 거의 확실합니다. VavCore 라이브러리의 소스 코드를 직접 수정할
수 없는 현 상황에서, 우리는 VavCore 라이브러리가 올바른 포맷을 인지하도록 다른 방법을 찾아야 합니다.
VavCore가 MediaCodec의 출력 포맷을 결정하는 데 영향을 줄 수 있는 부분은 vav2player 애플리케이션에서 MediaCodec을 설정하는 부분일 것입니다.
vavcore_vulkan_bridge.cpp 로그를 다시 살펴보면, MediaCodec을 설정하고 ImageReader에 연결하는 과정이 보입니다. 이 ImageReader를 생성할 때
출력 이미지 포맷을 명시적으로 지정할 수 있습니다. 만약 현재 ImageReader가 특정 포맷 지정 없이 생성되고 있다면, 드라이버가 기본 포맷(그리고
VK_FORMAT_UNDEFINED를 반환하는)을 사용하게 될 수 있습니다.
다음 단계:
vavcore_vulkan_bridge.cpp 또는 관련 JNI 코드에서 ImageReader를 생성하거나 MediaCodec을 설정하는 부분을 찾아, 이미지 포맷을 어떻게 설정하고
있는지 확인해야 합니다.
vavcore_vulkan_bridge.cpp 파일에서 ImageReader 또는 MediaCodec.configure와 관련된 부분을 찾아 분석해 보겠습니다. 다시 한번
vavcore_vulkan_bridge.cpp 파일을 읽고 해당 부분을 집중적으로 확인하겠습니다.
╭────────────────────────────────────────────────────────╮
│ ✓ ReadFile ...ons\main\cpp\vavcore_vulkan_bridge.cpp │
╰────────────────────────────────────────────────────────╯
✦ vavcore_vulkan_bridge.cpp 파일을 다시 검토했지만, 이 파일은 VavCore 라이브러리의 공개 API를 호출하는 역할만 할 뿐, ImageReader의 포맷을 직접
설정하는 부분은 포함하고 있지 않습니다. ImageReader 생성 및 설정은 libVavCore.so 라이브러리 내부에 캡슐화되어 있는 것으로 보입니다.
따라서 VavCore 라이브러리 소스 코드 없이는 ImageReader가 어떤 포맷으로 생성되는지 직접 수정하기는 어렵습니다.
새로운 가설: 셰이더 문제
문제의 원인이 VK_FORMAT_UNDEFINED를 NV12로 강제 변환하는 부분이라는 것은 거의 확실합니다. 그렇다면, 반대로 생각해볼 수 있습니다.
VulkanRenderer의 픽셀 셰이더가 NV12 포맷을 올바르게 처리하고 있는지 확인하는 것입니다.
만약 셰이더 코드에 버그가 있거나, NV12가 아닌 다른 포맷(예: RGBA)을 기대하고 있다면, NV12 데이터가 입력되었을 때 화면이 검게 나올 수
있습니다.
다음 단계: 렌더러 및 셰이더 코드 확인
VulkanRenderer의 소스 코드와 YUV를 RGB로 변환하는 GLSL 셰이더 코드를 찾아서 분석해 보겠습니다. 이를 통해 렌더러가 어떤 색상 포맷을 기대하고
있는지, 그리고 색상 변환 로직이 올바른지 확인할 수 있습니다.
먼저 VulkanRenderer의 소스 파일을 찾아보겠습니다.
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ✓ FindFiles '**/vulkan_renderer.*' within . │
│ │
│ Found 2 matching file(s) │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
✦ 좋습니다. vulkan_renderer 소스 파일들을 찾았습니다.
먼저 헤더 파일(vulkan_renderer.h)을 읽어서 RenderVulkanImage 함수의 인터페이스와 클래스의 전반적인 구조를 파악하겠습니다.
╭────────────────────────────────────────────────────────╮
│ ✓ ReadFile ...ons\app\src\main\cpp\vulkan_renderer.h │
╰────────────────────────────────────────────────────────╯
✦ 헤더 파일을 잘 분석했습니다. RenderVulkanImage라는 이름의 제로-카피(zero-copy) 렌더링 함수와, 별도의 Y, U, V 평면을 처리하는 CPU 기반 렌더링
함수들이 모두 선언되어 있군요.
이제 가장 중요한 vulkan_renderer.cpp 구현 파일을 읽어서, 셰이더 코드가 어떻게 작성되어 있고 RenderVulkanImage 함수가 외부 VkImage를 어떻게
처리하는지 직접 확인해 보겠습니다. 특히 YUV를 RGB로 변환하는 셰이더 코드와 vkCreateImageView 함수 호출 부분을 집중적으로 분석하겠습니다.
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ✓ ReadFile ...tions\src\main\cpp\vulkan_renderer.cpp │
│ │
│ Read lines 1-2000 of 2577 from applications/vav2player/app/src/main/cpp/vulkan_renderer.cpp │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
✦ vulkan_renderer.cpp 파일의 첫 부분을 분석한 결과, 흥미로운 점을 발견했습니다.
1. `#include "yuv_shaders.h"`: 셰이더 코드가 별도의 yuv_shaders.h 파일에 포함되어 있습니다. 이 파일을 분석하면 색상 변환 로직을 정확히 파악할
수 있습니다.
2. `RenderVulkanImage` 함수의 부재: 로그에서는 분명히 RenderVulkanImage 함수가 호출되고 있지만, 제가 읽은 파일의 첫 2000줄에는 해당 함수의
구현이 없습니다. 파일의 뒷부분에 있을 것으로 보입니다.
계속해서 vulkan_renderer.cpp 파일의 나머지 부분을 읽어서 RenderVulkanImage 함수의 실제 구현과, 비디오 렌더링에 사용되는 그래픽스 파이프라인
및 디스크립터 셋(descriptor set)이 어떻게 설정되는지 확인하겠습니다.
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ✓ ReadFile ...tions\src\main\cpp\vulkan_renderer.cpp │
│ │
│ Read lines 2001-2577 of 2577 from applications/vav2player/app/src/main/cpp/vulkan_renderer.cpp │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
✦ vulkan_renderer.cpp 파일의 나머지 부분을 모두 분석했으며, 결정적인 단서를 찾았습니다.
분석 결과
1. `RenderVulkanImage` 함수 구현 확인
파일 끝에서 RenderVulkanImage 함수의 구현을 찾았습니다. 이 함수는 입력받은 VkImage를 NV12 포맷(2-plane YUV)으로 간주하고, Y 평면과 UV
평면에 대해 각각 별도의 VkImageView를 생성합니다.
* Y 평면 (Luminance): VK_FORMAT_R8_UNORM 포맷의 뷰 생성
* UV 평면 (Chrominance): VK_FORMAT_R8G8_UNORM 포맷의 뷰 생성
이것은 NV12 포맷을 처리하는 표준적인 접근 방식이며, 이 부분의 코드는 올바르게 작성된 것으로 보입니다.
2. 치명적인 모순 발견
하지만 심각한 문제점을 발견했습니다. RenderVulkanImage 함수는 Y 평면과 UV 평면, 이렇게 2개의 텍스처를 셰이더에 전달하기 위해 디스크립터
셋(descriptor set)의 바인딩 0번과 1번을 업데이트합니다.
그러나 파이프라인을 생성하는 CreateDescriptorSetLayout 함수는 다음과 같이 바인딩을 전혀 만들지 않도록 코딩되어 있습니다.
1 // vulkan_renderer.cpp 내의 CreateDescriptorSetLayout 함수
2 bool VulkanVideoRenderer::CreateDescriptorSetLayout() {
3 LOGI("Creating descriptor set layout (no bindings - Hello Triangle style)...");
4
5 // No bindings for hardcoded triangle shader
6 VkDescriptorSetLayoutCreateInfo layoutInfo = {};
7 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
8 layoutInfo.bindingCount = 0; // <-- 바인딩 개수가 0입니다!
9 layoutInfo.pBindings = nullptr;
10 // ...
11 }
결론: 문제의 원인
이것이 바로 검은 화면의 원인입니다.
Vulkan 렌더링 파이프라인이 텍스처를 받을 준비가 전혀 되어 있지 않은 상태(바인딩 0개)에서, 렌더링 루프는 디코딩된 비디오 텍스처를
파이프라인에 전달하려고 시도(바인딩 2개 업데이트)하고 있습니다.
Vulkan의 유효성 검사 계층(validation layer)이 비활성화되어 있다면 이 API 호출이 오류를 뿜지 않고 조용히 실패할 수 있습니다. 결과적으로
셰이더는 샘플링할 텍스처를 전혀 받지 못하므로, 최종적으로 검은색만 출력하게 됩니다.
다음 단계: 셰이더 확인 및 수정 제안
이제 마지막 확인 단계로, 셰이더 코드가 실제로 2개의 텍스처(Y, UV)를 필요로 하는지 yuv_shaders.h 파일을 읽어 확인하겠습니다. 이 확인이
끝나면, CreateDescriptorSetLayout 함수를 수정하여 문제를 해결하는 코드를 제안해 드릴 수 있습니다.
yuv_shaders.h 파일을 읽겠습니다.
╭────────────────────────────────────────────────────────╮
│ ✓ ReadFile ...cations\app\src\main\cpp\yuv_shaders.h │
╰────────────────────────────────────────────────────────╯
✦ yuv_shaders.h 파일을 분석한 결과, 마지막 퍼즐 조각이 맞춰졌고, 동시에 또 다른 모순점을 발견했습니다.
최종 분석 및 원인 확정
1. 셰이더의 요구사항: 주석으로 포함된 GLSL 프래그먼트 셰이더 코드는 3개의 분리된 텍스처(yTexture, uTexture, vTexture)를 바인딩 0, 1, 2번에서
받을 것으로 기대하고 있습니다. 이는 Y, U, V 평면이 완전히 분리된 YUV420p 포맷을 위한 코드입니다.
2. 드러난 모순점 종합:
* 파이프라인 vs 렌더러: CreateDescriptorSetLayout 함수는 0개의 텍스처 바인딩을 설정합니다. 하지만 RenderVulkanImage 함수는 2개의
바인딩(Y, UV)을 사용하려고 합니다.
* 렌더러 vs 셰이더: RenderVulkanImage 함수는 2-plane NV12 포맷(Y 평면 1개, UV 평면 1개)에 맞춰 2개의 텍스처 뷰를 준비합니다. 하지만
프래그먼트 셰이더는 3-plane YUV420p 포맷을 위한 3개의 텍스처를 기대합니다.
결론: 현재 코드는 렌더링 파이프라인의 여러 단계에 걸쳐 서로 맞지 않는 약속을 하고 있습니다. 파이프라인은 텍스처를 받을 준비가 안 됐고, 설령
받는다고 해도 렌더러가 준비한 데이터와 셰이더가 기대하는 데이터의 형식이 다릅니다. 이로 인해 GPU는 아무것도 그리지 못하고 검은 화면만
출력하게 됩니다.
---
해결 방안
이 문제를 해결하려면 이 세 가지 구성요소(파이프라인, 렌더러, 셰이더)가 모두 NV12 포맷을 사용하도록 통일해야 합니다. RenderVulkanImage 함수가
이미 NV12를 기준으로 작성되어 있으므로, 나머지 두 부분을 여기에 맞추는 것이 가장 효율적입니다.
수정 계획:
1. `CreateDescriptorSetLayout` 수정: 2개의 텍스처(Y, UV)를 받을 수 있도록 바인딩 2개를 설정하는 코드로 교체합니다.
2. 프래그먼트 셰이더 교체: 3개의 텍스처(Y, U, V) 대신 2개의 텍스처(Y, UV)를 입력받아 색상 변환을 수행하는 올바른 NV12용 GLSL 셰이더 코드로
교체합니다.
먼저, 셰이더 코드가 들어있는 파일을 찾아야 합니다. yuv_shaders.h에 선언된 vertex_shader_spirv 변수가 정의된 파일을 검색하겠습니다.
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ✓ SearchText 'vertex_shader_spirv' in **/*.cpp within ./ │
│ │
│ Found 4 matches │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
✦ 좋습니다. 셰이더 코드가 yuv_shaders.cpp 파일에 정의되어 있는 것을 확인했습니다.
분석 결과, 문제는 세 부분에 걸쳐 있습니다.
1. 파이프라인 설정(`CreateDescriptorSetLayout`): 텍스처를 받을 준비(바인딩)가 전혀 되어있지 않습니다.
2. 렌더러-셰이더 바인딩 불일치: RenderVulkanImage 함수는 Y, UV 텍스처를 바인딩 0, 1번에 전달하려 하지만, 셰이더는 Y, U, V 텍스처를 바인딩 0,
1, 2번에서 기대합니다.
3. 셰이더 포맷 불일치: 렌더러는 2-plane NV12 포맷을 준비하지만, 셰이더는 3-plane YUV420p 포맷을 기대합니다.
이 문제들을 한 번에 해결하기 위해, NV12 포맷을 기준으로 코드를 통일하겠습니다.
수정 작업 계획:
1. `vulkan_renderer.cpp` 수정:
* CreateDescriptorSetLayout 함수를 3개의 바인딩(UBO, Y-sampler, UV-sampler)을 갖도록 수정합니다.
* CreateGraphicsPipeline 함수가 텍스처 좌표를 사용하는 정점(vertex) 속성을 올바르게 사용하도록 수정합니다.
* RenderVulkanImage 함수가 Y와 UV 텍스처를 올바른 바인딩 번호(1번, 2번)에 전달하도록 수정합니다.
2. `yuv_shaders.cpp` 수정:
* 프래그먼트 셰이더 코드를 2개의 텍스처(Y, UV)를 입력받아 NV12 포맷을 올바르게 처리하는 코드로 교체합니다.
먼저 1단계인 vulkan_renderer.cpp 파일 수정을 시작하겠습니다. 여러 부분을 수정해야 하므로, 가장 핵심적인 CreateDescriptorSetLayout 함수부터
교체하겠습니다.