diff --git a/Assets/GetPixelsExample.cs b/Assets/GetPixelsExample.cs new file mode 100644 index 0000000..9172ed8 --- /dev/null +++ b/Assets/GetPixelsExample.cs @@ -0,0 +1,36 @@ +using UnityEngine; + +public class GetPixelsExample : MonoBehaviour +{ + [SerializeField] uDesktopDuplication.Texture uddTexture; + + [SerializeField] int x = 100; + [SerializeField] int y = 100; + const int width = 64; + const int height = 32; + + public Texture2D texture; + Color32[] colors = new Color32[width * height]; + + void Start() + { + // must be called (performance will be slightly down). + uDesktopDuplication.Manager.useGetPixels = true; + + texture = new Texture2D(width, height, TextureFormat.ARGB32, false); + GetComponent().material.mainTexture = texture; + } + + void Update() + { + var monitor = uddTexture.monitor; + if (!monitor.hasBeenUpdated) return; + + if (monitor.GetPixels(colors, x, y, width, height)) { + texture.SetPixels32(colors); + texture.Apply(); + } + + Debug.Log(monitor.GetPixel(monitor.cursorX, monitor.cursorY)); + } +} diff --git a/Assets/GetPixelsExample.cs.meta b/Assets/GetPixelsExample.cs.meta new file mode 100644 index 0000000..40b5ee2 --- /dev/null +++ b/Assets/GetPixelsExample.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: fd3f838ff57152e44a1810274a9df49b +timeCreated: 1480420430 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/uDesktopDuplication/Examples/Scenes/GetPixels.unity b/Assets/uDesktopDuplication/Examples/Scenes/GetPixels.unity new file mode 100644 index 0000000..1855e9a Binary files /dev/null and b/Assets/uDesktopDuplication/Examples/Scenes/GetPixels.unity differ diff --git a/Assets/uDesktopDuplication/Examples/Scenes/GetPixels.unity.meta b/Assets/uDesktopDuplication/Examples/Scenes/GetPixels.unity.meta new file mode 100644 index 0000000..14d8b34 --- /dev/null +++ b/Assets/uDesktopDuplication/Examples/Scenes/GetPixels.unity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 38a5db360d5147c46ae925104162b881 +timeCreated: 1480420198 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/uDesktopDuplication/Examples/Scripts/GetPixels.cs b/Assets/uDesktopDuplication/Examples/Scripts/GetPixels.cs new file mode 100644 index 0000000..61e8bd0 --- /dev/null +++ b/Assets/uDesktopDuplication/Examples/Scripts/GetPixels.cs @@ -0,0 +1,15 @@ +using UnityEngine; +using System.Collections; + +public class GetPixels : MonoBehaviour { + + // Use this for initialization + void Start () { + + } + + // Update is called once per frame + void Update () { + + } +} diff --git a/Assets/uDesktopDuplication/Examples/Scripts/GetPixels.cs.meta b/Assets/uDesktopDuplication/Examples/Scripts/GetPixels.cs.meta new file mode 100644 index 0000000..e390621 --- /dev/null +++ b/Assets/uDesktopDuplication/Examples/Scripts/GetPixels.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: bc0250b1a79f38b48bfddf1e05bda638 +timeCreated: 1480420057 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/uDesktopDuplication/Resources.meta b/Assets/uDesktopDuplication/Models/Resources.meta similarity index 100% rename from Assets/uDesktopDuplication/Resources.meta rename to Assets/uDesktopDuplication/Models/Resources.meta diff --git a/Assets/uDesktopDuplication/Resources/uDesktopDuplication.meta b/Assets/uDesktopDuplication/Models/Resources/uDesktopDuplication.meta similarity index 100% rename from Assets/uDesktopDuplication/Resources/uDesktopDuplication.meta rename to Assets/uDesktopDuplication/Models/Resources/uDesktopDuplication.meta diff --git a/Assets/uDesktopDuplication/Resources/uDesktopDuplication/Textures.meta b/Assets/uDesktopDuplication/Models/Resources/uDesktopDuplication/Textures.meta similarity index 100% rename from Assets/uDesktopDuplication/Resources/uDesktopDuplication/Textures.meta rename to Assets/uDesktopDuplication/Models/Resources/uDesktopDuplication/Textures.meta diff --git a/Assets/uDesktopDuplication/Resources/uDesktopDuplication/Textures/NotAvailable.png b/Assets/uDesktopDuplication/Models/Resources/uDesktopDuplication/Textures/NotAvailable.png similarity index 100% rename from Assets/uDesktopDuplication/Resources/uDesktopDuplication/Textures/NotAvailable.png rename to Assets/uDesktopDuplication/Models/Resources/uDesktopDuplication/Textures/NotAvailable.png diff --git a/Assets/uDesktopDuplication/Resources/uDesktopDuplication/Textures/NotAvailable.png.meta b/Assets/uDesktopDuplication/Models/Resources/uDesktopDuplication/Textures/NotAvailable.png.meta similarity index 100% rename from Assets/uDesktopDuplication/Resources/uDesktopDuplication/Textures/NotAvailable.png.meta rename to Assets/uDesktopDuplication/Models/Resources/uDesktopDuplication/Textures/NotAvailable.png.meta diff --git a/Assets/uDesktopDuplication/Plugins/x86_64/uDesktopDuplication.dll b/Assets/uDesktopDuplication/Plugins/x86_64/uDesktopDuplication.dll index e889d0c..74c8f9f 100644 Binary files a/Assets/uDesktopDuplication/Plugins/x86_64/uDesktopDuplication.dll and b/Assets/uDesktopDuplication/Plugins/x86_64/uDesktopDuplication.dll differ diff --git a/Assets/uDesktopDuplication/Scripts/Lib.cs b/Assets/uDesktopDuplication/Scripts/Lib.cs index f3dbfbe..b39e726 100644 --- a/Assets/uDesktopDuplication/Scripts/Lib.cs +++ b/Assets/uDesktopDuplication/Scripts/Lib.cs @@ -1,4 +1,5 @@ -using System; +using UnityEngine; +using System; using System.Text; using System.Runtime.InteropServices; @@ -78,6 +79,8 @@ public static class Lib [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void DebugLogDelegate(string str); + [DllImport("uDesktopDuplication")] + public static extern bool IsInitialized(); [DllImport("uDesktopDuplication")] public static extern void Initialize(); [DllImport("uDesktopDuplication")] @@ -164,6 +167,12 @@ public static class Lib public static extern int GetDirtyRectCount(int id); [DllImport("uDesktopDuplication", EntryPoint = "GetDirtyRects")] private static extern IntPtr GetDirtyRects_Internal(int id); + [DllImport("uDesktopDuplication", EntryPoint = "GetPixels")] + private static extern bool GetPixels_Internal(int id, System.IntPtr ptr, int x, int y, int width, int height); + [DllImport("uDesktopDuplication")] + public static extern bool HasBeenUpdated(int id); + [DllImport("uDesktopDuplication")] + public static extern bool UseGetPixels(bool use); public static string GetName(int id) { @@ -197,6 +206,38 @@ public static class Lib } return rects; } + + public static Color32[] GetPixels(int id, int x, int y, int width, int height) + { + var color = new Color32[width * height]; + GetPixels(id, color, x, y, width, height); + return color; + } + + public static bool GetPixels(int id, Color32[] colors, int x, int y, int width, int height) + { + if (!Mathf.IsPowerOfTwo(width) || !Mathf.IsPowerOfTwo(height)) { + Debug.LogErrorFormat("width and height must be power of two."); + return false; + } + if (colors.Length < width * height) { + Debug.LogErrorFormat("colors is small.", id, x, y, width, height); + return false; + } + var handle = GCHandle.Alloc(colors, GCHandleType.Pinned); + var ptr = handle.AddrOfPinnedObject(); + if (!GetPixels_Internal(id, ptr, x, y, width, height)) { + Debug.LogErrorFormat("GetPixels({0}, {1}, {2}, {3}, {4}) failed.", id, x, y, width, height); + return false; + } + handle.Free(); + return true; + } + + public static Color32 GetPixel(int id, int x, int y) + { + return GetPixels(id, x, y, 1, 1)[0]; + } } } \ No newline at end of file diff --git a/Assets/uDesktopDuplication/Scripts/Manager.cs b/Assets/uDesktopDuplication/Scripts/Manager.cs index d08dd10..5cff3b3 100644 --- a/Assets/uDesktopDuplication/Scripts/Manager.cs +++ b/Assets/uDesktopDuplication/Scripts/Manager.cs @@ -52,6 +52,14 @@ public class Manager : MonoBehaviour } } + static public bool useGetPixels + { + set + { + Lib.UseGetPixels(value); + } + } + [Tooltip("Debug mode is not applied while running.")] [SerializeField] DebugMode debugMode = DebugMode.File; diff --git a/Assets/uDesktopDuplication/Scripts/Monitor.cs b/Assets/uDesktopDuplication/Scripts/Monitor.cs index 72faa17..07f0ceb 100644 --- a/Assets/uDesktopDuplication/Scripts/Monitor.cs +++ b/Assets/uDesktopDuplication/Scripts/Monitor.cs @@ -232,6 +232,11 @@ public class Monitor get { return Lib.GetDirtyRects(id); } } + public bool hasBeenUpdated + { + get { return Lib.HasBeenUpdated(id); } + } + public bool shouldBeUpdated { get; @@ -317,6 +322,21 @@ public class Monitor { CreateTextureIfNeeded(); } + + public Color32[] GetPixels(int x, int y, int width, int height) + { + return Lib.GetPixels(id, x, y, width, height); + } + + public bool GetPixels(Color32[] colors, int x, int y, int width, int height) + { + return Lib.GetPixels(id, colors, x, y, width, height); + } + + public Color32 GetPixel(int x, int y) + { + return Lib.GetPixel(id, x, y); + } } } \ No newline at end of file diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/Cursor.cpp b/Plugins/uDesktopDuplication/uDesktopDuplication/Cursor.cpp index f035f3a..31ac461 100644 --- a/Plugins/uDesktopDuplication/uDesktopDuplication/Cursor.cpp +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/Cursor.cpp @@ -176,8 +176,8 @@ void Cursor::Draw(Monitor* monitor) // Check if box is inner desktop area if (capturedImageLeft < 0 || capturedImageTop < 0 || - capturedImageRight > desktopImageWidth || - capturedImageBottom > desktopImageHeight) + capturedImageRight >= desktopImageWidth || + capturedImageBottom >= desktopImageHeight) { Debug::Error("Cursor::UpdateTexture() => box is out of area."); Debug::Error( diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/Monitor.cpp b/Plugins/uDesktopDuplication/uDesktopDuplication/Monitor.cpp index 8278cf2..5e25881 100644 --- a/Plugins/uDesktopDuplication/uDesktopDuplication/Monitor.cpp +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/Monitor.cpp @@ -193,6 +193,22 @@ void Monitor::Render(UINT timeout) GetDevice()->GetImmediateContext(&context); context->CopyResource(unityTexture_, texture); } + + { + std::lock_guard lock(mutex_); + + ComPtr context; + if (!ddaTexture_) + { + if (FAILED(GetDevice()->CreateTexture2D(&dstDesc, nullptr, &ddaTexture_))) + { + Debug::Error("Monitor::Render() => GetDevice()->CreateTexture2D() failed."); + return; + } + } + GetDevice()->GetImmediateContext(&context); + context->CopyResource(ddaTexture_.Get(), texture); + } } UpdateMetadata(frameInfo); @@ -232,6 +248,8 @@ void Monitor::Render(UINT timeout) } return; } + + hasBeenUpdated_ = true; } @@ -456,4 +474,155 @@ int Monitor::GetDirtyRectCount() const RECT* Monitor::GetDirtyRects() const { return metaData_.As(moveRectSize_); +} + + +bool Monitor::GetPixels(UINT* ptr, int x, int y, int width, int height) +{ + if (!GetMonitorManager()->UseGetPixels()) + { + Debug::Error("Monitor::GetPixels() => UseGetPixels(true) must have been called when you want to use GetPixels()."); + return false; + } + + if (!ddaTexture_) + { + Debug::Error("Monitor::GetPixels() => texture is not set."); + return false; + } + + const auto monitorRot = static_cast(GetRotation()); + const auto monitorWidth = GetWidth(); + const auto monitorHeight = GetHeight(); + const auto isVertical = + monitorRot == DXGI_MODE_ROTATION_ROTATE90 || + monitorRot == DXGI_MODE_ROTATION_ROTATE270; + const auto desktopImageWidth = !isVertical ? monitorWidth : monitorHeight; + const auto desktopImageHeight = !isVertical ? monitorHeight : monitorWidth; + + int left, top, right, bottom; + + switch (monitorRot) + { + case DXGI_MODE_ROTATION_ROTATE90: + { + left = y; + top = monitorWidth - x - width; + right = y + width; + bottom = monitorWidth - x; + break; + } + case DXGI_MODE_ROTATION_ROTATE180: + { + left = monitorWidth - x - width; + top = monitorHeight - y - height; + right = monitorWidth - x; + bottom = monitorHeight - y; + break; + } + case DXGI_MODE_ROTATION_ROTATE270: + { + left = monitorHeight - y - height; + top = x; + right = monitorHeight - y; + bottom = x + width; + break; + } + case DXGI_MODE_ROTATION_IDENTITY: + case DXGI_MODE_ROTATION_UNSPECIFIED: + default: + { + left = x; + top = y; + right = x + width; + bottom = y + height; + break; + } + } + + if (left < 0 || + top < 0 || + right >= desktopImageWidth || + bottom >= desktopImageHeight) + { + Debug::Error("Monitor::GetPixels() => is out of area."); + Debug::Error( + " ", + "(", left, ", ", top, ")", + " ~ (", right, ", ", bottom, ") > ", + "(", desktopImageWidth, ", ", desktopImageHeight, ")"); + return false; + } + + D3D11_BOX box; + box.left = left; + box.top = top; + box.front = 0; + box.right = right; + box.bottom = bottom; + box.back = 1; + + // Create texture for capturing desktop image + ComPtr texture; + D3D11_TEXTURE2D_DESC desc; + desc.Width = width; + desc.Height = height; + 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; + + if (FAILED(GetDevice()->CreateTexture2D(&desc, nullptr, &texture))) + { + Debug::Error("Monitor::GetPixels() => GetDevice()->CreateTexture2D() failed."); + return false; + } + + std::lock_guard lock(mutex_); + + ComPtr context; + GetDevice()->GetImmediateContext(&context); + context->CopySubresourceRegion(texture.Get(), 0, 0, 0, 0, ddaTexture_.Get(), 0, &box); + + ComPtr surface; + if (FAILED(texture.As(&surface))) + { + Debug::Error("Monitor::GetPixels() => texture.As() failed."); + return false; + } + + DXGI_MAPPED_RECT mappedSurface; + if (FAILED(surface->Map(&mappedSurface, DXGI_MAP_READ))) + { + Debug::Error("Monitor::GetPixels() => surface->Map() failed."); + return false; + } + + const auto colors = reinterpret_cast(mappedSurface.pBits); + std::memcpy(ptr, colors, width * height * sizeof(UINT)); + std::reverse(&ptr[0], &ptr[width * height - 1]); + for (int row = 0; row < height; ++row) + { + const auto start = width * row; + const auto end = width * (row + 1) - 1; + std::reverse(&ptr[start], &ptr[end]); + } + + if (FAILED(surface->Unmap())) + { + Debug::Error("Monitor::GetPixels() => surface->Unmap() failed."); + return false; + } + + return true; +} + +bool Monitor::HasBeenUpdated() const +{ + return hasBeenUpdated_; } \ No newline at end of file diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/Monitor.h b/Plugins/uDesktopDuplication/uDesktopDuplication/Monitor.h index 3c7c01e..18443c8 100644 --- a/Plugins/uDesktopDuplication/uDesktopDuplication/Monitor.h +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/Monitor.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "Common.h" enum class MonitorState @@ -51,6 +52,8 @@ public: DXGI_OUTDUPL_MOVE_RECT* GetMoveRects() const; int GetDirtyRectCount() const; RECT* GetDirtyRects() const; + bool GetPixels(UINT* ptr, int x, int y, int width, int height); + bool HasBeenUpdated() const; private: void UpdateCursor(const DXGI_OUTDUPL_FRAME_INFO& frameInfo); @@ -61,12 +64,15 @@ private: int id_ = -1; UINT dpiX_ = -1, dpiY_ = -1; int width_ = -1, height_ = -1; + bool hasBeenUpdated_ = false; State state_ = State::NotSet; IDXGIOutputDuplication* deskDupl_ = nullptr; ID3D11Texture2D* unityTexture_ = nullptr; + Microsoft::WRL::ComPtr ddaTexture_; DXGI_OUTPUT_DESC outputDesc_; MONITORINFOEX monitorInfo_; Buffer metaData_; UINT moveRectSize_ = 0; UINT dirtyRectSize_ = 0;; + mutable std::mutex mutex_; }; \ No newline at end of file diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/MonitorManager.cpp b/Plugins/uDesktopDuplication/uDesktopDuplication/MonitorManager.cpp index f7a12f8..475d8a2 100644 --- a/Plugins/uDesktopDuplication/uDesktopDuplication/MonitorManager.cpp +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/MonitorManager.cpp @@ -140,6 +140,18 @@ int MonitorManager::GetTimeout() const } +void MonitorManager::UseGetPixels(bool use) +{ + useGetPixels_ = use; +} + + +bool MonitorManager::UseGetPixels() const +{ + return useGetPixels_; +} + + int MonitorManager::GetMonitorCount() const { return static_cast(monitors_.size()); diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/MonitorManager.h b/Plugins/uDesktopDuplication/uDesktopDuplication/MonitorManager.h index 968f2d5..e4f3b62 100644 --- a/Plugins/uDesktopDuplication/uDesktopDuplication/MonitorManager.h +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/MonitorManager.h @@ -32,6 +32,8 @@ public: void Update(); void SetTimeout(int timeout); int GetTimeout() const; + void UseGetPixels(bool use); + bool UseGetPixels() const; // Getters from Unity public: @@ -41,6 +43,7 @@ public: private: int timeout_ = 10; + bool useGetPixels_ = false; std::vector> monitors_; std::shared_ptr cursor_ = std::make_shared(); int cursorMonitorId_ = -1; diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/main.cpp b/Plugins/uDesktopDuplication/uDesktopDuplication/main.cpp index 23205b4..f35916e 100644 --- a/Plugins/uDesktopDuplication/uDesktopDuplication/main.cpp +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/main.cpp @@ -25,6 +25,11 @@ std::queue g_messages; extern "C" { + UNITY_INTERFACE_EXPORT bool UNITY_INTERFACE_API IsInitialized() + { + return g_unity && g_manager; + } + UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API Initialize() { if (g_unity && !g_manager) @@ -387,4 +392,30 @@ extern "C" } return nullptr; } + + UNITY_INTERFACE_EXPORT bool UNITY_INTERFACE_API GetPixels(int id, UINT* ptr, int x, int y, int width, int height) + { + if (!g_manager) return nullptr; + if (auto monitor = g_manager->GetMonitor(id)) + { + return monitor->GetPixels(ptr, x, y, width, height); + } + return false; + } + + UNITY_INTERFACE_EXPORT bool UNITY_INTERFACE_API HasBeenUpdated(int id) + { + if (!g_manager) return nullptr; + if (auto monitor = g_manager->GetMonitor(id)) + { + return monitor->HasBeenUpdated(); + } + return false; + } + + UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UseGetPixels(bool use) + { + if (!g_manager) return; + g_manager->UseGetPixels(use); + } } \ No newline at end of file