OpenGL* 性能提示:相比图像,纹理具有更出色的渲染性能

简介

本文讨论了为何使用纹理而非图像可以提升 OpenGL 渲染性能。 为说明这一问题,我们在一款简单的 C++ 应用中轮流使用纹理和图像。 该应用旨在表明使用这两种技术时对渲染性能(每帧毫秒数)的影响。 尽管本文适用于图形游戏开发人员,但这些概念适用于使用 OpenGL 4.3 及更高版本的所有应用。 示例代码用 C ++ 编写,专为 Windows* 8.1 和 10 设备而设计。

要求

构建和运行示例应用需要具备以下条件:

  • 采用第六代智能英特尔® 酷睿™ 处理器(代号为 Skylake)的电脑
  • OpenGL 4.3 或更高版本
  • Microsoft Visual Studio* 2013 或更高版本

相比图像,纹理具有更出色的渲染性能

使用纹理而非图像可帮助 OpenGL 实现最佳的渲染性能。 通过轮流使用纹理和 2D 图像,附带的应用证明了这一结论。 每种技术目前实现的性能(以每帧毫秒数进行显示),与每秒帧数一起显示在控制台窗口中。 按下空格键可轮流显示各种组合,方便您进行比较。 进行切换时,图像会显示动画效果,可视化表示切换操作。

测量纹理时使用了下列组合:

  • GL_TEXTURE_MAX_LEVEL
  • GL_TEXTURE_BASE_LEVEL
  • GL_TEXTURE_MAG_FILTER
  • GL_TEXTURE_MIN_FILTER

为公平衡量使用图像时的性能,该应用计算了纹理中与纹理采样硬件在纹理 mipmap 链中处理的图像大小最接近的图像。 然后使用该大小的图像。 您可以在 reshape() 函数中看到计算结果。 这意味着屏幕图像越大,应用在 mipmap 链中使用的图像就越大。 反之亦然。

Skylake 处理器图形

第六代智能英特尔酷睿处理器提供卓越的 2D 和 3D 图形性能,最高可达 1152 GFLOPS。 其多核架构可提高性能并增加每个时钟周期的指令数。

与前几代相比,这些处理器具有许多新的优势,可显著提升整体计算性能和视觉性能。 示例增强功能包括 GPU,结合性能更高的 CPU,GPU 可提供相比前代英特尔® 处理器高出最多 40% 的图形性能。 第六代智能英特尔酷睿处理器经过重新设计,可提供更高保真度的视频输出,更高分辨率的视频播放,以及对更低功耗系统的更加无缝的响应能力。 Skylake 支持 4K 视频播放和扩展的超频,是游戏开发人员的理想之选。

GPU 内存访问包含原子最小值、最大值以及共享本地内存或全局内存中 32 位浮点值的比较和交换。 这一新架构还为到同一地址的紧接原子提供了性能改进。 平铺资源包括对部分驻留(稀疏)的大型纹理和缓冲区的支持。 读取未映射磁贴会返回零,并且对其的写入操作将被丢弃。 此外还有用于固定 LOD 和获取操作状态的新着色器指令。 现在还支持较大的纹理和缓冲区。 (例如,您最大可使用 128k x 128k x 8B 的细化 2D 纹理。)

由图形 API 提供支持时,无边界资源可将一个着色器可使用的动态资源数量从大约 256 个增加至 2,000,000 个。 这一改变可降低与更新绑定表相关的开销,为程序员提供更高的灵活性。

执行单元也改进了本地 16 位浮点支持。 使用半精度时,这种增强的浮点支持可带来功耗和性能方面的双重优势。

显示功能进一步提供了多平面重叠选项,拥有在显示时缩放、转换、颜色校正和构成多个曲面的硬件支持。 曲面可来自使用不同更新频率和分辨率的单独交换链(例如,在放大、较低分辨率的帧渲染基础上组合的全分辨率 GUI 元素),从而提供显著增强。

其架构支持最多三个切片的 GPU(提供 72 个 EU)。 这一架构还提供了更高的功率门控和时钟域灵活性,这是值得利用的。

构建和运行应用

