Direct 2D与Direct 3D 11协同工作时遇到的一些问题

c/c++

浏览数:218

2019-3-30

Direct 2D与Direct 3D 11协同工作时遇到的一些问题

1、前言

最近在把游戏引擎的API从DirectX 9升级到DirectX 11,因为两套API之间存在巨大的差异,因此在升级迁移的过程中撞了不少坑,现在主要把Direct 2D和Direct3D 11协同工作时遇到的问题总结一下。

原来的游戏引擎对纹理做处理的时候(如写字、绘制简单图形等)用的都是GDI+这套API,使用的方法也比较傻,就是在内存中存两套完全相同的图片,一套交给Direct 3D作为纹理,一套作为Bitmap保留在GDI+中,当需要对纹理做一些处理的时候,首先在Bitmap中用 GDI+来处理,然后把Direct 3D中的纹理Lock住,然后往里面逐个拷贝像素。

在打算升级到DirectX 11的时候,就已经打算不再使用GDI+了,改为使用Direct 2D,毕竟Direct 2D有硬件加速并且更加底层,而且能够直接和Direct 3D交互。

2、思路

对于Direct 2D和Direct 3D的交互,我的主要思路是在Direct 2D中绘制图片,然后作为纹理让Direct 3D使用,中途参考了MSDN上的文档:

Direct2D and Direct3D Interoperability Overview

我一开始是按照这篇文章中Using Direct2D Content as a Texture这一小节的说明为指导来进行编写的,但是在前面的工作都顺利完成的时候,最后卡在了这里:

hr = m_pD2DFactory->CreateDxgiSurfaceRenderTarget(
        pDxgiSurface,
        &props,
        &m_pRenderTarget
     );

也就是我无论如何都无法创建一个DxgiSurfaceRenderTargethr返回值永远都是INVALIDARGS

而整体的思路很简单,将Direct2D的操作作为纹理让Direct3D能够使用的步骤如下:

  1. Direct 3D创建一个离屏纹理OffscreenTexcture作为Direct 2D的渲染对象;
  2. OffscreenTexcture创建相应的DxgiSurface作为离屏表面;
  3. DxgiSurface创建Direct 2D的RenderTarget

当我们拿到RenderTarget之后就能够和Direct 2D中的其他普通渲染对象一样,进行各种操作了;但问题就在于,我现在无法创建这个渲染对象。

于是我Google了很久,开始怀疑是我的Direct 3D初始化的问题,接着又怀疑是我的显卡驱动问题;找了半天,我注意到上面那篇文章讲的是Direct X 10和Direct 2D的交互——开始怀疑是Direct X版本的问题,于是按着这个思路搜索,终于在Stack Overflow上找到了这样一个回答:

Can Direct2D actually work with a direct3D 11 device?

综合另外一些搜索结果我大致得到了以下结论:

Direct 2D是基于DirectX 10.1的,不能保证它可以和DirectX 10.1以外的API按照上面那篇MSDN的文章的说明来直接交互。

当然,有的地方又说在Windows 8以后Direct 3D 11是可以和Direct 2D交互的,然而我是Windows 10,就我自己的结果来看,是不可以的。

但是我想因为Direct 2D的这个问题把API降级到DirectX 10.1,但另一方面对Direct 2D又不死心——实在是不怎么想继续用GDI+,因此就以Using DirectX 11 With Direct 2D为中心来搜索,终于,搜了大半天让我搜到了下面这篇答案:

Can’t create Direct2D DXGI Surface

下面的回答:

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.

  1. Create a texture with the D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX option in order to allow access to the ID3D11KeyedMutex interface.

  2. Use the GetSharedHandle to get a handle to the texture that can be shared among devices.

  3. Create a Direct3D 10.1 device, ensuring that it is created on the same adapter.

  4. Use OpenSharedResource function on the Direct3D 10.1 device to get a version of the texture for Direct3D 10.1.

  5. Get access to the D3D10 KeyedMutex interface for the texture.

  6. Use the Direct3D 10.1 version of the texture to create the RenderTarget using Direct2D.

  7. 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.

这里给了我两个关键字:GetSharedHandle以及Open SharedResource,终于,在MSDN上又找到了下面这篇文章:

Surface Sharing Between Windows Graphcis APIs

这篇文章介绍了如何在不同的Windows Graphcis API之间共享Surface,这里我只说一下怎么让Direct 2D和Direct 3D进行交互:

  1. 创建Direct 3D 10.1的D3D Device;
  2. 用Direct 3D 11的D3D Device创建纹理SharedTexture并且把纹理描述符的MiscFlags设置为D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX**;
  3. 通过SharedTexture创建IDXGIResource,转换为作为所有API的共享资源;
  4. 通过IDGIResource获得该资源的共享句柄SharedHandle
  5. 此外还需要IDGIResource获取属于Direct 3D 11的Sync SurfaceSync Mutex(IDXGIKeyedMutex);

