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

2019年12月9日 星期一

考慮 Direct3D12 的 Fence 會溢位的問題

考慮 Direct3D12 的 Fence 會溢位的問題

前言

  在 [ GitHub ] Direct3D12 的教學範例 中, Fence 在使用時沒考慮"溢位"的問題,本以為是"溢位"也能自動處理,但如果把 Fence 的初始值改成即將"溢位"的數值會發現 Debug mode 會回報錯誤,表示"溢位"並不會自動處理,這次就來研究一下"溢位"的問題,在此做個紀錄。

內容

  關於 Fence 的說明在搜尋後發現都用很簡短的方式說明它的用處,加上我剛學習 Direct3D12 ,所以我至今都不太了解這個元件。我對 Fence 的認知是它其實就是個計數器,型別是 UINT64 ,每當 CommandQueue 執行完一個 CommandList 後,它就會計數一次,這個計數的量是不能控制的,只能是每個 CommandList 都"+1",最多能控制計數器的初始值, ID3D12CommandQueue::Signal() 的時候的第二個參數是設定一個期望在計數到計數到的數值,然後 ID3D12Fence::GetCompletedValue() 取得的是目前計數器的數值,如果計數器的值尚未達到期望值就透過 ID3D12Fence::SetEventOnCompletion() 來等待數值達到期望值,在 [ GitHub ] Direct3D12 的教學範例 的範例程式碼如下
 
const UINT64 fence = m_fenceValue;
ThrowIfFailed(m_commandQueue->Signal(m_fence.Get(), fence));
m_fenceValue++;

if (m_fence->GetCompletedValue() < fence)
{
 ThrowIfFailed(m_fence->SetEventOnCompletion(fence, m_fenceEvent));
 WaitForSingleObject(m_fenceEvent, INFINITE);
}

這個範例其實沒考慮 Fence 的數值如果發生"溢位"的狀況,該如何解決呢?很直覺,重製計數器的數值,透過 ID3D12Fence::Signal() 可以重製計數器的數值,修改的範例如下
const UINT64 fence = m_fenceValue;
if(m_fenceValue == UINT64_MAX)
{
    //Reset fence value
    m_fence->Signal(0);
    m_fenceValue = 0;
}
ThrowIfFailed(m_commandQueue->Signal(m_fence.Get(), fence));
m_fenceValue++;

if (m_fence->GetCompletedValue() < fence)
{
 ThrowIfFailed(m_fence->SetEventOnCompletion(fence, m_fenceEvent));
 WaitForSingleObject(m_fenceEvent, INFINITE);
}

當數值到達"溢位"的數值後執行重製,很簡單。雖然我沒實驗過,但根據 [ MSDN ] ID3D12Fence interface 的說法,Fence 是一個 CPU 與 GPU 都會存取的數值, Fence 的計數是由 GPU 來存取數值,而 ID3D12Fence::Signal() 是由 CPU 來存取數值,所以重製動作不應該常常執行(每個 Frame )。

參考資料

[ GitHub ] Direct3D12 的教學範例
[ MSDN ] ID3D12Fence interface

2019年12月2日 星期一

Vulkan API 的奇怪設計

Vulkan API 的奇怪設計

前言

  在之前的 開啟Vulkan的 Debug mode 裡開啟了 Vulkan API 的 Debug mode ,有些看似可以省略的流程在沒有 Debug mode 時可以正常工作,但開啟 Debug mode 後就被視為錯誤的奇怪設計,在此做個紀錄。

內容

  這次的主角是 vkGetPhysicalDeviceQueueFamilyProperties() 與 vkGetPhysicalDeviceSurfaceFormatsKHR() ,這兩個 Function 看起來都只是作為詢問,但事實上這個詢問遽然是不可省略的詢問!如果不詢問會在 Debug mode 時報錯。

  如果沒有詢問 vkGetPhysicalDeviceQueueFamilyProperties() 時,會在 vkCreateSwapchainKHR() 時得到下圖
vkGetPhysicalDeviceQueueFamilyProperties() 的報錯

如果沒有詢問 vkGetPhysicalDeviceSurfaceFormatsKHR() 時,會在 vkCreateSwapchainKHR() 時得到下圖
 vkGetPhysicalDeviceSurfaceFormatsKHR() 的報錯

在 Vulkan SDK 裡的範例裡,程式碼看起來是可以省略這兩個 Funciton 的詢問直接用"預設值"來替代結果,但不幸地,在 Debug mode 會判定為錯誤,這設計奇怪的點是有些平台的硬體狀況是很"固定",例如一定支援某些格式的狀況,但這個流程卻不能省略就會看起來很奇怪。

相關文章

開啟Vulkan的 Debug mode