从SharpDX中的DataStream读取导致内存泄漏

时间:2016-03-14 14:46:56

标签: c# sharpdx

我试图从SharpDX.DataStream中获取像素。我这样做每秒25次,这导致了大量的内存使用。我想我错过了一些清理工作。

一旦我开始调用GetColor方法,内存使用量就开始上升。我试过放弃课堂,但无济于事。拥有更多SharpDX经验的人可能能够指出我所缺少的东西。它很可能像释放资源一样简单,但我很困难。

// Original code by Florian Schnell
// http://www.floschnell.de/computer-science/super-fast-screen-capture-with-windows-8.html

using System;
using System.IO;
using SharpDX;
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using Color = System.Drawing.Color;
using Device = SharpDX.Direct3D11.Device;
using MapFlags = SharpDX.DXGI.MapFlags;
using Point = System.Drawing.Point;
using Resource = SharpDX.DXGI.Resource;
using ResultCode = SharpDX.DXGI.ResultCode;

namespace Artemis.Modules.Effects.AmbientLightning
{
internal class ScreenCapture : IDisposable
{
    private readonly Device _device;
    private readonly Factory1 _factory;
    private readonly Texture2D _screenTexture;
    private DataStream _dataStream;
    private readonly OutputDuplication _duplicatedOutput;
    private Resource _screenResource;
    private Surface _screenSurface;

    public ScreenCapture()
    {
        // Create device and factory
        _device = new Device(DriverType.Hardware);
        _factory = new Factory1();

        // Creating CPU-accessible texture resource
        var texdes = new Texture2DDescription
        {
            CpuAccessFlags = CpuAccessFlags.Read,
            BindFlags = BindFlags.None,
            Format = Format.B8G8R8A8_UNorm,
            Height = _factory.Adapters1[0].Outputs[0].Description.DesktopBounds.Bottom,
            Width = _factory.Adapters1[0].Outputs[0].Description.DesktopBounds.Right,
            OptionFlags = ResourceOptionFlags.None,
            MipLevels = 1,
            ArraySize = 1,
            SampleDescription =
            {
                Count = 1,
                Quality = 0
            },
            Usage = ResourceUsage.Staging
        };
        _screenTexture = new Texture2D(_device, texdes);

        // duplicate output stuff
        var output = new Output1(_factory.Adapters1[0].Outputs[0].NativePointer);
        _duplicatedOutput = output.DuplicateOutput(_device);
        _screenResource = null;
        _dataStream = null;
    }

    public void Dispose()
    {
        _duplicatedOutput.Dispose();
        _screenResource.Dispose();
        _dataStream.Dispose();
        _factory.Dispose();
    }

    public DataStream Capture()
    {
        try
        {
            OutputDuplicateFrameInformation duplicateFrameInformation;
            _duplicatedOutput.AcquireNextFrame(1000, out duplicateFrameInformation, out _screenResource);
        }
        catch (SharpDXException e)
        {
            if (e.ResultCode.Code == ResultCode.WaitTimeout.Result.Code ||
                e.ResultCode.Code == ResultCode.AccessDenied.Result.Code ||
                e.ResultCode.Code == ResultCode.AccessLost.Result.Code)
                return null;
            throw;
        }

        // copy resource into memory that can be accessed by the CPU
        _device.ImmediateContext.CopyResource(_screenResource.QueryInterface<SharpDX.Direct3D11.Resource>(),
            _screenTexture);

        // cast from texture to surface, so we can access its bytes
        _screenSurface = _screenTexture.QueryInterface<Surface>();

        // map the resource to access it
        _screenSurface.Map(MapFlags.Read, out _dataStream);

        // seek within the stream and read one byte
        _dataStream.Position = 4;
        _dataStream.ReadByte();

        // free resources
        _dataStream.Close();
        _screenSurface.Unmap();
        _screenSurface.Dispose();
        _screenResource.Dispose();
        _duplicatedOutput.ReleaseFrame();

        return _dataStream;
    }

    /// <summary>
    ///     Gets a specific pixel out of the data stream.
    /// </summary>
    /// <param name="surfaceDataStream"></param>
    /// <param name="position">Given point on the screen.</param>
    /// <returns></returns>
    public Color GetColor(DataStream surfaceDataStream, Point position)
    {
        var data = new byte[4];
        surfaceDataStream.Seek(
            position.Y*_factory.Adapters1[0].Outputs[0].Description.DesktopBounds.Right*4 + position.X*4,
            SeekOrigin.Begin);
        surfaceDataStream.Read(data, 0, 4);
        return Color.FromArgb(255, data[2], data[1], data[0]);
    }
}
}

2 个答案:

答案 0 :(得分:3)

不确定它是否会解决您的内存问题,但有一些改进:

  • 处理_screenResource.QueryInterface<SharpDX.Direct3D11.Resource>()
  • _screenSurface = _screenTexture.QueryInterface<Surface>();
  • 直接执行此操作时,请避免浏览地图Direct3D11.DeviceContext.Map()的DXGI
  • 使用返回结构的DeviceContext.Map()版本(仅包含非托管内存指针)而不是DataStream
  • 使用Utilities.Read<ColorBGRA>(intptr)和正确的字节偏移量来提取数据(请注意,DataStream也允许直接读取颜色,它不能与ReadByte一起使用)
  • 而不是position.Y*_factory.Adapters1[0].Outputs[0].Description.DesktopBounds.Right*4 + position.X*4。使用Map方法(DataBox.RowPitch)返回的步幅偏移到正确的行而不是纹理的宽度(每行的字节数可以改变,可能不与纹理宽度对齐)

答案 1 :(得分:2)

如果问题出在GetColor方法中,则可能是您创建的字节数组的绝对数量。您必须等待GC接收它们,如果必须检查每个参考文献,它将需要一段时间。

如果您想直接阅读,可以尝试以ints形式存储:

int b = DataStream.ReadByte(), g = DataStream.ReadByte(), r = DataStream.ReadByte();
DataStream.ReadByte(); //discard the alpha byte
return Color.FromRgb((byte)r, (byte)g, (byte)b);

对我来说似乎有点奇怪,你的流是bgr而不是rbg。