桌面屏幕到 Unity3D 中的平面(RAM 溢出)
Desktop screen to Plane in Unity3D (RAM overflow)
我尝试将计算机游戏的桌面流式传输到 Unity3D 中的平面上(然后与之交互)。我开始进行屏幕截图,但一分钟后 unity 游戏就填满了我 PC 的内存 (~30GB)。
我已经尝试通过手动调用垃圾收集器来清理它,但调用它似乎没有任何改变。
我还在 Visual Studio 中使用 Windows 表单用本机 C# 编写了一个测试应用程序。在 C# 测试应用程序中,代码运行良好,不会溢出 RAM。
有没有可能,Unity 不知道如何释放 RAM?为了让这段代码正常工作,我必须将 System.Drawing.dll
添加到我的 Unity 游戏中。
如果这种方法不起作用,是否有任何其他选项可以流式传输桌面并将其显示在 Unity 中的平面上?
这是我当前的代码:
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using UnityEngine;
public class ScreenCap : MonoBehaviour {
public Renderer renderer;
public MemoryStream stream;
// Use this for initialization
void Start () {
renderer = GetComponent<Renderer>();
startScreenCaptureThread();
}
// Update is called once per frame
void Update () {
Texture2D tex = new Texture2D(200, 300, TextureFormat.RGB24, false);
if (stream != null)
{
tex.LoadImage(stream.ToArray());
renderer.material.mainTexture = tex;
stream.Dispose();
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
}
}
public void startScreenCaptureThread()
{
Thread t = new Thread(ScreenCapture);
t.Start();
}
public void ScreenCapture()
{
Rectangle screenSize;
Bitmap target;
MemoryStream tempStream;
while (true)
{
System.Diagnostics.Process proc = System.Diagnostics.Process.GetCurrentProcess();
Debug.Log(proc.PrivateMemorySize64);
screenSize = System.Windows.Forms.Screen.PrimaryScreen.Bounds;
target = new Bitmap(screenSize.Width, screenSize.Height);
using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(target))
{
g.CopyFromScreen(0, 0, 0, 0, new Size(screenSize.Width, screenSize.Height));
}
tempStream = new MemoryStream();
target.Save(tempStream, ImageFormat.Png);
tempStream.Seek(0, SeekOrigin.Begin);
stream = tempStream;
target.Dispose();
tempStream.Dispose();
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
}
}
}
创建新的 Texture2D
时,它永远不会被销毁,直到您加载另一个场景。然后 Unity 将清理 Texture2D
.
使用的资源
根据您的代码,您每帧都在创建新的 Texture2D
。这确实应该是您代码中最大的问题。
只要移动
Texture2D tex = new Texture2D(200, 300, TextureFormat.RGB24, false);
进入 Start
函数,然后使 tex
变量成为 public 变量,以便它可以在 Update 函数中使用。这样,您就不会在每一帧都创建新的 Texture2D
。
Texture2D tex;
void Start ()
{
tex = new Texture2D(200, 300, TextureFormat.RGB24, false);
}
void Update ()
{
if (stream != null)
{
tex.LoadImage(stream.ToArray());
....
...
}
}
注意:
您的代码中存在大量不相关的问题:
1。你的代码无论如何都不是线程安全的。您应该使用 Thread.MemoryBarrier
或 lock
关键字。这是一个很长的话题要在这里讨论。您可以在 Internet 上找到很多资源。
2。你在盲目更新Texture2D
,不知道有没有新截图。您可以通过从 ScreenCapture()
函数调用 Update
函数内的加载代码来解决此问题。
当然,你会得到
xxx can only be called from the main thread
异常。使用 UnityThread.executeInUpdate
函数这个 UnityThread
class.
将下面的代码放在 while
循环的末尾:
UnityThread.executeInUpdate(() =>
{
if (stream != null)
{
tex.LoadImage(stream.ToArray());
renderer.material.mainTexture = tex;
stream.Dispose();
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
}
});
您仍然需要执行 #1 以使其成为线程安全的。我会把它留给你。您只需要使 stream
变量成为线程安全的,因为它被不同的线程使用。
我尝试将计算机游戏的桌面流式传输到 Unity3D 中的平面上(然后与之交互)。我开始进行屏幕截图,但一分钟后 unity 游戏就填满了我 PC 的内存 (~30GB)。
我已经尝试通过手动调用垃圾收集器来清理它,但调用它似乎没有任何改变。
我还在 Visual Studio 中使用 Windows 表单用本机 C# 编写了一个测试应用程序。在 C# 测试应用程序中,代码运行良好,不会溢出 RAM。
有没有可能,Unity 不知道如何释放 RAM?为了让这段代码正常工作,我必须将 System.Drawing.dll
添加到我的 Unity 游戏中。
如果这种方法不起作用,是否有任何其他选项可以流式传输桌面并将其显示在 Unity 中的平面上?
这是我当前的代码:
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using UnityEngine;
public class ScreenCap : MonoBehaviour {
public Renderer renderer;
public MemoryStream stream;
// Use this for initialization
void Start () {
renderer = GetComponent<Renderer>();
startScreenCaptureThread();
}
// Update is called once per frame
void Update () {
Texture2D tex = new Texture2D(200, 300, TextureFormat.RGB24, false);
if (stream != null)
{
tex.LoadImage(stream.ToArray());
renderer.material.mainTexture = tex;
stream.Dispose();
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
}
}
public void startScreenCaptureThread()
{
Thread t = new Thread(ScreenCapture);
t.Start();
}
public void ScreenCapture()
{
Rectangle screenSize;
Bitmap target;
MemoryStream tempStream;
while (true)
{
System.Diagnostics.Process proc = System.Diagnostics.Process.GetCurrentProcess();
Debug.Log(proc.PrivateMemorySize64);
screenSize = System.Windows.Forms.Screen.PrimaryScreen.Bounds;
target = new Bitmap(screenSize.Width, screenSize.Height);
using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(target))
{
g.CopyFromScreen(0, 0, 0, 0, new Size(screenSize.Width, screenSize.Height));
}
tempStream = new MemoryStream();
target.Save(tempStream, ImageFormat.Png);
tempStream.Seek(0, SeekOrigin.Begin);
stream = tempStream;
target.Dispose();
tempStream.Dispose();
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
}
}
}
创建新的 Texture2D
时,它永远不会被销毁,直到您加载另一个场景。然后 Unity 将清理 Texture2D
.
根据您的代码,您每帧都在创建新的 Texture2D
。这确实应该是您代码中最大的问题。
只要移动
Texture2D tex = new Texture2D(200, 300, TextureFormat.RGB24, false);
进入 Start
函数,然后使 tex
变量成为 public 变量,以便它可以在 Update 函数中使用。这样,您就不会在每一帧都创建新的 Texture2D
。
Texture2D tex;
void Start ()
{
tex = new Texture2D(200, 300, TextureFormat.RGB24, false);
}
void Update ()
{
if (stream != null)
{
tex.LoadImage(stream.ToArray());
....
...
}
}
注意:
您的代码中存在大量不相关的问题:
1。你的代码无论如何都不是线程安全的。您应该使用 Thread.MemoryBarrier
或 lock
关键字。这是一个很长的话题要在这里讨论。您可以在 Internet 上找到很多资源。
2。你在盲目更新Texture2D
,不知道有没有新截图。您可以通过从 ScreenCapture()
函数调用 Update
函数内的加载代码来解决此问题。
当然,你会得到
xxx can only be called from the main thread
异常。使用 UnityThread.executeInUpdate
函数这个 UnityThread
class.
将下面的代码放在 while
循环的末尾:
UnityThread.executeInUpdate(() =>
{
if (stream != null)
{
tex.LoadImage(stream.ToArray());
renderer.material.mainTexture = tex;
stream.Dispose();
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
}
});
您仍然需要执行 #1 以使其成为线程安全的。我会把它留给你。您只需要使 stream
变量成为线程安全的,因为它被不同的线程使用。