将ARGB纹理转换为2纹理的下一个幂的快速方法

时间:2014-09-25 14:51:16

标签: c++ opengl c++11

我们有一些不支持非彩色纹理的旧设备,我们有一个将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);
}

3 个答案:

答案 0 :(得分:4)

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

使用glTexImage2D创建纹理作为2的精确幂和NO CONTENT,为数据传递空指针。

  

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

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

答案 1 :(得分:0)

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

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

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

是你如何计算它们,并且使用它们你将获得一个包含s,t坐标的Vec2,只需将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中做到这一点,但你得到了一般的想法。

答案 2 :(得分:0)

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

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

你想要做的是(对于每个维度)找到logarithm-base2(log2)的底限并将其放入2 ** n + 1。标准数学库具有函数log2,但它在浮点上运行。但我们可以使用的是。 2 ** n可以写成1 << 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),这要好得多。