XNA 3.1 至 4.0 需要不断重绘,否则会显示紫屏

发布于 2024-10-03 17:34:35 字数 787 浏览 0 评论 0原文

对于我的游戏中的菜单,我将它们绘制到屏幕上一次,然后仅在它们被视为脏时才重新绘制。每当用户执行应导致重绘的操作时,都会通过设置为 true 的布尔值来处理此问题,然后绘制循环将在绘制菜单之前检查该值。这个逻辑在 3.1 中完美运行,但在 4.0 中菜单会闪烁(绘制 1 帧),然后显示紫色屏幕,直到再次绘制。

我在 4.0 中创建了一个非常简单的测试游戏来演示如下所示的问题。您会注意到屏幕看起来只是紫色。如果将 _isDirty 设置为 false 的行删除,您将看到矢车菊蓝色背景。

public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    bool _isDirty = true;

    public Game1()
    {
        graphics = new GraphicsDeviceManager(this);
    }
    protected override void Draw(GameTime gameTime)
    {
        if (_isDirty)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            _isDirty = false;
        }
        base.Draw(gameTime);
    }
}

我将如何从 XNA 3.1 获取行为?我见过几个人提到 PreserveContents,但这在 4.0 中似乎没有任何效果,除非我错误地应用它。

For menus in my game, I draw them once to the screen, then only redraw if they've been deemed dirty. This is handled through a boolean set to true whenever the user performs an action that should cause a redraw, and then the draw loop will check that value before drawing the menu. This logic worked perfectly in 3.1, but in 4.0 the menu will flicker (drawn for 1 frame) then show a purple screen until drawn again.

I've created a very simple test game in 4.0 to demonstrate the issue shown below. You will notice that the screen just looks purple. If you remove the line setting _isDirty to false, you will see the cornflower blue background.

public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    bool _isDirty = true;

    public Game1()
    {
        graphics = new GraphicsDeviceManager(this);
    }
    protected override void Draw(GameTime gameTime)
    {
        if (_isDirty)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            _isDirty = false;
        }
        base.Draw(gameTime);
    }
}

How would I go about getting the behavior from XNA 3.1? I've seen several people mention PreserveContents, but that doesn't seem to have any effect in 4.0 unless I'm applying it incorrectly.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

ま柒月 2024-10-10 17:34:35

以下是 XNA 游戏中调用内容的粗略概述:

Update
BeginDraw
Draw
EndDraw

EndDraw 的默认实现最终调用 GraphicsDevice.Present。打开双缓冲后,这会交换后缓冲区和前缓冲区。

所以发生的事情是:

  • 第一次:您将场景绘制到后台缓冲区,然后让 XNA 将其交换到前面。

  • 第二次:您什么也不绘制,让表面填充 DirectX 初始化这些表面的紫色,并将交换到前面!

  • 随后的时间:您没有绘制任何内容,因此您会看到这两个表面之间的显示闪烁。

有多种方法可以抑制 XNA 中的绘制。我在这个类似问题的答案中回顾了它们< /a>.正如在该答案中一样,我建议您重写 BeginDraw,如下所示:

protected override bool BeginDraw()
{
    if(_isDirty)
        return base.BeginDraw();
    else
        return false;
}

BeginDraw 返回 false 时,DrawEndDraw (因此 Present) 不会被称为该框架。不会绘制任何内容,前/后缓冲区也不会交换。

Here is a rough overview of what gets called in an XNA game:

Update
BeginDraw
Draw
EndDraw

The default implementation of EndDraw ultimately calls GraphicsDevice.Present. With double-buffering turned on, this swaps the back and front buffers.

So what is happening is:

  • First time around: you're drawing your scene to the back buffer and then letting XNA swap it to the front.

  • Second time around: you're drawing nothing, leaving the surface filled with the purple colour that DirectX initialises these surfaces to, and swapping that to the front!

  • Subsequent times: you're drawing nothing, so you'll see the display flicker between these two surfaces.

