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

2019年11月25日 星期一

運用 Viewport 修正 Vulkan 的Y軸繪圖空間

運用 Viewport 修正 Vulkan 的Y軸繪圖空間 

前言

  最近在學習 Vulkan ,但發現到 Vulkan 的Y軸繪圖空間跟 Direct3D 與 OpenGL 不一樣,幸運地,是有辦法將會Y軸繪圖空間修正回來,在此把學習的過程做個紀錄。

內容

  首先先來了解 Vulkan 的Y軸繪圖空間有何不一樣,如下圖
Y軸繪圖空間的差異

可以在圖中看到 Vulkan 的Y軸繪圖空間是從"下到上",但舊的 API 的Y軸繪圖空間卻是從"下到上",如果是從舊的 API 整合 Vulkan 到繪圖引擎就會發生繪製的結果上下顛倒的問題,信運地,在經過 google 後找到  [ Sascha Willems ] Flipping the Vulkan viewport ,裡面有提到解決的方法。

  解決的方法是透過 Viewport 來修正Y軸的繪圖空間,但要用這個方法時必須開啟"VK_KHR_maintenance1",這是一個 Device extension ,要在 VkDeviceCreateInfo 裡的啟動。在啟動"VK_KHR_maintenance1"後,就可以利用 Viewport  來修正Y軸了,如何修正呢?在 VkViewport::height 裡填上"負"的高度,這樣整個Y軸繪製空間就會變成"下到上",如果沒開啟"VK_KHR_maintenance1"填上負值會在 Debug 訊息回報該值不能是負值。填完"負"的高度得到的結果如下圖
填完"負"的高度的繪製結果

圖中 Render result 的部分是繪製的結果但這次的Y軸是"下到上",藍色的部分是最後會被寫到 Render target 的區域,所以還要將繪製的結果校正到這個區域才能達到目的,如何校正呢?在 VkViewport::y 裡加一個"正"的高度就可以達到目的。最後提供一個轉換的範例如下
void SetUpViewportAndCorrectYAxis(VkViewport& viewport,float x,float y,float width,float height,float minDepth,float maxDepth)
{
  viewport.x = x;
  viewport.y = -y + height;
  viewport.width = width;
  viewport.height = -height;
  viewport.minDepth = mixDepth;
  viewport.maxDepth = maxDepth;
}

參考資料

[ Sascha Willems ] Flipping the Vulkan viewport

2019年11月18日 星期一

開啟Vulkan的 Debug mode

開啟Vulkan的 Debug mode

前言

  最近在入門 Vulkan ,發現開啟 Debug mode 有一些麻煩,不像 Direct3D 只要設定旗標就可以開啟, Vulkan 需要比較多的步驟,在此把學習的過程做個紀錄。

內容

  在創建 VkInstance 會透過 VkInstanceCreateInfo 來輸入參數, VkInstanceCreateInfo 的定義如下
typedef struct VkInstanceCreateInfo {
    VkStructureType             sType;
    const void*                 pNext;
    VkInstanceCreateFlags       flags;
    const VkApplicationInfo*    pApplicationInfo;
    uint32_t                    enabledLayerCount;
    const char* const*          ppEnabledLayerNames;
    uint32_t                    enabledExtensionCount;
    const char* const*          ppEnabledExtensionNames;
} VkInstanceCreateInfo;

在 ppEnabledLayerNames 的陣列裡要加入"VK_LAYER_KHRONOS_validation",而 ppEnabledExtensionNames 的陣列要加入"VK_EXT_debug_utils",這兩個缺一不可,由於剛入門所以有踩到坑,我踩得坑是指加入"VK_EXT_debug_utils",會發生什麼事呢?我會告訴你行為不可預期, VkInstance 雖然可以正常產出,但其它的 API 可能會出問題,我發生問題狀況是 vkCreateGraphicsPipelines() 喚起時一定 Access violation 。在 pNext 的參數要設定VkDebugUtilsMessengerCreateInfoEXT ,範例如下
VkInstanceCreateInfo createInstanceInfo;
//...
//
VkDebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfo;
debugUtilsMessengerCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
debugUtilsMessengerCreateInfo.pNext = NULL;
debugUtilsMessengerCreateInfo.flags = 0;
debugUtilsMessengerCreateInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT|VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
debugUtilsMessengerCreateInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
                                            VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
                                            VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
debugUtilsMessengerCreateInfo.pfnUserCallback = myDebugCallBack;
debugUtilsMessengerCreateInfo.pUserData = NULL;
//
createInstanceInfo.pNext = &debugUtilsMessengerCreateInfo;
//
//call vkCreateInstance() to create
//...

在設定 pfnUserCallback 時需要一個 Function pointer ,可以設定成以下( 在Windows 平台)
VkBool32 myDebugCallBack(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,VkDebugUtilsMessageTypeFlagsEXT messageType,const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData,void *pUserData)
{
  char prefix[64] = "";
  char *message = (char *)malloc(strlen(pCallbackData->pMessage) + 5000);
  //
  if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) 
  {
    strcat(prefix, "VERBOSE : ");
  } 
  else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) 
  {
    strcat(prefix, "INFO : ");
  } 
  else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) 
  {
    strcat(prefix, "WARNING : ");
  } 
  else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) 
  {
    strcat(prefix, "ERROR : ");
  }
  //
  if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) 
  {
    strcat(prefix, "GENERAL");
  } 
  else 
  {
    if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) 
    {
      strcat(prefix, "VALIDATION");  
    }
    if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) 
    {
      if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) 
      {
        strcat(prefix, "|");
      }
      strcat(prefix, "PERFORMANCE");
    }
  }
  sprintf(message, "%s - Message Id Number: %d | Message Id Name: %s\n\t%s\n", prefix, pCallbackData->messageIdNumber,
  pCallbackData->pMessageIdName, pCallbackData->pMessage);
  if (pCallbackData->objectCount > 0) 
  {
    char tmp_message[500];
    sprintf(tmp_message, "\n\tObjects - %d\n", pCallbackData->objectCount);
    strcat(message, tmp_message);
    for (uint32_t object = 0; object < pCallbackData->objectCount; ++object) 
    {
      if (NULL != pCallbackData->pObjects[object].pObjectName && strlen(pCallbackData->pObjects[object].pObjectName) > 0) 
      {
        sprintf(tmp_message, "\t\tObject[%d] - %s, Handle %p, Name \"%s\"\n", object,
                ConvertVkObjectType(pCallbackData->pObjects[object].objectType),
                (void *)(pCallbackData->pObjects[object].objectHandle), pCallbackData->pObjects[object].pObjectName);
      } 
      else 
      {
        sprintf(tmp_message, "\t\tObject[%d] - %s, Handle %p\n", object,
                ConvertVkObjectType(pCallbackData->pObjects[object].objectType),
                (void *)(pCallbackData->pObjects[object].objectHandle));
      }
      strcat(message, tmp_message);
    }
  }
  if (pCallbackData->cmdBufLabelCount > 0) 
  {
    char tmp_message[500];
    sprintf(tmp_message, "\n\tCommand Buffer Labels - %d\n", pCallbackData->cmdBufLabelCount);
    strcat(message, tmp_message);
    for (uint32_t cmd_buf_label = 0; cmd_buf_label < pCallbackData->cmdBufLabelCount; ++cmd_buf_label) 
    {
      sprintf(tmp_message, "\t\tLabel[%d] - %s { %f, %f, %f, %f}\n", cmd_buf_label,
              pCallbackData->pCmdBufLabels[cmd_buf_label].pLabelName, pCallbackData->pCmdBufLabels[cmd_buf_label].color[0],
              pCallbackData->pCmdBufLabels[cmd_buf_label].color[1], pCallbackData->pCmdBufLabels[cmd_buf_label].color[2],
              pCallbackData->pCmdBufLabels[cmd_buf_label].color[3]);
      strcat(message, tmp_message);
    }
  }

  ::OutputDebugStringA(message);
  fflush(stdout);
  //
  free(message);

  // Don't bail out, but keep going.
  return false;
}

這個 Function 是從 Vulkan SDK 的範例裡拔出來的,紅字的部分是顯示 Debug 訊息的地方,請依不同的平台需求來改造。設定 VkInstanceCreateInfo.pNext 的部分要指向 VkDebugUtilsMessengerCreateInfoEXT ,這個用法相當特別,正常的 pNext 都會是 NULL。

  在 VkInstanceCreateInfo 裡設好參數並確實的創建 VkInstance 後需要取得 Debug mode 需要的 Function ,因為是 Extension ,所以要另外取得可以理解,範例如下
