Click here to Skip to main content
15,886,362 members
Articles / Desktop Programming / WPF

Using Direct2D with WPF

Rate me:
Please Sign up or sign in to vote.
4.96/5 (28 votes)
3 Nov 2010CPOL11 min read 188.9K   6.1K   65  
Hosting Direct2D content in WPF controls.
using System;
using D2D = Microsoft.WindowsAPICodePack.DirectX.Direct2D1;
using D3D10 = Microsoft.WindowsAPICodePack.DirectX.Direct3D10;
using DirectX = Microsoft.WindowsAPICodePack.DirectX;

namespace Direct2D
{
    /// <summary>Represents a Direct2D drawing.</summary>
    public abstract class Scene : IDisposable
    {
        private D3D10.D3DDevice device;
        private D2D.RenderTarget renderTarget;
        private D3D10.Texture2D texture;
        private bool disposed;

        /// <summary>
        /// Finalizes an instance of the Scene class.
        /// </summary>
        ~Scene()
        {
            this.Dispose(false);
        }

        /// <summary>Gets the surface this instance draws to.</summary>
        /// <exception cref="ObjectDisposedException">
        /// <see cref="Dispose()"/> has been called on this instance.
        /// </exception>
        public D3D10.Texture2D Texture
        {
            get
            {
                this.ThrowIfDisposed();
                return this.texture;
            }
        }

        /// <summary>
        /// Immediately frees any system resources that the object might hold.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Creates a DirectX 10 device and related device specific resources.
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// A previous call to CreateResources has not been followed by a call to
        /// <see cref="FreeResources"/>.
        /// </exception>
        /// <exception cref="ObjectDisposedException">
        /// <see cref="Dispose()"/> has been called on this instance.
        /// </exception>
        /// <exception cref="DirectX.DirectXException">
        /// Unable to create a DirectX 10 device or an error occured creating
        /// device dependent resources.
        /// </exception>
        public void CreateResources()
        {
            this.ThrowIfDisposed();
            if (this.device != null)
            {
                throw new InvalidOperationException("A previous call to CreateResources has not been followed by a call to FreeResources.");
            }
            // Try to create a hardware device first and fall back to a
            // software (WARP doens't let us share resources)
            var device1 = TryCreateDevice1(D3D10.DriverType.Hardware);
            if (device1 == null)
            {
                device1 = TryCreateDevice1(D3D10.DriverType.Software);
                if (device1 == null)
                {
                    throw new DirectX.DirectXException("Unable to create a DirectX 10 device.");
                }
            }
            this.device = device1.QueryInterface<D3D10.D3DDevice>();
            device1.Dispose();
        }

        /// <summary>
        /// Releases the DirectX device and any device dependent resources.
        /// </summary>
        /// <remarks>
        /// This method is safe to be called even if the instance has been disposed.
        /// </remarks>
        public void FreeResources()
        {
            this.OnFreeResources();

            if (this.texture != null)
            {
                this.texture.Dispose();
                this.texture = null;
            }
            if (this.renderTarget != null)
            {
                this.renderTarget.Dispose();
                this.renderTarget = null;
            }
            if (this.device != null)
            {
                this.device.Dispose();
                this.device = null;
            }
        }

        /// <summary>
        /// Causes the scene to redraw its contents.
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// <see cref="Resize"/> has not been called.
        /// </exception>
        /// <exception cref="ObjectDisposedException">
        /// <see cref="Dispose()"/> has been called on this instance.
        /// </exception>
        public void Render()
        {
            this.ThrowIfDisposed();
            if (this.renderTarget == null)
            {
                throw new InvalidOperationException("Resize has not been called.");
            }

            this.OnRender();
            this.device.Flush();
        }

        /// <summary>Resizes the scene.</summary>
        /// <param name="width">The new width for the scene.</param>
        /// <param name="height">The new height for the scene.</param>
        /// <exception cref="ArgumentOutOfRangeException">
        /// width/height is less than zero.
        /// </exception>
        /// <exception cref="InvalidOperationException">
        /// <see cref="CreateResources"/> has not been called.
        /// </exception>
        /// <exception cref="ObjectDisposedException">
        /// <see cref="Dispose()"/> has been called on this instance.
        /// </exception>
        /// <exception cref="DirectX.DirectXException">
        /// An error occured creating device dependent resources.
        /// </exception>
        public void Resize(int width, int height)
        {
            this.ThrowIfDisposed();
            if (width < 0)
            {
                throw new ArgumentOutOfRangeException("width", "Value must be positive.");
            }
            if (height < 0)
            {
                throw new ArgumentOutOfRangeException("height", "Value must be positive.");
            }
            if (this.device == null)
            {
                throw new InvalidOperationException("CreateResources has not been called.");
            }

            // Recreate the render target
            this.CreateTexture(width, height);
            using (var surface = this.texture.QueryInterface<DirectX.Graphics.Surface>())
            {
                this.CreateRenderTarget(surface);
            }

            // Resize our viewport
            var viewport = new D3D10.Viewport();
            viewport.Height = (uint)height;
            viewport.MaxDepth = 1;
            viewport.MinDepth = 0;
            viewport.TopLeftX = 0;
            viewport.TopLeftY = 0;
            viewport.Width = (uint)width;
            this.device.RS.Viewports = new D3D10.Viewport[] { viewport };

            // Destroy and recreate any dependent resources declared in a
            // derived class only (i.e don't destroy our resources).
            this.OnFreeResources();
            this.OnCreateResources();
        }

