입력 조립기(2) - 정점 버퍼

WanJu Kim·2022년 12월 29일

Direct3D

목록 보기
9/29

GPU가 정점 배열에 접근할 수 있으려면, 배열의 정점들을 버퍼(buffer)라고 부르는 특별한 자원에 담아 두어야 한다. 마치 자원 말고 자원 뷰를 묶는 것처럼? Direct3D 코드에서 버퍼는 ID3D11Buffer 인터페이스로 대표된다. 정점들을 담는 버퍼를 '정점 버퍼'라고 부른다. Direct3D의 버퍼들은 자료를 담을 뿐만 아니라, CPU나 GPU가 자료에 어떻게 접근할 수 있고 버퍼가 파이프라인의 어디에 묶이는지에 대한 정보도 가진다. 정점 버퍼는 다음 과정을 통해 생성하고 묶는다.

  1. 생성할 버퍼를 서술하는 D3D11_BUFFER_DESC 구조체를 채운다.
  2. 버퍼의 초기화에 사용할 자료를 서술하는 D3D11_SUBRESOURCE_DATA 구조체를 채운다.
  3. ID3D11Device::CreateBuffer를 호출해서 버퍼를 생성한다.
  4. ID3D11DeviceContext::IASetVertexBuffers를 호출해서 파이프라인에 묶는다.

다음은 D3D11_BUFFER_DESC 구조체이다.

