Windows Phone Mango–What’s New? (“Silverlight/XNA Interoperability” – Part 7 of 8)

May 24, 2011

Windows Phone RTM didn’t allowed to mix Silverlight and XNA content. Mango enables the scenarios where Silverlight content can be rendered along with XNA content. In the sample presented in this post we will build 2-pager Silverlight application with pure Silverlight page (1st page) and mixed 3D XNA & Silverlight page (2nd page).


Final application looks like the following:


imageimage


In order to ease on the developers, after installing Mango developer tools, Visual Studio provides us with two templates – one in Silverlight for Windows Phone area and one in XNA Game Studio 4.0 area:


image


and


image


The templates are almost identical. The first one (from Silverlight section) has some basic texture moving on-screen while second has minimum required to run XNA.


Both solutions has some plumbing in App in order to properly initialize XNA’s ContentManager, FrameworkDispatcher, etc. If you familiar with XNA development you probably know, that the main class in XNA game derives from Game class (part of Microsoft.Xna.Framework namespace). This class takes care of all XNA stuff initialization. In case of hybrid applications, like one we have here, the Silverlight’s application class have to take care about proper initializations.


Review create App.xaml.cs file – it implements IServiceProvider interface (which pure Silverlight applications usually doesn’t), and has XNA application init region with ContentManager and GameTimer initialization:

#region XNA application initialization

// Performs initialization of the XNA types required for the application.
private void InitializeXnaApplication()
{
// Create the ContentManager so the application can load precompiled assets
Content = new ContentManager(this, “Content”);

// Create a GameTimer to pump the XNA FrameworkDispatcher
GameTimer frameworkDispatcherTimer = new GameTimer();
frameworkDispatcherTimer.FrameAction += FrameworkDispatcherFrameAction;
frameworkDispatcherTimer.Start();
}

// An event handler that pumps the FrameworkDispatcher each frame.
// FrameworkDispatcher is required for a lot of the XNA events and
// for certain functionality such as SoundEffect playback.
private void FrameworkDispatcherFrameAction(object sender, EventArgs e)
{
FrameworkDispatcher.Update();
}

#endregion


Note: you don’t have to write this code if you are using a visual studio template; if you need to add some XNA parts to your existing Silverlight application you have to write this in order to use XNA.


Let’s see the “GamePage.xaml” create by Visual Studio – first thing you probably mention there is:

<!–No XAML content as the page is rendered entirely with XNA–>

Ok, let’s understand what is it and why.


Silverlight/XNA interoperability is based on Silverlight’s implementation of XNA class – GraphicsDeviceManager; in Silverlight hybrid apps it called SharedGraphicsDeviceManager. It allows to “switch” rendering mode from Silverlight’s retained graphics mode to XNA’s immediate graphics. Unfortunately  SharedGraphicsDeviceManager’s switch affects all the screen area, which means there are no way to mix between XNA and Silverlight rendering engine on the same screen. Let’s deal with this limitation later on.


My sample uses relatively simple 3D model of Alien Spaceship (could be found in XNA Creators Club Samples). The rendering procedure is exactly the same as we (at least I do) used to do in XNA.


Note: I created simple class to take care on all 3D rendering named XnaModelWrapper.cs which can be found in attached sources. This post is focused on hybrid apps and not on XNA – to understand how 3D rendering works in XNA please refer to XNA documentation and tutorials.


Our GamePage class uses GameTimer to emulate XNA’s game loop and subscribes to Update and Draw events. Those events match (by name and meaning) to the corresponding events in regular XNA games:

// Create a timer for this page
timer = new GameTimer();
timer.UpdateInterval = TimeSpan.FromTicks(333333);
timer.Update += new EventHandler<GameTimerEventArgs>(timer_Update);
timer.Draw += new EventHandler<GameTimerEventArgs>(timer_Draw);

In addition I want to provide some level of interactivity to my 3D model and for this sample I preferred to use XNA’s TouchPanel class. It provides me with built-in gestures recognition (which Silverlight doesn’t):

//Initialize gestures support – Pinch for Zoom and horizontal drag for rotate
TouchPanel.EnabledGestures = GestureType.FreeDrag | GestureType.Pinch | GestureType.PinchComplete;

Now, every 33ms the GameTimer will fire Update event and I’ll handle it as follows:

void timer_Update(object sender, GameTimerEventArgs e)
{
HandleInput();

float yaw = MathHelper.Pi + MathHelper.PiOver2 + xRotation / 100;
float pitch = yRotation / 100;
model.Rotation = modelMetadata.World * Matrix.CreateFromYawPitchRoll(yaw, pitch, 0);
model.View = modelMetadata.ViewMatrix;
model.IsTextureEnabled = true;
model.IsPerPixelLightingEnabled = true;
model.Projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.ToRadians(cameraFOV) / modelMetadata.FieldOfViewDivisor,
modelMetadata.AspectRatio,
modelMetadata.NearPlaneDistance,
modelMetadata.FarPlaneDistance);
}


The takes care of handling touch input first and the does some calculations to rotate 3D model.

Note: To better understand this code please refer to XNA 3D drawing tutorials


Handle input takes care of gestures reported to the application (according to initialized gestures above):