fCreateDebugUtilsMessengerEXT =(PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(hInstance,"vkCreateDebugUtilsMessengerEXT");
fDestroyDebugUtilsMessengerEXT =(PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(hInstance,"vkDestroyDebugUtilsMessengerEXT");
fSubmitDebugUtilsMessageEXT =(PFN_vkSubmitDebugUtilsMessageEXT)vkGetInstanceProcAddr(hInstance,"vkSubmitDebugUtilsMessageEXT");
fCmdBeginDebugUtilsLabelEXT=(PFN_vkCmdBeginDebugUtilsLabelEXT)vkGetInstanceProcAddr(hInstance,"vkCmdBeginDebugUtilsLabelEXT");
fCmdEndDebugUtilsLabelEXT=(PFN_vkCmdEndDebugUtilsLabelEXT)vkGetInstanceProcAddr(hInstance,"vkCmdEndDebugUtilsLabelEXT");
fCmdInsertDebugUtilsLabelEXT=(PFN_vkCmdInsertDebugUtilsLabelEXT)vkGetInstanceProcAddr(hInstance,"vkCmdInsertDebugUtilsLabelEXT");
fSetDebugUtilsObjectNameEXT=(PFN_vkSetDebugUtilsObjectNameEXT)vkGetInstanceProcAddr(hInstance,"vkSetDebugUtilsObjectNameEXT");

這每個都是 Function pointer ,範例沒宣告型別名稱,要型別名稱可以看式子的右側都有 cast。取得 Function pointer 後再利用 fCreateDebugUtilsMessengerEXT() 來創建 VkDebugUtilsMessengerEXT  ,範例如下
VkDebugUtilsMessengerEXT hDebugUtilsMessenger;
//Create...
fCreateDebugUtilsMessengerEXT(hInstance,&debugUtilsMessengerCreateInfo,NULL,&hDebugUtilsMessenger);

//Destroy...
fDestroyDebugUtilsMessengerEXT(hInstance,hDebugUtilsMessenger,NULL);

在創建完後就代表 Debug mode 開啟了。既然創建了 VkDebugUtilsMessengerEXT ,在程式最後要記得透過fDestroyDebugUtilsMessengerEXT() 來銷毀 VkDebugUtilsMessengerEXT 。

  最後抱怨一下, 這個啟動過程的設計我覺得非常糟糕,流程只有兩個,創建 VkInstance 與創建 VkDebugUtilsMessengerEXT ,但兩個流程的參數都需要 VkDebugUtilsMessengerCreateInfoEXT ,設定的方法還很特別,不看範例很難記得。還有在啟動"VK_LAYER_KHRONOS_validation"與"VK_EXT_debug_utils"的部分也設計得很糟,Layer 與 Extension 的啟動有相依性,錯了也不報錯。Call back 的部分我覺得明明官方就可以給預設,但就是要自己寫一個,規格還有點複雜,這部份的設計也很奇怪。

參考資料

[ 書籍 ] Vulkan 應用開發指南,出版商:人民郵電,ISBN-13:9787115506801

2019年11月11日 星期一

關於 ID3D12GraphicsCommandList::SetDescriptorHeaps()

關於 ID3D12GraphicsCommandList::SetDescriptorHeaps()

前言

  最近在研究 Direct3D12 ,手邊的資料有 Direct3D12 的範例與 [ 書籍 ] DirectX 12 3D 游戲開發實戰,出版商:人民郵電出版社,ISBN-13:9787115479211 ,本以為就入門來說應該很充足,但在實務上會碰到一些不知道在做什麼的 Function ,就像是 ID3D12GraphicsCommandList::SetDescriptorHeaps() ,這個 Function 不論是 [ MSDN ]ID3D12GraphicsCommandList::SetDescriptorHeaps method 或是手邊的資料都沒有說得很清楚,都使是在範例裡有寫這個步驟,但都不特別解釋,所以我就對這個 Funciton 做了研究,在此做個紀錄。

內容

  ID3D12GraphicsCommandList::SetDescriptorHeaps() 這個 Function 在介面上有些設計不良,
介面如下
void SetDescriptorHeaps(
  UINT                 NumDescriptorHeaps,
  ID3D12DescriptorHeap * const *ppDescriptorHeaps
);

介面看起來會讓人很直覺得可以輸入很多個 ID3D12DescriptorHeap ,但是事實上是輸入的 DescriptorHeap 是有限制的!每個 ID3D12DescriptorHeap 在 Create 的時候會需要 D3D12_DESCRIPTOR_HEAP_TYPE,定義如下
typedef enum D3D12_DESCRIPTOR_HEAP_TYPE
{
    D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,    // Constant buffer/Shader resource/Unordered access views
    D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER,        // Samplers
    D3D12_DESCRIPTOR_HEAP_TYPE_RTV,            // Render target view
    D3D12_DESCRIPTOR_HEAP_TYPE_DSV,            // Depth stencil view
    D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES       // Simply the number of descriptor heap types
} D3D12_DESCRIPTOR_HEAP_TYPE;

這個 Type 會決定 DescriptorHeap 的型態, Create 完後不能更改,而在 ID3D12GraphicsCommandList::SetDescriptorHeaps() 輸入的每個 DescriptorHeap 必須是不同的  D3D12_DESCRIPTOR_HEAP_TYPE ,加上這個 Function 其實只處理"輸入"的部分,所以D3D12_DESCRIPTOR_HEAP_TYPE_RTV 與 D3D12_DESCRIPTOR_HEAP_TYPE_DSV 就不會用到,所以實際上最多只能輸入兩個 DescriptorHeap !這些在 MSDN 上都沒有很詳述的說明,我是在輸入同一種 Type 後會出現 Debug 的錯誤訊息才知道這個 Fuction 不能輸入不同 Type 的 DescriptorHeap 。個人認為理想的介面應該如下
void SetDescriptorHeaps(
  ID3D12DescriptorHeap * pDescriptorHeapsType_CBV_SRV_UAV;
  ID3D12DescriptorHeap * pDescriptorHeapsType_SAMPLER;
);

  接著來說一下喚起的時機,如下圖
SetDescriptorHeaps() 的喚起時機

如中上方的部分說明只能接收兩種 DescriptorHeap ,接著下方的 Draw call 是指每次的繪圖會透過 SetGraphicsRootDescriptorTable() 時要注意 GPUDescriptorHandle 必須來自 SetDescriptorHeaps() 的 DescriptorHeap ,手邊的資料也都沒明說這樣的規則,這是經過多次的實驗得來的經驗。如果需要切換 DescriptorHeap 的話,要注意切換的時機要發生在 Draw call 與 Draw call 之間,不能發生在 Draw call 的裡面,舉個例來說,如果一個 Draw call 裡需要做兩次 SetGraphicsRootDescriptorTable() ,但期望來自兩個 DescriptorHeap( D3D12_DESCRIPTOR_HEAP_TYPE 是一樣的),這個時候發生需要在同一個 Draw call 裡喚起兩次 SetDescriptorHeaps(),如以下範例
ComPtr<ID3D12DescriptorHeap> m_myHeap1;
ComPtr<ID3D12DescriptorHeap> m_myHeap2;
//Init heap1 and heap2
//...
//
ID3D12DescriptorHeap* ppHeaps[] = { m_myHeap1.Get()};
m_commandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);
m_commandList->SetGraphicsRootDescriptorTable(0,m_myHeap1->GetGPUDescriptorHandleForHeapStart() );
ppHeaps[0] = m_myHeap2.Get();
m_commandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);
m_commandList->SetGraphicsRootDescriptorTable(1,m_myHeap2->GetGPUDescriptorHandleForHeapStart() );
m_commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_commandList->IASetVertexBuffers(0, 1, &m_vertexBufferView);
m_commandList->DrawInstanced(3, 1, 0, 0);

這個做法目前不確定是否為"正規"用法,因為這個用法在 NVIDIA GeForce GTX1050 的顯卡時可以正常工作,但在 Intel HD Graphics 630 時無法正常工作,所以不確定是否可以這樣用,
說明文件也沒說明是否可這樣用,這部分目前的解決方案是絕不在 Draw call 中切換 DescriptorHeap ,這樣可以取得較好的兼容性。

參考資料

[ MSDN ]ID3D12GraphicsCommandList::SetDescriptorHeaps method
[ 書籍 ] DirectX 12 3D 游戲開發實戰,出版商:人民郵電出版社,ISBN-13:9787115479211

2019年11月4日 星期一

在 Windows 平台建置 LLVM 專案

在 Windows 平台建置 LLVM 專案

前言

  最近買了 [ 書籍 ] LLVM編譯器實戰教程,出版發行:機械工業出版社,ISBN-13:9787111631972,想要入門 LLVM,但發現 LLVM 專案在 Windows 平台建置時不是很直覺,所以在此做個紀錄。

內容

  LLVM 的專案在 Windows 平台是用 CMake 來產生專案,可以到 CMake 官網 下載。接著是關於 LLVM 的專案如何下載?可以在 LLVM 官網  下載,如下圖
LLVM 官網的下載

圖中可以看到有很多的專案,但那些專案是需要下載的呢?其實我也不知道,因為我剛入門並不熟悉,但官方有提供 Git 的位址,可以一次下載整個 LLVM 專案, Git 的位置如下
https://github.com/llvm/llvm-project.git

透過這個 Git 可以取得 LLVM 整個專案的原始碼。

  在取得原始碼專案後,接著就是用 CMake 來產生專案,在設定好 Source code 與 binaries 的位置後,請新增兩個 Entry ,如下圖
新增 CMAKE_INSTALL_PREFIX


