Quantcast
Channel: 英特尔开发人员专区文章
Viewing all articles
Browse latest Browse all 583

OpenGL* 性能提示: 原子计数器缓冲区与着色器存储缓冲区对象

$
0
0

下载 PDF [PDF 703 KB]  
下载代码示例

简介

OpenGL* 提供了两种存储和检索数据的机制,作为着色器的输入和输出。 游戏开发人员可以选择着色器存储缓冲区对象 (SSBO) 或原子计数缓冲区(ACB)。 本文演示了编写图形密集型游戏时,使用 ACB 替代 SSBO 没有实际的性能优势。

本文随附了一个可在 SSBO 和 ACB 之间交替的简单 C++ 应用。 游戏开发人员可以看到两种方法对渲染性能的效果(毫秒/帧)。 尽管本文适用于图形游戏开发人员,但这些概念适用于使用 OpenGL 4.3 及更高版本的所有应用。 示例代码用 C ++ 编写,专为 Windows 8.1 和 10 设备而设计。

要求

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

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

原子计数器缓冲区与着色器存储缓冲区对象

SSBO 是一个用于在 OpenGL 中存储和检索数据的缓冲区对象,为着色器的输入和输出提供了一个通用机制。 另一个存储选项是 ACB,支持原子内存操作的 OpenGL 存储机制。

相比 SSBO,ACB 在使用上更有优势。如果您符合其限制,如 OpenGL foundation 的网站所示:https://www.opengl.org/wiki/Atomic_Counter,我们一般推荐使用 ACB 而不是 SSBO。

  • 原子计数器只能是无符号整数
  • 它们只能递增或递减 1
  • 原子计数器内存访问不一致,也就是说,它不使用普通的 OpenGL 内存模型

从性能角度而言,ACB 相比 SSBO 没有优势。 这是因为 ACB 在内部实现为 SSBO 原子操作,因此利用 ACB 没有实际的性能优势。

本文随附的应用通过在 SSBO 和 ACB 之间交替来说明这一点,同时显示当前的每帧毫秒数和每秒帧数。 按空格键可在 SSBO 和 ACB 之间切换。 出现这种情况时,图像将呈现动画显示效果,以表明发生了变化。

Skylake 处理器图形

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

与前几代相比,第六代智能英特尔酷睿处理器具有许多新的优势,可显著提升整体计算性能和视觉性能。 示例增强功能包括一个 GPU,加上 CPU 的增强计算能力,可提供比之前的英特尔® 处理器显卡高出 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. 双击打开lesson4_ACBvsSSBO/lesson4.sln文件,启动 Microsoft Visual Studio 2013。
  3. 选择 <Build>/<Build Solution>,构建应用。
  4. 构建成功后,您可以在 Visual Studio 中运行示例。

应用运行时,主窗口将打开,您将看到一个使用 SSBO 渲染的图像,以及 Microsoft Visual Studio 2013 控制台窗口中显示的性能测量结果。 按空格键在 SSBO 和 ACB 之间交替,查看性能差异。 切换模式时,图像将呈现动画显示效果以直观地反映变化。 按 ESC 退出应用。

代码亮点

这一示例针对 ACB 和 SSBO 使用单独的着色器;其定义如下。 动画使用一个单独的着色器,以表示应用已在 ACB 和 SSBO 之间切换。

// Fragment shader used for animation gets output color from a texture

static std::string aniFragmentShader =

    "#version 430 core\n""\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 used bor ACB gets output color from a texture

static std::string acbFragmentShader =

    "#version 430 core\n""\n""uniform sampler2D texUnit;\n""\n""layout(binding = 0) uniform atomic_uint acb[" s(nCounters) "];\n""\n""smooth in vec2 texcoord;\n""\n""layout(location = 0) out vec4 fragColor;\n""\n""void main()\n""{\n""    for (int i=0; i<"  s(nCounters) "; ++i) atomicCounterIncrement(acb[i]);\n""    fragColor = texture(texUnit, texcoord);\n""}\n"

;



// Fragment shader used for SSBO gets output color from a texture

static std::string ssboFragmentShader =

    "#version 430 core\n""\n""uniform sampler2D texUnit;\n""\n""smooth in vec2 texcoord;\n""\n""layout(location = 0) out vec4 fragColor;\n""\n""layout(std430, binding = 0) buffer ssbo_data\n""{\n""    uint v[" s(nCounters) "];\n""};\n""\n""void main()\n""{\n""    for (int i=0; i<" s(nCounters) "; ++i) atomicAdd(v[i], 1);\n""    fragColor = texture(texUnit, texcoord);\n""}\n"

;

着色器进行了编译和准备,以便使用。

// compile and link the shaders into a program, make it active
vShader     = compileShader(vertexShader,   GL_VERTEX_SHADER);
anifShader  = compileShader(aniFragmentShader, GL_FRAGMENT_SHADER);
acbfShader  = compileShader(acbFragmentShader, GL_FRAGMENT_SHADER);
ssbofShader = compileShader(ssboFragmentShader, GL_FRAGMENT_SHADER);
aniProgram  = createProgram({ vShader, anifShader });
acbProgram  = createProgram({ vShader, acbfShader });
ssboProgram = createProgram({ vShader, ssbofShader });
aniOffset   = glGetUniformLocation(aniProgram, "offset");                    GLCHK;
aniTexUnit  = glGetUniformLocation(aniProgram, "texUnit");                   GLCHK;
acbOffset   = glGetUniformLocation(acbProgram, "offset");                    GLCHK;
acbTexUnit  = glGetUniformLocation(acbProgram, "texUnit");                   GLCHK;
ssboOffset  = glGetUniformLocation(ssboProgram, "offset");                   GLCHK;
ssboTexUnit = glGetUniformLocation(ssboProgram, "texUnit");                  GLCHK;

