2

I'm trying to get pixels from a SharpDX.DataStream. I do this 25 times per second and it's causing a huge amount of memory usage. I think I'm missing some sort of cleanup.

As soon as I start calling the GetColor method, memory usage starts rising. I've tried Displosing the class, but to no avail. Anyone with a bit more SharpDX experience might be able to point out what I'm missing. It's most likely something simple like releasing resources but I'm quite stuck.

// 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]);
    }
}
}
Robert
  • 642
  • 7
  • 11

2 Answers2

3

Not sure it will solve your problem of memory, but there are a couple of improvements:

  • Dispose _screenResource.QueryInterface<SharpDX.Direct3D11.Resource>()
  • Avoid going through DXGI for map _screenSurface = _screenTexture.QueryInterface<Surface>(); while you can do this directly the Direct3D11.DeviceContext.Map()
  • Use the version of DeviceContext.Map() that returns a struct (with only an unmanaged memory pointer) instead of a DataStream
  • Use Utilities.Read<ColorBGRA>(intptr) with the correct byte offset to extract the data (note that DataStream allows also to read a color directly, it was not meant to be used with ReadByte)
  • Instead of this position.Y*_factory.Adapters1[0].Outputs[0].Description.DesktopBounds.Right*4 + position.X*4. Use the stride returned by the Map method (DataBox.RowPitch) to offset to the correct line and not the width of the texture (the number of bytes per row can change and may not be aligned to the texture width)
xoofx
  • 3,682
  • 1
  • 17
  • 32
2

If the issue is in the GetColor method, then it would probably be the sheer number of byte arrays you create. You'll have to wait for the GC to pick them up, and it will take a while if it has to check references for each one.

If you want to read directly, you can try to store in ints instead:

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);

Seems a bit odd to me that your stream is bgr instead of rbg.

Isaac van Bakel
  • 1,772
  • 10
  • 22
  • Hey, indeed I was confused about it being BGR too, but that's how it is, hehe. I tried your solution, and it sadly didn't help. After commeting out the Seek method, the high usage was gone though. But that means I can't read the Stream properly – Robert Mar 14 '16 at 17:21