新增 LLVM_TARGETS_TO_BUILD

CMAKE_INSTALL_PREFIX 的位置是指建置完的 .h 、 .lib 與工具的執行檔要放的位址,增加完兩個 Entry 後,就按下 Configure 設定編譯器,接著按下 Generate ,就可以產生專案如下圖
產生專案

在圖中"1"的位置會看到紅字,請不要理會它,這是正常現象,接著是圖中"2"的位置會看到一大推 not found ,這也是正常現象,小小的抱怨一下,這種建置訊息會留下"看似"有問題的訊息實在不是很欣賞的建置。

  接著開啟用 CMake 產生的方案後,建置 INSTALL 專案,如下圖
建置 INSTALL 專案

建置完後就可以在之前 CMAKE_INSTALL_PREFIX 的位置裡得到結果。

參考資料

[ 書籍 ] LLVM編譯器實戰教程,出版發行:機械工業出版社,ISBN-13:9787111631972
CMake 官網
LLVM 官網

2019年10月28日 星期一

將曲線( Curve )連起來

將曲線( Curve )連起來

前言

  續之前的 WebGL的繪製三次方貝茲曲線( Cubic bezier curve ) ,在該篇只是單純的繪製曲線( Curve ),如果需要彎曲變化較豐富的話,就需要增加控制點的數量,查詢資料發現貝茲曲線( ( Bezier curve )不只有二次方與三次方,可以到四次方、五次方...等,每次改變控制點數量就要換一種算式計算,實在不是個好方法,所以打算用多個三次方貝茲曲線( Cubic bezier curve )連起來達到增加控制點的方式來增加彎曲變化,在此把學習的過程做個紀錄。

內容

  雖然貝茲曲線( Bezier curve )不止到三次方,但每次增加控制點就要改變算式相當麻煩,所以採用"連"起來的方式來完成曲線,到底是怎麼連起來的呢?請看下圖
曲線( Curve )的連結

圖中是連結兩個三次方貝茲曲線( Cubic bezier curve),比較要注意的是連結後所需要控制點是7個,並不是8個,第一個曲線( Curve )的結尾會跟第二個曲線( Curve)的開頭共用同一個控制點。接著來看實作範例
HTML 的部分
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
<canvas id="myCanvas1" width=400 height=300></canvas>
<br>
<input id="btnDrawCtrlPoints"type="button" value="DrawCtrlPoints"/>
<br>
<input id="btnDrawCurve"type="button" value="DrawCurve"/>
<input type="range" min="2" max="100" value="30" class="slider" id="sliderLerp">
<label id="labelLerpValue">30</label>
</body>
</html>

Javascript 的部分
let canvas1 = document.getElementById('myCanvas1');
let glCTX1 = canvas1.getContext('webgl');
let vboPrimitiveCon = 0;
let vbo=createDynamicBuffer(glCTX1);
let shaderProg = createShader(glCTX1);
//
function createShader(glContext){
  let vertShader = glContext.createShader(glContext.VERTEX_SHADER);
  glContext.shaderSource(
    vertShader , 
    'attribute vec3 pos;void main(void){gl_Position=vec4(pos, 1.0);}'
  );
  glContext.compileShader(vertShader);
  let fragShader = glContext.createShader(glContext.FRAGMENT_SHADER);
  glContext.shaderSource(
    fragShader, 
    'void main(void){gl_FragColor=vec4(1,1,1,1);}'
  );
  glContext.compileShader(fragShader);
  let prog = glContext.createProgram();
  glContext.attachShader(prog, vertShader);
  glContext.attachShader(prog, fragShader);
  glContext.linkProgram(prog);
  glContext.useProgram(prog);  
  
  return prog;
}
function createDynamicBuffer(glContext){
  let vertexBuf = glContext.createBuffer();
  glContext.bindBuffer(glContext.ARRAY_BUFFER, vertexBuf);
  let dataArray=new Float32Array([ 
       0.0, 0.5, 0.0,  
      -0.5,-0.5, 0.0,  
      -0.5,-0.5, 0.0,
       0.5,-0.5, 0.0,
       0.5,-0.5, 0.0,
       0.0, 0.5, 0.0
    ]);
  glContext.bufferData(
    glContext.ARRAY_BUFFER, 
    3000, 
    glContext.DYNAMIC_DRAW
  );
  //write deafult data...
  glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,dataArray);
  vboPrimitiveCon = dataArray.length / 3;
  return vertexBuf;
}
function simpleDraw(glContext){
  glContext.useProgram(shaderProg);
  //
  glContext.viewport(0,0,glContext.canvas.width,glContext.canvas.height);
  glContext.clearColor(0, 0, 1, 1);
  glContext.clear(glContext.COLOR_BUFFER_BIT);
  //
  glContext.bindBuffer(glContext.ARRAY_BUFFER, vbo);
  let posLoc = glContext.getAttribLocation(shaderProg, "pos");
  glContext.vertexAttribPointer(posLoc, 3, glContext.FLOAT, false, 0, 0);
  glContext.enableVertexAttribArray(posLoc);

  glContext.drawArrays(glContext.LINES, 0, vboPrimitiveCon);
}
function generateLineListData(ar){
  let tagAr=[];
  let mod=ar.length%3;
  let elementAmount=(ar.length-mod)/3;
  if(elementAmount>=2 && mod===0){
    tagAr.push(ar[0]);
    tagAr.push(ar[1]);
    tagAr.push(ar[2]);
    //
    for(let i=3;i<(ar.length-3);i+=3){
      tagAr.push(ar[i]);
      tagAr.push(ar[i+1]);
      tagAr.push(ar[i+2]);
      //
      tagAr.push(ar[i]);
      tagAr.push(ar[i+1]);
      tagAr.push(ar[i+2]);
    }
    //
    tagAr.push(ar[ar.length-3]);
    tagAr.push(ar[ar.length-2]);
    tagAr.push(ar[ar.length-1]);
  }
  return new Float32Array(tagAr);
}
function generateBezierCurve(p0,p1,p2,lerp){
  if(lerp < 2)
    return [];
  //
  let tagAr = [];
  for(let i=0;i < lerp;i++){
    let t = i/(lerp-1);
    let invT = 1.0-t;
    let part0Value = invT * invT;
    let part1Value = 2 * t * invT;
    let part2Value = t * t;
    let part0 = [part0Value*p0[0], part0Value*p0[1], part0Value*p0[2] ];
    let part1 = [part1Value*p1[0], part1Value*p1[1], part1Value*p1[2] ];
    let part2 = [part2Value*p2[0], part2Value*p2[1], part2Value*p2[2] ];
    tagAr.push(part0[0] + part1[0] + part2[0]);
    tagAr.push(part0[1] + part1[1] + part2[1]);
    tagAr.push(part0[2] + part1[2] + part2[2]);
  }
  return tagAr;
}
function generateCubicBezierCurve(p0,p1,p2,p3,lerp){
  if(lerp < 2)
    return [];
  //
  let tagAr = [];
  for(let i=0;i < lerp;i++){
    let t = i/(lerp-1);
    let invT = 1.0-t;
    let part0Value = invT * invT * invT;
    let part1Value = 3 * t * invT * invT;
    let part2Value = 3 * t * t * invT;
    let part3Value = t * t * t;
    let part0 = [part0Value*p0[0], part0Value*p0[1], part0Value*p0[2] ];
    let part1 = [part1Value*p1[0], part1Value*p1[1], part1Value*p1[2] ];
    let part2 = [part2Value*p2[0], part2Value*p2[1], part2Value*p2[2] ];
    let part3 = [part3Value*p3[0], part3Value*p3[1], part3Value*p3[2] ];
    tagAr.push(part0[0] + part1[0] + part2[0] + part3[0]);
    tagAr.push(part0[1] + part1[1] + part2[1] + part3[1]);
    tagAr.push(part0[2] + part1[2] + part2[2] + part3[2]);
  }
  return tagAr;
}
function myRender(){
  simpleDraw(glCTX1);
  //
  window.requestAnimationFrame(myRender);
}
//
let ctrlPointList=[
  [-0.9,0.0,0.0],
  [-0.7,0.9,0.0],
  [-0.5,0.9,0.0],
  [-0.3,0.0,0.0],
  [-0.1,-0.6,0.0],
  [0.1,-0.6,0.0],
  [0.3,0.0,0.0],
  [0.5,0.9,0.0],
  [0.7,0.9,0.0],
  [0.9,0.0,0.0],
];
let tagLerpValue=document.getElementById("sliderLerp").value;
function UpdateCurveData(){
  let tagCurveData=[];
  for(let i=0;i<ctrlPointList.length;i+=3){
    if( (ctrlPointList.length - i) < 4)
      break;
    //
    let data=generateCubicBezierCurve(
      ctrlPointList[i],
      ctrlPointList[i+1],
      ctrlPointList[i+2],
      ctrlPointList[i+3],
      tagLerpValue);
    tagCurveData.push(...data);
  }
  let dataArray=generateLineListData(tagCurveData);
  glCTX1.bindBuffer(glCTX1.ARRAY_BUFFER, vbo);
  glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,dataArray);
  vboPrimitiveCon = dataArray.length / 3;
}
document.getElementById("btnDrawCtrlPoints").onclick=function(evt){
  let data = [];
  for(let i=0;i<ctrlPointList.length;i++)
    data.push(...ctrlPointList[i]);
  //
  let dataArray=generateLineListData(data);
  glCTX1.bindBuffer(glCTX1.ARRAY_BUFFER, vbo);
  glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,dataArray);
  vboPrimitiveCon = dataArray.length / 3;
}
document.getElementById("btnDrawCurve").onclick=function(evt){
  UpdateCurveData();
}
document.getElementById("sliderLerp").oninput=function(evt){
  tagLerpValue=this.value;
  UpdateCurveData();
  document.getElementById("labelLerpValue").innerHTML = this.value;
}
window.onload = function(){
  window.requestAnimationFrame(myRender);
  
}

