Compare commits

...

28 Commits

Author SHA1 Message Date
hecomi
4350ac3953 cache pointer texture. 2016-11-01 02:20:46 +09:00
hecomi
6ba381f5d4 fetch cursor texture. 2016-11-01 02:15:25 +09:00
hecomi
9287ef2f2d simplify duplication code. 2016-10-30 21:20:31 +09:00
hecomi
dc6b8bc204 fix vertical screen bug. 2016-10-30 19:56:22 +09:00
hecomi
14cd67e0c6 check if monitor is vertical or not. 2016-10-30 19:35:23 +09:00
hecomi
73018f671a add error code getter. 2016-10-30 19:35:00 +09:00
hecomi
7a27d86eff consider aspect ratio in Multiple Monitor scene. 2016-10-30 19:03:25 +09:00
hecomi
99e1774388 add Monitor.aspect. 2016-10-30 19:03:06 +09:00
hecomi
e0408d4749 fix line endings. 2016-10-30 18:42:16 +09:00
hecomi
23ceef6502 modify invert method. 2016-10-30 18:41:43 +09:00
hecomi
4317fc06e3 update .gitignore. 2016-10-30 18:41:30 +09:00
hecomi
f8cb71b472 fix typo. 2016-10-28 20:15:10 +09:00
hecomi
f1a7c302bd update .gitignore. 2016-10-28 20:15:03 +09:00
hecomi
ec82c49d7f fix toggle scene. 2016-10-28 19:41:44 +09:00
hecomi
f8146c15bd update readme. 2016-10-28 19:33:45 +09:00
hecomi
d5a9827b4d add toggle example. 2016-10-28 19:33:37 +09:00
hecomi
4b499a96de update readme. 2016-10-28 19:23:10 +09:00
hecomi
583326c333 add license. 2016-10-28 19:09:42 +09:00
hecomi
c3ec17dd7b add shader example. 2016-10-28 19:08:08 +09:00
hecomi
6f375e2e5b change class names. / add multiple screen example. / performance tuning. 2016-10-28 18:48:53 +09:00
hecomi
d9f83cf89e implement multiple screen. 2016-10-28 17:28:22 +09:00
hecomi
ba5ebe8094 update readme. 2016-10-27 19:19:45 +09:00
hecomi
1e2ac40507 support linear color. 2016-10-27 19:18:44 +09:00
hecomi
de0c157fb5 update readme. 2016-10-27 18:53:36 +09:00
hecomi
c93bd58ec4 modify default shader. 2016-10-27 18:51:10 +09:00
hecomi
c4db1ab14a add shader family. 2016-10-27 18:50:45 +09:00
hecomi
e72eff0ee7 add mouse cursor. 2016-10-27 18:17:37 +09:00
hecomi
50fd042e35 update readme. 2016-10-27 16:33:39 +09:00
65 changed files with 1994 additions and 162 deletions

3
.gitignore vendored
View File

@@ -4,6 +4,7 @@
/[Oo]bj/
/[Bb]uild/
/[Ww]iki/
/[Mm]isc/
# Autogenerated VS/MD solution and project files
*.csproj
@@ -24,3 +25,5 @@ sysinfo.txt
# Others
.DS_Store
/Assets/AssetStoreTools*
/Assets/Extensions*

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 678e1de5e6de7914baed516a78940503
folderAsset: yes
timeCreated: 1477643728
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: de6f36e446e5e8a48bf61fe198985bb4
timeCreated: 1477643732
licenseType: Pro
NativeFormatImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 0a3456d4b2f59e84f969e31deca4016d
folderAsset: yes
timeCreated: 1477643596
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 445860b05342be845967392bfd70224e
timeCreated: 1477643648
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 281e2991d4d33984fa180845e56cd3f3
timeCreated: 1477648643
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f66b67cb0e751dc4ca0251b38bc8638d
timeCreated: 1477648784
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 78e54899a518eb749a589b921a1b263f
timeCreated: 1477650252
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 71921882396f0f345aa8a66e2fcb0b6c
folderAsset: yes
timeCreated: 1477643608
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,38 @@
using UnityEngine;
public class MultipleMonitorCreator : MonoBehaviour
{
[SerializeField] GameObject monitorPrefab;
[SerializeField] float scale = 10f;
[SerializeField] float margin = 1f;
[SerializeField] float ratio = 0.001f;
void Start()
{
var monitors = uDesktopDuplication.Manager.monitors;
var n = monitors.Count;
var totalWidth = 0f;
for (int i = 0 ; i < n; ++i) {
var go = Instantiate(monitorPrefab);
go.name = "Monitor " + i;
var texture = go.GetComponent<uDesktopDuplication.Texture>();
texture.monitorId = i;
go.transform.localScale = new Vector3(texture.monitor.width * ratio, 1f, texture.monitor.height * ratio);
go.transform.SetParent(transform);
totalWidth += texture.monitor.width * ratio * scale;
}
totalWidth += margin * (n - 1);
var x = -totalWidth / 2;
for (int i = 0 ; i < n; ++i) {
var go = transform.FindChild("Monitor " + i);
var texture = go.GetComponent<uDesktopDuplication.Texture>();
var halfWidth = texture.monitor.width * ratio * scale / 2;
x += halfWidth;
go.transform.position = new Vector3(x, 0f, 0f);
x += halfWidth + margin;
}
}
}

View File

@@ -1,6 +1,6 @@
fileFormatVersion: 2
guid: bb54a34570e4747429b1c5b66c69d356
timeCreated: 1452445649
guid: fb22986017201f74aa864dcbf0cab751
timeCreated: 1477643778
licenseType: Pro
MonoImporter:
serializedVersion: 2

View File

