Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resizing a DXGI Resource or Texture2D in SharpDX

I want to resize a screen captured using the Desktop Duplication API in SharpDX. I am using the Screen Capture sample code from the SharpDX Samples repository, relevant portion follows:.

SharpDX.DXGI.Resource screenResource;
OutputDuplicateFrameInformation duplicateFrameInformation;

// Try to get duplicated frame within given time
duplicatedOutput.AcquireNextFrame(10000, out duplicateFrameInformation, out screenResource);

if (i > 0)
{
    // copy resource into memory that can be accessed by the CPU
    using (var screenTexture2D = screenResource.QueryInterface<Texture2D>()) 
    device.ImmediateContext.CopyResource(screenTexture2D, screenTexture);

    // Get the desktop capture texture
    var mapSource = device.ImmediateContext.MapSubresource(screenTexture, 0, MapMode.Read, MapFlags.None);

    System.Diagnostics.Debug.WriteLine(watch.Elapsed);

    // Create Drawing.Bitmap
    var bitmap = new System.Drawing.Bitmap(width, height, PixelFormat.Format32bppArgb);
    var boundsRect = new System.Drawing.Rectangle(0, 0, width, height);

    // Copy pixels from screen capture Texture to GDI bitmap
    var mapDest = bitmap.LockBits(boundsRect, ImageLockMode.WriteOnly, bitmap.PixelFormat);
    var sourcePtr = mapSource.DataPointer;
    var destPtr = mapDest.Scan0;
    for (int y = 0; y < height; y++)
    {
        // Iterate and write to bitmap...

I would like to resize the image much smaller than the actual screen size before processing it as a byte array. I do not need to save the image, just get at the bytes. I would like to do this relatively quickly and efficiently (e.g. leveraging GPU if possible).

I'm not able to scale during CopyResource, as the output dimensions are required to be the same as the input dimensions. Can I perform another copy from my screenTexture2D to scale? How exactly do I scale the resource - do I use a Swap Chain, Matrix transform, or something else?

like image 332
Jon Galloway Avatar asked Jun 05 '14 15:06

Jon Galloway


2 Answers

If you are fine resizing to a power of two from the screen, you can do it by:

  • Create a smaller texture with RenderTarget/ShaderResource usage, and options GenerateMipMaps, same size of screen, mipcount > 1 (2 for having size /2, 3 for having /4...etc.).
  • Copy the first mipmap of the screen texture to the smaller texture
  • DeviceContext.GenerateMipMaps on the smaller texture
  • Copy the selected mimap of the smaller texture (1: /2, 2: /4...etc.) to the staging texture (that should also be declared smaller, i.e. same size as the mipmap that is going to be used)

A quick hack on the original code to generate a /2 texture would be like this:

    [STAThread]
    private static void Main()
    {
        // # of graphics card adapter
        const int numAdapter = 0;

        // # of output device (i.e. monitor)
        const int numOutput = 0;

        const string outputFileName = "ScreenCapture.bmp";

        // Create DXGI Factory1
        var factory = new Factory1();
        var adapter = factory.GetAdapter1(numAdapter);

        // Create device from Adapter
        var device = new Device(adapter);

        // Get DXGI.Output
        var output = adapter.GetOutput(numOutput);
        var output1 = output.QueryInterface<Output1>();

        // Width/Height of desktop to capture
        int width = output.Description.DesktopBounds.Width;
        int height = output.Description.DesktopBounds.Height;

        // Create Staging texture CPU-accessible
        var textureDesc = new Texture2DDescription
                              {
                                  CpuAccessFlags = CpuAccessFlags.Read,
                                  BindFlags = BindFlags.None,
                                  Format = Format.B8G8R8A8_UNorm,
                                  Width = width/2,
                                  Height = height/2,
                                  OptionFlags = ResourceOptionFlags.None,
                                  MipLevels = 1,
                                  ArraySize = 1,
                                  SampleDescription = { Count = 1, Quality = 0 },
                                  Usage = ResourceUsage.Staging
                              };
        var stagingTexture = new Texture2D(device, textureDesc);

        // Create Staging texture CPU-accessible
        var smallerTextureDesc = new Texture2DDescription
        {
            CpuAccessFlags = CpuAccessFlags.None,
            BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
            Format = Format.B8G8R8A8_UNorm,
            Width = width,
            Height = height,
            OptionFlags = ResourceOptionFlags.GenerateMipMaps,
            MipLevels = 4,
            ArraySize = 1,
            SampleDescription = { Count = 1, Quality = 0 },
            Usage = ResourceUsage.Default
        };
        var smallerTexture = new Texture2D(device, smallerTextureDesc);
        var smallerTextureView = new ShaderResourceView(device, smallerTexture);

        // Duplicate the output
        var duplicatedOutput = output1.DuplicateOutput(device);

        bool captureDone = false;
        for (int i = 0; !captureDone; i++)
        {
            try
            {
                SharpDX.DXGI.Resource screenResource;
                OutputDuplicateFrameInformation duplicateFrameInformation;

                // Try to get duplicated frame within given time
                duplicatedOutput.AcquireNextFrame(10000, out duplicateFrameInformation, out screenResource);

                if (i > 0)
                {
                    // copy resource into memory that can be accessed by the CPU
                    using (var screenTexture2D = screenResource.QueryInterface<Texture2D>())
                        device.ImmediateContext.CopySubresourceRegion(screenTexture2D, 0, null, smallerTexture, 0);

                    // Generates the mipmap of the screen
                    device.ImmediateContext.GenerateMips(smallerTextureView);

                    // Copy the mipmap 1 of smallerTexture (size/2) to the staging texture
                    device.ImmediateContext.CopySubresourceRegion(smallerTexture, 1, null, stagingTexture, 0);

                    // Get the desktop capture texture
                    var mapSource = device.ImmediateContext.MapSubresource(stagingTexture, 0, MapMode.Read, MapFlags.None);

                    // Create Drawing.Bitmap
                    var bitmap = new System.Drawing.Bitmap(width/2, height/2, PixelFormat.Format32bppArgb);
                    var boundsRect = new System.Drawing.Rectangle(0, 0, width/2, height/2);

                    // Copy pixels from screen capture Texture to GDI bitmap
                    var mapDest = bitmap.LockBits(boundsRect, ImageLockMode.WriteOnly, bitmap.PixelFormat);
                    var sourcePtr = mapSource.DataPointer;
                    var destPtr = mapDest.Scan0;
                    for (int y = 0; y < height/2; y++)
                    {
                        // Copy a single line 
                        Utilities.CopyMemory(destPtr, sourcePtr, width/2 * 4);

                        // Advance pointers
                        sourcePtr = IntPtr.Add(sourcePtr, mapSource.RowPitch);
                        destPtr = IntPtr.Add(destPtr, mapDest.Stride);
                    }

                    // Release source and dest locks
                    bitmap.UnlockBits(mapDest);
                    device.ImmediateContext.UnmapSubresource(stagingTexture, 0);

                    // Save the output
                    bitmap.Save(outputFileName);

                    // Capture done
                    captureDone = true;
                }

                screenResource.Dispose();
                duplicatedOutput.ReleaseFrame();

            }
            catch (SharpDXException e)
            {
                if (e.ResultCode.Code != SharpDX.DXGI.ResultCode.WaitTimeout.Result.Code)
                {
                    throw e;
                }
            }
        }

        // Display the texture using system associated viewer
        System.Diagnostics.Process.Start(Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, outputFileName)));

        // TODO: We should cleanp up all allocated COM objects here
    }
like image 163
xoofx Avatar answered Nov 08 '22 20:11

xoofx


You need to take your original source surface in GPU memory and Draw() it on to a smaller surface. This involves simple vector/pixel shaders, which some folks with simple needs would rather bypass.

I would look to see if someone made a sprite lib for sharpdx. It should be a common "thing"...or using Direct2D (which is much more fun). Since D2D is just a user-mode library over D3D, it interops with D3D very easily.

I've never used SharpDx, but fFrom memory you would do something like this:

1.) Create an ID2D1Device, wrapping your existing DXGI Device (make sure your dxgi device creation flag has D3D11_CREATE_DEVICE_BGRA_SUPPORT)

2.) Get the ID2D1DeviceContext from your ID2D1Device

3.) Wrap your source and destination DXGI surfaces into D2D bitmaps with ID2D1DeviceContext::CreateBitmapFromDxgiSurface

4.) ID2D1DeviceContext::SetTarget of your destination surface

5.) BeginDraw, ID2D1DeviceContext::DrawBitmap, passing your source D2D bitmap. EndDraw

6.) Save your destination

like image 29
Jeremiah Morrill Avatar answered Nov 08 '22 18:11

Jeremiah Morrill