Files
video-v1/vav2/docs/working/Triple_Buffering_Refactoring_Design.md

1140 lines
37 KiB
Markdown
Raw Normal View History

2025-10-11 02:08:57 +09:00
# Triple Buffering Refactoring Design
**문서 작성일**: 2025-10-10
**작성자**: Claude Code
**상태**: 설계 완료, 구현 대기 중
## 📋 목차
1. [개요](#개요)
2. [현재 문제점](#현재-문제점)
3. [새로운 설계](#새로운-설계)
4. [구현 계획](#구현-계획)
5. [테스트 계획](#테스트-계획)
---
## 개요
### 목적
현재 staging texture 기반 복사 구조를 제거하고, 진정한 triple buffering 구조로 리팩토링하여 30fps 디코딩 + 60fps 렌더링을 안정적으로 지원
### 핵심 아이디어
- **Staging texture 제거**: 불필요한 복사 및 동기화 오버헤드 제거
- **명확한 버퍼 역할 분리**: Render, Decode, Idle 3가지 상태로 텍스처 관리
- **33ms 간격 프레임 전환**: 디코딩 완료 시 렌더링/디코딩 인덱스 동시 전환
### 기대 효과
- ✅ 코드 복잡도 감소 (staging texture 관련 ~200줄 제거)
- ✅ 명확한 소유권 (렌더링 중인 텍스처는 디코더가 건드리지 않음)
- ✅ 자연스러운 동기화 (33ms 간격으로 자동 동기화)
- ✅ NULL 텍스처 문제 근본 해결
---
## 현재 문제점
### 1. Staging Texture 기반 복사 구조의 문제
```
DecodeToSurface → texture[0/1/2] → CopyToStagingTexture → staging → Render
(33ms) (GPU copy) (읽기 전용)
```
**문제점**:
- **불필요한 복사**: GPU 메모리 간 복사 오버헤드
- **복잡한 동기화**: CopyToStagingTexture + WaitForCopyCompletion
- **애매한 소유권**: 원본 텍스처가 언제 재사용 가능한지 불명확
- **NULL 텍스처 버그**: Frame 19에서 texture[0]이 NULL이 되는 문제
### 2. 현재 코드의 동기화 구조
```cpp
// FrameProcessor.cpp (lines 107-139)
if (m_framesDecoded >= 16) {
ID3D12Resource* rgbaTexture = m_renderer->GetNextRGBATextureForCUDAInterop();
result = vavcore_decode_to_surface(player, VAVCORE_SURFACE_D3D12_RESOURCE, rgbaTexture, &vavFrame);
if (result == VAVCORE_SUCCESS) {
auto backend = m_renderer->GetRGBASurfaceBackend();
if (backend) {
// 문제: 여기서 staging으로 복사하는데 원본 텍스처 재사용 타이밍이 애매함
HRESULT hr = backend->CopyToStagingTexture(rgbaTexture);
hr = backend->WaitForCopyCompletion();
}
}
}
```
### 3. Triple Buffering 순환 문제
```
Frame 16: texture[0] → staging → render
Frame 17: texture[1] → staging → render (33ms)
Frame 18: texture[2] → staging → render (33ms)
Frame 19: texture[0] 재사용 시도 → NULL ❌
```
---
## 새로운 설계
### 1. Triple Buffering 구조
#### 버퍼 상태 정의
```
texture[0]: RENDERING (현재 화면에 출력 중)
texture[1]: IDLE (대기 중, 이미 디코딩 완료)
texture[2]: DECODING (현재 디코딩 작업 중)
```
#### 프레임 전환 시퀀스
```
초기화 단계 (Frames 0-15):
DecodeToSurface(NULL) × 16번 → VavCore 내부 CUDA DPB 채우기
Triple Buffer 채우기 (Frames 16-18):
Frame 16: DecodeToSurface → texture[0]
Frame 17: DecodeToSurface → texture[1]
Frame 18: DecodeToSurface → texture[2]
정상 디코딩/렌더링 (Frame 19+):
[State: R=0, D=0]
Render: texture[0] 화면 출력 (60fps로 여러 번)
33ms 후 디코딩 완료...
[State: R=1, D=1]
AdvanceFrame() 호출:
- m_renderTextureIndex = 1 (texture[1]로 렌더링 전환)
- m_decodeTextureIndex = 1 (texture[1]을 다음 디코딩 타겟으로)
DecodeToSurface → texture[0] 덮어쓰기 (이제 안전)
Render: texture[1] 화면 출력
33ms 후 디코딩 완료...
[State: R=2, D=2]
AdvanceFrame() 호출:
- m_renderTextureIndex = 2
- m_decodeTextureIndex = 2
DecodeToSurface → texture[1] 덮어쓰기
Render: texture[2] 화면 출력
```
### 2. 클래스 구조 변경
#### RGBASurfaceBackend.h 변경사항
```cpp
class RGBASurfaceBackend : public IVideoBackend {
public:
// 삭제될 메서드
// ❌ ID3D12Resource* GetNextVideoTexture();
// ❌ ID3D12Resource* GetStagingTexture() const;
// ❌ HRESULT CopyToStagingTexture(ID3D12Resource* sourceTexture);
// ❌ HRESULT WaitForCopyCompletion();
// 새로운 메서드
// ✅ GetCurrentRenderTexture() - 렌더링에서 사용
ID3D12Resource* GetCurrentRenderTexture() const {
return m_rgbaTextures[m_renderTextureIndex].Get();
}
// ✅ GetNextDecodeTexture() - 디코딩에서 사용
ID3D12Resource* GetNextDecodeTexture() const {
return m_rgbaTextures[m_decodeTextureIndex].Get();
}
// ✅ AdvanceFrame() - 33ms마다 호출 (디코딩 완료 시)
void AdvanceFrame() {
// 렌더링을 다음 텍스처로 전환
m_renderTextureIndex = (m_renderTextureIndex + 1) % BUFFER_COUNT;
// 디코딩도 다음 텍스처로 전환 (이전 렌더링 텍스처를 덮어씀)
m_decodeTextureIndex = (m_decodeTextureIndex + 1) % BUFFER_COUNT;
LOGF_INFO("[RGBASurfaceBackend] AdvanceFrame: render=%d, decode=%d",
m_renderTextureIndex, m_decodeTextureIndex);
}
// ✅ GetRenderTextureIndex() - 디버깅용
int GetRenderTextureIndex() const { return m_renderTextureIndex; }
int GetDecodeTextureIndex() const { return m_decodeTextureIndex; }
private:
// 삭제될 멤버 변수
// ❌ ComPtr<ID3D12Resource> m_stagingTexture;
// ❌ ComPtr<ID3D12CommandAllocator> m_copyCommandAllocator;
// ❌ ComPtr<ID3D12GraphicsCommandList> m_copyCommandList;
// ❌ ComPtr<ID3D12Fence> m_copyFence;
// ❌ UINT64 m_copyFenceValue = 0;
// ❌ HANDLE m_copyFenceEvent = nullptr;
// ❌ bool m_firstCopy = true;
// ❌ int m_currentTextureIndex = 0;
// 새로운 멤버 변수
// ✅ 렌더링용 텍스처 인덱스 (현재 화면에 출력 중)
int m_renderTextureIndex = 0;
// ✅ 디코딩용 텍스처 인덱스 (다음 디코딩 타겟)
int m_decodeTextureIndex = 0;
// 기존 유지
ComPtr<ID3D12Resource> m_rgbaTextures[BUFFER_COUNT]; // 3개 텍스처 유지
};
```
#### FrameProcessor.cpp 변경사항
```cpp
bool FrameProcessor::ProcessFrame(VavCorePlayer* player,
std::function<void(bool success)> onComplete)
{
// ... (기존 초기 검증 코드 유지)
VavCoreVideoFrame vavFrame = {};
VavCoreResult result;
if (m_decoderType == VAVCORE_DECODER_DAV1D) {
// DAV1D: CPU 디코딩 (기존 로직 유지)
result = vavcore_decode_next_frame(player, &vavFrame);
if (result == VAVCORE_SUCCESS) {
vavFrame.surface_type = VAVCORE_SURFACE_CPU;
}
} else {
// NVDEC/Hardware: D3D12 surface decoding with triple buffering
// Phase 1: Initial 16-frame buffering (NULL surface)
if (m_framesDecoded < 16) {
LOGF_DEBUG("[FrameProcessor] Initial buffering phase: frame %llu/16", m_framesDecoded.load());
result = vavcore_decode_to_surface(player, VAVCORE_SURFACE_D3D12_RESOURCE, nullptr, &vavFrame);
}
// Phase 2: Fill triple buffer (frames 16, 17, 18)
else if (m_framesDecoded < 19) {
LOGF_DEBUG("[FrameProcessor] Filling triple buffer: frame %llu/19", m_framesDecoded.load());
auto backend = m_renderer->GetRGBASurfaceBackend();
ID3D12Resource* decodeTexture = backend->GetNextDecodeTexture();
result = vavcore_decode_to_surface(player, VAVCORE_SURFACE_D3D12_RESOURCE, decodeTexture, &vavFrame);
if (result == VAVCORE_SUCCESS) {
// Triple buffer 채우기 완료 시 인덱스 전진
backend->AdvanceFrame();
}
}
// Phase 3: Normal decoding (frame 19+)
else {
auto backend = m_renderer->GetRGBASurfaceBackend();
// 다음 디코딩용 텍스처 가져오기 (현재 렌더링 중이 아닌 텍스처)
ID3D12Resource* decodeTexture = backend->GetNextDecodeTexture();
LOGF_DEBUG("[FrameProcessor] Normal decoding: decode_idx=%d, render_idx=%d",
backend->GetDecodeTextureIndex(), backend->GetRenderTextureIndex());
result = vavcore_decode_to_surface(player, VAVCORE_SURFACE_D3D12_RESOURCE, decodeTexture, &vavFrame);
if (result == VAVCORE_SUCCESS) {
// 디코딩 완료 - 렌더링/디코딩 인덱스 전환
backend->AdvanceFrame();
LOGF_INFO("[FrameProcessor] Frame decoded, advanced to render_idx=%d",
backend->GetRenderTextureIndex());
}
}
}
// Phase 1 & 2: Buffering 단계는 렌더링 안 함
if (m_framesDecoded < 19) {
if (result == VAVCORE_PACKET_ACCEPTED || result == VAVCORE_SUCCESS) {
m_framesDecoded++;
m_frameProcessing.store(false);
if (onComplete) onComplete(true);
return true;
}
}
// Phase 3: 정상 렌더링 (기존 로직 유지)
if (result != VAVCORE_SUCCESS) {
// ... (기존 에러 처리)
}
m_framesDecoded++;
// 렌더링 큐에 추가 (기존 로직 유지)
bool enqueued = m_dispatcherQueue.TryEnqueue([this, vavFrame, onComplete, player, processStart]() {
// ... (기존 렌더링 로직)
});
return true;
}
```
#### D3D12VideoRenderer.cpp 변경사항
```cpp
HRESULT D3D12VideoRenderer::RenderVideoFrame(const VavCoreVideoFrame& frame, VavCorePlayer* player)
{
// ... (기존 초기 검증 코드)
if (frame.surface_type == VAVCORE_SURFACE_D3D12_RESOURCE) {
auto backend = m_rgbaSurfaceBackend.get();
if (!backend) {
return E_NOT_VALID_STATE;
}
// 현재 렌더링용 텍스처 사용 (이미 디코딩 완료된 안정적인 텍스처)
ID3D12Resource* renderTexture = backend->GetCurrentRenderTexture();
if (!renderTexture) {
LOGF_ERROR("[D3D12VideoRenderer] Current render texture is NULL!");
return E_INVALIDARG;
}
LOGF_DEBUG("[D3D12VideoRenderer] Rendering texture at index %d",
backend->GetRenderTextureIndex());
// SRV 업데이트 (렌더링용 텍스처로)
UpdateSRVForTexture(renderTexture);
// 렌더링 수행 (기존 RenderToBackBuffer 로직)
hr = backend->RenderToBackBuffer(frame, backBuffer, commandList.Get(), rtvHandle);
}
// ... (나머지 기존 로직)
}
```
### 3. CreateSrvHeap() 수정
```cpp
HRESULT RGBASurfaceBackend::CreateSrvHeap() {
// SRV를 동적으로 업데이트하거나, 3개 텍스처 모두 SRV 생성 후 인덱스로 선택
// Option A: 단일 SRV, 매 프레임 업데이트 (간단)
D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc = {};
srvHeapDesc.NumDescriptors = 1;
srvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
srvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
HRESULT hr = m_device->CreateDescriptorHeap(&srvHeapDesc, IID_PPV_ARGS(&m_srvHeap));
if (FAILED(hr)) return hr;
// 초기에는 texture[0]의 SRV 생성
UpdateSRVForCurrentRenderTexture();
return S_OK;
}
// 새로운 헬퍼 메서드
HRESULT RGBASurfaceBackend::UpdateSRVForCurrentRenderTexture() {
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = 1;
CD3DX12_CPU_DESCRIPTOR_HANDLE srvHandle(m_srvHeap->GetCPUDescriptorHandleForHeapStart());
m_device->CreateShaderResourceView(
m_rgbaTextures[m_renderTextureIndex].Get(),
&srvDesc,
srvHandle
);
return S_OK;
}
```
---
## 구현 계획
### Phase 1: RGBASurfaceBackend 리팩토링
#### Step 1.1: 멤버 변수 정리
- [ ] `RGBASurfaceBackend.h`: Staging texture 관련 멤버 변수 제거
- [ ] `RGBASurfaceBackend.h`: 새로운 인덱스 변수 추가 (`m_renderTextureIndex`, `m_decodeTextureIndex`)
- [ ] `RGBASurfaceBackend.h`: 새로운 메서드 선언 추가
**파일**: `D:\Project\video-av1\vav2\platforms\windows\applications\vav2player\Vav2Player\src\Rendering\RGBASurfaceBackend.h`
**제거할 코드** (lines 84-96):
```cpp
// ❌ 제거
ComPtr<ID3D12Resource> m_stagingTexture;
ComPtr<ID3D12CommandAllocator> m_copyCommandAllocator;
ComPtr<ID3D12GraphicsCommandList> m_copyCommandList;
ComPtr<ID3D12Fence> m_copyFence;
UINT64 m_copyFenceValue = 0;
HANDLE m_copyFenceEvent = nullptr;
int m_currentTextureIndex = 0;
bool m_firstCopy = true;
```
**추가할 코드**:
```cpp
// ✅ 추가
int m_renderTextureIndex = 0; // Current texture being rendered
int m_decodeTextureIndex = 0; // Next texture for decoding
```
#### Step 1.2: 메서드 시그니처 변경
- [ ] `RGBASurfaceBackend.h`: 삭제될 메서드 제거 선언
- [ ] `RGBASurfaceBackend.h`: 새로운 메서드 추가 선언
**파일**: `D:\Project\video-av1\vav2\platforms\windows\applications\vav2player\Vav2Player\src\Rendering\RGBASurfaceBackend.h`
**제거할 메서드 선언** (lines 45-56):
```cpp
// ❌ 제거
ID3D12Resource* GetNextVideoTexture();
HRESULT CopyToStagingTexture(ID3D12Resource* sourceTexture);
HRESULT WaitForCopyCompletion();
ID3D12Resource* GetStagingTexture() const;
int GetCurrentTextureIndex() const;
```
**추가할 메서드 선언**:
```cpp
// ✅ 추가
ID3D12Resource* GetCurrentRenderTexture() const;
ID3D12Resource* GetNextDecodeTexture() const;
void AdvanceFrame();
int GetRenderTextureIndex() const;
int GetDecodeTextureIndex() const;
HRESULT UpdateSRVForCurrentRenderTexture();
```
#### Step 1.3: 구현 파일 수정
- [ ] `RGBASurfaceBackend.cpp`: `CreateVideoTexture()` 수정 (staging texture 생성 제거)
- [ ] `RGBASurfaceBackend.cpp`: `Shutdown()` 수정 (staging texture 정리 제거)
- [ ] `RGBASurfaceBackend.cpp`: 삭제될 메서드 구현 제거
- [ ] `RGBASurfaceBackend.cpp`: 새로운 메서드 구현 추가
**파일**: `D:\Project\video-av1\vav2\platforms\windows\applications\vav2player\Vav2Player\src\Rendering\RGBASurfaceBackend.cpp`
**수정 범위**:
- Lines 80-223: `CreateVideoTexture()` - staging texture 생성 코드 제거
- Lines 44-78: `Shutdown()` - staging texture 정리 코드 제거
- Lines 608-627: `GetNextVideoTexture()` - 완전 제거
- Lines 628-696: `CopyToStagingTexture()` - 완전 제거
- Lines 698-744: `WaitForCopyCompletion()` - 완전 제거
**새로 추가할 구현**:
```cpp
ID3D12Resource* RGBASurfaceBackend::GetCurrentRenderTexture() const {
return m_rgbaTextures[m_renderTextureIndex].Get();
}
ID3D12Resource* RGBASurfaceBackend::GetNextDecodeTexture() const {
return m_rgbaTextures[m_decodeTextureIndex].Get();
}
void RGBASurfaceBackend::AdvanceFrame() {
int prevRender = m_renderTextureIndex;
int prevDecode = m_decodeTextureIndex;
m_renderTextureIndex = (m_renderTextureIndex + 1) % BUFFER_COUNT;
m_decodeTextureIndex = (m_decodeTextureIndex + 1) % BUFFER_COUNT;
LOGF_INFO("[RGBASurfaceBackend] AdvanceFrame: render %d->%d, decode %d->%d",
prevRender, m_renderTextureIndex, prevDecode, m_decodeTextureIndex);
// Update SRV to point to new render texture
UpdateSRVForCurrentRenderTexture();
}
int RGBASurfaceBackend::GetRenderTextureIndex() const {
return m_renderTextureIndex;
}
int RGBASurfaceBackend::GetDecodeTextureIndex() const {
return m_decodeTextureIndex;
}
HRESULT RGBASurfaceBackend::UpdateSRVForCurrentRenderTexture() {
if (!m_srvHeap || !m_rgbaTextures[m_renderTextureIndex]) {
return E_NOT_VALID_STATE;
}
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = 1;
CD3DX12_CPU_DESCRIPTOR_HANDLE srvHandle(m_srvHeap->GetCPUDescriptorHandleForHeapStart());
m_device->CreateShaderResourceView(
m_rgbaTextures[m_renderTextureIndex].Get(),
&srvDesc,
srvHandle
);
LOGF_DEBUG("[RGBASurfaceBackend] Updated SRV for render texture[%d]", m_renderTextureIndex);
return S_OK;
}
```
#### Step 1.4: CreateVideoTexture() 간소화
- [ ] Staging texture 생성 코드 제거 (lines 143-223)
- [ ] Copy command allocator/list 생성 제거
- [ ] Copy fence 생성 제거
**수정 후 CreateVideoTexture() 구조**:
```cpp
HRESULT RGBASurfaceBackend::CreateVideoTexture(uint32_t width, uint32_t height) {
LOGF_INFO("[RGBASurfaceBackend] CreateVideoTexture called: %ux%u", width, height);
m_videoWidth = width;
m_videoHeight = height;
HRESULT hr = S_OK;
// Create RGBA texture descriptor for CUDA Surface Object write
D3D12_RESOURCE_DESC rgbaTextureDesc = {};
// ... (기존 descriptor 설정)
D3D12_HEAP_PROPERTIES defaultHeapProps = {};
defaultHeapProps.Type = D3D12_HEAP_TYPE_DEFAULT;
// Create triple-buffered textures
for (int i = 0; i < BUFFER_COUNT; i++) {
hr = m_device->CreateCommittedResource(
&defaultHeapProps,
D3D12_HEAP_FLAG_SHARED,
&rgbaTextureDesc,
D3D12_RESOURCE_STATE_COMMON,
nullptr,
IID_PPV_ARGS(&m_rgbaTextures[i])
);
if (FAILED(hr)) {
LOGF_ERROR("[RGBASurfaceBackend] Failed to create RGBA texture[%d]: 0x%08X", i, hr);
for (int j = 0; j < i; j++) {
m_rgbaTextures[j].Reset();
}
return hr;
}
LOGF_INFO("[RGBASurfaceBackend] Created RGBA texture[%d]: %p", i, m_rgbaTextures[i].Get());
}
m_renderTextureIndex = 0;
m_decodeTextureIndex = 0;
LOGF_INFO("[RGBASurfaceBackend] All %d RGBA textures created successfully", BUFFER_COUNT);
// Create SRV for rendering
hr = CreateSrvHeap();
if (FAILED(hr)) {
return hr;
}
// Update constant buffer
hr = UpdateConstantBuffer();
if (FAILED(hr)) {
return hr;
}
return S_OK;
}
```
#### Step 1.5: CreateSrvHeap() 수정
- [ ] Staging texture 대신 현재 render texture로 SRV 생성
- [ ] 초기화 시 texture[0]의 SRV 생성
**파일**: `D:\Project\video-av1\vav2\platforms\windows\applications\vav2player\Vav2Player\src\Rendering\RGBASurfaceBackend.cpp`
**수정 위치**: Lines 379-409
**수정 후 코드**:
```cpp
HRESULT RGBASurfaceBackend::CreateSrvHeap() {
// Create descriptor heap with 1 descriptor for current render texture
D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc = {};
srvHeapDesc.NumDescriptors = 1;
srvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
srvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
HRESULT hr = m_device->CreateDescriptorHeap(&srvHeapDesc, IID_PPV_ARGS(&m_srvHeap));
if (FAILED(hr)) {
return hr;
}
// Create initial SRV for texture[0] (m_renderTextureIndex = 0)
hr = UpdateSRVForCurrentRenderTexture();
if (FAILED(hr)) {
return hr;
}
LOGF_INFO("[RGBASurfaceBackend] Created SRV heap for render texture");
return S_OK;
}
```
#### Step 1.6: RenderToBackBuffer() 수정
- [ ] Staging texture 참조 제거
- [ ] 현재 render texture 사용하도록 변경
- [ ] Staging texture 관련 주석 제거
**파일**: `D:\Project\video-av1\vav2\platforms\windows\applications\vav2player\Vav2Player\src\Rendering\RGBASurfaceBackend.cpp`
**수정 위치**: Lines 225-301 (RenderToBackBuffer 함수 전체)
**주요 변경사항**:
```cpp
// Line 233-238: 기존 staging texture 사용 제거
// ❌ 제거
ID3D12Resource* renderTexture = m_stagingTexture.Get();
if (!renderTexture) {
LOGF_ERROR("[RGBASurfaceBackend] RenderToBackBuffer: staging texture is NULL!");
return E_INVALIDARG;
}
// ✅ 추가
ID3D12Resource* renderTexture = m_rgbaTextures[m_renderTextureIndex].Get();
if (!renderTexture) {
LOGF_ERROR("[RGBASurfaceBackend] RenderToBackBuffer: render texture[%d] is NULL!", m_renderTextureIndex);
return E_INVALIDARG;
}
LOGF_DEBUG("[RGBASurfaceBackend] RenderToBackBuffer: using texture[%d], ptr=%p",
m_renderTextureIndex, renderTexture);
// Line 241-243: Staging texture 관련 주석 제거
// ❌ 제거
// Staging texture is already in PIXEL_SHADER_RESOURCE state (set by CopyToStagingTexture)
// No barrier needed here
// ✅ 추가
// Render texture is in COMMON state (CUDA managed)
// No barrier needed for reading in pixel shader
```
#### Step 1.7: Shutdown() 간소화
- [ ] Staging texture Release 제거
- [ ] Copy command objects Release 제거
- [ ] Copy fence Release 제거
**파일**: `D:\Project\video-av1\vav2\platforms\windows\applications\vav2player\Vav2Player\src\Rendering\RGBASurfaceBackend.cpp`
**수정 위치**: Lines 44-78
**제거할 코드** (lines 59-72):
```cpp
// ❌ 제거
// Release staging texture and copy command objects
m_copyCommandList.Reset();
m_copyCommandAllocator.Reset();
m_stagingTexture.Reset();
// Close fence event handle
if (m_copyFenceEvent != nullptr) {
CloseHandle(m_copyFenceEvent);
m_copyFenceEvent = nullptr;
}
// Release fence
m_copyFence.Reset();
```
**수정 후 Shutdown() 구조**:
```cpp
void RGBASurfaceBackend::Shutdown() {
// Release resources
m_constantBuffer.Reset();
m_pixelShaderBlob.Reset();
m_vertexShaderBlob.Reset();
m_srvHeap.Reset();
m_pipelineState.Reset();
m_rootSignature.Reset();
// Release all texture buffers
for (int i = 0; i < BUFFER_COUNT; i++) {
m_rgbaTextures[i].Reset();
}
m_renderTextureIndex = 0;
m_decodeTextureIndex = 0;
// Clear references (not owned)
m_device = nullptr;
m_commandQueue = nullptr;
m_initialized = false;
}
```
### Phase 2: FrameProcessor 수정
#### Step 2.1: ProcessFrame() 로직 변경
- [ ] 초기 16-frame buffering (NULL surface) - 기존 유지
- [ ] Triple buffer 채우기 (frames 16-18) - 새로운 로직
- [ ] 정상 디코딩 (frame 19+) - 새로운 로직
**파일**: `D:\Project\video-av1\vav2\platforms\windows\applications\vav2player\Vav2Player\src\Playback\FrameProcessor.cpp`
**수정 위치**: Lines 88-140 (NVDEC/Hardware 디코딩 부분)
**수정 후 코드**:
```cpp
} else {
// NVDEC/Hardware: D3D12 surface decoding with triple buffering
// Phase 1: Initial 16-frame buffering (NULL surface)
if (m_framesDecoded < 16) {
LOGF_DEBUG("[FrameProcessor] Initial buffering phase: frame %llu/16", m_framesDecoded.load());
result = vavcore_decode_to_surface(
player,
VAVCORE_SURFACE_D3D12_RESOURCE,
nullptr,
&vavFrame
);
}
// Phase 2: Fill triple buffer (frames 16, 17, 18)
else if (m_framesDecoded < 19) {
LOGF_DEBUG("[FrameProcessor] Filling triple buffer: frame %llu/19", m_framesDecoded.load());
auto backend = m_renderer->GetRGBASurfaceBackend();
if (!backend) {
LOGF_ERROR("[FrameProcessor] RGBASurfaceBackend is NULL");
m_frameProcessing.store(false);
if (onComplete) onComplete(false);
return false;
}
ID3D12Resource* decodeTexture = backend->GetNextDecodeTexture();
if (!decodeTexture) {
LOGF_ERROR("[FrameProcessor] Failed to get decode texture");
m_frameProcessing.store(false);
if (onComplete) onComplete(false);
return false;
}
result = vavcore_decode_to_surface(
player,
VAVCORE_SURFACE_D3D12_RESOURCE,
decodeTexture,
&vavFrame
);
if (result == VAVCORE_SUCCESS) {
// Triple buffer 채우기 완료 시 인덱스 전진
backend->AdvanceFrame();
LOGF_INFO("[FrameProcessor] Triple buffer[%llu] filled, advanced frame", m_framesDecoded.load());
}
}
// Phase 3: Normal decoding (frame 19+)
else {
auto backend = m_renderer->GetRGBASurfaceBackend();
if (!backend) {
LOGF_ERROR("[FrameProcessor] RGBASurfaceBackend is NULL");
m_frameProcessing.store(false);
if (onComplete) onComplete(false);
return false;
}
// 다음 디코딩용 텍스처 가져오기 (현재 렌더링 중이 아닌 텍스처)
ID3D12Resource* decodeTexture = backend->GetNextDecodeTexture();
if (!decodeTexture) {
LOGF_ERROR("[FrameProcessor] Failed to get decode texture");
m_frameProcessing.store(false);
if (onComplete) onComplete(false);
return false;
}
LOGF_DEBUG("[FrameProcessor] Normal decoding: decode_idx=%d, render_idx=%d",
backend->GetDecodeTextureIndex(), backend->GetRenderTextureIndex());
result = vavcore_decode_to_surface(
player,
VAVCORE_SURFACE_D3D12_RESOURCE,
decodeTexture,
&vavFrame
);
if (result == VAVCORE_SUCCESS) {
// 디코딩 완료 - 렌더링/디코딩 인덱스 전환
backend->AdvanceFrame();
LOGF_INFO("[FrameProcessor] Frame decoded, advanced to render_idx=%d",
backend->GetRenderTextureIndex());
}
}
}
```
#### Step 2.2: 렌더링 시작 조건 변경
- [ ] Phase 1 & 2 (frames 0-18): 렌더링 안 함
- [ ] Phase 3 (frame 19+): 정상 렌더링
**파일**: `D:\Project\video-av1\vav2\platforms\windows\applications\vav2player\Vav2Player\src\Playback\FrameProcessor.cpp`
**수정 위치**: Lines 146-179
**수정 후 코드**:
```cpp
auto decodeEnd = std::chrono::high_resolution_clock::now();
double decodeTime = std::chrono::duration<double, std::milli>(decodeEnd - decodeStart).count();
// Phase 1 & 2: Buffering 단계는 렌더링 안 함
if (m_framesDecoded < 19) {
if (result == VAVCORE_PACKET_ACCEPTED || result == VAVCORE_SUCCESS) {
m_framesDecoded++;
LOGF_DEBUG("[FrameProcessor] Buffering frame %llu, no rendering yet", m_framesDecoded.load());
m_frameProcessing.store(false);
if (onComplete) onComplete(true);
return true;
}
}
// Error handling for all phases
if (result != VAVCORE_SUCCESS) {
if (result == VAVCORE_END_OF_STREAM) {
LOGF_INFO("[FrameProcessor] End of stream");
m_frameProcessing.store(false);
if (onComplete) onComplete(true);
return false;
}
if (result == VAVCORE_PACKET_ACCEPTED) {
// VavCore CUDA DPB buffering
LOGF_DEBUG("[FrameProcessor] PACKET ACCEPTED - Frame buffered");
m_framesDecoded++;
m_frameProcessing.store(false);
if (onComplete) onComplete(true);
return true;
}
// All other errors
m_decodeErrors++;
LOGF_ERROR("[FrameProcessor] Decode ERROR: result=%d", result);
m_frameProcessing.store(false);
if (onComplete) onComplete(false);
return false;
}
m_framesDecoded++;
LOGF_INFO("[FrameProcessor] DECODE: %.1f ms", decodeTime);
// Phase 3: Enqueue render on UI thread (frame 19+)
bool enqueued = m_dispatcherQueue.TryEnqueue([this, vavFrame, onComplete, player, processStart]() {
// ... (기존 렌더링 로직 유지)
});
```
#### Step 2.3: CopyToStagingTexture 호출 제거
- [ ] Lines 123-139의 CopyToStagingTexture + WaitForCopyCompletion 제거
**파일**: `D:\Project\video-av1\vav2\platforms\windows\applications\vav2player\Vav2Player\src\Playback\FrameProcessor.cpp`
**제거할 코드** (lines 123-139):
```cpp
// ❌ 완전 제거
// After successful decode, copy to staging texture for safe rendering
if (result == VAVCORE_SUCCESS) {
auto backend = m_renderer->GetRGBASurfaceBackend();
if (backend) {
HRESULT hr = backend->CopyToStagingTexture(rgbaTexture);
if (FAILED(hr)) {
LOGF_ERROR("[FrameProcessor] Failed to copy to staging texture: 0x%08X", hr);
} else {
// Wait for GPU copy to complete before proceeding
hr = backend->WaitForCopyCompletion();
if (FAILED(hr)) {
LOGF_ERROR("[FrameProcessor] Failed to wait for copy completion: 0x%08X", hr);
} else {
LOGF_INFO("[FrameProcessor] GPU copy completed, staging texture ready");
}
}
}
}
```
### Phase 3: D3D12VideoRenderer 수정
#### Step 3.1: RenderVideoFrame() 수정
- [ ] GetStagingTexture() 제거
- [ ] GetCurrentRenderTexture() 사용
**파일**: `D:\Project\video-av1\vav2\platforms\windows\applications\vav2player\Vav2Player\src\Rendering\D3D12VideoRenderer.cpp`
**수정 위치**: RenderVideoFrame() 함수 내부
**주요 변경사항**:
```cpp
// ❌ 제거 (기존 코드 없음, RenderToBackBuffer가 내부에서 처리)
// ✅ RenderToBackBuffer에서 자동으로 GetCurrentRenderTexture() 사용
// 추가 수정 불필요 (RGBASurfaceBackend::RenderToBackBuffer가 이미 올바른 텍스처 사용)
```
### Phase 4: 빌드 및 초기 테스트
#### Step 4.1: 빌드 오류 수정
- [ ] Vav2Player.vcxproj 빌드
- [ ] 컴파일 오류 수정
- [ ] 링크 오류 수정
**빌드 명령어**:
```bash
cd "D:\Project\video-av1\vav2\platforms\windows\applications\vav2player\Vav2Player"
"/c/Program Files/Microsoft Visual Studio/2022/Community/MSBuild/Current/Bin/MSBuild.exe" Vav2Player.vcxproj //p:Configuration=Debug //p:Platform=x64 //v:minimal
```
#### Step 4.2: 기본 동작 테스트
- [ ] 애플리케이션 실행
- [ ] 비디오 로드
- [ ] 초기 19프레임 버퍼링 확인
- [ ] 정상 재생 시작 확인
**예상 로그 패턴**:
```
[FrameProcessor] Initial buffering phase: frame 0/16
...
[FrameProcessor] Initial buffering phase: frame 15/16
[FrameProcessor] Filling triple buffer: frame 16/19
[RGBASurfaceBackend] AdvanceFrame: render 0->1, decode 0->1
[FrameProcessor] Triple buffer[16] filled, advanced frame
[FrameProcessor] Filling triple buffer: frame 17/19
[RGBASurfaceBackend] AdvanceFrame: render 1->2, decode 1->2
[FrameProcessor] Triple buffer[17] filled, advanced frame
[FrameProcessor] Filling triple buffer: frame 18/19
[RGBASurfaceBackend] AdvanceFrame: render 2->0, decode 2->0
[FrameProcessor] Triple buffer[18] filled, advanced frame
[FrameProcessor] Normal decoding: decode_idx=0, render_idx=0
[FrameProcessor] Frame decoded, advanced to render_idx=1
[FrameProcessor] DECODE: XX.X ms
[FrameProcessor] RENDER: XX.X ms | PRESENT: XX.X ms
```
---
## 테스트 계획
### Test Case 1: 초기 버퍼링 단계 검증
**목적**: 16-frame + 3-frame 버퍼링이 정상 동작하는지 확인
**테스트 절차**:
1. 비디오 파일 로드
2. 재생 시작
3. time.log 확인
**예상 결과**:
```
✅ Frame 0-15: "Initial buffering phase" 로그 16개
✅ Frame 16-18: "Filling triple buffer" 로그 3개
✅ Frame 19+: "Normal decoding" 로그
✅ 각 triple buffer 채우기 후 "AdvanceFrame" 로그
```
**실패 조건**:
- ❌ 19프레임 이전에 렌더링 시도
- ❌ Triple buffer 채우기 중 인덱스 전환 실패
- ❌ NULL 텍스처 에러
### Test Case 2: Triple Buffering 순환 검증
**목적**: texture[0] → texture[1] → texture[2] → texture[0] 순환이 정상인지 확인
**테스트 절차**:
1. 30프레임 이상 재생
2. time.log에서 render_idx, decode_idx 추적
**예상 결과**:
```
✅ Frame 19: render_idx=0, decode_idx=0
✅ Frame 20: render_idx=1, decode_idx=1
✅ Frame 21: render_idx=2, decode_idx=2
✅ Frame 22: render_idx=0, decode_idx=0 (순환 완료)
✅ 모든 프레임에서 NULL 텍스처 없음
```
**실패 조건**:
- ❌ Frame 22에서 texture[0] NULL 발생
- ❌ render_idx와 decode_idx가 동일하지 않음
- ❌ 순환 패턴이 깨짐
### Test Case 3: 60fps 렌더링 안정성
**목적**: 30fps 디코딩 + 60fps 렌더링 시나리오에서 안정성 확인
**테스트 절차**:
1. 60fps 모니터에서 비디오 재생 (VSync 활성화)
2. 1분 이상 재생
3. 프레임 드롭, 스터터링 확인
**예상 결과**:
```
✅ 부드러운 60fps 렌더링
✅ 프레임 드롭 0개
✅ 같은 디코딩 프레임이 2번 렌더링됨
✅ 메모리 누수 없음
```
**실패 조건**:
- ❌ 스터터링 발생
- ❌ 프레임 드롭 발생
- ❌ 디코딩/렌더링 비동기 깨짐
### Test Case 4: Seek 및 Reset 동작
**목적**: 탐색 및 재시작 시 triple buffering이 올바르게 재초기화되는지 확인
**테스트 절차**:
1. 비디오 중간으로 seek
2. 재생 재시작
3. 버퍼링 단계가 다시 정상 실행되는지 확인
**예상 결과**:
```
✅ Seek 후 16-frame buffering 재시작
✅ Triple buffer 재초기화
✅ render_idx, decode_idx 모두 0으로 리셋
✅ 정상 재생 재개
```
**실패 조건**:
- ❌ Seek 후 인덱스 리셋 실패
- ❌ 버퍼링 단계 스킵
- ❌ 이전 프레임 잔상
### Test Case 5: 성능 측정
**목적**: Staging texture 제거 후 성능 개선 확인
**측정 항목**:
- 디코딩 시간 (ms)
- 렌더링 시간 (ms)
- Present 시간 (ms)
- 전체 프레임 처리 시간 (ms)
**예상 결과**:
```
✅ 디코딩 시간: 10-15ms (기존과 동일)
✅ 렌더링 시간: 0.4-0.8ms (기존과 동일 또는 개선)
✅ GPU copy 제거로 2-5ms 절약
✅ 전체 처리 시간: 11-13ms (개선)
```
**비교 기준** (기존 staging texture 방식):
```
[FrameProcessor] DECODE: 34.0 ms (late binding 포함)
[FrameProcessor] RENDER: 0.8 ms | PRESENT: 1.9 ms | TOTAL: 37.1 ms
```
---
## 위험 요소 및 대응
### 위험 1: SRV 업데이트 오버헤드
**문제**: 매 프레임마다 `UpdateSRVForCurrentRenderTexture()` 호출 시 성능 저하 가능
**대응 방안**:
- Option A: SRV를 3개 생성하고 descriptor table로 선택
- Option B: SRV 업데이트가 충분히 빠르다면 현재 방식 유지 (권장)
**검증 방법**:
```cpp
auto srvUpdateStart = std::chrono::high_resolution_clock::now();
UpdateSRVForCurrentRenderTexture();
auto srvUpdateEnd = std::chrono::high_resolution_clock::now();
double srvUpdateTime = std::chrono::duration<double, std::milli>(srvUpdateEnd - srvUpdateStart).count();
LOGF_DEBUG("[RGBASurfaceBackend] SRV update time: %.3f ms", srvUpdateTime);
```
### 위험 2: 인덱스 동기화 실패
**문제**: render_idx와 decode_idx가 올바르게 전환되지 않아 같은 텍스처를 동시에 읽기/쓰기
**대응 방안**:
- 모든 AdvanceFrame() 호출에 로깅 추가
- 인덱스 충돌 감지 로직 추가
**검증 코드**:
```cpp
void RGBASurfaceBackend::AdvanceFrame() {
int nextRender = (m_renderTextureIndex + 1) % BUFFER_COUNT;
int nextDecode = (m_decodeTextureIndex + 1) % BUFFER_COUNT;
// Safety check: render and decode should move together
if (nextRender != nextDecode) {
LOGF_ERROR("[RGBASurfaceBackend] Index mismatch! render=%d, decode=%d", nextRender, nextDecode);
}
m_renderTextureIndex = nextRender;
m_decodeTextureIndex = nextDecode;
}
```
### 위험 3: Triple buffer 채우기 실패
**문제**: Frames 16-18 디코딩 중 에러 발생 시 버퍼가 부분적으로만 채워짐
**대응 방안**:
- 에러 발생 시 재시도 로직
- 최소 버퍼 확보 검증
**검증 코드**:
```cpp
if (m_framesDecoded >= 19) {
// Verify all buffers are filled
for (int i = 0; i < BUFFER_COUNT; i++) {
if (!m_rgbaTextures[i].Get()) {
LOGF_ERROR("[RGBASurfaceBackend] Triple buffer[%d] is NULL!", i);
return E_FAIL;
}
}
}
```
---
## 성공 기준
### 필수 달성 목표
1.**빌드 성공**: 모든 컴파일/링크 오류 해결
2.**NULL 텍스처 제거**: Frame 19+ NULL 텍스처 문제 완전 해결
3.**부드러운 재생**: 30fps 디코딩 + 60fps 렌더링 안정적 동작
4.**코드 단순화**: Staging texture 관련 ~200줄 제거
### 우수 달성 목표
1.**성능 개선**: GPU copy 제거로 2-5ms 절약
2.**메모리 효율**: Staging texture 제거로 VRAM 절약
3.**코드 가독성**: 명확한 triple buffering 구조
### 탁월 달성 목표
1.**확장성**: 다른 렌더링 백엔드에도 적용 가능한 구조
2.**문서화**: 완전한 설계 문서 및 코드 주석
3.**테스트 커버리지**: 5개 테스트 케이스 모두 통과
---
## 일정 및 마일스톤
### Milestone 1: 코드 리팩토링 (1-2시간)
- [ ] Phase 1: RGBASurfaceBackend 리팩토링
- [ ] Phase 2: FrameProcessor 수정
- [ ] Phase 3: D3D12VideoRenderer 수정
- [ ] Phase 4: 빌드 및 초기 테스트
### Milestone 2: 기능 검증 (30분)
- [ ] Test Case 1: 초기 버퍼링 단계 검증
- [ ] Test Case 2: Triple Buffering 순환 검증
- [ ] Test Case 3: 60fps 렌더링 안정성
### Milestone 3: 고급 테스트 (30분)
- [ ] Test Case 4: Seek 및 Reset 동작
- [ ] Test Case 5: 성능 측정
- [ ] 문서 업데이트
**총 예상 시간**: 2-3시간
---
## 참고 자료
### 관련 파일
- `D:\Project\video-av1\vav2\platforms\windows\applications\vav2player\Vav2Player\src\Rendering\RGBASurfaceBackend.h`
- `D:\Project\video-av1\vav2\platforms\windows\applications\vav2player\Vav2Player\src\Rendering\RGBASurfaceBackend.cpp`
- `D:\Project\video-av1\vav2\platforms\windows\applications\vav2player\Vav2Player\src\Playback\FrameProcessor.h`
- `D:\Project\video-av1\vav2\platforms\windows\applications\vav2player\Vav2Player\src\Playback\FrameProcessor.cpp`
- `D:\Project\video-av1\vav2\platforms\windows\applications\vav2player\Vav2Player\src\Rendering\D3D12VideoRenderer.h`
- `D:\Project\video-av1\vav2\platforms\windows\applications\vav2player\Vav2Player\src\Rendering\D3D12VideoRenderer.cpp`
### 기술 문서
- [Vav2Player Stutter Fix Design](Vav2Player_Stutter_Fix_Design.md) - Staging texture 도입 배경
- [VavCore NVDEC DPB Redesign](../../../docs/completed/windows/VavCore_NVDEC_DPB_Redesign.md) - CUDA DPB 구조
- [Vav2Player NVDEC DPB Integration](../../../docs/completed/windows/Vav2Player_NVDEC_DPB_Integration.md) - Late binding 메커니즘
---
*문서 버전: 1.0*
*최종 수정: 2025-10-10*