기록공간

[DirectX 12] 기본지식 - CPU / GPU 동기화 본문

DirectX/기초

[DirectX 12] 기본지식 - CPU / GPU 동기화

입코딩 2020. 2. 19. 21:24
반응형

한 시스템에 CPU와 GPU가 병렬로 실행되다 보니 동기화 문제가 발생한다. 예를 들어 그리고자 하는 어떤 기하구조의 위치를 R이라는 자원에 담는다고 하자. 그 기하구조를 위치 p1에 그리려는 목적으로 CPU는 위치 p1을 R에 추가하고, R을 참조하는 그리기 명령 C를 명령 대기열에 추가한다. 

 

명령 대기열에 명령을 추가하는 연산은 CPU의 실행을 차단하지 않으므로, CPU는 계속해서 다음 단계로 넘어간다. 만약 GPU가 그리기 명령 C를 실행하기 전에 CPU가 새 위치 p2를 R에 추가해서 R에 있던 기존 p1을 덮어쓰면, 기하구조는 의도했던 위치에 그려지지 않게 된다. 

 

이런 문제의 해결책은 GPU가 명령 대기열의 명령들 중 특정 지점까지의 모든 명령을 다 처리할 때까지 CPU를 기다리게 하는 것이다. 대기열의 특정 지점까지의 명령을 처리하는 것을 가리켜 명령 대기열을 비운다 또는 방출한다(Flush)라고 말한다.

 

이때 필요한 것이 바로 울타리(Fence)이다. 울타리(펜스)는 ID3D12Fence 인터페이스로 대표되며, GPU와 CPU의 동기화를 위한 수단으로 쓰인다. 다음은 펜스 객체를 생성하는 메서드이다.

 

사용 예)

m_pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, 
			__uuidof(ID3D12Fence), (void**)&m_pd3dFence);

펜스 객체는 UINT64 값 하나를 관리한다. 이 값은 시간상의 특정 펜스 지점을 식별하는 정수이다. 이 값을 0으로 두고, 새 펜스 지점을 만들 때마다 이 값을 1씩 증가시킨다. UINT64의 최대값은 엄청나게 큰 값이기 때문에(약 1.8천경 정도 된다) 아무리 많은 시간동안 게임을 실행하며 이 값을 1씩 증가시킨다고 해도 이 값이 최대 값을 넘어간다는 걱정은 하지 않아도 된다.

 

다음은 펜스를 이용해서 명령 대기열을 비우는 방법을 보여주는 코드이다.

// 현재 펜스 지점까지의 명령들을 표시하도록 펜스 값을 전진
m_nFenceValue++;

// 새 펜스 지점을 설정하는 명령을 명령 대기열에 추가한다.
m_pd3dCommandQueue->Signal(m_pd3dFence, m_nFenceValue);

// GPU가 이 펜스 지점까지의 명령들을 완료할 때까지 기다린다.
if(m_pd3dFence->GetCompletedValue() < m_nFenceValue)
{
    // GPU가 현재 펜스 지점에 도달했으면 이벤트를 발동시킨다.
    m_pd3dFence->SetEventOnCompletion(m_nFenceValue, m_hFenceEvent);
    
    // GPU가 현재 펜스 지점에 도달했음을 뜻하는 이벤트를 기다린다.
    ::WaitForSignalObject(m_hFenceEvent, INFINITE);
}

이 코드를 도식화하면 다음과 같다.

 

위 숫자 순으로 보면 어떤 순서로 돌아가는지 알 수 있을것이다. GPU는 현재 프레임에서 필요한 명령들을 완료하면 펜스 객체의 값을 하나 증가시키는 명령을 실행하게 된다. CPU는 그 작업을 하기 전까지 대기하는 것이다.

반응형
Comments