【问题标题】:Desktop screen to Plane in Unity3D (RAM overflow)Unity3D 中的桌面屏幕到平面(RAM 溢出)
【发布时间】:2017-05-26 10:10:49
【问题描述】:

我尝试将计算机游戏的桌面流式传输到 Unity3D 中的平面上(然后与它进行交互)。我开始进行屏幕捕捉,但一分钟后,Unity 游戏已经填满了我 PC 的 RAM (~30GB)。

我已经尝试通过手动调用垃圾收集器来清理它,但调用它似乎并没有改变任何东西。

我还使用 Windows 窗体在 Visual Studio 中使用本机 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、Rectangle、Bitmap 和 MemoryStream 变量的初始化移出方法。我也不确定 Unity 中的线程。我认为使用协程会更好。
  • 我正在尝试在我的 VR 项目中添加一个虚拟桌面,发现使用 CopyFromSreen 在性能方面表现不佳,最高为 30fps,充分利用了 1 个 CPU 内核。看起来更好的方法是使用像 SharpDX 这样的 DirectX 库,或者像 github.com/Phylliida/UnityWindowsCapture 这样的东西(还不知道它是如何工作的)

标签: c# unity3d garbage-collection screen-capture


【解决方案1】:

当创建新的Texture2D 时,它永远不会被销毁,直到您加载另一个场景。然后Unity会清理Texture2D使用的资源。

从您的代码中,您正在每帧创建新的Texture2D。这确实应该是您代码中最大的问题。

动起来

Texture2D tex = new Texture2D(200, 300, TextureFormat.RGB24, false);

进入Start 函数,然后将tex 变量设为公共变量,以便它可以在更新函数中使用。这样,您就不会每帧都创建新的Texture2D

Texture2D tex;

void Start () 
{
    tex = new Texture2D(200, 300, TextureFormat.RGB24, false);
}

void Update () 
{
    if (stream != null)
    {
        tex.LoadImage(stream.ToArray());
        ....
        ...
    }
}

注意

你的代码中有很大的不相关的问题:

1。无论如何,您的代码都不是线程安全的。您应该使用 Thread.MemoryBarrierlock 关键字。这是一个很长的话题要在这里讨论。您可以在 Internet 上找到许多资源。

2.你在盲目更新Texture2D,不知道有没有新的截图。您可以通过从 ScreenCapture() 函数调用 Update 函数内的加载代码来解决此问题。

当然,你会得到

xxx can only be called from the main thread

异常。使用 UnityThread.executeInUpdate 函数这个 UnityThread 类。

将下面的代码放在while循环的末尾:

UnityThread.executeInUpdate(() =>
{
    if (stream != null)
    {
        tex.LoadImage(stream.ToArray());

        renderer.material.mainTexture = tex;
        stream.Dispose();
        System.GC.Collect();
        System.GC.WaitForPendingFinalizers();
    }
});

您仍然需要执行 #1 以使其成为线程安全的。我会把它留给你。您只需要将 stream 变量设为线程安全,因为它被不同的线程使用。

【讨论】:

  • 感谢您的帮助,这对我帮助很大。
猜你喜欢
  • 2010-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-07
  • 2021-06-18
  • 2012-06-19
  • 2011-02-20
  • 1970-01-01
相关资源
最近更新 更多