【问题标题】:ToggleFullScreen in XNA causes lost device like errorsXNA 中的 ToggleFullScreen 会导致设备丢失,例如错误
【发布时间】:2011-01-17 06:56:30
【问题描述】:

我正在尝试编写我的第一个 XNA 游戏,我想包含 ALT+ENTER 功能以在全屏和窗口模式之间切换。我的游戏现在非常基础,没什么特别的。我可以让它在我的 LoadContent 函数中以窗口(默认)或全屏(通过调用 ToggleFullScreen)运行,并且一切正常。

但是,当我使用完全相同的代码在 Update 函数中调用 ToggleFullScreen 时,我的游戏失败了。如果我开始窗口化并切换到全屏,它似乎会冻结,只显示一帧并且不接受键盘输入(我必须 CTRL+ALT+DEL)。如果我开始全屏并切换到窗口,它会在 DrawIndexedPrimitive 上出错,并显示消息“当前顶点声明不包括当前顶点着色器所需的所有元素。Normal0 丢失。” (这不是真的;我的顶点缓冲区是 VertexPositionNormalTexture 并且包含数据,并且 GraphicsDevice 状态是正常的)。

这两个问题似乎都表明与设备的连接以某种方式丢失,但我不知道为什么。我在网上找到的每个资源都说切换全屏就像调用 ToggleFullScreen 函数一样简单,没有提到重置设备或缓冲区。有什么想法吗?

编辑:这是一些代码,省略了无关的东西:

    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);
        }
    }

【问题讨论】:

    标签: xna fullscreen xna-4.0


    【解决方案1】:

    XNA Game 类和相关的 GraphicsDeviceManager 是围绕 Update 函数用于更新游戏状态而 Draw 函数用于渲染该状态的概念构建的。当您在更新功能期间设置图形设备的状态时,您会打破该假设。

    当您切换到全屏时,设备会丢失。框架的幕后发生了很多神奇的事情来隐藏它的复杂性。必须重新创建与设备关联的资源等。框架假设您在下一个 Draw 函数之前不会再次执行任何渲染操作,因此在此之前它不能保证一致的状态。

    所以...不要在更新功能期间在图形设备上设置缓冲区。在绘图函数开始时执行此操作。

    我不确定这是否在任何地方都明确记录。方法文档更暗示了这一点。 Game.Draw 文档说:

    当游戏确定它是时调用 是时候画一个框架了。覆盖这个 具有游戏特定渲染的方法 代码。

    更新方法说:

    当游戏确定时调用 该游戏逻辑需要处理。 这可能包括管理 游戏状态,用户处理 输入,或模拟的更新 数据。覆盖此方法 游戏特定的逻辑。

    【讨论】:

    • 谢谢,这就是我要找的。现在我的问题是:我真的必须在 every 调用 Draw 时设置缓冲区吗?这似乎会增加很多额外的开销,因为我的缓冲区仅在游戏更新时才会改变。如果我不必在每次绘制调用时都设置它们,我如何确定何时设置它们?
    • 您不必在每次调用绘图时都设置缓冲区,但开销不是很高。为了简单和可靠,我会在每一帧都这样做。在我的代码中,我还在绘制结束时取消设置缓冲区。这是因为如果您在 GraphicsDevice 上主动设置资源时尝试调用 SetData,则会引发异常。
    • 要仅在必要时将它们更改为设备,请在游戏“deviceBuffersDirty”中保留一个布尔标志,当缓冲区更改时将其设置为 true。然后在您的绘图功能中,您可以检查此标志,如果已设置,则将缓冲区设置为设备,然后清除该标志。
    【解决方案2】:

    根据 App Hub 论坛上的一个答案,您必须确保重建投影矩阵。

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

    从那个链接....

    另外,请注意不同的 完全切换时的纵横比 屏幕。如果你在 3D 中,你需要 重建你的投影矩阵。如果 你在 2D 中,你可能需要 将缩放矩阵传递给您的 SpriteBatch.Begin 函数。这 文章有关于做的信息 这是二维的。

    因此,您需要确保您正在这样做(不幸的是,发布链接的文章在 Ziggyware 上,并且该网站已经消失了一段时间,因此要找到他们链接到您的 2D 示例可能必须如果您有兴趣查看它,请使用像 WaybackMachine 这样的存档网站,因为您正在做 3D,我认为它可能不感兴趣)。

    【讨论】:

    • 谢谢,但我认为那只是在谈论您是否更改分辨率。我一直保持在 800x600。编辑:另外,看起来我在切换后正在重建我的投影矩阵。
    【解决方案3】:

    我能够使用以下类切换全屏/窗口模式:

    /// <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();
            }
        }
    }
    

    【讨论】:

    • 这也适用于我,但它只使用 spriteBatch。我猜我的问题与绘制索引图元有关。
    • 我更新了示例以使用 3D 模型,应该也可以。我会看看我可以使用自定义顶点缓冲区生成什么。
    • 与其把一堵代码墙拍成答案,你能编辑到重要的部分吗?
    【解决方案4】:

    因此,经过一些测试,切换全屏似乎会丢失顶点缓冲区。也就是说,my 缓冲区仍然存在,但它不再连接到设备。当我从 Update 函数中取出这一行并将其添加到 Draw 函数时,一切正常:

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

    奇怪的是,索引缓冲区似乎没有受到影响。

    我仍然不明白为什么会这样,因为我在任何地方都找不到对这种现象的单一参考。我什至不知道这是否是我的配置所特有的,还是顶点缓冲区和全屏的一般问题,或者什么。如果有人有任何线索,我将不胜感激。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-04-16
      • 2018-12-20
      • 1970-01-01
      • 2013-11-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多