4.2 CPU와 GPU의 상호작용
- 최적의 성능을 얻기 위해
CPU
,GPU
를 최대한 사용 <- 동기화를 최소화
4.2.1 명령 대기열과 명령 목룍
GPU
는명령 대기열(command queue)
1개 보유CPU
는 그리기 명령이 담긴명령 목록(command list)
를 D3D API를 통해서명령 대기열
에 제출- 명령 대기열에 제출 했다고, GPU가 즉시 실행 X
ID3D12CommandQueue
: 명령 대기열을 대표하는 인터페이스
1)D3D12_COMMAND_QUEUE_DESC
구조체를 채움
2)ID3D12Device::CreateCommandQueue
를 호출Microsoft::WRL::ComPtr<ID3D12CommandQueue> mCommandQueue; D3D12_COMMAND_QUEUE_DESC queueDesc = {}; queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; ThrowIfFailed(md3dDevice->CreateCommandQueue( &queueDesc, IID_PPV_ARGS(&mCommandQueue)));
#define IID_PPV_ARGS(ppType) __uuidof(**(ppType)), IID_PPV_ARGS_Helper(ppType)
- COM 인터페이스에서 특정 인터페이스를 요청할 떄 사용
- COM에서는 인터페이스를 식별하고 사용하기 위해 GUID(Globally Unique Identifier)
__uuidof(**(ppType))
:(**(ppType))
의 가리키는 인터페이스 타입의 GUID를 반환IID_PPV_ARGS_Helper(ppType)
:ppType
에 대해 추가적인 처리를 수행하는 도우미 매크로, *ppType
을 `void`로 캐스팅***
ID3D12CommandQueue::ExecuteCommandLists
: 명령 목록 요소들을 대기열에 추가/제출 <- CPU의 실행 차단 xvoid ID3D12CommandQueue::ExecuteCommandLists( UINT Count, // 배열에 있는 명령 목록들의 개수 ID3D12CommandList *const *ppCommandLists // 명령 목록들의 배열 첫 원소 가리키는 포인터); )
- CommandList에 대해
ID3D12GraphicsCommandList
:ID3D12CommandList
를 상속하는 실제 그래픽 작업을 위한 명령 목록 인터페이스- "CommandQueue"에 추가한 뒤에는 지워도 됨.
- 실제 추가되는 명령은 "CommandAllocator"를 참조하기 때문
- 뷰포트 설정 및 렌더 대상 뷰 지우고, 그리기 호출 ( 즉시 실행 x)
// mCommandList는 ID3D12CommandList 포인터 mCommandList->RSSetViewports(1, &mScreenViewport); mCommandList->ClearRederTargetView(mBackBufferView, Colors::LightSteelBlue, 0, nullptr); mCommandList->DrawIndexedInstanced(36, 1, 0, 0, 0);
- 명령의 기록이 끝나면, 닫기 :
mCommandList->Close();
ID3D12CommandAllocator
형식의 메모리 할당자가 1 개 연관- '명령 목록'에 추가된 명령이 저장되는 곳
CreateCommandList
: 생성HRESULT ID3D12Device::CreateCommandList( UNIT nodeMask, // GPU가 하나면 0, 아니면 연관시킬 어댑터 노드 지어 bit mask 값 D3D12_COMMAND_LIST_TYPE type, // 아래 type 유형 참조 ID3D12CommandAllocator *pCommandAllocator, // 할당자 종류는 목록 종류와 일치 ID3D12PipelineState *pInitialState // 번들 및 초기화, 실제 그리기 명령 없으면 널 지정 가능 REFIID riid, void **ppCommandList); // 생성된 명령 목록 가리키는 포인터
ID3D12Device::GetNodeCount
: GPU 어댑터 노드 개수
Reset
:- 초기화, 번거로움 없이 메모리 재사용
- size는 0이 되어도, capacity는 그대로
- 필요 렌더링 명령 모두 GPU 제출 후, 다음 프레임을 위해 재사용 용도
- Queue에 제출된 명령에는 영향x
- 할당자의 메모리에 여전히 존재
HRESULT ID3D12CommandList::Reset( ID3D12CommandAllocator *pAllocator, ID3D12PipelineState *pInitialState);
- 초기화, 번거로움 없이 메모리 재사용
- 명령 메모리 할당자
HRESULT ID3D12Device::CreateCommandAllocator( D3D!@_COMMAND_LIST_TYPE type, REFIID riid, void **ppCommandAllocator);
type
: 할당자와 연관 시킬 수 있는 명령 목록 종류D3D12_COMMAND_LIST_TYPE_DIRECT
: GPU 직접 실행 명령 목록D3D12_COMMAND_LIST_TYPE_BUNDLE
:- CPU 부담을 줄이기 위한 일련의 명령들을 '묶음' 단위로 최적화
- 렌더링 도중 실행 최적화를 위해, 번들 명령 전처리
- 성능상 이득이 있는 경우만 사용
riid
:ID3D12CommandAllocator
인터페이스의 COM IDppCommandAllocator
: 생성된 명령 할당자를 가리키는 포인터- 목록 생성 || 재설정 시, "열린" 상태가 됨 주의
4.2.2 CPU/GPU 동기화
1) CPU가 'C' 명려을 GPU에 제출
2) GPU 명령 수행중, R 업뎃 - CPU 'Resource'에 p1 저장
3) GPU 명령 수행중 , R 업뎃- CPU 'Resource'에 p2 로 저장 변경- GPU 명령 수행중에 참조하는 R의 값이 변하는 문제가 발생 <- 비동기로 돌아가서
flush(방출한다)
: 대기열의 모든 명령을 처리하는 것ID3D12Fence
가 필요 : GPU, CPU 동기화 수단- 울타리 생성
HRESULT ID3D12Device::CreateFence( UINT64 InitialValue, D3D12_FENCE_FLAGS Flags, REFIID riid; void **ppFence);
- 울타리를 이용한 명령 대기열 비우기
UINT64 mCurrentFence = 0; void D3DApp::FlushCommandQueue() { // Advance the fence value to mark commands up to this fence point. mCurrentFence++; // Add an instruction to the command queue to set a new fence point. // Because we are on the GPU timeline, the new fence point won’t be // set until the GPU finishes processing all the commands prior to // this Signal(). ThrowIfFailed(mCommandQueue->Signal(mFence.Get(), mCurrentFence)); // Wait until the GPU has completed commands up to this fence point. if(mFence->GetCompletedValue() < mCurrentFence) { HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS); // Fire event when GPU hits current fence. ThrowIfFailed(mFence-> SetEventOnCompletion(mCurrentFence, eventHandle)); // Wait until the GPU hits current fence event is fired. WaitForSingleObject(eventHandle, INFINITE); CloseHandle(eventHandle); } }
- 울타리 생성
4.2.3 자원 상태 전이
- 자원 위험 상황(resource hazard) : 자원의 기록이 제대로 안된 상태에서 자료를 읽을려 할 떄 생기는 문제
- 임의의 상태 전이를 'D3D'에게 보고하는 것은 '응용 프로그램 몫'
- 리소스 배리어는 후면 버퍼를 `제시` ->`렌더링` 상태로 전환 (쓰기모드)
- 리소스 배리어는 후면 버퍼를 `렌더링`->`제시` 상태로 전환 (읽기모드)
- 자원 상태 전이 :
- 전이 자원 장벽(transition resource barrier)들의 배열을 설정해서 지정
D3D12_RESOURCE_BARRIER_DESC
struct CD3DX12_RESOURCE_BARRIER : public D3D12_RESOURCE_BARRIER { // [...] convenience methods static inline CD3DX12_RESOURCE_BARRIER Transition( _In_ ID3D12Resource* pResource, D3D12_RESOURCE_STATES stateBefore, D3D12_RESOURCE_STATES stateAfter, UINT subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, D3D12_RESOURCE_BARRIER_FLAGS flags = D3D12_RESOURCE_BARRIER_FLAG_NONE) { CD3DX12_RESOURCE_BARRIER result; ZeroMemory(&result, sizeof(result)); D3D12_RESOURCE_BARRIER &barrier = result; result.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; result.Flags = flags; barrier.Transition.pResource = pResource; barrier.Transition.StateBefore = stateBefore; barrier.Transition.StateAfter = stateAfter; barrier.Transition.Subresource = subresource; return result; } // [...] more convenience methods };
CDIDX12_
확장 버전은d3dx12.h
에 정의
- 텍스처 자원 제시 상태 -> 렌더 대상 상태 전이 예시
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition( CurrentBackBuffer(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));
- 전이 자원 장벽(transition resource barrier)들의 배열을 설정해서 지정
4.2.4 명령 목록을 이용한 다중 스레드 활용
- 명령 목록 구축에 다중 스레드 적용 주의점
- 명령 목록은 자유 스레드(free-threaded) 모형을 따르지 않음;
- 여러 스레드가 같은 명령 공유x, 동시에 호출 x, 각 스레드는 자신만의 명령 목록
- 명령 할당자도 자유 스레드X, 각 스레드는 각자 자신만의 명령 할당자를 가짐
- 명령 대기열은 자유 스레드 모형;
- 여러 스레드가 동시 호출 , 스레드들이 각자 자신이 생성한 명령 목록을 동시에 명령 대기열에 제출 가능
- 성능상 이유로, 응용 프로그램은 동시 기록 가능 명령 목록들의 최대 개수를 초기화 시점에 설정해야 함
- 명령 목록은 자유 스레드(free-threaded) 모형을 따르지 않음;
'프레임워크 > DirectX' 카테고리의 다른 글
Direct3D의 초기화_게임프레임워크&타이머 코드 의문점 (0) | 2025.03.07 |
---|---|
04.Direct3D의_초기화03 (1) | 2025.03.04 |
04.Direct3D의_초기화01 (1) | 2025.03.01 |
02.Matrix (0) | 2025.02.25 |
01.Vector (0) | 2025.02.25 |