執行結果如下
繪製控制點
繪製曲線( Curve )

這次的範例從 WebGL的繪製三次方貝茲曲線( Cubic bezier curve ) 的範例更改而來,這次只說明不一樣的部分。這次的範例會"連"接三個三次方貝茲曲線( Cubic bezier curve ),所以控制點的數量改成10個,接著在 UpadteCurveData() 中,由於這次要把各個曲線( Curve )"連"起來,所以改成用迴圈的方式來產生。

參考資料

 [ Wiki ]貝茲曲線

 相關網站

  WebGL的繪製三次方貝茲曲線( Cubic bezier curve )

2019年10月21日 星期一

WebGL的繪製三次方貝茲曲線( Cubic bezier curve )

WebGL的繪製三次方貝茲曲線( Cubic bezier curve )

前言

  續之前的 WebGL的繪製曲線( Curve ) ,該篇所繪製的曲線為二次貝茲曲線(Quadratic bezier curve),但常見的貝茲曲線是三次方貝茲曲線( Cubic bezier curve ),這次會製作繪製三次方貝茲曲線( Cubic bezier curve )的範例,在此做個紀錄。

內容

  二次貝茲曲線(Quadratic bezier curve)與三次方貝茲曲線( Cubic bezier curve )的差異在於前者的控制點是3個,後者的控制點是4個。為什麼說三次方貝茲曲線( Cubic bezier curve )比較常見呢?手上的繪圖軟體 Painter.NET 與 Blender 的貝茲曲線工具都是4個控制點,如下圖
Painter.NET 的曲線工具
Blender的曲線工具
接著就來看範例,如下
HTML 的部分
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
<canvas id="myCanvas1" width=400 height=300></canvas>
<br>
<input id="btnDrawCtrlPoints"type="button" value="DrawCtrlPoints"/>
<br>
<input id="btnDrawCurve"type="button" value="DrawCurve"/>
<input type="range" min="2" max="100" value="30" class="slider" id="sliderLerp">
<label id="labelLerpValue">30</label>
</body>
</html>

Javascript 的部分
let canvas1 = document.getElementById('myCanvas1');
let glCTX1 = canvas1.getContext('webgl');
let vboPrimitiveCon = 0;
let vbo=createDynamicBuffer(glCTX1);
let shaderProg = createShader(glCTX1);
//
function createShader(glContext){
  let vertShader = glContext.createShader(glContext.VERTEX_SHADER);
  glContext.shaderSource(
    vertShader , 
    'attribute vec3 pos;void main(void){gl_Position=vec4(pos, 1.0);}'
  );
  glContext.compileShader(vertShader);
  let fragShader = glContext.createShader(glContext.FRAGMENT_SHADER);
  glContext.shaderSource(
    fragShader, 
    'void main(void){gl_FragColor=vec4(1,1,1,1);}'
  );
  glContext.compileShader(fragShader);
  let prog = glContext.createProgram();
  glContext.attachShader(prog, vertShader);
  glContext.attachShader(prog, fragShader);
  glContext.linkProgram(prog);
  glContext.useProgram(prog);  
  
  return prog;
}
function createDynamicBuffer(glContext){
  let vertexBuf = glContext.createBuffer();
  glContext.bindBuffer(glContext.ARRAY_BUFFER, vertexBuf);
  let dataArray=new Float32Array([ 
       0.0, 0.5, 0.0,  
      -0.5,-0.5, 0.0,  
      -0.5,-0.5, 0.0,
       0.5,-0.5, 0.0,
       0.5,-0.5, 0.0,
       0.0, 0.5, 0.0
    ]);
  glContext.bufferData(
    glContext.ARRAY_BUFFER, 
    3000, 
    glContext.DYNAMIC_DRAW
  );
  //write deafult data...
  glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,dataArray);
  vboPrimitiveCon = dataArray.length / 3;
  return vertexBuf;
}
function simpleDraw(glContext){
  glContext.useProgram(shaderProg);
  //
  glContext.viewport(0,0,glContext.canvas.width,glContext.canvas.height);
  glContext.clearColor(0, 0, 1, 1);
  glContext.clear(glContext.COLOR_BUFFER_BIT);
  //
  glContext.bindBuffer(glContext.ARRAY_BUFFER, vbo);
  let posLoc = glContext.getAttribLocation(shaderProg, "pos");
  glContext.vertexAttribPointer(posLoc, 3, glContext.FLOAT, false, 0, 0);
  glContext.enableVertexAttribArray(posLoc);

  glContext.drawArrays(glContext.LINES, 0, vboPrimitiveCon);
}
function generateLineListData(ar){
  let tagAr=[];
  let mod=ar.length%3;
  let elementAmount=(ar.length-mod)/3;
  if(elementAmount>=2 && mod===0){
    tagAr.push(ar[0]);
    tagAr.push(ar[1]);
    tagAr.push(ar[2]);
    //
    for(let i=3;i<(ar.length-3);i+=3){
      tagAr.push(ar[i]);
      tagAr.push(ar[i+1]);
      tagAr.push(ar[i+2]);
      //
      tagAr.push(ar[i]);
      tagAr.push(ar[i+1]);
      tagAr.push(ar[i+2]);
    }
    //
    tagAr.push(ar[ar.length-3]);
    tagAr.push(ar[ar.length-2]);
    tagAr.push(ar[ar.length-1]);
  }
  return new Float32Array(tagAr);
}
function generateBezierCurve(p0,p1,p2,lerp){
  if(lerp < 2)
    return [];
  //
  let tagAr = [];
  for(let i=0;i < lerp;i++){
    let t = i/(lerp-1);
    let invT = 1.0-t;
    let part0Value = invT * invT;
    let part1Value = 2 * t * invT;
    let part2Value = t * t;
    let part0 = [part0Value*p0[0], part0Value*p0[1], part0Value*p0[2] ];
    let part1 = [part1Value*p1[0], part1Value*p1[1], part1Value*p1[2] ];
    let part2 = [part2Value*p2[0], part2Value*p2[1], part2Value*p2[2] ];
    tagAr.push(part0[0] + part1[0] + part2[0]);
    tagAr.push(part0[1] + part1[1] + part2[1]);
    tagAr.push(part0[2] + part1[2] + part2[2]);
  }
  return tagAr;
}
function generateCubicBezierCurve(p0,p1,p2,p3,lerp){
  if(lerp < 2)
    return [];
  //
  let tagAr = [];
  for(let i=0;i < lerp;i++){
    let t = i/(lerp-1);
    let invT = 1.0-t;
    let part0Value = invT * invT * invT;
    let part1Value = 3 * t * invT * invT;
    let part2Value = 3 * t * t * invT;
    let part3Value = t * t * t;
    let part0 = [part0Value*p0[0], part0Value*p0[1], part0Value*p0[2] ];
    let part1 = [part1Value*p1[0], part1Value*p1[1], part1Value*p1[2] ];
    let part2 = [part2Value*p2[0], part2Value*p2[1], part2Value*p2[2] ];
    let part3 = [part3Value*p3[0], part3Value*p3[1], part3Value*p3[2] ];
    tagAr.push(part0[0] + part1[0] + part2[0] + part3[0]);
    tagAr.push(part0[1] + part1[1] + part2[1] + part3[1]);
    tagAr.push(part0[2] + part1[2] + part2[2] + part3[2]);
  }
  return tagAr;
}
function myRender(){
  simpleDraw(glCTX1);
  //
  window.requestAnimationFrame(myRender);
}
//
let ctrlPointList=[
  [-0.9,0.0,0.0],
  [-0.45,0.9,0.0],
  [0.45,0.9,0.0],
  [0.9,0.0,0.0],
];
let tagLerpValue=document.getElementById("sliderLerp").value;
function UpdateCurveData(){
  let data=generateCubicBezierCurve(
    ctrlPointList[0],
    ctrlPointList[1],
    ctrlPointList[2],
    ctrlPointList[3],
    tagLerpValue);
  let dataArray=generateLineListData(data);
  glCTX1.bindBuffer(glCTX1.ARRAY_BUFFER, vbo);
  glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,dataArray);
  vboPrimitiveCon = dataArray.length / 3;
}
document.getElementById("btnDrawCtrlPoints").onclick=function(evt){
  let data = [];
  for(let i=0;i<ctrlPointList.length;i++)
    data.push(...ctrlPointList[i]);
  //
  let dataArray=generateLineListData(data);
  glCTX1.bindBuffer(glCTX1.ARRAY_BUFFER, vbo);
  glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,dataArray);
  vboPrimitiveCon = dataArray.length / 3;
}
document.getElementById("btnDrawCurve").onclick=function(evt){
  UpdateCurveData();
}
document.getElementById("sliderLerp").oninput=function(evt){
  tagLerpValue=this.value;
  UpdateCurveData();
  document.getElementById("labelLerpValue").innerHTML = this.value;
}
window.onload = function(){
  window.requestAnimationFrame(myRender);
  
}

