如何在 SharpDX 中使用 ID2D1SpriteBatch?

How to use ID2D1SpriteBatch in SharpDX?

尝试在 direct2d 中使用 ID2D1SpriteBatch 以获得比常规 DrawBitmap() 更高的性能。

设法设置它,但当我调用 DeviceContext.EndDraw() 时我得到 "The object was not in the correct state to process the method"。

我可以让 DeviceContext.DrawBitmap() 工作(参见注释部分)。尝试了所有我能想到的使设备上下文处于正确状态以处理 spritebatch 但没有运气。

试图尽可能地简化这个示例,但也不想遗漏任何步骤以防万一这是罪魁祸首。

有什么让它发挥作用的想法吗?

using SharpDX;
using _d2d = SharpDX.Direct2D1;
using _d3d = SharpDX.Direct3D;
using _d3d11 = SharpDX.Direct3D11;
using _dxgi = SharpDX.DXGI;
using _directWrite = SharpDX.DirectWrite;
using _wic = SharpDX.WIC;
using SharpDX.Direct2D1;
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using SharpDX.Windows;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using SharpDX.IO;
using SharpDX.Mathematics.Interop;

namespace TestApp
{
    public class SpriteBatchIssue
    {
        [STAThread]
        static void Main(string[] args)
        {
            var app = new SpriteBatchIssue();
            app.Run();
        }

        bool isClosed = false;

