Question

I'm trying to write my first XNA game, and I'd like to include ALT+ENTER functionality to toggle between full screen and windowed modes. My game's extremely basic right now, nothing fancy. I can get it to run in either windowed (default) or full screen (by calling ToggleFullScreen) in my LoadContent function, and everything works great.

However, when I call ToggleFullScreen in my Update function, using the exact same code, my game fails. If I start windowed and switch to full screen, it seems to freeze, displaying only one frame and not accepting keyboard input (I have to CTRL+ALT+DEL). If I start fullscreen and switch to windowed, it errors on DrawIndexedPrimitive with the message "The current vertex declaration does not include all the elements required by the current vertex shader. Normal0 is missing." (which is not true; my vertex buffer is VertexPositionNormalTexture and contains data, and the GraphicsDevice status is Normal).

Both of these issues seem to be pointing to there being a lost connection with the device somehow, but I can't figure out why. Every resource I've found online says toggling full screen is as easy as calling the ToggleFullScreen function, with no mention of resetting devices or buffers. Any ideas?

Edit: Here's some code, with extraneous stuff left out:

    protected override void LoadContent()
    {
        graphics.PreferredBackBufferWidth = 800; 
        graphics.PreferredBackBufferHeight = 600;
        graphics.ToggleFullScreen();

        basicEffect = new BasicEffect(graphics.GraphicsDevice);
        // etc.
    }

    protected override void Update(GameTime gameTime)
    {
        if (k.IsKeyDown(Keys.Enter) && (k.IsKeyDown(Keys.LeftAlt) || k.IsKeyDown(Keys.RightAlt)) && !oldKeys.Contains(Keys.Enter)) {
            graphics.ToggleFullScreen();
            gameWorld.Refresh();
        }
         // update the world's graphics; this fills my buffers
        GraphicsBuffers gb = gameWorld.Render(graphics.GraphicsDevice);
        graphics.GraphicsDevice.SetVertexBuffer(gb.vb, 0);
        graphics.GraphicsDevice.Indices = gb.ib;
   }

    protected override void Draw(GameTime gameTime)
    {
        // buffer reference again; these are persistent once filled in Update, they don't get created anew
        GraphicsBuffers gb = gameWorld.Render(graphics.GraphicsDevice);

        foreach (EffectPass effectPass in basicEffect.CurrentTechnique.Passes)
        {
            effectPass.Apply();

            basicEffect.Texture = textures;
            graphics.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, gb.vb.VertexCount, 0, gb.ib.IndexCount / 3);
        }
    }
Was it helpful?

Solution

The XNA Game class and the associated GraphicsDeviceManager are built around the concept that the Update function is for updating the state of your game and the Draw function is for rendering that state. When you set the state of the graphics device during the Update function you break that assumption.

When you toggle to fullscreen the device is lost. There is a lot of magic happening behind the scenes in the Framework to hide the complexity of this from you. The resources associated with the device have to be recreated etc. The framework is assuming you won't do any rendering operations again until the next Draw function and so it doesn't guarantee a consistent state until that happens.

So... Don't set your buffers on the graphics device during the update function. Do this at the start of the drawing function.

I'm not sure that this is explicitly documented anywhere. It is more implied by the method documentation. The Game.Draw documentation says:

Called when the game determines it is time to draw a frame. Override this method with game-specific rendering code.

and the update method says:

Called when the game has determined that game logic needs to be processed. This might include the management of the game state, the processing of user input, or the updating of simulation data. Override this method with game-specific logic.

OTHER TIPS

According to one answer on the App Hub forums you have to make sure you rebuild your projection matrix.

http://forums.create.msdn.com/forums/p/31773/182231.aspx

from that link....

Also, be careful about different aspect ratios when toggling full screen. If you're in 3D, you'll need to rebuild your projection matrix. If you're in 2D, you'll probably need to pass a scaling matrix to your SpriteBatch.Begin functions. This article has information about doing this in 2D.

So you'll want to make sure you're doing that (unfortunately the article that post links to was on Ziggyware and that site has been gone for quite a while so to find the 2D example they linked to you might have to use some archive site like the WaybackMachine if you're interested in looking at it, since you're doing 3D I figured it might not be of interest).

I was able to toggle fullscreen/ windowed mode using the following class:

