通过 HWND 获取 DXGI 交换链
Getting DXGI swapchain by HWND
有一个简单的应用程序可用作 MS RDP 会话的管理器。您打开到不同计算机的 RDP 会话,然后您可以在公共监控面板中查看它们的预览。
OLE 控件用于组织 RDP 会话。
CWnd::CreateControl(CLSID_MsRdpClient6NotSafeForScripting, ....
之前我只是通过 PrintWindow 复制 RDP window 然后在预览中显示它。
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 绘制 window 内容。
我考虑过使用 DirectX 函数挂钩来获取图片,但它看起来很荒谬。这是一个很大的开销。我可以完全控制应用程序。我可以访问 RDP OPWindowClass HWND。我什至可以通过 ShowWindow 等来控制它们
有什么方法可以获取连接到 HWND 的 DirectX 对象(如 IDXGISwapChain1 或 ID2D1HwndRenderTarget)?
我找不到解决问题的记录 API。因此,我挂钩 IDXGISwapChain::Present 方法并捕获黑框 window 帧。
请务必记住,在 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
如有任何问题,请随时与我联系!
有一个简单的应用程序可用作 MS RDP 会话的管理器。您打开到不同计算机的 RDP 会话,然后您可以在公共监控面板中查看它们的预览。
OLE 控件用于组织 RDP 会话。
CWnd::CreateControl(CLSID_MsRdpClient6NotSafeForScripting, ....
之前我只是通过 PrintWindow 复制 RDP window 然后在预览中显示它。
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 绘制 window 内容。
我考虑过使用 DirectX 函数挂钩来获取图片,但它看起来很荒谬。这是一个很大的开销。我可以完全控制应用程序。我可以访问 RDP OPWindowClass HWND。我什至可以通过 ShowWindow 等来控制它们
有什么方法可以获取连接到 HWND 的 DirectX 对象(如 IDXGISwapChain1 或 ID2D1HwndRenderTarget)?
我找不到解决问题的记录 API。因此,我挂钩 IDXGISwapChain::Present 方法并捕获黑框 window 帧。
请务必记住,在 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
如有任何问题,请随时与我联系!