1140 lines
37 KiB
Markdown
1140 lines
37 KiB
Markdown
|
|
# 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*
|