2020年1月27日 星期一

使用 Map() 與 UpdateSubresource() 更新 Constant buffer 的差異

使用 Map() 與 UpdateSubresource() 更新 Constant buffer 的差異

前言

  在 Direct3D11 裡提供兩種更新 Constant buffer 資料的方法,分別是 Map() 與 UpdateSubresource() ,在 Direct3D11 的官方範例中的入門範例( Direct3D11Tutorials )裡採用的是 UpdateSubresource() ,但其它範例卻用 Map() 來更新,到底兩者的差異在哪?在此把學習的過程做個紀錄。

內容

  Map() 與 UpdateSubresource()  兩者在 Create 的時候有些不一樣, Map() 的 Usage 使用的是 D3D11_USAGE_DYNAMIC ,而 UpdateSubresource() 的 Usage 使用的是 D3D11_USAGE_DEFAULT ,由於 Usage 無法在 Create 後改變,所以在 Create 時其實就確定了更新的方法。就 [ Windows dev center ] D3D11_USAGE enumeration 裡對 D3D11_USAGE_DYNAMIC 的描述非常符合 Constant buffer 的情形(  CPU 寫 GPU 讀  ) ,那 UpdateSubresource() 又是怎麼回事?

  這兩者的差異我並沒有真的去測試差異,但就我所知,GPU讀  D3D11_USAGE_DEFAULT 的速度比 D3D11_USAGE_DYNAMIC 快,但這是在不考慮寫資料的狀況下,如果同時考慮 CPU 寫與 GPU 讀的話,請使用  D3D11_USAGE_DYNAMIC ,但是如果只是偶而更新資料的話就可以使用 D3D11_USAGE_DEFAULT 。可是 Constant buffer 很難保證它不會常常更新,所以適合的更新方法其實是 Map() , UpdateSubresource() 其實是不適合的更新方式,或者是說 UpdateSubresource() 並不適合用在 Constant buffer ,而且之後的 Direct3D12 也是採用 Map() 的方式更新 Constant buffer ,所以全部使用 Map() 的方法是較好的做法,把 UpdateSubresource() 視為優化的方法(確定不常更新)。

參考資料

[ gamedev.net ] Constant buffer UpdateSubResource vs Map
[ Windows dev center ] D3D11_USAGE enumeration

2020年1月20日 星期一

使用 NDK 建置 Shaderc 在 Windows

使用 NDK 建置 Shaderc 在 Windows

前言

  在之前的 建置 glslang 在 Windows 裡建置了 glslang 來當 Shader 的編譯器,但不幸的是 Android 平台並不能使用 glslang 而是由 Google 提供的 Shaderc 專案來當 Shader 的編譯器,並且要自己編譯成 Library 來使用,在此把過程做個紀錄。

內容

  Shaderc 的專案在 [ Github ] Shaderc 裡可以取得最新的版本,但幸運的是 NDK 本身會自帶一份 Shaderc 的專案,可以在以下的位址找到它
(NDK_ROOT)/sources/third_party/shaderc

可以在該資料夾下發現 Android.mk ,接著在使用以下命令來建置
..\..\..\ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=Android.mk APP_ABI=all APP_PLATFORM=android-24 APP_STL=c++_static -j8 clean libshaderc_combined

要注意的是命令中的 APP_STL 參數,範例用的是 c++_static ,如果需要別的 APP_STL 請修改此參數,建置完成後會產生 include 與 libs 這兩個資料夾, include 裡會包含所需要的 Header 檔 , libs 則包含對應各種 ABI 的 library 檔( .a 檔),建置到此就完成了。

  建置完成後就是如何加入到 NDK 的  Android.mk 裡來引用,以下是使用範例
#=====shaderc
include $(CLEAR_VARS)

LOCAL_MODULE := Shaderc_prebuild
LOCAL_SRC_FILES := (NDK_ROOT)/sources/third_party/shaderc/libs/$(APP_STL)/$(TARGET_ARCH_ABI)/libshaderc.a

include $(PREBUILT_STATIC_LIBRARY)

#=====
include $(CLEAR_VARS)
#...
LOCAL_C_INCLUDES += (NDK_ROOT)/sources/third_party/shaderc/include
#...
LOCAL_STATIC_LIBRARIES += Shaderc_prebuild

