Click here to Skip to main content
15,885,896 members
Articles / Desktop Programming / WPF

D3dHost - MDX and WPF interoperability

Rate me:
Please Sign up or sign in to vote.
5.00/5 (7 votes)
13 Nov 2012CPOL5 min read 34K   2.6K   20  
This article shows how we can render an interoperable MDX (Managed DirectX) scene, inside a WPF window.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.DirectX.Direct3D;
using Microsoft.DirectX;
using System.IO;
using System.Threading;

namespace MdxWpfInteroperability
{
    [TemplatePart(Name = "PART_D3dRegion", Type = typeof(Rectangle))]
    public class D3dHost : Control, IDisposable
    {
        private Rectangle _d3dRegion;

        public D3dHost()
        {
            TryUseD3DImageBeforeUsingMemory = true;
            TryUseMemoryBeforeUsingFilesSystem = true;
            FreeMemoryBeforeUpdateD3dRegion = true;
            MillisecondsForDispatcherInvokeTimeout = 200;

            Loaded += OnLoaded;
            Unloaded += OnUnloaded;
        }

        protected virtual void OnLoaded(object sender, RoutedEventArgs e)
        {
            try
            {
                if (_d3dDevice == null)
                {
                    InitDevice();
                }
            }
            catch
            {
            }
        }

        protected virtual void OnUnloaded(object sender, RoutedEventArgs e)
        {
            Dispose();
        }

        #region properties

        #region D3dHostingPanel
        private System.Windows.Forms.Panel _d3dHostingPanel;
        protected System.Windows.Forms.Panel D3dHostingPanel
        {
            get
            {
                if (_d3dHostingPanel == null)
                {
                    _d3dHostingPanel = new System.Windows.Forms.Panel();

                    int surfaceWidth = (int)D3dSurfaceWidth;
                    int surfaceHeight = (int)D3dSurfaceHeight;

                    _d3dHostingPanel.Width = (surfaceWidth > 0) ? surfaceWidth : 1;
                    _d3dHostingPanel.Height = (surfaceHeight > 0) ? surfaceHeight : 1;
                }

                return _d3dHostingPanel;
            }
        }
        #endregion

        #region D3dDevice
        private Device _d3dDevice;
        public Device D3dDevice
        {
            get
            {
                if (_d3dDevice == null)
                {
                    InitDevice();
                }

                return _d3dDevice;
            }
        }

        protected virtual void InitDevice()
        {
            try
            {
                ReleaseDevice();

                PresentParameters presentParams = new PresentParameters();
                presentParams.Windowed = true;
                presentParams.SwapEffect = D3dSwapEffect;
                presentParams.EnableAutoDepthStencil = D3dEnableAutoDepthStencil;
                presentParams.AutoDepthStencilFormat = D3dAutoDepthStencilFormat;

                _d3dDevice = new Device(0, D3dDeviceType, D3dHostingPanel, D3dCreateFlags, presentParams);
            }
            catch (Exception ex)
            {
                // Raise a failure event.
                D3dDeviceFailureEventArgs initFailureArgs =
                    new D3dDeviceFailureEventArgs(D3dDeviceInitFailureEvent)
                    {
                        FailureException = ex
                    };
                RaiseEvent(initFailureArgs);
            }
        }

        protected virtual void ReleaseDevice()
        {
            if (_d3dDevice != null)
            {
                try
                {
                    _d3dDevice.Dispose();
                }
                catch
                {
                }
                finally
                {
                    _d3dDevice = null;
                }
            }
        }

        protected void HandleD3dDeviceFailure(Exception ex)
        {
            D3dDeviceFailureEventArgs beforeFailureArgs =
                new D3dDeviceFailureEventArgs(BeforeHandlingD3dDeviceFailureEvent)
                {
                    FailureException = ex
                };
            RaiseEvent(beforeFailureArgs);

            OnD3dDeviceFailure(ex);

            D3dDeviceFailureEventArgs afterFailureArgs =
                new D3dDeviceFailureEventArgs(AfterHandlingD3dDeviceFailureEvent)
                {
                    FailureException = ex
                };
            RaiseEvent(afterFailureArgs);
        }

        protected virtual void OnD3dDeviceFailure(Exception ex)
        {
            // Restart the device.
            InitDevice();
        }
        #endregion

        #region D3dDeviceType
        public DeviceType D3dDeviceType
        {
            get { return (DeviceType)GetValue(D3dDeviceTypeProperty); }
            set { SetValue(D3dDeviceTypeProperty, value); }
        }

        public static readonly DependencyProperty D3dDeviceTypeProperty =
            DependencyProperty.Register("D3dDeviceType", typeof(DeviceType), typeof(D3dHost),
                new UIPropertyMetadata(DeviceType.Hardware, OnD3dDeviceTypeChanged));

        private static void OnD3dDeviceTypeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            D3dHost dh = sender as D3dHost;
            if (dh == null)
            {
                return;
            }

            if (dh._d3dDevice != null)
            {
                // The device has been created with a different value. So, recreate it.
                dh.InitDevice();
            }
        }
        #endregion

        #region D3dCreateFlags
        public CreateFlags D3dCreateFlags
        {
            get { return (CreateFlags)GetValue(D3dCreateFlagsProperty); }
            set { SetValue(D3dCreateFlagsProperty, value); }
        }

        public static readonly DependencyProperty D3dCreateFlagsProperty =
            DependencyProperty.Register("D3dCreateFlags", typeof(CreateFlags), typeof(D3dHost),
                new UIPropertyMetadata(CreateFlags.SoftwareVertexProcessing, OnD3dCreateFlagsChanged));

        private static void OnD3dCreateFlagsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            D3dHost dh = sender as D3dHost;
            if (dh == null)
            {
                return;
            }