/// <summary>
/// This is the main type for your game
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;

    private Model grid;
    Model tank;
    int zoom = 1;

    Texture2D thumb;
    Vector2 position = new Vector2( 400, 240 );
    Vector2 velocity = new Vector2( -1, -1 );
    Matrix projection, view;
    public Game1()
    {
        graphics = new GraphicsDeviceManager( this );
        Content.RootDirectory = "Content";
    }

    /// <summary>
    /// Allows the game to perform any initialization it needs to before starting to run.
    /// This is where it can query for any required services and load any non-graphic
    /// related content.  Calling base.Initialize will enumerate through any components
    /// and initialize them as well.
    /// </summary>
    protected override void Initialize()
    {
        // TODO: Add your initialization logic here

        base.Initialize();

        projection = Matrix.CreatePerspectiveFieldOfView( MathHelper.PiOver4,
                                                                GraphicsDevice.Viewport.AspectRatio,
                                                                10,
                                                                20000 );

        view = Matrix.CreateLookAt( new Vector3( 1500, 550, 0 ) * zoom + new Vector3( 0, 150, 0 ),
                                          new Vector3( 0, 150, 0 ),
                                          Vector3.Up );

    }

    /// <summary>
    /// LoadContent will be called once per game and is the place to load
    /// all of your content.
    /// </summary>
    protected override void LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch( GraphicsDevice );

        // TODO: use this.Content to load your game content here
        thumb = Content.Load<Texture2D>( "GameThumbnail" );
        grid = Content.Load<Model>( "grid" );
        tank = Content.Load<Model>( "tank" );

    }

    /// <summary>
    /// UnloadContent will be called once per game and is the place to unload
    /// all content.
    /// </summary>
    protected override void UnloadContent()
    {
        // TODO: Unload any non ContentManager content here
    }

    int ToggleDelay = 0;
    /// <summary>
    /// Allows the game to run logic such as updating the world,
    /// checking for collisions, gathering input, and playing audio.
    /// </summary>
    /// <param name="gameTime">Provides a snapshot of timing values.</param>
    protected override void Update( GameTime gameTime )
    {
        // Allows the game to exit
        if ( GamePad.GetState( PlayerIndex.One ).Buttons.Back == ButtonState.Pressed )
            this.Exit();

        var kbState = Keyboard.GetState();
        if ( ( kbState.IsKeyDown( Keys.LeftAlt ) || kbState.IsKeyDown( Keys.RightAlt ) ) &&
             kbState.IsKeyDown( Keys.Enter ) && ToggleDelay <= 0 )
        {
            graphics.ToggleFullScreen();
            ToggleDelay = 1000;
        }

        if ( ToggleDelay >= 0 )
        {
            ToggleDelay -= gameTime.ElapsedGameTime.Milliseconds;
        }
        // TODO: Add your update logic here

        int x, y;
        x = (int)position.X;
        y = (int)position.Y;

        x += (int)velocity.X;
        y += (int)velocity.Y;
        if ( x > 480 - 64 )
            velocity.X = +1;
        if ( x < 0 )
            velocity.X = -1;
        if ( y < 0 )
            velocity.Y = +1;
        if ( y > 800 - 64 )
            velocity.Y = -1;

        position.X = x;
        position.Y = y;

        base.Update( gameTime );
    }

    /// <summary>
    /// This is called when the game should draw itself.
    /// </summary>
    /// <param name="gameTime">Provides a snapshot of timing values.</param>
    protected override void Draw( GameTime gameTime )
    {
        GraphicsDevice.Clear( Color.CornflowerBlue );

        Matrix rotation = Matrix.CreateRotationY( gameTime.TotalGameTime.Seconds * 0.1f );

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

        grid.Draw( rotation, view, projection );

        DrawModel( tank, rotation, view, projection );

        // TODO: Add your drawing code here
        spriteBatch.Begin();
        spriteBatch.Draw( thumb, new Rectangle( (int)position.X, (int)position.Y, 64, 64 ), Color.White );
        spriteBatch.End();

        base.Draw( gameTime );
    }

    public void DrawModel( Model model, Matrix world, Matrix view, Matrix projection )
    {
        // Set the world matrix as the root transform of the model.
        model.Root.Transform = world;

        // Allocate the transform matrix array.
        Matrix[] boneTransforms = new Matrix[ model.Bones.Count ];

        // Look up combined bone matrices for the entire model.
        model.CopyAbsoluteBoneTransformsTo( boneTransforms );

        // Draw the model.
        foreach ( ModelMesh mesh in model.Meshes )
        {
            foreach ( BasicEffect effect in mesh.Effects )
            {
                effect.World = boneTransforms[ mesh.ParentBone.Index ];
                effect.View = view;
                effect.Projection = projection;

                //switch (lightMode)
                //{
                //    case LightingMode.NoLighting:
                //        effect.LightingEnabled = false;
                //        break;

                //    case LightingMode.OneVertexLight:
                //        effect.EnableDefaultLighting();
                //        effect.PreferPerPixelLighting = false;
                //        effect.DirectionalLight1.Enabled = false;
                //        effect.DirectionalLight2.Enabled = false;
                //        break;

                //    case LightingMode.ThreeVertexLights:
                //effect.EnableDefaultLighting();
                //effect.PreferPerPixelLighting = false;
                //        break;

                //    case LightingMode.ThreePixelLights:
                //        effect.EnableDefaultLighting();
                //        effect.PreferPerPixelLighting = true;
                //        break;
                //}

                effect.SpecularColor = new Vector3( 0.8f, 0.8f, 0.6f );
                effect.SpecularPower = 16;
                effect.TextureEnabled = true;
            }

            mesh.Draw();
        }
    }
}

So, after some testing, it looks like toggling full screen loses the vertex buffer. That is, my buffer still exists, but it's not connected to the device anymore. When I took this line from my Update function and added it to my Draw function, everything worked:

    graphics.GraphicsDevice.SetVertexBuffer(gb.vb, 0);

Strangely, the index buffer didn't seem to be affected.

I still don't understand why this is the case, though, given that I can't find a single reference to this phenomenon anywhere. I don't even know if this is something specific to my configuration, or a general issue with vertex buffers and full screen, or what. If anyone has any clues, I'd be grateful.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top