上面1-5步是完成将Direct 3D 11的纹理作为IDGIRersource共享的工作,接下来是如何将其重新取出来并且“转化”成Direct 10.1版本的Sync Surface

  1. 通过Direct 3D 10.1的D3D Device通过OpenSharedResource将之前用Direct 3D 11存进去的Texture读出来,并且转换为相应的格式的Sumc Surface。(MSDN上直接转换成了ID3D10Texture2D那是因为示例是两个Direct 3D 10.1之间的共享,但是如果是不同API之间的共享的话,这里应该转换成IDXGISurface1**,这是一个巨坑,我在这里调试了好久,程序一直Crash掉);
  2. 此外,还要从读出来的Sync Surface获取转换过后的属于Direct 10.1的Sync Mutex

上面两步间接地完成了“转换”的步骤,而如果要进行实际的操作的话还需要以下两步:

  1. 如果是Direct 3D 11侧需要操作共享表面,那么就必须事先对Direct 3D 11的Sync Mutex进行锁住以保证两套API之间的同步;
  2. 反之;如果是Direct 3D 10.1侧需要操作共享表面,那么就必须事先对Direct 3D 10.1的Sync Mutex进行锁住以保证两套API之间的同步。

3、踩坑

如果说按照上面的步骤来做的话,应该就完成了,但是我在做的时候依然问题不断。

第一个问题就是代码死活创建不了Sync Surface,程序一运行到这里就报错了。

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,我查了很久的问题都没解决。

最终,我仔细检查了一下我的Device,突然发现我的Direct 3D 10的Device用的是ID3D10Device,而我同时又发现还有个ID3D10Device1,我查了一下,恍然大悟,后面那个ID3D10Device1才是Direct 3D 10.1的D3D Device,于是我把原来所有和Direct 3D 10有关的API都换成了Direct 3D 10.1的API就把这个问题以及除了这个问题以外的其他问题都解决了**。

然而另外一个坑就是

IDXGIResource* pDXGIResource = nullptr;
hResult = pTexture->QueryInterface(__uuidof(IDXGIResource), reinterpret_cast<LPVOID*>(&pDXGIResource));
if (FAILED(hResult)) {
    goto failed_release;
}

在转换数据的时候,这里始终Crash掉,当然后面问题的解决依赖了两点:

  1. 上面那个ID3D10Device1的问题;
  2. 原来我用的不是IDXGIResource而是ID3D10Texture2D

最后,还有一个坑是关于WIC(Windows Image Components)的。

因为Direct 2D自身不包括读取加载图片的功能,它依赖于WIC。而当我在创建Bitmap的时候,WIC老是提示我不支持的格式:

auto hResult = pDxgiRenderTarget->CreateBitmapFromWicBitmap(pConverter, &pBitmap);
if (FAILED(hResult)) {
    SafeCOMRelease(pBitmap);
    SafeCOMRelease(pConverter);
    return false;
}

然后我检查了半天,发现了这么一个问题:

我的Converter的初始化是这样的:

hResult = pConverter->Initialize(pSource,
                                 GUID_WICPixelFormat32bppRGBA, 
                                 WICBitmapDitherTypeNone, 
                                 nullptr, 
                                 0.0f, 
                                 WICBitmapPaletteTypeMedianCut);

这里应该使用GUID_WICPixelFormat32bppPRGBA

4、代码

这里简单整理一下整个纹理的加载创建过程的代码:

主过程

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;

    // 通过WIC生成Bitmap
    if (!LoadWICResource(wstrUri, pSource, pConverter)) {
        return false;
    }
    // 使用Direct 3D 11创建一张纹理用以共享
    if (!CreateTexture(pSource, pTexture)) {
        return false;
    }

    // 将纹理进行共享
    if (!MakeSharedResource(pTexture, hResourceShareHandle, pDX11Mutex)) {
        return false;
    }

    // 通过共享的纹理创建DXGI渲染对象以用于Direct 2D
    if (!CreateDxgiRenderTarget(hResourceShareHandle, pDxgiRenderTarget, pDX10Mutex)) {
        return false;
    }

    // 创建Bitmap
    auto hResult = pDxgiRenderTarget->CreateBitmapFromWicBitmap(pConverter, &pBitmap);
    if (FAILED(hResult)) {
        SafeCOMRelease(pBitmap);
        SafeCOMRelease(pConverter);
        return false;
    }
    SafeCOMRelease(pConverter);

    // 请求共享锁
    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;
  
    // 通过路径解码文件
    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);

    //通过读入的图片大小创建相同大小的纹理
    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;
    // 设置该纹理为共享纹理
    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设置为共享纹理
    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) {
    
    // 通过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
    );

    // 通过共享资源创建Direct 2D的渲染对象
    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;
}