            if (dh._d3dDevice != null)
            {
                // The device has been created with a different value. So, recreate it.
                dh.InitDevice();
            }
        }
        #endregion

        #region D3dSwapEffect
        public SwapEffect D3dSwapEffect
        {
            get { return (SwapEffect)GetValue(D3dSwapEffectProperty); }
            set { SetValue(D3dSwapEffectProperty, value); }
        }

        public static readonly DependencyProperty D3dSwapEffectProperty =
            DependencyProperty.Register("D3dSwapEffect", typeof(SwapEffect), typeof(D3dHost),
                new UIPropertyMetadata(SwapEffect.Discard, OnD3dSwapEffectChanged));

        private static void OnD3dSwapEffectChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            D3dHost dh = sender as D3dHost;
            if (dh == null)
            {
                return;
            }

            if (dh._d3dDevice != null)
            {
                // The device has been created with a different value. So, recreate it.
                dh.InitDevice();
            }
        }
        #endregion

        #region D3dEnableAutoDepthStencil
        public bool D3dEnableAutoDepthStencil
        {
            get { return (bool)GetValue(D3dEnableAutoDepthStencilProperty); }
            set { SetValue(D3dEnableAutoDepthStencilProperty, value); }
        }

        public static readonly DependencyProperty D3dEnableAutoDepthStencilProperty =
            DependencyProperty.Register("D3dEnableAutoDepthStencil", typeof(bool), typeof(D3dHost),
                new UIPropertyMetadata(true, OnD3dEnableAutoDepthStencilChanged));

        private static void OnD3dEnableAutoDepthStencilChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            D3dHost dh = sender as D3dHost;
            if (dh == null)
            {
                return;
            }

            if (dh._d3dDevice != null)
            {
                // The device has been created with a different value. So, recreate it.
                dh.InitDevice();
            }
        }
        #endregion

        #region D3dAutoDepthStencilFormat
        public DepthFormat D3dAutoDepthStencilFormat
        {
            get { return (DepthFormat)GetValue(D3dAutoDepthStencilFormatProperty); }
            set { SetValue(D3dAutoDepthStencilFormatProperty, value); }
        }

        public static readonly DependencyProperty D3dAutoDepthStencilFormatProperty =
            DependencyProperty.Register("D3dAutoDepthStencilFormat", typeof(DepthFormat), typeof(D3dHost),
            new UIPropertyMetadata(DepthFormat.D16, OnD3dAutoDepthStencilFormatChanged));

        private static void OnD3dAutoDepthStencilFormatChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            D3dHost dh = sender as D3dHost;
            if (dh == null)
            {
                return;
            }

            if (dh._d3dDevice != null)
            {
                // The device has been created with a different value. So, recreate it.
                dh.InitDevice();
            }
        }
        #endregion

        #region D3dSurfaceWidth
        public double D3dSurfaceWidth
        {
            get { return (double)GetValue(D3dSurfaceWidthProperty); }
            set { SetValue(D3dSurfaceWidthProperty, value); }
        }

        public static readonly DependencyProperty D3dSurfaceWidthProperty =
            DependencyProperty.Register("D3dSurfaceWidth", typeof(double), typeof(D3dHost),
                new UIPropertyMetadata(1000.0, OnD3dSurfaceWidthChanged));

        private static void OnD3dSurfaceWidthChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            D3dHost dh = sender as D3dHost;
            if (dh == null)
            {
                return;
            }

            dh.UpdateDeviceWidth();
        }

        private void UpdateDeviceWidth()
        {
            int surfaceWidth = (int)D3dSurfaceWidth;
            D3dHostingPanel.Width = (surfaceWidth > 0) ? surfaceWidth : 1;
        }
        #endregion

        #region D3dSurfaceHeight
        public double D3dSurfaceHeight
        {
            get { return (double)GetValue(D3dSurfaceHeightProperty); }
            set { SetValue(D3dSurfaceHeightProperty, value); }
        }

        public static readonly DependencyProperty D3dSurfaceHeightProperty =
            DependencyProperty.Register("D3dSurfaceHeight", typeof(double), typeof(D3dHost),
                new UIPropertyMetadata(1000.0, OnD3dSurfaceHeightChanged));

        private static void OnD3dSurfaceHeightChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            D3dHost dh = sender as D3dHost;
            if (dh == null)
            {
                return;
            }

            dh.UpdateDeviceHeight();
        }

        private void UpdateDeviceHeight()
        {
            int surfaceHeight = (int)D3dSurfaceHeight;
            D3dHostingPanel.Height = (surfaceHeight > 0) ? surfaceHeight : 1;
        }
        #endregion

        #region D3dRegionActualWidth
        public double D3dRegionActualWidth
        {
            get { return (double)GetValue(D3dRegionActualWidthProperty); }
            private set { SetValue(D3dRegionActualWidthProperty, value); }
        }

        public static readonly DependencyProperty D3dRegionActualWidthProperty =
            DependencyProperty.Register("D3dRegionActualWidth", typeof(double), typeof(D3dHost),
                new UIPropertyMetadata(0.0));
        #endregion

        #region D3dRegionActualHeight
        public double D3dRegionActualHeight
        {
            get { return (double)GetValue(D3dRegionActualHeightProperty); }
            private set { SetValue(D3dRegionActualHeightProperty, value); }
        }

        public static readonly DependencyProperty D3dRegionActualHeightProperty =
            DependencyProperty.Register("D3dRegionActualHeight", typeof(double), typeof(D3dHost),
                new UIPropertyMetadata(0.0));
        #endregion

        #region D3dRegion
        protected Rectangle D3dRegion { get { return _d3dRegion; } }
        #endregion

        #region TryUseD3DImageBeforeUsingMemory
        public bool TryUseD3DImageBeforeUsingMemory { get; set; }
        #endregion

        #region TryUseMemoryBeforeUsingFilesSystem
        public bool TryUseMemoryBeforeUsingFilesSystem { get; set; }
        #endregion

        #region FreeMemoryBeforeUpdateD3dRegion
        public bool FreeMemoryBeforeUpdateD3dRegion { get; set; }
        #endregion

        #region MillisecondsForDispatcherInvokeTimeout
        public double MillisecondsForDispatcherInvokeTimeout { get; set; }
        #endregion

        #endregion

        #region Events

        #region BeforeHandlingD3dDeviceFailure
        public static readonly RoutedEvent BeforeHandlingD3dDeviceFailureEvent = EventManager.RegisterRoutedEvent(
            "BeforeHandlingD3dDeviceFailure", RoutingStrategy.Bubble, typeof(D3dDeviceFailureEventHandler), typeof(D3dHost));

        public event D3dDeviceFailureEventHandler BeforeHandlingD3dDeviceFailure
        {
            add { AddHandler(BeforeHandlingD3dDeviceFailureEvent, value); }
            remove { RemoveHandler(BeforeHandlingD3dDeviceFailureEvent, value); }
        }
        #endregion

        #region AfterHandlingD3dDeviceFailure
        public static readonly RoutedEvent AfterHandlingD3dDeviceFailureEvent = EventManager.RegisterRoutedEvent(
            "AfterHandlingD3dDeviceFailure", RoutingStrategy.Bubble, typeof(D3dDeviceFailureEventHandler), typeof(D3dHost));

        public event D3dDeviceFailureEventHandler AfterHandlingD3dDeviceFailure
        {
            add { AddHandler(AfterHandlingD3dDeviceFailureEvent, value); }
            remove { RemoveHandler(AfterHandlingD3dDeviceFailureEvent, value); }
        }
        #endregion

        #region D3dDeviceInitFailure
        public static readonly RoutedEvent D3dDeviceInitFailureEvent = EventManager.RegisterRoutedEvent(
            "D3dDeviceInitFailure", RoutingStrategy.Bubble, typeof(D3dDeviceFailureEventHandler), typeof(D3dHost));

        public event D3dDeviceFailureEventHandler D3dDeviceInitFailure
        {
            add { AddHandler(D3dDeviceInitFailureEvent, value); }
            remove { RemoveHandler(D3dDeviceInitFailureEvent, value); }
        }
        #endregion

        #region D3dRegionSizeChanged
        public static readonly RoutedEvent D3dRegionSizeChangedEvent = EventManager.RegisterRoutedEvent(
            "D3dRegionSizeChanged", RoutingStrategy.Bubble, typeof(SizeChangedEventHandler), typeof(D3dHost));

        public event SizeChangedEventHandler D3dRegionSizeChanged
        {
            add { AddHandler(D3dRegionSizeChangedEvent, value); }
            remove { RemoveHandler(D3dRegionSizeChangedEvent, value); }
        }
        #endregion

        #region D3dSurfaceGotMouseCapture
        public static readonly RoutedEvent D3dSurfaceGotMouseCaptureEvent = EventManager.RegisterRoutedEvent(
            "D3dSurfaceGotMouseCapture", RoutingStrategy.Bubble, typeof(D3dSurfaceMouseEventHandler), typeof(D3dHost));

        public event D3dSurfaceMouseEventHandler D3dSurfaceGotMouseCapture
        {
            add { AddHandler(D3dSurfaceGotMouseCaptureEvent, value); }
            remove { RemoveHandler(D3dSurfaceGotMouseCaptureEvent, value); }
        }
        #endregion

        #region D3dSurfaceLostMouseCapture
        public static readonly RoutedEvent D3dSurfaceLostMouseCaptureEvent = EventManager.RegisterRoutedEvent(
            "D3dSurfaceLostMouseCapture", RoutingStrategy.Bubble, typeof(D3dSurfaceMouseEventHandler), typeof(D3dHost));

        public event D3dSurfaceMouseEventHandler D3dSurfaceLostMouseCapture
        {
            add { AddHandler(D3dSurfaceLostMouseCaptureEvent, value); }
            remove { RemoveHandler(D3dSurfaceLostMouseCaptureEvent, value); }
        }
        #endregion

        #region D3dSurfaceMouseEnter
        public static readonly RoutedEvent D3dSurfaceMouseEnterEvent = EventManager.RegisterRoutedEvent(
            "D3dSurfaceMouseEnter", RoutingStrategy.Bubble, typeof(D3dSurfaceMouseEventHandler), typeof(D3dHost));

        public event D3dSurfaceMouseEventHandler D3dSurfaceMouseEnter
        {
            add { AddHandler(D3dSurfaceMouseEnterEvent, value); }
            remove { RemoveHandler(D3dSurfaceMouseEnterEvent, value); }
        }
        #endregion

        #region D3dSurfaceMouseLeave
        public static readonly RoutedEvent D3dSurfaceMouseLeaveEvent = EventManager.RegisterRoutedEvent(
            "D3dSurfaceMouseLeave", RoutingStrategy.Bubble, typeof(D3dSurfaceMouseEventHandler), typeof(D3dHost));

        public event D3dSurfaceMouseEventHandler D3dSurfaceMouseLeave
        {
            add { AddHandler(D3dSurfaceMouseLeaveEvent, value); }
            remove { RemoveHandler(D3dSurfaceMouseLeaveEvent, value); }
        }
        #endregion

        #region D3dSurfaceMouseMove
        public static readonly RoutedEvent D3dSurfaceMouseMoveEvent = EventManager.RegisterRoutedEvent(
            "D3dSurfaceMouseMove", RoutingStrategy.Bubble, typeof(D3dSurfaceMouseEventHandler), typeof(D3dHost));

        public event D3dSurfaceMouseEventHandler D3dSurfaceMouseMove
        {
            add { AddHandler(D3dSurfaceMouseMoveEvent, value); }
            remove { RemoveHandler(D3dSurfaceMouseMoveEvent, value); }
        }
        #endregion

        #region D3dSurfaceMouseDown
        public static readonly RoutedEvent D3dSurfaceMouseDownEvent = EventManager.RegisterRoutedEvent(
            "D3dSurfaceMouseDown", RoutingStrategy.Bubble, typeof(D3dSurfaceMouseButtonEventHandler), typeof(D3dHost));

        public event D3dSurfaceMouseButtonEventHandler D3dSurfaceMouseDown
        {
            add { AddHandler(D3dSurfaceMouseDownEvent, value); }
            remove { RemoveHandler(D3dSurfaceMouseDownEvent, value); }
        }
        #endregion

        #region D3dSurfaceMouseLeftButtonDown
        public static readonly RoutedEvent D3dSurfaceMouseLeftButtonDownEvent = EventManager.RegisterRoutedEvent(
            "D3dSurfaceMouseLeftButtonDown", RoutingStrategy.Bubble, typeof(D3dSurfaceMouseButtonEventHandler), typeof(D3dHost));

        public event D3dSurfaceMouseButtonEventHandler D3dSurfaceMouseLeftButtonDown
        {
            add { AddHandler(D3dSurfaceMouseLeftButtonDownEvent, value); }
            remove { RemoveHandler(D3dSurfaceMouseLeftButtonDownEvent, value); }
        }
        #endregion

        #region D3dSurfaceMouseLeftButtonUp
        public static readonly RoutedEvent D3dSurfaceMouseLeftButtonUpEvent = EventManager.RegisterRoutedEvent(
            "D3dSurfaceMouseLeftButtonUp", RoutingStrategy.Bubble, typeof(D3dSurfaceMouseButtonEventHandler), typeof(D3dHost));

        public event D3dSurfaceMouseButtonEventHandler D3dSurfaceMouseLeftButtonUp
        {
            add { AddHandler(D3dSurfaceMouseLeftButtonUpEvent, value); }
            remove { RemoveHandler(D3dSurfaceMouseLeftButtonUpEvent, value); }
        }
        #endregion

        #region D3dSurfaceMouseRightButtonDown
        public static readonly RoutedEvent D3dSurfaceMouseRightButtonDownEvent = EventManager.RegisterRoutedEvent(
            "D3dSurfaceMouseRightButtonDown", RoutingStrategy.Bubble, typeof(D3dSurfaceMouseButtonEventHandler), typeof(D3dHost));

        public event D3dSurfaceMouseButtonEventHandler D3dSurfaceMouseRightButtonDown
        {
            add { AddHandler(D3dSurfaceMouseRightButtonDownEvent, value); }
            remove { RemoveHandler(D3dSurfaceMouseRightButtonDownEvent, value); }
        }
        #endregion

        #region D3dSurfaceMouseRightButtonUp
        public static readonly RoutedEvent D3dSurfaceMouseRightButtonUpEvent = EventManager.RegisterRoutedEvent(
            "D3dSurfaceMouseRightButtonUp", RoutingStrategy.Bubble, typeof(D3dSurfaceMouseButtonEventHandler), typeof(D3dHost));

        public event D3dSurfaceMouseButtonEventHandler D3dSurfaceMouseRightButtonUp
        {
            add { AddHandler(D3dSurfaceMouseRightButtonUpEvent, value); }
            remove { RemoveHandler(D3dSurfaceMouseRightButtonUpEvent, value); }
        }
        #endregion

        #region D3dSurfaceMouseUp
        public static readonly RoutedEvent D3dSurfaceMouseUpEvent = EventManager.RegisterRoutedEvent(
            "D3dSurfaceMouseUp", RoutingStrategy.Bubble, typeof(D3dSurfaceMouseButtonEventHandler), typeof(D3dHost));

        public event D3dSurfaceMouseButtonEventHandler D3dSurfaceMouseUp
        {
            add { AddHandler(D3dSurfaceMouseUpEvent, value); }
            remove { RemoveHandler(D3dSurfaceMouseUpEvent, value); }
        }
        #endregion

        #region D3dSurfaceMouseWheel
        public static readonly RoutedEvent D3dSurfaceMouseWheelEvent = EventManager.RegisterRoutedEvent(
            "D3dSurfaceMouseWheel", RoutingStrategy.Bubble, typeof(D3dSurfaceMouseWheelEventHandler), typeof(D3dHost));

        public event D3dSurfaceMouseWheelEventHandler D3dSurfaceMouseWheel
        {
            add { AddHandler(D3dSurfaceMouseWheelEvent, value); }
            remove { RemoveHandler(D3dSurfaceMouseWheelEvent, value); }
        }
        #endregion

        #region PreviewD3dSurfaceMouseDown
        public static readonly RoutedEvent PreviewD3dSurfaceMouseDownEvent = EventManager.RegisterRoutedEvent(
            "PreviewD3dSurfaceMouseDown", RoutingStrategy.Tunnel, typeof(D3dSurfaceMouseButtonEventHandler), typeof(D3dHost));

        public event D3dSurfaceMouseButtonEventHandler PreviewD3dSurfaceMouseDown
        {
            add { AddHandler(PreviewD3dSurfaceMouseDownEvent, value); }
            remove { RemoveHandler(PreviewD3dSurfaceMouseDownEvent, value); }
        }
        #endregion

        #region PreviewD3dSurfaceMouseLeftButtonDown
        public static readonly RoutedEvent PreviewD3dSurfaceMouseLeftButtonDownEvent = EventManager.RegisterRoutedEvent(
            "PreviewD3dSurfaceMouseLeftButtonDown", RoutingStrategy.Tunnel, typeof(D3dSurfaceMouseButtonEventHandler), typeof(D3dHost));

        public event D3dSurfaceMouseButtonEventHandler PreviewD3dSurfaceMouseLeftButtonDown
        {
            add { AddHandler(PreviewD3dSurfaceMouseLeftButtonDownEvent, value); }
            remove { RemoveHandler(PreviewD3dSurfaceMouseLeftButtonDownEvent, value); }
        }
        #endregion

        #region PreviewD3dSurfaceMouseLeftButtonUp
        public static readonly RoutedEvent PreviewD3dSurfaceMouseLeftButtonUpEvent = EventManager.RegisterRoutedEvent(
            "PreviewD3dSurfaceMouseLeftButtonUp", RoutingStrategy.Tunnel, typeof(D3dSurfaceMouseButtonEventHandler), typeof(D3dHost));

        public event D3dSurfaceMouseButtonEventHandler PreviewD3dSurfaceMouseLeftButtonUp
        {
            add { AddHandler(PreviewD3dSurfaceMouseLeftButtonUpEvent, value); }
            remove { RemoveHandler(PreviewD3dSurfaceMouseLeftButtonUpEvent, value); }
        }
        #endregion

        #region PreviewD3dSurfaceMouseMove
        public static readonly RoutedEvent PreviewD3dSurfaceMouseMoveEvent = EventManager.RegisterRoutedEvent(
            "PreviewD3dSurfaceMouseMove", RoutingStrategy.Tunnel, typeof(D3dSurfaceMouseEventHandler), typeof(D3dHost));

        public event D3dSurfaceMouseEventHandler PreviewD3dSurfaceMouseMove
        {
            add { AddHandler(PreviewD3dSurfaceMouseMoveEvent, value); }
            remove { RemoveHandler(PreviewD3dSurfaceMouseMoveEvent, value); }
        }
        #endregion

        #region PreviewD3dSurfaceMouseRightButtonDown
        public static readonly RoutedEvent PreviewD3dSurfaceMouseRightButtonDownEvent = EventManager.RegisterRoutedEvent(
            "PreviewD3dSurfaceMouseRightButtonDown", RoutingStrategy.Tunnel, typeof(D3dSurfaceMouseButtonEventHandler), typeof(D3dHost));

        public event D3dSurfaceMouseButtonEventHandler PreviewD3dSurfaceMouseRightButtonDown
        {
            add { AddHandler(PreviewD3dSurfaceMouseRightButtonDownEvent, value); }
            remove { RemoveHandler(PreviewD3dSurfaceMouseRightButtonDownEvent, value); }
        }
        #endregion

        #region PreviewD3dSurfaceMouseRightButtonUp
        public static readonly RoutedEvent PreviewD3dSurfaceMouseRightButtonUpEvent = EventManager.RegisterRoutedEvent(
            "PreviewD3dSurfaceMouseRightButtonUp", RoutingStrategy.Tunnel, typeof(D3dSurfaceMouseButtonEventHandler), typeof(D3dHost));

        public event D3dSurfaceMouseButtonEventHandler PreviewD3dSurfaceMouseRightButtonUp
        {
            add { AddHandler(PreviewD3dSurfaceMouseRightButtonUpEvent, value); }
            remove { RemoveHandler(PreviewD3dSurfaceMouseRightButtonUpEvent, value); }
        }
        #endregion

        #region PreviewD3dSurfaceMouseUp
        public static readonly RoutedEvent PreviewD3dSurfaceMouseUpEvent = EventManager.RegisterRoutedEvent(
            "PreviewD3dSurfaceMouseUp", RoutingStrategy.Tunnel, typeof(D3dSurfaceMouseButtonEventHandler), typeof(D3dHost));

        public event D3dSurfaceMouseButtonEventHandler PreviewD3dSurfaceMouseUp
        {
            add { AddHandler(PreviewD3dSurfaceMouseUpEvent, value); }
            remove { RemoveHandler(PreviewD3dSurfaceMouseUpEvent, value); }
        }
        #endregion

        #region PreviewD3dSurfaceMouseWheel
        public static readonly RoutedEvent PreviewD3dSurfaceMouseWheelEvent = EventManager.RegisterRoutedEvent(
            "PreviewD3dSurfaceMouseWheel", RoutingStrategy.Tunnel, typeof(D3dSurfaceMouseWheelEventHandler), typeof(D3dHost));

        public event D3dSurfaceMouseWheelEventHandler PreviewD3dSurfaceMouseWheel
        {
            add { AddHandler(PreviewD3dSurfaceMouseWheelEvent, value); }
            remove { RemoveHandler(PreviewD3dSurfaceMouseWheelEvent, value); }
        }
        #endregion

        #endregion

        public void BeginDrawing()
        {
            // Lock the D3dHostingPanel, for ensuring that the scene is fully rendered.
            Monitor.Enter(D3dHostingPanel);
        }

        public void EndDrawing()
        {
            // Unlock the D3dHostingPanel, for letting the scene to be presented.
            Monitor.Exit(D3dHostingPanel);

            // Present the scene.
            InvalidateD3dRegion();
        }

        #region InvalidateD3dRegion

        public void InvalidateD3dRegion()
        {
            if (_d3dRegion == null)
            {
                return;
            }

            // Start the thread that updates the Fill of the 3D region, if it is needed.
            if (_updateD3dRegionThread == null)
            {
                StartUpdateD3dRegionThread();
            }

            // Indicate that the Fill of the 3D region is invalid.
            _updateD3dRegionEvent.Set();
        }

        #region UpdateD3dRegion

        private Thread _updateD3dRegionThread = null;
        private bool _continueUpdateD3dRegionThread;
        private AutoResetEvent _updateD3dRegionEvent = new AutoResetEvent(false);
        private bool _isMemoryFreeNeeded = false;
        private bool? _isSetD3dRegionFillUsingD3DImageSupported = null;
        private bool? _isSetD3dRegionFillUsingMemorySupported = null;

        private void UpdateD3dRegion()
        {
            if (_d3dRegion == null)
            {
                return;
            }

            //Lock the D3dHostingPanel, for waiting to the scene to be fully rendered.
            Monitor.Enter(D3dHostingPanel);

            if (FreeMemoryBeforeUpdateD3dRegion || _isMemoryFreeNeeded)
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }

            try
            {
                // Get the device's back-buffer.
                Surface s = D3dDevice.GetBackBuffer(0, 0, BackBufferType.Mono);

                if (TryUseD3DImageBeforeUsingMemory && _isSetD3dRegionFillUsingD3DImageSupported != false)
                {
                    SetD3dRegionFillUsingD3DImage(s);
                }
                else if (TryUseMemoryBeforeUsingFilesSystem && _isSetD3dRegionFillUsingMemorySupported != false)
                {
                    SetD3dRegionFillUsingMemory(s);
                }
                else
                {
                    SetD3dRegionFillUsingFile(s);
                }
            }
            catch (Exception ex)
            {
                Dispatcher.Invoke(new ThreadStart(() => HandleD3dDeviceFailure(ex)),
                                  TimeSpan.FromMilliseconds(MillisecondsForDispatcherInvokeTimeout));
            }
            finally
            {
                // Unlock the D3dHostingPanel, for letting the scene to be rendered.
                Monitor.Exit(D3dHostingPanel);
            }
        }

        private void StartUpdateD3dRegionThread()
        {
            if (_updateD3dRegionThread == null)
            {
                _continueUpdateD3dRegionThread = true;

                _updateD3dRegionThread = new Thread(new ThreadStart(() =>
                {
                    while (_continueUpdateD3dRegionThread)
                    {
                        _updateD3dRegionEvent.WaitOne();

                        if (_continueUpdateD3dRegionThread)
                        {
                            UpdateD3dRegion();
                        }
                    }
                }));

                _updateD3dRegionThread.Start();
            }
        }

        private void StopUpdateD3dRegionThread()
        {
            if (_updateD3dRegionThread != null)
            {
                _continueUpdateD3dRegionThread = false;
                _updateD3dRegionEvent.Set();
                _updateD3dRegionThread.Join();
                _updateD3dRegionThread = null;
            }
        }

        #endregion

        #region SetD3dRegionFillUsingD3DImage

        // First approach for setting the Fill of the 3D region, can be using a D3DImage.
        // For presenting a DirectX scene using a D3DImage,
        // we can set the D3DImage's back-buffer with the surface's pointer. 
        // But, If we set the D3DImage (that holds the surface's pointer) as the Fill of the 3D region,
        // every change to the surface, can be reflected directly to the Fill of the 3D region.
        // Therefore, in order to update the Fill of the 3D region,
        // only when the scene has been completely rendered,
        // we set the Fill of the 3D region with a copy of the D3Dimage's back-buffer.
        // Since the D3DImage.CopyBackBuffer method is protected,
        // we create another class for getting the copy of the back-buffer (D3DImageEx).

        protected class D3DImageEx : D3DImage
        {
            public D3DImageEx()
            {
            }

            public D3DImageEx(double width, double height)
                : base(width, height)
            {
            }

            public BitmapSource GetBackBufferCopy()
            {
                return CopyBackBuffer();
            }
        }

        private void SetD3dRegionFillUsingD3DImage(Surface s)
        {
            bool isD3dRegionUpdateNeeded = true;

            // Update the Fill of the 3D region, in the UI thread.
            Dispatcher.Invoke(
                new ThreadStart(() =>
                {
                    try
                    {
                        // Create a D3DImage that holds the surface's pointer.
                        D3DImageEx di = new D3DImageEx(D3dSurfaceWidth, D3dSurfaceHeight);
                        SetD3dImageBackBuffer(di, s);

                        // Set the Fill of the 3D region, to the D3DImage's back-buffer's copy.
                        BitmapSource bs = di.GetBackBufferCopy();
                        _d3dRegion.Fill = new ImageBrush(bs);

                        isD3dRegionUpdateNeeded = false;

                        _isSetD3dRegionFillUsingD3DImageSupported = true;
                    }
                    catch (Exception ex)
                    {
                        if (_isSetD3dRegionFillUsingD3DImageSupported != true)
                        {
                            // The update using D3DImage isn't supported...
                            _isSetD3dRegionFillUsingD3DImageSupported = false;
                        }
                        else
                        {
                            HandleD3dDeviceFailure(ex);
                            isD3dRegionUpdateNeeded = false;
                        }
                    }
                }), TimeSpan.FromMilliseconds(MillisecondsForDispatcherInvokeTimeout));

            if (isD3dRegionUpdateNeeded && _continueUpdateD3dRegionThread)
            {
                InvalidateD3dRegion();
            }
        }

        private void SetD3dImageBackBuffer(D3DImage di, Surface s)
        {
            if (di == null || s == null)
            {
                return;
            }

            IntPtr backBuffer;
            unsafe
            {
                backBuffer = new IntPtr(s.UnmanagedComPointer);
            }

            di.Lock();
            di.SetBackBuffer(D3DResourceType.IDirect3DSurface9, backBuffer);
            di.AddDirtyRect(new Int32Rect(0, 0, di.PixelWidth,
                di.PixelHeight));
            di.Unlock();
        }

        #endregion

        #region SetD3dRegionFillUsingMemory

        // A second approach for setting the Fill of the 3D region, can be using a buffer in the memory.

        private GraphicsStream _d3dGraphicsStream;

        private void SetD3dRegionFillUsingMemory(Surface s)
        {
            GraphicsStream oldGraphicsStream = _d3dGraphicsStream;

            // Store the back-buffer as an image in the memory.
            GraphicsStream newGraphicsStream = SurfaceLoader.SaveToStream(ImageFileFormat.Bmp, s);
            newGraphicsStream.Seek(0, System.IO.SeekOrigin.Begin);

            lock (_d3dRegion)
            {
                _d3dGraphicsStream = newGraphicsStream;
            }

            // Update the Fill of the 3D region, in the UI thread.
            Dispatcher.BeginInvoke(new ThreadStart(() =>
            {
                lock (_d3dRegion)
                {
                    if (_continueUpdateD3dRegionThread)
                    {
                        try
                        {
                            // Create an ImageSource that contains the image of the back-buffer.
                            BitmapImage bi = new BitmapImage();
                            bi.BeginInit();
                            bi.StreamSource = _d3dGraphicsStream;
                            bi.EndInit();

                            // Set the Fill of the 3D region to the image of the back-buffer.           
                            _d3dRegion.Fill = new ImageBrush(bi);

                            // The operation succeeded. So, it is supported.
                            _isSetD3dRegionFillUsingMemorySupported = true;
                        }
                        catch
                        {
                            if (_isSetD3dRegionFillUsingMemorySupported == true)
                            {
                                // There is a failure in the operation. But, it's supported.
                                // Maybe we have to free the memory of the unneeded GraphicsStream objects.
                                _isMemoryFreeNeeded = true;
                            }
                            else
                            {
                                // The update using the memory isn't supported...
                                _isSetD3dRegionFillUsingMemorySupported = false;
                            }

                            // This operation has failed. Give it another chance.
                            _updateD3dRegionEvent.Set();
                        }
                    }
                }
            }));

            ReleaseD3dGraphicsStream(oldGraphicsStream);
        }

        private void ReleaseD3dGraphicsStream(GraphicsStream d3dGraphicsStream)
        {
            if (d3dGraphicsStream != null)
            {
                d3dGraphicsStream.Close();
                d3dGraphicsStream.Close(); // Extra Close ( http://www.eggheadcafe.com/microsoft/Win32-DirectX-Managed/31961917/surfaceloadersavetostream-major-memory-leak.aspx )
            }
        }

        protected void ReleaseD3dRegionMemory()
        {
            if (_d3dRegion == null)
            {
                return;
            }

            lock (_d3dRegion)
            {
                if (_d3dGraphicsStream != null)
                {
                    ReleaseD3dGraphicsStream(_d3dGraphicsStream);
                    _d3dGraphicsStream = null;
                }
            }
        }
        #endregion

        #region SetD3dRegionFillUsingFile

        // A third approach for setting the Fill of the 3D region, can be using an image file.

        private string _currentD3dRegionFillFileName;

        #region UsedFileNames
        private List<string> _usedFileNames;
        public List<string> UsedFileNames
        {
            get { return _usedFileNames ?? (_usedFileNames = new List<string>()); }
        }
        #endregion

        private void SetD3dRegionFillUsingFile(Surface s)
        {
            // Get available file name, for the storing the back-buffer.
            string currD3dRegionFillFileName = GetAvailableFileName();

            // Save the back-buffer as a file.
            SurfaceLoader.Save(currD3dRegionFillFileName, ImageFileFormat.Jpg, s);

            lock (_d3dRegion)
            {
                _currentD3dRegionFillFileName = currD3dRegionFillFileName;

                // Store the back-buffer file's name, in order to delete it later.
                UsedFileNames.Add(currD3dRegionFillFileName);
            }

            // Update the Fill of the 3D region, in the UI thread.
            Dispatcher.BeginInvoke(new ThreadStart(() =>
            {
                lock (_d3dRegion)
                {
                    if (_continueUpdateD3dRegionThread)
                    {
                        try
                        {
                            // Set the Fill of the 3D region to the saved back-buffer's file.
                            _d3dRegion.Fill =
                                new ImageBrush(new BitmapImage(new Uri(_currentD3dRegionFillFileName, UriKind.Relative)));
                        }
                        catch
                        {
                        }
                    }
                }
            }));

            // Delete the used files except the last one (we use it as the current Fill...).
            DeleteD3dRegionFiles(false);
        }

        private string GetAvailableFileName()
        {
            string fileNameBegin = "MdxWpf";
            string fileNameEnd = ".jpg";

            int fileNameCounter = 1;

            string currFileName = string.Format("{0}{1}{2}",
                fileNameBegin, fileNameCounter.ToString(), fileNameEnd);

            while (File.Exists(currFileName) && fileNameCounter > 0)
            {
                fileNameCounter++;

                currFileName = string.Format("{0}{1}{2}",
                    fileNameBegin, fileNameCounter.ToString(), fileNameEnd);
            }

            return currFileName;
        }

        protected void DeleteD3dRegionFiles(bool deleteLastFile)
        {
            if (_d3dRegion == null)
            {
                return;
            }

            string[] usedFileNamesCopy = null;

            lock (_d3dRegion)
            {
                usedFileNamesCopy = UsedFileNames.ToArray();
            }

            int filesCount = usedFileNamesCopy.Length;

            if (filesCount < 1)
            {
                return;
            }

            if (!deleteLastFile)
            {
                filesCount--;
            }

            for (int fileInx = 0; fileInx < filesCount; fileInx++)
            {
                string currFileName = usedFileNamesCopy[fileInx];

                try
                {
                    if (File.Exists(currFileName))
                    {
                        File.Delete(currFileName);
                    }

                    lock (_d3dRegion)
                    {
                        UsedFileNames.Remove(currFileName);
                    }
                }
                catch
                {
                }
            }
        }
        #endregion

        protected void ReleaseD3dRegion()
        {
            StopUpdateD3dRegionThread();

            ReleaseD3dRegionMemory();

            DeleteD3dRegionFiles(true);
        }

        #endregion

        public override void OnApplyTemplate()
        {
            _d3dRegion = GetTemplateChild("PART_D3dRegion") as Rectangle;

            if (_d3dRegion != null)
            {
                D3dRegionActualWidth = _d3dRegion.ActualWidth;
                D3dRegionActualHeight = _d3dRegion.ActualHeight;

                _d3dRegion.SizeChanged += (s, e) =>
                {
                    D3dRegionActualWidth = _d3dRegion.ActualWidth;
                    D3dRegionActualHeight = _d3dRegion.ActualHeight;

                    // Raise the D3dRegionSizeChanged on the D3D region's size is changed.
                    e.RoutedEvent = D3dHost.D3dRegionSizeChangedEvent;
                    RaiseEvent(e);
                };

                RegisterD3dRegionMouseEvents();
            }

            base.OnApplyTemplate();
        }

        #region Mouse event-handlers
        private void RegisterD3dRegionMouseEvents()
        {
            if (_d3dRegion == null)
            {
                return;
            }

            _d3dRegion.GotMouseCapture += OnD3dRegionMouseEvent;
            _d3dRegion.LostMouseCapture += OnD3dRegionMouseEvent;
            _d3dRegion.MouseEnter += OnD3dRegionMouseEvent;
            _d3dRegion.MouseLeave += OnD3dRegionMouseEvent;
            _d3dRegion.MouseMove += OnD3dRegionMouseEvent;

            _d3dRegion.MouseDown += OnD3dRegionMouseButtonEvent;
            _d3dRegion.MouseLeftButtonDown += OnD3dRegionMouseButtonEvent;
            _d3dRegion.MouseLeftButtonUp += OnD3dRegionMouseButtonEvent;
            _d3dRegion.MouseRightButtonDown += OnD3dRegionMouseButtonEvent;
            _d3dRegion.MouseRightButtonUp += OnD3dRegionMouseButtonEvent;
            _d3dRegion.MouseUp += OnD3dRegionMouseButtonEvent;

            _d3dRegion.MouseWheel += OnD3dRegionMouseWheelEvent;

            _d3dRegion.PreviewMouseDown += OnD3dRegionMouseButtonEvent;
            _d3dRegion.PreviewMouseLeftButtonDown += OnD3dRegionMouseButtonEvent;
            _d3dRegion.PreviewMouseLeftButtonUp += OnD3dRegionMouseButtonEvent;
            _d3dRegion.PreviewMouseMove += OnD3dRegionMouseEvent;
            _d3dRegion.PreviewMouseRightButtonDown += OnD3dRegionMouseButtonEvent;
            _d3dRegion.PreviewMouseRightButtonUp += OnD3dRegionMouseButtonEvent;
            _d3dRegion.PreviewMouseUp += OnD3dRegionMouseButtonEvent;
            _d3dRegion.PreviewMouseWheel += OnD3dRegionMouseWheelEvent;
        }

        private void OnD3dRegionMouseWheelEvent(object sender, MouseWheelEventArgs e)
        {
            RoutedEvent d3dSurfaceMouseEvent = GetD3dSurfaceMouseEvent(e);

            if (d3dSurfaceMouseEvent != null)
            {
                D3dSurfaceMouseWheelEventArgs d3dSurfaceEventArgs =
                    new D3dSurfaceMouseWheelEventArgs(d3dSurfaceMouseEvent)
                    {
                        MouseWheelEventArgs = e,
                        D3dSurfaceMousePosition = GetD3dSurfaceMousePosition(e)
                    };

                RaiseEvent(d3dSurfaceEventArgs);
            }
        }

        private void OnD3dRegionMouseButtonEvent(object sender, MouseButtonEventArgs e)
        {
            RoutedEvent d3dSurfaceMouseEvent = GetD3dSurfaceMouseEvent(e);

            if (d3dSurfaceMouseEvent != null)
            {
                D3dSurfaceMouseButtonEventArgs d3dSurfaceEventArgs =
                    new D3dSurfaceMouseButtonEventArgs(d3dSurfaceMouseEvent)
                    {
                        MouseButtonEventArgs = e,
                        D3dSurfaceMousePosition = GetD3dSurfaceMousePosition(e)
                    };

                RaiseEvent(d3dSurfaceEventArgs);
            }
        }

        private void OnD3dRegionMouseEvent(object sender, MouseEventArgs e)
        {
            RoutedEvent d3dSurfaceMouseEvent = GetD3dSurfaceMouseEvent(e);

            if (d3dSurfaceMouseEvent != null)
            {
                D3dSurfaceMouseEventArgs d3dSurfaceEventArgs =
                    new D3dSurfaceMouseEventArgs(d3dSurfaceMouseEvent)
                    {
                        MouseEventArgs = e,
                        D3dSurfaceMousePosition = GetD3dSurfaceMousePosition(e)
                    };

                RaiseEvent(d3dSurfaceEventArgs);
            }
        }

        private Point GetD3dSurfaceMousePosition(MouseEventArgs mouseArgs)
        {
            // Get the mouse position on the 3D region.
            Point d3dRegionMousePosition = mouseArgs.GetPosition(_d3dRegion);

            // Calculate the mouse position on the MDX surface.
            Point d3dSurfaceMousePosition =
                new Point(d3dRegionMousePosition.X * D3dSurfaceWidth / D3dRegionActualWidth,
                    d3dRegionMousePosition.Y * D3dSurfaceHeight / D3dRegionActualHeight);

            return d3dSurfaceMousePosition;
        }

        private RoutedEvent GetD3dSurfaceMouseEvent(MouseEventArgs mouseArgs)
        {
            string d3dRegionEventName = mouseArgs.RoutedEvent.Name;

            string d3dSurfaceEventName;
            if (d3dRegionEventName.StartsWith("Preview"))
            {
                d3dSurfaceEventName = "PreviewD3dSurface" + d3dRegionEventName.Substring(7);
            }
            else
            {
                d3dSurfaceEventName = "D3dSurface" + d3dRegionEventName;
            }

            RoutedEvent d3dSurfaceMouseEvent =
                EventManager.GetRoutedEvents().FirstOrDefault(re => re.OwnerType == typeof(D3dHost) && re.Name == d3dSurfaceEventName);

            return d3dSurfaceMouseEvent;
        }
        #endregion

        static D3dHost()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(D3dHost), new FrameworkPropertyMetadata(typeof(D3dHost)));
        }

        ~D3dHost()
        {
            Dispose();
        }

        #region IDisposable implementation
        public virtual void Dispose()
        {
            ReleaseD3dRegion();

            ReleaseDevice();
        }
        #endregion
    }
}

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
Software Developer
Israel Israel
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions