sot*_*ips 3 opengl-es core-graphics core-video ios
我有一个 CVImageBuffer,它的记录高度为 640 像素,宽度为 852 像素。每行的字节数为 3456。您会注意到 3456/852px != 4(类似于 4.05)。经过一些检查,864 将是使 bytesPerRow/width = 4.0 的宽度。因此,每行似乎都有一个额外的 12px(填充在右侧)。我假设这是因为这些缓冲区针对该图像没有的某些倍数进行了优化。
当我在 OpenGL 中渲染出这个缓冲区时,它看起来很糟糕(见下文)。我注意到该模式每 71 像素重复一次,这是有道理的,因为如果有额外的 12 像素,则 (852/12 像素 = 71)。因此,额外的 12 个像素似乎是导致问题的原因。
如何非常快速地摆脱这些额外的像素,然后使用这些数据读入 OpenGL ES?或者更确切地说,我如何通过跳过每一行上的这些额外像素来读入 OpenGL ES?

与高速图像处理算法一起使用的图像在每行的末尾有一个填充是很常见的,这样图像的像素或字节大小是 4、8、16、32 等的倍数在。这使得优化某些算法的速度变得更加容易,尤其是与 SIMD 指令集(如 x86 上的 SSE 或 ARM 上的 NEON)结合使用时。在您的情况下,填充是 12 像素,这意味着 Apple 似乎优化了他们的算法来处理每行 32 像素;852 不能被 32 整除,864 是,因此这些行被 12 像素填充以保持 32 像素对齐。正确的技术术语是尺寸和步幅大小,或者在图像的情况下,宽度和步幅宽度。宽度是每行实际像素数据量,步幅宽度是实际行大小,
标准 OpenGL 允许加载步幅宽度大于实际纹理宽度的纹理。这是通过相应地设置glPixelStore参数来实现的GL_PACK_ROW_LENGTH。请注意,这种“跨步填充跳过”通常是在驱动程序的 CPU 部分中实现的,因此不会在 GPU 上执行此操作,实际上驱动程序会在将数据上传到 GPU 之前删除额外的填充。由于 OpenGL ES 设计为在 CPU 资源可能非常有限的嵌入式设备上运行,因此从 OpenGL ES 中删除了此选项以保持驱动程序开发简单,即使对于非常弱的嵌入式 CPU。这为您提供了四个选项来处理您的问题:
使用 C 复制循环预处理纹理以去除填充,这会跳过每行末尾的额外像素。这种实现相当慢,但很容易实现。
在选项 (1) 的情况下预处理纹理,但使用编译器 SIMD 宏来使用 NEON 指令。这将比选项 (1) 快大约 2 倍,但它也更难实现,您需要一些有关 NEON 指令以及如何使用它们来实现此目标的知识。
在选项 (2) 的情况下预处理纹理,但使用纯装配实现。这将比选项 (2) 快大约 3 倍,比选项 (1) 快大约 6 倍,但它也更难实现,因为您需要有关 ARM 汇编编程 + NEON 指令的知识。
加载带有填充的纹理并调整 OpenGL 的纹理坐标以使其忽略填充像素。根据纹理映射的复杂程度,这可能很容易实现,它比上述任何其他选项都快,唯一的缺点是您在 GPU 上浪费了更多的纹理内存。
我对 ARM 汇编编程知之甚少,对 NEON 指令知之甚少,因此我无法真正为您提供选项 (2) 和 (3) 的帮助。我可以向您展示选项 (1) 的实现,但是,恐怕它对于您的目的来说可能太慢了。这只留下了我过去多次使用的最后一个选项。
我们声明了 3 个变量:宽度、高度和步幅宽度。
GLsizei width = 852;
GLsizei height = 640;
GLsizei strideWidth = 864;
Run Code Online (Sandbox Code Playgroud)
当您加载纹理数据(假设 rawData 指向原始图像字节)时,您将 strideWidth 假装为“真实宽度”:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
strideWidth, height, 0, GL_RGB, GL_UNSIGNED_BYTE, rawData);
Run Code Online (Sandbox Code Playgroud)
OpenGL 中的纹理坐标是标准化的,这意味着左下角始终为(0.0f, 0.0f),右上角始终为(1.0f, 1.0f),无论纹理实际具有什么像素大小。这两个值可以称为(x, y),但为了不要将它们与顶点坐标混淆,而是将它们称为(s, t)。
为了让OpenGL切除填充像素,你只需要将所有s坐标调整一个特定的因子,我们称之为SPCF(Stride Padding Cut Factor),你可以通过以下方式计算:
float SPCF = (float)width / strideWidth;
Run Code Online (Sandbox Code Playgroud)
因此(0.35f, 0.6f),您将使用(0.35f * SPCF, 0.6f). 当然,您不应该为每个渲染帧执行一次此计算。相反,您应该复制原始纹理坐标,通过 SPCF 调整一次所有 s 坐标,然后在渲染帧时使用这些调整后的坐标。如果您以后重新加载纹理并且 SPCF 已更改,请重复调整过程。如果宽度等于 strideWidth,该算法也能工作,因为在这种情况下 SPCF 是 1.0f,因此根本不会改变 s 坐标,这是正确的,因为没有填充要切断。
这个技巧的缺点是,在你的情况下,纹理需要比其他情况多 2.4% 的内存,这也意味着纹理上传glTexImage2D将慢 2.4%。我想这是可以接受的,并且仍然比上述任何其他 CPU 密集型选项快得多。
| 归档时间: |
|
| 查看次数: |
1603 次 |
| 最近记录: |