ACB 和 SSBO 一样。

// create and configure the Atomic Counter Buffer

glGenBuffers(1, &acb);                                                       GLCHK;

glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, acb);                          GLCHK;

glBufferData(GL_ATOMIC_COUNTER_BUFFER, nCounters * 4, NULL, GL_STATIC_COPY); GLCHK;

glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, acb);                          GLCHK;


// create and configure the Shader Storage Buffer Object

glGenBuffers(1,&ssbo);                                                      GLCHK;

glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo);                         GLCHK;

glBufferData(GL_SHADER_STORAGE_BUFFER, nCounters * 4, NULL, GL_STATIC_COPY); GLCHK;

glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo);                         GLCHK;

GLuint idx = glGetProgramResourceIndex(ssboProgram, GL_SHADER_STORAGE_BLOCK,

                                       "ssbo_data");                         GLCHK;

glShaderStorageBlockBinding(ssboProgram, idx, 0);                            GLCHK;

// configure texture unit
glActiveTexture(GL_TEXTURE0);                                                GLCHK;
glUseProgram(aniProgram);                                                    GLCHK;
glUniform1i(aniTexUnit, 0);                                                  GLCHK;
glUseProgram(acbProgram);                                                    GLCHK;
glUniform1i(acbTexUnit, 0);                                                  GLCHK;
glUseProgram(ssboProgram);                                                   GLCHK;
glUniform1i(ssboTexUnit, 0);                                                 GLCHK;

最后,准备纹理。

    // create and configure the textures

    glGenTextures(1, &texture);                                                  GLCHK;

    glBindTexture(GL_TEXTURE_2D, texture);                                       GLCHK;

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);                GLCHK;

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);                GLCHK;

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);           GLCHK;

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);           GLCHK;



    // load texture image

    std::vector<GLubyte> img; unsigned w, h;

    if (lodepng::decode(img, w, h, "sample.png"))                        __debugbreak();



    // upload the non-pow2 image to vram

    glBindTexture(GL_TEXTURE_2D, texture);                                       GLCHK;

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE,

                 &img[0]);                                                       GLCHK;

}

绘制图像时,选择正确的着色器程序:

// GLUT display function.   Draw one frame's worth of imagery.

void display()

{

    // attributeless rendering

    glClear(GL_COLOR_BUFFER_BIT);                                                GLCHK;

    glBindTexture(GL_TEXTURE_2D, texture);                                       GLCHK;

    if (animating) {

        glUseProgram(aniProgram);                                                GLCHK;

        glUniform1f(aniOffset, animation);                                       GLCHK;

    } else if (!selector) {

        glUseProgram(acbProgram);                                                GLCHK;

        glUniform1f(acbOffset, 0.f);                                             GLCHK;

    } else {

        glUseProgram(ssboProgram);                                               GLCHK;

        glUniform1f(ssboOffset, 0.f);                                            GLCHK;

    }

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);                                       GLCHK;

    if (!animating && selector) {

        glMemoryBarrier(GL_ALL_BARRIER_BITS);                                    GLCHK;

    }

    glutSwapBuffers();

}

每次绘制视频帧时,控制台中将更新性能输出,应用检查空格键或 ECS 是否已按下。 按空格键会让应用移动到阵列中的下一组组合;按 ESC 即可退出应用。 在 ACB 和 SSBO 之间交替时,性能测量结果将重置,图像将呈现动画显示效果,以表示发生了变化。 如果没有按下任何键,则渲染下一帧。

// GLUT idle function.  Called once per video frame.  Calculate and print timing reports and handle console input.

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 ^= 1; skip = 0;

            cnt = start = 0;

            print();

        }

    }



    // Or measuring

    else if (sec >= 2)

    {

        printf("frames rendered = %I64u, uS = %I64u, fps = %f,

               milliseconds-per-frame = %f\n", cnt, us, cnt * 1000000. / us,

               us / (cnt * 1000.));

        if (swap) {

            animating = true; animationStart = now; swap = false;

        } else {

            cnt = start = 0;

        }

    }



    // 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();

}

结论

出于各种原因,OpenGL foundation 推荐使用 ACB 替代 SSBO,但这样做并不是为了提高性能。 这是因为 ACB 在内部实现为 SSBO 原子操作;因此,利用 ACB 没有实际的性能优势。 决策通常取决于您是否能够承受 ACB 的局限性。

通过将这种技术与第六代智能英特尔酷睿处理器的优势相结合,图形游戏开发商可以确保其游戏按照设计的方式运行。

下载代码示例

下面是 Github 上的代码示例的链接

https://github.com/IntelSoftware/OpenGLBestPracticesfor6thGenIntelProcessor

参考资料

第六代智能英特尔® 酷睿™ 处理器(代号为 Skylake)概述

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

关于作者

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


Viewing all articles
Browse latest Browse all 583

Trending Articles