Direct 2D與Direct 3D 11協(xié)同工作時(shí)遇到的一些問(wèn)題
1、前言
最近在把游戲引擎的API從DirectX 9升級(jí)到DirectX 11,因?yàn)閮商譇PI之間存在巨大的差異,因此在升級(jí)遷移的過(guò)程中撞了不少坑,現(xiàn)在主要把Direct 2D和Direct3D 11協(xié)同工作時(shí)遇到的問(wèn)題總結(jié)一下。
原來(lái)的游戲引擎對(duì)紋理做處理的時(shí)候(如寫(xiě)字、繪制簡(jiǎn)單圖形等)用的都是GDI+這套API,使用的方法也比較傻,就是在內(nèi)存中存兩套完全相同的圖片,一套交給Direct 3D作為紋理,一套作為Bitmap保留在GDI+中,當(dāng)需要對(duì)紋理做一些處理的時(shí)候,首先在Bitmap中用 GDI+來(lái)處理,然后把Direct 3D中的紋理Lock住,然后往里面逐個(gè)拷貝像素。
在打算升級(jí)到DirectX 11的時(shí)候,就已經(jīng)打算不再使用GDI+了,改為使用Direct 2D,畢竟Direct 2D有硬件加速并且更加底層,而且能夠直接和Direct 3D交互。
2、思路
對(duì)于Direct 2D和Direct 3D的交互,我的主要思路是在Direct 2D中繪制圖片,然后作為紋理讓Direct 3D使用,中途參考了MSDN上的文檔:
我一開(kāi)始是按照這篇文章中Using Direct2D Content as a Texture這一小節(jié)的說(shuō)明為指導(dǎo)來(lái)進(jìn)行編寫(xiě)的,但是在前面的工作都順利完成的時(shí)候,最后卡在了這里:
hr = m_pD2DFactory->CreateDxgiSurfaceRenderTarget(
pDxgiSurface,
&props,
&m_pRenderTarget
);
也就是我無(wú)論如何都無(wú)法創(chuàng)建一個(gè)DxgiSurfaceRenderTarget,hr返回值永遠(yuǎn)都是INVALIDARGS。
而整體的思路很簡(jiǎn)單,將Direct2D的操作作為紋理讓Direct3D能夠使用的步驟如下:
- Direct 3D創(chuàng)建一個(gè)離屏紋理OffscreenTexcture作為Direct 2D的渲染對(duì)象;
- 對(duì)OffscreenTexcture創(chuàng)建相應(yīng)的DxgiSurface作為離屏表面;
- 對(duì)DxgiSurface創(chuàng)建Direct 2D的RenderTarget。
當(dāng)我們拿到RenderTarget之后就能夠和Direct 2D中的其他普通渲染對(duì)象一樣,進(jìn)行各種操作了;但問(wèn)題就在于,我現(xiàn)在無(wú)法創(chuàng)建這個(gè)渲染對(duì)象。
于是我Google了很久,開(kāi)始懷疑是我的Direct 3D初始化的問(wèn)題,接著又懷疑是我的顯卡驅(qū)動(dòng)問(wèn)題;找了半天,我注意到上面那篇文章講的是Direct X 10和Direct 2D的交互——開(kāi)始懷疑是Direct X版本的問(wèn)題,于是按著這個(gè)思路搜索,終于在Stack Overflow上找到了這樣一個(gè)回答:
綜合另外一些搜索結(jié)果我大致得到了以下結(jié)論:
Direct 2D是基于DirectX 10.1的,不能保證它可以和DirectX 10.1以外的API按照上面那篇MSDN的文章的說(shuō)明來(lái)直接交互。
當(dāng)然,有的地方又說(shuō)在Windows 8以后Direct 3D 11是可以和Direct 2D交互的,然而我是Windows 10,就我自己的結(jié)果來(lái)看,是不可以的。
但是我想因?yàn)镈irect 2D的這個(gè)問(wèn)題把API降級(jí)到DirectX 10.1,但另一方面對(duì)Direct 2D又不死心——實(shí)在是不怎么想繼續(xù)用GDI+,因此就以Using DirectX 11 With Direct 2D為中心來(lái)搜索,終于,搜了大半天讓我搜到了下面這篇答案:
下面的回答:
Direct2D only works when you create a Direct3D 10.1 device, but it can share surfaces with Direct3D 11. All you need to do is create both devices and render all of your Direct2D content to a texture that you share between them. I use this technique in my own applications to use Direct2D with Direct3D 11. It incurs a slight cost, but it is small and constant per frame.
A basic outline of the process you will need to use is:
Create your Direct3D 11 device like you do normally.
Create a texture with the D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX option in order to allow access to the ID3D11KeyedMutex interface.
Use the GetSharedHandle to get a handle to the texture that can be shared among devices.
Create a Direct3D 10.1 device, ensuring that it is created on the same adapter.
Use OpenSharedResource function on the Direct3D 10.1 device to get a version of the texture for Direct3D 10.1.
Get access to the D3D10 KeyedMutex interface for the texture.
Use the Direct3D 10.1 version of the texture to create the RenderTarget using Direct2D.
When you want to render with D2D, use the keyed mutex to lock the texture for the D3D10 device. Then, acquire it in D3D11 and render the texture like you were probably already trying to do.
It's not trivial, but it works well, and it is the way that they intended you to interoperate between them. Windows 8 looks like it will introduce full D3D11 compatibility, so it will be just as simple as you expect.
這里給了我兩個(gè)關(guān)鍵字:GetSharedHandle以及Open SharedResource,終于,在MSDN上又找到了下面這篇文章:
這篇文章介紹了如何在不同的Windows Graphcis API之間共享Surface,這里我只說(shuō)一下怎么讓Direct 2D和Direct 3D進(jìn)行交互:
- 創(chuàng)建Direct 3D 10.1的D3D Device;
- 用Direct 3D 11的D3D Device創(chuàng)建紋理SharedTexture,并且把紋理描述符的MiscFlags設(shè)置為D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX**;
- 通過(guò)SharedTexture創(chuàng)建IDXGIResource,轉(zhuǎn)換為作為所有API的共享資源;
- 通過(guò)IDGIResource獲得該資源的共享句柄SharedHandle;
- 此外還需要IDGIResource獲取屬于Direct 3D 11的Sync Surface的Sync Mutex(IDXGIKeyedMutex);
上面1-5步是完成將Direct 3D 11的紋理作為IDGIRersource共享的工作,接下來(lái)是如何將其重新取出來(lái)并且“轉(zhuǎn)化”成Direct 10.1版本的Sync Surface。
- 通過(guò)Direct 3D 10.1的D3D Device通過(guò)OpenSharedResource將之前用Direct 3D 11存進(jìn)去的Texture讀出來(lái),并且轉(zhuǎn)換為相應(yīng)的格式的Sumc Surface。(MSDN上直接轉(zhuǎn)換成了ID3D10Texture2D那是因?yàn)槭纠莾蓚€(gè)Direct 3D 10.1之間的共享,但是如果是不同API之間的共享的話,這里應(yīng)該轉(zhuǎn)換成IDXGISurface1**,這是一個(gè)巨坑,我在這里調(diào)試了好久,程序一直Crash掉);
- 此外,還要從讀出來(lái)的Sync Surface獲取轉(zhuǎn)換過(guò)后的屬于Direct 10.1的Sync Mutex;
上面兩步間接地完成了“轉(zhuǎn)換”的步驟,而如果要進(jìn)行實(shí)際的操作的話還需要以下兩步:
- 如果是Direct 3D 11側(cè)需要操作共享表面,那么就必須事先對(duì)Direct 3D 11的Sync Mutex進(jìn)行鎖住以保證兩套API之間的同步;
- 反之;如果是Direct 3D 10.1側(cè)需要操作共享表面,那么就必須事先對(duì)Direct 3D 10.1的Sync Mutex進(jìn)行鎖住以保證兩套API之間的同步。
3、踩坑
如果說(shuō)按照上面的步驟來(lái)做的話,應(yīng)該就完成了,但是我在做的時(shí)候依然問(wèn)題不斷。
第一個(gè)問(wèn)題就是代碼死活創(chuàng)建不了Sync Surface,程序一運(yùn)行到這里就報(bào)錯(cuò)了。
float fDpiX = 0.0f;
float fDpiY = 0.0f;
m_pD2DFactory->GetDesktopDpi(&fDpiX, &fDpiY);
auto dsProps = D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_HARDWARE,
D2D1::PixelFormat(DXGI_FORMAT_R8G8B8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
fDpiX,
fDpiY
);
hResult = m_pD2DFactory->CreateDxgiSurfaceRenderTarget(
pDxgiSurface,
&dsProps,
&pDxgiRenderTarget
);
if (FAILED(hResult)) {
goto failed_release;
}
代碼大概是這樣的,在m_pD2DFactory->CreateDxgiSurfaceRenderTarget這里,hResult一直是INVALIDARGS,我查了很久的問(wèn)題都沒(méi)解決。
最終,我仔細(xì)檢查了一下我的Device,突然發(fā)現(xiàn)我的Direct 3D 10的Device用的是ID3D10Device,而我同時(shí)又發(fā)現(xiàn)還有個(gè)ID3D10Device1,我查了一下,恍然大悟,后面那個(gè)ID3D10Device1才是Direct 3D 10.1的D3D Device,于是我把原來(lái)所有和Direct 3D 10有關(guān)的API都換成了Direct 3D 10.1的API就把這個(gè)問(wèn)題以及除了這個(gè)問(wèn)題以外的其他問(wèn)題都解決了**。
然而另外一個(gè)坑就是
IDXGIResource* pDXGIResource = nullptr;
hResult = pTexture->QueryInterface(__uuidof(IDXGIResource), reinterpret_cast<LPVOID*>(&pDXGIResource));
if (FAILED(hResult)) {
goto failed_release;
}
在轉(zhuǎn)換數(shù)據(jù)的時(shí)候,這里始終Crash掉,當(dāng)然后面問(wèn)題的解決依賴了兩點(diǎn):
- 上面那個(gè)ID3D10Device1的問(wèn)題;
- 原來(lái)我用的不是IDXGIResource而是ID3D10Texture2D。
最后,還有一個(gè)坑是關(guān)于WIC(Windows Image Components)的。
因?yàn)镈irect 2D自身不包括讀取加載圖片的功能,它依賴于WIC。而當(dāng)我在創(chuàng)建Bitmap的時(shí)候,WIC老是提示我不支持的格式:
auto hResult = pDxgiRenderTarget->CreateBitmapFromWicBitmap(pConverter, &pBitmap);
if (FAILED(hResult)) {
SafeCOMRelease(pBitmap);
SafeCOMRelease(pConverter);
return false;
}
然后我檢查了半天,發(fā)現(xiàn)了這么一個(gè)問(wèn)題:
我的Converter的初始化是這樣的:
hResult = pConverter->Initialize(pSource,
GUID_WICPixelFormat32bppRGBA,
WICBitmapDitherTypeNone,
nullptr,
0.0f,
WICBitmapPaletteTypeMedianCut);
這里應(yīng)該使用GUID_WICPixelFormat32bppPRGBA。
4、代碼
這里簡(jiǎn)單整理一下整個(gè)紋理的加載創(chuàng)建過(guò)程的代碼:
主過(guò)程
bool IrisD2DResourceManager::LoadBitmapFromFile(
const std::wstring& wstrUri,
ID2D1RenderTarget*& pDxgiRenderTarget,
ID3D11Texture2D*& pTexture,
ID2D1Bitmap*& pBitmap,
HANDLE& hResourceShareHandle,
IDXGIKeyedMutex*& pDX10Mutex,
IDXGIKeyedMutex*& pDX11Mutex)
{
IWICBitmapFrameDecode* pSource = nullptr;
IWICFormatConverter* pConverter = nullptr;
pBitmap = nullptr;
pDxgiRenderTarget = nullptr;
pTexture = nullptr;
hResourceShareHandle = nullptr;
// 通過(guò)WIC生成Bitmap
if (!LoadWICResource(wstrUri, pSource, pConverter)) {
return false;
}
// 使用Direct 3D 11創(chuàng)建一張紋理用以共享
if (!CreateTexture(pSource, pTexture)) {
return false;
}
// 將紋理進(jìn)行共享
if (!MakeSharedResource(pTexture, hResourceShareHandle, pDX11Mutex)) {
return false;
}
// 通過(guò)共享的紋理創(chuàng)建DXGI渲染對(duì)象以用于Direct 2D
if (!CreateDxgiRenderTarget(hResourceShareHandle, pDxgiRenderTarget, pDX10Mutex)) {
return false;
}
// 創(chuàng)建Bitmap
auto hResult = pDxgiRenderTarget->CreateBitmapFromWicBitmap(pConverter, &pBitmap);
if (FAILED(hResult)) {
SafeCOMRelease(pBitmap);
SafeCOMRelease(pConverter);
return false;
}
SafeCOMRelease(pConverter);
// 請(qǐng)求共享鎖
hResult = pDX10Mutex->AcquireSync(0, INFINITE);
// 把Bitmap繪制到Sync Surface上
pDxgiRenderTarget->BeginDraw();
pDxgiRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
auto siSize = pBitmap->GetSize();
auto ptTop = D2D1::Point2F(0.0f, 0.0f);
pDxgiRenderTarget->DrawBitmap(pBitmap,
D2D1::RectF(
ptTop.x,
ptTop.y,
ptTop.x + siSize.width,
ptTop.y + siSize.height
)
);
hResult = pDxgiRenderTarget->EndDraw();
// 釋放共享鎖
pDX10Mutex->ReleaseSync(1);
if (FAILED(hResult)) {
return false;
}
return true;
}
LoadWICResource
bool IrisD2DResourceManager::LoadWICResource(const std::wstring& wstrUri, IWICBitmapFrameDecode*& pSource, IWICFormatConverter*& pConverter) {
IWICBitmapDecoder* pDecoder = nullptr;
// 通過(guò)路徑解碼文件
auto hResult = m_pWICImagingFactory->CreateDecoderFromFilename(
wstrUri.c_str(),
nullptr,
GENERIC_READ,
WICDecodeMetadataCacheOnLoad,
&pDecoder
);
if (FAILED(hResult)) {
goto failed_release;
}
// 獲取圖像(暫不考慮GIF)
hResult = pDecoder->GetFrame(0, &pSource);
if (FAILED(hResult))
{
goto failed_release;
}
// 生成Converter
hResult = m_pWICImagingFactory->CreateFormatConverter(&pConverter);
if (FAILED(hResult))
{
goto failed_release;
}
// 初始化Converter
hResult = pConverter->Initialize(pSource,
GUID_WICPixelFormat32bppPRGBA,
WICBitmapDitherTypeNone,
nullptr,
0.0f,
WICBitmapPaletteTypeMedianCut);
if (FAILED(hResult))
{
goto failed_release;
}
SafeCOMRelease(pDecoder);
return true;
failed_release:
SafeCOMRelease(pDecoder);
return false;
}
CreateTexture
bool IrisD2DResourceManager::CreateTexture(IWICBitmapFrameDecode*& pSource, ID3D11Texture2D*& pTexture) {
unsigned int nWidth = 0;
unsigned int nHeight = 0;
pSource->GetSize(&nWidth, &nHeight);
//通過(guò)讀入的圖片大小創(chuàng)建相同大小的紋理
D3D11_TEXTURE2D_DESC texDesc;
texDesc.ArraySize = 1;
texDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
texDesc.CPUAccessFlags = 0;
texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
texDesc.Height = nWidth;
texDesc.Width = nHeight;
texDesc.MipLevels = 1;
// 設(shè)置該紋理為共享紋理
texDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;
texDesc.Usage = D3D11_USAGE_DEFAULT;
texDesc.SampleDesc.Count = 1;
texDesc.SampleDesc.Quality = 0;
texDesc.Usage = D3D11_USAGE_DEFAULT;
auto hResult = IrisD3DResourceManager::Instance()->
GetD3D11Device()->
CreateTexture2D(&texDesc, nullptr, &pTexture);
if (FAILED(hResult)) {
SafeCOMRelease(pSource);
SafeCOMRelease(pTexture);
return false;;
}
SafeCOMRelease(pSource);
return true;
}
MakeSharedResource
bool IrisD2DResourceManager::MakeSharedResource(ID3D11Texture2D* pTexture, HANDLE& hResourceShareHandle, IDXGIKeyedMutex*& pDX11Mutex) {
// 獲取屬于D3D 11的共享鎖
pDX11Mutex = nullptr;
auto hResult = pTexture->QueryInterface(__uuidof(IDXGIKeyedMutex), (LPVOID*)&pDX11Mutex);
if (FAILED(hResult) || (pDX11Mutex == nullptr)) {
goto failed_release;
return false;
}
IDXGIResource* pDXGIResource = nullptr;
hResult = pTexture->QueryInterface(__uuidof(IDXGIResource), reinterpret_cast<LPVOID*>(&pDXGIResource));
if (FAILED(hResult)) {
goto failed_release;
}
// 將pTexture設(shè)置為共享紋理
hResourceShareHandle = nullptr;
hResult = pDXGIResource->GetSharedHandle(&hResourceShareHandle);
if (FAILED(hResult)) {
goto failed_release;
}
SafeCOMRelease(pDXGIResource);
return true;
failed_release:
SafeCOMRelease(pDX11Mutex);
SafeCOMRelease(pDXGIResource);
return false;
}
CreateDxgiRenderTarget
bool IrisD2DResourceManager::CreateDxgiRenderTarget(HANDLE hResourceShareHandle, ID2D1RenderTarget*& pDxgiRenderTarget, IDXGIKeyedMutex*& pDX10Mutex) {
// 通過(guò)SharedHandle獲取共享資源(Sync Surface)
IDXGISurface1* pDxgiSurface = nullptr;
auto hResult = IrisD3DResourceManager::Instance()->
GetD3D10Device()->
OpenSharedResource(
hResourceShareHandle,
__uuidof(IDXGISurface1),
reinterpret_cast<LPVOID*>(&pDxgiSurface)
);
if (FAILED(hResult)) {
goto failed_release;
}
// 獲取屬于D3D 10.1的共享鎖
pDX10Mutex = nullptr;
hResult = pDxgiSurface->QueryInterface(__uuidof(IDXGIKeyedMutex), reinterpret_cast<LPVOID*>(&pDX10Mutex));
if (FAILED(hResult) || pDX10Mutex == nullptr) {
goto failed_release;
}
float fDpiX = 0.0f;
float fDpiY = 0.0f;
m_pD2DFactory->GetDesktopDpi(&fDpiX, &fDpiY);
auto dsProps = D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_HARDWARE,
D2D1::PixelFormat(DXGI_FORMAT_R8G8B8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
fDpiX,
fDpiY
);
// 通過(guò)共享資源創(chuàng)建Direct 2D的渲染對(duì)象
hResult = m_pD2DFactory->CreateDxgiSurfaceRenderTarget(
pDxgiSurface,
&dsProps,
&pDxgiRenderTarget
);
if (FAILED(hResult)) {
goto failed_release;
}
SafeCOMRelease(pDxgiSurface);
return true;
failed_release:
SafeCOMRelease(pDxgiSurface);
SafeCOMRelease(pDX10Mutex);
SafeCOMRelease(pDxgiRenderTarget);
return false;
}