        public void Run()
        {
            #region setup resources      
            var clientSize = new Size2(1000, 500);

            var mainForm = new RenderForm();
            mainForm.ClientSize = new System.Drawing.Size(
                clientSize.Width,
                clientSize.Height);

            mainForm.FormClosed += mainForm_FormClosed;

            var scDescription = new SwapChainDescription()
            {
                BufferCount = 1,
                ModeDescription =
                    new ModeDescription(
                        clientSize.Width,
                        clientSize.Height,
                        new Rational(60, 1),
                        Format.R8G8B8A8_UNorm),
                IsWindowed = true,
                OutputHandle = mainForm.Handle,
                SampleDescription = new SampleDescription(1, 0),
                SwapEffect = SwapEffect.Discard,
                Usage = Usage.RenderTargetOutput
            };

            // Create Device and SwapChain
            _d3d11.Device d3d11Device;
            SwapChain swapChain;
            _d3d11.Device.CreateWithSwapChain(
                DriverType.Hardware,
                DeviceCreationFlags.BgraSupport,
                new[] { _d3d.FeatureLevel.Level_12_1 },
                scDescription,
                out d3d11Device,
                out swapChain);

            // Ignore all windows events
            var dxgiFactory = swapChain.GetParent<_dxgi.Factory1>();
            dxgiFactory.MakeWindowAssociation(mainForm.Handle, WindowAssociationFlags.IgnoreAll);

            // New RenderTargetView from the backbuffer
            var backBuffer = Texture2D.FromSwapChain<Texture2D>(swapChain, 0);
            var backBufferView = new RenderTargetView(d3d11Device, backBuffer);

            var d2dFactory = new _d2d.Factory();
            var d2dFactory4 = d2dFactory.QueryInterface<_d2d.Factory4>();

            var dxgiDevice = d3d11Device.QueryInterface<_dxgi.Device>();
            var d2dDevice3 = new _d2d.Device3(d2dFactory4, dxgiDevice);
            var d2dDeviceContext3 = new _d2d.DeviceContext3(d2dDevice3, DeviceContextOptions.None);

            using (var surface = backBuffer.QueryInterface<Surface>())
            {
                var bmpProperties = new BitmapProperties1(
                    new PixelFormat(Format.R8G8B8A8_UNorm, _d2d.AlphaMode.Premultiplied),
                    dpiX: 96,
                    dpiY: 96,
                    bitmapOptions: BitmapOptions.Target | BitmapOptions.CannotDraw);

                var d2dTarget = new Bitmap1(
                    d2dDeviceContext3,
                    surface,
                    bmpProperties);

                d2dDeviceContext3.Target = d2dTarget;
            }
            #endregion

            #region setup drawing parameters
            var bmp = createD2DBitmap(@"C:\yourPath\yourImage.png", d2dDeviceContext3);

            var spriteBatch = new SpriteBatch(d2dDeviceContext3);
            var destinationRects = new RawRectangleF[1];
            destinationRects[0] = new RectangleF(100, 50, bmp.Size.Width, bmp.Size.Height);

            var sourceRects = new RawRectangle[1];
            sourceRects[0] = new RectangleF(0, 0, bmp.Size.Width, bmp.Size.Height);

            var colors = new RawColor4[1];
            colors[0] = Color.White;

            var transforms = new RawMatrix3x2[1];
            transforms[0] = Matrix3x2.Identity;
            #endregion

            #region mainLoop
            RenderLoop.Run(mainForm, () =>
            {
                if (isClosed)
                {
                    return;
                }

                d3d11Device.ImmediateContext.Rasterizer.SetViewport(new Viewport(0, 0, clientSize.Width, clientSize.Height));
                d3d11Device.ImmediateContext.OutputMerger.SetTargets(backBufferView);

                d2dDeviceContext3.BeginDraw();

                //this technique works
                //d2dDeviceContext3.DrawBitmap(
                //    bitmap: bmp,
                //    destinationRectangle: destinationRects[0],
                //    opacity: 1,
                //    interpolationMode: BitmapInterpolationMode.Linear,
                //    sourceRectangle: new RectangleF(0, 0, bmp.Size.Width, bmp.Size.Height));

                //this technique does not work
                spriteBatch.Clear();
                spriteBatch.AddSprites(
                    1,
                    destinationRects,
                    sourceRects,
                    colors,
                    transforms,
                    destinationRectanglesStride: 0, //0 stride because there is only 1 element
                    sourceRectanglesStride: 0, //i've also tried using Marshal.SizeOf() to get the stride, but i get the same error
                    colorsStride: 0,
                    transformsStride: 0);

                d2dDeviceContext3.DrawSpriteBatch(
                    spriteBatch: spriteBatch,
                    startIndex: 0,
                    spriteCount: 1,
                    bitmap: bmp,
                    interpolationMode: BitmapInterpolationMode.Linear,
                    spriteOptions: SpriteOptions.ClampToSourceRectangle);

                //when using the spritebatch technique, this throws exception:
                // "The object was not in the correct state to process the method"
                d2dDeviceContext3.EndDraw();

                //first param set to 1 would indicate waitVerticalBlanking
                swapChain.Present(0, PresentFlags.None);
            });
            #endregion
        }

        Bitmap createD2DBitmap(string filePath, _d2d.DeviceContext deviceContext)
        {
            var imagingFactory = new _wic.ImagingFactory();

            var fileStream = new NativeFileStream(
                filePath,
                NativeFileMode.Open,
                NativeFileAccess.Read);

            var bitmapDecoder = new _wic.BitmapDecoder(imagingFactory, fileStream, _wic.DecodeOptions.CacheOnDemand);
            var frame = bitmapDecoder.GetFrame(0);

            var converter = new _wic.FormatConverter(imagingFactory);
            converter.Initialize(frame, SharpDX.WIC.PixelFormat.Format32bppPRGBA);

            var newBitmap = SharpDX.Direct2D1.Bitmap1.FromWicBitmap(deviceContext, converter);

            return newBitmap;
        }

        void mainForm_FormClosed(object sender, System.Windows.Forms.FormClosedEventArgs e)
        {
            isClosed = true;
        }
    }
}

问题是您不能对 spritebatch 使用每个原始抗锯齿。 BeginDraw() 之前的这一行修复了它:d2dDeviceContext3.AntialiasMode = AntialiasMode.Aliased;

