From 2c61fcd470b6fffaf9a017134c50f78d5e7f751d Mon Sep 17 00:00:00 2001 From: hecomi Date: Fri, 4 Nov 2016 15:45:35 +0900 Subject: [PATCH] refactoring. --- .../Plugins/uDesktopDuplication.dll | Bin 23040 -> 141312 bytes Assets/uDesktopDuplication/Scripts/Cursor.cs | 4 +- Assets/uDesktopDuplication/Scripts/Lib.cs | 29 +- Assets/uDesktopDuplication/Scripts/Manager.cs | 7 - Assets/uDesktopDuplication/Scripts/Monitor.cs | 20 +- .../uDesktopDuplication/Common.h | 8 + .../uDesktopDuplication/Cursor.cpp | 259 +++++++++++ .../uDesktopDuplication/Cursor.h | 33 ++ .../uDesktopDuplication/Duplication.cpp | 424 ------------------ .../uDesktopDuplication/Duplication.h | 72 --- .../uDesktopDuplication/Monitor.cpp | 118 +++++ .../uDesktopDuplication/Monitor.h | 33 ++ .../uDesktopDuplication/MonitorManager.cpp | 221 +++++++++ .../uDesktopDuplication/MonitorManager.h | 52 +++ .../{ => include}/IUnityGraphics.h | 0 .../{ => include}/IUnityGraphicsD3D11.h | 0 .../{ => include}/IUnityInterface.h | 0 .../uDesktopDuplication/main.cpp | 89 ++-- .../uDesktopDuplication.vcxproj | 25 +- .../uDesktopDuplication.vcxproj.filters | 29 ++ 20 files changed, 841 insertions(+), 582 deletions(-) create mode 100644 Plugins/uDesktopDuplication/uDesktopDuplication/Common.h create mode 100644 Plugins/uDesktopDuplication/uDesktopDuplication/Cursor.cpp create mode 100644 Plugins/uDesktopDuplication/uDesktopDuplication/Cursor.h delete mode 100644 Plugins/uDesktopDuplication/uDesktopDuplication/Duplication.cpp delete mode 100644 Plugins/uDesktopDuplication/uDesktopDuplication/Duplication.h create mode 100644 Plugins/uDesktopDuplication/uDesktopDuplication/Monitor.cpp create mode 100644 Plugins/uDesktopDuplication/uDesktopDuplication/Monitor.h create mode 100644 Plugins/uDesktopDuplication/uDesktopDuplication/MonitorManager.cpp create mode 100644 Plugins/uDesktopDuplication/uDesktopDuplication/MonitorManager.h rename Plugins/uDesktopDuplication/uDesktopDuplication/{ => include}/IUnityGraphics.h (100%) rename Plugins/uDesktopDuplication/uDesktopDuplication/{ => include}/IUnityGraphicsD3D11.h (100%) rename Plugins/uDesktopDuplication/uDesktopDuplication/{ => include}/IUnityInterface.h (100%) create mode 100644 Plugins/uDesktopDuplication/uDesktopDuplication/uDesktopDuplication.vcxproj.filters diff --git a/Assets/uDesktopDuplication/Plugins/uDesktopDuplication.dll b/Assets/uDesktopDuplication/Plugins/uDesktopDuplication.dll index b7bc5d6babc61e605f00b4c860a3e9b641892dd6..5964779dabf7a4613901f0d5d50e08b87047ef08 100644 GIT binary patch literal 141312 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%0DnD* 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 literal 23040 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 z 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 diff --git a/Assets/uDesktopDuplication/Scripts/Cursor.cs b/Assets/uDesktopDuplication/Scripts/Cursor.cs index 3a030b4..b7d1dc7 100644 --- a/Assets/uDesktopDuplication/Scripts/Cursor.cs +++ b/Assets/uDesktopDuplication/Scripts/Cursor.cs @@ -47,7 +47,7 @@ public class Cursor : MonoBehaviour void Update() { - if (monitor.isPointerVisible) { + if (monitor.isCursorVisible) { UpdatePosition(); UpdateTexture(); UpdateMaterial(); @@ -76,7 +76,7 @@ public class Cursor : MonoBehaviour pointerTextures_.Add(scale, texture); } var pointerTexture = pointerTextures_[scale]; - monitor.UpdatePointerTexture(pointerTexture.GetNativeTexturePtr()); + monitor.UpdateCursorTexture(pointerTexture.GetNativeTexturePtr()); cursorMaterial_.SetTexture("_MainTex", pointerTexture); } diff --git a/Assets/uDesktopDuplication/Scripts/Lib.cs b/Assets/uDesktopDuplication/Scripts/Lib.cs index aff5155..2445604 100644 --- a/Assets/uDesktopDuplication/Scripts/Lib.cs +++ b/Assets/uDesktopDuplication/Scripts/Lib.cs @@ -5,7 +5,7 @@ using System.Runtime.InteropServices; namespace uDesktopDuplication { -public enum PointerShapeType +public enum CursorShapeType { MonoChrome = 1, Color = 2, @@ -29,27 +29,23 @@ public static class Lib [DllImport("uDesktopDuplication")] public static extern bool IsPrimary(int id); [DllImport("uDesktopDuplication")] - public static extern bool IsPointerVisible(int id); + public static extern bool IsCursorVisible(int id); [DllImport("uDesktopDuplication")] - public static extern int GetPointerX(int id); + public static extern int GetCursorX(int id); [DllImport("uDesktopDuplication")] - public static extern int GetPointerY(int id); + public static extern int GetCursorY(int id); [DllImport("uDesktopDuplication")] - public static extern int GetPointerShapeWidth(int id); + public static extern int GetCursorShapeWidth(int id); [DllImport("uDesktopDuplication")] - public static extern int GetPointerShapeHeight(int id); + public static extern int GetCursorShapeHeight(int id); [DllImport("uDesktopDuplication")] - public static extern int GetPointerShapePitch(int id); + public static extern int GetCursorShapePitch(int id); [DllImport("uDesktopDuplication")] - public static extern PointerShapeType GetPointerShapeType(int id); + public static extern CursorShapeType GetCursorShapeType(int id); [DllImport("uDesktopDuplication")] - public static extern void UpdatePointerTexture(int id, System.IntPtr ptr); + public static extern void UpdateCursorTexture(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) { @@ -57,13 +53,6 @@ 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 2db896e..0220dde 100644 --- a/Assets/uDesktopDuplication/Scripts/Manager.cs +++ b/Assets/uDesktopDuplication/Scripts/Manager.cs @@ -40,9 +40,6 @@ 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() @@ -81,10 +78,6 @@ 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 bb7c3ae..f886f6b 100644 --- a/Assets/uDesktopDuplication/Scripts/Monitor.cs +++ b/Assets/uDesktopDuplication/Scripts/Monitor.cs @@ -51,34 +51,34 @@ public class Monitor get { return height > width; } } - public bool isPointerVisible + public bool isCursorVisible { - get { return Lib.IsPointerVisible(id); } + get { return Lib.IsCursorVisible(id); } } public int pointerX { - get { return Lib.GetPointerX(id); } + get { return Lib.GetCursorX(id); } } public int pointerY { - get { return Lib.GetPointerY(id); } + get { return Lib.GetCursorY(id); } } public int pointerShapeWidth { - get { return Lib.GetPointerShapeWidth(id); } + get { return Lib.GetCursorShapeWidth(id); } } public int pointerShapeHeight { - get { return Lib.GetPointerShapeHeight(id); } + get { return Lib.GetCursorShapeHeight(id); } } - public PointerShapeType pointerShapeType + public CursorShapeType pointerShapeType { - get { return Lib.GetPointerShapeType(id); } + get { return Lib.GetCursorShapeType(id); } } public bool shouldBeUpdated @@ -107,9 +107,9 @@ public class Monitor GL.IssuePluginEvent(Lib.GetRenderEventFunc(), id); } - public void UpdatePointerTexture(System.IntPtr ptr) + public void UpdateCursorTexture(System.IntPtr ptr) { - Lib.UpdatePointerTexture(id, ptr); + Lib.UpdateCursorTexture(id, ptr); } } diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/Common.h b/Plugins/uDesktopDuplication/uDesktopDuplication/Common.h new file mode 100644 index 0000000..ade96bf --- /dev/null +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/Common.h @@ -0,0 +1,8 @@ +#include +#include "IUnityInterface.h" + +class MonitorManager; + +IUnityInterfaces* GetUnity(); +ID3D11Device* GetDevice(); +const std::unique_ptr& GetMonitorManager(); \ No newline at end of file diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/Cursor.cpp b/Plugins/uDesktopDuplication/uDesktopDuplication/Cursor.cpp new file mode 100644 index 0000000..0b66378 --- /dev/null +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/Cursor.cpp @@ -0,0 +1,259 @@ +#include +#include "Common.h" +#include "MonitorManager.h" +#include "Monitor.h" +#include "Cursor.h" + + +Cursor::Cursor(Monitor* monitor) + : monitor_(monitor) +{ +} + + +Cursor::~Cursor() +{ + if (apiBuffer_ != nullptr) + { + delete[] apiBuffer_; + apiBuffer_ = nullptr; + } + if (bgra32Buffer_ != nullptr) + { + delete[] bgra32Buffer_; + bgra32Buffer_ = nullptr; + } +} + + +void Cursor::Update(const DXGI_OUTDUPL_FRAME_INFO& frameInfo) +{ + isVisible_ = frameInfo.PointerPosition.Visible != 0; + x_ = frameInfo.PointerPosition.Position.x; + y_ = frameInfo.PointerPosition.Position.y; + + // TODO: see more information to check where the cursor is. + if (isVisible_) + { + GetMonitorManager()->SetCursorMonitorId(monitor_->GetId()); + } + + if (GetMonitorManager()->GetCursorMonitorId() != monitor_->GetId()) { + return; + } + + // Increase the buffer size if needed + if (frameInfo.PointerShapeBufferSize > apiBufferSize_) + { + if (apiBuffer_) delete[] apiBuffer_; + apiBuffer_ = new BYTE[frameInfo.PointerShapeBufferSize]; + apiBufferSize_ = frameInfo.PointerShapeBufferSize; + } + if (apiBuffer_ == nullptr) return; + + // Get information about the mouse pointer if needed + if (frameInfo.PointerShapeBufferSize != 0) + { + UINT bufferSize; + monitor_->GetDeskDupl()->GetFramePointerShape( + frameInfo.PointerShapeBufferSize, + reinterpret_cast(apiBuffer_), + &bufferSize, + &shapeInfo_); + } + + // cursor type + const auto cursorType = shapeInfo_.Type; + const bool isMono = cursorType == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME; + const bool isColorMask = cursorType == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR; + + // Size + const auto w = shapeInfo_.Width; + const auto h = shapeInfo_.Height / (isMono ? 2 : 1); + const auto p = shapeInfo_.Pitch; + + // Convert the buffer given by API into BGRA32 + const auto bgraBufferSize = w * h * 4; + if (bgraBufferSize > bgra32BufferSize_) + { + if (bgra32Buffer_) delete[] bgra32Buffer_; + bgra32Buffer_ = new BYTE[bgraBufferSize]; + bgra32BufferSize_ = bgraBufferSize; + } + if (bgra32Buffer_ == nullptr) return; + + // 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; + + ID3D11Texture2D* texture = nullptr; + hr = GetDevice()->CreateTexture2D(&desc, nullptr, &texture); + if (FAILED(hr)) + { + return; + } + + D3D11_BOX box; + box.front = 0; + box.back = 1; + box.left = x_; + box.top = y_; + box.right = x_ + w; + box.bottom = y_ + h; + + ID3D11DeviceContext* context; + GetDevice()->GetImmediateContext(&context); + context->CopySubresourceRegion(texture, 0, 0, 0, 0, monitor_->GetUnityTexture(), 0, &box); + context->Release(); + + IDXGISurface* surface = nullptr; + hr = texture->QueryInterface(__uuidof(IDXGISurface), (void**)&surface); + texture->Release(); + if (FAILED(hr)) + { + return; + } + + DXGI_MAPPED_RECT mappedSurface; + hr = surface->Map(&mappedSurface, DXGI_MAP_READ); + if (FAILED(hr)) + { + surface->Release(); + return; + } + + // Finally, get the texture behind the mouse cursor. + 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(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 = apiBuffer_[col / 8 + row * p] & mask; + const BYTE xorMask = 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(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; + } + } + else // DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR + { + auto output32 = reinterpret_cast(bgra32Buffer_); + const auto buffer32 = reinterpret_cast(apiBuffer_); + for (UINT i = 0; i < w * h; ++i) + { + output32[i] = buffer32[i]; + } + } + + return; +} + + +void Cursor::UpdateTexture(ID3D11Texture2D* texture) +{ + if (bgra32Buffer_ == nullptr) return; + ID3D11DeviceContext* context; + GetDevice()->GetImmediateContext(&context); + context->UpdateSubresource(texture, 0, nullptr, bgra32Buffer_, shapeInfo_.Width * 4, 0); + context->Release(); +} + + +bool Cursor::IsVisible() const +{ + return isVisible_; +} + + +int Cursor::GetX() const +{ + return x_; +} + + +int Cursor::GetY() const +{ + return y_; +} + + +int Cursor::GetWidth() const +{ + return shapeInfo_.Width; +} + + +int Cursor::GetHeight() const +{ + return (shapeInfo_.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME) ? + shapeInfo_.Height / 2 : + shapeInfo_.Height; +} + + +int Cursor::GetPitch() const +{ + return shapeInfo_.Pitch; +} + + +int Cursor::GetType() const +{ + return shapeInfo_.Type; +} diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/Cursor.h b/Plugins/uDesktopDuplication/uDesktopDuplication/Cursor.h new file mode 100644 index 0000000..37777cc --- /dev/null +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/Cursor.h @@ -0,0 +1,33 @@ +#include +#include +#include + +class Monitor; + +class Cursor +{ +public: + explicit Cursor(Monitor* monitor); + ~Cursor(); + void Update(const DXGI_OUTDUPL_FRAME_INFO& frameInfo); + void UpdateTexture(ID3D11Texture2D* texture); + + bool IsVisible() const; + int GetX() const; + int GetY() const; + int GetWidth() const; + int GetHeight() const; + int GetPitch() const; + int GetType() const; + +private: + Monitor* monitor_; + 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_; +}; \ No newline at end of file diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/Duplication.cpp b/Plugins/uDesktopDuplication/uDesktopDuplication/Duplication.cpp deleted file mode 100644 index c90741b..0000000 --- a/Plugins/uDesktopDuplication/uDesktopDuplication/Duplication.cpp +++ /dev/null @@ -1,424 +0,0 @@ -#include -#include -#include -#include - -#include "IUnityInterface.h" -#include "IUnityGraphicsD3D11.h" -#include "Duplication.h" - - -Duplication::Duplication(IUnityInterfaces* unity) - : unity_(unity) - , device_(unity->Get()->GetDevice()) -{ - Initialize(); -} - - -Duplication::~Duplication() -{ - Finalize(); - unity_ = nullptr; -} - - -void Duplication::Initialize() -{ - Finalize(); - - // Get factory - IDXGIFactory1* factory; - 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) - { - // Search the main monitor from all outputs - IDXGIOutput* output; - 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); - GetMonitorInfo(monitor.outputDesc.Monitor, &monitor.monitorInfo); - - auto output1 = reinterpret_cast(output); - output1->DuplicateOutput(device_, &monitor.deskDupl); - output->Release(); - - monitors_.push_back(monitor); - } - - adapter->Release(); - } - - factory->Release(); -} - - -void Duplication::Finalize() -{ - // Release all duplicaitons - for (auto& monitor : monitors_) - { - monitor.deskDupl->Release(); - } - monitors_.clear(); -} - - -bool Duplication::IsValidId(int id) const -{ - return id >= 0 && id < monitors_.size(); -} - - -void Duplication::OnRender(int id) -{ - errorCode_ = 0; - errorMessage_ = ""; - - if (!IsValidId(id)) return; - auto& monitor = monitors_[id]; - - if (monitor.deskDupl == nullptr || monitor.texture == nullptr) return; - - IDXGIResource* resource = nullptr; - DXGI_OUTDUPL_FRAME_INFO frameInfo; - - errorCode_ = monitor.deskDupl->AcquireNextFrame(timeout_, &frameInfo, &resource); - if (FAILED(errorCode_)) - { - if (errorCode_ == DXGI_ERROR_ACCESS_LOST) - { - Initialize(); - errorMessage_ = "[IDXGIOutputDuplication::AcquireNextFrame()] Access lost."; - } - else - { - errorMessage_ = "[IDXGIOutputDuplication::AcquireNextFrame()] Maybe timeout."; - } - return; - } - - ID3D11Texture2D* texture; - resource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast(&texture)); - - ID3D11DeviceContext* context; - device_->GetImmediateContext(&context); - context->CopyResource(monitor.texture, texture); - - if (!UpdateMouse(frameInfo, monitor)) - { - errorCode_ = -999; - errorMessage_ = "[UpdateMouse()] failed."; - } - - resource->Release(); - monitor.deskDupl->ReleaseFrame(); -} - - -bool Duplication::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) - { - mouseMonitor_ = monitor.id; - } - - if (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; - 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 Duplication::UpdatePointerTexture(int id, ID3D11Texture2D* ptr) -{ - if (!IsValidId(id)) return; - const auto& pointer = monitors_[id].pointer; - if (!pointer.bgra32Buffer) return; - ID3D11DeviceContext* context; - device_->GetImmediateContext(&context); - context->UpdateSubresource(ptr, 0, nullptr, pointer.bgra32Buffer, pointer.shapeInfo.Width * 4, 0); -} - - -void Duplication::SetTimeout(int timeout) -{ - timeout_ = timeout; -} - - -void Duplication::SetTexturePtr(int id, void* texture) -{ - if (!IsValidId(id)) return; - monitors_[id].texture = reinterpret_cast(texture); -} - - -int Duplication::GetMonitorCount() const -{ - return static_cast(monitors_.size()); -} - - -void Duplication::GetName(int id, char* buf, int len) const -{ - if (!IsValidId(id)) return; - strcpy_s(buf, len, monitors_[id].monitorInfo.szDevice); -} - - -bool Duplication::IsPrimary(int id) const -{ - if (!IsValidId(id)) return false; - return monitors_[id].monitorInfo.dwFlags == MONITORINFOF_PRIMARY; -} - - -int Duplication::GetWidth(int id) const -{ - if (!IsValidId(id)) return -1; - const auto rect = monitors_[id].monitorInfo.rcMonitor; - return rect.right - rect.left; -} - - -int Duplication::GetHeight(int id) const -{ - if (!IsValidId(id)) return -1; - const auto rect = monitors_[id].monitorInfo.rcMonitor; - return rect.bottom - rect.top; -} - - -int Duplication::IsPointerVisible(int id) const -{ - if (!IsValidId(id)) return false; - return monitors_[id].pointer.isVisible; -} - - -int Duplication::GetPointerX(int id) const -{ - if (!IsValidId(id)) return -1; - return monitors_[id].pointer.x; -} - - -int Duplication::GetPointerY(int id) const -{ - if (!IsValidId(id)) return -1; - return monitors_[id].pointer.y; -} - - -int Duplication::GetPointerShapeWidth(int id) const -{ - if (!IsValidId(id)) return -1; - return monitors_[id].pointer.shapeInfo.Width; -} - - -int Duplication::GetPointerShapeHeight(int id) const -{ - if (!IsValidId(id)) return -1; - const auto& info = monitors_[id].pointer.shapeInfo; - return (info.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME) ? info.Height / 2 : info.Height; -} - - -int Duplication::GetPointerShapePitch(int id) const -{ - if (!IsValidId(id)) return -1; - return monitors_[id].pointer.shapeInfo.Pitch; -} - - -int Duplication::GetPointerShapeType(int id) const -{ - if (!IsValidId(id)) return -1; - return monitors_[id].pointer.shapeInfo.Type; -} - - -int Duplication::GetErrorCode() const -{ - return errorCode_; -} - - -void Duplication::GetErrorMessage(char* buf, int len) const -{ - strcpy_s(buf, len, errorMessage_.c_str()); -} \ No newline at end of file diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/Duplication.h b/Plugins/uDesktopDuplication/uDesktopDuplication/Duplication.h deleted file mode 100644 index 1eba567..0000000 --- a/Plugins/uDesktopDuplication/uDesktopDuplication/Duplication.h +++ /dev/null @@ -1,72 +0,0 @@ -#include -#include -#include -#include - -struct IUnityInterfaces; - -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; -}; - -struct Monitor -{ - int id = -1; - IDXGIOutputDuplication* deskDupl = nullptr; - ID3D11Texture2D* texture = nullptr; - DXGI_OUTPUT_DESC outputDesc; - MONITORINFOEX monitorInfo; - Pointer pointer; -}; - -class Duplication -{ -public: - explicit Duplication(IUnityInterfaces* unity); - ~Duplication(); - void OnRender(int id); - -public: - void UpdatePointerTexture(int id, ID3D11Texture2D* ptr); - void SetTimeout(int timeout); - void SetTexturePtr(int id, void* texture); - -public: - int GetMonitorCount() const; - void GetName(int id, char* buf, int len) const; - bool IsPrimary(int id) const; - int GetWidth(int id) const; - int GetHeight(int id) const; - int IsPointerVisible(int id) const; - int GetPointerX(int id) const; - int GetPointerY(int id) const; - int GetPointerShapeWidth(int id) const; - int GetPointerShapeHeight(int id) const; - int GetPointerShapePitch(int id) const; - int GetPointerShapeType(int id) const; - int GetErrorCode() const; - void GetErrorMessage(char* buf, int len) const; - -private: - void Initialize(); - void Finalize(); - bool UpdateMouse(const DXGI_OUTDUPL_FRAME_INFO& frameInfo, Monitor& monitor); - - bool IsValidId(int id) const; - - IUnityInterfaces* unity_ = nullptr; - ID3D11Device* device_ = nullptr; - int mouseMonitor_ = 0; - int timeout_ = 10; - HRESULT errorCode_ = 0; - std::string errorMessage_ = ""; - std::vector monitors_; -}; \ No newline at end of file diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/Monitor.cpp b/Plugins/uDesktopDuplication/uDesktopDuplication/Monitor.cpp new file mode 100644 index 0000000..5883d6b --- /dev/null +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/Monitor.cpp @@ -0,0 +1,118 @@ +#include +#include "Common.h" +#include "Cursor.h" +#include "MonitorManager.h" +#include "Monitor.h" + + +Monitor::Monitor(int id, IDXGIOutput* output) + : id_(id) +{ + // MEMO: 'output' will be released automatically after this ctor. + output->GetDesc(&outputDesc_); + monitorInfo_.cbSize = sizeof(MONITORINFOEX); + GetMonitorInfo(outputDesc_.Monitor, &monitorInfo_); + + auto output1 = reinterpret_cast(output); + output1->DuplicateOutput(GetDevice(), &deskDupl_); + + cursor_ = std::make_unique(this); +} + + +Monitor::~Monitor() +{ + deskDupl_->Release(); +} + + +HRESULT Monitor::Render(UINT timeout) +{ + if (deskDupl_ == nullptr || unityTexture_ == nullptr) return 0; + + IDXGIResource* resource = nullptr; + DXGI_OUTDUPL_FRAME_INFO frameInfo; + + const auto hr = deskDupl_->AcquireNextFrame(timeout, &frameInfo, &resource); + if (FAILED(hr)) + { + return hr; + } + + ID3D11Texture2D* texture; + resource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast(&texture)); + + ID3D11DeviceContext* context; + GetDevice()->GetImmediateContext(&context); + context->CopyResource(unityTexture_, texture); + context->Release(); + + cursor_->Update(frameInfo); + + resource->Release(); + deskDupl_->ReleaseFrame(); + + return 0; +} + + +int Monitor::GetId() const +{ + return id_; +} + + +void Monitor::SetUnityTexture(ID3D11Texture2D* texture) +{ + unityTexture_ = texture; +} + + +ID3D11Texture2D* Monitor::GetUnityTexture() const +{ + return unityTexture_; +} + + +IDXGIOutputDuplication* Monitor::GetDeskDupl() +{ + return deskDupl_; +} + + +const std::unique_ptr& Monitor::GetCursor() +{ + return cursor_; +} + + +void Monitor::UpdateCursorTexture(ID3D11Texture2D* texture) +{ + cursor_->UpdateTexture(texture); +} + + +void Monitor::GetName(char* buf, int len) const +{ + strcpy_s(buf, len, monitorInfo_.szDevice); +} + + +bool Monitor::IsPrimary() const +{ + return monitorInfo_.dwFlags == MONITORINFOF_PRIMARY; +} + + +int Monitor::GetWidth() const +{ + const auto rect = monitorInfo_.rcMonitor; + return rect.right - rect.left; +} + + +int Monitor::GetHeight() const +{ + const auto rect = monitorInfo_.rcMonitor; + return rect.bottom - rect.top; +} \ No newline at end of file diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/Monitor.h b/Plugins/uDesktopDuplication/uDesktopDuplication/Monitor.h new file mode 100644 index 0000000..e3066ef --- /dev/null +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/Monitor.h @@ -0,0 +1,33 @@ +#include +#include +#include + +class Cursor; + +class Monitor +{ +public: + Monitor(int id, IDXGIOutput* output); + ~Monitor(); + HRESULT Render(UINT timeout = 0); + void UpdateCursorTexture(ID3D11Texture2D* texture); + +public: + int GetId() const; + void SetUnityTexture(ID3D11Texture2D* texture); + ID3D11Texture2D* GetUnityTexture() const; + void GetName(char* buf, int len) const; + bool IsPrimary() const; + int GetWidth() const; + int GetHeight() const; + IDXGIOutputDuplication* GetDeskDupl(); + const std::unique_ptr& GetCursor(); + +private: + int id_ = -1; + std::unique_ptr cursor_; + IDXGIOutputDuplication* deskDupl_ = nullptr; + ID3D11Texture2D* unityTexture_ = nullptr; + DXGI_OUTPUT_DESC outputDesc_; + MONITORINFOEX monitorInfo_; +}; \ No newline at end of file diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/MonitorManager.cpp b/Plugins/uDesktopDuplication/uDesktopDuplication/MonitorManager.cpp new file mode 100644 index 0000000..8f9b2dc --- /dev/null +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/MonitorManager.cpp @@ -0,0 +1,221 @@ +#include +#include +#include +#include + +#include "IUnityInterface.h" +#include "IUnityGraphicsD3D11.h" + +#include "Monitor.h" +#include "Cursor.h" +#include "MonitorManager.h" + + +MonitorManager::MonitorManager() +{ + Initialize(); +} + + +MonitorManager::~MonitorManager() +{ + Finalize(); +} + + +void MonitorManager::Initialize() +{ + Finalize(); + + // Get factory + IDXGIFactory1* factory; + 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) + { + // Search the main monitor from all outputs + IDXGIOutput* output; + for (int j = 0; (adapter->EnumOutputs(j, &output) != DXGI_ERROR_NOT_FOUND); ++j) + { + auto monitor = std::make_shared(id++, output); + monitors_.push_back(monitor); + output->Release(); + } + adapter->Release(); + } + factory->Release(); +} + + +void MonitorManager::Finalize() +{ + monitors_.clear(); +} + + +std::shared_ptr MonitorManager::GetMonitor(int id) const +{ + if (id >= 0 && id < monitors_.size()) + { + return monitors_[id]; + } + return nullptr; +} + + +void MonitorManager::OnRender(int id) +{ + if (auto monitor = GetMonitor(id)) + { + // If any monitor setting has changed (e.g. monitor size has changed), + // it is necessary to re-initialize monitors. + const auto hr = monitor->Render(timeout_); + if (hr == DXGI_ERROR_ACCESS_LOST) + { + Initialize(); + } + } +} + + +void MonitorManager::UpdateCursorTexture(int id, ID3D11Texture2D* texture) +{ + if (auto monitor = GetMonitor(id)) + { + monitor->UpdateCursorTexture(texture); + } +} + + +void MonitorManager::SetTimeout(int timeout) +{ + timeout_ = timeout; +} + + +void MonitorManager::SetTexturePtr(int id, void* texture) +{ + if (auto monitor = GetMonitor(id)) + { + auto d3d11Texture = reinterpret_cast(texture); + monitor->SetUnityTexture(d3d11Texture); + } +} + + +int MonitorManager::GetMonitorCount() const +{ + return static_cast(monitors_.size()); +} + + +void MonitorManager::GetName(int id, char* buf, int len) const +{ + if (auto monitor = GetMonitor(id)) + { + monitor->GetName(buf, len); + } +} + + +bool MonitorManager::IsPrimary(int id) const +{ + if (auto monitor = GetMonitor(id)) + { + return monitor->IsPrimary(); + } + return false; +} + + +int MonitorManager::GetWidth(int id) const +{ + if (auto monitor = GetMonitor(id)) + { + return monitor->GetWidth(); + } + return -1; +} + + +int MonitorManager::GetHeight(int id) const +{ + if (auto monitor = GetMonitor(id)) + { + return monitor->GetHeight(); + } + return -1; +} + + +int MonitorManager::IsCursorVisible(int id) const +{ + if (auto monitor = GetMonitor(id)) + { + return monitor->GetCursor()->IsVisible(); + } + return false; +} + + +int MonitorManager::GetCursorX(int id) const +{ + if (auto monitor = GetMonitor(id)) + { + return monitor->GetCursor()->GetX(); + } + return -1; +} + + +int MonitorManager::GetCursorY(int id) const +{ + if (auto monitor = GetMonitor(id)) + { + return monitor->GetCursor()->GetY(); + } + return -1; +} + + +int MonitorManager::GetCursorShapeWidth(int id) const +{ + if (auto monitor = GetMonitor(id)) + { + return monitor->GetCursor()->GetWidth(); + } + return -1; +} + + +int MonitorManager::GetCursorShapeHeight(int id) const +{ + if (auto monitor = GetMonitor(id)) + { + return monitor->GetCursor()->GetHeight(); + } + return -1; +} + + +int MonitorManager::GetCursorShapePitch(int id) const +{ + if (auto monitor = GetMonitor(id)) + { + return monitor->GetCursor()->GetPitch(); + } + return -1; +} + + +int MonitorManager::GetCursorShapeType(int id) const +{ + if (auto monitor = GetMonitor(id)) + { + return monitor->GetCursor()->GetType(); + } + return -1; +} \ No newline at end of file diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/MonitorManager.h b/Plugins/uDesktopDuplication/uDesktopDuplication/MonitorManager.h new file mode 100644 index 0000000..08b9e9b --- /dev/null +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/MonitorManager.h @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include + +struct IUnityInterfaces; +class Monitor; +class Cursor; + +class MonitorManager +{ +public: + explicit MonitorManager(); + ~MonitorManager(); + + void SetCursorMonitorId(int id) { cursorMonitorId_ = id; } + int GetCursorMonitorId() const { return cursorMonitorId_; } + std::shared_ptr GetMonitor(int id) const; + +private: + void Initialize(); + void Finalize(); + +// Setters from Unity +public: + void OnRender(int id); + void UpdateCursorTexture(int id, ID3D11Texture2D* ptr); + void SetTimeout(int timeout); + void SetTexturePtr(int id, void* texture); + +// Getters from Unity +public: + int GetMonitorCount() const; + void GetName(int id, char* buf, int len) const; + bool IsPrimary(int id) const; + int GetWidth(int id) const; + int GetHeight(int id) const; + int IsCursorVisible(int id) const; + int GetCursorX(int id) const; + int GetCursorY(int id) const; + int GetCursorShapeWidth(int id) const; + int GetCursorShapeHeight(int id) const; + int GetCursorShapePitch(int id) const; + int GetCursorShapeType(int id) const; + +private: + int timeout_ = 10; + std::vector> monitors_; + std::shared_ptr cursor_; + int cursorMonitorId_ = -1; +}; \ No newline at end of file diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/IUnityGraphics.h b/Plugins/uDesktopDuplication/uDesktopDuplication/include/IUnityGraphics.h similarity index 100% rename from Plugins/uDesktopDuplication/uDesktopDuplication/IUnityGraphics.h rename to Plugins/uDesktopDuplication/uDesktopDuplication/include/IUnityGraphics.h diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/IUnityGraphicsD3D11.h b/Plugins/uDesktopDuplication/uDesktopDuplication/include/IUnityGraphicsD3D11.h similarity index 100% rename from Plugins/uDesktopDuplication/uDesktopDuplication/IUnityGraphicsD3D11.h rename to Plugins/uDesktopDuplication/uDesktopDuplication/include/IUnityGraphicsD3D11.h diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/IUnityInterface.h b/Plugins/uDesktopDuplication/uDesktopDuplication/include/IUnityInterface.h similarity index 100% rename from Plugins/uDesktopDuplication/uDesktopDuplication/IUnityInterface.h rename to Plugins/uDesktopDuplication/uDesktopDuplication/include/IUnityInterface.h diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/main.cpp b/Plugins/uDesktopDuplication/uDesktopDuplication/main.cpp index 4ee35df..03f5d78 100644 --- a/Plugins/uDesktopDuplication/uDesktopDuplication/main.cpp +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/main.cpp @@ -6,14 +6,35 @@ #include "IUnityInterface.h" #include "IUnityGraphics.h" -#include "Duplication.h" +#include "IUnityGraphicsD3D11.h" + +#include "Common.h" +#include "MonitorManager.h" #pragma comment(lib, "dxgi.lib") namespace { - std::unique_ptr g_dupl; + IUnityInterfaces* g_unity = nullptr; + std::unique_ptr g_manager; +} + + +IUnityInterfaces* GetUnity() +{ + return g_unity; +} + + +ID3D11Device* GetDevice() +{ + return GetUnity()->Get()->GetDevice(); +} + +const std::unique_ptr& GetMonitorManager() +{ + return g_manager; } @@ -21,17 +42,18 @@ extern "C" { UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces* unityInterfaces) { - g_dupl = std::make_unique(unityInterfaces); + g_unity = unityInterfaces; + g_manager = std::make_unique(); } UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UnityPluginUnload() { - g_dupl.reset(); + g_manager.reset(); } void UNITY_INTERFACE_API OnRenderEvent(int id) { - g_dupl->OnRender(id); + g_manager->OnRender(id); } UNITY_INTERFACE_EXPORT UnityRenderingEvent UNITY_INTERFACE_API GetRenderEventFunc() @@ -41,87 +63,76 @@ extern "C" UNITY_INTERFACE_EXPORT size_t UNITY_INTERFACE_API GetMonitorCount() { - return g_dupl->GetMonitorCount(); + return g_manager->GetMonitorCount(); } UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API SetTimeout(int timeout) { - g_dupl->SetTimeout(timeout); + g_manager->SetTimeout(timeout); } UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API GetName(int id, char* buf, int len) { - g_dupl->GetName(id, buf, len); + g_manager->GetName(id, buf, len); } UNITY_INTERFACE_EXPORT bool UNITY_INTERFACE_API IsPrimary(int id) { - return g_dupl->IsPrimary(id); + return g_manager->IsPrimary(id); } UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetWidth(int id) { - return g_dupl->GetWidth(id); + return g_manager->GetWidth(id); } UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetHeight(int id) { - return g_dupl->GetHeight(id); + return g_manager->GetHeight(id); } - UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API IsPointerVisible(int id) + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API IsCursorVisible(int id) { - return g_dupl->IsPointerVisible(id); + return g_manager->IsCursorVisible(id); } - UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerX(int id) + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetCursorX(int id) { - return g_dupl->GetPointerX(id); + return g_manager->GetCursorX(id); } - UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerY(int id) + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetCursorY(int id) { - return g_dupl->GetPointerY(id); + return g_manager->GetCursorY(id); } - UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapeWidth(int id) + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetCursorShapeWidth(int id) { - return g_dupl->GetPointerShapeWidth(id); + return g_manager->GetCursorShapeWidth(id); } - UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapeHeight(int id) + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetCursorShapeHeight(int id) { - return g_dupl->GetPointerShapeHeight(id); + return g_manager->GetCursorShapeHeight(id); } - UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapePitch(int id) + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetCursorShapePitch(int id) { - return g_dupl->GetPointerShapePitch(id); + return g_manager->GetCursorShapePitch(id); } - UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapeType(int id) + UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetCursorShapeType(int id) { - return g_dupl->GetPointerShapeType(id); + return g_manager->GetCursorShapeType(id); } - UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UpdatePointerTexture(int id, ID3D11Texture2D* ptr) + UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UpdateCursorTexture(int id, ID3D11Texture2D* ptr) { - g_dupl->UpdatePointerTexture(id, ptr); + g_manager->UpdateCursorTexture(id, ptr); } UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API SetTexturePtr(int id, void* texture) { - g_dupl->SetTexturePtr(id, texture); + g_manager->SetTexturePtr(id, texture); } - - UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetErrorCode() - { - return g_dupl->GetErrorCode(); - } - - UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API GetErrorMessage(char* buf, int len) - { - g_dupl->GetErrorMessage(buf, len); - } - } \ No newline at end of file diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/uDesktopDuplication.vcxproj b/Plugins/uDesktopDuplication/uDesktopDuplication/uDesktopDuplication.vcxproj index ce95a48..b74e58c 100644 --- a/Plugins/uDesktopDuplication/uDesktopDuplication/uDesktopDuplication.vcxproj +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/uDesktopDuplication.vcxproj @@ -69,8 +69,12 @@ - - + + $(VC_IncludePath);$(WindowsSDK_IncludePath);include + + + $(VC_IncludePath);$(WindowsSDK_IncludePath);include + Level3 @@ -122,14 +126,19 @@ - - + + + + - - - - + + + + + + + diff --git a/Plugins/uDesktopDuplication/uDesktopDuplication/uDesktopDuplication.vcxproj.filters b/Plugins/uDesktopDuplication/uDesktopDuplication/uDesktopDuplication.vcxproj.filters new file mode 100644 index 0000000..bcdb742 --- /dev/null +++ b/Plugins/uDesktopDuplication/uDesktopDuplication/uDesktopDuplication.vcxproj.filters @@ -0,0 +1,29 @@ + + + + + {a820eab3-2c2a-4d56-9670-064198e6a1cf} + + + + + Include + + + Include + + + Include + + + + + + + + + + + + + \ No newline at end of file