다양한 종류의 리소스 업로드
하나의 버퍼를 사용하여 상수 버퍼 데이터와 꼭짓점 버퍼 데이터를 둘 다 GPU에 업로드하는 방법과 버퍼 내에서 데이터를 적절하게 하위 할당 및 배치하는 방법을 보여 줍니다. 단일 버퍼를 사용하면 메모리 사용 유연성이 향상되고 애플리케이션에서 메모리 사용량을 보다 엄격하게 제어할 수 있습니다. 또한 다양한 유형의 리소스를 업로드하기 위한 Direct3D 11과 Direct3D 12 모델의 차이점을 보여 줍니다.
다양한 유형의 리소스 업로드
Direct3D 12에서는 업로드할 다양한 유형의 리소스 데이터를 수용하기 위해 하나의 버퍼를 만들고 다른 리소스 데이터에 대해 비슷한 방식으로 동일한 버퍼에 리소스 데이터를 복사합니다. 그런 다음, Direct3D 12 리소스 바인딩 모델의 그래픽 파이프라인에 해당 리소스 데이터를 바인딩하기 위해 개별 뷰가 만들어집니다.
Direct3D 11에서는 다양한 유형의 리소스 데이터에 대해 별도의 버퍼를 만들고(아래 Direct3D 11 샘플 코드에서 사용되는 다른 BindFlags
항목 참고), 각 리소스 버퍼를 그래픽 파이프라인에 명시적으로 바인딩하고, 다양한 리소스 종류에 따라 다른 방법으로 리소스 데이터를 업데이트합니다.
Direct3D 12 및 Direct3D 11 모두에서 CPU가 데이터를 한 번 쓰는 경우에만 업로드 리소스를 사용해야 하며 GPU는 데이터를 한 번 읽습니다.
경우에 따라
- GPU는 데이터를 여러 번 읽거나
- GPU가 데이터를 선형으로 읽지 않거나
- 렌더링은 이미 GPU가 크게 제한됩니다.
이 경우 ID3D12GraphicsCommandList::CopyTextureRegion 또는 ID3D12GraphicsCommandList::CopyBufferRegion을 사용하여 업로드 버퍼 데이터를 기본 리소스에 복사하는 것이 더 좋습니다.
기본 리소스는 개별 GPU의 실제 비디오 메모리에 상주할 수 있습니다.
코드 예제: Direct3D 11
// Direct3D 11: Separate buffers for each resource type.
void main()
{
// ...
// Create a constant buffer.
float constantBufferData[] = ...;
D3D11_BUFFER_DESC constantBufferDesc = {0};
constantBufferDesc.ByteWidth = sizeof(constantBufferData);
constantBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
constantBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
constantBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
ComPtr<ID3D11Buffer> constantBuffer;
d3dDevice->CreateBuffer(
&constantBufferDesc,
NULL,
&constantBuffer
);
// Create a vertex buffer.
float vertexBufferData[] = ...;
D3D11_BUFFER_DESC vertexBufferDesc = { 0 };
vertexBufferDesc.ByteWidth = sizeof(vertexBufferData);
vertexBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
ComPtr<ID3D11Buffer> vertexBuffer;
d3dDevice->CreateBuffer(
&vertexBufferDesc,
NULL,
&vertexBuffer
);
// ...
}
void DrawFrame()
{
// ...
// Bind buffers to the graphics pipeline.
d3dDeviceContext->VSSetConstantBuffers(0, 1, constantBuffer.Get());
d3dDeviceContext->IASetVertexBuffers(0, 1, vertexBuffer.Get(), ...);
// Update the constant buffer.
D3D11_MAPPED_SUBRESOURCE mappedResource;
d3dDeviceContext->Map(
constantBuffer.Get(),
0,
D3D11_MAP_WRITE_DISCARD,
0,
&mappedResource
);
memcpy(mappedResource.pData, constantBufferData,
sizeof(contatnBufferData));
d3dDeviceContext->Unmap(constantBuffer.Get(), 0);
// Update the vertex buffer.
d3dDeviceContext->UpdateSubresource(
vertexBuffer.Get(),
0,
NULL,
vertexBufferData,
sizeof(vertexBufferData),
0
);
// ...
}
코드 예제: Direct3D 12
// Direct3D 12: One buffer to accommodate different types of resources
ComPtr<ID3D12Resource> m_spUploadBuffer;
UINT8* m_pDataBegin = nullptr; // starting position of upload buffer
UINT8* m_pDataCur = nullptr; // current position of upload buffer
UINT8* m_pDataEnd = nullptr; // ending position of upload buffer
void main()
{
//
// Initialize an upload buffer
//
InitializeUploadBuffer(64 * 1024);
// ...
}
void DrawFrame()
{
// ...
// Set vertices data to the upload buffer.
float vertices[] = ...;
UINT verticesOffset = 0;
ThrowIfFailed(
SetDataToUploadBuffer(
vertices, sizeof(float), sizeof(vertices) / sizeof(float),
sizeof(float),
verticesOffset
));
// Set constant data to the upload buffer.
float constants[] = ...;
UINT constantsOffset = 0;
ThrowIfFailed(
SetDataToUploadBuffer(
constants, sizeof(float), sizeof(constants) / sizeof(float),
D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT,
constantsOffset
));
// Create vertex buffer views for the new binding model.
D3D12_VERTEX_BUFFER_VIEW vertexBufferViewDesc = {
m_spUploadBuffer->GetGPUVirtualAddress() + verticesOffset,
sizeof(vertices), // size
sizeof(float) * 4, // stride
};
commandList->IASetVertexBuffers(
0,
1,
&vertexBufferViewDesc,
));
// Create constant buffer views for the new binding model.
D3D12_CONSTANT_BUFFER_VIEW_DESC constantBufferViewDesc = {
m_spUploadBuffer->GetGPUVirtualAddress() + constantsOffset,
sizeof(constants) // size
};
d3dDevice->CreateConstantBufferView(
&constantBufferViewDesc,
...
));
// Continue command list building and execution ...
}
//
// Create an upload buffer and keep it always mapped.
//
HRESULT InitializeUploadBuffer(SIZE_T uSize)
{
HRESULT hr = d3dDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES( D3D12_HEAP_TYPE_UPLOAD ),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer( uSize ),
D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,
IID_PPV_ARGS( &m_spUploadBuffer ) );
if (SUCCEEDED(hr))
{
void* pData;
//
// No CPU reads will be done from the resource.
//
CD3DX12_RANGE readRange(0, 0);
m_spUploadBuffer->Map( 0, &readRange, &pData );
m_pDataCur = m_pDataBegin = reinterpret_cast< UINT8* >( pData );
m_pDataEnd = m_pDataBegin + uSize;
}
return hr;
}
//
// Sub-allocate from the buffer, with offset aligned.
//
HRESULT SuballocateFromBuffer(SIZE_T uSize, UINT uAlign)
{
m_pDataCur = reinterpret_cast< UINT8* >(
Align(reinterpret_cast< SIZE_T >(m_pDataCur), uAlign)
);
return (m_pDataCur + uSize > m_pDataEnd) ? E_INVALIDARG : S_OK;
}
//
// Place and copy data to the upload buffer.
//
HRESULT SetDataToUploadBuffer(
const void* pData,
UINT bytesPerData,
UINT dataCount,
UINT alignment,
UINT& byteOffset
)
{
SIZE_T byteSize = bytesPerData * dataCount;
HRESULT hr = SuballocateFromBuffer(byteSize, alignment);
if (SUCCEEDED(hr))
{
byteOffset = UINT(m_pDataCur - m_pDataBegin);
memcpy(m_pDataCur, pData, byteSize);
m_pDataCur += byteSize;
}
return hr;
}
//
// Align uLocation to the next multiple of uAlign.
//
UINT Align(UINT uLocation, UINT uAlign)
{
if ( (0 == uAlign) || (uAlign & (uAlign-1)) )
{
ThrowException("non-pow2 alignment");
}
return ( (uLocation + (uAlign-1)) & ~(uAlign-1) );
}
도우미 구조 CD3DX12_HEAP_PROPERTIES 및 CD3DX12_RESOURCE_DESC 사용합니다.
상수
업로드 또는 읽기 저장 힙 내에서 상수, 꼭짓점 및 인덱스를 설정하려면 다음 API를 사용합니다.
- ID3D12Device::CreateConstantBufferView
- ID3D12GraphicsCommandList::IASetVertexBuffers
- ID3D12GraphicsCommandList::IASetIndexBuffer
리소스
리소스는 GPU 실제 메모리의 사용을 추상화하는 Direct3D 개념입니다. 리소스에는 실제 메모리에 액세스하기 위한 GPU 가상 주소 공간이 필요합니다. 리소스 생성은 자유 스레드 방식으로 진행됩니다.
Direct3D 12에는 가상 주소 만들기 및 유연성과 관련하여 세 가지 유형의 리소스가 있습니다.
커밋된 리소스
커밋된 리소스는 세대에 걸쳐 Direct3D 리소스의 가장 일반적인 아이디어입니다. 이러한 리소스를 만들면 전체 리소스에 맞는 충분히 큰 암시적 힙에 해당하는 가상 주소 범위가 할당되고, 가상 주소 범위가 힙이 캡슐화하는 실제 메모리로 커밋됩니다. 이전 Direct3D 버전과 기능 패리티를 일치하려면 암시적 힙 속성을 전달해야 합니다. ID3D12Device::CreateCommittedResource를 참조하세요.
예약된 리소스
예약된 리소스는 Direct3D 11 타일형 리소스와 동일합니다. 만들 때 가상 주소 범위만 할당되고 힙에 매핑되지 않습니다. 애플리케이션은 나중에 이러한 리소스를 힙에 매핑합니다. 이러한 리소스의 기능은 UpdateTileMappings를 사용하여 64KB 타일 세분성의 힙에 매핑될 수 있으므로 현재 Direct3D 11에서 변경되지 않습니다. ID3D12Device::CreateReservedResource를 참조하세요.
배치된 리소스
Direct3D 12의 새로운 기능으로 리소스와 별도로 힙을 만들 수 있습니다. 그런 다음, 단일 힙 내에서 여러 리소스를 찾을 수 있습니다. 타일형 또는 예약된 리소스를 만들지 않고도 이 작업을 수행할 수 있으므로 애플리케이션에서 직접 만들 수 있는 모든 리소스 종류에 대한 기능을 사용할 수 있습니다. 여러 리소스가 겹칠 수 있으며 실제 메모리를 올바르게 다시 사용하려면 ID3D12GraphicsCommandList::ResourceBarrier를 사용해야 합니다. ID3D12Device::CreatePlacedResource를 참조하세요.
리소스 크기 리플렉션
리소스 크기 리플렉션을 사용하여 힙에서 알 수 없는 텍스처 레이아웃이 필요한 공간 텍스처의 양을 이해해야 합니다. 대개 편의상 버퍼도 지원됩니다.
리소스를 더 조밀하게 압축하는 데 도움이 되도록 주요 맞춤 불일치를 알고 있어야 합니다.
예를 들어 1바이트 버퍼가 있는 단일 요소 배열은 버퍼 가 64KB에만 정렬될 수 있으므로 크기가 64KB이고 맞춤이 64KB입니다.
또한 2개의 단일 텍셀 64KB 정렬 텍스처와 1개의 단일 텍셀 4MB 정렬 텍스처가 있는 3요소 배열은 배열 순서에 따라 다른 크기를 보고합니다. 4MB 맞춤 텍스처가 중간에 있으면 결과 크기 는 12MB입니다. 그렇지 않으면 결과 크기는 8MB입니다. 반환되는 맞춤은 항상 4MB로, 리소스 배열에 있는 모든 맞춤의 상위 집합입니다.
다음 API를 참조하세요.
버퍼 맞춤
버퍼 맞춤 제한은 Direct3D 11에서 변경되지 않았습니다. 특히 다음과 같습니다.
- 다중 샘플 텍스처의 4MB
- 단일 샘플 질감 및 버퍼의 64KB