call AcquireNextFrame on isolated thread

This commit is contained in:
ousttrue
2016-12-28 02:12:03 +09:00
parent e6407d1c3b
commit f6ce875463
12 changed files with 484 additions and 19 deletions
@@ -45,6 +45,10 @@ public class DisplacementMapping : MonoBehaviour
case TargetMonitor.Next: ++id; break; case TargetMonitor.Next: ++id; break;
case TargetMonitor.Prev: --id; break; case TargetMonitor.Prev: --id; break;
} }
if (uDesktopDuplication.Manager.monitorCount == 0)
{
return;
}
id = Mathf.Clamp(id, 0, uDesktopDuplication.Manager.monitorCount - 1); id = Mathf.Clamp(id, 0, uDesktopDuplication.Manager.monitorCount - 1);
var monitor = uDesktopDuplication.Manager.GetMonitor(id); var monitor = uDesktopDuplication.Manager.GetMonitor(id);
monitor.shouldBeUpdated = true; monitor.shouldBeUpdated = true;
+1 -1
View File
@@ -86,7 +86,7 @@ public static class Lib
[DllImport("uDesktopDuplication")] [DllImport("uDesktopDuplication")]
public static extern void Finalize(); public static extern void Finalize();
[DllImport("uDesktopDuplication")] [DllImport("uDesktopDuplication")]
public static extern void Reinitialize(); public static extern void Reinitialize(bool useThread);
[DllImport("uDesktopDuplication")] [DllImport("uDesktopDuplication")]
public static extern void Update(); public static extern void Update();
[DllImport("uDesktopDuplication")] [DllImport("uDesktopDuplication")]
@@ -57,6 +57,8 @@ public class Manager : MonoBehaviour
[SerializeField] int desktopDuplicationApiTimeout = 0; [SerializeField] int desktopDuplicationApiTimeout = 0;
[SerializeField] float retryReinitializationDuration = 1f; [SerializeField] float retryReinitializationDuration = 1f;
[SerializeField]
bool useThread = false;
private Coroutine renderCoroutine_ = null; private Coroutine renderCoroutine_ = null;
private bool shouldReinitialize_ = false; private bool shouldReinitialize_ = false;
@@ -143,7 +145,7 @@ public class Manager : MonoBehaviour
public void Reinitialize() public void Reinitialize()
{ {
Debug.Log("[uDD] Reinitialize"); Debug.Log("[uDD] Reinitialize");
Lib.Reinitialize(); Lib.Reinitialize(useThread);
CreateMonitors(); CreateMonitors();
if (onReinitialized != null) { if (onReinitialized != null) {
onReinitialized(); onReinitialized();
@@ -11,16 +11,23 @@ using namespace Microsoft::WRL;
Monitor::Monitor(int id) Monitor::Monitor(int id)
: id_(id) : id_(id)
{ {
ZeroMemory(&monitorInfo_, sizeof(monitorInfo_));
} }
Monitor::~Monitor() Monitor::~Monitor()
{ {
m_stopLoop = true;
if (deskDupl_) if (deskDupl_)
{ {
deskDupl_->Release(); deskDupl_->Release();
deskDupl_ = nullptr; deskDupl_ = nullptr;
} }
if (m_desktopDuplicationThread.joinable()) {
m_desktopDuplicationThread.join();
}
} }
@@ -5,6 +5,7 @@
#include <wrl/client.h> #include <wrl/client.h>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <thread>
#include "Common.h" #include "Common.h"
enum class MonitorState enum class MonitorState
@@ -23,6 +24,8 @@ enum class MonitorState
class Monitor class Monitor
{ {
class ThreadedDesktopDuplicator *m_threaded = nullptr;
public: public:
using State = MonitorState; using State = MonitorState;
@@ -57,6 +60,19 @@ public:
bool UseGetPixels() const; bool UseGetPixels() const;
bool GetPixels(BYTE* output, int x, int y, int width, int height); bool GetPixels(BYTE* output, int x, int y, int width, int height);
public:
void InitializeThreaded(const Microsoft::WRL::ComPtr<struct IDXGIAdapter> &adapter
, bool isUnityDeviceAdapter
, const Microsoft::WRL::ComPtr<struct IDXGIOutput> &output);
void CopyTextureFromThread();
private:
void DuplicateAndCopyLoop();
void DuplicateAndMapLoop();
std::shared_ptr<class IsolatedD3D11Device> m_pIsolated;
std::thread m_desktopDuplicationThread;
volatile bool m_stopLoop = false;
std::shared_ptr<class TextureQueue> m_textureQueue;
private: private:
void UpdateCursor(const DXGI_OUTDUPL_FRAME_INFO& frameInfo); void UpdateCursor(const DXGI_OUTDUPL_FRAME_INFO& frameInfo);
void UpdateMetadata(const DXGI_OUTDUPL_FRAME_INFO& frameInfo); void UpdateMetadata(const DXGI_OUTDUPL_FRAME_INFO& frameInfo);
@@ -17,9 +17,9 @@
using namespace Microsoft::WRL; using namespace Microsoft::WRL;
MonitorManager::MonitorManager() MonitorManager::MonitorManager(LUID unityAdapterLuid)
: unityAdapterLuid_(unityAdapterLuid)
{ {
Initialize();
} }
@@ -29,7 +29,7 @@ MonitorManager::~MonitorManager()
} }
void MonitorManager::Initialize() void MonitorManager::Initialize(LUID unityAdapterLuid, bool useThread)
{ {
Finalize(); Finalize();
@@ -46,12 +46,23 @@ void MonitorManager::Initialize()
ComPtr<IDXGIAdapter1> adapter; ComPtr<IDXGIAdapter1> adapter;
for (int i = 0; (factory->EnumAdapters1(i, &adapter) != DXGI_ERROR_NOT_FOUND); ++i) for (int i = 0; (factory->EnumAdapters1(i, &adapter) != DXGI_ERROR_NOT_FOUND); ++i)
{ {
DXGI_ADAPTER_DESC desc;
adapter->GetDesc(&desc);
// Search the main monitor from all outputs // Search the main monitor from all outputs
ComPtr<IDXGIOutput> output; ComPtr<IDXGIOutput> output;
for (int j = 0; (adapter->EnumOutputs(j, &output) != DXGI_ERROR_NOT_FOUND); ++j) for (int j = 0; (adapter->EnumOutputs(j, &output) != DXGI_ERROR_NOT_FOUND); ++j)
{ {
auto monitor = std::make_shared<Monitor>(id++); auto monitor = std::make_shared<Monitor>(id++);
monitor->Initialize(output.Get()); if (useThread) {
monitor->InitializeThreaded(adapter
, desc.AdapterLuid.HighPart == unityAdapterLuid.HighPart
&& desc.AdapterLuid.LowPart == unityAdapterLuid.LowPart
, output);
}
else {
monitor->Initialize(output.Get());
}
monitors_.push_back(monitor); monitors_.push_back(monitor);
} }
} }
@@ -70,8 +81,9 @@ void MonitorManager::RequireReinitilization()
} }
void MonitorManager::Reinitialize() void MonitorManager::Reinitialize(bool useThread)
{ {
useThread_ = useThread;
Debug::Log("MonitorManager::Reinitialize()"); Debug::Log("MonitorManager::Reinitialize()");
Initialize(); Initialize();
SendMessageToUnity(Message::Reinitialized); SendMessageToUnity(Message::Reinitialized);
@@ -104,6 +116,9 @@ bool MonitorManager::HasMonitorCountChanged() const
std::shared_ptr<Monitor> MonitorManager::GetMonitor(int id) const std::shared_ptr<Monitor> MonitorManager::GetMonitor(int id) const
{ {
if(monitors_.empty()) {
const_cast<MonitorManager*>(this)->Initialize();
}
if (id >= 0 && id < static_cast<int>(monitors_.size())) if (id >= 0 && id < static_cast<int>(monitors_.size()))
{ {
return monitors_[id]; return monitors_[id];
@@ -122,7 +137,7 @@ void MonitorManager::Update()
{ {
if (isReinitializationRequired_) if (isReinitializationRequired_)
{ {
Reinitialize(); Reinitialize(useThread_);
isReinitializationRequired_ = false; isReinitializationRequired_ = false;
} }
} }
@@ -142,6 +157,9 @@ int MonitorManager::GetTimeout() const
int MonitorManager::GetMonitorCount() const int MonitorManager::GetMonitorCount() const
{ {
if (monitors_.empty()) {
const_cast<MonitorManager*>(this)->Initialize();
}
return static_cast<int>(monitors_.size()); return static_cast<int>(monitors_.size());
} }
@@ -13,9 +13,9 @@ class Cursor;
class MonitorManager class MonitorManager
{ {
public: public:
explicit MonitorManager(); explicit MonitorManager(LUID unityAdapterLuid_);
~MonitorManager(); ~MonitorManager();
void Reinitialize(); void Reinitialize(bool useThread);
bool HasMonitorCountChanged() const; bool HasMonitorCountChanged() const;
void RequireReinitilization(); void RequireReinitilization();
void SetCursorMonitorId(int id) { cursorMonitorId_ = id; } void SetCursorMonitorId(int id) { cursorMonitorId_ = id; }
@@ -24,7 +24,8 @@ public:
std::shared_ptr<Cursor> GetCursor() const; std::shared_ptr<Cursor> GetCursor() const;
private: private:
void Initialize(); void Initialize(){ Initialize(unityAdapterLuid_, useThread_); }
void Initialize(LUID unityAdapterLuid, bool useThread);
void Finalize(); void Finalize();
// Setters from Unity // Setters from Unity
@@ -38,8 +39,12 @@ public:
int GetMonitorCount() const; int GetMonitorCount() const;
int GetTotalWidth() const; int GetTotalWidth() const;
int GetTotalHeight() const; int GetTotalHeight() const;
bool UseThread()const { return useThread_; }
private: private:
LUID unityAdapterLuid_;
bool useThread_ = false;
int timeout_ = 10; int timeout_ = 10;
bool enableTextureCopyFromGpuToCpu_ = false; bool enableTextureCopyFromGpuToCpu_ = false;
std::vector<std::shared_ptr<Monitor>> monitors_; std::vector<std::shared_ptr<Monitor>> monitors_;
@@ -0,0 +1,386 @@
#include "Monitor.h"
#include "MonitorManager.h"
#include "Debug.h"
#include <ShellScalingApi.h>
#include <queue>
#pragma comment(lib, "d3d11.lib")
using namespace Microsoft::WRL;
///
/// Thraed safe self created ID3D11Device from specified adapter
///
class IsolatedD3D11Device
{
ComPtr<ID3D11Device> m_device;
ComPtr<ID3D11Texture2D> m_copyTarget;
public:
ComPtr<ID3D11Device> GetDevice()const { return m_device; }
bool CreateDevice(const ComPtr<IDXGIAdapter> &adapter)
{
UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT // D2D Compatible
//| D3D11_CREATE_DEVICE_SINGLETHREADED
//| D3D11_CREATE_DEVICE_VIDEO_SUPPORT // MediaFoundation
;
D3D_FEATURE_LEVEL FeatureLevelsRequested[6] = {
D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2, D3D_FEATURE_LEVEL_9_1 };
UINT numLevelsRequested = sizeof(FeatureLevelsRequested) / sizeof(D3D_FEATURE_LEVEL);
D3D_FEATURE_LEVEL FeatureLevelsSupported;
if (FAILED(D3D11CreateDevice(adapter.Get()
, adapter ? D3D_DRIVER_TYPE_UNKNOWN : D3D_DRIVER_TYPE_HARDWARE, nullptr
, flags
, FeatureLevelsRequested, numLevelsRequested
, D3D11_SDK_VERSION
, &m_device
, &FeatureLevelsSupported
, nullptr
))) {
return false;
}
return true;
}
ComPtr<ID3D11Texture2D> CreateCopyTargetWithSharedFlag(const ComPtr<ID3D11Texture2D> &src)
{
D3D11_TEXTURE2D_DESC desc;
src->GetDesc(&desc);
if (m_copyTarget) {
D3D11_TEXTURE2D_DESC target_desc;
m_copyTarget->GetDesc(&target_desc);
if (
target_desc.Format == desc.Format
&& target_desc.Width == desc.Width
&& target_desc.Height == desc.Height
) {
// already created
return m_copyTarget;
}
}
//desc.BindFlags |= D3D11_BIND_SHADER_RESOURCE;
desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED; // for share texture with unity device
if (FAILED(m_device->CreateTexture2D(&desc, NULL, &m_copyTarget)))
{
return nullptr;
}
return m_copyTarget;
}
};
// RAII helper
class ReleaseGuard
{
ComPtr<IDXGIOutputDuplication> m_dd;
ReleaseGuard(const ReleaseGuard &)=delete;
ReleaseGuard& operator=(const ReleaseGuard &)=delete;
public:
ReleaseGuard(const ComPtr<IDXGIOutputDuplication> &dd)
: m_dd(dd)
{}
~ReleaseGuard()
{
m_dd->ReleaseFrame();
}
};
struct QueueItem
{
ComPtr<ID3D11Texture2D> Texture;
DXGI_OUTDUPL_FRAME_INFO Info;
QueueItem()
{}
QueueItem(const ComPtr<ID3D11Texture2D> &texture
, const DXGI_OUTDUPL_FRAME_INFO &info
)
: Texture(texture), Info(info)
{}
};
class TextureQueue
{
std::queue<QueueItem> m_queue;
std::mutex m_mutex;
public:
void Enqueue(const QueueItem &item)
{
std::lock_guard<std::mutex> lk(m_mutex);
m_queue.push(item);
}
QueueItem Dequeue()
{
std::lock_guard<std::mutex> lk(m_mutex);
if (m_queue.empty()) {
return QueueItem();
}
auto front = m_queue.front();
m_queue.pop();
return front;
}
};
void Monitor::InitializeThreaded(
const ComPtr<IDXGIAdapter> &adapter
, bool isUnityDeviceAdapter
, const ComPtr<IDXGIOutput> &output
)
{
m_pIsolated = std::make_shared<IsolatedD3D11Device>();
m_textureQueue = std::make_shared<TextureQueue>();
if (!m_pIsolated->CreateDevice(adapter)) {
return;
}
if (FAILED(output->GetDesc(&outputDesc_)))
{
Debug::Error("Monitor::Initialize() => IDXGIOutput::GetDesc() failed.");
return;
}
monitorInfo_.cbSize = sizeof(MONITORINFOEX);
if (!GetMonitorInfo(outputDesc_.Monitor, &monitorInfo_))
{
Debug::Error("Monitor::Initialize() => GetMonitorInfo() failed.");
return;
}
else
{
const auto rect = monitorInfo_.rcMonitor;
width_ = rect.right - rect.left;
height_ = rect.bottom - rect.top;
}
if (FAILED(GetDpiForMonitor(outputDesc_.Monitor, MDT_RAW_DPI, &dpiX_, &dpiY_)))
{
Debug::Error("Monitor::Initialize() => GetDpiForMonitor() failed.");
// DPI is set as -1, so the application has to use the appropriate value.
}
ComPtr<IDXGIOutput1> output1;
if (FAILED(output.As(&output1))) {
return;
}
// use self created device
auto hr = output1->DuplicateOutput(m_pIsolated->GetDevice().Get(), &deskDupl_);
switch (hr)
{
case S_OK:
{
state_ = State::Available;
const auto rot = static_cast<DXGI_MODE_ROTATION>(GetRotation());
Debug::Log("Monitor::Initialize() => OK.");
Debug::Log(" ID : ", GetId());
Debug::Log(" Size : (", GetWidth(), ", ", GetHeight(), ")");
Debug::Log(" DPI : (", GetDpiX(), ", ", GetDpiY(), ")");
Debug::Log(" Rot : ",
rot == DXGI_MODE_ROTATION_IDENTITY ? "Landscape" :
rot == DXGI_MODE_ROTATION_ROTATE90 ? "Portrait" :
rot == DXGI_MODE_ROTATION_ROTATE180 ? "Landscape (flipped)" :
rot == DXGI_MODE_ROTATION_ROTATE270 ? "Portrait (flipped)" :
"Unspecified");
break;
}
case E_INVALIDARG:
{
state_ = State::InvalidArg;
Debug::Error("Monitor::Initialize() => Invalid arguments.");
break;
}
case E_ACCESSDENIED:
{
// For example, when the user presses Ctrl + Alt + Delete and the screen
// switches to admin screen, this error occurs.
state_ = State::AccessDenied;
Debug::Error("Monitor::Initialize() => Access denied.");
break;
}
case DXGI_ERROR_UNSUPPORTED:
{
// If the display adapter on the computer is running under the Microsoft Hybrid system,
// this error occurs.
state_ = State::Unsupported;
Debug::Error("Monitor::Initialize() => Unsupported display.");
break;
}
case DXGI_ERROR_NOT_CURRENTLY_AVAILABLE:
{
// When other application use Desktop Duplication API, this error occurs.
state_ = State::CurrentlyNotAvailable;
Debug::Error("Monitor::Initialize() => Currently not available.");
break;
}
case DXGI_ERROR_SESSION_DISCONNECTED:
{
state_ = State::SessionDisconnected;
Debug::Error("Monitor::Initialize() => Session disconnected.");
break;
}
default:
{
state_ = State::Unknown;
Debug::Error("Monitor::Render() => Unknown Error.");
break;
}
}
// start desktop duplication thread
m_stopLoop = false;
if (isUnityDeviceAdapter) {
m_desktopDuplicationThread = std::thread(std::bind(&Monitor::DuplicateAndCopyLoop, this));
}
else {
m_desktopDuplicationThread = std::thread(std::bind(&Monitor::DuplicateAndMapLoop, this));
}
}
void Monitor::CopyTextureFromThread()
{
QueueItem item;
if (m_textureQueue) {
item=m_textureQueue->Dequeue();
}
auto texture = item.Texture;
if (!texture) {
return;
}
auto &frameInfo = item.Info;
// Get texture
if (unityTexture_)
{
D3D11_TEXTURE2D_DESC srcDesc, dstDesc;
texture->GetDesc(&srcDesc);
unityTexture_->GetDesc(&dstDesc);
if (srcDesc.Width != dstDesc.Width ||
srcDesc.Height != dstDesc.Height)
{
Debug::Error("Monitor::Render() => Texture sizes are defferent.");
Debug::Error(" Source : (", srcDesc.Width, ", ", srcDesc.Height, ")");
Debug::Error(" Dest : (", dstDesc.Width, ", ", dstDesc.Height, ")");
//Debug::Log(" => Try modifying width/height using reported value from DDA.");
//width_ = srcDesc.Width;
//height_ = srcDesc.Height;
state_ = MonitorState::TextureSizeInconsistent;
//SendMessageToUnity(Message::TextureSizeChanged);
}
else
{
ComPtr<ID3D11DeviceContext> context;
GetDevice()->GetImmediateContext(&context);
context->CopyResource(unityTexture_, texture.Get());
}
}
UpdateMetadata(frameInfo);
if (frameInfo.PointerPosition.Visible)
{
GetMonitorManager()->SetCursorMonitorId(id_);
}
if (GetMonitorManager()->GetCursorMonitorId() == id_)
{
UpdateCursor(frameInfo);
}
if (UseGetPixels())
{
CopyTextureFromGpuToCpu(texture.Get());
}
hasBeenUpdated_ = true;
}
void Monitor::DuplicateAndCopyLoop()
{
if (!deskDupl_)return;
while (!m_stopLoop)
{
ComPtr<IDXGIResource> resource;
DXGI_OUTDUPL_FRAME_INFO frameInfo;
auto hr = deskDupl_->AcquireNextFrame(INFINITE, &frameInfo, &resource);
if (FAILED(hr)) {
switch (hr)
{
case DXGI_ERROR_ACCESS_LOST:
{
// If any monitor setting has changed (e.g. monitor size has changed),
// it is necessary to re-initialize monitors.
//Debug::Log("Monitor::Render() => DXGI_ERROR_ACCESS_LOST.");
state_ = State::AccessLost;
break;
}
case DXGI_ERROR_WAIT_TIMEOUT:
{
// This often occurs when timeout value is small and it is not problem.
// Debug::Log("Monitor::Render() => DXGI_ERROR_WAIT_TIMEOUT.");
break;
}
case DXGI_ERROR_INVALID_CALL:
{
//Debug::Error("Monitor::Render() => DXGI_ERROR_INVALID_CALL.");
break;
}
case E_INVALIDARG:
{
//Debug::Error("Monitor::Render() => E_INVALIDARG.");
break;
}
default:
{
state_ = State::Unknown;
//Debug::Error("Monitor::Render() => Unknown Error.");
break;
}
}
}
else
{
ReleaseGuard guard(deskDupl_);
ComPtr<ID3D11Texture2D> texture;
if (FAILED(resource.As(&texture))) {
return;
}
// copy target
auto copyTarget = m_pIsolated->CreateCopyTargetWithSharedFlag(texture);
if (!copyTarget) {
return;
}
// copy
ComPtr<ID3D11DeviceContext> context;
m_pIsolated->GetDevice()->GetImmediateContext(&context);
context->CopyResource(copyTarget.Get(), texture.Get());
m_textureQueue->Enqueue(QueueItem(copyTarget, frameInfo));
}
}
}
void Monitor::DuplicateAndMapLoop()
{
// not implemented;
}
@@ -7,6 +7,7 @@
#include "IUnityInterface.h" #include "IUnityInterface.h"
#include "IUnityGraphics.h" #include "IUnityGraphics.h"
#include "include/IUnityGraphicsD3D11.h"
#include "Common.h" #include "Common.h"
#include "Debug.h" #include "Debug.h"
@@ -36,7 +37,25 @@ extern "C"
if (g_unity && !g_manager) if (g_unity && !g_manager)
{ {
Debug::Initialize(); Debug::Initialize();
g_manager = std::make_unique<MonitorManager>();
auto device = g_unity->Get<IUnityGraphicsD3D11>()->GetDevice();
Microsoft::WRL::ComPtr<IDXGIDevice1> dxgiDevice;
if (FAILED(device->QueryInterface(IID_PPV_ARGS(&dxgiDevice)))){
Debug::Error("fatal");
return;
}
Microsoft::WRL::ComPtr<IDXGIAdapter> dxgiAdapter;
if (FAILED(dxgiDevice->GetAdapter(&dxgiAdapter))) {
Debug::Error("fatal");
return;
}
DXGI_ADAPTER_DESC desc;
dxgiAdapter->GetDesc(&desc);
g_manager = std::make_unique<MonitorManager>(desc.AdapterLuid);
} }
} }
@@ -87,7 +106,12 @@ extern "C"
if (!g_manager) return; if (!g_manager) return;
if (auto monitor = g_manager->GetMonitor(id)) if (auto monitor = g_manager->GetMonitor(id))
{ {
monitor->Render(g_manager->GetTimeout()); if (g_manager->UseThread()) {
monitor->CopyTextureFromThread();
}
else {
monitor->Render(g_manager->GetTimeout());
}
} }
} }
@@ -96,10 +120,10 @@ extern "C"
return OnRenderEvent; return OnRenderEvent;
} }
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API Reinitialize() UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API Reinitialize(int useThread)
{ {
if (!g_manager) return; if (!g_manager) return;
return g_manager->Reinitialize(); return g_manager->Reinitialize(useThread);
} }
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API Update() UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API Update()
@@ -90,7 +90,8 @@
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
</ClCompile> </ClCompile>
<PostBuildEvent> <PostBuildEvent>
<Command>copy /Y "$(SolutionDir)$(Platform)\$(Configuration)\$(TargetFileName)" "$(SolutionDir)..\..\Assets\$(ProjectName)\Plugins\x86_64"</Command> <Command>copy /Y "$(SolutionDir)$(Platform)\$(Configuration)\$(TargetFileName)" "$(SolutionDir)..\..\Assets\$(ProjectName)\Plugins\x86_64"
copy /Y "$(SolutionDir)$(Platform)\$(Configuration)\$(TargetName).pdb" "$(SolutionDir)..\..\Assets\$(ProjectName)\Plugins\x86_64"</Command>
</PostBuildEvent> </PostBuildEvent>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
@@ -144,6 +145,7 @@
<ClCompile Include="Main.cpp" /> <ClCompile Include="Main.cpp" />
<ClCompile Include="Monitor.cpp" /> <ClCompile Include="Monitor.cpp" />
<ClCompile Include="Cursor.cpp" /> <ClCompile Include="Cursor.cpp" />
<ClCompile Include="ThreadedDesktopDuplicator.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="Common.h" /> <ClInclude Include="Common.h" />
@@ -28,5 +28,6 @@
<ClCompile Include="MonitorManager.cpp" /> <ClCompile Include="MonitorManager.cpp" />
<ClCompile Include="Common.cpp" /> <ClCompile Include="Common.cpp" />
<ClCompile Include="Debug.cpp" /> <ClCompile Include="Debug.cpp" />
<ClCompile Include="ThreadedDesktopDuplicator.cpp" />
</ItemGroup> </ItemGroup>
</Project> </Project>