Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

codingfarm

6. Direct3D의 그리기 연산 - 파이프라인 상태 객체 본문

computer graphics/DX12 book

6. Direct3D의 그리기 연산 - 파이프라인 상태 객체

scarecrow1992 2021. 6. 19. 16:18

지금까지 input layout description 구조체를 만드는 방법과 vertex shader 및 pixel shader를 만드는 방법 그리고 rasterrizer state 그룹을 구성하는 방법 등 여러가지 렌더링 준비 과정을 살펴보았다.

이제 이런 객체들을  실제로 쓰기 위해 rendering pipeline에 binding 하는 방법을 알아보자.

 

파이프라인 상태 객체(pipeline state object, PSO)

  • rendering pipeline의 상태를 제어하는 대부분의 객체를 지정하는데 사용함
  • ID3D12PipelineState로 대표됨

 

PSO의 생성 과정

  1. pipeline state를 서술하는 D3D12_GRAPHICS_PIPELINE_STATE_DESC 구조체의 인스턴스를 채운다.
  2. ID3D12Device::CreateGraphicsPipelineState 메서드를 호출하여 ID3D12PipelineState 객체를 생성

 

1. D3D12_GRAPHICS_PIPELINE_STATE_DESC 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedef struct D3D12_GRAPHICS_PIPELINE_STATE_DESC {
  ID3D12RootSignature                *pRootSignature;
  D3D12_SHADER_BYTECODE              VS;
  D3D12_SHADER_BYTECODE              PS;
  D3D12_SHADER_BYTECODE              DS;
  D3D12_SHADER_BYTECODE              HS;
  D3D12_SHADER_BYTECODE              GS;
  D3D12_STREAM_OUTPUT_DESC           StreamOutput;
  D3D12_BLEND_DESC                   BlendState;
  UINT                               SampleMask;
  D3D12_RASTERIZER_DESC              RasterizerState;
  D3D12_DEPTH_STENCIL_DESC           DepthStencilState;
  D3D12_INPUT_LAYOUT_DESC            InputLayout;
  D3D12_INDEX_BUFFER_STRIP_CUT_VALUE IBStripCutValue;
  D3D12_PRIMITIVE_TOPOLOGY_TYPE      PrimitiveTopologyType;
  UINT                               NumRenderTargets;
  DXGI_FORMAT                        RTVFormats[8];
  DXGI_FORMAT                        DSVFormat;
  DXGI_SAMPLE_DESC                   SampleDesc;
  UINT                               NodeMask;
  D3D12_CACHED_PIPELINE_STATE        CachedPSO;
  D3D12_PIPELINE_STATE_FLAGS         Flags;
} D3D12_GRAPHICS_PIPELINE_STATE_DESC;
cs

graphics pipeline state object를 서술한다.

 

 

2. CreateGraphicsPipeLineState 메서드 호출

ID3D12Device::CreateGraphicsPipeLineState 메서드를 이용하여 ID3D12PipelineState 객체를 생성한다.

1
2
3
4
5
HRESULT CreateGraphicsPipelineState(
  const D3D12_GRAPHICS_PIPELINE_STATE_DESC *pDesc,
  REFIID                                   riid,
  void                                     **ppPipelineState
);
cs

 

 

PSO를 생성하는 예제코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 1. D3D12_GRAPHICS_PIPELINE_STATE_DESC
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc;
ZeroMemory(&psoDesc, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
psoDesc.InputLayout = { mInputLayout.data(), (UINT)mInputLayout.size() };
psoDesc.pRootSignature = mRootSignature.Get();
psoDesc.VS = 
    reinterpret_cast<BYTE*>(mvsByteCode->GetBufferPointer()), 
    mvsByteCode->GetBufferSize() 
};
psoDesc.PS = 
    reinterpret_cast<BYTE*>(mpsByteCode->GetBufferPointer()), 
    mpsByteCode->GetBufferSize() 
};
psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
psoDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
psoDesc.SampleMask = UINT_MAX;
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.NumRenderTargets = 1;
psoDesc.RTVFormats[0= mBackBufferFormat;
psoDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
psoDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
psoDesc.DSVFormat = mDepthStencilFormat;
 
// 2. CreateGraphicsPipeLineState 메서드 호출
ComPtr<ID3D12PipelineState> mPSO = nullptr;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&mPSO)));
cs

 

