diff --git a/GraphicsDeviceService.cs b/GraphicsDeviceService.cs
new file mode 100644
index 0000000..a995a17
--- /dev/null
+++ b/GraphicsDeviceService.cs
@@ -0,0 +1,111 @@
+///
+/// Helper class responsible for creating and managing the GraphicsDevice.
+/// All GraphicsDeviceControl instances share the same GraphicsDeviceService,
+/// so even though there can be many controls, there will only ever be a
+/// single underlying GraphicsDevice. This implements the standard
+/// IGraphicsDeviceService interface, which provides notification events for
+/// when the device is reset or disposed.
+///
+public class GraphicsDeviceService : IGraphicsDeviceService
+{
+ // Singleton device service instance.
+ private static GraphicsDeviceService singletonInstance;
+
+ // Keep track of how many controls are sharing the singletonInstance.
+ private static int referenceCount;
+
+ ///
+ /// Gets the single instance of the service class for the application.
+ ///
+ public static GraphicsDeviceService Instance
+ {
+ get
+ {
+ if (singletonInstance == null)
+ singletonInstance = new GraphicsDeviceService();
+ return singletonInstance;
+ }
+ }
+
+ // Store the current device settings.
+ private PresentationParameters parameters;
+
+ ///
+ /// Gets the current graphics device.
+ ///
+ public GraphicsDevice GraphicsDevice { get; private set; }
+
+ // IGraphicsDeviceService events.
+ public event EventHandler DeviceCreated;
+ public event EventHandler DeviceDisposing;
+ public event EventHandler DeviceReset;
+ public event EventHandler DeviceResetting;
+
+ ///
+ /// Constructor is private, because this is a singleton class:
+ /// client controls should use the public AddRef method instead.
+ ///
+ GraphicsDeviceService() { }
+
+ ///
+ /// Creates the GraphicsDevice for the service.
+ ///
+ private void CreateDevice(IntPtr windowHandle)
+ {
+ parameters = new PresentationParameters();
+
+ // since we're using render targets anyway, the
+ // backbuffer size is somewhat irrelevant
+ parameters.BackBufferWidth = 480;
+ parameters.BackBufferHeight = 320;
+ parameters.BackBufferFormat = SurfaceFormat.Color;
+ parameters.DeviceWindowHandle = windowHandle;
+ parameters.DepthStencilFormat = DepthFormat.Depth24Stencil8;
+ parameters.IsFullScreen = false;
+
+ GraphicsDevice = new GraphicsDevice(
+ GraphicsAdapter.DefaultAdapter,
+ GraphicsProfile.HiDef,
+ parameters);
+
+ if (DeviceCreated != null)
+ DeviceCreated(this, EventArgs.Empty);
+ }
+
+ ///
+ /// Gets a reference to the singleton instance.
+ ///
+ public static GraphicsDeviceService AddRef(IntPtr windowHandle)
+ {
+ // Increment the "how many controls sharing the device"
+ // reference count.
+ if (Interlocked.Increment(ref referenceCount) == 1)
+ {
+ // If this is the first control to start using the
+ // device, we must create the device.
+ Instance.CreateDevice(windowHandle);
+ }
+
+ return singletonInstance;
+ }
+
+ ///
+ /// Releases a reference to the singleton instance.
+ ///
+ public void Release()
+ {
+ // Decrement the "how many controls sharing the device"
+ // reference count.
+ if (Interlocked.Decrement(ref referenceCount) == 0)
+ {
+ // If this is the last control to finish using the
+ // device, we should dispose the singleton instance.
+ if (DeviceDisposing != null)
+ DeviceDisposing(this, EventArgs.Empty);
+
+ GraphicsDevice.Dispose();
+
+ GraphicsDevice = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..b83bf6e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2010 Nick Gravelyn, Microsoft
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5499391
--- /dev/null
+++ b/README.md
@@ -0,0 +1,135 @@
+# Overview
+
+This is a repository preserving example code by [Nick Gravelyn](https://www.nickgravelyn.com/) did some blog post on XNA in the Microsoft Docs. As long as it's there, the original post can be found in the [Microsoft Docs Blog Archive](https://docs.microsoft.com/en-us/archive/blogs/nicgrave/rendering-with-xna-framework-4-0-inside-of-a-wpf-application). Originally there was no license included. By mail and in [this issue at MerjTek's WPF integration](https://github.com/MerjTek/MerjTek.WpfIntegration/issues/1#issuecomment-902622749) he kindly answered the code may be considered MIT or Microsoft Public License, so I put it here under MIT.
+
+This repository allows for using the code without legal concerns which may occur with the missing license in the blog post.
+
+
+# Example Code
+
+There was more example code included in the blogpost, which is preserved here.
+
+
+## Example 1
+
+```csharp
+ // What importers or processors should we load?
+private const string xnaVersion =
+ ", Version=4.0.0.0, PublicKeyToken=6d5c3888ef60e27d";
+private static readonly string[] pipelineAssemblies =
+{
+ "Microsoft.Xna.Framework.Content.Pipeline.AudioImporters" + xnaVersion,
+ "Microsoft.Xna.Framework.Content.Pipeline.EffectImporter" + xnaVersion,
+ "Microsoft.Xna.Framework.Content.Pipeline.FBXImporter" + xnaVersion,
+ "Microsoft.Xna.Framework.Content.Pipeline.TextureImporter" + xnaVersion,
+ "Microsoft.Xna.Framework.Content.Pipeline.VideoImporters" + xnaVersion,
+ "Microsoft.Xna.Framework.Content.Pipeline.XImporter" + xnaVersion,
+};
+```
+
+
+## Example 2
+
+```csharp
+using Microsoft.Build.Evaluation;
+```
+
+
+## Example 3
+
+```csharp
+// MSBuild objects used to dynamically build content.
+private ProjectCollection projectCollection;
+private Project msBuildProject;
+```
+
+## Example 4
+
+```csharp
+///
+/// Creates a temporary MSBuild content project in memory.
+///
+void CreateBuildProject()
+{
+ string projectPath =
+ Path.Combine(buildDirectory, "content.contentproj");
+ string outputPath = Path.Combine(buildDirectory, "bin");
+
+ // Create the project collection
+ projectCollection = new ProjectCollection();
+
+ // Hook up our custom error logger.
+ errorLogger = new ErrorLogger();
+ projectCollection.RegisterLogger(errorLogger);
+
+ // Create the build project.
+ msBuildProject = new Project(projectCollection);
+ msBuildProject.FullPath = projectPath;
+
+ // set up the properties we care about
+ msBuildProject.SetProperty("XnaPlatform", "Windows");
+ msBuildProject.SetProperty("XnaFrameworkVersion", "v4.0");
+ msBuildProject.SetProperty("XnaProfile", "HiDef");
+ msBuildProject.SetProperty("Configuration", "Release");
+ msBuildProject.SetProperty("OutputPath", outputPath);
+
+ // Register any custom importers or processors.
+ foreach (string pipelineAssembly in pipelineAssemblies)
+ {
+ msBuildProject.AddItem("Reference", pipelineAssembly);
+ }
+
+ // Include the standard targets file that defines
+ // how to build XNA Framework content.
+ msBuildProject.Xml.AddImport(
+ "$(MSBuildExtensionsPath)\\Microsoft\\XNA Game Studio\\v4.0\\" +
+ "Microsoft.Xna.GameStudio.ContentPipeline.targets");
+}
+```
+
+
+## Example 5
+
+```csharp
+///
+/// Adds a new content file to the MSBuild project. The importer and
+/// processor are optional: if you leave the importer null, it will
+/// be autodetected based on the file extension, and if you leave the
+/// processor null, data will be passed through without any processing.
+///
+public void Add(
+ string filename, string name,
+ string importer, string processor)
+{
+ // set up the metadata for this item
+ var metadata = new SortedList();
+ metadata.Add("Link", Path.GetFileName(filename));
+ metadata.Add("Name", name);
+
+ if (!string.IsNullOrEmpty(importer))
+ metadata.Add("Importer", importer);
+
+ if (!string.IsNullOrEmpty(processor))
+ metadata.Add("Processor", processor);
+
+ // add the item
+ msBuildProject.AddItem("Compile", filename, metadata);
+}
+```
+
+
+## Example 6
+
+```csharp
+///
+/// Removes all content files from the MSBuild project.
+///
+public void Clear()
+{
+ // select all compiled objects in the project and remove them
+ var compileObjects = from i in project.Items
+ where i.ItemType == "Compile"
+ select i;
+ project.RemoveItems(compileObjects);
+}
+```
\ No newline at end of file
diff --git a/XnaControl.cs b/XnaControl.cs
new file mode 100644
index 0000000..5336587
--- /dev/null
+++ b/XnaControl.cs
@@ -0,0 +1,105 @@
+public partial class XnaControl : UserControl
+{
+ private GraphicsDeviceService graphicsService;
+ private XnaImageSource imageSource;
+
+ ///
+ /// Gets the GraphicsDevice behind the control.
+ ///
+ public GraphicsDevice GraphicsDevice
+ {
+ get { return graphicsService.GraphicsDevice; }
+ }
+
+ ///
+ /// Invoked when the XnaControl needs to be redrawn.
+ ///
+ public Action DrawFunction;
+
+ public XnaControl()
+ {
+ InitializeComponent();
+
+ // hook up an event to fire when the control has finished loading
+ Loaded += new RoutedEventHandler(XnaControl_Loaded);
+ }
+
+ ~XnaControl()
+ {
+ imageSource.Dispose();
+
+ // release on finalizer to clean up the graphics device
+ if (graphicsService != null)
+ graphicsService.Release();
+ }
+
+ void XnaControl_Loaded(object sender, RoutedEventArgs e)
+ {
+ // if we're not in design mode, initialize the graphics device
+ if (DesignerProperties.GetIsInDesignMode(this) == false)
+ {
+ InitializeGraphicsDevice();
+ }
+ }
+
+ protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
+ {
+ // if we're not in design mode, recreate the
+ // image source for the new size
+ if (DesignerProperties.GetIsInDesignMode(this) == false &&
+ graphicsService != null)
+ {
+ // recreate the image source
+ imageSource.Dispose();
+ imageSource = new XnaImageSource(
+ GraphicsDevice, (int)ActualWidth, (int)ActualHeight);
+ rootImage.Source = imageSource.WriteableBitmap;
+ }
+
+ base.OnRenderSizeChanged(sizeInfo);
+ }
+
+ private void InitializeGraphicsDevice()
+ {
+ if (graphicsService == null)
+ {
+ // add a reference to the graphics device
+ graphicsService = GraphicsDeviceService.AddRef(
+ (PresentationSource.FromVisual(this) as HwndSource).Handle);
+
+ // create the image source
+ imageSource = new XnaImageSource(
+ GraphicsDevice, (int)ActualWidth, (int)ActualHeight);
+ rootImage.Source = imageSource.WriteableBitmap;
+
+ // hook the rendering event
+ CompositionTarget.Rendering += CompositionTarget_Rendering;
+ }
+ }
+
+ ///
+ /// Draws the control and allows subclasses to override
+ /// the default behavior of delegating the rendering.
+ ///
+ protected virtual void Render()
+ {
+ // invoke the draw delegate so someone will draw something pretty
+ if (DrawFunction != null)
+ DrawFunction(GraphicsDevice);
+ }
+
+ void CompositionTarget_Rendering(object sender, EventArgs e)
+ {
+ // set the image source render target
+ GraphicsDevice.SetRenderTarget(imageSource.RenderTarget);
+
+ // allow the control to draw
+ Render();
+
+ // unset the render target
+ GraphicsDevice.SetRenderTarget(null);
+
+ // commit the changes to the image source
+ imageSource.Commit();
+ }
+}
\ No newline at end of file
diff --git a/XnaControl.xaml b/XnaControl.xaml
new file mode 100644
index 0000000..23063fb
--- /dev/null
+++ b/XnaControl.xaml
@@ -0,0 +1,6 @@
+
+
+
\ No newline at end of file
diff --git a/XnaImageSource.cs b/XnaImageSource.cs
new file mode 100644
index 0000000..0774c2c
--- /dev/null
+++ b/XnaImageSource.cs
@@ -0,0 +1,98 @@
+///
+/// A wrapper for a RenderTarget2D and WriteableBitmap
+/// that handles taking the XNA rendering and moving it
+/// into the WriteableBitmap which is consumed as the
+/// ImageSource for an Image control.
+///
+public class XnaImageSource : IDisposable
+{
+ // the render target we draw to
+ private RenderTarget2D renderTarget;
+
+ // a WriteableBitmap we copy the pixels into for
+ // display into the Image
+ private WriteableBitmap writeableBitmap;
+
+ // a buffer array that gets the data from the render target
+ private byte[] buffer;
+
+ ///
+ /// Gets the render target used for this image source.
+ ///
+ public RenderTarget2D RenderTarget
+ {
+ get { return renderTarget; }
+ }
+
+ ///
+ /// Gets the underlying WriteableBitmap that can
+ /// be bound as an ImageSource.
+ ///
+ public WriteableBitmap WriteableBitmap
+ {
+ get { return writeableBitmap; }
+ }
+
+ ///
+ /// Creates a new XnaImageSource.
+ ///
+ /// The GraphicsDevice to use.
+ /// The width of the image source.
+ /// The height of the image source.
+ public XnaImageSource(GraphicsDevice graphics, int width, int height)
+ {
+ // create the render target and buffer to hold the data
+ renderTarget = new RenderTarget2D(
+ graphics, width, height, false,
+ SurfaceFormat.Color,
+ DepthFormat.Depth24Stencil8);
+ buffer = new byte[width * height * 4];
+ writeableBitmap = new WriteableBitmap(
+ width, height, 96, 96,
+ PixelFormats.Bgra32, null);
+ }
+
+ ~XnaImageSource()
+ {
+ Dispose(false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ renderTarget.Dispose();
+
+ if (disposing)
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Commits the render target data into our underlying bitmap source.
+ ///
+ public void Commit()
+ {
+ // get the data from the render target
+ renderTarget.GetData(buffer);
+
+ // because the only 32 bit pixel format for WPF is
+ // BGRA but XNA is all RGBA, we have to swap the R
+ // and B bytes for each pixel
+ for (int i = 0; i < buffer.Length - 2; i += 4)
+ {
+ byte r = buffer[i];
+ buffer[i] = buffer[i + 2];
+ buffer[i + 2] = r;
+ }
+
+ // write our pixels into the bitmap source
+ writeableBitmap.Lock();
+ Marshal.Copy(buffer, 0, writeableBitmap.BackBuffer, buffer.Length);
+ writeableBitmap.AddDirtyRect(
+ new Int32Rect(0, 0, renderTarget.Width, renderTarget.Height));
+ writeableBitmap.Unlock();
+ }
+}
\ No newline at end of file