はじめに
以下のエントリではスクリーンキャプチャを System.Drawing.Graphics.CopyFromScreen()
を別スレッドで回して行っていました。
ここで触れられているようにネイティブでやった方が速いということもあり、本エントリでは Windows 8 から使える Desktop Duplication API を利用したスクリーンキャプチャの簡単なサンプルを紹介します。
環境
- Windows 8.1
- Unity 5.3.0f4
結果
サンプル
追記(2016/10/28)
Github に整理したものを上げました。
概要
DirectX 11 および Low-Level Native Plugin Interface を通じてテクスチャを取得しますので、詳細については以下のエントリをご参照下さい。
Desktop Duplication API は以下に詳細がまとめられています。
IDXGIOutputDuplication::AcquireNextFrame()
を通じてテクスチャやマウスの情報などが受け取れるので、IDXGIOutputDuplication
のインスタンスをメインディスプレイとなる IDXGIOutput1
から取得し、毎フレームこの関数を呼んで Unity 側のテクスチャへと反映します。
C++ 側のコード
#include <d3d11.h> #include <dxgi1_2.h> #include "IUnityInterface.h" #include "IUnityGraphics.h" #include "IUnityGraphicsD3D11.h" #pragma comment(lib, "dxgi.lib") namespace { IUnityInterfaces* g_unity = nullptr; IDXGIOutputDuplication* g_deskDupl = nullptr; ID3D11Texture2D* g_texture = nullptr; bool g_isPointerVisible = false; int g_pointerX = -1; int g_pointerY = -1; int g_width = -1; int g_height = -1; } extern "C" { UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces* unityInterfaces) { g_unity = unityInterfaces; IDXGIFactory1* factory; CreateDXGIFactory1(__uuidof(IDXGIFactory1), reinterpret_cast<void**>(&factory)); // 全ディスプレイアダプタを調べる IDXGIAdapter1* adapter; for (int i = 0; (factory->EnumAdapters1(i, &adapter) != DXGI_ERROR_NOT_FOUND); ++i) { // アウトプットを一通り調べてメインモニタを探す IDXGIOutput* output; for (int j = 0; (adapter->EnumOutputs(j, &output) != DXGI_ERROR_NOT_FOUND); j++) { DXGI_OUTPUT_DESC outputDesc; output->GetDesc(&outputDesc); MONITORINFOEX monitorInfo; monitorInfo.cbSize = sizeof(MONITORINFOEX); GetMonitorInfo(outputDesc.Monitor, &monitorInfo); if (monitorInfo.dwFlags == MONITORINFOF_PRIMARY) { g_width = monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left; g_height = monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top; auto device = g_unity->Get<IUnityGraphicsD3D11>()->GetDevice(); IDXGIOutput1* output1; output1 = reinterpret_cast<IDXGIOutput1*>(output); output1->DuplicateOutput(device, &g_deskDupl); output->Release(); adapter->Release(); factory->Release(); return; } output->Release(); } adapter->Release(); } factory->Release(); } UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UnityPluginUnload() { g_unity = nullptr; g_deskDupl->Release(); g_deskDupl = nullptr; g_texture = nullptr; g_isPointerVisible = false; g_width = -1; g_height = -1; g_pointerX = -1; g_pointerY = -1; } void UNITY_INTERFACE_API OnRenderEvent(int eventId) { if (g_deskDupl == nullptr || g_texture == nullptr) return; IDXGIResource* resource = nullptr; DXGI_OUTDUPL_FRAME_INFO frameInfo; const UINT timeout = 500; // ms g_deskDupl->AcquireNextFrame(timeout, &frameInfo, &resource); g_isPointerVisible = frameInfo.PointerPosition.Visible; g_pointerX = frameInfo.PointerPosition.Position.x; g_pointerY = frameInfo.PointerPosition.Position.y; ID3D11Texture2D* texture; resource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&texture)); resource->Release(); ID3D11DeviceContext* context; auto device = g_unity->Get<IUnityGraphicsD3D11>()->GetDevice(); device->GetImmediateContext(&context); context->CopyResource(g_texture, texture); g_deskDupl->ReleaseFrame(); } UNITY_INTERFACE_EXPORT UnityRenderingEvent UNITY_INTERFACE_API GetRenderEventFunc() { return OnRenderEvent; } UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetWidth() { return g_width; } UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetHeight() { return g_height; } UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API IsPointerVisible() { return g_isPointerVisible; } UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerX() { return g_pointerX; } UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerY() { return g_pointerY; } UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API SetTexturePtr(void* texture) { g_texture = reinterpret_cast<ID3D11Texture2D*>(texture); } }
Unity 側のコード(C#)
using UnityEngine; using System; using System.Collections; using System.Runtime.InteropServices; public class DesktopCapture : MonoBehaviour { [DllImport ("DesktopCapture")] private static extern int GetWidth(); [DllImport ("DesktopCapture")] private static extern int GetHeight(); [DllImport ("DesktopCapture")] private static extern bool IsPointerVisible(); [DllImport ("DesktopCapture")] private static extern int GetPointerX(); [DllImport ("DesktopCapture")] private static extern int GetPointerY(); [DllImport ("DesktopCapture")] private static extern int SetTexturePtr(IntPtr ptr); [DllImport ("DesktopCapture")] private static extern IntPtr GetRenderEventFunc(); public bool isPointerVisible = false; public int pointerX = 0; public int pointerY = 0; void Start() { var tex = new Texture2D(GetWidth(), GetHeight(), TextureFormat.BGRA32, false); GetComponent<Renderer>().material.mainTexture = tex; SetTexturePtr(tex.GetNativeTexturePtr()); StartCoroutine(OnRender()); } void Update() { isPointerVisible = IsPointerVisible(); pointerX = GetPointerX(); pointerY = GetPointerY(); } IEnumerator OnRender() { for (;;) { yield return new WaitForEndOfFrame(); GL.IssuePluginEvent(GetRenderEventFunc(), 0); } } }
おわりに
Virtual Desktop みたいなアプリがこれで作れるのではないでしょうか。