【问题标题】:Fast way to convert ARGB texture to next power of 2 texture将 ARGB 纹理转换为 2 纹理的下一个幂的快速方法
【发布时间】:2014-09-25 14:51:16
【问题描述】:

我们有一些不支持非罐纹理的旧设备,并且我们有一个将 ARGB 纹理转换为 2 次幂纹理的功能。问题是它很慢,我们想知道是否有更好的方法来转换这些纹理。

void PotTexture()
{
    size_t u2 = 1; while (u2 < imageData.width) u2 *= 2;
    size_t v2 = 1; while (v2 < imageData.height) v2 *= 2;

    std::vector<unsigned char> pottedImageData;
    pottedImageData.resize(u2 * v2 * 4);

    size_t y, x, c;
    for (y = 0; y < imageData.height; y++)
    {
        for (x = 0; x < imageData.width; x++)
        {
            for (c = 0; c < 4; c++)
            {
                pottedImageData[4 * u2 * y + 4 * x + c] = imageData.convertedData[4 * imageData.width * y + 4 * x + c];
            }
        }
    }

    imageData.width = u2;
    imageData.height = v2;
    std::swap(imageData.convertedData, pottedImageData);
}

在某些设备上,这可以轻松使用 100% 的 CPU,因此任何优化都会令人惊叹。有没有我可以查看的执行此转换的现有函数?

编辑:

我已经稍微优化了上面的循环:

for (y = 0; y < imageData.height; y++)
{
    memcpy(
        &(pottedImageData[y * u2 * 4]), 
        &(imageData.convertedData[y * imageData.width * 4]), 
        imageData.width * 4);
}

【问题讨论】:

  • 使用memcpy 进行优化,而不是memmove,因为您知道源缓冲区和目标缓冲区不能重叠。
  • @PaulR 我正要发布同样的内容。在我们的测试中,memcpy 的速度要快得多。
  • 好的——在这一点上,我希望你的内存带宽非常有限,所以我认为你在这个级别上没有什么可以做的。但是,看起来您现在在下面的答案中有一个更“全面”的解决方案,所以我猜代码优化现在是多余的。

标签: c++ opengl c++11


【解决方案1】:

即使不支持 NPOT 纹理的设备也应该支持 NPOT 加载。

使用glTexImage2D将纹理创建为2的精确幂且无内容,为数据传递一个空指针。

data 可能是一个空指针。在这种情况下,纹理内存被分配以容纳宽度为width 和高度为height 的纹理。然后,您可以下载子纹理来初始化此纹理内存。如果用户尝试将纹理图像的未初始化部分应用于图元,则图像未定义。

然后用glTexSubImage2D上传一张NPOT图片,只占整个纹理的一部分。这可以在没有任何 CPU 端图像重新排列的情况下完成。

【讨论】:

  • 这种方法确实有一些限制,根据具体的用例,这可能是也可能不是问题。像CLAMP_TO_EDGEREPEAT 这样的包装模式不会像人们预期的那样工作。此外,对这种部分占用的纹理使用 mipmapping 是有问题的,如果你不是很小心,很可能不会给出预期的结果。
  • @RetoKoradi:虽然这是真的,但所有这些限制也适用于问题中所做的 CPU 端重新排列。
【解决方案2】:

在我编写的程序中遇到了类似的问题,我采取了一种非常不同的方法。我没有拉伸源纹理,而是将其复制到原本为空的二次幂纹理的左上角。

然后在像素着色器中使用一对浮点数来调整 s,t 值,以便仅从左上角获取。

float sAdjust = static_cast<float>(textureWidth) / static_cast<float>(containerWidth)
float tAdjust = static_cast<float>(textureHeight) / static_cast<float>(containerHeight)

是您计算它们的方式,要使用它们,您将获得一个 Vec2 保存 s,t 坐标,只需将 s 乘以 sAdjust 并将 t 乘以 tAdjust,然后再使用它们进行获取。如果您使用的是 Direct3D,则类似于以下内容:

D3DXVECTOR4 stAdjust;
stAdjust.x = sAdjust;
stAdjust.y = tAdjust;
// Transfer stAdjust into a float4 inside your pixel shader, call it stAdjust in there

现在在像素着色器中假设你有:

float2 texcoord;
float4 stAdjust;

你只是说:

texcoord.x = texcoord.x * stAdjust.x;
texcoord.y = texcoord.y * stAdjust.y;

在使用 texcoord 之前。抱歉,我无法告诉您如何在 GLSL 中执行此操作,但您大致了解。

【讨论】:

    【解决方案3】:

    好的,第一个优化可以在这里完成:

    size_t u2 = 1; while (u2 < imageData.width) u2 *= 2;
    size_t v2 = 1; while (v2 < imageData.height) v2 *= 2;
    

    您想要做的是(对于每个维度)找到对数底数 2 (log2) 的底并将其放入 2**n+1。标准数学库具有函数log2,但它在浮点上运行。但是我们可以使用is。 2**n 可以写成1 &lt;&lt; n。所以这给了

    size_t const dim_p2_… = 1 << (int)floor(log2(dim_…)+1);
    

    更好但还不理想,因为浮点转换。 Bit Twiddling hacks 文档有一些整数 ilog2 的函数:https://graphics.stanford.edu/~seander/bithacks.html#IntegerLog

    但我们仍然不是最优的。让我向您介绍编译器内在函数,如果相关机器可以在金属上执行它,它会转换为机器指令。

    • GNU GCC:int __builtin_ffs (unsigned int x),它返回一个加上 x 的最低有效 1 位的索引,或者如果 x 为零,则返回零。

    • MSVC++:_BitScanReverse,返回最高有效位设置为零的运行长度。所以 _BitScanReverse 就像 builtin_ffs - 1 (还有一个 builtin_clz 的行为与 BitScanReverse 完全一样。

    所以我们可以这样做

    #define ilog2_p1(x) (__builtin_ffs(x))
    

    #define ilog2_p1(x) (__BitScanReverse(x)+1)
    

    并使用它。

    size_t const dim_p2_… = 1 << (int)floor(ilog2_p1(dim_…));
    

    虽然我们正在玩弄:如果纹理已经具有两种格式的能力,我们可以节省整个考验。几年前,我(独立地)重新发现了奇妙的可移植位旋转技巧,利用了补码 2 整数的属性。您也可以在 bit twiddles 文档中找到它。但类型中性、简洁的宏观形式却很少见。所以这里是:

    #define ISPOW2(x) ( (x) && !( (x) & ((x) - 1) ) )
    

    您正在使用 C++,因此模板是有序的:

    template<typename T> bool ispow2(T const x) { return x && !( x & (x - 1) ); }
    

    然后 Ben Voight 已经告诉过你,如何使用 glTexSubImage2D 将其加载到纹理中。还可以查看GL_ARB_texture_rectangle 扩展,它允许加载 NPOT 纹理,但不能进行 mipmapping 和高级过滤。但这对您来说可能是一个可行的选择。

    如果您觉得需要缩放纹理,那么总是值得研究双重空间。本例为空间频域对偶空间。放大信号本质上是一种脉冲响应。因此,它可以被描述为卷积。卷积的复杂度通常为 O(n²)。但是由于傅里叶空间中的傅里叶卷积定理,等价物是简单的乘法,所以它变成了 O(n)。 FFT可以用O(n log n)完成,所以总复杂度在O(n + 2n log n)左右,要好很多。

    【讨论】:

      猜你喜欢
      • 2012-06-02
      • 1970-01-01
      • 1970-01-01
      • 2018-06-09
      • 1970-01-01
      • 1970-01-01
      • 2019-05-18
      • 1970-01-01
      • 2018-08-20
      相关资源
      最近更新 更多