PETZOLD BOOK BLOG

Charles Petzold on writing books, reading books, and exercising the internal UTM


Recent Entries
< PreviousBrowse the ArchivesNext >
Subscribe to the RSS Feed

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.


Comments:

Hey Charles, sorry this was such a pain to figure out!

I think you ran into a bug in the CTP releaes, where unhandled exceptions don't always show up properly in the debugger, so you just get the "first chance" exception, but then this gets swallowed and the game loop continues to run, so you never actually see the exception message.

If you go to Debug / Exceptions and set this to break on thrown as well as unhandled, you should see a more useful message in this situation.

Shawn Hargreaves, Wed, 31 Mar 2010 10:35:15 -0400

Many thanks for the debugging hint. (With any luck I'll never have to use it!) — Charles


Recent Entries
< PreviousBrowse the ArchivesNext >
Subscribe to the RSS Feed

(c) Copyright Charles Petzold
www.charlespetzold.com