Convert IDirect3DSurface9 to ID3D11Texture2D

Started by
7 comments, last by VytsL 3 years, 6 months ago

Hello,

I have DX9 IDirect3DSurface9 that is a back buffer with the rendered content. I need to convert it to DX11 ID3D11Texture2D. How can I do that? If I read MSDN correctly, there's some level of interopability between various DX versions.

I found example how to convert render surface to texture in DX9, but I can't figure out how to do it across APIs. StretchRect is removed in DX11 and CopyResource requires ID3D11Resource*.

To clarify what I am doing - I am adding a VR support to an old DX9 game.

Cheers!

EDIT: I was able to copy bytes of DX9 surface to DX11 texture, not sure if there's better way though: There has been a lot of dance above that code to get rid of MSAA


D3DLOCKED_RECT lrSrc{};
 RETURN_IF_D3D_FAILED(bbsfCopy->LockRect(&lrSrc, NULL, D3DLOCK_NO_DIRTY_UPDATE | D3DLOCK_READONLY)); {
   auto onExit = Utils::MakeScopeGuard([&]() { RETURN_IF_D3D_FAILED(bbsfCopy->UnlockRect()); });

   D3D11_MAPPED_SUBRESOURCE mappedDst{};
   auto& dstTexture = mCurrEye == vr::Eye_Left ? mLeftTexture11 : mRightTexture11;
   RETURN_IF_D3D_FAILED(mDeviceContext11->Map(mLeftTexture11, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedDst));
   memcpy(mappedDst.pData, lrSrc.pBits, desc.Width * desc.Height * 4);
 
Advertisement

That's the way I'd do it as well.

The only other thing maybe is porting your dx9 game to dx11, it would reduce to 1 lock, but honestly just for the sake of the 1 lock, it's fine as it is (if it is not bottlenecking).

If u wanted to port, here is a starter…just in case:

https://docs.microsoft.com/en-us/windows/uwp/gaming/porting-considerations

That's it… all the best ?

Thanks - but I do not have source for the game ? It is our mod.

Now I am stuck on this coming from OpenVR Submit:

D3D11 ERROR: ID3D11DeviceContext::CopySubresourceRegion: Cannot invoke CopySubresourceRegion when the Formats of each Resource are not the same or at least castable to each other, unless one format is compressed (DXGI_FORMAT_R9G9B9E5_SHAREDEXP, or DXGI_FORMAT_BC[1,2,3,4,5]_* ) and the source format is similar to the dest according to: BC[1|4] ~= R16G16B16A16|R32G32, BC[2|3|5] ~= R32G32B32A32, R9G9B9E5_SHAREDEXP ~= R32. [ RESOURCE_MANIPULATION ERROR #281: COPYSUBRESOURCEREGION_INVALIDSOURCE]

DX9 texture is: D3DFMT_A8R8G8B8

DX11 texture is: DXGI_FORMAT_B8G8R8A8_UNORM (Per MSDN).

I found, that if I build Release DX11 device, I see no ill effects. Also, if I change to:

DXGI_FORMAT_R8G8B8A8_UNORM error goes away even in Debug, but colors are wrong, meaning I need to reorder bytes somehow.

So I got it all connected and working, but without much surprise, this is the slow part:

Any suggestions how could I make it faster (copy using GPU somehow?)

// This call is quite slow it seems.
  RETURN_IF_D3D_FAILED(mDMR->GetRenderer()->pD3DDevice9->GetRenderTargetData(mBackBufferNoMSAA9, mBackBufferLocable9));
  
  // Memcpy/locking is slow as well
  D3DLOCKED_RECT lrSrc{};
  RETURN_IF_D3D_FAILED(mBackBufferLocable9->LockRect(&lrSrc, nullptr, D3DLOCK_READONLY));
  {
    auto onExit = Utils::MakeScopeGuard([&]() { LOG_IF_D3D_FAILED(mBackBufferLocable9->UnlockRect()); });

    D3D11_MAPPED_SUBRESOURCE mappedDst{};
    auto& dstTexture = mCurrEye == vr::Eye_Left ? mLeftTexture11 : mRightTexture11;
    RETURN_IF_D3D_FAILED(mDeviceContext11->Map(dstTexture, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedDst));

    if (lrSrc.Pitch == mappedDst.RowPitch)
      ::memcpy(mappedDst.pData, lrSrc.pBits, bbDesc.Width * bbDesc.Height * 4);
    else {
      for (auto y = 0u; y < bbDesc.Height; ++y) {
        auto pSrc = static_cast<unsigned char*>(lrSrc.pBits) + (lrSrc.Pitch * y);
        auto pDst = static_cast<unsigned char*>(mappedDst.pData) + (mappedDst.RowPitch * y);
        ::memcpy(pDst, pSrc, lrSrc.Pitch);
      }
    }

    mDeviceContext11->Unmap(dstTexture, 0);
  }

So in the end this is what worked: https://docs.microsoft.com/en-us/windows/win32/direct3darticles/surface-sharing-between-windows-graphics-apis

I replaced D3D9 device with D3D9Ex device and set the queue up. This repo https://github.com/Planimeter/gmcl_openvr​ was helpful in understanding how to actually set it up, because approach is quite confusing otherwise.

memcpy approach actually worked pretty well for Desktop resolutions and single pass. But VR res is a lot higher, and GPU to CPU to GPU approach did not scale.

End result is amazing - copy is pretty much free and I get insane framerates at very high res.

well done and thanks for sharing ?

Looks like you got everything figured out, but I figured I would point out another option that might work for this scenario:

If you use D3D9on12, then everything uses D3D12 resources under the hood. This makes it fairly trivial to grab the D3D12 resource, and copy from that into the VR texture (assuming the VR API you're using supports D3D12). The main catch would be that there might be new bugs or performance issues that pop up from using 9on12.

@MJP definitely, good to know. But now that bb is efficiently (enough) copied to DX11, I have higher priority problem to solve - certain reflections are off, and I've no idea why :D Remember, no source code, assembly only…. + 20 years of beer drinking does not help - and yet, reflections is the last problem to solve, I am so agonizingly close. Hrrrr!!!

This topic is closed to new replies.

Advertisement