執行結果如下
繪製控制點
繪製曲線


這次的範例是由 WebGL的繪製曲線( Curve ) 的範例改來,重複的部分就不再說明。這次多了generateCubicBezierCurve() ,由於控制點多了一個,所以參數多一個 p3 ,曲線的攻勢可以參考 [ Wiki ]貝茲曲線 ,裡面有對應的公式。 ctrlPointList 要多一個控制點,UpdateCurveData()換成用 generateCubicBezierCurve() 來產生資料,最後在 btnDrawCtrlPoints 的事件改了作法,上次直接使用寫3行推入陣列,這次改採使用迴圈的方法。

參考資料

[ Wiki ]貝茲曲線

相關文章

WebGL的繪製曲線( Curve )

2019年10月14日 星期一

WebGL的繪製曲線( Curve )

WebGL的繪製曲線( Curve )

前言

  續之前的 WebGL的繪製線段 ,該篇介紹了 WebGL 的基本繪製線段的方法,這次要建立一個繪製曲線( Curve )的基本繪製環境,在此做個紀錄。

內容

  這次會製作一個繪製曲線的基本環境,範例會繪製一個二次貝茲曲線,二次貝茲曲線的知識可以在 [ Wiki ]貝茲曲線 取得。範例可以在曲線( Curve )與控制點線段做切換,方便比對差異,範例如下
HTML 的部分
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
<canvas id="myCanvas1" width=400 height=300></canvas>
<br>
<input id="btnDrawCtrlPoints"type="button" value="DrawCtrlPoints"/>
<br>
<input id="btnDrawCurve"type="button" value="DrawCurve"/>
<input type="range" min="2" max="100" value="30" class="slider" id="sliderLerp">
<label id="labelLerpValue">30</label>
</body>
</html>

Javascript 的部分
let canvas1 = document.getElementById('myCanvas1');
let glCTX1 = canvas1.getContext('webgl');
let vboPrimitiveCon = 0;
let vbo=createDynamicBuffer(glCTX1);
let shaderProg = createShader(glCTX1);
//
function createShader(glContext){
  let vertShader = glContext.createShader(glContext.VERTEX_SHADER);
  glContext.shaderSource(
    vertShader , 
    'attribute vec3 pos;void main(void){gl_Position=vec4(pos, 1.0);}'
  );
  glContext.compileShader(vertShader);
  let fragShader = glContext.createShader(glContext.FRAGMENT_SHADER);
  glContext.shaderSource(
    fragShader, 
    'void main(void){gl_FragColor=vec4(1,1,1,1);}'
  );
  glContext.compileShader(fragShader);
  let prog = glContext.createProgram();
  glContext.attachShader(prog, vertShader);
  glContext.attachShader(prog, fragShader);
  glContext.linkProgram(prog);
  glContext.useProgram(prog);  
  
  return prog;
}
function createDynamicBuffer(glContext){
  let vertexBuf = glContext.createBuffer();
  glContext.bindBuffer(glContext.ARRAY_BUFFER, vertexBuf);
  let dataArray=new Float32Array([ 
       0.0, 0.5, 0.0,  
      -0.5,-0.5, 0.0,  
      -0.5,-0.5, 0.0,
       0.5,-0.5, 0.0,
       0.5,-0.5, 0.0,
       0.0, 0.5, 0.0
    ]);
  glContext.bufferData(
    glContext.ARRAY_BUFFER, 
    3000, 
    glContext.DYNAMIC_DRAW
  );
  //write deafult data...
  glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,dataArray);
  vboPrimitiveCon = dataArray.length / 3;
  return vertexBuf;
}
function simpleDraw(glContext){
  glContext.useProgram(shaderProg);
  //
  glContext.viewport(0,0,glContext.canvas.width,glContext.canvas.height);
  glContext.clearColor(0, 0, 1, 1);
  glContext.clear(glContext.COLOR_BUFFER_BIT);
  //
  glContext.bindBuffer(glContext.ARRAY_BUFFER, vbo);
  let posLoc = glContext.getAttribLocation(shaderProg, "pos");
  glContext.vertexAttribPointer(posLoc, 3, glContext.FLOAT, false, 0, 0);
  glContext.enableVertexAttribArray(posLoc);

  glContext.drawArrays(glContext.LINES, 0, vboPrimitiveCon);
}
function generateLineListData(ar){
  let tagAr=[];
  let mod=ar.length%3;
  let elementAmount=(ar.length-mod)/3;
  if(elementAmount>=2 && mod===0){
    tagAr.push(ar[0]);
    tagAr.push(ar[1]);
    tagAr.push(ar[2]);
    //
    for(let i=3;i<(ar.length-3);i+=3){
      tagAr.push(ar[i]);
      tagAr.push(ar[i+1]);
      tagAr.push(ar[i+2]);
      //
      tagAr.push(ar[i]);
      tagAr.push(ar[i+1]);
      tagAr.push(ar[i+2]);
    }
    //
    tagAr.push(ar[ar.length-3]);
    tagAr.push(ar[ar.length-2]);
    tagAr.push(ar[ar.length-1]);
  }
  return new Float32Array(tagAr);
}
function generateBezierCurve(p0,p1,p2,lerp){
  if(lerp < 2)
    return [];
  //
  let tagAr = [];
  for(let i=0;i < lerp;i++){
    let t = i/(lerp-1);
    let invT = 1.0-t;
    let part0Value = invT * invT;
    let part1Value = 2 * t * invT;
    let part2Value = t * t;
    let part0 = [part0Value*p0[0], part0Value*p0[1], part0Value*p0[2] ];
    let part1 = [part1Value*p1[0], part1Value*p1[1], part1Value*p1[2] ];
    let part2 = [part2Value*p2[0], part2Value*p2[1], part2Value*p2[2] ];
    tagAr.push(part0[0] + part1[0] + part2[0]);
    tagAr.push(part0[1] + part1[1] + part2[1]);
    tagAr.push(part0[2] + part1[2] + part2[2]);
  }
  return tagAr;
}
function myRender(){
  simpleDraw(glCTX1);
  //
  window.requestAnimationFrame(myRender);
}
//
let ctrlPointList=[
  [-0.9,0.0,0.0],
  [0.0,0.9,0.0],
  [0.9,0.0,0.0],
];
let tagLerpValue=document.getElementById("sliderLerp").value;
function UpdateCurveData(){
  let data=generateBezierCurve(
    ctrlPointList[0],
    ctrlPointList[1],
    ctrlPointList[2],
    tagLerpValue);
  let dataArray=generateLineListData(data);
  glCTX1.bindBuffer(glCTX1.ARRAY_BUFFER, vbo);
  glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,dataArray);
  vboPrimitiveCon = dataArray.length / 3;
}
document.getElementById("btnDrawCtrlPoints").onclick=function(evt){
  let data = [];
  data.push(...ctrlPointList[0]);
  data.push(...ctrlPointList[1]);
  data.push(...ctrlPointList[2]);
  let dataArray=generateLineListData(data);
  glCTX1.bindBuffer(glCTX1.ARRAY_BUFFER, vbo);
  glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,dataArray);
  vboPrimitiveCon = dataArray.length / 3;
}
document.getElementById("btnDrawCurve").onclick=function(evt){
  UpdateCurveData();
}
document.getElementById("sliderLerp").oninput=function(evt){
  tagLerpValue=this.value;
  UpdateCurveData();
  document.getElementById("labelLerpValue").innerHTML = this.value;
}
window.onload = function(){
  window.requestAnimationFrame(myRender);
  
}

範例的的執行結果如下
執行結果

在按下 DrawCtrlPoints 的按鍵後可以看到控制點線段,如下圖
控制點線段

在按下 DarwCurve 的按鍵後可以看到曲線( Curve ),如下圖
曲線( Curve )

 DarwCurve 按鍵有個可以調的數值,這個數值可以控制線段的精細度,各種精細度的比對如下
左上精細度為10,右上的精細度為8,左下的精細度為5,右下的精細度為3,可以看到精細度兌現對的影響程度

這次的範例是從 WebGL的繪製線段 的範例改來,所以只解說改的地方,在createDynamicBuffer() 的地方和上次不一樣,這次是用 size 來絕決定 Vertex buffer 的大小,主要是要給一個不容易爆的值,3000可以塞1000個點,如果不夠可以再改大。generateLineListData() 可以將頂點的資料轉成 WebGL 的 drawArrays() 所需要的頂點排列,其實內容很簡單,除了第一個頂點與最後一個頂點外都要複製一次。generateBezierCurve() 是負責產生二次貝茲曲線,輸入3個頂點與精細度後產生頂點,如果對二次貝茲曲線不熟可以參考
 [ Wiki ]貝茲曲線  。剩下就是因應這次的需求所增加的UI事件,由於期望每次改變精細度的時候能馬上反應,所以刻意留一個 updateCurveData() 可以馬上反應資料到畫面。

  本次的範例只是做為一個實驗場,由於對曲線( Curve )不是很熟,需要實驗場來輔助理解曲線( Curve ),下次會嘗試繪製三次貝茲曲線。

參考資料

[ Wiki ]貝茲曲線

相關文章 

WebGL的繪製線段

2019年10月7日 星期一

WebGL的繪製線段

WebGL的繪製線段

前言

  最近想整合曲線( Curve )到引擎,需要將曲線( Curve )繪製出來,曲線( Curve )要繪製出來就需要靠繪製大量的直線來實現,但想想自己只有在 OpenGL 與 Direct3D 繪製線段, WebGL 的繪製方法也一樣嗎?在此做個紀錄。

內容

  WebGL 繪製線段的方法和 OpenGL 差不多,有一點不太一樣,就是 WebGL 的繪製線段不能控制線段寬度,這裡提供一個繪製線段的範例,方便日後實驗繪製曲線( Curve )
HTML 的部分
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
<canvas id="myCanvas1" width=400 height=300></canvas>
<br>
<input id="btnUpdate"type="button" value="Update"/>
</body>
</html>

Javascript 的部分
let canvas1 = document.getElementById('myCanvas1');
let glCTX1 = canvas1.getContext('webgl');
let vboPrimitiveCon = 0;
let vbo=createDynamicBuffer(glCTX1);
let shaderProg = createShader(glCTX1);
//
function createShader(glContext){
  let vertShader = glContext.createShader(glContext.VERTEX_SHADER);
  glContext.shaderSource(
    vertShader , 
    'attribute vec3 pos;void main(void){gl_Position=vec4(pos, 1.0);}'
  );
  glContext.compileShader(vertShader);
  let fragShader = glContext.createShader(glContext.FRAGMENT_SHADER);
  glContext.shaderSource(
    fragShader, 
    'void main(void){gl_FragColor=vec4(1,1,1,1);}'
  );
  glContext.compileShader(fragShader);
  let prog = glContext.createProgram();
  glContext.attachShader(prog, vertShader);
  glContext.attachShader(prog, fragShader);
  glContext.linkProgram(prog);
  glContext.useProgram(prog);  
  
  return prog;
}
function createDynamicBuffer(glContext){
  let vertexBuf = glContext.createBuffer();
  glContext.bindBuffer(glContext.ARRAY_BUFFER, vertexBuf);
  let dataArray=new Float32Array([ 
       0.0, 0.5, 0.0,  
      -0.5,-0.5, 0.0,  
      -0.5,-0.5, 0.0,
       0.5,-0.5, 0.0,
       0.5,-0.5, 0.0,
       0.0, 0.5, 0.0
    ]);
  glContext.bufferData(
    glContext.ARRAY_BUFFER, 
    dataArray, 
    glContext.DYNAMIC_DRAW
  );
  vboPrimitiveCon = dataArray.length / 3;
  return vertexBuf;
}
function simpleDraw(glContext){
  glContext.useProgram(shaderProg);
  //
  glContext.viewport(0,0,glContext.canvas.width,glContext.canvas.height);
  glContext.clearColor(0, 0, 1, 1);
  glContext.clear(glContext.COLOR_BUFFER_BIT);
  //
  glContext.bindBuffer(glContext.ARRAY_BUFFER, vbo);
  let posLoc = glContext.getAttribLocation(shaderProg, "pos");
  glContext.vertexAttribPointer(posLoc, 3, glContext.FLOAT, false, 0, 0);
  glContext.enableVertexAttribArray(posLoc);

  glContext.drawArrays(glContext.LINES, 0, vboPrimitiveCon);
}

function myRender(){
  simpleDraw(glCTX1);
  //
  window.requestAnimationFrame(myRender);
}
document.getElementById("btnUpdate").onclick=function(evt){
  let dataArray=new Float32Array([ 
       0.0,-0.5, 0.0,  
      -0.5, 0.5, 0.0,  
      -0.5, 0.5, 0.0,
       0.5, 0.5, 0.0,
       0.5, 0.5, 0.0,
       0.0,-0.5, 0.0
  ]);
  glCTX1.bindBuffer(glCTX1.ARRAY_BUFFER, vbo);
  glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,dataArray);
  vboPrimitiveCon = dataArray.length / 3;
}
window.onload = function(){
  window.requestAnimationFrame(myRender);
  
}


這次的範例從 解決多個 WebGLRenderContext 之間無法共享資源的問題 的範例修改而來,Vertex buffer 的部分採用動態的方式來建造,初始化時會給一個正三角的線段資料,畫面如下
範例執行畫面

範例有個 Update 的按鈕,按下去後會填充倒三角形的資料到 Vertex buffer ,畫面如下
更改Vertex buffer 的線段資料

這次的範例是個開端,之後會再推出繪製曲線( Curve )的範例。

相關文章

解決多個 WebGLRenderContext 之間無法共享資源的問題

2019年9月30日 星期一

觀看 Vulkan 的 Extension 的軟體

觀看 Vulkan 的 Extension 的軟體

前言

  要觀看 Vulkan 的 Extension 可以利用官方 SDK 裡的 Vulkan Configuration Tool 來觀看,但我覺得介面做的不是很好看,所以找了另一個軟體來替代。

內容

   Vulkan Configuration Tool 在"(Vulkan SDK root)/(SDK version)/Tools/vkconfig.exe",執行後畫面如下
Vulkan Configuration Tool 

執行後點選 Installation Analzer 頁面,裡面的資料相當完整,但點選的介面個人不是很喜歡,Extension 的部分使用表格的方式顯示,但很佔畫面,感覺不是很密集。

  GPU Caps Viewer 這個軟體的排版就是我喜歡的密集排法,執行畫面如下
GPU Caps Viewer

用法也很簡單,執行後點選 Vulkan 頁面就可以看到所有的 Extension ,顯示方式密集比較不佔空間。

參考資料

GPU Caps Viewer

2019年9月23日 星期一

在 Direct3D12 替換掉 IID_PPV_ARGS()

在 Direct3D12 替換掉 IID_PPV_ARGS()

前言

  在 [ Github ]微軟的 Direct3D12 範例 裡,常常會在製造資源的相關 Function 中看到這個 Macro( IID_PPV_ARGS()  ),如果追蹤這個 Macro 會發現裡面會用到擴充語法 __uuidof ,由於個人不喜歡用擴充語法,所以就想找替代 IID_PPV_ARGS() 的方法,在此把學習的過程做個紀錄。

內容

  在追蹤這個 Macro 後會發現內容如下
#define IID_PPV_ARGS(ppType) __uuidof(**(ppType)), IID_PPV_ARGS_Helper(ppType)

先來看看使用 Macro 的範例,以 D3D12CreateDevice() 為例,如下
ComPtr<IDXGIAdapter1> hardwareAdapter;
ComPtr<ID3D12Device> m_device;
D3D12CreateDevice(hardwareAdapter.Get(),D3D_FEATURE_LEVEL_11_0,IID_PPV_ARGS(&m_device);

再搭配 [ MSDN ] D3D12CreateDevice() 來看,會發現這個 Macro 其實就是說入兩個變數,一個是 RFEIID 與一個 void** 的指標, void**指標的部分很簡單,其實就是強制轉型而已,但 REFIID 的部分就比較麻煩,因為使用到擴充語法  __uuidof ,可以在 [ MSDN ] __uuidof 取得更多的資料。

  REFIID 在追蹤後發現 Direcct3D12 會為一些 COM 定義專屬的ID,ID3D12Device 對應的ID就是 IID_ID3D12Device,這個ID會定義在 d3d12.h 裡,所以範例的程式碼可以換成以下
IDXGIAdapter1* pHardwareAdapter;
ID3D12Device* m_pDevice;
D3D12CreateDevice(pHardwareAdapter,D3D_FEATURE_LEVEL_11_0,IID_ID3D12Device,(void**)&m_pDevice);

使用這個交換要注意要多 Link 一個 dxguid.lib,這樣就可以迴避使用擴充語法。

參考資料

[ Github ]微軟的 Direct3D12 範例
[ MSDN ] __uuidof
[ MSDN ] D3D12CreateDevice()

2019年9月16日 星期一

用 electron-builder 建置 Windows 平台應用程式

用 electron-builder 建置 Windows 平台應用程式

前言

  在先前的 初探Electron 裡建置了 Electron 的基本環境,雖然可以透過 npm 來執行,但如果要發佈程式給一般使用者時這個方法就不專業了, Electron 目前有 electron-packager 和 electron-builder 兩個套件可以提供打包,這次我選擇 electron-builder 來進行打包,並把學習的過程做個紀錄。

內容

  首先建立一個基本的 Electron 的基本執行環境,可以參考之前的 初探Electron 與  Electron4的使用範例 ,這裡就不再詳細解說。建立好基本執行環境後就可以安裝 electron-builder 套件,
到專案的 root 資料夾下,輸入以下命令
npm install electron-builder

安裝完套件後,將原本的 package.json 修改如下
{
  "name": "electronbuildertest",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "pack": "electron-builder --dir",
    "dist": "electron-builder",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    
  },
  "build": {
    "appId": "com.xxx.app.electronbuildertest",
    "productName": "electron-builder_test",
    "directories": {
      "output": "build"
    },
    "win": {
    }
  },
  "devDependencies": {
    "electron": "^6.0.0",
    "electron-builder": "^21.2.0"
  }
}

紅字的地方為需要修改的部分,在 scripts 新增 "pack"與"dist"兩道命令, dependencies 的部分如果有 electron 與 electron-builder 的話,請將內容移到 devDependencies 裡,這是因為 electron-builder 在打包時不允許上述套件有相依。 build 的部分 appId 與 productName,可以自己命名,directories 是設定打包後的資料放置位置, win 的部分指的是打包 windows 平台時的參數,不寫會用預設的。

  接下來就是打包,在專案的 root 資料夾下,輸入以下
npm run dist

執行完後,可以在專案 root 資料夾下的 build 資料夾看到以下
electron-builder 建置 Windows 平台的安裝檔

在圖中可以看到 win-unpacked 資料夾,裡面有執行檔可以直接看結果,當然要透過安裝檔看結果也可以。

  一般的程式通常會需要有自己的資料,這些資料 electron-builder 並不會自己打包,要在  package.json 裡面註記才行,這裡建議把程式所需的資料都放在同一個資料夾,這樣就不用每次新增或刪除資料都要動到 package.json 。這裡提供一個範例是打包資料夾的範例,首先修改index.html
<!DOCTYPE html>
  <html>
    <head>
      <meta charset="UTF-8">
      <title>Hello World!</title>
    </head>
    <body>
      <h1>Hello World!</h1>
      我們用了 node <script>document.write(process.versions.node)</script>,
      Chrome <script>document.write(process.versions.chrome)</script>,
      以及 Electron <script>document.write(process.versions.electron)</script>.
      <img src="./ProjectData/img/test.png"></img>
    </body>
  </html>

修改的部分很簡單就是新增一個 Image ,它會去讀取 ./ProjectData/img/test.png ,請產生對應的資料夾與檔案,接著修改 package.json 如下
{
  "name": "electronbuildertest",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "pack": "electron-builder --dir",
    "dist": "electron-builder",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    
  },
  "build": {
    "appId": "com.xxx.app.electronbuildertest",
    "productName": "electron-builder_test",
    "directories": {
      "output": "build"
    },
    "extraFiles": [
      {
        "from": "ProjectData",
        "to": ""
      }

    ],
    "win": {
    }
  },
  "devDependencies": {
    "electron": "^6.0.0",
    "electron-builder": "^21.2.0"
  }
}

紅字的部分為新增 ProjectData 為打包資料夾,這個部分的命名可以自由更改,更改完再打包一次就可以看到結果。

參考資料

electron-builder 官網
Michael_Jheng的部落格

相關文章

初探Electron
Electron4的使用範例

2019年9月7日 星期六

關於"Vulkan開發實戰詳解"這本書

關於"Vulkan開發實戰詳解"這本書

前言

  由於最近在研究 Vulkan 就買書研究,最近出版的"Vulkan開發實戰詳解"這本書今天寄來了,看了一下後發現這本書有一些問題,在此做個紀錄。

內容

  "Vulkan開發實戰詳解"這本書是一本簡體書,該書的封面如下
Vulkan開發實戰詳解

這本書由於剛拿到並沒有看完,但看了一下發現有問題,書中解釋範例時雖然很貼心的會註明該程式碼片段在哪個檔案,但這本書並沒有隨書附贈光碟!本以為範例可能放在 Github 或者作者的網站之類的地方,但最後發現是放在出版社的异步社区 的官網。在官網搜尋該書後可以找到下載範例的位址,如下圖
範例下載位址

下載時需要登錄網站,所以需要註冊帳號,如下圖
官網的帳號註冊

官網的註冊帳號強迫使用手機號碼,由於個人很介意提供手機號碼,所以沒註冊,也就是說我沒辦法拿到範例的原始碼,看來只能等等看有沒有下載完的放在別的空間了。

  這本書在買之前請考慮是否提供手機號碼給出版社,不給手機號碼就不給範例原始碼,這"套路"我第一次碰到。這本書並不便宜,連壓光碟的錢也要省,省了壓光碟的錢又可以得到客戶的電話號碼,這套路以後可要小心小心。




2019年9月2日 星期一

關於顯示卡對 Direct3D12 的支援情況

關於顯示卡對 Direct3D12 的支援情況

前言

  在我的桌機用GPU-Z打開後會看到下圖
GPU-Z 顯示 DirectX Support 為12(11_0)

注意圖中的 DirectX Support 這個欄位,顯示的內容是 12(11_0) ,以前只覺得怪怪的,為什麼後方有個 11_0 ?恰巧最近將繪圖引擎導入 Direct3D12,在導入的過程了解到後方的數字是什麼意思,在此把學習的過程做個紀錄。

內容

  一般在初始化 Direct3D12 時會使用 D3D12CreateDevice(),就像以下
HRESULT hr=D3D12CreateDevice(pAdapter,D3D_FEATURE_LEVEL_11_0,IID_PPV_ARGS(&pDevice) );

注意第二個參數,這個參數就是在 GPU-Z 裡的 11_0,這個參數的說明可以在 [ MSDN ] D3D12CreateDevice 。如果把參數改成"D3D_FEATURE_LEVEL_12_0"或"D3D_FEATURE_LEVEL_12_1"的話,在 GTX 650 Ti BOOST 的顯示卡就會失敗。目前 Direct3D12 的最低相容是"D3D_FEATURE_LEVEL_11_0",所以範例的寫法可以解釋成用最大的相容性來製造 Device,如果不要相容性的話就改成"D3D_FEATURE_LEVEL_12_0"或"D3D_FEATURE_LEVEL_12_1"。

  要查詢是否支援 Direct3D12 話可以用 GPU Specs Database 來做查詢,以下說明查詢 GTX 650 Ti BOOST 的流程,如下圖

查詢 GTX 650 Ti BOOST 的步驟-1

在 Generation 欄位選擇 GeForce 600,接著如下圖
查詢 GTX 650 Ti BOOST 的步驟-2

在下方的資料選擇  GTX 650 Ti BOOST ,接著就可以看到結果如下
查詢 GTX 650 Ti BOOST 的結果

這個網站相當的方便,不只是桌機的顯示卡,連筆電的 GPU 也可以查詢,美中不足的是不能查手機的GPU。

參考資料

GPU Specs Database
[ MSDN ] D3D12CreateDevice

2019年8月26日 星期一

使圖片的 Blob 初始化 HTMLImageElement

使圖片的 Blob 初始化 HTMLImageElement

前言

  最近需要透過 Electron 來讀圖檔後轉成 WebGL 的 Texture ,但這個過程會有問題,Electron 讀檔後的資料型態是 UInt8Array ,本來想得很簡單,透過在如何直接把檔案的 Binary data 給 HTMLImage​Element裡所用的方法來餵資料,不幸的有問題,這裡把解決的過程做個紀錄。

內容

  從 Electron 讀檔後的資料型態為  UInt8Array ,本想編碼成 Base64 後再加上 MIME 的資訊後就能直接初始化 HTMLImageElement ,這個過程的問題是如何從 UInt8Array  裡判斷出 MIME 的資訊,難道要 Parse 裡面的資料嗎? 瀏覽器支援的圖片格式不少,這樣 Parse 的成本實在太高,所以找了一下網路的資料,在HTML – DOWNLOAD IMAGE THROUGH AJAX AND DISPLAY IT 裡發現它透過 Ajax 下載圖檔後,遽然不用 pasre 任何資料就可以初始化 HTMLImageElement ,是如何辦到的呢?它將下載完的 Blob 透過 window.URL.createObjectURL() 回傳的結果就可以直接初始化 HTMLImageElement ,範例如下
HTML 的部分
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  <input id="btn" type="button" value="go" />
  <img id="img"></img>
</body>
</html>

Javascript的部分
window.onload=function(){
  document.getElementById("btn").onclick=function(evt){
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
      if (xhr.readyState==4 && xhr.status==200) {
        var imgURL = window.URL.createObjectURL(xhr.response);
        var img = document.getElementById("img");
        img.onload = function(){
          window.URL.revokeObjectURL(xhr.response);
        }
        img.src = imgURL;
        //
        
      }
    }
    xhr.responseType = "blob";
    xhr.open("GET","https://i.imgur.com/rPv3FWk.jpg",true);
  
    xhr.send();
  }
}