也终于学会了如何让调试层工作。创建设备时包括调试标志(请参阅下面的注释)。如果它抛出异常,那可能是因为您没有正确版本的 windows sdk。如果您使用 visual studio,请转到 visual studio 安装程序并修改您的安装以包含 windows sdk。

接下来您需要右键单击您的项目->属性->调试(在左侧面板上)->勾选"enable native code debugging"。在我这样做之后,有一行写入输出 window 说明:"D2D DEBUG ERROR - DrawSpriteBatch requires that the antialias mode be set to D2D1_ANTIALIAS_MODE_ALIASED."

我了解到的其他内容与答案没有直接关系但值得注意:"Note that ComObject in SharpDX is not disposed by the .NET finalizer. If a COM object is not released by a call to Dispose() or ReleaseReference(), it will not release the native object and memory attached to it." 从这里:http://sharpdx.org/wiki/usage/

这是一个完整的工作示例:

using SharpDX;
using _d2d = SharpDX.Direct2D1;
using _d3d = SharpDX.Direct3D;
using _d3d11 = SharpDX.Direct3D11;
using _dxgi = SharpDX.DXGI;
using _directWrite = SharpDX.DirectWrite;
using _wic = SharpDX.WIC;
using SharpDX.Direct2D1;
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using SharpDX.Windows;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using SharpDX.IO;
using SharpDX.Mathematics.Interop;

namespace TestApp
{
    public class SpriteBatchIssue
    {
        [STAThread]
        static void Main(string[] args)
        {
            var app = new SpriteBatchIssue();
            app.Run();
        }

        #region Variables
        _d3d11.Device d3d11Device;
        SwapChain swapChain;
        _dxgi.Factory1 dxgiFactory;
        _d2d.Factory d2dFactory;
        _d2d.Factory4 d2dFactory4;

        _dxgi.Device dxgiDevice;
        _d2d.Device3 d2dDevice3;
        _d2d.DeviceContext3 d2dDeviceContext3;

        Bitmap1 sourceImage;
        SpriteBatch spriteBatch;
        Bitmap1 d2dTarget;
        #endregion

        ~SpriteBatchIssue()
        {
            safeDispose(ref d3d11Device);
            safeDispose(ref swapChain);
            safeDispose(ref dxgiFactory);
            safeDispose(ref d2dFactory);
            safeDispose(ref d2dFactory4);
            safeDispose(ref dxgiDevice);
            safeDispose(ref d2dDevice3);
            safeDispose(ref d2dDeviceContext3);
            safeDispose(ref sourceImage);
            safeDispose(ref spriteBatch);
            safeDispose(ref d2dTarget);
        }

