D3D12엔진개발 – Light Map Object를 위한 Texture Update

SuicaLevelEditor_d3d12_01.png

절대로 움직이지 않는 static한 오브젝트들은 Light Map을 사용한다.
Graphics API를 사용하는 계층 말고 그 위의 상위 계층에서 Light Map의 좌표를 부여하고 각 Texel의 광도를 계산한다.
렌더러에서는 이미지 데이타로 받아서 Light Map Texture를 생성하고 렌더링할 뿐이다. 따라서 D3D11 -> D3D12로 전환되어도 Light Map처리 자체가 바뀌진 않는다. D3D Resource로서의 Light Texture 다루는 방법만 바뀔 뿐.

Light Texture는 당연히 dds파일 같은걸로 들고 있지 않다.
파일 수가 엄청나게 많아지고 용량의 낭비가 심하기 때문에 그렇게는 안한다.
커다란 이미지 한장에 packing해서 담아두고 있고 그 이미지들을 또 별도 포맷의 하나의 파일로 가지고 있다. 따라서 렌더러의 LightMap Object 생성 후에 전달되는 Light Texture 는 D3D Resource가 아닌 포인터와 사이즈의 bytes stream이다.
이걸 D3DResource에 Map()해서(시스템 메모리 포인터를 얻어서) 써넣기를 해야한다.

그런데 D3D12에선 GPU 메모리에 존재하는 리소스에 곧바로 데이타를 써넣을 방법은 없다. 간편하게 전송할 방법은 없다.

반드시 GPU메모리에 있는 ID3D12Resource와 같은 포맷의 Upload전용의 ID3D12Resource를 생성해서 정해진 API – ID3D12GraphicsCommandList::CopyTextureRegion()등..을 사용해서 전송한다.

요약하면

렌더러 바깥에서 Light Texture의 픽셀데이타 업데이트 요청 -> Light Texture와 같은 포맷의 ID3D12Resource를 CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD)속성으로 생성 -> 이쪽을 Map()해서 CPU메모리 포인터를 얻어서 쓰기 -> Unmap() -> CopyTextureRegion()

이렇게 진행된다.
그런데 맵 전체를 이루는 Static한 오브젝트들의 개수는 보통 수백개 이상 된다. 맵을 로딩할때마다 수백개씩의 Light Texture를 저런식으로 업데이트 하면 ID3D12Resource를 생성하고 삭제하는 오버헤드를 무시할 수 없게 된다.

그래서 CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD)속성으로 생성하는(CPU write용의) ID3D12Resource는 하나만 생성해두고 재활용한다.
오브젝트마다 Light Texture의 사이즈가 다른데 매 업데이트 요청마다 이미지의 가로/세로 사이즈를 비교해서 현재의 ID3D12Resource의 사이즈 안에 들어가면 재활용, 새로 요청하는 사이즈가 큰 경우는 새로운 사이즈로 다시 생성하게 한다.
최종적으론 가장 큰 사이즈의 Light Texture의 크기로 맞춰진다.

이 내용은 첨부한 소스코드에서 m_pWorkingTexture->Alloc() 한 줄로 표현된다. 이 함수 안에서 사이즈를 비교하고 재할당할지 유지할지를 결정한다.

이하 업데이트 함수 코드

BOOL __stdcall CBaseTextureMap::CopyImageToTexture(RGBA_PIXEL* pSrc,DWORD dwWidth,DWORD dwHeight,int iAddR,int iAddG,int iAddB)
{
	ID3D12Device*			pDevice = m_pRenderer->INL_GetD3DDevice();
	CD3DResourceManager*	pResourceManager = m_pRenderer->INL_GetResourceManager();

	if ((dwWidth != m_dwWidth) || (dwHeight != m_dwHeight))
		return FALSE;
	
	ID3D12Resource*	pDestResource = m_Texture.GetTexture();
	ID3D12Resource*	pWritableResource = m_pWorkingTexture->Alloc(dwWidth,dwHeight,DXGI_FORMAT_R8G8B8A8_UNORM);

	D3D12_PLACED_SUBRESOURCE_FOOTPRINT Footprint;
	UINT	Rows = 0;
	UINT64	RowSize = 0;
	UINT64	TotalBytes = 0;
	D3D12_RESOURCE_DESC Desc = pDestResource->GetDesc();
	pDevice->GetCopyableFootprints(&Desc,0,1,0,&Footprint,&Rows,&RowSize,&TotalBytes);
	
	char*	pMappedPtr = NULL;
	CD3DX12_RANGE readRange(0, 0);

	HRESULT hr = pWritableResource->Map(0, &readRange, reinterpret_cast<void**>(&pMappedPtr));
	if (FAILED(hr))
		__debugbreak();
	
	UINT	Pitch = Footprint.Footprint.RowPitch;
	DWORD	dwWidthSize = dwWidth<<2; 		

	RGBA*		pDest = (RGBA*)pMappedPtr;
	
	for (DWORD y=0; y<dwHeight; y++)
	{
		for (DWORD x=0; x<dwWidth; x++) 		{ 			int	r = pSrc->r + iAddR;
			int	g = pSrc->g + iAddG;
			int	b = pSrc->b + iAddB;

			if (r > 255)
				r = 255;

			if (r < 0)
				r = 0;
				
			if (g < 0) 				g = 0; 				 			if (g > 255)
				g = 255;

			if (b < 0) 				b = 0; 			if (b > 255)
				b = 255;

			pDest->a = pSrc->a;
			pDest->r = r;
			pDest->g = g;
			pDest->b = b;
	
			pDest++;
			pSrc++;
		}
		pDest = (RGBA*)( ((char*)pDest) - dwWidthSize);
		pDest = (RGBA*)( ((char*)pDest) + Pitch);
	}

	pWritableResource->Unmap(0, nullptr);

	
	D3D12_BOX	box;	
	box.front = 0;
	box.back = 1;
	box.left = 0;
	box.right = dwWidth;
	box.top = 0;
	box.bottom = dwHeight;

	D3D12_TEXTURE_COPY_LOCATION	destLocation;
	destLocation.PlacedFootprint = Footprint;
	destLocation.pResource = pDestResource;
	destLocation.SubresourceIndex = 0;
	destLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;

	D3D12_TEXTURE_COPY_LOCATION	srcLocation;
	srcLocation.PlacedFootprint = Footprint;
	srcLocation.pResource = pWritableResource;
	srcLocation.SubresourceIndex = 0;
	srcLocation.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;

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

	return TRUE;
}

답글 남기기

댓글을 게시하려면 다음의 방법 중 하나를 사용하여 로그인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중