Freigeben über


WPF Hosting for XNA Game Studio 4.0

A while back I wrote up a method for rendering XNA Game Studio 4.0 content inside of a WPF window. My method involved using a render target and doing a CPU transfer of those bits into a WriteableBitmap. This method had some pros and cons:

Pros:

  1. Extremely simple in concept and in actual implementation.
  2. Enabled high level of composition with native WPF elements.
  3. Allowed app to leverage WPF input events.

Cons:

  1. Not the most efficient process, given a full CPU swap of the pixels each frame. (The R/B swap I did on the CPU could be moved to a pixel shader prior to getting the data which would reduce a good bit of the work, but you're still moving a lot of data from the GPU into the WriteableBitmap every frame).
  2. Limits the app from using render targets without more plumbing work as switching away from the main render target prevents the content from making it to the screen.

These limits may be acceptable for some people. I built a real-time shader editing tool using this system and it worked great for my needs. However since some people have asked for more ways to do this integration that provide more performance and don't require the render targets, I went back to the drawing board a bit. After talking with a coworker for a bit (i.e. taking his core idea and running with it ;-) ), I have come up a new solution for this integration using WPF's HwndHost base type.

The idea of the new solution is simple: create a control subclasses WPF's HwndHost to create a child window to which the XNA Framework will render graphics. I leveraged the GraphicsDeviceService from the WinForms sample (with some modifications) to handle GraphicsDevice management and used the CompositionTarget.Rendering event to drive my draw loop. I also used some concepts from the GraphicsDeviceControl in the WinForms sample so that each of my controls will present to the correct window handle, thus enabling apps to have multiple controls running at the same time and sharing the same GraphicsDevice.

This solution, however, is not without its own set of trade offs. Essentially we're flipping the pro/con list from above:

Pros:

  1. Rendering requires no CPU copies as we Present directly to the child window where we want our content. This means we get some great performance even with large windows.
  2. Since we're presenting directly to the window, we no longer use render targets which allows the app itself to use them for any purpose.

Cons:

  1. Due to the nature of hosting a child window, you cannot composite WPF controls on top of the GraphicsDeviceControl (the exception being other child window content such as menus or dialogs).
  2. Because the control is really a child window, mouse input is directed to that window's WndProc which means input handling requires a bit more work and causes some duplication of events. My sample does implement enough of this work to be useful, however there may be some things you want that I didn't tackle.
  3. This solution requires a few native method calls and an implementation of a WndProc which means the implementation is quite a bit more complex than my previous solution. The usage is still relatively straight forward, however.

What it comes down to is that these two solutions are both useful in different ways and therefore I recommend you look at them both to see which will meet your needs.

At a high level, use of this new sample is very simple. Add a GraphicsDeviceControl into your XAML layout where you want to render XNA graphics. The GraphicsDeviceControl has a LoadContent event that will fire when the GraphicsDevice has been created and a RenderXna event that fires each frame for you to draw your content. Additionally there are a number of "Hwnd*" events for various aspects of mouse input since I couldn't route it through the normal UIElement mouse events. The control also has methods to capture/release the mouse which makes the cursor invisible and forces the cursor position to reset, which is great for tools where you want to rotate an object and not worry about the mouse leaving the view.

The sample application shows how to leverage these various actions in an app with two GraphicsDeviceControls. The top control draws a constantly spinning cube whose color is based on the sliders in the left panel. The bottom control draws a cube in one of three colors than can be changed with the buttons next to the control (or using the 'R', 'G', or 'B' keyboard keys which are set up as hotkeys for those buttons). Additionally you can use the right mouse button and drag to rotate the bottom cube without capturing the mouse or the left mouse button and drag to rotate the cube with capturing the mouse.

Hopefully this proves useful to some of you. Feel free to leave questions or comments below, but to make this point clear: the implementation of this solution is pretty complex and I cannot provide much support as I simply don't have the time. I encourage everyone to play with it, but if you run into issues please start by checking out MSDN documentation or ask on the App Hub forums so that the community can work with you to solve the issue.

Disclaimer: This code was written by a colleague and myself and is not an official solution for mixing XNA Game Studio and WPF. Use of this sample is at your own risk and I make no guarantees as to the quality or usability of the code. I do not guarantee any level of support for users of this sample. The code in the downloadable sample is licensed under the Microsoft Public License, the terms of which can be found here:  https://www.microsoft.com/opensource/licenses.mspx#Ms-PL

WpfHostedXna.zip

Comments

  • Anonymous
    December 15, 2011
    Hi Nick! This was an great post! I'm using this as a base for an graphic intensive application. I just have one question which isn't really related to the main subject here. But I can't get the mouse wheel event to be sent to the WndProc. I've added a check for (0x020A). But maybe I need to register for that event in some special way? I would be very thankful if you had time to answer this. Best regards, Erik

  • Anonymous
    December 21, 2011
    I implemented mouse wheel support by using the native method SetFocus(hWnd); every time the mouse enters the control. I used GetFocus() before this to save the current window, and set it back when the mouse leaves the control. The changes are to GraphicsDeviceControl.cs and NativeMethods.cs You can see more details on the implementation here: github.com/.../XnaContentHost

  • Anonymous
    January 15, 2012
    Thanks BinaryConstruct. That worked perfectly!

  • Anonymous
    January 26, 2012
    The comment has been removed

  • Anonymous
    February 28, 2012
    How would you load your own 3d content into this?

  • Anonymous
    April 14, 2012
    The comment has been removed

  • Anonymous
    June 28, 2012
    Does anyone can provide the code snippet on how to support ContentManager on this demo? I need to launch my own shader file, but failed with can't find ServiceContainer() in XNA 4.0

  • Anonymous
    July 02, 2012
    Very great and usefull code! Thanks, Nick. Jiancong, you can get the ServiceContrainer from here: create.msdn.com/.../winforms_series_1. This is the WinForms equivalent from this code. Implement the ServiceContainer class in your own code. You will need to add 5 lines in your existing code. Add these snippets and you will be fine I guess: In GraphicsDeviceControl.cs: public ServiceContainer Services {    get { return services; } } ServiceContainer services = new ServiceContainer(); Also, don't forget to add the service to the service container in the XnaWindowHost_Loaded event: services.AddService<IGraphicsDeviceService>(graphicsService); Access the content manager by the following snippet: contentManager = new ContentManager(yourXnaControl.Services, "Content"); Also, I ran into a few problems while rendering multiple XNA controls on my screen; where the application would crash if the viewport is bigger then the first rendered control (in Nick's demo, the app will crash when you set the second, bottom, XNA control larger then the first one). This can be easily fixed by resetting the GraphicsDevice when needed in the CompositionTarget_Rendering event in GraphicsDeviceControl.cs. Again, we can copy some code from the WinForms equivalent: // If the control has no width or no height, skip drawing since it's not visible if (width < 1 || height < 1)    return; // This piece of code is copied from the WinForms equivalent string deviceResetError = HandleDeviceReset(); if (!string.IsNullOrEmpty(deviceResetError)) {    return; } // Create the active viewport to which we'll render our content Viewport viewport = new Viewport(0, 0, width, height); GraphicsDevice.Viewport = viewport; Dont forget to also copy the HandleDeviceReset() method from the WinForms equivalent.

  • Anonymous
    March 07, 2013
    Hello, can someone help me^ I got my Content loaded, but I dont know how to draw them, essentially just a Texture2D.

  • Anonymous
    March 08, 2013
    Actually I can't load my texture properly. Problem with Content Manager, it never compile my asset and I can't load pre-compiled .XB files.

  • Anonymous
    March 13, 2013
    This looks to be exactly what I need, but I am having one small issue.  I can't seem to place any controls over top of the XNA controls.  Is there a way to do this?

  • Anonymous
    March 11, 2014
    I wanted to overlap hwnd window that you created with semi transparent wpf control, but its not working. I also tried to change window style in CreateWindowsEx function. So if possible guide me how to draw wpf control on that window. Thank you