2014-09-25 72 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),這更好。