Compare commits

...

4 Commits

Author SHA1 Message Date
hecomi
f7eba2434f fix bug #2 that causes crash after returning from UAC. 2016-11-24 23:38:07 +09:00
hecomi
ddbfad6e95 upload cursor texture in native plugin. 2016-11-20 22:35:52 +09:00
hecomi
49aef432e7 improve performance. 2016-11-20 15:59:06 +09:00
hecomi
fc81892917 cache cursor texture pointer to improve performance. 2016-11-20 15:41:47 +09:00
16 changed files with 232 additions and 157 deletions

View File

@@ -3,6 +3,8 @@ using MeshForwardDirection = uDesktopDuplication.Texture.MeshForwardDirection;
public class MultipleMonitorRoundLayouter : MultipleMonitorLayouter
{
[SerializeField] bool debugDraw = true;
public float radius = 10f;
public Vector3 offsetAngle = Vector3.zero;
@@ -80,7 +82,7 @@ public class MultipleMonitorRoundLayouter : MultipleMonitorLayouter
protected override void Update()
{
base.Update();
DebugDraw();
if (debugDraw) DebugDraw();
}
void DebugDraw()

View File

@@ -1,6 +1,4 @@
using UnityEngine;
using UnityEngine.Assertions;
using System.Collections.Generic;
namespace uDesktopDuplication
{
@@ -15,7 +13,6 @@ public class Cursor : MonoBehaviour
private Texture uddTexture_;
private Monitor monitor { get { return uddTexture_.monitor; } }
private Dictionary<int, Texture2D> textures_ = new Dictionary<int, Texture2D>();
void Start()
{
@@ -26,9 +23,8 @@ public class Cursor : MonoBehaviour
{
if (monitor.isCursorVisible) {
UpdatePosition();
UpdateTexture();
}
UpdateMaterial();
UpdateCoords();
}
void UpdatePosition()
@@ -40,33 +36,10 @@ public class Cursor : MonoBehaviour
worldPosition = transform.TransformPoint(localPos);
}
void UpdateTexture()
{
var w = monitor.cursorShapeWidth;
var h = monitor.cursorShapeHeight;
if (w == 0 || h == 0) return;
var key = w + h * 100;
if (!textures_.ContainsKey(key)) {
var texture = new Texture2D(w, h, TextureFormat.BGRA32, false);
texture.wrapMode = TextureWrapMode.Clamp;
textures_.Add(key, texture);
}
var cursorTexture = textures_[key];
Assert.IsNotNull(cursorTexture);
monitor.GetCursorTexture(cursorTexture.GetNativeTexturePtr());
uddTexture_.material.SetTexture("_CursorTex", cursorTexture);
}
void UpdateMaterial()
void UpdateCoords()
{
var x = monitor.isCursorVisible ? (float)monitor.cursorX / monitor.width : -9999f;
var y = monitor.isCursorVisible ? (float)monitor.cursorY / monitor.height : -9999f;
var w = (float)monitor.cursorShapeWidth / monitor.width;
var h = (float)monitor.cursorShapeHeight / monitor.height;
uddTexture_.material.SetVector("_CursorPositionScale", new Vector4(x, y, w, h));
coord = new Vector2(x, y);
}
}

View File

@@ -15,15 +15,17 @@ public class Manager : MonoBehaviour
public static Manager CreateInstance()
{
if (instance_) {
return instance_;
}
if (instance_ != null) return instance_;
var manager = FindObjectOfType<Manager>();
if (manager) return manager;
if (manager) {
instance_ = manager;
return manager;
}
var go = new GameObject("uDesktopDuplicationManager");
return go.AddComponent<Manager>();
instance_ = go.AddComponent<Manager>();
return instance_;
}
private List<Monitor> monitors_ = new List<Monitor>();
@@ -74,10 +76,16 @@ public class Manager : MonoBehaviour
void Awake()
{
// for simple singleton
if (instance_ != null) {
if (instance_ == this) {
return;
}
if (instance_ != null && instance_ != this) {
Destroy(gameObject);
return;
}
instance_ = this;
Lib.SetDebugMode(debugMode);
@@ -134,11 +142,12 @@ public class Manager : MonoBehaviour
for (int i = 0; i < monitors.Count; ++i) {
var monitor = monitors[i];
var state = monitor.state;
if (
monitor.state == MonitorState.NotSet ||
monitor.state == MonitorState.AccessLost ||
monitor.state == MonitorState.AccessDenied ||
monitor.state == MonitorState.SessionDisconnected
state == MonitorState.NotSet ||
state == MonitorState.AccessLost ||
state == MonitorState.AccessDenied ||
state == MonitorState.SessionDisconnected
) {
reinitializeNeeded = true;
break;

View File

@@ -240,6 +240,7 @@ public class Monitor
}
private Texture2D texture_;
private System.IntPtr texturePtr_;
public Texture2D texture
{
get
@@ -252,8 +253,8 @@ public class Monitor
public void Render()
{
if (texture_ && available) {
Lib.SetTexturePtr(id, texture_.GetNativeTexturePtr());
if (texture_ && available && texturePtr_ != System.IntPtr.Zero) {
Lib.SetTexturePtr(id, texturePtr_);
GL.IssuePluginEvent(Lib.GetRenderEventFunc(), id);
}
}
@@ -290,6 +291,7 @@ public class Monitor
var w = isHorizontal ? width : height;
var h = isHorizontal ? height : width;
texture_ = new Texture2D(w, h, TextureFormat.BGRA32, false);
texturePtr_ = texture_.GetNativeTexturePtr();
}
public void DestroyTexture()
@@ -297,6 +299,7 @@ public class Monitor
if (texture_) {
Object.Destroy(texture_);
texture_ = null;
texturePtr_ = System.IntPtr.Zero;
}
}

View File

@@ -64,32 +64,14 @@ inline void uddConvertToLinearIfNeeded(inout fixed3 rgb)
}
inline fixed4 uddGetScreenTexture(float2 uv)
{
fixed4 c = tex2D(_MainTex, uv);
return c;
}
inline fixed4 uddGetCursorTexture(float2 uv)
{
uv.x = (uv.x - _CursorX) / _CursorWidth;
uv.y = (uv.y - _CursorY) / _CursorHeight;
fixed4 c = tex2D(_CursorTex, uv);
fixed a = step(0, uv.x) * step(0, uv.y) * step(uv.x, 1) * step(uv.y, 1);
c.a *= step(0.01, a);
return c;
}
inline fixed4 uddGetScreenTextureWithCursor(float2 uv)
{
uv = uddInvertUV(uv);
#ifdef USE_CLIP
uv = uddClipUV(uv);
#endif
fixed4 screen = uddGetScreenTexture(uddRotateUV(uv));
fixed4 cursor = uddGetCursorTexture(uv);
fixed4 color = lerp(screen, cursor, cursor.a);
uddConvertToLinearIfNeeded(color.rgb);
return color;
fixed4 c = tex2D(_MainTex, uddRotateUV(uv));
uddConvertToLinearIfNeeded(c.rgb);
return c;
}
inline void uddBendVertex(inout float4 v, half radius, half width, half thickness)

View File

@@ -34,7 +34,7 @@ SubShader
void surf(Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = uddGetScreenTextureWithCursor(IN.uv_MainTex) * _Color;
fixed4 c = uddGetScreenTexture(IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;

View File

@@ -39,7 +39,7 @@ v2f vert(appdata v)
fixed4 frag(v2f i) : SV_Target
{
return uddGetScreenTextureWithCursor(i.uv);
return uddGetScreenTexture(i.uv);
}
ENDCG

View File

@@ -43,7 +43,7 @@ v2f vert(appdata v)
fixed4 frag(v2f i) : SV_Target
{
fixed4 tex = uddGetScreenTextureWithCursor(i.uv);
fixed4 tex = uddGetScreenTexture(i.uv);
fixed alpha = pow((tex.r + tex.g + tex.b) / 3.0, _Mask);
return fixed4(tex.rgb * _Color.rgb, alpha * _Color.a);
}

View File

@@ -41,7 +41,7 @@ v2f vert(appdata v)
fixed4 frag(v2f i) : SV_Target
{
return fixed4(uddGetScreenTextureWithCursor(i.uv).rgb, 1.0) * _Color;
return fixed4(uddGetScreenTexture(i.uv).rgb, 1.0) * _Color;
}
ENDCG

View File

@@ -83,7 +83,7 @@ public:
return value_ != nullptr;
}
T operator [](UINT index) const
const T operator [](UINT index) const
{
if (index >= size_)
{
@@ -93,6 +93,16 @@ public:
return value_[index];
}
T& operator [](UINT index)
{
if (index >= size_)
{
Debug::Error("Array index out of range: ", index, size_);
return value_[0];
}
return value_[index];
}
private:
std::unique_ptr<T[]> value_;
UINT size_ = 0;

View File

@@ -79,33 +79,43 @@ void Cursor::UpdateTexture()
return;
}
// Check desktop texure
if (monitor_->GetUnityTexture() == nullptr)
{
Debug::Error("Cursor::UpdateTexture() => Monitor::GetUnityTexture() is null.");
return;
}
// Cursor information
const bool isMono = GetType() == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME;
const bool isColorMask = GetType() == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR;
const auto cursorImageWidth = GetWidth();
const auto cursorImageHeight = GetHeight();
const auto cursorImagePitch = GetPitch();
// Captured area
// Captured size
auto capturedImageWidth = cursorImageWidth;
auto capturedImageHeight = cursorImageHeight;
// Convert the buffer given by API into BGRA32
const UINT bgraBufferSize = cursorImageWidth * cursorImageHeight * 4;
bgra32Buffer_.ExpandIfNeeded(bgraBufferSize);
bgraBuffer_.ExpandIfNeeded(bgraBufferSize);
// Check buffers
if (!bgra32Buffer_ || !apiBuffer_)
if (!bgraBuffer_ || !apiBuffer_)
{
return;
}
// If masked, copy the desktop image and merge it with masked image.
if (isMono || isColorMask)
// Calculate information to capture desktop image under cursor.
D3D11_BOX box;
const auto monitorRot = static_cast<DXGI_MODE_ROTATION>(monitor_->GetRotation());
auto colMin = 0;
auto colMax = cursorImageWidth;
auto rowMin = 0;
auto rowMax = cursorImageHeight;
{
const auto monitorWidth = monitor_->GetWidth();
const auto monitorHeight = monitor_->GetHeight();
const auto monitorRot = static_cast<DXGI_MODE_ROTATION>(monitor_->GetRotation());
const auto isVertical =
monitorRot == DXGI_MODE_ROTATION_ROTATE90 ||
monitorRot == DXGI_MODE_ROTATION_ROTATE270;
@@ -114,10 +124,6 @@ void Cursor::UpdateTexture()
auto x = x_;
auto y = y_;
auto colMin = 0;
auto colMax = cursorImageWidth;
auto rowMin = 0;
auto rowMax = cursorImageHeight;
if (x < 0)
{
@@ -142,7 +148,6 @@ void Cursor::UpdateTexture()
rowMax = capturedImageHeight;
}
D3D11_BOX box;
box.front = 0;
box.back = 1;
@@ -196,13 +201,11 @@ void Cursor::UpdateTexture()
"(", desktopImageWidth, ", ", desktopImageHeight, ")");
return;
}
}
if (monitor_->GetUnityTexture() == nullptr)
{
Debug::Error("Cursor::UpdateTexture() => Monitor::GetUnityTexture() is null.");
return;
}
// Create texture
ComPtr<ID3D11Texture2D> texture;
{
D3D11_TEXTURE2D_DESC desc;
desc.Width = capturedImageWidth;
desc.Height = capturedImageHeight;
@@ -216,83 +219,89 @@ void Cursor::UpdateTexture()
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
desc.MiscFlags = 0;
ComPtr<ID3D11Texture2D> texture;
if (FAILED(GetDevice()->CreateTexture2D(&desc, nullptr, &texture)))
if (FAILED(GetDevice()->CreateTexture2D(&desc, nullptr, &texture)))
{
Debug::Error("Cursor::UpdateTexture() => GetDevice()->CreateTexture2D() failed.");
return;
}
}
// Copy desktop image to the texture
{
ComPtr<ID3D11DeviceContext> context;
GetDevice()->GetImmediateContext(&context);
context->CopySubresourceRegion(texture.Get(), 0, 0, 0, 0, monitor_->GetUnityTexture(), 0, &box);
}
// Get mapped surface
ComPtr<IDXGISurface> surface;
if (FAILED(texture.As(&surface)))
{
Debug::Error("Cursor::UpdateTexture() => texture->QueryInterface() failed.");
return;
}
DXGI_MAPPED_RECT mappedSurface;
if (FAILED(surface->Map(&mappedSurface, DXGI_MAP_READ)))
{
Debug::Error("Cursor::UpdateTexture() => surface->Map() failed.");
return;
}
// Finally, get the desktop texture under the mouse cursor.
const auto desktop32 = reinterpret_cast<UINT*>(mappedSurface.pBits);
const UINT desktopPitch = mappedSurface.Pitch / sizeof(UINT);
// Take the monitor orientation into consideration.
const auto getDesktop32 = [&](int col, int row)
{
switch (monitorRot)
{
ComPtr<ID3D11DeviceContext> context;
GetDevice()->GetImmediateContext(&context);
context->CopySubresourceRegion(texture.Get(), 0, 0, 0, 0, monitor_->GetUnityTexture(), 0, &box);
case DXGI_MODE_ROTATION_ROTATE90:
return desktop32[(capturedImageWidth - 1 - col) * desktopPitch + row];
case DXGI_MODE_ROTATION_ROTATE180:
return desktop32[(capturedImageHeight - 1 - row) * desktopPitch + (capturedImageWidth - 1 - col)];
case DXGI_MODE_ROTATION_ROTATE270:
return desktop32[col * desktopPitch + (capturedImageHeight - 1 - row)];
case DXGI_MODE_ROTATION_IDENTITY:
case DXGI_MODE_ROTATION_UNSPECIFIED:
return desktop32[row * desktopPitch + col];
}
};
ComPtr<IDXGISurface> surface;
if (FAILED(texture.As(&surface)))
// Access RGBA values at the same time
Buffer<BYTE> output;
output.ExpandIfNeeded(bgraBuffer_.Size());
auto output32 = output.As<UINT>();
switch (GetType())
{
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME:
{
Debug::Error("Cursor::UpdateTexture() => texture->QueryInterface() failed.");
return;
}
DXGI_MAPPED_RECT mappedSurface;
if (FAILED(surface->Map(&mappedSurface, DXGI_MAP_READ)))
{
Debug::Error("Cursor::UpdateTexture() => surface->Map() failed.");
return;
}
// Finally, get the texture behind the mouse cursor.
const auto desktop32 = reinterpret_cast<UINT*>(mappedSurface.pBits);
const UINT desktopPitch = mappedSurface.Pitch / sizeof(UINT);
// Take the monitor orientation into consideration.
const auto getDesktop32 = [&](int col, int row)
{
switch (monitorRot)
for (int row = rowMin, y = 0; row < rowMax; ++row, ++y)
{
case DXGI_MODE_ROTATION_ROTATE90:
return desktop32[(capturedImageWidth - 1 - col) * desktopPitch + row];
break;
case DXGI_MODE_ROTATION_ROTATE180:
return desktop32[(capturedImageHeight - 1 - row) * desktopPitch + (capturedImageWidth - 1 - col)];
break;
case DXGI_MODE_ROTATION_ROTATE270:
return desktop32[col * desktopPitch + (capturedImageHeight - 1 - row)];
break;
case DXGI_MODE_ROTATION_IDENTITY:
case DXGI_MODE_ROTATION_UNSPECIFIED:
return desktop32[row * desktopPitch + col];
break;
}
};
// Access RGBA values at the same time
auto output32 = reinterpret_cast<UINT*>(bgra32Buffer_.Get());
if (isMono)
{
for (int row = rowMin, y = 0; row < rowMax; ++row, ++y)
{
for (int col = colMin, x = 0; col < colMax; ++col, ++x)
for (int col = colMin, x = 0; col < colMax; ++col, ++x)
{
const int i = col + row * cursorImageWidth;
BYTE mask = 0b10000000 >> (col % 8);
const BYTE andMask = apiBuffer_[col / 8 + row * cursorImagePitch] & mask;
const BYTE xorMask = apiBuffer_[col / 8 + (row + capturedImageHeight) * cursorImagePitch] & mask;
const UINT andMask32 = andMask ? 0xFFFFFFFF : 0x00000000;
const UINT xorMask32 = xorMask ? 0xFFFFFFFF : 0x00000000;
output32[row * cursorImageWidth + col] = (getDesktop32(x, y) & andMask32) ^ xorMask32;
output32[i] = (getDesktop32(x, y) & andMask32) ^ xorMask32;
}
}
break;
}
else // DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR:
{
const auto buffer32 = reinterpret_cast<UINT*>(apiBuffer_.Get());
const auto buffer32 = apiBuffer_.As<UINT>();
for (int row = rowMin, y = 0; row < rowMax; ++row, ++y)
for (int row = rowMin, y = 0; row < rowMax; ++row, ++y)
{
for (int col = colMin, x = 0; col < colMax; ++col, ++x)
for (int col = colMin, x = 0; col < colMax; ++col, ++x)
{
const int i = col + row * cursorImageWidth;
const int j = col + row * cursorImagePitch / sizeof(UINT);
@@ -308,28 +317,92 @@ void Cursor::UpdateTexture()
}
}
}
break;
}
if (FAILED(surface->Unmap()))
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR:
{
return;
const auto buffer32 = apiBuffer_.As<UINT>();
auto output32 = bgraBuffer_.As<UINT>();
for (int row = rowMin, y = 0; row < rowMax; ++row, ++y)
{
for (int col = colMin, x = 0; col < colMax; ++col, ++x)
{
const int i = 4 * (col + row * cursorImageWidth);
const auto desktop32 = getDesktop32(x, y);
const auto desktop = (BYTE*)(&desktop32);
const auto desktopB = desktop[0];
const auto desktopG = desktop[1];
const auto desktopR = desktop[2];
const auto desktopA = desktop[3];
const auto cursorB = apiBuffer_[i + 0];
const auto cursorG = apiBuffer_[i + 1];
const auto cursorR = apiBuffer_[i + 2];
const auto cursorA = apiBuffer_[i + 3];
const auto a0 = cursorA / 255.f;
const auto a1 = 1.f - a0;
output[i + 0] = static_cast<BYTE>(cursorB * a0 + desktopB * a1);
output[i + 1] = static_cast<BYTE>(cursorG * a0 + desktopG * a1);
output[i + 2] = static_cast<BYTE>(cursorR * a0 + desktopR * a1);
output[i + 3] = desktopA;
}
}
break;
}
default:
{
Debug::Error("Cursor::UpdateTexture() => Unknown cursor type");
break;
}
}
else // DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR
// Rotation
auto bgraBuffer32 = bgraBuffer_.As<UINT>();
for (int row = rowMin, y = 0; row < rowMax; ++row, ++y)
{
auto output32 = reinterpret_cast<UINT*>(bgra32Buffer_.Get());
const auto buffer32 = reinterpret_cast<UINT*>(apiBuffer_.Get());
for (int i = 0; i < cursorImageWidth * cursorImageHeight; ++i)
for (int col = colMin, x = 0; col < colMax; ++col, ++x)
{
output32[i] = buffer32[i];
const auto i = col + row * cursorImageWidth;
switch (monitorRot)
{
case DXGI_MODE_ROTATION_ROTATE90:
bgraBuffer32[i] = output32[col * cursorImageHeight + (cursorImageHeight - 1 - row)];
break;
case DXGI_MODE_ROTATION_ROTATE180:
bgraBuffer32[i] = output32[(cursorImageHeight - 1 - row) * cursorImageWidth + (cursorImageWidth - 1 - col)];
break;
case DXGI_MODE_ROTATION_ROTATE270:
bgraBuffer32[i] = output32[(cursorImageWidth - 1 - col) * cursorImageHeight + row];
break;
case DXGI_MODE_ROTATION_IDENTITY:
case DXGI_MODE_ROTATION_UNSPECIFIED:
bgraBuffer32[i] = output32[i];
break;
}
}
}
{
ComPtr<ID3D11DeviceContext> context;
GetDevice()->GetImmediateContext(&context);
context->UpdateSubresource(monitor_->GetUnityTexture(), 0, &box, bgraBuffer_.Get(), GetWidth() * 4, 0);
}
if (FAILED(surface->Unmap()))
{
Debug::Error("Cursor::UpdateTexture() => surface->Unmap() failed.");
return;
}
}
void Cursor::GetTexture(ID3D11Texture2D* texture)
{
if (!bgra32Buffer_)
if (!bgraBuffer_)
{
Debug::Error("Cursor::GetTexture() => bgra32Buffer is null.");
return;
@@ -356,7 +429,7 @@ void Cursor::GetTexture(ID3D11Texture2D* texture)
ComPtr<ID3D11DeviceContext> context;
GetDevice()->GetImmediateContext(&context);
context->UpdateSubresource(texture, 0, nullptr, bgra32Buffer_.Get(), GetWidth() * 4, 0);
context->UpdateSubresource(texture, 0, nullptr, bgraBuffer_.Get(), GetWidth() * 4, 0);
}

View File

@@ -32,7 +32,7 @@ private:
int x_ = -1;
int y_ = -1;
Buffer<BYTE> apiBuffer_;
Buffer<BYTE> bgra32Buffer_;
Buffer<BYTE> bgraBuffer_;
DXGI_OUTDUPL_POINTER_SHAPE_INFO shapeInfo_;
LARGE_INTEGER timestamp_;
};