按照下面的步骤编译和运行示例应用。

  1. 下载包含示例应用源代码的 ZIP 文件,将其解压到工作目录下。
  2. 双击打开 lesson3_textureVsImage/lesson3.sln 文件,启动 Microsoft Visual Studio 2013。
  3. 选择 <Build>/<Build Solution>,构建应用。
  4. 构建成功后,您可以在 Visual Studio 中运行示例。

应用运行时,主窗口将打开,您将看到一个使用纹理或图像组合渲染的图像,以及 Microsoft Visual Studio 2013 控制台窗口中显示的性能测量结果。 按空格键切换至下一种模式,查看性能差异。 切换模式时,图像将呈现动画显示效果以直观地反映变化。 按 ESC 便可退出应用。

代码亮点

该示例使用纹理或图像。 下面为纹理和图像碎片着色器:

// Fragment shader gets output color from texture() 
static std::string texFragmentShader = 
    "#version 430 coren" 
    "n" 
    "uniform sampler2D texUnit;n" 
    "n" 
    "smooth in vec2 texcoord;n" 
    "n" 
    "layout(location = 0) out vec4 fragColor;n" 
    "n" 
    "void main()n" 
    "{n" 
    "    fragColor = texture(texUnit, texcoord);n" 
    "}n" 
; 

// Fragment shader gets output color from imageLoad() 
static std::string imgFragmentShader = 
    "#version 430 coren" 
    "n" 
    "readonly restrict layout(rgba8) uniform image2D texUnit;n" 
    "n" 
    "smooth in vec2 texcoord;n" 
    "n" 
    "layout(location = 0) out vec4 fragColor;n" 
    "n" 
    "void main()n" 
    "{n" 
    "    fragColor = imageLoad(texUnit, ivec2(texcoord * imageSize(texUnit)));n" 
    "}n" 
;

应用将测试的组合存储在阵列中。

// 结构阵列,我们针对每个选项测试一个项目
#define I(texture, magFilter, minFilter, maxLevel, baseLevel) texture,                #texture, magFilter, #magFilter, minFilter, #minFilter, maxLevel, baseLevel 
struct { 
    GLuint& texture;   const char* textureStr; 
    GLint   magFilter; const char* magFilterStr; 
    GLint   minFilter; const char* minFilterStr; 
    GLint   maxLevel; 
    GLint   baseLevel; 
} options[] = { 
    { I(magTexture, GL_NEAREST, GL_NEAREST,                0,        0 )}, 
    { I(magTexture, GL_LINEAR,  GL_NEAREST,                0,        0 )}, 
    { I(minTexture, GL_NEAREST, GL_NEAREST,                0,        0 )}, 
    { I(minTexture, GL_NEAREST, GL_LINEAR,                 0,        0 )}, 
    { I(minTexture, GL_NEAREST, GL_NEAREST_MIPMAP_NEAREST, mipLevel, 0 )}, 
    { I(minTexture, GL_NEAREST, GL_NEAREST_MIPMAP_LINEAR,  mipLevel, 0 )}, 
    { I(minTexture, GL_NEAREST, GL_LINEAR_MIPMAP_NEAREST,  mipLevel, 0 )}, 
    { I(minTexture, GL_NEAREST, GL_LINEAR_MIPMAP_LINEAR,   mipLevel, 0 )}, 
};

在绘制图像时,使用的选项基于该阵列。 使用 GL_TEXTURE_MAX_LEVEL、GL_TEXTURE_BASE_LEVEL、GL_TEXTURE_MAG_FILTER 和 GL_TEXTURE_MIN_FILTER,您可以看到纹理的使用效果。

// GLUT display function.   Draw one frame's worth of imagery.
void display()
{
    // attribute-less rendering
    glClear(GL_COLOR_BUFFER_BIT);                                               GLCHK;
    if (animating) {
        glUseProgram(texProgram);                                               GLCHK;
        glUniform1f(texOffset, animation);                                      GLCHK;
    } else if (mode) {
        glUseProgram(texProgram);                                               GLCHK;
        glUniform1f(texOffset, 0);                                              GLCHK;
        glBindTexture(GL_TEXTURE_2D, options[selector].texture);                GLCHK;
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, options[selector].maxLevel);
                       GLCHK;
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL,
                        options[selector].baseLevel);                           GLCHK;
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, 
			   options[selector].magFilter);                           GLCHK;
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, 
                        options[selector].minFilter);                           GLCHK;
    } else {
        glUseProgram(imgProgram);                                               GLCHK;
        glUniform1f(imgOffset, 0);                                              GLCHK;
        glBindImageTexture(0, minTexture, selector < 2 ? 7 : imgLevel, GL_FALSE, 0,
                    GL_READ_ONLY, GL_RGBA8);                             GLCHK;
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);      GLCHK;
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
                       GLCHK;
    }
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);                                      GLCHK;
    glutSwapBuffers();
}

