缓存OpenGL纹理

Caching OpenGL textures

本文关键字:纹理 OpenGL 缓存      更新时间:2023-10-16

我正在开发一款使用SDL的2D游戏。由于有些系统的CPU很弱,GPU很强,所以除了普通的SDL/软件外,我还有一个使用OpenGL的渲染器后端。

渲染器界面的简化版本如下所示:

class Renderer {
public:
virtual void render_surface(SDL_Surface* surface) = 0;
virtual void render_text(const std::string& text, const Font& font) = 0;
};

但这有一个问题:每当我使用OpenGL绘制曲面时,我都需要重复glBindTexture调用,这让我损失了很多时间。目前,我有一个基于曲面内存地址的愚蠢缓存,但它显然不适用于动态生成的曲面,例如在render_text中。

我能想到的唯一合适的解决方案是完全改变接口,并让调用方明智地缓存纹理:

class Renderer {
public:
virtual Texture load_surface(SDL_Surface* surface) = 0;
virtual Texture load_text(const std::string& text, const Font& font) = 0;
virtual void render_texture(const Texture& texture) = 0;
};

但这是IMO使用起来有点难看,并且必须为软件渲染器伪造。

对此我还能做些什么吗?

这实际上听起来像是两个独立的问题(至少您提出的解决方案是这样)。我将对这两个问题提出几点建议,因为目前还不完全清楚你想要实现什么。


1.冗余状态更改/绘制调用

您可以始终将渲染命令排队,然后在实际绘制之前按纹理/着色器/其他昂贵状态对它们进行排序(别担心,排序会使其听起来比实际情况更复杂/更昂贵)。

您真正要做的是根据需要的纹理、半透明还是不透明等创建不同的类别来放置绘图命令,然后在收到完成框架所需的所有绘图命令后,以系统的方式运行这些类别。唯一真正的排序将发生在插入时,因为bucket相对较小,所以与在绘图时尝试对一堆随机的命令进行排序相比,成本要低得多。

这就是高性能游戏引擎自《雷神之锤》问世以来的工作原理。这样做的目的是尽可能减少纹理变化并绘制调用。在过去,draw调用本身有很多开销(需要将顶点阵列内存从CPU复制到GPU,并在一些API中切换内核模式上下文),但现在它们仍然很昂贵,但原因不同。如果您可以将尽可能多的与订单无关的绘制操作组合到单个调用中,您通常会显著提高性能。

事实上,PowerVR在硬件层面也做了类似的事情。它等待所有绘制命令,然后将屏幕划分为多个瓦片,在那里它可以确定哪些命令是多余的(例如隐藏的表面),并在必须光栅化任何内容之前将其剔除。只要绘制操作不依赖于顺序(例如阿尔法混合),它就可以减少内存/功耗。


2.GPU存储的低效/非持久使用

在最坏的情况下,您可以随时考虑将纹理打包到图集中。这样,您就不需要为了交换绑定纹理而将breakdraw调用分开,只需要更智能地计算纹理坐标即可。

为此,您经常在GUI的多个帧中打印相同的文本。您可以轻松地编写软件,将渲染的字符串/格式化的段落等缓存为纹理。如果你很聪明的话,你可以将其扩展到整个GUI窗口,并且只有在必须重新绘制渲染窗口时,才能重新打包存储渲染窗口的纹理部分。