        /// <summary>
        /// Gets the <see cref="D2D.RenderTarget"/> used for drawing.
        /// </summary>
        protected D2D.RenderTarget RenderTarget
        {
            get { return this.renderTarget; }
        }

        /// <summary>
        /// Immediately frees any system resources that the object might hold.
        /// </summary>
        /// <param name="disposing">
        /// Set to true if called from an explicit disposer; otherwise, false.
        /// </param>
        protected virtual void Dispose(bool disposing)
        {
            this.FreeResources();
            this.disposed = true;
        }

        /// <summary>
        /// When overriden in a derived class, creates device dependent resources.
        /// </summary>
        protected virtual void OnCreateResources()
        {
        }

        /// <summary>
        /// When overriden in a deriven class, releases device dependent resources.
        /// </summary>
        protected virtual void OnFreeResources()
        {
        }

        /// <summary>
        /// When overriden in a derived class, renders the Direct2D content.
        /// </summary>
        protected virtual void OnRender()
        {
        }

        /// <summary>
        /// Throws an <see cref="ObjectDisposedException"/> if
        /// <see cref="Dispose()"/> has been called on this instance.
        /// </summary>
        protected void ThrowIfDisposed()
        {
            if (this.disposed)
            {
                throw new ObjectDisposedException(this.GetType().Name);
            }
        }

        private static D3D10.D3DDevice1 TryCreateDevice1(D3D10.DriverType type)
        {
            // We'll try to create the device that supports any of these feature levels
            DirectX.Direct3D.FeatureLevel[] levels =
            {
                DirectX.Direct3D.FeatureLevel.Ten,
                DirectX.Direct3D.FeatureLevel.NinePointThree,
                DirectX.Direct3D.FeatureLevel.NinePointTwo,
                DirectX.Direct3D.FeatureLevel.NinePointOne
            };

            foreach (var level in levels)
            {
                try
                {
                    return D3D10.D3DDevice1.CreateDevice1(null, type, null, D3D10.CreateDeviceOptions.SupportBgra, DirectX.Direct3D.FeatureLevel.Ten);
                }
                catch (ArgumentException) // E_INVALIDARG
                {
                    continue; // Try the next feature level
                }
                catch (OutOfMemoryException) // E_OUTOFMEMORY
                {
                    continue; // Try the next feature level
                }
                catch (DirectX.DirectXException) // D3DERR_INVALIDCALL or E_FAIL
                {
                    continue; // Try the next feature level
                }
            }
            return null; // We failed to create a device at any required feature level
        }

        private void CreateRenderTarget(DirectX.Graphics.Surface surface)
        {
            // Create a D2D render target which can draw into our offscreen D3D
            // surface. D2D uses device independant units, like WPF, at 96/inch
            var properties = new D2D.RenderTargetProperties();
            properties.DpiX = 96;
            properties.DpiY = 96;
            properties.MinLevel = DirectX.Direct3D.FeatureLevel.Default;
            properties.PixelFormat = new D2D.PixelFormat(DirectX.Graphics.Format.Unknown, D2D.AlphaMode.Premultiplied);
            properties.RenderTargetType = D2D.RenderTargetType.Default;
            properties.Usage = D2D.RenderTargetUsages.None;

            using (var factory = D2D.D2DFactory.CreateFactory(D2D.D2DFactoryType.Multithreaded))
            {
                // Assign result to temporary variable in case CreateGraphicsSurfaceRenderTarget throws
                var target = factory.CreateGraphicsSurfaceRenderTarget(surface, properties);

                if (this.renderTarget != null)
                {
                    this.renderTarget.Dispose();
                }
                this.renderTarget = target;
            }
        }

        private void CreateTexture(int width, int height)
        {
            var description = new D3D10.Texture2DDescription();
            description.ArraySize = 1;
            description.BindingOptions = D3D10.BindingOptions.RenderTarget | D3D10.BindingOptions.ShaderResource;
            description.CpuAccessOptions = D3D10.CpuAccessOptions.None;
            description.Format = DirectX.Graphics.Format.B8G8R8A8UNorm;
            description.MipLevels = 1;
            description.MiscellaneousResourceOptions = D3D10.MiscellaneousResourceOptions.Shared;
            description.SampleDescription = new DirectX.Graphics.SampleDescription(1, 0);
            description.Usage = D3D10.Usage.Default;

            description.Height = (uint)height;
            description.Width = (uint)width;

            // Assign result to temporary variable in case CreateTexture2D throws
            var texture = this.device.CreateTexture2D(description);
            if (this.texture != null)
            {
                this.texture.Dispose();
            }
            this.texture = texture;
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions