Экран рабочего стола для Plane в Unity3D (переполнение ОЗУ)

Я пытаюсь транслировать рабочий стол компьютера в игре на плоскость в Unity3D (и позже взаимодействую с ним). Я приступил к съемке экрана, но уже через минуту игра Unity просто заполняет оперативную память моего компьютера (~30 ГБ).

Я уже пытаюсь очистить его, вызвав сборщик мусора вручную, но вызов этого, похоже, ничего не меняет.

Я также написал тестовое приложение на родном C# в Visual Studio с Windows Forms. В тестовом приложении C# код ведет себя хорошо и не загружает оперативную память.

Есть ли вероятность, что Unity не знает, как освободить оперативную память? Чтобы этот код работал, мне нужно было добавить 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();
        }
    }
}

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.MemoryBarrier или lock ключевое слово. Это длинная тема для обсуждения здесь. Вы можете найти много ресурсов в Интернете.

2. Вы слепо обновляете Texture2D не зная, есть ли новый скриншот или нет. Вы можете это исправить, вызвав коды загрузки, которые находятся внутри Update функция от ScreenCapture() функция.

Конечно, вы получите

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 переменная Thread-safe, так как она используется из разных потоков.

Другие вопросы по тегам