在 Ajax 下載完後透過 window.URL.createObjectURL() 得到 imgURL ,接著直接拿  imgURL 來初始化 HTMLImageElement ,整個過程相當簡單。 imgURL 的內容是什麼?如果把他列應出來會發現是一個字串!一個類似網址的字串,可以想像是製造一個瀏覽器本地的檔案位置,當 HTMLImageElement  初始化完成後,透過 window.URL.revokeObjectURL() 來取消檔案位址。

  開頭說過 Electron 讀檔的資料型態為 UInt8Array,但範例需要的型態是 Blob ,如何轉換呢?可以參考  ArrayBuffer to blob conversion ,過程不會太複雜,範例如下
var blobData=new Blob( [uint8ArrayData] );

要注意的是要用 Array的型態來給,不能省略。

參考資料

[ MDN ] URL.createObjectURL()
ArrayBuffer to blob conversion
HTML – DOWNLOAD IMAGE THROUGH AJAX AND DISPLAY IT

相關文章

如何直接把檔案的 Binary data 給 HTMLImage​Element

2019年8月19日 星期一

建置 glslang 在 Windows

建置 glslang 在 Windows

前言

  最近研究 Vulkan 時發現 API 本身不提供 Shader 編譯器,官方的建議是利用官方提供的 glslangValidator ,這個程式可以在 Vulkan SDK 裡找到,利用 Console 命令編譯 Shader後輸出Spir-V程式碼。但如果需要在 Runtime 時編譯 Shader 的話就需要自己編譯一個編譯器,幸運的是官方有提供編譯器,但不幸的是官方不會 Build 好在 Vulkan SDK ,只能自己 Build,由於 Build 的過程有些繁雜,在此將過程做個紀錄。

內容

  Vulkan 官方提供兩個編譯器,glslang 與 shaderc,這兩個編譯器的專案都在 Vulkan SDK 裡面,但為什麼官方需要兩個編譯器呢?在官方的範例 (Vulkan SDK)\Samples\API-Samples\utils\util.cpp 裡有一段程式碼如下
bool GLSLtoSPV(const VkShaderStageFlagBits shader_type, const char *pshader, std::vector<unsigned int> &spirv) {
#ifndef __ANDROID__
    EShLanguage stage = FindLanguage(shader_type);
    glslang::TShader shader(stage);
    glslang::TProgram program;
    const char *shaderStrings[1];
    TBuiltInResource Resources = {};
    init_resources(Resources);

    // Enable SPIR-V and Vulkan rules when parsing GLSL
    EShMessages messages = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules);

    shaderStrings[0] = pshader;
    shader.setStrings(shaderStrings, 1);

    if (!shader.parse(&Resources, 100, false, messages)) {
        puts(shader.getInfoLog());
        puts(shader.getInfoDebugLog());
        return false;  // something didn't work
    }

    program.addShader(&shader);

    //
    // Program-level processing...
    //

    if (!program.link(messages)) {
        puts(shader.getInfoLog());
        puts(shader.getInfoDebugLog());
        fflush(stdout);
        return false;
    }

    glslang::GlslangToSpv(*program.getIntermediate(stage), spirv);
#else
    // On Android, use shaderc instead.
    shaderc::Compiler compiler;
    shaderc::SpvCompilationResult module =
        compiler.CompileGlslToSpv(pshader, strlen(pshader), MapShadercType(shader_type), "shader");
    if (module.GetCompilationStatus() != shaderc_compilation_status_success) {
        LOGE("Error: Id=%d, Msg=%s", module.GetCompilationStatus(), module.GetErrorMessage().c_str());
        return false;
    }
    spirv.assign(module.cbegin(), module.cend());
#endif
    return true;
}

 GLSLtoSPV 是把 GLSL 編成 Spir-V 碼, Function 在開始就使用 Preprocessor 隔開,一邊是"非Android",另一邊是"Android"。在"非Android"時會使用 glslang ,另一邊則是 shaderc, glslang 完全沒出現在 (Vulkan SDK)\include 裡,所以要自己 Build ,這沒什麼問題,但 shaderc 的部分遽然有出現在 (Vulkan SDK)\include !可惜我實際 include 後會有錯誤,所以還是要建置  glslang 。

  glslang 所使用的建置是 CMake ,設定的方式可以參考下圖
使用CMake 建置 glslang
在"1"的位置設定 glslang 的專案位置,"2"的部分是建置的目標位置,這裡是直接在 glslang 的資料夾下產生 ProjWindows ,並把目標為位置設定在那,接著按下"3",就可以取得組態,組態取完後按下"4"就可以產生 Visual studio 方案。開啟方案後請建置"SPIRV"專案,如下圖
建置"SPIRV"專案

接著手動建置 Release 組態的"spirv-remap"專案,如下圖
手動建置 Release 組態的"spirv-remap"專案

這個步驟的主要目的產生 Release 的 SPVRemapper.lib,建置 Release 的"SPIRV"專案並不會產生,所以請手動建置。SPVRemapper.lib雖不需要用在編譯,但如果需要建置 Vulkan SDK 裡的其它專案時會用到,請不要省略此步驟。不幸的是建置完後並不會將所有相關的 lib 都集中起來,所以要自己 Copy ,這裡整理了需要 Copy 的檔案,清單如下
(CMake build target)\glslang\Debug\glslangd.lib
(CMake build target)\glslang\Release\glslang.lib
(CMake build target)\hlsl\Debug\HLSLd.lib
(CMake build target)\hlsl\Release\HLSL.lib
(CMake build target)\OGLCompilersDLL\Debug\OGLCompilerd.lib
(CMake build target)\OGLCompilersDLL\Release\OGLCompiler.lib
(CMake build target)\glslang\OSDependent\Windows\Debug\OSDependentd.lib
(CMake build target)\glslang\OSDependent\Windows\Release\OSDependent.lib
(CMake build target)\SPIRV\Debug\SPIRVd.lib
(CMake build target)\SPIRV\Release\SPIRV.lib
(CMake build target)\External\spirv-tools\source\Debug\SPIRV-Toolsd.lib
(CMake build target)\External\spirv-tools\source\Release\SPIRV-Tools.lib
(CMake build target)\External\spirv-tools\source\opt\Debug\SPIRV-Tools-optd.lib
(CMake build target)\External\spirv-tools\source\opt\Release\SPIRV-Tools-opt.lib

  最後還有需要 include 的檔案,這個比較簡單, (Vulkan SDK)\glslang\glslang 與  (Vulkan SDK)\glslang\SPIRV 兩個資料夾 的內容就是所有需要 include 的檔案,請示需要 Copy。

參考資料

Vulkan Tutorial

2019年8月12日 星期一

整理 WebGL2 與 WebGL1 的差異

整理 WebGL2 與 WebGL1 的差異

前言

  WebGL2 已經推出有一段時間了,自己學 WebGL2 也有一段時間了,這裡就把 WebGL2 與 WebGL1 的差異做個比較,順便做個紀錄。

內容

  在 WebGL1 的時候取得 Context 的程式碼如下
var context =  canvas.getContext( 'webgl') ||
               canvas.getContext( 'webkit-3d') ||
               canvas.getContext( 'experimental-webgl')||
               canvas.getContext( 'moz-3d');

WebGL2在取得 Context 的程式碼如下
var context =  canvas.getContext( 'webgl2');

WebGL2 在這部分比較簡單,因為沒有歷史的包袱。WebGL2 相容於 WebGL1,所以舊的程式應該能沿用,要注意的是依據 Extension 來執行的程式碼,有些 Extension 在WebGL1會出現,但在 WebGL2 卻不會再顯示,如 Ext_sRGB 與 WEBGL_depth_texture。

  根據  [ WiKi ] WebGL 裡所說WebGL2是基於 OpenGLES3 ,但在 Shader model 方面差相當的多, OpenGLES3 的 Shader mpdel 如下
OpenGLES3 的 Shader model
而 WebGL1 與 WebGL2 的 Shader model 如下
WebGL 的 Shader model

這裡可以看到 WebGL2 在 Shader model 是沒有改變的,所以不支援 Geometry shader 與 Tessellation shader。

  WebGL2 開始支援 UBO (Uniform buffer object)與 Sampler Object,用法跟 OpenGL 的用法一樣沒改,但 Sampler Object 使用要注意之前我發現的 Electron搭配WebGL使用的Bug。WebGL2 支援 Transform feedback ,但這個功能我沒用過以後再研究。

  最後介紹一下一些實用的網站,在 查詢瀏覽器支援 WebGL2 的狀況 可以查詢各個瀏覽器對於 WebGL2 的支援狀況,如果要查詢目前瀏覽器的支援狀況可以到 WebGL Report,網址是 查詢目前瀏覽器是否支援 WebGL2,開啟後可以看到下圖
查詢目前瀏覽器對於 WebGL 的支援狀況
不只可以查 WebGL2 ,WebGL1也可以順便查, Extension 的部分也會整個列印出來,用起來相當方便。

參考資料

查詢瀏覽器支援 WebGL2 的狀況
查詢目前瀏覽器是否支援 WebGL2
[ WiKi ] WebGL
[ MDN ] WebGL2RenderingContext