通过 HWND 获取 DXGI 交换链

4

有一个简单的应用程序,它可以作为MS RDP会话的管理器。您可以打开不同计算机的RDP会话,然后在公共监视面板中查看它们的预览。

使用OLE控件来组织RDP会话。

CWnd::CreateControl(CLSID_MsRdpClient6NotSafeForScripting, ....

以前我只是通过PrintWindow复制RDP窗口,然后在预览中显示它。

PrintWindow(hWnd, hCompatibleDC,0);

在Windows 10中它不起作用。Microsoft Spy++显示以下图片。
Window "Output Painter Window" OPContainerClass
   Window "Output Painter Child Window" OPWindowClass (Invisible)
   Window "Output Painter DX Child Window" OPWindowClass 

在较旧的Windows系统上,“Output Painter Child Window”主要可见,在这种情况下,PrintWindow有效。

同时,在Windows 10上,如果我删除DXGI.dll,远程桌面协议(RDP)将完全无法工作。因此,我认为PrintWindow无法正常工作是因为RDP使用DirectX绘制窗口内容。

我考虑过使用DirectX函数挂钩来获取图像,但这看起来很荒谬。这是一种巨大的开销。我可以完全控制应用程序。我可以访问RDP OPWindowClass HWND,甚至可以通过ShowWindow等方式对它们进行控制。

是否有任何方法可以获得与HWND相关联的DirectX对象(例如IDXGISwapChain1或ID2D1HwndRenderTarget)?


1
你仍然需要挂钩DX CreateSwapChain函数,并从那里找出HWND并保持swapchain-> HWNDs的映射。 - VuVirt
@VuVirt,是的,这是我的第一个想法,但它会带来很大的开销。我正在尝试避免它。 - Eugen
1
我认为你没有其他选择。我参与过这样的项目,使用钩子绝对可行,而且不会带来太大的负担。 - VuVirt
由于无法在一个进程中挂钩32位和64位,所以出现了问题。 - Wilson Chen
1
@Eugen 你解决了这个问题吗? - tunafish24
@tunafish24,是的,我看到了。很抱歉回复晚了。请查看我的答案和Github项目。 - Eugen
1个回答

2

我找不到一个记录的API来解决这个问题。所以,我钩住了IDXGISwapChain::Present方法并捕获了黑盒窗口框架。

重要的是要记住,在DirectX 11中,当你捕获帧时,必须使用创建交换链时使用的相同的DirectX设备和设备上下文对象。这是可能的。

DirectX 12中,很难获取刚刚呈现的当前帧。所以,我捕获了之前呈现的帧。对我来说,这不是一个大问题。

DirectX 11钩子算法是:

// Create a sample window.
...

// Create a swap chain for that window.
...

// Get the swap chain pointer.
// I do it via a DirectX helper class.
std::uintptr_t* swapChainPointer =
   static_cast<std::uintptr_t*>(static_cast<void*>
   (d3d11Helper.GetSwapChain()));

// Get the virtual table pointer.
std::uintptr_t* virtualTablePointer =
   reinterpret_cast<std::uintptr_t*>(swapChainPointer[0]);

// Get the "Present" method pointer.
// "Present" has index 8 because:
// - "Present" is the first original virtual method of IDXGISwapChain.
// - IDXGISwapChain is based on IDXGIDeviceSubObject
//   which has 1 original virtual method (GetDevice).
// - IDXGIDeviceSubObject is based on IDXGIObject
//   which has 4 original virtual methods (SetPrivateData,..).
// - IDXGIObject is based on IUnknown
//   which has 3 original virtual methods (QueryInterface, ...). 
// So, 0 + 1 + 4 + 3 = 8.
presentPointer_ = static_cast<std::uint64_t>(virtualTablePointer[8]);

// Use the **MinHook** or **PolyHook 2** library
// to hook the method with a trampoline.
...

DirectX 11 帧捕获算法是:

// Get the swap chain DirectX 11 device.
Microsoft::WRL::ComPtr<ID3D11Device> d3d11Device;
hr = swapChain->GetDevice(__uuidof(ID3D11Device), &d3d11Device);

// Also, get the device context.
Microsoft::WRL::ComPtr<ID3D11DeviceContext> d3d11DeviceContext;
d3d11Device->GetImmediateContext(&d3d11DeviceContext);

// Get the swap chain texture.
Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11SwapChainTexture;
hr = swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D),
  &d3d11SwapChainTexture);

// Then you can create texture
// (staging texture if you want to copy from the GPU to the CPU).
...

// Copy the frame.
// Here, the destination is a staging texture.
d3d11DeviceContext->CopyResource(d3d11StagingTexture.Get(),
  d3d11SwapChainTexture.Get());

// Access the data from the CPU.
D3D11_MAPPED_SUBRESOURCE mappedSubresource;
UINT subresource = D3D11CalcSubresource(0, 0, 0);
hr = d3d11DeviceContext->Map(d3d11StagingTexture.Get(), subresource,
  D3D11_MAP_READ_WRITE, 0, &mappedSubresource);

以下是DirectX 12钩子算法的亮点。

// Overall, the algorithm is similar to the DirectX 11 one
// but you also need the command queue which was used to
// create the black box window swap chain.
// The simplest way is to find the offset from the swap chain
// object start when you create a sample swap chain.

std::uintptr_t i = 0;
const char* start = static_cast<char*>(
    static_cast<void*>(d3d12Helper.GetSwapChain()));
while (true) {
  if (reinterpret_cast<std::uintptr_t>(d3d12Helper.GetCommandQueue()) ==
      *static_cast<const std::uintptr_t*>(
        static_cast<const void*>(start + i))) {
    commandQueueOffset_ = i;
    break;
  }
  ++i;
}

以下是DirectX 12捕获算法的亮点。

// You need a readback resource property
// to get the frame from the GPU to CPU.  
Microsoft::WRL::ComPtr<ID3D12Resource> readbackResource_;


if (readbackResource_.Get()) {

  // If you are here, then the capture method was already called.

  // If the pointer is nullptr, map it. With DirectX 12,
  // it is not necessary to unmap it between frames.    
  if (readbackData_ == nullptr) {
    hr = readbackResource_->Map(0, nullptr, &readbackData_);
    if (FAILED(hr)) {
      return;
    }
  }

  // static_cast<std::uint8_t*>(readbackData_)
  // UINT frameRowPitch = readbackDataPitch_;
  // UINT frameWidth = frameRowPitch / 4;
  // UINT frameHeight = readbackDataHeight_;

} else {

   // Create the read back resource
   ...
}

// Initialize the copying of the swap chain resource
// to your read back resource.
...

// Just find the command queue object.
const char* start = static_cast<char*>(static_cast<void*( 
  swapChain3.Get()));
ID3D12CommandQueue* commandQueue =
  reinterpret_cast<ID3D12CommandQueue*>(
    *static_cast<const std::uintptr_t*>(
      static_cast<const void*>(start + commandQueueOffset_)));

// ... then execute the command list
// to copy the swap chain texture to the read back texture.
ID3D12CommandList* commandLists[] = {copyCommandList.Get()};
commandQueue->ExecuteCommandLists(ARRAYSIZE(commandLists), commandLists);

// The read back texture does not contain the correct picture yet!
// You will get it when the capture method is called next time.

请查看并收藏完整的示例项目,链接在这里:https://github.com/eugen15/directx-present-hook 如果您有任何问题,请不要犹豫与我联系!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接