@@ -0,0 +1,17 @@
using UnityEngine;
public class ToggleMonitors : MonoBehaviour
{
void Update()
{
if (Input.GetKeyDown(KeyCode.Tab)) {
var texture = GetComponent<uDesktopDuplication.Texture>();
if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) {
texture.monitorId--;
} else {
texture.monitorId++;
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 602d3a9bed585d14eaf21e0528d24d54
timeCreated: 1477650268
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 033e0db89e0ca1d46a399c953629f67d
timeCreated: 1477555367
licenseType: Pro
NativeFormatImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 06ef13d14bbcf9242a8eba19a94803f3
timeCreated: 1477648806
licenseType: Pro
NativeFormatImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3abaadab838fb3c4ba342eda7431d4f7
timeCreated: 1477648806
licenseType: Pro
NativeFormatImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 647550562f402264fb69871ec732b74c
timeCreated: 1477648806
licenseType: Pro
NativeFormatImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 6e15bc51aada81042a909c808e25c176
folderAsset: yes
timeCreated: 1477555207
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 48b92326b146fa848beec6f35365b1f4
folderAsset: yes
timeCreated: 1477555207
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: a615a0cde495b914d95b9d2d53182305
folderAsset: yes
timeCreated: 1477555207
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 B

View File

@@ -0,0 +1,59 @@
fileFormatVersion: 2
guid: 89616e59e1cccb346bd4e959257990ca
timeCreated: 1477555207
licenseType: Pro
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 7
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -1
maxTextureSize: 2048
textureSettings:
filterMode: 0
aniso: 1
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
rGBM: 0
compressionQuality: 50
allowsAlphaSplitting: 0
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
buildTargetSettings: []
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,107 @@
using UnityEngine;
using System.Collections.Generic;
namespace uDesktopDuplication
{
[AddComponentMenu("uDesktopDuplication/Cursor"),
RequireComponent(typeof(Texture))]
public class Cursor : MonoBehaviour
{
[SerializeField] Vector2 modelScale = Vector2.one;
Vector3 worldPosition { get; set; }
private Texture uddTexture_;
private Monitor monitor { get { return uddTexture_.monitor; } }
private Texture2D pointerTexture_;
private Material cursorMaterial_;
private Dictionary<Vector2, Texture2D> pointerTextures_ = new Dictionary<Vector2, Texture2D>();
void OnEnable()
{
uddTexture_ = GetComponent<Texture>();
}
void Start()
{
var clone = new GameObject(name + " Cursor");
clone.transform.SetParent(transform);
clone.transform.localPosition = Vector3.zero;
clone.transform.localRotation = Quaternion.identity;
clone.transform.localScale = Vector3.one;
var filter = clone.AddComponent<MeshFilter>();
filter.mesh = GetComponent<MeshFilter>().sharedMesh;
var renderer = clone.AddComponent<MeshRenderer>();
var shader = Shader.Find("uDesktopDuplication/Cursor");
cursorMaterial_ = new Material(shader);
renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
renderer.receiveShadows = false;
renderer.motionVectors = false;
renderer.material = cursorMaterial_;
}
void Update()
{
if (monitor.isPointerVisible) {
UpdatePosition();
UpdateTexture();
UpdateMaterial();
}
}
void UpdatePosition()
{
var x = (1f * monitor.pointerX / monitor.width - 0.5f) * modelScale.x;
var y = (1f * monitor.pointerY / monitor.height - 0.5f) * modelScale.y;
var iy = uddTexture_.invertY ? -1 : +1;
var localPos = transform.right * x + iy * transform.up * y;
worldPosition = transform.TransformPoint(localPos);
}
void UpdateTexture()
{
var scale = new Vector2(monitor.pointerShapeWidth, monitor.pointerShapeHeight);
if (!pointerTextures_.ContainsKey(scale)) {
var texture = new Texture2D(monitor.pointerShapeWidth, monitor.pointerShapeHeight, TextureFormat.BGRA32, false);
texture.wrapMode = TextureWrapMode.Clamp;
pointerTextures_.Add(scale, texture);
}
var pointerTexture = pointerTextures_[scale];
monitor.UpdatePointerTexture(pointerTexture.GetNativeTexturePtr());
cursorMaterial_.SetTexture("_MainTex", pointerTexture);
}
void UpdateMaterial()
{
if (uddTexture_.invertX) {
cursorMaterial_.EnableKeyword("INVERT_X");
} else {
cursorMaterial_.DisableKeyword("INVERT_X");
}
if (uddTexture_.invertY) {
cursorMaterial_.EnableKeyword("INVERT_Y");
} else {
cursorMaterial_.DisableKeyword("INVERT_Y");
}
if (monitor.isVertical) {
cursorMaterial_.EnableKeyword("VERTICAL");
} else {
cursorMaterial_.DisableKeyword("VERTICAL");
}
var x = (float)monitor.pointerX / monitor.width;
var y = (float)monitor.pointerY / monitor.height;
var w = (float)monitor.pointerShapeWidth / monitor.width;
var h = (float)monitor.pointerShapeHeight / monitor.height;
cursorMaterial_.SetVector("_PositionScale", new Vector4(x, y, w, h));
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: d2d9e4ca459ecb44da8815bafe9655c5
timeCreated: 1477635630
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 89616e59e1cccb346bd4e959257990ca, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,69 @@
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace uDesktopDuplication
{
public enum PointerShapeType
{
MonoChrome = 1,
Color = 2,
MaskedColor = 4,
}
public static class Lib
{
[DllImport("uDesktopDuplication")]
public static extern int GetMonitorCount();
[DllImport("uDesktopDuplication")]
public static extern void SetTimeout(int timeout);
[DllImport("uDesktopDuplication")]
public static extern IntPtr GetRenderEventFunc();
[DllImport("uDesktopDuplication")]
public static extern void GetName(int id, StringBuilder buf, int len);
[DllImport("uDesktopDuplication")]
public static extern int GetWidth(int id);
[DllImport("uDesktopDuplication")]
public static extern int GetHeight(int id);
[DllImport("uDesktopDuplication")]
public static extern bool IsPrimary(int id);
[DllImport("uDesktopDuplication")]
public static extern bool IsPointerVisible(int id);
[DllImport("uDesktopDuplication")]
public static extern int GetPointerX(int id);
[DllImport("uDesktopDuplication")]
public static extern int GetPointerY(int id);
[DllImport("uDesktopDuplication")]
public static extern int GetPointerShapeWidth(int id);
[DllImport("uDesktopDuplication")]
public static extern int GetPointerShapeHeight(int id);
[DllImport("uDesktopDuplication")]
public static extern int GetPointerShapePitch(int id);
[DllImport("uDesktopDuplication")]
public static extern PointerShapeType GetPointerShapeType(int id);
[DllImport("uDesktopDuplication")]
public static extern void UpdatePointerTexture(int id, System.IntPtr ptr);
[DllImport("uDesktopDuplication")]
public static extern int SetTexturePtr(int id, IntPtr ptr);
[DllImport("uDesktopDuplication")]
public static extern int GetErrorCode();
[DllImport("uDesktopDuplication")]
public static extern void GetErrorMessage(StringBuilder buf, int len);
public static string GetName(int id)
{
var buf = new StringBuilder(32);
GetName(id, buf, buf.Capacity);
return buf.ToString();
}
public static string GetErrorMessage()
{
var buf = new StringBuilder(64);
GetErrorMessage(buf, buf.Capacity);
return buf.ToString();
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: cb02196c3e766c14ba398dc130e443e8
timeCreated: 1477643164
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 89616e59e1cccb346bd4e959257990ca, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,92 @@
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace uDesktopDuplication
{
public class Manager : MonoBehaviour
{
private static Manager instance_;
public static Manager instance
{
get
{
if (instance_) return instance_;
var go = new GameObject("uDesktopDuplicationManager");
return go.AddComponent<Manager>();
}
}
private List<Monitor> monitors_ = new List<Monitor>();
static public List<Monitor> monitors
{
get { return instance.monitors_; }
}
static public int monitorCount
{
get { return instance.monitors_.Count; }
}
static public Monitor primary
{
get
{
return instance.monitors_.Find(monitor => monitor.isPrimary);
}
}
[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()
{
if (instance_ != null) return;
instance_ = this;
for (int i = 0; i < Lib.GetMonitorCount(); ++i) {
monitors.Add(new Monitor(i));
}
Lib.SetTimeout(timeout);
}
void OnEnable()
{
renderCoroutine_ = StartCoroutine(OnRender());
}
void OnDisable()
{
if (renderCoroutine_ != null) {
StopCoroutine(renderCoroutine_);
renderCoroutine_ = null;
}
}
IEnumerator OnRender()
{
for (;;) {
yield return new WaitForEndOfFrame();
for (int i = 0; i < monitors.Count; ++i) {
var monitor = monitors[i];
if (monitor.shouldBeUpdated) {
monitor.Render();
}
monitor.shouldBeUpdated = false;
}
if (outputDebugLog && Lib.GetErrorCode() != 0)
{
Debug.Log(Lib.GetErrorMessage());
}
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 4dccb434f55ebee4f91bfb9935092bbf
timeCreated: 1477643174
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 89616e59e1cccb346bd4e959257990ca, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,116 @@
using UnityEngine;
namespace uDesktopDuplication
{
public class Monitor
{
public Monitor(int id)
{
this.id = id;
}
public int id
{
get;
private set;
}
public string name
{
get { return Lib.GetName(id); }
}
public bool isPrimary
{
get { return Lib.IsPrimary(id); }
}
public int width
{
get { return Lib.GetWidth(id); }
}
public int height
{
get { return Lib.GetHeight(id); }
}
public float aspect
{
get { return (float)width / height; }
}
public bool isHorizontal
{
get { return width > height; }
}
public bool isVertical
{
get { return height > width; }
}
public bool isPointerVisible
{
get { return Lib.IsPointerVisible(id); }
}
public int pointerX
{
get { return Lib.GetPointerX(id); }
}
public int pointerY
{
get { return Lib.GetPointerY(id); }
}
public int pointerShapeWidth
{
get { return Lib.GetPointerShapeWidth(id); }
}
public int pointerShapeHeight
{
get { return Lib.GetPointerShapeHeight(id); }
}
public PointerShapeType pointerShapeType
{
get { return Lib.GetPointerShapeType(id); }
}
public bool shouldBeUpdated
{
get;
set;
}
private Texture2D texture_;
public Texture2D texture
{
get
{
if (texture_ == null) {
var w = isHorizontal ? width : height;
var h = isHorizontal ? height : width;
texture_ = new Texture2D(w, h, TextureFormat.BGRA32, false);
Lib.SetTexturePtr(id, texture_.GetNativeTexturePtr());
}
return texture_;
}
}
public void Render()
{
GL.IssuePluginEvent(Lib.GetRenderEventFunc(), id);
}
public void UpdatePointerTexture(System.IntPtr ptr)
{
Lib.UpdatePointerTexture(id, ptr);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 310590b113d909b43b4ea19702904ac3
timeCreated: 1477643181
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 89616e59e1cccb346bd4e959257990ca, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,67 @@
using UnityEngine;
namespace uDesktopDuplication
{
[AddComponentMenu("uDesktopDuplication/Texture")]
public class Texture : MonoBehaviour
{
private Monitor monitor_;
public Monitor monitor
{
get { return monitor_; }
set
{
monitor_ = value;
material_ = GetComponent<Renderer>().material;
material_.mainTexture = monitor_.texture;
}
}
public int monitorId
{
get { return monitor.id; }
set { monitor = Manager.monitors[Mathf.Clamp(value, 0, Manager.monitorCount - 1)]; }
}
public bool invertX = false;
public bool invertY = false;
private Material material_;
void OnEnable()
{
if (monitor == null) {
monitor = Manager.primary;
}
}
void Update()
{
monitor.shouldBeUpdated = true;
UpdateMaterial();
}
void UpdateMaterial()
{
if (invertX) {
material_.EnableKeyword("INVERT_X");
} else {
material_.DisableKeyword("INVERT_X");
}
if (invertY) {
material_.EnableKeyword("INVERT_Y");
} else {
material_.DisableKeyword("INVERT_Y");
}
if (monitor.isVertical) {
material_.EnableKeyword("VERTICAL");
} else {
material_.DisableKeyword("VERTICAL");
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: bb54a34570e4747429b1c5b66c69d356
timeCreated: 1477635636
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 89616e59e1cccb346bd4e959257990ca, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,62 +0,0 @@
using UnityEngine;
using System;
using System.Collections;
using System.Runtime.InteropServices;
public class uDesktopDuplication : MonoBehaviour
{
[DllImport("uDesktopDuplication")]
private static extern void Initialize();
[DllImport("uDesktopDuplication")]
private static extern int GetWidth();
[DllImport("uDesktopDuplication")]
private static extern int GetHeight();
[DllImport("uDesktopDuplication")]
private static extern bool IsPointerVisible();
[DllImport("uDesktopDuplication")]
private static extern int GetPointerX();
[DllImport("uDesktopDuplication")]
private static extern int GetPointerY();
[DllImport("uDesktopDuplication")]
private static extern int SetTexturePtr(IntPtr ptr);
[DllImport("uDesktopDuplication")]
private static extern IntPtr GetRenderEventFunc();
public bool isPointerVisible = false;
public int pointerX = 0;
public int pointerY = 0;
Coroutine renderCoroutine_ = null;
void OnEnable()
{
var tex = new Texture2D(GetWidth(), GetHeight(), TextureFormat.BGRA32, false);
GetComponent<Renderer>().sharedMaterial.mainTexture = tex;
SetTexturePtr(tex.GetNativeTexturePtr());
renderCoroutine_ = StartCoroutine(OnRender());
}
void OnDisable()
{
if (renderCoroutine_ != null) {
StopCoroutine(renderCoroutine_);
renderCoroutine_ = null;
}
}
void Update()
{
isPointerVisible = IsPointerVisible();
pointerX = GetPointerX();
pointerY = GetPointerY();
}
IEnumerator OnRender()
{
for (;;) {
yield return new WaitForEndOfFrame();
GL.IssuePluginEvent(GetRenderEventFunc(), 0);
}
}
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 9844af21eb203794b852f0bcaa5f2be2
folderAsset: yes
timeCreated: 1477556975
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,36 @@
#ifndef UDD_COMMON_CGINC
#define UDD_COMMON_CGINC
#include "UnityCG.cginc"
inline void uddInvertUV(inout float2 uv)
{
#ifdef INVERT_X
uv.x = 1.0 - uv.x;
#endif
#ifdef INVERT_Y
uv.y = 1.0 - uv.y;
#endif
#ifdef VERTICAL
float2 tmp = uv;
uv.x = tmp.y;
uv.y = 1.0 - tmp.x;
#endif
}
inline void uddToLinearIfNeeded(inout float3 rgb)
{
if (!IsGammaSpace()) {
rgb = GammaToLinearSpace(rgb);
}
}
inline fixed4 uddGetTexture(sampler2D tex, float2 uv)
{
uddInvertUV(uv);
fixed4 c = tex2D(tex, uv);
uddToLinearIfNeeded(c.rgb);
return c;
}
#endif

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 1090e048de83c254d99a242f407890c6
timeCreated: 1477561310
licenseType: Pro
ShaderImporter:
defaultTextures: []
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,79 @@
Shader "uDesktopDuplication/Cursor"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
ZWrite Off
Offset -1, -1
CGINCLUDE
#include "UnityCG.cginc"
#include "./uDD_Common.cginc"
half4 _PositionScale;
#define _PointerX _PositionScale.x
#define _PointerY _PositionScale.y
#define _PointerWidth _PositionScale.z
#define _PointerHeight _PositionScale.w
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
};
sampler2D _MainTex;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
uddInvertUV(i.uv);
i.uv.x = (i.uv.x - _PointerX) / _PointerWidth;
i.uv.y = (i.uv.y - _PointerY) / _PointerHeight;
fixed4 color = tex2D(_MainTex, i.uv);
color.a *= step(0, i.uv.x) * step(0, i.uv.y) * step(i.uv.x, 1) * step(i.uv.y, 1);
clip(color.a - 0.01);
uddToLinearIfNeeded(color.rgb);
return color;
}
ENDCG
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile ___ INVERT_X
#pragma multi_compile ___ INVERT_Y
#pragma multi_compile ___ VERTICAL
ENDCG
}
}
Fallback "Unlit/Texture"
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: d224cadaa1bfa2d46a6224ce2a1443c7
timeCreated: 1477847636
licenseType: Pro
ShaderImporter:
defaultTextures: []
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,51 @@
Shader "uDesktopDuplication/Standard"
{
Properties
{
_Color ("Color", Color) = (1, 1, 1, 1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0, 1)) = 0.5
_Metallic ("Metallic", Range(0, 1)) = 0.0
}
SubShader
{
Tags { "RenderType"="Opaque" }
CGPROGRAM
#pragma target 3.0
#pragma surface surf Standard fullforwardshadows
#pragma multi_compile ___ INVERT_X
#pragma multi_compile ___ INVERT_Y
#pragma multi_compile ___ VERTICAL
#include "./uDD_Common.cginc"
sampler2D _MainTex;
struct Input
{
float2 uv_MainTex;
};
half _Glossiness;
half _Metallic;
fixed4 _Color;
void surf(Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = uddGetTexture(_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 1428bebbde173ab4ab509941809d6aec
timeCreated: 1477559925
licenseType: Pro
ShaderImporter:
defaultTextures: []
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,66 @@
Shader "uDesktopDuplication/Unlit"
{
Properties
{
_Color ("Color", Color) = (1, 1, 1, 1)
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
CGINCLUDE
#include "UnityCG.cginc"
#include "./uDD_Common.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return uddGetTexture(_MainTex, i.uv) * _Color;
}
ENDCG
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile ___ INVERT_X
#pragma multi_compile ___ INVERT_Y
#pragma multi_compile ___ VERTICAL
ENDCG
}
}
Fallback "Unlit/Texture"
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 2ca7b38c676bddd439a77a646a279a54
timeCreated: 1477556963
licenseType: Pro
ShaderImporter:
defaultTextures: []
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,73 @@
Shader "uDesktopDuplication/Unlit BlackMask"
{
Properties
{
_Color ("Color", Color) = (1, 1, 1, 1)
_MainTex ("Texture", 2D) = "white" {}
_Mask ("Mask", Range(0, 1)) = 0.1
}
SubShader
{
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGINCLUDE
#include "UnityCG.cginc"
#include "./uDD_Common.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
fixed _Mask;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 tex = uddGetTexture(_MainTex, i.uv);
fixed alpha = pow((tex.r + tex.g + tex.b) / 3.0, _Mask);
return fixed4(tex.rgb * _Color.rgb, alpha);
}
ENDCG
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile ___ INVERT_X
#pragma multi_compile ___ INVERT_Y
#pragma multi_compile ___ VERTICAL
ENDCG
}
}
Fallback "Unlit/Texture"
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 8d8bed8b85c7bb844a44481b2feb53ca
timeCreated: 1477561145
licenseType: Pro
ShaderImporter:
defaultTextures: []
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,69 @@
Shader "uDesktopDuplication/Unlit Transparent"
{
Properties
{
_Color ("Color", Color) = (1, 1, 1, 1)
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGINCLUDE
#include "UnityCG.cginc"
#include "./uDD_Common.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return fixed4(uddGetTexture(_MainTex, i.uv).rgb, 1.0) * _Color;
}
ENDCG
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile ___ INVERT_X
#pragma multi_compile ___ INVERT_Y
#pragma multi_compile ___ VERTICAL
ENDCG
}
}
Fallback "Unlit/Transparent"
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 32b14c1fb31640f43b002138823eb1e1
timeCreated: 1477560210
licenseType: Pro
ShaderImporter:
defaultTextures: []
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,405 @@
#include <d3d11.h>
#include <dxgi1_2.h>
#include <vector>
#include <string>
#include "IUnityInterface.h"
#include "IUnityGraphics.h"
#include "IUnityGraphicsD3D11.h"
#pragma comment(lib, "dxgi.lib")
namespace
{
struct Monitor
{
IDXGIOutputDuplication* deskDupl = nullptr;
ID3D11Texture2D* texture = nullptr;
DXGI_OUTPUT_DESC outputDesc;
MONITORINFOEX monitorInfo;
struct Pointer
{
bool isVisible = false;
int x = -1;
int y = -1;
BYTE* apiBuffer = nullptr;
UINT apiBufferSize = 0;
BYTE* bgra32Buffer = nullptr;
UINT bgra32BufferSize = 0;
DXGI_OUTDUPL_POINTER_SHAPE_INFO shapeInfo;
};
Pointer pointer;
};
IUnityInterfaces* g_unity = nullptr;
int g_timeout = 10;
HRESULT g_errorCode = 0;
std::string g_errorMessage = "";
std::vector<Monitor> g_monitors;
}
extern "C"
{
void FinalizeDuplication()
{
for (auto& monitor : g_monitors)
{
monitor.deskDupl->Release();
}
g_monitors.clear();
}
void InitializeDuplication()
{
FinalizeDuplication();
IDXGIFactory1* factory;
CreateDXGIFactory1(__uuidof(IDXGIFactory1), reinterpret_cast<void**>(&factory));
// Check all display adapters.
IDXGIAdapter1* adapter;
for (int i = 0; (factory->EnumAdapters1(i, &adapter) != DXGI_ERROR_NOT_FOUND); ++i)
{
// Search the main monitor from all outputs.
IDXGIOutput* output;
for (int j = 0; (adapter->EnumOutputs(j, &output) != DXGI_ERROR_NOT_FOUND); ++j)
{
Monitor monitor;
output->GetDesc(&monitor.outputDesc);
monitor.monitorInfo.cbSize = sizeof(MONITORINFOEX);
GetMonitorInfo(monitor.outputDesc.Monitor, &monitor.monitorInfo);
auto device = g_unity->Get<IUnityGraphicsD3D11>()->GetDevice();
auto output1 = reinterpret_cast<IDXGIOutput1*>(output);
output1->DuplicateOutput(device, &monitor.deskDupl);
output->Release();
g_monitors.push_back(monitor);
}
adapter->Release();
}
factory->Release();
}
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces* unityInterfaces)
{
g_unity = unityInterfaces;
InitializeDuplication();
}
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UnityPluginUnload()
{
g_unity = nullptr;
FinalizeDuplication();
}
bool IsValidMonitorId(int id)
{
return id >= 0 && id < g_monitors.size();
}
bool UpdateMouse(const DXGI_OUTDUPL_FRAME_INFO& frameInfo, Monitor& monitor)
{
auto& pointer = monitor.pointer;
pointer.isVisible = frameInfo.PointerPosition.Visible != 0;
pointer.x = frameInfo.PointerPosition.Position.x;
pointer.y = frameInfo.PointerPosition.Position.y;
if (frameInfo.PointerShapeBufferSize != 0)
{
if (frameInfo.PointerShapeBufferSize > pointer.apiBufferSize)
{
if (pointer.apiBuffer) delete[] pointer.apiBuffer;
pointer.apiBuffer = new BYTE[frameInfo.PointerShapeBufferSize];
pointer.apiBufferSize = frameInfo.PointerShapeBufferSize;
}
if (pointer.apiBuffer)
{
UINT bufferSize;
monitor.deskDupl->GetFramePointerShape(
frameInfo.PointerShapeBufferSize,
reinterpret_cast<void*>(pointer.apiBuffer),
&bufferSize,
&pointer.shapeInfo);
}
// Convert buffer give by API into BGRA32
const auto bgraBufferSize = pointer.shapeInfo.Width * pointer.shapeInfo.Height * 4;
if (bgraBufferSize > pointer.bgra32BufferSize)
{
if (pointer.bgra32Buffer) delete[] pointer.bgra32Buffer;
pointer.bgra32Buffer = new BYTE[bgraBufferSize];
pointer.bgra32BufferSize = bgraBufferSize;
}
if (pointer.bgra32Buffer)
{
const auto w = pointer.shapeInfo.Width;
const auto h = pointer.shapeInfo.Height;
const auto p = pointer.shapeInfo.Pitch;
// If masked, copy the desktop image and merge it with masked image.
const bool isMasked =
pointer.shapeInfo.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME ||
pointer.shapeInfo.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR;
if (isMasked)
{
HRESULT hr;
D3D11_TEXTURE2D_DESC desc;
desc.Width = w;
desc.Height = h;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.Usage = D3D11_USAGE_STAGING;
desc.BindFlags = 0;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
desc.MiscFlags = 0;
ID3D11DeviceContext* context;
auto device = g_unity->Get<IUnityGraphicsD3D11>()->GetDevice();
device->GetImmediateContext(&context);
ID3D11Texture2D* texture = nullptr;
hr = device->CreateTexture2D(&desc, nullptr, &texture);
if (FAILED(hr))
{
return false;
}
D3D11_BOX box;
box.left = pointer.x;
box.top = pointer.y;
box.right = pointer.x + w;
box.bottom = pointer.y + h;
context->CopySubresourceRegion(texture, 0, 0, 0, 0, monitor.texture, 0, &box);
IDXGISurface* surface = nullptr;
hr = texture->QueryInterface(__uuidof(IDXGISurface), (void**)&surface);
texture->Release();
if (FAILED(hr))
{
return false;
}
DXGI_MAPPED_RECT mappedSurface;
hr = surface->Map(&mappedSurface, DXGI_MAP_READ);
if (FAILED(hr))
{
surface->Release();
return false;
}
// Finally, get the texture behind the mouse pointer.
const auto desktop32 = reinterpret_cast<UINT*>(mappedSurface.pBits);
const auto desktopPitch = mappedSurface.Pitch / sizeof(UINT);
// Access RGBA values at the same time
auto output32 = reinterpret_cast<UINT*>(pointer.bgra32Buffer);
if (pointer.shapeInfo.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME)
{
for (UINT row = 0; row < h; ++row)
{
for (UINT col = 0; col < w; ++col)
{
const int i = row * w + col;
const UINT mask = 0b10000000 >> (i % 8);
const BYTE andMask = pointer.apiBuffer[col / 8 + row * p] & mask;
const BYTE xorMask = pointer.apiBuffer[col / 8 + (row + h / 2) * p] & mask;
const UINT andMask32 = andMask ? 0xFFFFFFFF : 0xFF000000;
const UINT xorMask32 = xorMask ? 0x00FFFFFF : 0x00000000;
output32[i] = (desktop32[row * desktopPitch + col] & andMask32) ^ xorMask32;
}
}
}
else // DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR
{
const auto buffer32 = reinterpret_cast<UINT*>(pointer.apiBuffer);
for (UINT row = 0; row < h; ++row)
{
for (UINT col = 0; col < w; ++col)
{
const int i = row * w + col;
const int j = row * p / sizeof(UINT) + col;
UINT MaskVal = 0xFF000000 & buffer32[j];
const UINT mask = 0xFF000000 & buffer[j];
if (mask) {
output[i] = 0xFF000000 ^ buffer[j];
} else {
output[i] = 0x00000000;
}
}
}
}
hr = surface->Unmap();
surface->Release();
if (FAILED(hr))
{
return false;
}
}
}
}
return true;
}
void UNITY_INTERFACE_API OnRenderEvent(int id)
{
if (!IsValidMonitorId(id)) return;
auto& monitor = g_monitors[id];
if (monitor.deskDupl == nullptr || monitor.texture == nullptr) return;
IDXGIResource* resource = nullptr;
DXGI_OUTDUPL_FRAME_INFO frameInfo;
g_errorCode = monitor.deskDupl->AcquireNextFrame(g_timeout, &frameInfo, &resource);
if (FAILED(g_errorCode))
{
if (g_errorCode == DXGI_ERROR_ACCESS_LOST)
{
InitializeDuplication();
g_errorMessage = "[IDXGIOutputDuplication::AcquireNextFrame()] Access lost.";
}
else
{
g_errorMessage = "[IDXGIOutputDuplication::AcquireNextFrame()] Unknown error.";
}
return;
}
ID3D11Texture2D* texture;
resource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&texture));
ID3D11DeviceContext* context;
auto device = g_unity->Get<IUnityGraphicsD3D11>()->GetDevice();
device->GetImmediateContext(&context);
context->CopyResource(monitor.texture, texture);
if (!UpdateMouse(frameInfo, monitor))
{
g_errorCode = -999;
g_errorMessage = "[UpdateMouse()] failed.";
}
resource->Release();
monitor.deskDupl->ReleaseFrame();
}
UNITY_INTERFACE_EXPORT UnityRenderingEvent UNITY_INTERFACE_API GetRenderEventFunc()
{
return OnRenderEvent;
}
UNITY_INTERFACE_EXPORT size_t UNITY_INTERFACE_API GetMonitorCount()
{
return g_monitors.size();
}
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API SetTimeout(int timeout)
{
g_timeout = timeout;
}
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API GetName(int id, char* buf, int len)
{
if (!IsValidMonitorId(id)) return;
strcpy_s(buf, len, g_monitors[id].monitorInfo.szDevice);
}
UNITY_INTERFACE_EXPORT bool UNITY_INTERFACE_API IsPrimary(int id)
{
if (!IsValidMonitorId(id)) return false;
return g_monitors[id].monitorInfo.dwFlags == MONITORINFOF_PRIMARY;
}
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetWidth(int id)
{
if (!IsValidMonitorId(id)) return -1;
const auto rect = g_monitors[id].monitorInfo.rcMonitor;
return rect.right - rect.left;
}
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetHeight(int id)
{
if (!IsValidMonitorId(id)) return -1;
const auto rect = g_monitors[id].monitorInfo.rcMonitor;
return rect.bottom - rect.top;
}
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API IsPointerVisible(int id)
{
if (!IsValidMonitorId(id)) return false;
return g_monitors[id].pointer.isVisible;
}
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerX(int id)
{
if (!IsValidMonitorId(id)) return -1;
return g_monitors[id].pointer.x;
}
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerY(int id)
{
if (!IsValidMonitorId(id)) return -1;
return g_monitors[id].pointer.y;
}
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapeWidth(int id)
{
if (!IsValidMonitorId(id)) return -1;
return g_monitors[id].pointer.shapeInfo.Width;
}
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapeHeight(int id)
{
if (!IsValidMonitorId(id)) return -1;
return g_monitors[id].pointer.shapeInfo.Height;
}
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapePitch(int id)
{
if (!IsValidMonitorId(id)) return -1;
return g_monitors[id].pointer.shapeInfo.Pitch;
}
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapeType(int id)
{
if (!IsValidMonitorId(id)) return -1;
return g_monitors[id].pointer.shapeInfo.Type;
}
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UpdatePointerTexture(int id, ID3D11Texture2D* ptr)
{
if (!IsValidMonitorId(id)) return;
const auto& pointer = g_monitors[id].pointer;
auto device = g_unity->Get<IUnityGraphicsD3D11>()->GetDevice();
ID3D11DeviceContext* context;
device->GetImmediateContext(&context);
context->UpdateSubresource(ptr, 0, nullptr, pointer.bgra32Buffer, pointer.shapeInfo.Pitch, 0);
}
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API SetTexturePtr(int id, void* texture)
{
if (!IsValidMonitorId(id)) return;
g_monitors[id].texture = reinterpret_cast<ID3D11Texture2D*>(texture);
}
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetErrorCode()
{
return static_cast<int>(g_errorCode);
}
}

View File

@@ -1,5 +1,7 @@
#include <d3d11.h>
#include <dxgi1_2.h>
#include <vector>
#include <string>
#include "IUnityInterface.h"
#include "IUnityGraphics.h"
@@ -10,57 +12,77 @@
namespace
{
IUnityInterfaces* g_unity = nullptr;
IDXGIOutputDuplication* g_deskDupl = nullptr;
ID3D11Texture2D* g_texture = nullptr;
bool g_isPointerVisible = -1;
int g_pointerX = -1;
int g_pointerY = -1;
int g_width = -1;
int g_height = -1;
struct Monitor
{
int id = -1;
IDXGIOutputDuplication* deskDupl = nullptr;
ID3D11Texture2D* texture = nullptr;
DXGI_OUTPUT_DESC outputDesc;
MONITORINFOEX monitorInfo;
struct Pointer
{
bool isVisible = false;
int x = -1;
int y = -1;
BYTE* apiBuffer = nullptr;
UINT apiBufferSize = 0;
BYTE* bgra32Buffer = nullptr;
UINT bgra32BufferSize = 0;
DXGI_OUTDUPL_POINTER_SHAPE_INFO shapeInfo;
};
Pointer pointer;
};
IUnityInterfaces* g_unity = nullptr;
int g_mouseMonitor = 0;
int g_timeout = 10;
HRESULT g_errorCode = 0;
std::string g_errorMessage = "";
std::vector<Monitor> g_monitors;
}
extern "C"
{
void FinalizeDuplication()
{
for (auto& monitor : g_monitors)
{
monitor.deskDupl->Release();
}
g_monitors.clear();
}
void InitializeDuplication()
{
FinalizeDuplication();
IDXGIFactory1* factory;
CreateDXGIFactory1(__uuidof(IDXGIFactory1), reinterpret_cast<void**>(&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++)
for (int j = 0; (adapter->EnumOutputs(j, &output) != DXGI_ERROR_NOT_FOUND); ++j)
{
DXGI_OUTPUT_DESC outputDesc;
output->GetDesc(&outputDesc);
Monitor monitor;
monitor.id = id++;
MONITORINFOEX monitorInfo;
monitorInfo.cbSize = sizeof(MONITORINFOEX);
GetMonitorInfo(outputDesc.Monitor, &monitorInfo);
if (monitorInfo.dwFlags == MONITORINFOF_PRIMARY)
{
g_width = monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left;
g_height = monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top;
auto device = g_unity->Get<IUnityGraphicsD3D11>()->GetDevice();
IDXGIOutput1* output1;
output1 = reinterpret_cast<IDXGIOutput1*>(output);
output1->DuplicateOutput(device, &g_deskDupl);
output->Release();
adapter->Release();
factory->Release();
return;
}
output->GetDesc(&monitor.outputDesc);
monitor.monitorInfo.cbSize = sizeof(MONITORINFOEX);
GetMonitorInfo(monitor.outputDesc.Monitor, &monitor.monitorInfo);
auto device = g_unity->Get<IUnityGraphicsD3D11>()->GetDevice();
auto output1 = reinterpret_cast<IDXGIOutput1*>(output);
output1->DuplicateOutput(device, &monitor.deskDupl);
output->Release();
g_monitors.push_back(monitor);
}
adapter->Release();
@@ -69,12 +91,6 @@ extern "C"
factory->Release();
}
void FinalizeDuplication()
{
g_deskDupl->Release();
g_deskDupl = nullptr;
}
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces* unityInterfaces)
{
g_unity = unityInterfaces;
@@ -84,62 +100,233 @@ extern "C"
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UnityPluginUnload()
{
g_unity = nullptr;
g_texture = nullptr;
g_width = -1;
g_height = -1;
g_pointerX = -1;
g_pointerY = -1;
FinalizeDuplication();
}
void UNITY_INTERFACE_API OnRenderEvent(int eventId)
bool IsValidMonitorId(int id)
{
if (g_deskDupl == nullptr || g_texture == nullptr) return;
return id >= 0 && id < g_monitors.size();
}
bool UpdateMouse(const DXGI_OUTDUPL_FRAME_INFO& frameInfo, Monitor& monitor)
{
auto& pointer = monitor.pointer;
pointer.isVisible = frameInfo.PointerPosition.Visible != 0;
pointer.x = frameInfo.PointerPosition.Position.x;
pointer.y = frameInfo.PointerPosition.Position.y;
// Pointer type
const auto pointerType = pointer.shapeInfo.Type;
const bool isMono = pointerType == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME;
const bool isColorMask = pointerType == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR;
if (pointer.isVisible)
{
g_mouseMonitor = monitor.id;
}
if (g_mouseMonitor != monitor.id) {
return true;
}
// Increase the buffer size if needed
if (frameInfo.PointerShapeBufferSize > pointer.apiBufferSize)
{
if (pointer.apiBuffer) delete[] pointer.apiBuffer;
pointer.apiBuffer = new BYTE[frameInfo.PointerShapeBufferSize];
pointer.apiBufferSize = frameInfo.PointerShapeBufferSize;
}
if (!pointer.apiBuffer) return true;
// Get information about the mouse pointer if needed
if (frameInfo.PointerShapeBufferSize != 0)
{
UINT bufferSize;
monitor.deskDupl->GetFramePointerShape(
frameInfo.PointerShapeBufferSize,
reinterpret_cast<void*>(pointer.apiBuffer),
&bufferSize,
&pointer.shapeInfo);
}
// Size
const auto w = pointer.shapeInfo.Width;
const auto h = pointer.shapeInfo.Height / (isMono ? 2 : 1);
const auto p = pointer.shapeInfo.Pitch;
// Convert the buffer given by API into BGRA32
const auto bgraBufferSize = w * h * 4;
if (bgraBufferSize > pointer.bgra32BufferSize)
{
if (pointer.bgra32Buffer) delete[] pointer.bgra32Buffer;
pointer.bgra32Buffer = new BYTE[bgraBufferSize];
pointer.bgra32BufferSize = bgraBufferSize;
}
if (!pointer.bgra32Buffer) return true;
// If masked, copy the desktop image and merge it with masked image.
if (isMono || isColorMask)
{
HRESULT hr;
D3D11_TEXTURE2D_DESC desc;
desc.Width = w;
desc.Height = h;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.Usage = D3D11_USAGE_STAGING;
desc.BindFlags = 0;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
desc.MiscFlags = 0;
ID3D11DeviceContext* context;
auto device = g_unity->Get<IUnityGraphicsD3D11>()->GetDevice();
device->GetImmediateContext(&context);
ID3D11Texture2D* texture = nullptr;
hr = device->CreateTexture2D(&desc, nullptr, &texture);
if (FAILED(hr))
{
return false;
}
D3D11_BOX box;
box.front = 0;
box.back = 1;
box.left = pointer.x;
box.top = pointer.y;
box.right = pointer.x + w;
box.bottom = pointer.y + h;
context->CopySubresourceRegion(texture, 0, 0, 0, 0, monitor.texture, 0, &box);
IDXGISurface* surface = nullptr;
hr = texture->QueryInterface(__uuidof(IDXGISurface), (void**)&surface);
texture->Release();
if (FAILED(hr))
{
return false;
}
DXGI_MAPPED_RECT mappedSurface;
hr = surface->Map(&mappedSurface, DXGI_MAP_READ);
if (FAILED(hr))
{
surface->Release();
return false;
}
// Finally, get the texture behind the mouse pointer.
const auto desktop32 = reinterpret_cast<UINT*>(mappedSurface.pBits);
const UINT desktopPitch = mappedSurface.Pitch / sizeof(UINT);
// Access RGBA values at the same time
auto output32 = reinterpret_cast<UINT*>(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<UINT*>(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<UINT*>(pointer.bgra32Buffer);
const auto buffer32 = reinterpret_cast<UINT*>(pointer.apiBuffer);
for (UINT i = 0; i < w * h; ++i)
{
output32[i] = buffer32[i];
}
}
return true;
}
void UNITY_INTERFACE_API OnRenderEvent(int id)
{
if (!IsValidMonitorId(id)) return;
auto& monitor = g_monitors[id];
if (monitor.deskDupl == nullptr || monitor.texture == nullptr) return;
HRESULT hr;
IDXGIResource* resource = nullptr;
DXGI_OUTDUPL_FRAME_INFO frameInfo;
const UINT timeout = 100; // ms
hr = g_deskDupl->AcquireNextFrame(timeout, &frameInfo, &resource);
switch (hr)
g_errorCode = monitor.deskDupl->AcquireNextFrame(g_timeout, &frameInfo, &resource);
if (FAILED(g_errorCode))
{
case S_OK:
if (g_errorCode == DXGI_ERROR_ACCESS_LOST)
{
break;
}
case DXGI_ERROR_ACCESS_LOST:
{
FinalizeDuplication();
InitializeDuplication();
return;
g_errorMessage = "[IDXGIOutputDuplication::AcquireNextFrame()] Access lost.";
}
default:
else
{
return;
g_errorMessage = "[IDXGIOutputDuplication::AcquireNextFrame()] Maybe timeout.";
}
}
g_isPointerVisible = frameInfo.PointerPosition.Visible;
g_pointerX = frameInfo.PointerPosition.Position.x;
g_pointerY = frameInfo.PointerPosition.Position.y;
ID3D11Texture2D* texture;
hr = resource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&texture));
if (hr != S_OK)
{
resource->Release();
return;
}
resource->Release();
ID3D11Texture2D* texture;
resource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&texture));
ID3D11DeviceContext* context;
auto device = g_unity->Get<IUnityGraphicsD3D11>()->GetDevice();
device->GetImmediateContext(&context);
context->CopyResource(g_texture, texture);
context->CopyResource(monitor.texture, texture);
g_deskDupl->ReleaseFrame();
if (!UpdateMouse(frameInfo, monitor))
{
g_errorCode = -999;
g_errorMessage = "[UpdateMouse()] failed.";
}
resource->Release();
monitor.deskDupl->ReleaseFrame();
}
UNITY_INTERFACE_EXPORT UnityRenderingEvent UNITY_INTERFACE_API GetRenderEventFunc()
@@ -147,33 +334,114 @@ extern "C"
return OnRenderEvent;
}
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetWidth()
UNITY_INTERFACE_EXPORT size_t UNITY_INTERFACE_API GetMonitorCount()
{
return g_width;
return g_monitors.size();
}
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetHeight()
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API SetTimeout(int timeout)
{
return g_height;
g_timeout = timeout;
}
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API IsPointerVisible()
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API GetName(int id, char* buf, int len)
{
return g_isPointerVisible;
if (!IsValidMonitorId(id)) return;
strcpy_s(buf, len, g_monitors[id].monitorInfo.szDevice);
}
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerX()
UNITY_INTERFACE_EXPORT bool UNITY_INTERFACE_API IsPrimary(int id)
{
return g_pointerX;
if (!IsValidMonitorId(id)) return false;
return g_monitors[id].monitorInfo.dwFlags == MONITORINFOF_PRIMARY;
}
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerY()
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetWidth(int id)
{
return g_pointerY;
if (!IsValidMonitorId(id)) return -1;
const auto rect = g_monitors[id].monitorInfo.rcMonitor;
return rect.right - rect.left;
}
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API SetTexturePtr(void* texture)
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetHeight(int id)
{
g_texture = reinterpret_cast<ID3D11Texture2D*>(texture);
if (!IsValidMonitorId(id)) return -1;
const auto rect = g_monitors[id].monitorInfo.rcMonitor;
return rect.bottom - rect.top;
}
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API IsPointerVisible(int id)
{
if (!IsValidMonitorId(id)) return false;
return g_monitors[id].pointer.isVisible;
}
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerX(int id)
{
if (!IsValidMonitorId(id)) return -1;
return g_monitors[id].pointer.x;
}
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerY(int id)
{
if (!IsValidMonitorId(id)) return -1;
return g_monitors[id].pointer.y;
}
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapeWidth(int id)
{
if (!IsValidMonitorId(id)) return -1;
return g_monitors[id].pointer.shapeInfo.Width;
}
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapeHeight(int id)
{
if (!IsValidMonitorId(id)) return -1;
const auto& info = g_monitors[id].pointer.shapeInfo;
return (info.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME) ? info.Height / 2 : info.Height;
}
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapePitch(int id)
{
if (!IsValidMonitorId(id)) return -1;
return g_monitors[id].pointer.shapeInfo.Pitch;
}
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetPointerShapeType(int id)
{
if (!IsValidMonitorId(id)) return -1;
return g_monitors[id].pointer.shapeInfo.Type;
}
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UpdatePointerTexture(int id, ID3D11Texture2D* ptr)
{
if (!IsValidMonitorId(id)) return;
const auto& pointer = g_monitors[id].pointer;
if (!pointer.bgra32Buffer) return;
auto device = g_unity->Get<IUnityGraphicsD3D11>()->GetDevice();
ID3D11DeviceContext* context;
device->GetImmediateContext(&context);
context->UpdateSubresource(ptr, 0, nullptr, pointer.bgra32Buffer, pointer.shapeInfo.Width * 4, 0);
}
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API SetTexturePtr(int id, void* texture)
{
if (!IsValidMonitorId(id)) return;
g_monitors[id].texture = reinterpret_cast<ID3D11Texture2D*>(texture);
}
UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API GetErrorCode()
{
const auto code = g_errorCode;
g_errorCode = 0;
return static_cast<int>(code);
}
UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API GetErrorMessage(char* buf, int len)
{
const auto message = g_errorMessage;
g_errorMessage = "";
strcpy_s(buf, len, message.c_str());
}
}

View File

@@ -1,12 +1,12 @@
uDesktopDuplication
===================
**uDesktopDuplication** is Desktop Duplication API implementation for Unity.
**uDesktopDuplication** is an Unity asset to use the realtime screen capture as `Texture2D` using Desktop Duplication API.
ScreenShot
----------
![uDesktopDuplication](https://raw.githubusercontent.com/wiki/hecomi/uDesktopDuplication/screenshot.png)
![uDesktopDuplication](https://raw.githubusercontent.com/wiki/hecomi/uDesktopDuplication/animation.gif)
Environment
@@ -22,20 +22,38 @@ Please download the latest *uDesktopDuplication.unitypackage* from the [release
Usage
-----
Attach `uDesktopDuplication.cs` component to the target object, then its main texture will be replaced with the captured screen.
TODOs
-----
- [ ] Mouse cursor
- [ ] Monitor selector
- [ ] Support linear color
Please request new features you want to issues.
Attach `uDesktopDuplication/Texture` component to the target object, then its main texture will be replaced with the captured screen. Please see example scenes for more details.
Version
-------
| Data | Version | Description |
| ---------- | ------- | --------------------------- |
| 2016/10/27 | 0.0.1 | Initial commit. |
| Data | Version | Description |
| ---------- | ------- | --------------------------------- |
| 2016/10/28 | 1.0.0 | Support multiple screens. |
| 2016/10/27 | 0.0.3 | Support lienar color. |
| 2016/10/27 | 0.0.2 | Add mouse cursor / shaders. |
| 2016/10/27 | 0.0.1 | Initial commit. |
Lisence
-------
The MIT License (MIT)
Copyright (c) 2016 hecomi
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.