하나의 PSO에 상당히 많은 객체가 들어있다. 이 모든 객체를 하나의 집합체로서 rendering pipeline에 지정하는 이유는 성능 때문이다. 이 모든것을 하나의 집합체로 지정하는 덕분에

  1. direct3d는 모든 상태가 호환되는지 미리 검증할 수 있다.
  2. 드라이버는 하드웨어 상태의 프로그래밍을 위한 모든 코드를 미리 생성할 수 있다.

위와 같은 장점을 지닌다.

directx11의 상태 모형에서는 이 렌더 상태 조각들을 개별적으로 설정했다. 그러나 이 렌더 상태 조각들은 서로 연관되어있다. 만일 한 조각의 상태가 변하면, 그에 의존하는 다른 조각을 위해 드라이버가 하드웨어를 다시 프로그래밍해야 할 수 있다. 파이프라인을 구성하는 과정에서 다수의 상태 조각을 변경하면 드라이버가 하드웨어를 중복해서 다시 프로그래밍 하기 쉽다. 이러한 중복을 피하려고 드라이버들이 흔히 사용하는 기법 하나는 하드웨어의 프로그래밍을 모든 pipeline의 state가 알려진 시점인 그리기 호출 시점으로 미루는것이다. 그러나 하드웨어 프로그래밍을 그런식으로 미루려면 실행 시점에서 드라이버가 추가적인 관리 작업을 수행해야 한다.

Direct3D 12의 새 모형에서는 응용 프로그램이 pipeline state의 대부분을 하나의 집합체로서 지정하므로, 드라이버는 pipeline의 프로그래밍에 필요한 모든 코드를 초기화 시점에서 생성할 수 있다.

PSO가 모든 렌더 상태를 포함하는것은 아니다. viewport나 가위 직사각형 같은 상태들은 PSO와는 따로 지정한다. 그런 상태들은 다른 pipeline state들과 독립적으로 지정해도 비효율적이지 않기 때문에, PSO에 포함해도 이득이 없다.

PSO 검증과 생성에는 많은 시간이 걸릴 수 잇으므로, PSO는 초기화 시점에서 생성해야한다. 한 가지 예이ㅗ라면, ㅣㄹ행시점에서 PSO가 처음 언급될 대 비로소 PSO를 생성하고, 그것을 해시 테이블처럼 빠르게 조회할 수 있는 컬렉션에 담아두고 이후에 필요할때마다 꺼내쓰는 기ㅓㅂ도 있다.

 

Direct3D는 기본적으로 하나의 상태 기계(state machine)이다. Direct3D에는 명시적으로 변경하지 않는 한 그대로 남아있는것들이 많다. 즉, 어떤 PSO를 command list에 묶었다면, 다른 PSO가 binding되기 전까지는 (또는 command list가 재설정 되기 전까지는) 그 PSO가 계속 적용된다. 그러므로 한 장면에서 여러 부류의 물체들을 각자 다른 PSO를 이용해서 그린다면, 코드의 구조를 다음과 같이 짜야 한다.

1
2
3
4
5
6
7
8
9
10
11
// Reset을 호출해서 초기 PS0를 지정한다
mCommandList->Rest(mDirectCmdListAlloc.Get(), mPS01.Get())
/* ...PS0 1을 이용해서 물체들을 그린다... */
 
// PS0을 변경한다
mCommandList->SetPipelineState(mPS02.Get());
/* ...PS0 2를 이용해서 물체들을 그린다 */
 
// PS0를 변경한다
mCommandList->SetPipelineState(mPS03.Get());
/* ...PS0 3를 이용해서 물체들을 그린다 */
cs

성능을 위해서는 PSO상태변경을 최소화 해야 한다. 같은 PSO를 사용할 수 있는 물체들은 모두 함께 그려야 마땅하다. 그리기 호출마다 PSO를 변경하지는 말아야 한다.

 

 


이때 까지 배웠던 command list를 통해 화면을 출력하기 위한 일련의 과정을 도식화한 모습이다.

input layout, shader object bytecode, root signature를 참조하는 PSO를 만들고, allocator를 만든 후, 이들 정보를 기반으로 command line을 reset한다.

그 이후 화면을 그리기 위한 vertex와 index buffer를 만들고 이들의 descriptor를 만든 후, 메시지 처리와 함께 이루어지는 반복문내에서 binding, 그리기 명령 제출, 기본 도형 위상 구조 설정 과정이 이루어 지면 화면에 그림이 출력된다.

 

 

Comments