private void HandleInput()
{
while (TouchPanel.IsGestureAvailable)
{
GestureSample gestureSample = TouchPanel.ReadGesture();
switch (gestureSample.GestureType)
{
case GestureType.FreeDrag:
xRotation += gestureSample.Delta.X;
yRotation -= gestureSample.Delta.Y;
break;

case GestureType.Pinch:
float gestureValue = 0;
float minFOV = 80;
float maxFOV = 20;
float gestureLengthToZoomScale = 10;

Vector2 gestureDiff = gestureSample.Position – gestureSample.Position2;
gestureValue = gestureDiff.Length() / gestureLengthToZoomScale;

if (null != prevLength) // Skip the first pinch event
cameraFOV -= gestureValue – prevLength.Value;

cameraFOV = MathHelper.Clamp(cameraFOV, maxFOV, minFOV);

prevLength = gestureValue;
break;

case GestureType.PinchComplete:
prevLength = null;
break;

default:
break;
}
}
}


Pay attention, that I can handle the gesture and not spend my time writing the gesture recognition (or use 3rd party gesture recognition libraries).


After gestures recognized and changes in my model calculated let’s see the Draw event handler:

void timer_Draw(object sender, GameTimerEventArgs e)
{
SharedGraphicsDeviceManager.Current.GraphicsDevice.Clear(Color.CornflowerBlue);

spriteBatch.Begin();
spriteBatch.Draw(background, Vector2.Zero, Color.White);
spriteBatch.End();

// Set render states.
SharedGraphicsDeviceManager.Current.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
SharedGraphicsDeviceManager.Current.GraphicsDevice.BlendState = BlendState.Opaque;
SharedGraphicsDeviceManager.Current.GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
SharedGraphicsDeviceManager.Current.GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;

// Draw the model
model.Draw();

// TODO

}


This code uses SpriteBatch instance to render 2D graphics (background texture), prepares the GraphicsDevice for 3D rendering and calls for 3D model’s Draw method. The approach is 100% compatible to XNA (beside the graphics device manager class name – in XNA it called GraphicsDeviceManager). This way we achieving the XNA (2D and 3D) content rendering in Silverlight application’s page.


But what about the Silverlight? It is well know, that XNA has no built-in controls – what about mixing some Silverlight controls & XNA?


Let’s change our GamePage.xaml a little bit:

<Grid x:Name=”LayoutRoot” Margin=”12″>
<
Grid.RowDefinitions>
<
RowDefinition Height=”Auto”/>
<
RowDefinition Height=”*”/>
<
RowDefinition Height=”Auto”/>
</
Grid.RowDefinitions>
<
TextBlock FontSize=”{StaticResource PhoneFontSizeExtraLarge}”
Foreground=”White”
Text=”{Binding ModelName}” Grid.Row=”0″
VerticalAlignment=”Top” HorizontalAlignment=”Left”/>
<
TextBlock FontSize=”{StaticResource PhoneFontSizeLarge}”
Foreground=”White”
Text=”{Binding ModelDesc}” Grid.Row=”1″
VerticalAlignment=”Top” HorizontalAlignment=”Left”/>
<
Button Content=”About” x:Name=”btnAbout” Click=”btnAbout_Click” Grid.Row=”2″
HorizontalAlignment=”Left” VerticalAlignment=”Bottom”/>
</
Grid>

The UI is very simple, yet it has interactive element and databinding. The button_Click event handler is super simple:

private void btnAbout_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(“Hello from Silverlight/XNA hybrid application!”);
}

Just by adding this XMAL on page it will not show itself. This is a side effect of SharedGraphicsDeviceManager switch – the Silverlight rendering engine is not working, thus nothing rendered:


image


I marked (red rectangle) the are with a button… Clicking inside at the place where the button should be will reveal the “hidden gem” – button still works:


image


This means, that Silverlight is fully functional but just not rendered. To render Silverlight on XNA driving surface we need to use UIElementRenderer – new class in Microsoft.Xna.Framework.Graphics namespace. It responsible of creating XNA’s Texture2D object from Silverlight’s UIElement. This means, that any part of our Silverlight interface could be rendered into XNA texture and used into XNA composition (like we did with a background). TThis means, that we could “paint” real 3D models with Silverlight UI!


Let’s see how to initialize this UIElementRenderer class. It should be initialized with width and height of texture to produce, then best place to initialize it if at Silverlight’s LayoutUpdated event (after all the controls has all the styles applied):

void GamePage_LayoutUpdated(object sender, EventArgs e)
{
if (uiRenderer == null || LayoutRoot.ActualWidth > 0 && LayoutRoot.ActualHeight > 0)
uiRenderer = new UIElementRenderer(LayoutRoot, (int)LayoutRoot.ActualWidth, (int)LayoutRoot.ActualHeight);
}

In this sample I’m initializing it from page LayoutRoot element and want to create an image to span over whole screen.


At the Draw event handler I’m replacing the “//TODO…” comment with actual rending command:

// Update the Silverlight UI
uiRenderer.Render();

// Draw the sprite
spriteBatch.Begin();
spriteBatch.Draw(uiRenderer.Texture, Vector2.Zero, Color.White);
spriteBatch.End();


Note, that XNA will render the 2D/3D elements at the order they appears in Draw method. Thus if you want to render part of the screen on upper layer send it to rendering last (like in case I did with Silverlight-rendered texture). Result is expected:


image


and the button is also fully functional (see the pressed state in marked red rectangle):


image


Note (Updated): There is one limitation of the SL/XNA interop – the popup controls are not supported. This means, that controls like Silverlight Toolkit’s AutoComplete and list picker do not work in interop mode.


The source used for this post hosted here.


Stay tuned to part 8 – “Push Notification & Tiles”


Alex

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>