include $(BUILD_SHARED_LIBRARY)

範例先是產生一個 Prbult static library ,接著在需要引用的專案加入 include path ,並在 LOCAL_STATIC_LIBRARIES 加入  Prbult static library  ,加入的時候後要注意用的 Module 名稱來加入,這樣就可以開始使用了。如果需要一個具體的使用範例來參考的話可以使用 [ Github ] GearVRf  這個專案裡有完整使用範例。

  在 NDK 的參數裡的參數 LOCAL_SRC_FILES 是用 ":=" 來給值,由於右方有NDK的變數( APP_STL 與 TARGET_ARCH_ABI ),請不要使用"+="給值,不然會在 Link 的時候得到 Permission denied 錯誤,這個錯誤個人足足找了4天才找到這個錯誤,希望沒有下一個人會踩到這個雷。

參考資料

[ Github ] Shaderc
[ https://developer.android.com ] Vulkan Setup
[ Github ] GearVRf

相關文章

建置 glslang 在 Windows

2020年1月13日 星期一

查詢 GPU 的 Extension

查詢 GPU 的 Extension

前言

  要取得 GPU 的 Extension 的話直接透過繪圖 API ( OpenGL 、 OpenGLES 與 Vulkan )來取得即可,但這個前提是機器必須在身邊,在手機平台的話,難道要把所有的機器的買來取得資料嗎!?幸運的有個網站可以查詢,在此做個紀錄。

內容

  在 [ gpuinfo.org ] gpuinfo.org 可以查詢繪圖API的資訊,進入網站後可以看到以下
gupinfo.org 的網頁

根據要查詢的繪圖API點選,網站會引導到查詢網頁,以下以 Vulkan 為例,會看到以下
查詢 Vulkan 的 Extension

上方的輸入可以用來依據需求來過濾想查詢的 GPU ,接著會以 AMD Radeon RX 570 series 為例,會看到以下
查詢 AMD Radeon RX 570 series 的 Extension

在上方可以發現其實不只可以查詢 Extension ,還包含 Formats 等資訊,用起來相當簡單。

參考資料

[ gpuinfo.org ] gpuinfo.org

2020年1月6日 星期一

在 OpenGL 的 Vertex buffer 裡使用 interger 資料

在 OpenGL 的 Vertex buffer 裡使用 Interger 資料

前言

  最近需要在 Vertex buffer 裡使用 Interger 資料,在 Direct3D 時沒什麼問題,但是變成 OpenGL 就會跑出不正確的答案,在此把研究的過程做個紀錄。

內容

   OpenGL 輸入資料時會透過 glVertexAttribPointer() 來輸入資料,一般輸入的範例如下
glVertexAttribPointer( posLoc , 4 , GL_FLOAT , GL_FALSE , 4*sizeof(float), (GLvoid*)0 );

這樣輸入對應到 GLSL 的型別是 vec4 ,所以如果 GLSL 的型別是 int4 的話可能會像以下這樣輸入
glVertexAttribPointer( posLoc , 4 , GL_INT , GL_FALSE , 4*sizeof(GLint), (GLvoid*)0 );

如果你按照上述輸入的話會是錯的!正確的輸入如下
glVertexAttribIPointer( posLoc , 4 , GL_INT , 4*sizeof(GLint), (GLvoid*)0 );

要輸入 interger 資料的話,要用 glVertexAttribIPointer() 來輸入,glVertexAttribPointer() 用來輸入資料是 float 的時候,參數的部分 glVertexAttribIPointer() 沒有 normalized ,詳細的差異可以在[ docs.gl ]glVertexAttribPointer 裡查詢。

  題外話,在 OpenGL2 或 OpenGLES2 的時候,由於不支援 Integer 型別,所以只能用 float 的型態來輸入,然後在 GLSL 會提供 int() 來轉型,要注意雖有 int() ,但沒有 uint() ,這是早期的規格制定的缺陷。

參考資料

[ docs.gl ]glVertexAttribPointer

2019年12月30日 星期一

解決 Blender 繪製 Material 強制線性採樣的問題

解決 Blender 繪製 Material 強制線性採樣的問題

內容

  Blender 的即時繪製有個麻煩,在 Material 模式的時候明明都拒絕採樣內插,但是即時繪製的部分還是會線性採樣,如下圖
即時繪製與 Material 的預覽不一致

圖中左下可以看採用 Material 模式繪製,右下把內插( Interpolation )關掉來拒絕採樣內插,右上可以看到 Material 的預覽圖已經沒有採樣內插,但左側的即時繪製依舊還是有採樣內插,如果把模式開在 Rendered 的話可以正確繪製,有辦法在 Material 模式正確繪製嗎?

  解決的方法如下圖
解決即時繪製強制線性採樣的問題

在 User preferences 裡的 System 頁籤裡,有個 Mipmaps 的選項,把它關掉後就可以正常繪製了。

2019年12月23日 星期一

關於在 Vulkan 使用 Barrier 的心得

關於在 Vulkan 使用 Barrier 的心得

前言

  最近在 Vulkan 使用 Barrier 時發生了問題,我在 [ stackoverflow ] How to render to texture in Vulkan? 提出了問題,不過並沒有得到詳細的解答,但可以知道關鍵的問題在同步( Synchronization ),這裡把學習的過程做個紀錄。

內容

  在舊的API( OpenGL 與 Direct3D11 )時,命令是即時執行,所以同步的問題不會浮現上來,
但新的API( Vulkan 與 Direct3D12 )時,命令會"並行"執行,如下圖
命令執行的差異

如果需要等某一群的命令執行完的話,就會需要用到 Barrier 來等待命令都結束,來達成所謂的同步( Synchronization )。 個人使用 Barrier 在 Direct3D12 沒遇到問題,但 Vulkan 卻出了問題,所以本篇主力說明 Vulkan 的 Barrier。

  先來看看 Vulkan 的 Barrier ,如下
void vkCmdPipelineBarrier(
    VkCommandBuffer                             commandBuffer,
    VkPipelineStageFlags                        srcStageMask,
    VkPipelineStageFlags                        dstStageMask,
    VkDependencyFlags                           dependencyFlags,
    uint32_t                                    memoryBarrierCount,
    const VkMemoryBarrier*                      pMemoryBarriers,
    uint32_t                                    bufferMemoryBarrierCount,
    const VkBufferMemoryBarrier*                pBufferMemoryBarriers,
    uint32_t                                    imageMemoryBarrierCount,
    const VkImageMemoryBarrier*                 pImageMemoryBarriers);

最讓我困惑的是 srcStageMask 與 dstStageMask ,在 Direct3D12 的 Barrier 並沒有這一類的參數,所以使我困惑,這兩個參數的作用我在  BARRIERS IN VULKAN : THEY ARE NOT THAT DIFFICULT 裡找到了解答,先看下圖
Pipeline stage 與 Barrier

所謂的"Stage"指的是平行處理區塊,"srcStage"可以解釋成 Barrier 之前的平行處理區塊,"dstStage"可以解釋成 Barrier 之後的平行處理區塊,如果要簡單地完成一個等上一個平行處理區塊完成再執行之後的平行處理區塊,可以像以下
vkCmdPipelineBarrier(
  hCommandBuffer,
  VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
  VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
  0,
  0, nullptr,//MemoryBarriers
  0, nullptr,//BufferMemoryBarriers
  0, nullptr//ImageMemoryBarriers
);


這段可以解釋成等到 Barrier 之前的所有 DrawCall 都達到 VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT 狀態後,執行 MemoryBarriers 、 BufferMemoryBarriers 與 ImageMemoryBarriers ,但本例都是空值所以不需要執行,這些執行什麼時候要完成呢?在 Barrier 之後的所有 DrawCall 都達到 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT ,利用這樣的機制可以達到等待的效果。

  由於 render to texture 需要轉變 texture 的 layout ,有兩個時機,一是被當成 rendertarget 時要將 layout 從輸入變輸出,二是被當成輸入的 texture 時要將 layout 從輸出便輸入,這個做法在 Direct3D12 也有類似的作法,但問題也發生在這,當我企圖做這兩件事的時候不是發生繪製結果錯誤,就是得到 debug 的錯誤訊息 ,我試了將近一個禮拜,但還是無法得到正確的轉 layout 的作法,這問題到目前都還沒解決。

  在遇到上段所提及的問題後,由於想不到方法解決,所以在網上找到了範例,在 [ Github ] SaschaWillems/Vulkan 裡的 offscreen 專案,該範例實作了 render to texture ,但卻發現它完全沒用到 vkCmdPipelineBarrier() ,該專案改變 layout 是利用 render pass 的自帶機制,在 VkAttachmentDescription 裡可以指名輸入與輸出的 layout ,定義如下
typedef struct VkAttachmentDescription {
    VkAttachmentDescriptionFlags    flags;
    VkFormat                        format;
    VkSampleCountFlagBits           samples;
    VkAttachmentLoadOp              loadOp;
    VkAttachmentStoreOp             storeOp;
    VkAttachmentLoadOp              stencilLoadOp;
    VkAttachmentStoreOp             stencilStoreOp;
    VkImageLayout                   initialLayout;
    VkImageLayout                   finalLayout;
} VkAttachmentDescription;

看最後兩個參數 initialLayout 與 finalLayout ,這是一個自動轉 layout 的機制, initialLayout 代表的是要轉入前要轉的 layout , finalLayout  代表的是輸出後要轉的 layout ,目前我利用這個機制來轉 layout ,但我覺得這不是個好方法,為什麼這麼說呢?由於輸出的時候 render target 和 surface 使用的是不同的 layout ,就會需要兩個 render pass ,一個給 render target ,另一個給 surface,當輸出被交換了,render pass 也可能需要被交換,想當麻煩,但目前沒辦法找到用 vkCmdPipelineBarrier() 的正確轉法,只能先用這個方法替代。

  整體來說, Vulkan 的 barrier 做得比 Direcct3D12 還來得複雜,至少我在 Direct3D12 沒遇到問題,加上用 vkCmdPipelineBarrier() 轉 layout 的範例目前沒找到,只能用 render pass 得自帶機制來替代,這不是最好的解決方法,但目前只能這樣了。

參考資料

[ stackoverflow ] How to render to texture in Vulkan?
[ www.khronos.org ] vkCmdPipelineBarrier
Synchronization Examples
BARRIERS IN VULKAN : THEY ARE NOT THAT DIFFICULT
[ Github ] SaschaWillems/Vulkan

2019年12月16日 星期一

關於 C++ 的 Trim() 實現

關於 C++ 的 Trim() 實現

前言

  C++ 的 std::string 本身提供的介面相當少,如果拿來 Parse 文字的話會覺得少了一些實用的介面,如 Trim() , Trim() 的功能是將字串的前或後的字元做過濾掉指定的字元的動作,通常拿來過濾空白、換行與指定的字元,這次就來實現這個介面,在此做個紀錄。

內容

  Trim() 在較新的程式語言都會直接支援,但 C++ 卻從不開這個介面出來,在搜尋後發現有簡單的方法可以實現,在 [ CSDN ] C++ string的trim, split方法 裡找到有簡短的方法可以實現,範例如下
std::string LTrim( std::string& srcStr , const std::string& charList)
{
    std::string tagStr = srcStr;
    tagStr.erase( 0 , tagStr.find_first_not_of( charList.c_str() ) );
    return tagStr;
}
std::string RTrim( std::string& srcStr , const std::string& charList)
{
    std::string tagStr = srcStr;
    tagStr.erase( tagStr.find_last_not_of( charList.c_str() ) + 1 );
    return tagStr;
}
std::string Trim( std::string& srcStr , const std::string& charList )
{
    return RTrim( LTrim( srcStr , charList ) , charList );
}
//
std::string str=" ../abc.txt\n";
std::string lTrimStr=LTrim( str , " ./");//abc.txt\n
std::string rTrimStr=RTrim( str , "\n");// ../abc.txt
std::string trimStr=Trim( str , " ./\n");//abc.txt

範例有 3 個 Function ,分別是 LTrim() 、 RTrim() 與 Trim() , LTrim() 只過濾前方, RTrim() 只過濾後方, Trim() 則會過濾前方與後方 ,要注意一下過濾字元是以字串的形式來傳遞,下方有個別運用的範例。

  整體來說,實現的方法很簡單,甚至不用開 Function,也或許是因為如此所以不開介面出來,但我個人還是會開 Funciton 出來直接使用,畢竟我不是 C++ 專家,把 Funciton 開出來不僅程式碼看起來好懂也比較短。

參考資料

[ CSDN ] C++ string的trim, split方法
[ cplusplus.com ] std::string