XNA - 合并精灵以获得更好的绘图性能?

发布于 2024-08-08 18:18:05 字数 727 浏览 3 评论 0原文

我在某处读到,当渲染大量 3D 对象时,可以将它们合并形成一个巨大的网格,这样就只进行一次绘制调用。因此,让引用:“GPU 发挥其魔力”,而 CPU 可以自由用于除绘制之外的其他调用。

所以我的问题是,在考虑到性能的情况下,在2D环境中这样做是否可行?

例如,假设我们有一个简单的图块系统,而不是对视图中的每个图块进行绘制调用,而是合并所有图块以形成一个大精灵,然后在其上调用绘制。

任何对此事的见解 - 无论是提示、链接还是其他什么 - 都将受到极大的赞赏,因为我之前没有图形性能方面的经验。

编辑:抱歉我的解释不好。我正在创建一个供个人使用的瓦片引擎,并希望它尽可能具有多功能性。因此,我想进行优化,以防万一我在不久的将来必须绘制大量瓷砖。

确实使用了tilesheet,但我的意思是,将要从sheet中绘制的所有tile合并到新的Texture2D中是否会获得性能。例如:

我们有 128x72 的图块要在屏幕上绘制。我们不是循环遍历所有图块并为每个要绘制的图块调用绘图,而是将所有图块合并为一个大小为 1280x720 的新精灵并绘制它。这样,draw() 方法每帧只会被调用一次。我的问题是这是否会提高性能,因为在执行 3D 时会将 3D 对象合并到单个网格中。

因为我收集到的信息是调用draw()会消耗性能,应该尽可能少地调用。有谁来证实或否认吗? :)

I read somewhere that when rendering a lot of 3D objects one could merge these to form a giant mesh so that only one draw call would be made. Therefore letting the, quote: "GPU do its magic", while the CPU is free for other calls than draw.

So to my question, would this be feasible to do in a 2D environment with performance in mind?

For example, say we have a simple tile-system and instead of making a draw call for each tile in view, one would instead merge all tiles to form a large sprite and then call a draw on it.

Any insight into the matter - either tips, links or whatnot - is greatly appreciated since I have no prior experience in graphics performance.

Edit: Sorry for my poor explanation. I am creating a tileengine for personal use and want it to be as versatile as possible. Therefore I want to optimize just in case I have to draw lots of tiles in the near future.

I do use a tilesheet but what I meant with the question is if merging all tiles that are to be drawn from the sheet into a new Texture2D will gain performance. For example:

We have 128x72 tiles to draw on the screen. Instead of looping through all tiles and calling draw for each tile to be drawn, we merge all tiles into a new sprite 1280x720 in size and draws it. This way the draw() method will only be called once per frame. My question was if this will improve performance as it would merging 3d objects into a single mesh when doing 3D.

Because the info I have gathered is that calling draw() is a performance hog and should be called as little as possible. Anyone to confirm or deny? :)

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

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

发布评论

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

