D3D12엔진 개발-폰트 렌더링, Texture에 실시간으로 써넣기

D3D12용 폰트 렌더링 코드를 작성했다. 오래전부터 써온 방식인데 D3D9, D3D11, D3D12에 따라서 구현은 달라질수밖에 없다.

d3d12_font_rendering

근간 전략은  동일 내용의 텍스트에 대해서는 텍스쳐로 만들어두고 최대한 재활용한다는 것이다.
즉 “ABCDEF”라는 문자열을 같은 폰트로 렌더링 하는 경우 첫번째 렌더링 시에는 GDI나 DWrite를 이용해 렌더링하고 그걸로 텍스쳐에 써넣지만, 두번때 렌더링시에는 이미 만들어진 텍스쳐와 그 좌표를 캐시로부터 얻어온다.
이 전략을 이전부터 계속 사용해왔고 이번에 D3D12용 엔진을 만들때도 이 전략을 적용해서 만들었다.
캐시에서 이전에 렌더링한 텍스트-텍스쳐 블럭을 찾는건 쉬운 내용이므로 굳이 언급하지 않는다.
중요한건 D3D12에서 어떻게 텍스쳐에 이미지를 카피하느냐이다.

일단 방법은 다음과 같다.
1.일정 사이즈의 텍스쳐(1024×256)를 여러장 만들어서 구성한 텍스쳐 풀을 만든다.이것은 렌더링 가능한 텍스쳐(CPU억세스 불가능)와 렌더링 불가 CPU Write가능한 텍스쳐가 짝으로 이루어진다.
2.GDI또는 DirectWrite를 이용해서 텍스트를 렌더링하고 그 이미지를 DIB로 얻는다.
3.텍스쳐 풀로부터 개별 텍스쳐와 그 텍스쳐 안에서 텍스트가 차지하는 만큼의 공간을 할당받는다. 아래 코드에서 pBlockDesc가 그것이다.
4.CPU쓰기 가능한 텍스쳐를 Map()해서 방금 얻은 영역에다 폰트 이미지를 카피한다.
5.CopyTextureRegion()함수를 이용해서 CPU쓰기 불가한 렌더링용 텍스쳐에 카피한다.
6.폰트용 버텍스버퍼를 이용해서 이 텍스쳐를 렌더링한다.

 

코드는 다음과 같다. 코드의 일부이므로 당연히 빌드는 안된다. 대충 이런식으로 한다는것만 봐두면 된다.

	RECT		rect;
	rect.left = pBlockDesc->wPosX;
	rect.right = pBlockDesc->wPosX + pBlockDesc->wWidth;
	rect.top = pBlockDesc->wPosY;
	rect.bottom = pBlockDesc->wPosY + pBlockDesc->wHeight;
	
	D3D12_RESOURCE_DESC Desc = pBlockDesc->pTexStage->GetDesc();
	
	D3D12_PLACED_SUBRESOURCE_FOOTPRINT Footprint;
	UINT	Rows = 0;
	UINT64	RowSize = 0;
	UINT64	TotalBytes = 0;

	pDevice->GetCopyableFootprints(&Desc,0,1,0,&Footprint,&Rows,&RowSize,&TotalBytes);
	
	HRESULT hr = pBlockDesc->pTexWrite->Map(0, &readRange, reinterpret_cast<void**>(&pMappedPtr));
	if (FAILED(hr))
		__debugbreak();
	
	int		iImagePitch = iFontWidth*4;
	char*	pDest = (char*)pMappedPtr + (rect.left*4) + (rect.top*Footprint.Footprint.RowPitch);
	char*	pOld = pDest;	

	for (DWORD y=0; y<(DWORD)iFontHeight; y++)
	{
		char*	pSrc = (char*)m_pImageBuffer + (iFontHeight-y-1)*iImagePitch;

		for (DWORD x=0; x<(DWORD)iFontWidth; x++) 		{ 			((RGBA*)pDest)->a = 0xff;
			((RGBA*)pDest)->r = ((ARGB*)pSrc)->r;
			((RGBA*)pDest)->g = ((ARGB*)pSrc)->g;
			((RGBA*)pDest)->b = ((ARGB*)pSrc)->b;
			pSrc += 4;
			pDest += 4;
		}
		pDest -= iImagePitch;
		pDest += Footprint.Footprint.RowPitch;

	}
		
	pBlockDesc->pTexWrite->Unmap(0, nullptr);
	
	D3D12_BOX	box;	
	box.front = 0;
	box.back = 1;
	box.left = (UINT)pBlockDesc->wPosX;
	box.right = (UINT)pBlockDesc->wPosX + (UINT)pBlockDesc->wWidth;
	box.top = (UINT)pBlockDesc->wPosY;
	box.bottom = (UINT)pBlockDesc->wPosY + (UINT)pBlockDesc->wHeight;

	D3D12_TEXTURE_COPY_LOCATION	destLocation;
	destLocation.PlacedFootprint = Footprint;
	destLocation.pResource = pBlockDesc->pTexStage;
	destLocation.SubresourceIndex = 0;
	destLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;

	D3D12_TEXTURE_COPY_LOCATION	srcLocation;
	srcLocation.PlacedFootprint = Footprint;
	srcLocation.pResource = pBlockDesc->pTexWrite;
	srcLocation.SubresourceIndex = 0;
	srcLocation.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;

	pResourceManager->CopyTextureRegion(&destLocation,box.left,box.top,0,&srcLocation,&box);


———————————————————————————————————

CopyTextureRegion()은 ID3D12GraphicsCommandList::CopyTextureRegion()과 동일한 형식으로 만들어서 사용하고 있다.

void CD3DResourceManager::CopyTextureRegion (const D3D12_TEXTURE_COPY_LOCATION *pDst,UINT DstX,UINT DstY,UINT DstZ,const D3D12_TEXTURE_COPY_LOCATION *pSrc,const D3D12_BOX* pSrcBox)
{


	if (FAILED(m_pCommandAllocator->Reset()))
		__debugbreak();

	if (FAILED(m_pCommandList->Reset(m_pCommandAllocator,NULL)))
		__debugbreak();

	

	m_pCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(pDst->pResource, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_STATE_COPY_DEST));

	//m_pCommandList->CopyResource(pTexResource,pTexResourceUpload);
	m_pCommandList->CopyTextureRegion(pDst,DstX,DstY,DstZ,pSrc,pSrcBox);

	m_pCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(pDst->pResource, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE));

	m_pCommandList->Close();

	m_pCommandQueue->Execute(1,(ID3D12CommandList**)&m_pCommandList);
	m_pCommandQueue->WaitForExecute();

}

 


D3D12엔진 개발-폰트 렌더링, Texture에 실시간으로 써넣기”에 대한 답글 2개

  1. 잘보았습니다. 그런데 1024×256의 뭔가 미묘한 사이즈의 텍스쳐를 생성하는것에는 따로 이유가 있나요?

    좋아요

    1. 초기에 저 방식을 처음 도입할때는 1024×1024 텍스쳐 1장을 사용하려고 했었습니다. 정확히는 1024×1024 텍스쳐 한장 분량의 메모리를 사용하려고 했었습니다. 그런데 한장이 문자열들의 이미지로 꽉 차면 캐시를 완전히 비우는 방식으로 작동하기 때문에(텍스쳐 한장을 완전 클리어) 1장보다는 여러장을 쓰는 편이 캐시 효율을 높일 수 있습니다. 그래서 1024×256을 4장 쓴거죠.
      요샌 GPU메모리 많으니 1024×1024 네장을 써도 되겠죠.

      좋아요

댓글 남기기