sharptooth
sharptooth

Reputation: 170539

How can I reliably identify when Direct3D9 "device" maps onto which Windows GDI "monitor"?

I'm trying to obtain screenshots of multiple monitors using IDirect3DDevice9::GetFrontBufferData() and it works but I cannot figure out how I could identify which IDirect3DDevice9 corresponds to which monitor and so how its output is positioned on Windows "virtual screen".

I tried IDirect3DDevice9::GetViewport() but it always obtains viewport coordinates which have left top coordinate set to "0,0" (and proper width and height) so it cannot be used to identify which monitor is left and which is right.

So far I found that when I call EnumDisplayMonitors() then monitors are enumerated in the order as devices are enumerated when I call IDirect3D9::GetAdapterDisplayMode() and this can be used. However I see not documentation saying whether any order of enumeration is guaranteed for these two functions.

Can I rely on EnumDisplayMonitors() and IDirect3D9::GetAdapterDisplayMode() having the same enumeration order? Is there any better way?

Upvotes: 0

Views: 127

Answers (1)

Chuck Walbourn
Chuck Walbourn

Reputation: 41127

Mapping a Direct3D device's swapchain back to a monitor is surprisingly challenging. Legacy Direct3D has the HMONITOR as noted by Raymond Chen. For a while, you could use IDXGISwapChain::GetContainingOutput but that one has been since deprecated and no longer works like it did.

The 'recommended' solution these days is something like this:

#include <algorithm>
#include <wrl/client.h>

using Microsoft::WRL::ComPtr;

namespace
{
    inline long ComputeIntersectionArea(
        long ax1, long ay1, long ax2, long ay2,
        long bx1, long by1, long bx2, long by2) noexcept
    {
        return std::max(0l, std::min(ax2, bx2) - std::max(ax1, bx1)) * std::max(0l, std::min(ay2, by2) - std::max(ay1, by1));
    }
}

// Get the retangle bounds of the app window
RECT windowBounds;
if (!GetWindowRect(/* HWND for swapchain window */, &windowBounds))
    // error

const long ax1 = windowBounds.left;
const long ay1 = windowBounds.top;
const long ax2 = windowBounds.right;
const long ay2 = windowBounds.bottom;

ComPtr<IDXGIOutput> bestOutput;
long bestIntersectArea = -1;

ComPtr<IDXGIAdapter> adapter;
for (UINT adapterIndex = 0;
    SUCCEEDED(m_dxgiFactory->EnumAdapters(adapterIndex, adapter.ReleaseAndGetAddressOf()));
    ++adapterIndex)
{
    ComPtr<IDXGIOutput> output;
    for (UINT outputIndex = 0;
        SUCCEEDED(adapter->EnumOutputs(outputIndex, output.ReleaseAndGetAddressOf()));
        ++outputIndex)
    {
        // Get the rectangle bounds of current output.
        DXGI_OUTPUT_DESC desc;
        ThrowIfFailed(output->GetDesc(&desc));
        const auto& r = desc.DesktopCoordinates;

        // Compute the intersection
        const long intersectArea = ComputeIntersectionArea(ax1, ay1, ax2, ay2, r.left, r.top, r.right, r.bottom);
        if (intersectArea > bestIntersectArea)
        {
            bestOutput.Swap(output);
            bestIntersectArea = intersectArea;
        }
    }
}

if (bestOutput)
{
    // Here is the best answer we came up with
}

Upvotes: 1

Related Questions