每次绘制视频帧时,控制台就会更新性能输出,且应用会检查空格键或 ESC 是否已按下。 按下空格键会导致应用切换至阵列中的下一个组合,按下 ESC 则退出应用。 加载新组合时,性能测量会重置,图像会呈现动画效果,表明设置已改变。 如果未按动任何键,下一个帧会被渲染。

/ GLUT 空闲函数。  每个视频帧调用一次。  计算并打印时间报告,处理控制台收入。
void idle() 
{ 
    // Calculate performance 
    static unsigned __int64 skip;  if (++skip < 512) return; 
    static unsigned __int64 start; if (!start && 
             !QueryPerformanceCounter((PLARGE_INTEGER)&start))    _debugbreak();
    unsigned __int64 now;  if (!QueryPerformanceCounter((PLARGE_INTEGER)&now))
                               __debugbreak(); 
    unsigned __int64 us = elapsedUS(now, start), sec = us / 1000000;
    static unsigned __int64 animationStart;
    static unsigned __int64 cnt; ++cnt;

    // We're either animating
    if (animating)
    {
        float sec = elapsedUS(now, animationStart) / 1000000.f; if (sec < 1.f) {
            animation = (sec < 0.5f ? sec : 1.f - sec) / 0.5f;
        } else {
            animating = false;
            selector = (selector + 1) % _countof(options); skip = 0;
            cnt = start = 0;
            mode ^= 1;
            print();
            printf("%s: ", (mode ? "texture()" : "image2d()")); fflush(stdout);
        }
    }

    // Or measuring
    else if (sec >= 2)
    {
        printf("frames rendered = %I64u, uS = %I64u, fps = %f,
               milliseconds-per-frame = %fn", cnt,  us,  cnt * 1000000. / us,
               us  / (cnt * 1000.));
        if (advance) {
            animating = true; animationStart = now; advance = false;
        } else {
            cnt = start = 0;
            mode ^= 1;
            printf("%s: ", (mode ? "texture()" : "image2d()")); fflush(stdout);
        }
    }

    // Get input from the console too.
    HANDLE h = GetStdHandle(STD_INPUT_HANDLE); INPUT_RECORD r[128]; DWORD n;
    if (PeekConsoleInput(h, r, 128, &n) && n)
        if (ReadConsoleInput(h, r, n, &n))
            for (DWORD i = 0; i < n; ++i)
                if (r[i].EventType == KEY_EVENT && r[i].Event.KeyEvent.bKeyDown)
                    keyboard(r[i].Event.KeyEvent.uChar.AsciiChar, 0, 0);

    // Ask for another frame
    glutPostRedisplay(); 
}

结论

该示例表明,相比使用图像,渲染时使用纹理具有性能优势。 这个结论对努力实现最高游戏性能的图形游戏开发人员至关重要。

将这一技术与先进的第六代智能英特尔酷睿处理器相结合,图形游戏开发人员可确保游戏运行符合设计初衷。

下载代码示例

此处为 Github 上的代理示例链接
https://github.com/IntelSoftware/OpenGLBestPracticesfor6thGenIntelProcessor

参考资料

第六代智能英特尔® 酷睿™ 处理器(代号 Skylake)概述
图形 API 开发人员第六代智能英特尔® 酷睿™ 处理器指南

关于作者

Praveen Kundurthy 任职于英特尔® 软件和服务事业部。 他拥有计算机工程硕士学位。 他主要专注于移动技术、Microsoft Windows* 和游戏开发领域。

有关编译器优化的更完整信息,请参阅优化通知