HTC ViveでペンタVR上でクリスタが使えるように改良する その3(DesktopCaptureが頻繁に落ちるのを修正する)

はじめに

 前回までは Unity で Desktop Duplication API を使ってスクリーンキャプチャしてみた - 凹みTips で配布されているパッケージ内のDLLをそのまま使用していましたが、何か操作するたびに(動画のシークやウィンドウの最大化など)頻繁に落ちていました。

Desktop Duplication API (Windows) を流し読みして調べたところ、AcquireNextFrame IDXGIOutputDuplication::AcquireNextFrame method (Windows)は結構色々なエラーコードを返してくるようです。
 前回までのソースコードを見る限りエラー処理はされていないので、そこで落ちてるのかなと見当をつけました。

手順

  • VisualStudio2015でファイルの新規作成->プロジェクト
  • VisualC++ のWin32Win32 Console Applicationを選択(名前はDesktopCaptureにしておく)
  • ウィザードでDLLを選択、空のプロジェクトをチェックして完了
  • Source Filesにcppファイルを新規作成
  • cppファイルにソースコードをコピー
  • ビルドターゲットをDebug->Release,x86 -> x64に変更
  • Unity - Manual: Low-level Native Plugin Interfaceからヘッダーファイルのzipを落として伸長
  • \RenderingPluginExample53\RenderingPlugin\Unity内の、IUnityGraphics.hとIUnityGraphicsD3D11.hとIUnityInterface.hを、作ったプロジェクトのフォルダ内(さっき作ったcppがあるフォルダ)にコピー
  • Header Filesに追加->既存の項目で上記3ファイルを選択
  • ビルドするとDesktopCapture.dllが出てくるので、既にインポートしてあるDesktopCapture.dllと差し替える


以下ソースコード
エラーコードDXGI_ERROR_ACCESS_LOSTのみ、g_deskDupl(IDXGIOutputDuplication)を生成し直す必要があると書いてあるため、対処している。

#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"
{
	void CreateDuplicatation()
	{
		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 UnityPluginLoad(IUnityInterfaces* unityInterfaces)
	{
		g_unity = unityInterfaces;
		CreateDuplicatation();
	}

	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
		HRESULT hr = g_deskDupl->AcquireNextFrame(timeout, &frameInfo, &resource);

		// 作り直し
		if (hr == DXGI_ERROR_ACCESS_LOST)
		{
			g_deskDupl->Release();
			g_deskDupl = nullptr;
			CreateDuplicatation();
		}
		// 失敗
		if (hr != S_OK)
		{
			return;
		}

		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エディタ実行や、動画のシーク、CLIP STUDIO PAINTの最大化なども落ちなくなりました。
(クリスタの最大化などは、落ちることはないんですが点滅したりします)

 ちなみに拡大鏡機能ですが、ムフフ動画を見ているときなんかに一部分をズームしたりできるので、なんだか楽しいです!

実行形式も置いておくので、暇な人は試してみてください(さすがにペンタVRとするわけにはいかないので、ペンタViveにしました!)
https://dl.dropboxusercontent.com/u/147606158/PentaVive.zip