struct D3D11_BUFFER_DESC {
	UINT ByteWidth;
    D3D11_USAGE Usage;
    UINT BindFlags;
    UINT CPUAccessFlags;
    UINT MiscFlags;
    UINT StructureByteStride;
  1. ByteWidth : 생성할 정점 버퍼의 크기(바이트 단위).
  2. Usage : 버퍼가 쓰이는 용도. 2차원 텍스처 만들 때도 이 변수가 있었다. 다음 네 가지 중 하나를 선택한다.
    (a) D3D11_USAGE_DEFAULT : GPU가 버퍼의 자원을 읽고 써야 한다면 이 용도를 지정한다. CPU는 이 용도를 가진 자원을 매핑 API(ID3D11DeviceContext::Map)을 통해서 읽거나 쓸 수 없다. 그러나 ID3D11DeviceContext::UpdateSubresource를 사용하는 것은 가능하다.
    (b) D3D11_USAGE_IMMUTABLE : 버퍼를 생성한 후에 전혀 변경하지 않을 것이라면 이 용도를 사용하면 된다. GPU 읽기만 가능해 최적화가 가능하다.
    (c) D3D11_USAGE_DYNAMIC : CPU가 이 자원의 내용을 자주(거의 매 프레임) 갱신할 때 사용한다. GPU가 읽을 수 있고, CPU는 매핑 API를 통해 자료를 기록할 수 있다. GPU의 자원을 CPU에서 동적으로 갱신하면 성능상의 피해가 생긴다. 그러므로 이 동적 용도는 꼭 필요한 경우가 아니라면 피해야 한다.
    (d) D3D11_USAGE_STAGING : CPU가 자원의 복사본을 읽어야 한다면 이 용도를 사용한다. CPU에서 CPU 메모리로의 자료 복사는 느린 연산이므로 꼭 필요한 경우가 아니면 피한다. 자원을 복사하는 메서드로는 ID3D11DeviceContext::CopyResource와 ID3D11DeviceContext::CopySubresourceRegion이 있다.
  3. BindFlags : 어떤 형식으로 바인딩할 것인가? 정점 버퍼의 경우에는 D3D11_BIND_VERTEX_BUFFER, 말 그대로 vertex 용도를 사용한다.
  4. CPUAccessFlags : CPU가 버퍼에 접근하는 방식을 결정하는 플래그들을 지정한다. 버퍼 생성 이후 CPU가 버퍼를 읽거나 쓰지 않는다면 0을 지정. CPU가 자료를 기록해서 버퍼를 갱신 시킨다면 D3D11_CPU_ACCESS_WRITE 지정. 이렇게 하려면 버퍼의 용도가 D3D11_USAGE_DYNAMIC이나 D3D11_USAGE_STAGING이어야 한다. CPU가 버퍼에서 자원을 읽어야 한다면 D3D11_CPU_ACCESS_READ를 지정한다. 이렇게 하려면 버퍼의 용도가 D3D11_USAGE_STAGING이어야 한다.
  5. MiscFlags : 정점 버퍼에 대해서는 묻지도 따지지도 말고 0 지정.
  6. StructureByteStride : 구조적 버퍼(structured buffer)에 저장된 원소 하나의 크기(바이트 단위). 이 변수는 구조적 버퍼를 위해서만 필요한 것이므로, 그 외는 모두 0을 지정하면 된다.

다음으로 D3D11_SUBRESOURCE_DATA 구조체의 정의는 다음과 같다.

struct D3D11_SUBRESOURCE_DATA {
	const void *pSysMem;
    UINT SysMemPitch;
    UINT SysMemSlicePitch;
}
  1. pSysMem : 정점에 대한 정보들을 가지고 있는 배열을 가리키는 포인터.
  2. SysMemPitch : 정점 버퍼에는 쓰이지 않음.
  3. SysMemSlicePitch : 정점 버퍼에는 쓰이지 않음.

이제 이 정보를 토대로 정점 버퍼를 생성할 수 있다. ID3D11Device::CreateBuffer를 사용한다.

md3dDevice->CreateBuffer(&desc, &subData, &vertexBuffer);
assert(SUCCEEDED(hr));

제1 매개변수는 D3D11_BUFFER_DESC 구조체 객체이다. 제2 매개변수는 D3D11_SUBRESOURCE_DATA 구조체 객체이다. 제3 매개변수는 생성한 정점 버퍼를 반환 받을 포인터 변수이다.

이렇게 생성된 버퍼의 정점들을 실제로 파이프라인으로 공급하려면, 버퍼를 장치의 한 입력 슬롯에 묶어야 한다. 이 메서드를 쓴다.

void ID3D11DeviceContext::IASetVertexBuffers(
	UINT StartSlot;
    UINT NumBuffers,
    ID3D11Buffer *const *ppVertexBuffers,
    const UINT *pStrides
    const UINT *pOffsets);
  1. StartSlot : 정점 버퍼들을 붙이기 시작할 입력 슬롯의 색인. 총 16개의 입력 슬롯이 있다.(색인은 0 ~ 15)
  2. NumBuffers : 이 호출에서 입력 슬롯들에 붙이고자 하는 버퍼들의 개수.
  3. ppVertexBuffers : 붙일 정점 버퍼들을 담은 배열의 첫 원소를 가리키는 포인터.
  4. pStrides : 보폭(stride)들의 배열의 첫 원소를 가리키는 포인터. 여기서 보폭은 해당 정점 버퍼의 한 원소의 바이트 단위 크기이다.
  5. pOffsets : 오프셋들의 배열의 첫 원소를 가리키는 포인터. 오프셋이란 정점 버퍼의 시작 위치에서 입력 조립기 단계가 정점 자료를 읽기 시작할 정점 버퍼 안 위치까지의 거리를 말한다. 정점 버퍼 앞부분의 일부 정점 자료를 건너뛰고자 하는 경우도 이 오프셋을 적절히 지정하면 된다.

입력 슬롯에 붙인 정점 버퍼는 다시 변경하지 않는 한 계속 그 입력 슬롯에 붙어있다. 마치 기본도형 위상구조(primitive topology), 입력 배치(input layout)처럼 말이다.

이렇게 하면 정점 버퍼를 GPU에게 완전히 제공한 것이다. 하지만 이렇게 했다고 저절로 그리기 작업이 진행되는 것은 아니다. 그리기 연산을 실제로 시작하려면, ID3D11DeviceContext::Draw 메서드를 호출해야 한다.

void ID3D11DeviceContext::Draw(UINT VertexCount, UINT StartVertexLocation);

제1 매개변수는 정점 갯수, 제2 매개변수는 그 정점의 시작 위치를 말한다.

다음은 예시 코드이다.

Vertex vertices[] =
    {
		{ XMFLOAT3(-1.0f, -1.0f, -1.0f), (const float*)&Colors::White   },
		{ XMFLOAT3(-1.0f, +1.0f, -1.0f), (const float*)&Colors::Black   },
		{ XMFLOAT3(+1.0f, +1.0f, -1.0f), (const float*)&Colors::Red     },
		{ XMFLOAT3(+1.0f, -1.0f, -1.0f), (const float*)&Colors::Green   },
		{ XMFLOAT3(-1.0f, -1.0f, +1.0f), (const float*)&Colors::Blue    },
		{ XMFLOAT3(-1.0f, +1.0f, +1.0f), (const float*)&Colors::Yellow  },
		{ XMFLOAT3(+1.0f, +1.0f, +1.0f), (const float*)&Colors::Cyan    },
		{ XMFLOAT3(+1.0f, -1.0f, +1.0f), (const float*)&Colors::Magenta }
    };

    D3D11_BUFFER_DESC vbd;
    vbd.Usage = D3D11_USAGE_IMMUTABLE;
    vbd.ByteWidth = sizeof(Vertex) * 8;
    vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vbd.CPUAccessFlags = 0;
    vbd.MiscFlags = 0;
	vbd.StructureByteStride = 0;
    
    D3D11_SUBRESOURCE_DATA vinitData;
    vinitData.pSysMem = vertices;
    
    ID3DllBuffer* mVB
    HR(md3dDevice->CreateBuffer(&vbd, &vinitData, &mvB));
    
    UINT stride = sizeof(Vertex);
    UINT offset = 0;
    md3dImmediateContext->IASetVertexBuffers(0, 1, &mvB, &stride, &offset);
    void ID3D11DeviceContext::Draw(UINT VertexCount, UINT StartVertexLocation)
profile
Question, Think, Select

0개의 댓글