diff --git a/Assets/uDesktopDuplication/Examples/Scenes/Toggle Monitors.unity b/Assets/uDesktopDuplication/Examples/Scenes/Toggle Monitors.unity index 8ba0857..9b4363c 100644 Binary files a/Assets/uDesktopDuplication/Examples/Scenes/Toggle Monitors.unity and b/Assets/uDesktopDuplication/Examples/Scenes/Toggle Monitors.unity differ diff --git a/Assets/uDesktopDuplication/Plugins/uDesktopDuplication.dll b/Assets/uDesktopDuplication/Plugins/uDesktopDuplication.dll index f3b1675..21a564d 100644 Binary files a/Assets/uDesktopDuplication/Plugins/uDesktopDuplication.dll and b/Assets/uDesktopDuplication/Plugins/uDesktopDuplication.dll differ diff --git a/Assets/uDesktopDuplication/Scripts/Cursor.cs b/Assets/uDesktopDuplication/Scripts/Cursor.cs index 7dc8943..1681ef4 100644 --- a/Assets/uDesktopDuplication/Scripts/Cursor.cs +++ b/Assets/uDesktopDuplication/Scripts/Cursor.cs @@ -7,35 +7,93 @@ namespace uDesktopDuplication RequireComponent(typeof(Texture))] public class Cursor : MonoBehaviour { - [SerializeField] - Transform cursor; - [SerializeField] - Vector2 modelScale = Vector2.one; - [SerializeField] - Vector2 offset = new Vector2(0.5f, 0.5f); + [SerializeField] Vector2 modelScale = Vector2.one; - private Texture texture_; + Vector3 worldPosition { get; set; } + + private Texture uddTexture_; + private Monitor monitor { get { return uddTexture_.monitor; } } + + private Texture2D pointerTexture_; + private Material cursorMaterial_; void OnEnable() { - texture_ = GetComponent(); + uddTexture_ = GetComponent(); + } + + void Start() + { + var clone = new GameObject(name + " Cursor"); + clone.transform.SetParent(transform); + clone.transform.localPosition = Vector3.zero; + clone.transform.localRotation = Quaternion.identity; + clone.transform.localScale = Vector3.one; + + var filter = clone.AddComponent(); + filter.mesh = GetComponent().sharedMesh; + + var renderer = clone.AddComponent(); + var shader = Shader.Find("uDesktopDuplication/Cursor"); + cursorMaterial_ = new Material(shader); + renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; + renderer.receiveShadows = false; + renderer.motionVectors = false; + renderer.material = cursorMaterial_; } void Update() { - var monitor = texture_.monitor; if (monitor.isPointerVisible) { - var x = (1f * monitor.pointerX / monitor.width - 0.5f) * modelScale.x; - var y = (0.5f - 1f * monitor.pointerY / monitor.height) * modelScale.y; - var iy = texture_.invertY ? +1 : -1; - var localPos = transform.right * x + iy * transform.up * y; - var worldPos = transform.TransformPoint(localPos); - worldPos += cursor.right * offset.x * cursor.localScale.x; - worldPos += -cursor.up * offset.y * cursor.localScale.y; - worldPos += -cursor.forward * 0.001f; - cursor.position = worldPos; + UpdatePosition(); + UpdateTexture(); + UpdateMaterial(); } } + + void UpdatePosition() + { + var x = (1f * monitor.pointerX / monitor.width - 0.5f) * modelScale.x; + var y = (1f * monitor.pointerY / monitor.height - 0.5f) * modelScale.y; + var iy = uddTexture_.invertY ? -1 : +1; + var localPos = transform.right * x + iy * transform.up * y; + worldPosition = transform.TransformPoint(localPos); + } + + void UpdateTexture() + { + pointerTexture_ = new Texture2D(monitor.pointerShapeWidth, monitor.pointerShapeHeight, TextureFormat.BGRA32, false); + pointerTexture_.wrapMode = TextureWrapMode.Clamp; + monitor.UpdatePointerTexture(pointerTexture_.GetNativeTexturePtr()); + cursorMaterial_.SetTexture("_MainTex", pointerTexture_); + } + + void UpdateMaterial() + { + if (uddTexture_.invertX) { + cursorMaterial_.EnableKeyword("INVERT_X"); + } else { + cursorMaterial_.DisableKeyword("INVERT_X"); + } + + if (uddTexture_.invertY) { + cursorMaterial_.EnableKeyword("INVERT_Y"); + } else { + cursorMaterial_.DisableKeyword("INVERT_Y"); + } + + if (monitor.isVertical) { + cursorMaterial_.EnableKeyword("VERTICAL"); + } else { + cursorMaterial_.DisableKeyword("VERTICAL"); + } + + var x = (float)monitor.pointerX / monitor.width; + var y = (float)monitor.pointerY / monitor.height; + var w = (float)monitor.pointerShapeWidth / monitor.width; + var h = (float)monitor.pointerShapeHeight / monitor.height; + cursorMaterial_.SetVector("_PositionScale", new Vector4(x, y, w, h)); + } } } \ No newline at end of file diff --git a/Assets/uDesktopDuplication/Scripts/Lib.cs b/Assets/uDesktopDuplication/Scripts/Lib.cs index 9a5564c..aff5155 100644 --- a/Assets/uDesktopDuplication/Scripts/Lib.cs +++ b/Assets/uDesktopDuplication/Scripts/Lib.cs @@ -5,6 +5,13 @@ using System.Runtime.InteropServices; namespace uDesktopDuplication { +public enum PointerShapeType +{ + MonoChrome = 1, + Color = 2, + MaskedColor = 4, +} + public static class Lib { [DllImport("uDesktopDuplication")] @@ -28,9 +35,21 @@ public static class Lib [DllImport("uDesktopDuplication")] public static extern int GetPointerY(int id); [DllImport("uDesktopDuplication")] + public static extern int GetPointerShapeWidth(int id); + [DllImport("uDesktopDuplication")] + public static extern int GetPointerShapeHeight(int id); + [DllImport("uDesktopDuplication")] + public static extern int GetPointerShapePitch(int id); + [DllImport("uDesktopDuplication")] + public static extern PointerShapeType GetPointerShapeType(int id); + [DllImport("uDesktopDuplication")] + public static extern void UpdatePointerTexture(int id, System.IntPtr ptr); + [DllImport("uDesktopDuplication")] public static extern int SetTexturePtr(int id, IntPtr ptr); [DllImport("uDesktopDuplication")] public static extern int GetErrorCode(); + [DllImport("uDesktopDuplication")] + public static extern void GetErrorMessage(StringBuilder buf, int len); public static string GetName(int id) { @@ -38,6 +57,13 @@ public static class Lib GetName(id, buf, buf.Capacity); return buf.ToString(); } + + public static string GetErrorMessage() + { + var buf = new StringBuilder(64); + GetErrorMessage(buf, buf.Capacity); + return buf.ToString(); + } } } \ No newline at end of file diff --git a/Assets/uDesktopDuplication/Scripts/Manager.cs b/Assets/uDesktopDuplication/Scripts/Manager.cs index 0220dde..2db896e 100644 --- a/Assets/uDesktopDuplication/Scripts/Manager.cs +++ b/Assets/uDesktopDuplication/Scripts/Manager.cs @@ -40,6 +40,9 @@ public class Manager : MonoBehaviour [SerializeField, Tooltip("Set Desktop Duplication API timeout (milliseconds).")] int timeout = 0; + [SerializeField, Tooltip("Output logs given by the plugin.")] + bool outputDebugLog = false; + private Coroutine renderCoroutine_ = null; void Awake() @@ -78,6 +81,10 @@ public class Manager : MonoBehaviour } monitor.shouldBeUpdated = false; } + if (outputDebugLog && Lib.GetErrorCode() != 0) + { + Debug.Log(Lib.GetErrorMessage()); + } } } } diff --git a/Assets/uDesktopDuplication/Scripts/Monitor.cs b/Assets/uDesktopDuplication/Scripts/Monitor.cs index ad34e3e..bb7c3ae 100644 --- a/Assets/uDesktopDuplication/Scripts/Monitor.cs +++ b/Assets/uDesktopDuplication/Scripts/Monitor.cs @@ -66,6 +66,21 @@ public class Monitor get { return Lib.GetPointerY(id); } } + public int pointerShapeWidth + { + get { return Lib.GetPointerShapeWidth(id); } + } + + public int pointerShapeHeight + { + get { return Lib.GetPointerShapeHeight(id); } + } + + public PointerShapeType pointerShapeType + { + get { return Lib.GetPointerShapeType(id); } + } + public bool shouldBeUpdated { get; @@ -91,6 +106,11 @@ public class Monitor { GL.IssuePluginEvent(Lib.GetRenderEventFunc(), id); } + + public void UpdatePointerTexture(System.IntPtr ptr) + { + Lib.UpdatePointerTexture(id, ptr); + } } } \ No newline at end of file diff --git a/Assets/uDesktopDuplication/Shaders/uDD_Common.cginc b/Assets/uDesktopDuplication/Shaders/uDD_Common.cginc index dcf294c..84b073f 100644 --- a/Assets/uDesktopDuplication/Shaders/uDD_Common.cginc +++ b/Assets/uDesktopDuplication/Shaders/uDD_Common.cginc @@ -18,13 +18,18 @@ inline void uddInvertUV(inout float2 uv) #endif } +inline void uddToLinearIfNeeded(inout float3 rgb) +{ + if (!IsGammaSpace()) { + rgb = GammaToLinearSpace(rgb); + } +} + inline fixed4 uddGetTexture(sampler2D tex, float2 uv) { uddInvertUV(uv); fixed4 c = tex2D(tex, uv); - if (!IsGammaSpace()) { - c.rgb = GammaToLinearSpace(c.rgb); - } + uddToLinearIfNeeded(c.rgb); return c; } diff --git a/Assets/uDesktopDuplication/Shaders/uDD_Cursor.shader b/Assets/uDesktopDuplication/Shaders/uDD_Cursor.shader new file mode 100644 index 0000000..7cfa6c7 --- /dev/null +++ b/Assets/uDesktopDuplication/Shaders/uDD_Cursor.shader @@ -0,0 +1,79 @@ +Shader "uDesktopDuplication/Cursor" +{ + +Properties +{ + _MainTex ("Texture", 2D) = "white" {} +} + +SubShader +{ + +Tags { "RenderType"="Opaque" } + +ZWrite Off +Offset -1, -1 + +CGINCLUDE + +#include "UnityCG.cginc" +#include "./uDD_Common.cginc" + +half4 _PositionScale; +#define _PointerX _PositionScale.x +#define _PointerY _PositionScale.y +#define _PointerWidth _PositionScale.z +#define _PointerHeight _PositionScale.w + +struct appdata +{ + float4 vertex : POSITION; + float2 uv : TEXCOORD0; +}; + +struct v2f +{ + float4 vertex : SV_POSITION; + float2 uv : TEXCOORD0; +}; + +sampler2D _MainTex; + +v2f vert(appdata v) +{ + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.uv = v.uv; + return o; +} + +fixed4 frag(v2f i) : SV_Target +{ + uddInvertUV(i.uv); + i.uv.x = (i.uv.x - _PointerX) / _PointerWidth; + i.uv.y = (i.uv.y - _PointerY) / _PointerHeight; + fixed4 color = tex2D(_MainTex, i.uv); + color.a *= step(0, i.uv.x) * step(0, i.uv.y) * step(i.uv.x, 1) * step(i.uv.y, 1); + clip(color.a - 0.01); + uddToLinearIfNeeded(color.rgb); + return color; +} + +ENDCG + +Pass +{ + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #pragma multi_compile ___ INVERT_X + #pragma multi_compile ___ INVERT_Y + #pragma multi_compile ___ VERTICAL + ENDCG +} + +} + +Fallback "Unlit/Texture" + +} diff --git a/Assets/uDesktopDuplication/Shaders/uDD_Cursor.shader.meta b/Assets/uDesktopDuplication/Shaders/uDD_Cursor.shader.meta new file mode 100644 index 0000000..95a4abc --- /dev/null +++ b/Assets/uDesktopDuplication/Shaders/uDD_Cursor.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: d224cadaa1bfa2d46a6224ce2a1443c7 +timeCreated: 1477847636 +licenseType: Pro +ShaderImporter: + defaultTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Plugins/uDesktopDuplication/' b/Plugins/uDesktopDuplication/' new file mode 100644 index 0000000..a4697dc --- /dev/null +++ b/Plugins/uDesktopDuplication/' @@ -0,0 +1,405 @@ +#include +#include +#include +#include + +#include "IUnityInterface.h" +#include "IUnityGraphics.h" +#include "IUnityGraphicsD3D11.h" + +#pragma comment(lib, "dxgi.lib") + + +namespace +{ + struct Monitor + { + IDXGIOutputDuplication* deskDupl = nullptr; + ID3D11Texture2D* texture = nullptr; + DXGI_OUTPUT_DESC outputDesc; + MONITORINFOEX monitorInfo; + + struct Pointer + { + bool isVisible = false; + int x = -1; + int y = -1; + BYTE* apiBuffer = nullptr; + UINT apiBufferSize = 0; + BYTE* bgra32Buffer = nullptr; + UINT bgra32BufferSize = 0; + DXGI_OUTDUPL_POINTER_SHAPE_INFO shapeInfo; + }; + Pointer pointer; + }; + + IUnityInterfaces* g_unity = nullptr; + int g_timeout = 10; + HRESULT g_errorCode = 0; + std::string g_errorMessage = ""; + std::vector g_monitors; +} + + +extern "C" +{ + void FinalizeDuplication() + { + for (auto& monitor : g_monitors) + { + monitor.deskDupl->Release(); + } + g_monitors.clear(); + } + + void InitializeDuplication() + { + FinalizeDuplication(); + + IDXGIFactory1* factory; + CreateDXGIFactory1(__uuidof(IDXGIFactory1), reinterpret_cast(&factory)); + + // Check all display adapters. + IDXGIAdapter1* adapter; + for (int i = 0; (factory->EnumAdapters1(i, &adapter) != DXGI_ERROR_NOT_FOUND); ++i) + { + // Search the main monitor from all outputs. + IDXGIOutput* output; + for (int j = 0; (adapter->EnumOutputs(j, &output) != DXGI_ERROR_NOT_FOUND); ++j) + { + Monitor monitor; + + output->GetDesc(&monitor.outputDesc); + monitor.monitorInfo.cbSize = sizeof(MONITORINFOEX); + GetMonitorInfo(monitor.outputDesc.Monitor, &monitor.monitorInfo); + + auto device = g_unity->Get()->GetDevice(); + auto output1 = reinterpret_cast(output); + output1->DuplicateOutput(device, &monitor.deskDupl); + output->Release(); + + g_monitors.push_back(monitor); + } + + adapter->Release(); + } + + factory->Release(); + } + + UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces* unityInterfaces) + { + g_unity = unityInterfaces; + InitializeDuplication(); + } + + UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UnityPluginUnload() + { + g_unity = nullptr; + FinalizeDuplication(); + } + + bool IsValidMonitorId(int id) + { + return id >= 0 && id < g_monitors.size(); + } + + bool UpdateMouse(const DXGI_OUTDUPL_FRAME_INFO& frameInfo, Monitor& monitor) + { + auto& pointer = monitor.pointer; + pointer.isVisible = frameInfo.PointerPosition.Visible != 0; + pointer.x = frameInfo.PointerPosition.Position.x; + pointer.y = frameInfo.PointerPosition.Position.y; + if (frameInfo.PointerShapeBufferSize != 0) + { + if (frameInfo.PointerShapeBufferSize > pointer.apiBufferSize) + { + if (pointer.apiBuffer) delete[] pointer.apiBuffer; + pointer.apiBuffer = new BYTE[frameInfo.PointerShapeBufferSize]; + pointer.apiBufferSize = frameInfo.PointerShapeBufferSize; + } + if (pointer.apiBuffer) + { + UINT bufferSize; + monitor.deskDupl->GetFramePointerShape( + frameInfo.PointerShapeBufferSize, + reinterpret_cast(pointer.apiBuffer), + &bufferSize, + &pointer.shapeInfo); + } + + // Convert buffer give by API into BGRA32 + const auto bgraBufferSize = pointer.shapeInfo.Width * pointer.shapeInfo.Height * 4; + if (bgraBufferSize > pointer.bgra32BufferSize) + { + if (pointer.bgra32Buffer) delete[] pointer.bgra32Buffer; + pointer.bgra32Buffer = new BYTE[bgraBufferSize]; + pointer.bgra32BufferSize = bgraBufferSize; + } + if (pointer.bgra32Buffer) + { + const auto w = pointer.shapeInfo.Width; + const auto h = pointer.shapeInfo.Height; + const auto p = pointer.shapeInfo.Pitch; + + // If masked, copy the desktop image and merge it with masked image. + const bool isMasked = + pointer.shapeInfo.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME || + pointer.shapeInfo.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR; + if (isMasked) + { + HRESULT hr; + + D3D11_TEXTURE2D_DESC desc; + desc.Width = w; + desc.Height = h; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.Usage = D3D11_USAGE_STAGING; + desc.BindFlags = 0; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + desc.MiscFlags = 0; + + ID3D11DeviceContext* context; + auto device = g_unity->Get()->GetDevice(); + device->GetImmediateContext(&context); + + ID3D11Texture2D* texture = nullptr; + hr = device->CreateTexture2D(&desc, nullptr, &texture); + if (FAILED(hr)) + { + return false; + } + + D3D11_BOX box; + box.left = pointer.x; + box.top = pointer.y; + box.right = pointer.x + w; + box.bottom = pointer.y + h; + context->CopySubresourceRegion(texture, 0, 0, 0, 0, monitor.texture, 0, &box); + + IDXGISurface* surface = nullptr; + hr = texture->QueryInterface(__uuidof(IDXGISurface), (void**)&surface); + texture->Release(); + if (FAILED(hr)) + { + return false; + } + + DXGI_MAPPED_RECT mappedSurface; + hr = surface->Map(&mappedSurface, DXGI_MAP_READ); + if (FAILED(hr)) + { + surface->Release(); + return false; + } + + // Finally, get the texture behind the mouse pointer. + const auto desktop32 = reinterpret_cast(mappedSurface.pBits); + const auto desktopPitch = mappedSurface.Pitch / sizeof(UINT); + + // Access RGBA values at the same time + auto output32 = reinterpret_cast(pointer.bgra32Buffer); + + if (pointer.shapeInfo.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME) + { + for (UINT row = 0; row < h; ++row) + { + for (UINT col = 0; col < w; ++col) + { + const int i = row * w + col; + const UINT mask = 0b10000000 >> (i % 8); + const BYTE andMask = pointer.apiBuffer[col / 8 + row * p] & mask; + const BYTE xorMask = pointer.apiBuffer[col / 8 + (row + h / 2) * p] & mask; + const UINT andMask32 = andMask ? 0xFFFFFFFF : 0xFF000000; + const UINT xorMask32 = xorMask ? 0x00FFFFFF : 0x00000000; + output32[i] = (desktop32[row * desktopPitch + col] & andMask32) ^ xorMask32; + } + } + } + else // DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR + { + const auto buffer32 = reinterpret_cast(pointer.apiBuffer); + + for (UINT row = 0; row < h; ++row) + { + for (UINT col = 0; col < w; ++col) + { + const int i = row * w + col; + const int j = row * p / sizeof(UINT) + col; + + UINT MaskVal = 0xFF000000 & buffer32[j]; + + const UINT mask = 0xFF000000 & buffer[j]; + if (mask) { + output[i] = 0xFF000000 ^ buffer[j]; + } else { + output[i] = 0x00000000; + } + } + } + } + + hr = surface->Unmap(); + surface->Release(); + if (FAILED(hr)) + { + return false; + } + } + } + } + + return true; + } + + void UNITY_INTERFACE_API OnRenderEvent(int id) + { + if (!IsValidMonitorId(id)) return; + auto& monitor = g_monitors[id]; + + if (monitor.deskDupl == nullptr || monitor.texture == nullptr) return; + + IDXGIResource* resource = nullptr; + DXGI_OUTDUPL_FRAME_INFO frameInfo; + + g_errorCode = monitor.deskDupl->AcquireNextFrame(g_timeout, &frameInfo, &resource); + if (FAILED(g_errorCode)) + { + if (g_errorCode == DXGI_ERROR_ACCESS_LOST) + { + InitializeDuplication(); + g_errorMessage = "[IDXGIOutputDuplication::AcquireNextFrame()] Access lost."; + } + else + { + g_errorMessage = "[IDXGIOutputDuplication::AcquireNextFrame()] Unknown error."; + } + return; + } + + ID3D11Texture2D* texture; + resource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast(&texture)); + + ID3D11DeviceContext* context; + auto device = g_unity->Get()->GetDevice(); + device->GetImmediateContext(&context); + context->CopyResource(monitor.texture, texture); + + if (!UpdateMouse(frameInfo, monitor)) + { + g_errorCode = -999; + g_errorMessage = "[UpdateMouse()] failed."; + } + + resource->Release(); + monitor.deskDupl->ReleaseFrame(); + } + + UNITY_INTERFACE_EXPORT UnityRenderingEvent UNITY_INTERFACE_API GetRenderEventFunc() + { + return OnRenderEvent; + } + + UNITY_INTERFACE_EXPORT size_t UNITY_INTERFACE_API GetMonitorCount() + { + return g_monitors.size(); + } + + UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API SetTimeout(int timeout) + { + g_timeout = timeout; + } + + UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API GetName(int id, char* buf, int len) + { + if (!IsValidMonitorId(id)) return; + strcpy_s(buf, len, g_monitors[id].monitorInfo.szDevice); + } + + UNITY_INTERFACE_EXPORT bool UNITY_INTERFACE_API IsPrimary(int id) + { + if (!IsValidMonitorId(id)) return false; + return g_monitors[id].monitorInfo.dwFlags == MONITORINFOF_PRIMARY; + } + + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetWidth(int id) + { + if (!IsValidMonitorId(id)) return -1; + const auto rect = g_monitors[id].monitorInfo.rcMonitor; + return rect.right - rect.left; + } + + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetHeight(int id) + { + if (!IsValidMonitorId(id)) return -1; + const auto rect = g_monitors[id].monitorInfo.rcMonitor; + return rect.bottom - rect.top; + } + + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API IsPointerVisible(int id) + { + if (!IsValidMonitorId(id)) return false; + return g_monitors[id].pointer.isVisible; + } + + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerX(int id) + { + if (!IsValidMonitorId(id)) return -1; + return g_monitors[id].pointer.x; + } + + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerY(int id) + { + if (!IsValidMonitorId(id)) return -1; + return g_monitors[id].pointer.y; + } + + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapeWidth(int id) + { + if (!IsValidMonitorId(id)) return -1; + return g_monitors[id].pointer.shapeInfo.Width; + } + + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapeHeight(int id) + { + if (!IsValidMonitorId(id)) return -1; + return g_monitors[id].pointer.shapeInfo.Height; + } + + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapePitch(int id) + { + if (!IsValidMonitorId(id)) return -1; + return g_monitors[id].pointer.shapeInfo.Pitch; + } + + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapeType(int id) + { + if (!IsValidMonitorId(id)) return -1; + return g_monitors[id].pointer.shapeInfo.Type; + } + + UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UpdatePointerTexture(int id, ID3D11Texture2D* ptr) + { + if (!IsValidMonitorId(id)) return; + const auto& pointer = g_monitors[id].pointer; + auto device = g_unity->Get()->GetDevice(); + ID3D11DeviceContext* context; + device->GetImmediateContext(&context); + context->UpdateSubresource(ptr, 0, nullptr, pointer.bgra32Buffer, pointer.shapeInfo.Pitch, 0); + } + + UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API SetTexturePtr(int id, void* texture) + { + if (!IsValidMonitorId(id)) return; + g_monitors[id].texture = reinterpret_cast(texture); + } + + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetErrorCode() + { + return static_cast(g_errorCode); + } +} \ No newline at end of file diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/main.cpp b/Plugins/uDesktopDuplication/uDesktopDuplication/main.cpp index 6e7b858..511dcc0 100644 --- a/Plugins/uDesktopDuplication/uDesktopDuplication/main.cpp +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/main.cpp @@ -14,22 +14,31 @@ namespace { struct Monitor { - IDXGIOutputDuplication* deskDupl = nullptr; - ID3D11Texture2D* texture = nullptr; + int id = -1; + IDXGIOutputDuplication* deskDupl = nullptr; + ID3D11Texture2D* texture = nullptr; DXGI_OUTPUT_DESC outputDesc; MONITORINFOEX monitorInfo; - std::string name = ""; - bool isPrimary = false; - bool isPointerVisible = false; - int pointerX = -1; - int pointerY = -1; - int width = -1; - int height = -1; + + struct Pointer + { + bool isVisible = false; + int x = -1; + int y = -1; + BYTE* apiBuffer = nullptr; + UINT apiBufferSize = 0; + BYTE* bgra32Buffer = nullptr; + UINT bgra32BufferSize = 0; + DXGI_OUTDUPL_POINTER_SHAPE_INFO shapeInfo; + }; + Pointer pointer; }; - IUnityInterfaces* g_unity = nullptr; - int g_timeout = 10; - HRESULT g_errorCode = 0; + IUnityInterfaces* g_unity = nullptr; + int g_mouseMonitor = 0; + int g_timeout = 10; + HRESULT g_errorCode = 0; + std::string g_errorMessage = ""; std::vector g_monitors; } @@ -53,6 +62,7 @@ extern "C" CreateDXGIFactory1(__uuidof(IDXGIFactory1), reinterpret_cast(&factory)); // Check all display adapters. + int id = 0; IDXGIAdapter1* adapter; for (int i = 0; (factory->EnumAdapters1(i, &adapter) != DXGI_ERROR_NOT_FOUND); ++i) { @@ -61,6 +71,7 @@ extern "C" for (int j = 0; (adapter->EnumOutputs(j, &output) != DXGI_ERROR_NOT_FOUND); ++j) { Monitor monitor; + monitor.id = id++; output->GetDesc(&monitor.outputDesc); monitor.monitorInfo.cbSize = sizeof(MONITORINFOEX); @@ -92,14 +103,192 @@ extern "C" FinalizeDuplication(); } - bool DoesMonitorExist(int id) + bool IsValidMonitorId(int id) { return id >= 0 && id < g_monitors.size(); } + bool UpdateMouse(const DXGI_OUTDUPL_FRAME_INFO& frameInfo, Monitor& monitor) + { + auto& pointer = monitor.pointer; + pointer.isVisible = frameInfo.PointerPosition.Visible != 0; + pointer.x = frameInfo.PointerPosition.Position.x; + pointer.y = frameInfo.PointerPosition.Position.y; + + // Pointer type + const auto pointerType = pointer.shapeInfo.Type; + const bool isMono = pointerType == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME; + const bool isColorMask = pointerType == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR; + + if (pointer.isVisible) + { + g_mouseMonitor = monitor.id; + } + + if (g_mouseMonitor != monitor.id) { + return true; + } + + // Increase the buffer size if needed + if (frameInfo.PointerShapeBufferSize > pointer.apiBufferSize) + { + if (pointer.apiBuffer) delete[] pointer.apiBuffer; + pointer.apiBuffer = new BYTE[frameInfo.PointerShapeBufferSize]; + pointer.apiBufferSize = frameInfo.PointerShapeBufferSize; + } + if (!pointer.apiBuffer) return true; + + // Get information about the mouse pointer if needed + if (frameInfo.PointerShapeBufferSize != 0) + { + UINT bufferSize; + monitor.deskDupl->GetFramePointerShape( + frameInfo.PointerShapeBufferSize, + reinterpret_cast(pointer.apiBuffer), + &bufferSize, + &pointer.shapeInfo); + } + + // Size + const auto w = pointer.shapeInfo.Width; + const auto h = pointer.shapeInfo.Height / (isMono ? 2 : 1); + const auto p = pointer.shapeInfo.Pitch; + + // Convert the buffer given by API into BGRA32 + const auto bgraBufferSize = w * h * 4; + if (bgraBufferSize > pointer.bgra32BufferSize) + { + if (pointer.bgra32Buffer) delete[] pointer.bgra32Buffer; + pointer.bgra32Buffer = new BYTE[bgraBufferSize]; + pointer.bgra32BufferSize = bgraBufferSize; + } + if (!pointer.bgra32Buffer) return true; + + // If masked, copy the desktop image and merge it with masked image. + if (isMono || isColorMask) + { + HRESULT hr; + + D3D11_TEXTURE2D_DESC desc; + desc.Width = w; + desc.Height = h; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.Usage = D3D11_USAGE_STAGING; + desc.BindFlags = 0; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + desc.MiscFlags = 0; + + ID3D11DeviceContext* context; + auto device = g_unity->Get()->GetDevice(); + device->GetImmediateContext(&context); + + ID3D11Texture2D* texture = nullptr; + hr = device->CreateTexture2D(&desc, nullptr, &texture); + if (FAILED(hr)) + { + return false; + } + + D3D11_BOX box; + box.front = 0; + box.back = 1; + box.left = pointer.x; + box.top = pointer.y; + box.right = pointer.x + w; + box.bottom = pointer.y + h; + context->CopySubresourceRegion(texture, 0, 0, 0, 0, monitor.texture, 0, &box); + + IDXGISurface* surface = nullptr; + hr = texture->QueryInterface(__uuidof(IDXGISurface), (void**)&surface); + texture->Release(); + if (FAILED(hr)) + { + return false; + } + + DXGI_MAPPED_RECT mappedSurface; + hr = surface->Map(&mappedSurface, DXGI_MAP_READ); + if (FAILED(hr)) + { + surface->Release(); + return false; + } + + // Finally, get the texture behind the mouse pointer. + const auto desktop32 = reinterpret_cast(mappedSurface.pBits); + const UINT desktopPitch = mappedSurface.Pitch / sizeof(UINT); + + // Access RGBA values at the same time + auto output32 = reinterpret_cast(pointer.bgra32Buffer); + + if (isMono) + { + for (UINT row = 0; row < h; ++row) + { + BYTE mask = 0x80; + for (UINT col = 0; col < w; ++col) + { + const int i = row * w + col; + const BYTE andMask = pointer.apiBuffer[col / 8 + row * p] & mask; + const BYTE xorMask = pointer.apiBuffer[col / 8 + (row + h) * p] & mask; + const UINT andMask32 = andMask ? 0xFFFFFFFF : 0xFF000000; + const UINT xorMask32 = xorMask ? 0x00FFFFFF : 0x00000000; + output32[i] = (desktop32[row * desktopPitch + col] & andMask32) ^ xorMask32; + mask = (mask == 0x01) ? 0x80 : (mask >> 1); + } + } + } + else // DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR + { + const auto buffer32 = reinterpret_cast(pointer.apiBuffer); + + for (UINT row = 0; row < h; ++row) + { + for (UINT col = 0; col < w; ++col) + { + const int i = row * w + col; + const int j = row * p / sizeof(UINT) + col; + + UINT mask = 0xFF000000 & buffer32[j]; + if (mask) + { + output32[i] = (desktop32[row * desktopPitch + col] ^ buffer32[j]) | 0xFF000000; + } + else + { + output32[i] = buffer32[j] | 0xFF000000; + } + } + } + } + + hr = surface->Unmap(); + surface->Release(); + if (FAILED(hr)) + { + return false; + } + } + else // DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR + { + auto output32 = reinterpret_cast(pointer.bgra32Buffer); + const auto buffer32 = reinterpret_cast(pointer.apiBuffer); + for (UINT i = 0; i < w * h; ++i) + { + output32[i] = buffer32[i]; + } + } + + return true; + } + void UNITY_INTERFACE_API OnRenderEvent(int id) { - if (!DoesMonitorExist(id)) return; + if (!IsValidMonitorId(id)) return; auto& monitor = g_monitors[id]; if (monitor.deskDupl == nullptr || monitor.texture == nullptr) return; @@ -108,44 +297,35 @@ extern "C" DXGI_OUTDUPL_FRAME_INFO frameInfo; g_errorCode = monitor.deskDupl->AcquireNextFrame(g_timeout, &frameInfo, &resource); - switch (g_errorCode) + if (FAILED(g_errorCode)) { - case S_OK: + if (g_errorCode == DXGI_ERROR_ACCESS_LOST) { - break; - } - case DXGI_ERROR_ACCESS_LOST: - { - resource->Release(); InitializeDuplication(); - return; + g_errorMessage = "[IDXGIOutputDuplication::AcquireNextFrame()] Access lost."; } - default: + else { - return; + g_errorMessage = "[IDXGIOutputDuplication::AcquireNextFrame()] Maybe timeout."; } - } - - monitor.isPointerVisible = frameInfo.PointerPosition.Visible == TRUE; - monitor.pointerX = frameInfo.PointerPosition.Position.x; - monitor.pointerY = frameInfo.PointerPosition.Position.y; - - ID3D11Texture2D* texture; - g_errorCode = resource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast(&texture)); - if (g_errorCode != S_OK) - { - resource->Release(); - monitor.deskDupl->ReleaseFrame(); return; } + ID3D11Texture2D* texture; + resource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast(&texture)); + ID3D11DeviceContext* context; auto device = g_unity->Get()->GetDevice(); device->GetImmediateContext(&context); context->CopyResource(monitor.texture, texture); + if (!UpdateMouse(frameInfo, monitor)) + { + g_errorCode = -999; + g_errorMessage = "[UpdateMouse()] failed."; + } + resource->Release(); - texture->Release(); monitor.deskDupl->ReleaseFrame(); } @@ -166,56 +346,102 @@ extern "C" UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API GetName(int id, char* buf, int len) { - if (!DoesMonitorExist(id)) return; + if (!IsValidMonitorId(id)) return; strcpy_s(buf, len, g_monitors[id].monitorInfo.szDevice); } UNITY_INTERFACE_EXPORT bool UNITY_INTERFACE_API IsPrimary(int id) { - if (!DoesMonitorExist(id)) return false; + if (!IsValidMonitorId(id)) return false; return g_monitors[id].monitorInfo.dwFlags == MONITORINFOF_PRIMARY; } UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetWidth(int id) { - if (!DoesMonitorExist(id)) return -1; + if (!IsValidMonitorId(id)) return -1; const auto rect = g_monitors[id].monitorInfo.rcMonitor; return rect.right - rect.left; } UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetHeight(int id) { - if (!DoesMonitorExist(id)) return -1; + if (!IsValidMonitorId(id)) return -1; const auto rect = g_monitors[id].monitorInfo.rcMonitor; return rect.bottom - rect.top; } UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API IsPointerVisible(int id) { - if (!DoesMonitorExist(id)) return false; - return g_monitors[id].isPointerVisible; + if (!IsValidMonitorId(id)) return false; + return g_monitors[id].pointer.isVisible; } UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerX(int id) { - if (!DoesMonitorExist(id)) return -1; - return g_monitors[id].pointerX; + if (!IsValidMonitorId(id)) return -1; + return g_monitors[id].pointer.x; } UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerY(int id) { - if (!DoesMonitorExist(id)) return -1; - return g_monitors[id].pointerY; + if (!IsValidMonitorId(id)) return -1; + return g_monitors[id].pointer.y; + } + + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapeWidth(int id) + { + if (!IsValidMonitorId(id)) return -1; + return g_monitors[id].pointer.shapeInfo.Width; + } + + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapeHeight(int id) + { + if (!IsValidMonitorId(id)) return -1; + const auto& info = g_monitors[id].pointer.shapeInfo; + return (info.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME) ? info.Height / 2 : info.Height; + } + + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapePitch(int id) + { + if (!IsValidMonitorId(id)) return -1; + return g_monitors[id].pointer.shapeInfo.Pitch; + } + + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapeType(int id) + { + if (!IsValidMonitorId(id)) return -1; + return g_monitors[id].pointer.shapeInfo.Type; + } + + UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UpdatePointerTexture(int id, ID3D11Texture2D* ptr) + { + if (!IsValidMonitorId(id)) return; + const auto& pointer = g_monitors[id].pointer; + if (!pointer.bgra32Buffer) return; + auto device = g_unity->Get()->GetDevice(); + ID3D11DeviceContext* context; + device->GetImmediateContext(&context); + context->UpdateSubresource(ptr, 0, nullptr, pointer.bgra32Buffer, pointer.shapeInfo.Width * 4, 0); } UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API SetTexturePtr(int id, void* texture) { - if (!DoesMonitorExist(id)) return; + if (!IsValidMonitorId(id)) return; g_monitors[id].texture = reinterpret_cast(texture); } UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetErrorCode() { - return static_cast(g_errorCode); + const auto code = g_errorCode; + g_errorCode = 0; + return static_cast(code); } + + UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API GetErrorMessage(char* buf, int len) + { + const auto message = g_errorMessage; + g_errorMessage = ""; + strcpy_s(buf, len, message.c_str()); + } + } \ No newline at end of file