目录
- 一、说明
- 二、着色器设置
-
- [2.2 捕获的数据格式](#2.2 捕获的数据格式)
- [2.2 高级交错](#2.2 高级交错)
- [2.3 双精度和对齐](#2.3 双精度和对齐)
- [2.4 In-shader规范](#2.4 In-shader规范)
- 三、缓冲区绑定
- 四、反馈过程
- 五、反馈对象
-
- [5.1 反馈暂停和恢复](#5.1 反馈暂停和恢复)
- [5.2 绑定暂停的反馈对象。](#5.2 绑定暂停的反馈对象。)
- 六、反馈渲染
- 七、局限性
一、说明
转换反馈是捕获由顶点处理步骤生成的基元的过程,并将这些基元中的数据记录到缓冲区对象中。这允许保留对象的转换后渲染状态,并多次重新提交此数据。
注意:将提到处理多个流输出的各种函数。对多个流的反馈需要访问 OpenGL 4.0 或 ARB_transform_feedback3 和 ARB_gpu_shader5。因此,如果您无法做到这一点,请忽略任何此类讨论。
二、着色器设置
为了捕获基元,包含最终顶点处理着色器阶段的程序必须与许多参数链接。这些参数必须在链接程序之前设置,而不是在链接程序之后设置。因此,如果要使用 glCreateShaderProgram,则必须使用 in-shader 规范 API,该 API 仅适用于 OpenGL 4.4 或 ARB_enhanced_layouts。
平台问题 (NVIDIA):NV_transform_feedback允许在链接后动态设置这些参数。
唯一对转换反馈很重要的程序对象是提供最后一个顶点处理着色器阶段的程序对象。这些设置可以位于单独程序中的任何其他程序上,但只会使用最后一个活动的"顶点处理"着色器阶段上的设置。
变换反馈可以在两种捕获模式之一下运行。在交错模式下,所有捕获的输出都进入同一个缓冲区,彼此交错。在单独的模式下,每个捕获的输出都进入单独的缓冲区。必须在链接时在程序对象上选择此选项。
注意:单独的捕获模式并不一定意味着它必须捕获到不同的缓冲区对象中。这仅仅意味着它们被捕获到单独的缓冲区结合点中。同一缓冲区对象的不同区域可以绑定到不同的绑定点,从而允许您使用单独的捕获到同一缓冲区对象中。只要绑定区域不重叠,这将起作用。
若要定义程序的捕获设置以及捕获哪些输出变量,请使用以下函数:
c
void glTransformFeedbackVaryings(GLuint program, GLsizei count, const char **varyings, GLenum bufferMode);
bufferMode 是捕获模式。它必须是GL_INTERLEAVED_ATTRIBS或GL_SEPARATE_ATTRIBS
计数是 varyings 数组中的字符串数。此数组指定要捕获的相应着色器阶段的输出变量的名称。提供变量的顺序定义了捕获变量的顺序。这些变量的名称符合命名 GLSL 变量的标准规则。
可以捕获的输出数量以及可以捕获的总分量数量是有限制的。在这些限制内,可以捕获任何输出变量,包括结构类型、数组、接口块成员的输出。
注意:此函数设置程序的所有反馈输出。因此,如果您再次调用它(在链接之前),则会忘记先前的设置。
2.2 捕获的数据格式
转换反馈显式捕获基元。虽然它确实捕获每个顶点的数据,但它是在将每个基元拆分为单独的基元之后捕获的。因此,如果您使用 GL_TRIANGLE_STRIP 进行渲染,并且使用 6 个顶点进行渲染,则会产生 4 个三角形。转换反馈将捕获 4 个三角形的数据。由于每个三角形有 3 个顶点,因此 TF 将捕获 12 个顶点,而不是绘图命令中预期的 6 个顶点。
每个基元都按照处理顺序写入。在基元中,写入的顶点数据的顺序是基元组装后的顶点顺序。这意味着,例如,当使用三角形条带绘制时,捕获的基元将每隔三角形切换两个顶点的顺序。这可确保人脸剔除仍将遵循顶点数据初始数组中提供的顺序。
在每个顶点内,数据按 varyings 数组指定的顺序写入(执行交错渲染时)。如果输出变量是聚合 (struct/array),则聚合的每个成员都按顺序写入。输出基本类型的每个组件都按顺序编写。最终,所有数据都紧密地写入在一起(尽管捕获双精度浮点数可能会导致填充)。
组件将始终是浮点数/双精度、有符号整数或无符号整数,使用 GLfloat/GLdouble、GLint 和 GLuint 的大小。不执行包装或归一化。变换反馈系统没有更灵活的顶点格式规范系统的自动模拟。
当然,这并不妨碍您在着色器中手动将位打包为无符号整数。
2.2 高级交错
版本中的核心 4.6
自版本以来的核心 4.0
核心 ARB 扩展 ARB_transform_feedback3
上述设置仅提供两种模式:所有捕获的输出都进入单独的缓冲区,或者它们都进入同一个缓冲区。在许多情况下,能够将多个组件写入一个缓冲区,同时将其他组件写入其他缓冲区是很有用的。
此外,捕获的交错数据是紧密打包的,每个变量的分量紧跟在前一个分量之后。如果某些数据发生更改而其他数据未发生更改,则能够跳过对某些数据的写入通常很有用。
这些可以通过在 varyings 数组中使用特殊的"varying"名称来实现。这些特殊名称不命名实际的输出变量;它们只会对后续写入产生一些特殊影响。
这些名称及其影响是:
c
gl_NextBuffer
这会导致所有后续输出路由到下一个缓冲区索引。缓冲区从 0 开始,每次在变化列表中遇到这种情况时,缓冲区都会递增 1。
这些缓冲区的数量不得超过可用于转换反馈的缓冲区数。因此,这些数量必须严格少于 GL_MAX_TRANSFORM_FEEDBACK_BUFFERS。
c
gl_SkipComponents#
这会导致系统跳过写入 # 个组件,其中 # 可能从 1 到 4。跳过的组件所覆盖的内存不会被修改。在这种情况下,每个组件都是浮点的大小。
请注意,以这种方式跳过的组件仍计入输出组件数量的限制。
可以将 Geometry Shader 中的输出变量声明为转到特定流。这是通过着色器规范控制的,但存在某些限制会影响高级组件交织。
同一缓冲区不能捕获两个流向不同流的输出。尝试这样做将导致链接器错误。因此,使用具有交错写入的多个流需要使用高级交错将属性路由到不同的缓冲区。
请注意,此功能实际上使单独的捕获模式变得多余。与这些设施交错是独立模式的功能超集,因为它可以单独捕获每个缓冲区的一个输出。
2.3 双精度和对齐
双精度对准
版本中的核心 4.6
自版本以来的核心 4.0
核心 ARB 扩展 ARB_gpu_shader_fp64
单精度浮点数和整数的对齐方式为 4 个 字节。但是,双精度值的对齐方式为 8 个字节。这会导致在捕获转换反馈数据时出现问题。
必须确保组件的对齐。这很容易用浮点数和整数来确保,但双精度需要特别小心。由用户来确保所有双精度数据的 8 字节对齐。具体而言,您必须确保两件事:
每个双精度组件都从 8 字节边界开始。您可能需要使用上面的跳过功能在需要的地方插入填充。
所有进入包含双精度组件的特定缓冲区的顶点数据都必须具有与 8 字节对齐的总顶点数据大小。这可确保第二个顶点将从 8 字节边界开始。因此,您可能需要在顶点数据的末尾添加填充。
例如,如果要捕获以下内容,请按此处定义的顺序:
c
out DataBlock
{
float var1;
dvec2 someDoubles;
float var3;
};
如果要按定义顺序捕获 varyings 数据,则在 varyings 数据中需要以下字符串序列:
c
const char *varyings[] =
{
"DataBlock.var1",
"gl_SkipComponents1", //Padding the next component to 8-byte alignment.
"DataBlock.someDoubles",
"DataBlock.var3",
"gl_SkipComponents1", //Padding out the entire vertex structure to 8-byte alignment.
};
如果不这样做,则会出现未定义的行为。您只需更改捕获它们的顺序即可避免填充。您不必更改在着色器中定义它们的顺序。
2.4 In-shader规范
In-shader 规范
版本中的核心 4.6
自版本以来的核心 4.4
核心 ARB 扩展 ARB_enhanced_layouts
着色器可以定义变换反馈捕获哪些输出,以及它们的捕获方式。当着色器定义它们时,查询程序的转换反馈模式将返回交错模式(因为高级交错使分隔模式成为交错模式的完整子集)。
V·E
布局限定符可用于定义在"变换反馈"操作中捕获哪些输出变量。在着色器中设置这些限定符时,它们将完全覆盖通过 glTransformFeedbackVaryings 从 OpenGL 设置转换反馈输出的任何尝试。
使用 xfb_offset布局限定符声明的任何输出变量或输出接口块都将是转换反馈输出的一部分。必须使用整数字节偏移量指定此限定符。偏移量是从顶点的开头写入到当前缓冲区到此特定输出变量的字节数。
包含值的偏移量(无论是在数组、结构中,还是接口块的成员中,如果整个块都有偏移量),都是根据先前组件的大小计算的,以便按指定的顺序打包它们。任何明确提供的偏移都不允许违反对齐限制。因此,如果定义包含双精度(直接或间接),则偏移量必须对齐 8 字节。
接口模块的成员可以直接在其上指定其偏移量,这将覆盖任何计算出的偏移量。此外,接口模块的所有成员都不需要写入输出(尽管如果您在模块本身上设置xfb_offset,则会发生这种情况)。对于块的所有成员,几何着色器的流分配要求相同,但偏移量则不然。
捕获的不同变量被分配给缓冲区绑定索引。对于单独的缓冲区,偏移分配是分开的。对于同一缓冲区捕获的两个变量具有重叠的字节偏移量(无论是自动计算还是显式分配),都是链接器错误。
显式缓冲区分配是通过在与偏移限定符相同的声明上使用 xfb_buffer 限定符进行的。这需要一个整数,该整数定义捕获的输出与之关联的缓冲区绑定索引。整数必须小于 GL_MAX_TRANSFORM_FEEDBACK_BUFFERS。
未显式指定缓冲区的全局变量或接口块的任何偏移量都将使用当前缓冲区。当前缓冲区设置如下:
c
layout(xfb_buffer = 1) out;
对于未显式指定缓冲区的全局变量,以下所有偏移量都将使用 1 作为其缓冲区。着色器的初始当前缓冲区为 0。
变量可以在不xfb_offset的情况下分配给它们xfb_buffer。这不执行任何操作,将被忽略。
接口块与缓冲区有特殊的关联。每个接口块都与缓冲区相关联,无论是否捕获其任何成员。缓冲区可以是上面定义的当前缓冲区,也可以是xfb_buffer显式指定的缓冲区。
如前所述,不必捕获块的所有成员。但是,如果捕获了块的任何成员,则必须将它们全部捕获到同一缓冲区中。具体来说,就是与该块关联的缓冲区。如果您提供的缓冲区索引与块使用的索引不同,则在成员上使用 xfb_buffer 是错误的。
举个例子:
c
layout(xfb_buffer = 2) out; // Default buffer of 2.
out OutputBlock1 // Block buffer index is implicitly 2.
{
float val1;
layout(xfb_buffer = 2, xfb_offset = 0) first; // The provided index is the same as the block's index.
layout(xfb_buffer = 1, xfb_offset = 0) other; // Compile error, due to changing the buffer index for a block member.
};
每个缓冲区都有步幅的概念。这表示从一个捕获的顶点的开始到下一个顶点的开始的字节计数。它的计算方法是获取具有最高xfb_offset值的输出,将其大小添加到该偏移量,然后将计算值与缓冲区的基本对齐方式对齐。缓冲区的对齐方式为 4,除非它捕获任何双精度值,在这种情况下,缓冲区的对齐方式为 8。这意味着您不需要像使用外部着色器设置那样手动填充结构以进行对齐。
还可以使用xfb_stride布局限定符显式设置缓冲区的步幅。这允许您在最后添加额外的空间,也许可以跳过不会更改的数据。如果指定的步幅为:
考虑到该缓冲区捕获的数据的偏移量和计算大小,太小。
未正确对齐。它必须至少对齐 4 个字节,如果缓冲区捕获任何双精度值,则它必须对齐 8 个字节。
缓冲区的步幅设置如下:
c
layout(xfb_buffer = 1, xfb_stride = 32) out; // Sets stride of buffer 1 to 32. Also, sets buffer 1 to be current.
如果缓冲区中捕获的任何输出在空间中重叠或违反填充,则会导致链接错误。例如:
c
layout(xfb_buffer = 0) out Data
{
layout(xfb_offset = 0) float val1;
layout(xfb_offset = 4) vec4 val2;
layout(xfb_offset = 16) float val3; // Compiler error. val2 covers bytes on the range [4, 20).
};
如果使用的是 Geometry Shader 输出流,并且来自不同流的两个输出被路由到同一缓冲区,则会导致编译器/链接器错误。
注意:将 ARB_enhanced_layouts 用作扩展(在较旧的硬件上)时,如果ARB_transform_feedback3也不可用,则只能输出到单个缓冲区。您仍然可以使用偏移量在顶点属性数据之间放置空格,但不能将xbf_buffer设置为除 0 以外的任何值。
三、缓冲区绑定
一旦你有一个具有适当设置来记录输出的程序,你现在必须设置缓冲区对象来捕获这些值。缓冲区对象及其存储是以通常的方式创建的。改变的是你在哪里使用它们。
当您希望开始转换反馈操作时,必须将一个或多个缓冲区绑定到索引GL_TRANSFORM_FEEDBACK_BUFFER绑定点。这是通过使用 glBindBufferRange 函数(或等效函数)完成的。您提供的偏移量必须是 4 字节对齐的,除非捕获到缓冲区中的数据包含双精度值。在这种情况下,它必须是 8 字节对齐的。
将缓冲区范围绑定到的索引与设置不同输出位置时使用的缓冲区绑定索引相同。在单独的输出模式下,每个列出的输出都进入不同的缓冲区索引,从 0 开始,按照变化提供的顺序顺序分配。在交错模式下,所有输出要么记录到一个缓冲区,要么记录到更高级的布局规范机制中指定的缓冲区。
四、反馈过程
绑定缓冲区后,可以开始反馈操作。为此,请调用以下函数:
c
void glBeginTransformFeedback(GLenum primitiveMode);
这将激活转换反馈模式。当变换反馈模式处于活动状态(且未暂停)时,如果执行绘图命令,则设置为由最终顶点处理阶段捕获的所有输出都将记录到绑定缓冲区中。这些将跟踪上次记录的位置,以便多个绘图命令将添加到记录的数据中。
如果绑定的缓冲区范围不够大,无法容纳记录的数据,则会导致未定义的行为。
具有当前程序分配给 的输出的所有反馈缓冲区绑定索引都必须具有有效的绑定。否则,此函数将失败并显示 OpenGL 错误。
primitiveMode 必须是 GL_POINTS、GL_LINES 或 GL_TRIANGLES 之一。这些模式定义了系统捕获的基元类型。他们还对达到最终原始组装阶段的基元类型施加了限制。该基元基本类型必须与 primitiveMode 匹配。渲染过程的基元类型按给定顺序定义如下:
如果 Geometry Shader 处于活动状态,则它是 GS 输出的基元类型。
如果曲面细分评估着色器处于活动状态,则它是曲面细分过程生成的基元类型。
如果这两个参数都不可用,则它是用于呈现的命令的 mode 参数。
虽然转换反馈处于活动状态(并且不会暂停),但肯定有一些事情是你不能做的:
更改GL_TRANSFORM_FEEDBACK_BUFFER缓冲区绑定。
执行从这些缓冲区的任何部分读取或写入的任何操作(当然,在反馈写入之外)。
为这些缓冲区中的任何一个重新分配存储。这包括无效。
更改当前程序。因此,无法调用 glUseProgram 或 glBindProgramPipeline。这还包括重新链接相应的程序,以及 glUseProgramStages(如果目标管道是绑定管道)。
要结束反馈模式,请调用以下命令:
无效 glEndTransformFeedback();
例如:
c
glUseProgram(g_program);
glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, feedback_buffer, buffer_offset, number_of_bytes);
glBeginTransformFeedback(GL_LINES);
glBindVertexArray(vao);
glDrawElements(GL_POINTS, sizeof(pindices)/sizeof(ushort), GL_UNSIGNED_SHORT, BUFFER_OFFSET(0));
glEndTransformFeedback();
glUseProgram(0);
五、反馈对象
转换反馈对象
版本中的核心 4.6
自版本以来的核心 4.0
核心 ARB 扩展 ARB_transform_feedback2
执行转换反馈操作所需的状态集可以封装到 OpenGL 对象中。转换反馈对象是容器对象。这些是以通常的方式创建和管理的,包括 glGenTransformFeedbacks、glDeleteTransformFeedbacks 等。
若要绑定转换反馈对象,请使用以下命令:
c
void glBindTransformFeedback(GLenum target, GLuint id);
目标必须始终GL_TRANSFORM_FEEDBACK。如果当前转换反馈对象处于活动状态且未暂停,则无法绑定转换反馈对象。
这些对象封装的状态包括:
通用缓冲区绑定目标GL_TRANSFORM_FEEDBACK_BUFFER。因此,所有对 glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, ...) 的调用都会将缓冲区附加到当前绑定的反馈对象。
所有索引GL_TRANSFORM_FEEDBACK_BUFFER绑定。因此,所有对 glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, ...) 的调用(或任何等效函数)都会将缓冲区的给定区域附加到当前绑定的反馈对象。
转换反馈是处于活动状态和/还是暂停状态。
当前反馈操作中记录的基元等的当前计数(如果处于活动状态)。
因此,反馈对象存储要记录到的缓冲区绑定。这样可以轻松地在不同的反馈缓冲区绑定集之间切换,而不必每次都绑定它们。
反馈对象 0 是默认的转换反馈对象。您可以像使用任何其他转换反馈对象一样使用它,只是不能删除它。
注意:上面关于何时对绑定用于转换反馈的缓冲区执行操作是合法的表示法仅适用于附加到当前绑定的反馈对象的缓冲区。因此,如果取消绑定当前转换反馈对象,则可以再次更改它们。
5.1 反馈暂停和恢复
转换反馈对象不仅适用于轻松交换不同的缓冲区集。您可以暂时停止转换反馈操作,执行一些未捕获的渲染,然后使用该操作恢复反馈操作。反馈对象将正确跟踪要记录顶点数据的位置。
要暂时暂停反馈操作,请调用以下函数:
c
void glPauseTransformFeedback();
此时,可以通过绑定不同的反馈对象来取消绑定反馈对象。可以更改当前程序等。反馈操作可以无限期暂停,并且从处于暂停反馈操作中的缓冲区读取数据是合法的(尽管您需要先取消绑定反馈对象)。
若要恢复暂停的反馈操作,必须执行以下操作:
5.2 绑定暂停的反馈对象。
绑定调用 glBeginTransformFeedback 时使用的确切程序或管道对象。
调用此函数:
c
void glResumeTransformFeedback();
请注意,恢复后,原始模式仍将相同。
如果对暂停的反馈对象调用 glEndTransformFeedback,它将正确结束该对象的反馈操作。不能对已结束的反馈对象使用 glResumeTransformFeedback。
六、反馈渲染
主条目:Vertex_Rendering#Transform_feedback_rendering
若要呈现变换反馈操作捕获的数据,可以使用查询对象获取捕获的基元数,将该基元数乘以每个基元的顶点数,然后将该顶点计数馈送到顶点渲染函数中。但是,此过程要求 CPU 从查询对象中读取数据。这可能会引发同步问题。虽然同步对象可以解决其中一些问题,但有更好的方法。
反馈对象记录它们在反馈操作中捕获的顶点数。请注意,反馈操作仅在调用 glEndTransformFeedback 时完成,因此在尝试使用此数据之前,必须先调用该操作。
反馈操作完成后,反馈对象将具有顶点计数。这可以通过绑定反馈对象并调用以下函数之一来用于渲染目的:
c
glDrawTransform反馈
glDrawTransformFeedbackInstanced
glDrawTransformFeedbackStream(使用 OpenGL 4.0 或 ARB_transform_feedback3)
glDrawTransformFeedbackStreamInstanced(使用 OpenGL 4.0 或 ARB_transform_feedback3)
这些函数的工作方式类似于 glDrawArrays(或 glDrawArraysInstanced)。请注意,这些函数不执行任何顶点规范设置工作。它们不会自动获取反馈缓冲区并将它们绑定以用作顶点数据。你必须这样做。所有这些函数所做的都是从反馈操作中获取顶点计数。另请注意,在第一次转换反馈传递时,必须调用非转换 glDraw* 函数才能将顶点数据写入转换反馈缓冲区,因为转换反馈对象尚不具有顶点计数信息。完成此操作后,glDrawTransform* 可以在转换反馈和渲染到屏幕期间使用。
七、局限性
使用单独捕获时,可捕获的变量总数有限制。这是GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,至少是 4。此外,任何特定变量可以包含的分量数量都有限制。这是GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS,至少是 4。如果超过这些限制,将导致程序链接错误。
使用交错捕获时,限制是可以捕获的组件总数。这是GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS,必须至少为 64。双精度组件计为 2。可以通过将缓冲区的所有步幅相加并除以 4 来计算着色器内规范的"组件"数量。该值必须小于此查询值。
使用高级交错将不同的变量路由到不同的缓冲区时,可用缓冲区的数量限制GL_MAX_TRANSFORM_FEEDBACK_BUFFERS。
在 OpenGL 4.0 或 ARB_transform_feedback3 之前,GL_TRANSFORM_FEEDBACK_BUFFER 的 glBindBufferRange 绑定索引限制为GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,因为多个缓冲区只能用于单独的输出。使用 OpenGL 4.0 或 ARB_transform_feedback3,这是GL_MAX_TRANSFORM_FEEDBACK_BUFFERS。