评论(4

天赋异禀 2024-08-15 18:18:05

我对 XNA 和 3D 没有任何经验,但我会给你一些关于 2D 游戏的建议。今年年初,我花了一些时间在 XNA 中创建一个图块引擎,并想知道同样的事情。我认为这里的简短答案是肯定的,如果您担心性能,将您的图块组合成更大的精灵是一个好主意。但是,如果您有兴趣,还有更长的答案。

一般来说,当谈到性能优化时,答案几乎总是“不要这样做”。如果您确定需要优化性能,下一个答案几乎总是“暂时不要这样做”。最后,如果您确实尝试优化性能,那么您可以做的最重要的事情就是使用基准测试来收集更改前后性能的精确测量结果。没有它,你不知道自己是否成功!

现在,更多地与 2D 游戏相关,我了解到,我切换纹理的次数越少,我的图块引擎的性能就越好。例如,假设我有一块草地瓷砖和一块砾石瓷砖。如果内存中存在两个独立的纹理,我先绘制一个草图块,然后绘制一个砾石图块,然后将草图块绘制到屏幕上,GPU 将加载草图纹理,然后将其切换出去以加载砾石纹理,然后切换草纹理返回以绘制另一个草图块。这会很快降低性能!解决这个问题的最简单方法是使用 spritesheet,将草和砾石块放入一个纹理中,然后告诉 SpriteBatch 每次从同一纹理上的不同区域进行绘制。

另一件需要考虑的事情是您要同时在屏幕上绘制多少个图块。我不记得具体了,但我一次绘制了数千个图块(在缩小的视图中)。我注意到,当我使用更大的图块并绘制更少的图块时,正如您在问题中所建议的那样,性能也得到了提高。不过,这并没有我在上一段中描述的那么大的改进,我仍然鼓励您衡量不同实现带来的性能变化。另外,如果您只绘制十几或几百个图块,那么现在可能不值得尝试对其进行优化(请参阅第二段)。

只是为了让你知道我并不是完全编造的,这里有一个来自 Shawn Hargreaves 的关于纹理交换、精灵表等的帖子的链接。如果你搜索,XNA 论坛以及 Shawn Hargreaves 的博客上可能有更好的帖子主题。

http://forums.xna.com/forums/p/24254/131437 .aspx#131437

更新:

既然您更新了问题,那么让我更新我的帖子。我决定只对一些示例进行基准测试,以便让您了解可能存在的性能差异。在我的 Draw() 函数中,我有以下内容:

        GraphicsDevice.Clear(Color.CornflowerBlue);

        Stopwatch sw = new Stopwatch();
        sw.Start();

        spriteBatch.Begin();

#if !DEBUG
        spriteBatch.Draw(tex, new Rectangle(0, 0, 
                         GraphicsDevice.Viewport.Width,
                         GraphicsDevice.Viewport.Height), 
                         Color.White);            
#else
        for (int i = 0; i < 128; i++)
            for (int j = 0; j < 72; j++)
            {
                Rectangle r = new Rectangle(i * 10, j * 10, 10, 10);
                spriteBatch.Draw(tex, r, r, Color.White);
            }
#endif
        spriteBatch.End();

        sw.Stop();

        if (draws > 60)
        {
            numTicks += sw.ElapsedTicks;
        }
        draws++;

        if (draws % 100 == 0)
            Console.WriteLine("avg ticks: " + numTicks / (draws - 60));

        base.Draw(gameTime);

只需在“#if !DEBUG”语句中删除感叹号即可在两种方法之间切换。我跳过了前 60 次抽奖,因为它们包含了一些初始设置(不太确定是什么)并且扭曲了平均值。我下载了一张 1280x720 的图像,对于顶部的测试用例,我只画了一次。对于底部测试用例,我在图块中绘制了一个源图像,128x72,就像您在问题中询问的那样。这是结果。

绘制一张图像:

avg ticks: 68566
avg ticks: 73668
avg ticks: 82659
avg ticks: 81654
avg ticks: 81104
avg ticks: 84664
avg ticks: 86626
avg ticks: 88211
avg ticks: 87677
avg ticks: 86694
avg ticks: 86713
avg ticks: 88116
avg ticks: 89380
avg ticks: 92158

绘制 128x72 图块:

avg ticks: 7902592
avg ticks: 8052819
avg ticks: 8012696
avg ticks: 8008819
avg ticks: 7985545
avg ticks: 8028217
avg ticks: 8046837
avg ticks: 8291755
avg ticks: 8309384
avg ticks: 8336120
avg ticks: 8320260
avg ticks: 8322150
avg ticks: 8381845
avg ticks: 8364629

如您所见,存在几个数量级的差异,因此非常显着。测试这种事情非常简单,我建议您针对您的特定设置运行自己的基准测试,以考虑到我可能错过的东西。

I don't have any experience with XNA and 3D, but I will give you some advice for 2D games. I spent some time creating a tile engine in XNA at the beginning of this year and wondered the same thing. I think the short answer here is yes, combining your tiles into a larger sprite is a good idea if you're worried about performance. However, there is a much longer answer if you're interested.

In general, when it comes to performance optimizations, the answer is almost always, "don't do it." If you're sure you need to optimize for performance, the next answer is almost always, "don't do it yet." Finally, if you do attempt to optimize for performance, the most important thing you can do is use benchmarks to gather precise measurements of the performance before and after the changes. Without that, you don't know if you're succeeding!

Now, related more to 2D games, I learned that I saw a lot better performance in my tile engine the less I switched textures. For example, let's say I have a grass tile and a gravel tile. If these are two separate textures in memory, and I draw a grass tile, then a gravel tile, then a grass tile to the screen, the GPU will load the grass texture, then switch it out to load the gravel texture, then switch the grass texture back in to draw another grass tile. This kills performance really quickly! The easiest way to get around this is to have a spritesheet, where you put your grass and gravel tiles into one texture, and just tell SpriteBatch to draw from a different area on the same texture each time.

Another thing to consider is how many tiles you're going to be drawing on the screen at once. I can't remember specifically, but I was drawing thousands of tiles at once (in a zoomed out view). I noticed that when I used bigger tiles and drew fewer of them, as you are suggesting in your question, that performance also improved. This wasn't as big of an improvement as what I described in the last paragraph though, and I would still encourage you to measure the performance changes resulting from different implementations. Also, if you're only drawing a dozen or few hundred tiles it may not be worth it to try to optimize that right now (see the 2nd paragraph).

Just so you know I'm not completely making this up, here's a link to a post from Shawn Hargreaves on texture swapping, spritesheets, etc. There are probably better posts on the XNA forums as well as Shawn Hargreaves' blog if you search on the topic.

http://forums.xna.com/forums/p/24254/131437.aspx#131437

Update:

Since you updated your question, let me update my post. I decided to just benchmark some samples to give you an idea of what the performance differences might be. In my Draw() function I have the following:

        GraphicsDevice.Clear(Color.CornflowerBlue);

        Stopwatch sw = new Stopwatch();
        sw.Start();

        spriteBatch.Begin();

#if !DEBUG
        spriteBatch.Draw(tex, new Rectangle(0, 0, 
                         GraphicsDevice.Viewport.Width,
                         GraphicsDevice.Viewport.Height), 
                         Color.White);            
#else
        for (int i = 0; i < 128; i++)
            for (int j = 0; j < 72; j++)
            {
                Rectangle r = new Rectangle(i * 10, j * 10, 10, 10);
                spriteBatch.Draw(tex, r, r, Color.White);
            }
#endif
        spriteBatch.End();

        sw.Stop();

        if (draws > 60)
        {
            numTicks += sw.ElapsedTicks;
        }
        draws++;

        if (draws % 100 == 0)
            Console.WriteLine("avg ticks: " + numTicks / (draws - 60));

        base.Draw(gameTime);

Just drop the exclamation point in the "#if !DEBUG" statement to switch between the two methods. I skipped the first 60 draws because they included some initial setup (not really sure what) and were skewing the averages. I downloaded one 1280x720 image, and for the top test case I just drew it once. For the bottom test case I drew the one source image in tiles, 128x72 like you asked about in your question. Here are the results.

Drawing one image:

avg ticks: 68566
avg ticks: 73668
avg ticks: 82659
avg ticks: 81654
avg ticks: 81104
avg ticks: 84664
avg ticks: 86626
avg ticks: 88211
avg ticks: 87677
avg ticks: 86694
avg ticks: 86713
avg ticks: 88116
avg ticks: 89380
avg ticks: 92158

Drawing 128x72 tiles:

avg ticks: 7902592
avg ticks: 8052819
avg ticks: 8012696
avg ticks: 8008819
avg ticks: 7985545
avg ticks: 8028217
avg ticks: 8046837
avg ticks: 8291755
avg ticks: 8309384
avg ticks: 8336120
avg ticks: 8320260
avg ticks: 8322150
avg ticks: 8381845
avg ticks: 8364629

As you can see, there's a couple order of magnitude difference there, so it's pretty significant. It's pretty simple to test this kind of thing, and I'd recommend you run your own benchmarks for your specific setup to take into account something I might've missed.

何时共饮酒 2024-08-15 18:18:05

显卡切换纹理的次数越少,性能就越好。如果您愿意,可以在 SpriteBatch.Begin() 方法中按纹理排序,以便显卡尽可能少地切换纹理。

当有意义的时候,我会将我的图块/精灵放入表格中。例如,一个字符可以在一张纸中,并且一层图块可以在同一张纸中。到目前为止,这种方法效果很好。

我想说的是,除非有必要,否则不要优化。如果您的游戏运行得足够快,那么为什么要费心优化呢?

The less the Graphics Card has to switch textures the better the performance. You can sort by Texture in the SpriteBatch.Begin() method if you like so that the Graphics Card will switch textures as little as possible.

I put my tiles / sprites into sheets when it makes sense. For instance one character may be in a sheet and one layer of tiles may be in the same sheet. This has worked pretty well thus far.

I would say that don't optimize until you have to though. If your games runs fast enough as is why bother with optimization.

生活了然无味 2024-08-15 18:18:05

矩形 r = new 矩形(i * 10, j * 10, 10, 10);

创建许多新对象将调用垃圾收集,这会减慢很多!
你不应该在大循环中分配新对象:

Rectangle r = new Rectangle(i * 10, j * 10, 10, 10);

Creating many new objects will call the garbage collection which can slow down a lot!
you should not allocate new objects in big loops:

拿命拼未来 2024-08-15 18:18:05

表现并不是普遍的要求,也不是上帝的诫命;这是达到目的的一种手段。如果您所拥有的性能足够好,那么您实际上不会从提高其性能中获得任何好处。保持“始终保持最佳性能”是过早优化的定义,它会带来比它所防止的更多的麻烦。

Performance is not a universal imperative or a commandment from God; it's a means to an end. If what you have performs well enough, you have literally nothing to gain from improving its performance. Maintaining "the best possible performance at all times" is the definition of premature optimization, and it will cause more headaches than it prevents.

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