Charles Petzold



Using VertexPositionTexture on Windows Phone

March 30, 2010
New York, N.Y.

I was having a helluva time with XNA 3D for Windows Phone applying a simple Texture2D to a simple model constructed using VertexPositionTexture and BasicEffect. The program would deploy on the phone emulator, all the DLLs would be loaded, and then Visual Studio's Output window would display:

At which point the program would terminate and the phone emulator would apparently be left in a dirty state because it would need to be restarted for future deployments. That the problem was occurring somewhere in internal processing and not as a direct result of a particular statement in my code left me totally stranded.

My big breakthrough came with a close reading of Shawn Hargreaves' "Reach vs. HiDef" blog entry. Due mostly to restrictions of the Windows Phone GPU, not all the many features of XNA are supported on the phone. Those features that are supported on the phone are grouped in a profile known as "Reach," meaning programs that have the furthest reach in platforms they run on.

The sixth row in Shawn's chart refers to "Non power of two textures," which are textures whose dimensions are not powers of two. For the Reach profile the restriction indicates "cannot use wrap addressing mode." But the wrap addressing mode is also the default setting!

A little background: When covering a 3D triangle mesh with a 2D texture (which is often just a simple bitmap), you supply 2D texture coordinates that range from (0, 0) — the upper-left corner — to (1, 1), the lower-right corner. But you can also supply texture coordinates outside that range, and based on the texture address mode setting, the image will be clamped (that is, the outside rim of pixels will just be repeated) or wrapped in a tiled pattern, or wrapped in a flip-flop mirror effect.

The setting is found in a SamplerState object associated with the GraphicsDevice object. The three properties AddressU, AddressV, and AddressW properties are of type TextureAddressMode, an enumeration that has members Wrap, Mirror, and Clamp. The problem is that Wrap is the default, making it incompatible with the GPU on the Windows Phone — at least for textures that don't have dimensions that are powers of two.

If you're generating a Texture2D algorithmically, you can just set the dimensions to a nice round number like 32 or 64 and all will be fine. If you're loading some arbitrary Texture2D and want it to work directly, you'll need to create a new SamplerState object and set it to the first element of the SamplerState array property on the GraphicsDevice. SamplerState also has some handy static fields with pre-defined SampleState objects, so you can do something as simple as this:

You can either set this alternative SamplerState once for the whole duration of the program or temporarily just for some selected drawing in the Draw method. The first part of the static field name refers to the type of filtering used when the image needs to be expanded or shrunk to fit.

Here's an entire XNA program for Windows Phone that loads a bitmap and slaps it on the surface of a simple flat square in 3D space:

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace ShiftingPlane
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        const float vertShiftSpeed = 0.17f / 1000;
        const float horzShiftSpeed = 0.13f / 1000;

        GraphicsDeviceManager graphics;
        VertexPositionTexture[] vertices;
        BasicEffect basicEffect;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            TargetElapsedTime = TimeSpan.FromSeconds(1 / 30.0);
        }

        protected override void LoadContent()
        {
            vertices = new VertexPositionTexture[]
            {
                new VertexPositionTexture(new Vector3(-1, 1, 0), new Vector2(0, 0)),
                new VertexPositionTexture(new Vector3(1, 1, 0), new Vector2(1, 0)),
                new VertexPositionTexture(new Vector3(-1, -1, 0), new Vector2(0, 1)),
                new VertexPositionTexture(new Vector3(1, -1, 0), new Vector2(1, 1))
            };

            Texture2D portrait = this.Content.Load<Texture2D>("PetzoldTattoo");
            float aspectRatio = graphics.GraphicsDevice.Viewport.AspectRatio;

            basicEffect = new BasicEffect(graphics.GraphicsDevice)
            {
                TextureEnabled = true,
                Texture = portrait,
                View = Matrix.CreateLookAt
                                (new Vector3(0, 0, 5), Vector3.Zero, Vector3.Up),
                Projection = Matrix.CreatePerspectiveFieldOfView
                                (MathHelper.PiOver4, aspectRatio, 0.1f, 10)
            };

            // Change the texture address mode to Clamp
            graphics.GraphicsDevice.SamplerStates[0] = SamplerState.PointClamp;
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            float msec = (float)gameTime.TotalGameTime.TotalMilliseconds;
            float vertAngle = (float)Math.Cos(MathHelper.Pi * (vertShiftSpeed * msec));
            float horzAngle = (float)Math.Cos(MathHelper.Pi * (horzShiftSpeed * msec));

            basicEffect.World = 
                Matrix.CreateRotationY(vertAngle) * Matrix.CreateRotationX(horzAngle);

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(Color.Navy);

            EffectPass effectPass = basicEffect.CurrentTechnique.Passes[0];
            effectPass.Apply();
            graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>
                (PrimitiveType.TriangleStrip, vertices, 0, vertices.Length - 2);

            base.Draw(gameTime);
        }
    }
}

That code in the Update method controls some animation that wiggles the square back and forth around the X and Y axes:

The Visual Studio solution requires installation of the Windows Phone 7 Series development tools available via the Windows Phone portal.