我们有一些不支持非彩色纹理的旧设备,我们有一个将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 :(得分: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),这要好得多。