SDL tilemap 渲染很慢

Posted

技术标签:

【中文标题】SDL tilemap 渲染很慢【英文标题】:SDL tilemap rendering quite slow 【发布时间】:2019-07-08 09:00:31 【问题描述】:

我正在使用 SDL 编写一个显示相当大的瓷砖地图(大约 240*240 瓷砖)的模拟。由于我对 SDL 库非常陌生,因此我无法确定在渲染超过 50,000 个图块时性能相当缓慢是否实际上是正常的。每个图块始终可见,大小约为 4*4px。目前它通过 2d 数组迭代每一帧并渲染每一个图块,这给了我大约 40fps 的速度,实际上太慢了,无法将任何游戏逻辑放在系统后面。

我试图找到一些替代系统,例如只更新更新的图块,但人们总是评论这是一种不好的做法,并且渲染器应该每帧都被清理等等。

Here a picture of the map

所以我基本上想问有没有比每帧渲染每个图块更高效的系统。

编辑:以下是我使用的简单渲染方法

void World::DirtyBiomeDraw(Graphics *graphics) 


    if(_biomeTexture == NULL) 
        _biomeTexture = graphics->loadImage("assets/biome_sprites.png");
        printf("Biome texture loaded.\n");
    

    for(int i = 0; i < globals::WORLD_WIDTH; i++) 
        for(int l = 0; l < globals::WORLD_HEIGHT; l++) 
            SDL_Rect srect;

            srect.h = globals::SPRITE_SIZE;
            srect.w = globals::SPRITE_SIZE;

            if(sites[l][i].biome > 0) 
                srect.y = 0;
                srect.x = (globals::SPRITE_SIZE * sites[l][i].biome) - globals::SPRITE_SIZE;
            
            else 
                srect.y = globals::SPRITE_SIZE;
                srect.x = globals::SPRITE_SIZE * fabs(sites[l][i].biome);
            
            SDL_Rect drect = i * globals::SPRITE_SIZE * globals::SPRITE_SCALE, l * globals::SPRITE_SIZE * globals::SPRITE_SCALE, 
                globals::SPRITE_SIZE * globals::SPRITE_SCALE, globals::SPRITE_SIZE * globals::SPRITE_SCALE;



            graphics->blitOnRenderer(_biomeTexture, &srect, &drect);

        
    

所以在这种情况下,每个图块都称为“站点”,这是因为它们还存储了湿度、温度等信息。

每个站点在生成过程中都分配了一个生物群落,每个生物群落基本上都是一个 ID,每个陆地生物群落的 ID 都大于 0,每个水的 ID 都是 0 或更低。

这允许我将每个按 ID 排序的生物群落精灵放入“biome_sprites.png”图像中。所有的土地精灵基本上都在第一排,而所有的水瓦都在第二排。这样我就不必手动将精灵分配给生物群系,并且该方法可以通过将图块大小(基本上是宽度)与生物群系相乘来自行完成。

这是biome ID table from my SDD/GDD 和actual spritesheet。

图形类中的 blitOnRenderer 方法基本上只是运行一个 SDL_RenderCopy 将纹理传送到渲染器上。

void Graphics::blitOnRenderer(SDL_Texture *texture, SDL_Rect 
*sourceRectangle, SDL_Rect *destinationRectangle) 
    SDL_RenderCopy(this->_renderer, texture, sourceRectangle, destinationRectangle);

在游戏循环中,每一帧都会调用 RenderClear 和 RenderPresent。

我真的希望我能理解地解释它,问任何你想要的东西,我是向你们寻求帮助的人,所以我能做的至少是合作:D

【问题讨论】:

不是每一帧的每一块瓷砖,而是将它们全部渲染到一个纹理,然后只渲染该纹理(当然,如果它们不改变)。另外,请确保您使用的是SDL_Texture 而不是SDL_Surface 所有 240x240 的图块是否同时可见?难以置信。即使在 4K 显示器上,每个图块水平方向最多 16 个像素(忽略垂直方向)。渲染中的另一个常见策略是“视锥体剔除”,即不将绝对不可见的内容输入 GPU。 没有代码我们无法分析它。唯一可以确定的是,其中通常存在几个类似或其他类型的幼稚性能瓶颈。 由于您的图块只是彩色方块,我建议构建单个 240x240 表面/纹理并使用单个 SDL_RenderCopy 调用对其进行渲染。 @Scheff 是的,它们在任何时候都同时可见。编辑:我稍后会发布代码 【参考方案1】:

戳 SDL2 开发人员以获得 SDL_RenderCopy() 的多项目版本(类似于现有的 SDL_RenderDrawLines()/SDL_RenderDrawPoints()/SDL_RenderDrawRects() 函数)和/或 batched SDL_Renderer 后端。

现在,您正在尝试将 240*240 = 57000 个绘制调用塞入 GPU 的喉咙;至少在任何给定的 16 毫秒内,您通常只能指望 1000-4000 次绘制调用。

或者切换到 OpenGL 并自己进行批处理。

【讨论】:

这是一个很好的答案:D 我到处寻找限制规范,到目前为止几乎没有发现任何东西。关于基于更新的渲染的任何提示?就像只在更新时渲染图块? @NovayaFinch: 嗯,如果一切都正确批处理(纹理图集 + 大 'ole 几何缓冲区),您可以在一次绘制调用中渲染整个 240x240 地图;如果图块发生变化,您可以通过glBufferSubData() 仅现场上传受影响的图块,或者重新上传整个几何缓冲区; 4-6 MiB 的几何数据并不多。

以上是关于SDL tilemap 渲染很慢的主要内容,如果未能解决你的问题,请参考以下文章

请教unity 制作tilemap的流程

Unity-Tilemap+2D-Extras

Unity2D学习笔记-Tilemap

Unity 简记--TileMap

Unity-TileMap瓦片地图

为啥unity5.6没有tilemap对象