2014-09-25 69 views
0

我们有一些不支持非纹理纹理的旧设备,我们有一个将ARGB纹理转换为2纹理下一个幂的功能。问题在于它很慢,我们想知道是否有更好的方法来转换这些纹理。将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); 
} 
+0

因为您知道源缓冲区和目标缓冲区不能重叠,所以使用'memcpy'来进行优化,而不是'memmove'。 – 2014-09-25 15:00:55

+0

@PaulR我正要发布相同的东西。在我们的测试中'memcpy'要快得多。 – Grapes 2014-09-25 15:05:59

+0

好的 - 在这一点上,我希望你的内存带宽有限,所以我认为你不能在这个级别做更多的事情。然而,看起来你现在在下面的答案中有一个更“全面”的解决方案,所以我猜现在代码优化是多余的。 – 2014-09-25 15:32:46

回答

4

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

使用glTexImage2D创建纹理作为2的精确幂并且使用glTexImage2D,通过空指针数据

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

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

+0

这种方法确实有一些限制,根据确切的用例可能会或可能不会成为问题。像'CLAMP_TO_EDGE'和'REPEAT'这样的换行模式不会像人们所期望的那样工作。此外,使用这种部分占用的纹理进行mipmapping是有问题的,如果你不太小心,很可能不会给出理想的结果。 – 2014-09-25 16:24:26

+0

@ReetoKoradi:虽然这是真的,但所有这些限制也适用于问题中所做的CPU端重新安排。 – 2014-09-25 16:42:10

0

有过一个节目,我写了一个类似的问题,我采取了一个非常不同的方法。我没有拉伸源纹理,而是将它复制到另一个空的“幂次幂”纹理的左上角。

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

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

是你如何计算它们,并使用它们,你会得到一个VEC 2抱着S,T坐标,只需tAdjust使用它们来获取前乘S按sAdjust和T。如果您使用的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中做到这一点,但你得到的总体思路。

0

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

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

你想要做的是什么(对于每个维度)发现的对数,BASE2(LOG2)的地板,并把那变成2 ** n + 1。标准数学库具有功能log2,但它在浮点运算。但我们可以用is。 2 ** n可写为1 << n。所以这给了

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

更好,但还不理想,因为那浮动转换。 Bit Twiddling hack文件对于整数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(也有它的行为完全一样BitScanReverse一个builtin_clz

所以我们可以做

#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_…)); 

虽然我们有点扭曲:如果纹理已经处于两种格式之中,我们可以挽救整个磨难。几年前,我(独立地)重新发现了这个奇妙的可移植位,它利用了complement-2整数的特性。您也可以在位twiddles文档中找到它。但类型中性,简洁的宏观形式很少见。所以在这里,它是:

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

您正在使用C++模板是为了:

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

然后奔沃特已经告诉你,如何使用glTexSubImage2D到加载到纹理。另请参阅GL_ARB_texture_rectangle扩展,该扩展允许加载NPOT纹理,但无法进行mipmap和高级过滤。但它对你来说可能是一个可行的选择。

如果您觉得需要缩放纹理,那么总是值得关注双重空间。在这种情况下,空间频率域的双重空间。放大信号本质上是一种脉冲响应。这样它可以被描述为卷积。卷积通常是O(n2)的复杂性。但由于傅立叶空间中的傅立叶卷积定理等价于简单乘法,因此它变为O(n)。 FFT可以用O(n log n)完成,所以总的复杂度约为O(n + 2n log n),这更好。