        public void Run()
        {
            #region setup resources      
            var mainForm = new RenderForm();

            var scDescription = new SwapChainDescription()
            {
                BufferCount = 1,
                ModeDescription =
                    new ModeDescription(
                        0,
                        0,
                        new Rational(60, 1),
                        Format.R8G8B8A8_UNorm),
                IsWindowed = true,
                OutputHandle = mainForm.Handle,
                SampleDescription = new SampleDescription(1, 0),
                SwapEffect = SwapEffect.Discard,
                Usage = Usage.RenderTargetOutput
            };

            //DeviceCreationFlags.Debug flag below will show debug layer messages in your output window.
            //Need proper version of windows sdk for it to work, otherwise it will throw an exception.
            //You also need to right click your project->properties->debug (on the left panel)-> check "enable native code debugging"

            // Create Device and SwapChain
            _d3d11.Device.CreateWithSwapChain(
                DriverType.Hardware,
                DeviceCreationFlags.BgraSupport | DeviceCreationFlags.Debug, 
                new[] { _d3d.FeatureLevel.Level_12_1 },
                scDescription,
                out d3d11Device,
                out swapChain);

            // Ignore all windows events
            dxgiFactory = swapChain.GetParent<_dxgi.Factory1>();
            dxgiFactory.MakeWindowAssociation(mainForm.Handle, WindowAssociationFlags.IgnoreAll);

            d2dFactory = new _d2d.Factory();
            d2dFactory4 = d2dFactory.QueryInterface<_d2d.Factory4>();

            dxgiDevice = d3d11Device.QueryInterface<_dxgi.Device>();
            d2dDevice3 = new _d2d.Device3(d2dFactory4, dxgiDevice);
            d2dDeviceContext3 = new _d2d.DeviceContext3(d2dDevice3, DeviceContextOptions.None);
            #endregion

            #region create drawing input
            sourceImage = createD2DBitmap(@"yourFile.png", d2dDeviceContext3);

            spriteBatch = new SpriteBatch(d2dDeviceContext3);
            var destinationRects = new RawRectangleF[1];
            destinationRects[0] = new RectangleF(100, 50, sourceImage.Size.Width, sourceImage.Size.Height);

            var sourceRects = new RawRectangle[1];
            sourceRects[0] = new RectangleF(0, 0, sourceImage.Size.Width, sourceImage.Size.Height);
            #endregion

            #region mainLoop
            RenderLoop.Run(mainForm, () =>
            {
                if (d2dTarget != null)
                {
                    d2dTarget.Dispose();
                    d2dTarget = null;
                }

                using (var backBuffer = Texture2D.FromSwapChain<Texture2D>(swapChain, 0))
                {
                    using (var surface = backBuffer.QueryInterface<Surface>())
                    {
                        var bmpProperties = new BitmapProperties1(
                            new PixelFormat(Format.R8G8B8A8_UNorm, _d2d.AlphaMode.Premultiplied),
                            dpiX: 96,
                            dpiY: 96,
                            bitmapOptions: BitmapOptions.Target | BitmapOptions.CannotDraw);

                        d2dTarget = new Bitmap1(
                            d2dDeviceContext3,
                            surface,
                            bmpProperties);

                        d2dDeviceContext3.Target = d2dTarget;
                    }
                }

                //the key missing piece: cannot use per primitive antialiasing with spritebatch
                d2dDeviceContext3.AntialiasMode = AntialiasMode.Aliased;
                d2dDeviceContext3.BeginDraw();

                spriteBatch.Clear();
                spriteBatch.AddSprites(
                    1,
                    destinationRects,
                    sourceRects,
                    null,
                    null,
                    destinationRectanglesStride: 0, //0 stride because there is only 1 element
                    sourceRectanglesStride: 0,
                    colorsStride: 0,
                    transformsStride: 0);

                d2dDeviceContext3.DrawSpriteBatch(
                    spriteBatch: spriteBatch,
                    startIndex: 0,
                    spriteCount: 1,
                    bitmap: sourceImage,
                    interpolationMode: BitmapInterpolationMode.Linear,
                    spriteOptions: SpriteOptions.ClampToSourceRectangle);

                d2dDeviceContext3.EndDraw();

                //first param set to 1 would indicate waitVerticalBlanking
                swapChain.Present(0, PresentFlags.None);
            });
            #endregion
        }

        void safeDispose<T>(ref T disposable) where T : class, IDisposable
        {
            if (disposable != null)
            {
                disposable.Dispose();
                disposable = null;
            }
        }

        Bitmap1 createD2DBitmap(string filePath, _d2d.DeviceContext deviceContext)
        {
            var imagingFactory = new _wic.ImagingFactory();

            var fileStream = new NativeFileStream(
                filePath,
                NativeFileMode.Open,
                NativeFileAccess.Read);

            var bitmapDecoder = new _wic.BitmapDecoder(imagingFactory, fileStream, _wic.DecodeOptions.CacheOnDemand);
            var frame = bitmapDecoder.GetFrame(0);

            var converter = new _wic.FormatConverter(imagingFactory);
            converter.Initialize(frame, SharpDX.WIC.PixelFormat.Format32bppPRGBA);

            var newBitmap = SharpDX.Direct2D1.Bitmap1.FromWicBitmap(deviceContext, converter);

            return newBitmap;
        }
    }
}