View File

@@ -113,10 +113,11 @@ void Monitor::Render(UINT timeout)
{
if (!deskDupl_) return;
HRESULT hr;
ComPtr<IDXGIResource> resource;
DXGI_OUTDUPL_FRAME_INFO frameInfo;
const auto hr = deskDupl_->AcquireNextFrame(timeout, &frameInfo, &resource);
hr = deskDupl_->AcquireNextFrame(timeout, &frameInfo, &resource);
if (FAILED(hr))
{
switch (hr)
@@ -191,9 +192,30 @@ void Monitor::Render(UINT timeout)
UpdateMetadata(frameInfo);
UpdateCursor(frameInfo);
if (FAILED(deskDupl_->ReleaseFrame()))
hr = deskDupl_->ReleaseFrame();
if (FAILED(hr))
{
Debug::Error("Monitor::Render() => ReleaseFrame() failed.");
switch (hr)
{
case DXGI_ERROR_ACCESS_LOST:
{
Debug::Log("Monitor::Render() => DXGI_ERROR_ACCESS_LOST.");
state_ = State::AccessLost;
break;
}
case DXGI_ERROR_INVALID_CALL:
{
Debug::Error("Monitor::Render() => DXGI_ERROR_INVALID_CALL.");
break;
}
default:
{
state_ = State::Unknown;
Debug::Error("Monitor::Render() => Unknown Error.");
break;
}
}
return;
}
}

View File

@@ -112,10 +112,11 @@
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<Optimization>Full</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>