There are several ways to suppress drawing in XNA. I go over them in this answer to a similar question. As in that answer, I recommend you override BeginDraw, like so:

protected override bool BeginDraw()
{
    if(_isDirty)
        return base.BeginDraw();
    else
        return false;
}

When BeginDraw returns false, Draw and EndDraw (and so Present) will not be called that frame. Nothing will draw and the front/back buffers won't swap.

独﹏钓一江月 2024-10-10 17:34:35

我最近也偶然发现了这个问题;我不认为这是翻转缓冲区的问题。 3.1 和 4.0 应该是一样的。我用 ILSpy 查看 GraphicsDevice 并发现了这一点:

发生的变化是,在 4.0 中,如果设置了私有标志,则首先在 GraphicsDevice.Present() 中清除当前渲染目标。默认情况下,最初将设置此标志(渲染目标是干净的)。绘制到渲染目标会清除此标志(渲染目标现在是脏的)。履行其职责后,GraphicsDevice.Present() 再次设置标志(渲染目标被“刷新”到您的窗口,现在再次被认为是干净的)。

要从 GraphicsDevice.Present() 获取结果,您需要严格按顺序调用 Draw() 和 EndDraw()。

如果您在没有事先调用 Draw() 的情况下调用 EndDraw(),您将无法获得渲染目标的内容(已被清除),而只能获得紫色屏幕。

不幸的是,该标志是私有的,没有干净的方法来获取它。您可以使用反射来清除标志,如下所示,强制渲染目标变脏而不绘制任何内容:

graphicsDevice.GetType().GetField("lazyClearFlags", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(graphicsDevice, 0);

(graphicsDevice 是您当前的 GraphicsDevice 实例)

将以上行放在调用 EndDraw() 之前,行为将恢复为 3.1 中的状态

I recently stumbled into this too; I do not think it's a problem with flipping buffers. That should be the same in 3.1 and 4.0. I looked at GraphicsDevice with ILSpy and found out this:

What has changed is that in 4.0, the current render target is first cleared in GraphicsDevice.Present() if a private flag is set. By default, this flag will be set initially (render target is clean). Drawing into the render target clears this flag (render target now dirty). After performing it's duty, GraphicsDevice.Present() again sets the flag (render target was "flushed out" to your window and is now again considered clean).

To get results from GraphicsDevice.Present(), you'll need to call Draw() and EndDraw() in strict sequence.

If you call EndDraw() without a prior call to Draw(), you will not get the contents of the render target (which gets cleared), only the purple screen instead.

Unfortunately, the flag is private and there's no clean way to get at it. You can use reflection to clear the flag like this, forcing the render target dirty without drawing anything into it:

graphicsDevice.GetType().GetField("lazyClearFlags", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(graphicsDevice, 0);

(graphicsDevice is your current GraphicsDevice instance)

Place the above line before your call to EndDraw(), and the behavior reverts to what it was in 3.1

九厘米的零° 2024-10-10 17:34:35

在我的特定情况下,我有一个状态机,可以在负责绘图的状态之间切换。因此,当我在两个游戏状态之间转换时,没有任何内容可绘制,屏幕会闪烁紫色,直到下一个游戏状态处于活动状态。

我解决这个问题的方法很简单,在更新中,如果我没有状态,则调用游戏对象中的 SuppressDraw() 方法。这可以防止在下一次更新发生之前进行绘制。

我想没有什么可以阻止你总是在 Update 中调用它,除非你设置了 isDirty 标志。但您必须准备好处理屏幕可能被损坏的其他事件/情况。

In my particular case, I have a state machine that switches between states that are responsible for drawing. So when I transition between two game states, there is nothing to draw, and the screen flashes purple until the next game state is active.

What I did to solve it was simply, in the Update, if I have no state, call the SuppressDraw() method in the game object. This prevents drawing from taking place until the next Update takes place.

I suppose that there is nothing stopping you from always calling it in Update, except when you set an isDirty flag. But you would have to be prepared to handle other events/cases where the screen might get trashed.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文