(cache) ☆PROJECT ASURA☆ [Vulkan] 『ポリゴン描画』

ポリゴン描画


 1.はじめに…
前回は,インストールと初期化処理を説明して終了してしまったので,今回はちゃんとポリゴン描画してみます。
…といっても,ディスクリプタセットも使わない,一番単純なやつです。





 2.ラッパークラス
さて,前回は初期化をメインに扱いました。さすがにあの初期化コードを毎回書くのはやってられないので,ラッパークラスとして処理をまとめておきます。
まともに説明してもよいのですが,説明した場合はほぼ前回の記事の焼き回しになってしまって,新しい内容がほとんど無いので,ラッパクラス自体についての説明は割愛させてください。興味がある人は各自でソースコードの実装を見てみてください。
現時点ではラッパークラスを下記のようにしてみました。
・DeviceMgr
 → VkInstance, VkDevice, VkPhysicalDevice, VkAllocationCallbackとか
・CommandList
 → VkCommandPool, VkCommandBufferとか
・Queue
 → VkQueue, VkFenceとか
・RenderBuffer
 → VkImageView, VkImageSubresourceRangeとか
・ImageResource
 → VkImage, VkDeviceMemoryとか
・BufferResource
 → VkBuffer,VkDeviceMemoryとか
・SwapChain
 → VkSurfaceKHR, VkSwapChainKHR, VkSemaphore, VkImageSubresourceRangeとか
さて,次はこれらのラッパークラスの初期化ですが,デフォルトで必要になるものは毎回ほぼ同じになるのでAppクラスの初期化処理で行ってしまうことにします。
今回からAppクラスににInitVulkan(),TermVulkan()を追加して,デフォルト状態で必要になるものはこのメソッドで生成・破棄を行います。
実装コードは下記のようにしました。
00331:  //-------------------------------------------------------------------------------------------------
00332:  //      Vulkanの初期化処理です.
00333:  //-------------------------------------------------------------------------------------------------
00334:  bool App::InitVulkan()
00335:  {
00336:      // デバイスマネージャ生成.
00337:      if (!m_DeviceMgr.Init())
00338:      {
00339:          ELOG( "Error : Device::Init() Failed." );
00340:          return false;
00341:      }
00342:  
00343:      // コマンドリスト生成.
00344:      if (!m_CommandList.Init(
00345:          &m_DeviceMgr, 
00346:          QueueType_Graphics,
00347:          VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
00348:          VK_COMMAND_BUFFER_LEVEL_PRIMARY,
00349:          ChainCount))
00350:      {
00351:          ELOG( "Error : CommandList::Init() Failed." );
00352:          return false;
00353:      }
00354:  
00355:      // コマンドリストをリセットしておく.
00356:      m_CommandList.Reset();
00357:      auto cmd = m_CommandList.GetCurrentCommandBuffer();
00358:  
00359:      // スワップチェインの生成.
00360:      {
00361:          SwapChainDesc desc;
00362:          desc.Width       = m_Width;
00363:          desc.Height      = m_Height;
00364:          desc.Format      = m_SwapChainFormat;
00365:          desc.ColorSpace  = VK_COLORSPACE_SRGB_NONLINEAR_KHR;
00366:          desc.BufferCount = ChainCount;
00367:          desc.hInstance   = m_hInst;
00368:          desc.hWnd        = m_hWnd;
00369:  
00370:          if (!m_SwapChain.Init(&m_DeviceMgr, cmd, &desc))
00371:          {
00372:              ELOG( "Error : SwapChain::Init() Falied." );
00373:              return false;
00374:          }
00375:      }
00376:  
00377:      // 深度バッファの生成.
00378:      {
00379:          RenderBufferDesc desc;
00380:          desc.Dimension   = VK_IMAGE_TYPE_2D;
00381:          desc.Width       = m_Width;
00382:          desc.Height      = m_Height;
00383:          desc.Depth       = 1;
00384:          desc.ArraySize   = 1;
00385:          desc.Format      = m_DepthFormat;
00386:          desc.MipLevels   = 1;
00387:          desc.Samples     = VK_SAMPLE_COUNT_1_BIT;
00388:          desc.Usage       = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
00389:  
00390:          if (!m_DepthBuffer.Init(&m_DeviceMgr, cmd, &desc))
00391:          {
00392:              ELOG( "Error : DepthBuffer::Init() Failed." );
00393:              return false;
00394:          }
00395:      }
00396:  
00397:      // レンダーパスの生成.
00398:      {
00399:          VkAttachmentDescription attachments[2];
00400:          attachments[0].format           = m_SwapChain.GetDesc().Format;
00401:          attachments[0].samples          = VK_SAMPLE_COUNT_1_BIT;
00402:          attachments[0].loadOp           = VK_ATTACHMENT_LOAD_OP_CLEAR;
00403:          attachments[0].storeOp          = VK_ATTACHMENT_STORE_OP_STORE;
00404:          attachments[0].stencilLoadOp    = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
00405:          attachments[0].stencilStoreOp   = VK_ATTACHMENT_STORE_OP_DONT_CARE;
00406:          attachments[0].initialLayout    = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
00407:          attachments[0].finalLayout      = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
00408:          attachments[0].flags            = 0;
00409:  
00410:          attachments[1].format           = m_DepthBuffer.GetDesc().Format;
00411:          attachments[1].samples          = m_DepthBuffer.GetDesc().Samples;
00412:          attachments[1].loadOp           = VK_ATTACHMENT_LOAD_OP_CLEAR;
00413:          attachments[1].storeOp          = VK_ATTACHMENT_STORE_OP_DONT_CARE;
00414:          attachments[1].stencilLoadOp    = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
00415:          attachments[1].stencilStoreOp   = VK_ATTACHMENT_STORE_OP_DONT_CARE;
00416:          attachments[1].initialLayout    = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
00417:          attachments[1].finalLayout      = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
00418:          attachments[1].flags            = 0;
00419:  
00420:          VkAttachmentReference colorRef = {};
00421:          colorRef.attachment = 0;
00422:          colorRef.layout     = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
00423:  
00424:          VkAttachmentReference depthRef = {};
00425:          depthRef.attachment = 1;
00426:          depthRef.layout     = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
00427:  
00428:          VkSubpassDescription subpass = {};
00429:          subpass.pipelineBindPoint       = VK_PIPELINE_BIND_POINT_GRAPHICS;
00430:          subpass.flags                   = 0;
00431:          subpass.inputAttachmentCount    = 0;
00432:          subpass.pInputAttachments       = nullptr;
00433:          subpass.colorAttachmentCount    = 1;
00434:          subpass.pColorAttachments       = &colorRef;
00435:          subpass.pResolveAttachments     = nullptr;
00436:          subpass.pDepthStencilAttachment = &depthRef;
00437:          subpass.preserveAttachmentCount = 0;
00438:          subpass.pPreserveAttachments    = nullptr;
00439:  
00440:          VkRenderPassCreateInfo info = {};
00441:          info.sType              = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
00442:          info.pNext              = nullptr;
00443:          info.flags              = 0;
00444:          info.attachmentCount    = 1;
00445:          info.pAttachments       = attachments;
00446:          info.subpassCount       = 1;
00447:          info.pSubpasses         = &subpass;
00448:          info.dependencyCount    = 0;
00449:          info.pDependencies      = nullptr;
00450:  
00451:          auto result = vkCreateRenderPass(m_DeviceMgr.GetDevice(), &info, nullptr, &m_RenderPass);
00452:          if (result != VK_SUCCESS)
00453:          {
00454:              ELOG( "Error : vkCreateRenderPass() Failed." );
00455:              return false;
00456:          }
00457:      }
00458:  
00459:      // フレームバッファの生成.
00460:      {
00461:          VkImageView attachments[2];
00462:  
00463:          VkFramebufferCreateInfo info = {};
00464:          info.sType              = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
00465:          info.pNext              = nullptr;
00466:          info.flags              = 0;
00467:          info.renderPass         = m_RenderPass;
00468:          info.attachmentCount    = 2;
00469:          info.pAttachments       = attachments;
00470:          info.width              = m_Width;
00471:          info.height             = m_Height;
00472:          info.layers             = 1;
00473:  
00474:          for(auto i=0u; i<ChainCount; ++i)
00475:          {
00476:              attachments[0] = m_SwapChain.GetBuffer(i)->View;
00477:              attachments[1] = m_DepthBuffer.GetView();
00478:  
00479:              auto result = vkCreateFramebuffer(m_DeviceMgr.GetDevice(), &info, nullptr, &m_FrameBuffer[i]);
00480:              if ( result != VK_SUCCESS )
00481:              {
00482:                  ELOG( "Error : vkCreateFramebuffer() Failed." );
00483:                  return false;
00484:              }
00485:          }
00486:      }
00487:  
00488:      // ビューポートとシザー矩形の設定.
00489:      {
00490:          m_Viewport.x        = 0.0f;
00491:          m_Viewport.y        = 0.0f;
00492:          m_Viewport.width    = static_cast<float>(m_Width);
00493:          m_Viewport.height   = static_cast<float>(m_Height);
00494:          m_Viewport.minDepth = 0.0f;
00495:          m_Viewport.maxDepth = 1.0f;
00496:  
00497:          m_Scissor.offset.x      = 0;
00498:          m_Scissor.offset.y      = 0;
00499:          m_Scissor.extent.width  = m_Width;
00500:          m_Scissor.extent.height = m_Height;
00501:      }
00502:  
00503:      // イメージレイアウトを設定しておく.
00504:      {
00505:          // コマンドの記録を終了.
00506:          m_CommandList.Close();
00507:  
00508:          // キューを取得.
00509:          auto pQueue = m_DeviceMgr.GetGraphicsQueue()->GetQueue();
00510:  
00511:          VkPipelineStageFlags pipeStageFlags = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
00512:  
00513:          // サブミット情報を設定する.
00514:          VkSubmitInfo info = {};
00515:          info.sType                  = VK_STRUCTURE_TYPE_SUBMIT_INFO;
00516:          info.pNext                  = nullptr;
00517:          info.waitSemaphoreCount     = 0;
00518:          info.pWaitSemaphores        = nullptr;
00519:          info.pWaitDstStageMask      = &pipeStageFlags;
00520:          info.commandBufferCount     = 1;
00521:          info.pCommandBuffers        = &cmd;
00522:          info.signalSemaphoreCount   = 0;
00523:          info.pSignalSemaphores      = nullptr;
00524:  
00525:          // コマンド実行.
00526:          auto result = vkQueueSubmit(pQueue, 1, &info, null_handle);
00527:          if ( result != VK_SUCCESS )
00528:          {
00529:              ELOG( "Error : vkQueueSubmit() Failed." );
00530:              return false;
00531:          }
00532:  
00533:          // 完了を待機.
00534:          result = vkQueueWaitIdle(pQueue);
00535:          if ( result != VK_SUCCESS )
00536:          {
00537:              ELOG( "Error : vkQueueWaitIdle() Failed." );
00538:              return false;
00539:          }
00540:  
00541:          // コマンドリストをリセット.
00542:          m_CommandList.Reset();
00543:      }
00544:  
00545:      // 正常終了.
00546:      return true;
00547:  }
00548:  
00549:  //-------------------------------------------------------------------------------------------------
00550:  //      Vulkanの終了処理です.
00551:  //-------------------------------------------------------------------------------------------------
00552:  void App::TermVulkan()
00553:  {
00554:      for(auto i=0u; i<ChainCount; ++i)
00555:      {
00556:          if(auto device = m_DeviceMgr.GetDevice())
00557:          {
00558:              vkDestroyFramebuffer(device, m_FrameBuffer[i], nullptr);
00559:              m_FrameBuffer[i] = null_handle;
00560:          }
00561:      }
00562:  
00563:      if (m_RenderPass != null_handle)
00564:      {
00565:          vkDestroyRenderPass(m_DeviceMgr.GetDevice(), m_RenderPass, nullptr);
00566:          m_RenderPass = null_handle;
00567:      }
00568:  
00569:      m_DepthBuffer.Term(&m_DeviceMgr);
00570:      m_SwapChain  .Term(&m_DeviceMgr);
00571:      m_CommandList.Term(&m_DeviceMgr);
00572:  
00573:      m_DeviceMgr.Term();
00574:  }
ラッパークラスを作ったので,ようやくD3D12ぐらいのコード量になりました。
初期化処理で前回説明していないものは,レンダーパスの生成処理になります。これについては次のセクションで説明します。


 3.レンダーパスの生成
さて,前回出てこなかったもので今回出てくるのがレンダーパスです。これはD3D12にも無くVulkan独自のものになります。
でこのレンダーパス何をやるかですが,レンダーターゲットのバインド設定やイメージレイアウトの設定をするもののようです。確かにVulkanのAPIを見るとD3D12のようなSetRenderTargets()的なものがありません。基本的にはレンダーターゲット関連の設定はこのレンダーパスでやるという感じのようです。では,さっそく生成処理のコードを見ていきましょう。実装は下記のようになります。
00397:      // レンダーパスの生成.
00398:      {
00399:          VkAttachmentDescription attachments[2];
00400:          attachments[0].format           = m_SwapChain.GetDesc().Format;
00401:          attachments[0].samples          = VK_SAMPLE_COUNT_1_BIT;
00402:          attachments[0].loadOp           = VK_ATTACHMENT_LOAD_OP_CLEAR;
00403:          attachments[0].storeOp          = VK_ATTACHMENT_STORE_OP_STORE;
00404:          attachments[0].stencilLoadOp    = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
00405:          attachments[0].stencilStoreOp   = VK_ATTACHMENT_STORE_OP_DONT_CARE;
00406:          attachments[0].initialLayout    = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
00407:          attachments[0].finalLayout      = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
00408:          attachments[0].flags            = 0;
00409:  
00410:          attachments[1].format           = m_DepthBuffer.GetDesc().Format;
00411:          attachments[1].samples          = m_DepthBuffer.GetDesc().Samples;
00412:          attachments[1].loadOp           = VK_ATTACHMENT_LOAD_OP_CLEAR;
00413:          attachments[1].storeOp          = VK_ATTACHMENT_STORE_OP_DONT_CARE;
00414:          attachments[1].stencilLoadOp    = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
00415:          attachments[1].stencilStoreOp   = VK_ATTACHMENT_STORE_OP_DONT_CARE;
00416:          attachments[1].initialLayout    = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
00417:          attachments[1].finalLayout      = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
00418:          attachments[1].flags            = 0;
00419:  
00420:          VkAttachmentReference colorRef = {};
00421:          colorRef.attachment = 0;
00422:          colorRef.layout     = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
00423:  
00424:          VkAttachmentReference depthRef = {};
00425:          depthRef.attachment = 1;
00426:          depthRef.layout     = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
00427:  
00428:          VkSubpassDescription subpass = {};
00429:          subpass.pipelineBindPoint       = VK_PIPELINE_BIND_POINT_GRAPHICS;
00430:          subpass.flags                   = 0;
00431:          subpass.inputAttachmentCount    = 0;
00432:          subpass.pInputAttachments       = nullptr;
00433:          subpass.colorAttachmentCount    = 1;
00434:          subpass.pColorAttachments       = &colorRef;
00435:          subpass.pResolveAttachments     = nullptr;
00436:          subpass.pDepthStencilAttachment = &depthRef;
00437:          subpass.preserveAttachmentCount = 0;
00438:          subpass.pPreserveAttachments    = nullptr;
00439:  
00440:          VkRenderPassCreateInfo info = {};
00441:          info.sType              = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
00442:          info.pNext              = nullptr;
00443:          info.flags              = 0;
00444:          info.attachmentCount    = 2;
00445:          info.pAttachments       = attachments;
00446:          info.subpassCount       = 1;
00447:          info.pSubpasses         = &subpass;
00448:          info.dependencyCount    = 0;
00449:          info.pDependencies      = nullptr;
00450:  
00451:          auto result = vkCreateRenderPass(m_DeviceMgr.GetDevice(), &info, nullptr, &m_RenderPass);
00452:          if (result != VK_SUCCESS)
00453:          {
00454:              ELOG( "Error : vkCreateRenderPass() Failed." );
00455:              return false;
00456:          }
00457:      }
まずは,アタッチメントするカラーバッファや深度バッファをVkAttachmentDescriptorで設定していきます。
flags には 有効なVkAttachmentDescriptorFlagBitsの組み合わせを設定します。今回は特に指定したいものも無いので0を設定します。
format は VkFormatを設定します。これは各イメージを生成したときに使ったVkFormatを指定しましょう。ラッパークラスの方で変数としてフォーマットを持っているので,取ってきた値をそのまま設定します。
samples は 有効なVkSampleCountFlagBitsの値を指定しますが,今回はマルチサンプルは使っていないのでVK_SAMPLE_COUNT_1_BITを指定します。
loadOp は カラーや深度のアタッチメントするものがサブパスの開始時にどのように扱われるかを指定します。今回は開始時にクリア処理を行ってほしいので,VK_ATTACHMENT_LOAD_OP_CLEARを指定します。
storeOp は カラーや深度アタッチメントするものがサブパスの終了時にどのように扱われるかを指定します。今回はカラーに関してはレンダーパス終了後も画面への表示に使用するので,VK_ATTACHMENT_STORE_OP_STOREを指定します。深度についてはレンダーパス終了後に読み込みなどは行わないので,深度にはVK_ATTACHMENT_STORE_OP_DONT_CAREを指定します。
stencilLoadOp, stencilStoreOp は同じくサブパスの開始時と終了時にステンシルコンポーネントをどのように扱うかを指定するものですが,今回のサンプルではステンシルは使わないので,VK_ATTACHMENT_LOAD_OP_DONT_CARE、VK_ATTACHMENT_STORE_OP_DONT_CAREをそれぞれ設定しました。
initialLayout, finalLayout はレンダーパス開始時と終了時のアタッチメントのレイアウトを指定します。後々のことを考えて,initialLayoutとfinalLayoutはカラーはVK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMALを,深度はVK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMALを設定しました。

次は,VkSubpassDescriptionに設定するためのVkAttachmentReferenceを設定します。
attachment はレンダーパスのアタッチメントのインデックスを設定します。今回は配列の0番にカラー,1番に深度を入れているので,カラーはattachment = 0, 深度は attachment = 1となります。
layout は サブパス中に使用するアタッチメントのレイアウトを指定します。今回はVK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMALとVK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMALをそれぞれ設定しました。

続いてVkSubpassDescriptionを設定します。
flags は将来のための予約領域です。0を設定することが必須なので0を指定します。
pipelineBindPoint はコンピュートあるいはグラフィックスサブパスのどちらかを指定します。現在はグラフィックスサブパスのみしかサポートされていないため,VK_PIPELINE_BIND_POINT_GRAPHICSを指定しましょう。
inputAttachmentCount は入力アタッチメントの数を指定します。今回はここは使わないので0を指定します。
pInputAttachments はレンダーパスのアタッチメントがサブパス中のシェーダ上で読み取り可能なVkAttachmentReference構造体の配列を指定します。今回は使わないのでnullptrを設定しました。
colorAttachmentCount はカラーアタッチメントの数を指定します。今回は1つ設定します。
pColorAttachments は カラーアタッチメントとして使用する VkAttachmentReference構造体の配列を指定します。今回は先ほど設定したcolorRefを指定します。
pResolveAttachment は今回は使わないのでnullptrを指定します。
pDepthAttachment は深度アタッチメントを指定します。先ほど設定したdepthRefを指定しましょう。
preserveAttachmentCount, pPreserveAttachments は今回使わないので,0とnullptrをそれぞれ設定します。

ここまででサブパスの設定が終わりました,最後にVkRenderPassCreateInfoに設定してきた値を指定して,レンダーパスの生成を行います。
sType は VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO を設定することが必須であるため,これを設定します。
pNext は 拡張仕様構造体へのポインタを指定できますが,現在はnullptrを設定することが必須であるため,nullptrを設定します。
flags は 将来の利用のための予約領域です。現在は0を指定することが必須であるため0を指定します。
attachmentCount はこのレンダーパスによって使用されるアタッチメントの数です。先ほど用意したカラーと深度の2つを設定するので2を指定します。
pAttachments はVkAttachmentDescriptor構造体の配列を設定します。これはattachmentCountと対応する配列を指定します。今回はattachmentsがそれに当たるので,これを指定します。
subpassCount はこのレンダーパスに対して生成するサブパスの数です。今回は1つ作りたいので1を指定します。
pSubpasses はサブパスのプロパティを記述するVkSubpassDescriptor構造体の配列を指定します。今回は先ほど設定した,subpassを指定します。
dependencyCount はサブパスのペア間の依存の数を指定します。今回依存は無いので0を指定します。
pDependencies はサブパスのペア間の依存を記述するVkSubpassDependency構造体の配列を指定します。今回依存はないのでnullptrを指定します。

これで,VkRenderPassCreateInfoの設定が終わったので,vkCreateRenderPass()を呼び出してレンダーパスを生成します。

続いて,フレームバッファの設定処理に入ります。前回は特にポリゴン描画しなかったのでrenderPassをNULLに設定して生成していましたが,ここをNULLのままvkCmdBeginRenderPass()などを呼び出すと手元の環境でドライバーがクラッシュするということが分かったので,きちんとレンダーパスを設定して生成してやります。
00459:      // フレームバッファの生成.
00460:      {
00461:          VkImageView attachments[2];
00462:  
00463:          VkFramebufferCreateInfo info = {};
00464:          info.sType              = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
00465:          info.pNext              = nullptr;
00466:          info.flags              = 0;
00467:          info.renderPass         = m_RenderPass;
00468:          info.attachmentCount    = 2;
00469:          info.pAttachments       = attachments;
00470:          info.width              = m_Width;
00471:          info.height             = m_Height;
00472:          info.layers             = 1;
00473:  
00474:          for(auto i=0u; i<ChainCount; ++i)
00475:          {
00476:              attachments[0] = m_SwapChain.GetBuffer(i)->View;
00477:              attachments[1] = m_DepthBuffer.GetView();
00478:  
00479:              auto result = vkCreateFramebuffer(m_DeviceMgr.GetDevice(), &info, nullptr, &m_FrameBuffer[i]);
00480:              if ( result != VK_SUCCESS )
00481:              {
00482:                  ELOG( "Error : vkCreateFramebuffer() Failed." );
00483:                  return false;
00484:              }
00485:          }
00486:      }
今回のプログラムでは,先ほど生成したm_RenderPassを指定しました。あとはvkCreateFrameBuffer()でフレームバッファを生成します。パラメータについては前回説明しているので,前回の記事を参照してください。


 4.ポリゴンの準備
次は,ポリゴン描画のための頂点バッファ生成処理などを行います。今回はインデックスバッファを使わず直接描画します。
まずデータの入れ物を用意します。下記のような感じです。
00014:  ///////////////////////////////////////////////////////////////////////////////////////////////////
00015:  // Mesh
00016:  ///////////////////////////////////////////////////////////////////////////////////////////////////
00017:  struct Mesh
00018:  {
00019:      VkBuffer                            Buffer;             //!< 頂点バッファ.
00020:      VkDeviceMemory                      Memory;             //!< デバイスメモリ.
00021:      VkVertexInputBindingDescription     Bindings;           //!< 入力バインディング設定.
00022:      VkVertexInputAttributeDescription   Attributes[3];      //!< 入力属性設定.
00023:  
00024:      //---------------------------------------------------------------------------------------------
00025:      //! @brief      コンストラクタです.
00026:      //---------------------------------------------------------------------------------------------
00027:      Mesh()
00028:      : Buffer(null_handle)
00029:      , Memory(null_handle)
00030:      { /* DO_NOTHING /* }
00031:  };
これらの初期化処理を下記のように行います。処理の流れとしては,VkBufferを生成,生成したVkBufferにメモリを割り当てし,頂点データをvkMapMemory()して書き込み。書き込み後にvkUnmapMemory()してマップを解除。最後にvkBindBufferMemory()でバッファにメモリを関連付けるという感じです。
00065:      // メッシュの初期化.
00066:      {
00067:          // 頂点データ.
00068:          Vertex vertices[3] = {
00069:              { asvk::Vector3(-0.5f, -0.75f, 0.0f), asvk::Vector2(0.0f, 0.0f), asvk::Vector4(1.0f, 0.0f, 0.0f, 1.0f) },
00070:              { asvk::Vector3( 0.0f,  0.75f, 0.0f), asvk::Vector2(0.5f, 1.0f), asvk::Vector4(0.0f, 1.0f, 0.0f, 1.0f) },
00071:              { asvk::Vector3( 0.5f, -0.75f, 0.0f), asvk::Vector2(1.0f, 0.0f), asvk::Vector4(0.0f, 0.0f, 1.0f, 1.0f) }
00072:          };
00073:  
00074:          // バッファ生成情報の設定.
00075:          VkBufferCreateInfo info = {};
00076:          info.sType                  = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
00077:          info.pNext                  = nullptr;
00078:          info.usage                  = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
00079:          info.size                   = sizeof(Vertex) * 3;
00080:          info.queueFamilyIndexCount  = 0;
00081:          info.pQueueFamilyIndices    = nullptr;
00082:          info.sharingMode            = VK_SHARING_MODE_EXCLUSIVE;
00083:          info.flags                  = 0;
00084:  
00085:          // 頂点バッファ生成.
00086:          auto result = vkCreateBuffer(device, &info, nullptr, &m_Mesh.Buffer);
00087:          if (result != VK_SUCCESS)
00088:          {
00089:              ELOG( "Error : vkCreateBuffer() Failed." );
00090:              return false;
00091:          }
00092:  
00093:          // メモリ要件を取得.
00094:          VkMemoryRequirements requirements;
00095:          vkGetBufferMemoryRequirements(device, m_Mesh.Buffer, &requirements);
00096:  
00097:          // メモリ割り当て情報を設定.
00098:          VkMemoryAllocateInfo allocInfo = {};
00099:          allocInfo.sType             = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
00100:          allocInfo.pNext             = nullptr;
00101:          allocInfo.memoryTypeIndex   = 0;
00102:          allocInfo.allocationSize    = requirements.size;
00103:  
00104:          // メモリを確保.
00105:          result = vkAllocateMemory(device, &allocInfo, nullptr, &m_Mesh.Memory);
00106:          if (result != VK_SUCCESS)
00107:          {
00108:              ELOG( "Error : vkAllocateMemory() Failed." );
00109:              return false;
00110:          }
00111:  
00112:          // マップする.
00113:          uint8_t* pData = nullptr;
00114:          result = vkMapMemory(device, m_Mesh.Memory, 0, requirements.size, 0, reinterpret_cast<void**>(&pData));
00115:          if (result != VK_SUCCESS)
00116:          {
00117:              ELOG( "Error : vkMapMemory() Failed." );
00118:              return false;
00119:          }
00120:  
00121:          // 頂点データを設定.
00122:          memcpy(pData, vertices, sizeof(vertices));
00123:  
00124:          // アンマップする.
00125:          vkUnmapMemory(device, m_Mesh.Memory);
00126:  
00127:          // メモリをバッファに関連付ける.
00128:          result = vkBindBufferMemory(device, m_Mesh.Buffer, m_Mesh.Memory, 0);
00129:          if (result != VK_SUCCESS)
00130:          {
00131:              ELOG( "Error : vkBindBufferMemory() Failed." );
00132:              return false;
00133:          }
00134:  
00135:          // バインディング情報の設定.
00136:          m_Mesh.Bindings.binding     = 0;
00137:          m_Mesh.Bindings.inputRate   = VK_VERTEX_INPUT_RATE_VERTEX;
00138:          m_Mesh.Bindings.stride      = sizeof(Vertex);
00139:  
00140:          uint32_t offset = 0;
00141:  
00142:          // 頂点属性の設定.
00143:          m_Mesh.Attributes[0].binding    = 0;
00144:          m_Mesh.Attributes[0].location   = 0;
00145:          m_Mesh.Attributes[0].format     = VK_FORMAT_R32G32B32_SFLOAT;
00146:          m_Mesh.Attributes[0].offset     = offset;
00147:          offset += sizeof(asvk::Vector3);
00148:  
00149:          m_Mesh.Attributes[1].binding    = 0;
00150:          m_Mesh.Attributes[1].location   = 1;
00151:          m_Mesh.Attributes[1].format     = VK_FORMAT_R32G32_SFLOAT;
00152:          m_Mesh.Attributes[1].offset     = offset;
00153:          offset += sizeof(asvk::Vector2);
00154:  
00155:          m_Mesh.Attributes[2].binding    = 0;
00156:          m_Mesh.Attributes[2].location   = 2;
00157:          m_Mesh.Attributes[2].format     = VK_FORMAT_R32G32B32A32_SFLOAT;
00158:          m_Mesh.Attributes[2].offset     = offset;
00159:          offset += sizeof(asvk::Vector4);
00160:      }
135行目~159行目のバインディング情報と頂点属性の値は後述するパイプラインの生成処理で必要となるものです。
143行目からの設定処理はOpenGLで言うところの頂点アトリビュート,Direct3Dで言うところの入力レイアウトの設定にあたるものになっています。

一応上記の処理で設定する構造体についても説明しておきましょう。
まずは,VkBufferCreateInfo構造体ですが,バッファ生成情報を設定するものです。
sType はVK_STRUCTURE_TYPE_BUFFER_CREATE_INFOを設定することが必須なので,これを設定します。
pNext は拡張仕様構造体へのポインタを設定できるようになりますが,現在はnullptrを設定することが必須であるため,nullptrを指定します。
flags は有効なVkBufferCreateFlagBitsの値の組み合わせを指定します。VkBufferCreateFlagBits列挙体で宣言されているものは以下のようになります。
  • VK_BUFFER_CREATE_SPARSE_BINDING_BIT
  • VK_BUFFER_CREATE_SPARSE_RESIDENCY_BIT
  • VK_BUFFER_CREATE_SPARSE_ALIASED_BIT
各値の説明は仕様書に記載されているので,そちらを参照してください。今回はスパースメモリを使わないので0を指定しています。
size は生成するためのバッファのサイズをバイト単位で指定します。今回は三角形を書きたいので頂点3つ分を指定しました。
usage はバッファの利用用途をVkBufferUsageFlagBitsの組み合わせで指定します。VkBufferUsageFlagBits列挙体で宣言されているものは以下の通りです。
  • VK_BUFFER_USAGE_TRANSFER_SRC_BIT
  • VK_BUFFER_USAGE_TRANSFER_DST_BIT
  • VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT
  • VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT
  • VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT
  • VK_BUFFER_USAGE_STORAGE_BUFFER_BIT
  • VK_BUFFER_USAGE_INDEX_BUFFER_BIT
  • VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
  • VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT
上記の内,頂点バッファに使用したい場合はVK_BUFFER_USAGE_VERTEX_BUFFER_BITを指定すればよいので,コードではこれを指定しています。あとで,インデックスバッファが追加で必要になる場合はVKBUFFER_USAGE_INDEX_BUFFER_BITを指定してバッファを作ってやればインデックスバッファが生成できます。バッファの生成情報の設定ができたら,vkCreateBuffer()でバッファを生成します。
次のVkMemoryRequiremntsとVkMemoryAllocateInfoについてですが,これも前回説明済みのため,前回の記事を参照してください。
vkMapMemory()とvkUnmapMemory()は前回出ていなかったと思うので,引数だけ確認しておきます。
VkResult vkMapMemory(
    VkDevice         device,
    VkDeviceMemory   memory,
    VkDeviceSize,    offset,
    VkDeviceSize     size,
    VkMemoryMapFlags flags,
    void**           ppData);
第1引数は,デバイスです。memoryはマップするためのオブジェクトのVkDeviceMemoryを設定します。offsetはメモリオブジェクトの開始からのオフセットをバイト単位で指定します。sizeはマップするためのメモリレンジのサイズを設定します。flagsは将来の利用のための予約領域で,現在は0を指定することが必須です。ppDataはマップされた領域の先頭ポインタの格納先です。
void vkUnmapMemory(
    VkDevice       device,
    VkDeviceMemory memory);
Unmapの方はそれぞれデバイスと,アンマップするためのメモリオブジェクトを指定するので,vkMapMemoryの第1引数と第2引数に対応するものを入れておけば良いですね。


 5.シェーダの準備
さて,次は描画のためのシェーダを用意します。Vulkanは基本的にはシェーダをGLSL形式で用意して,glslangValidator.exeによってSPIR-Vの中間バイナリ形式に変換します。今回は頂点シェーダとピクセルシェーダを用意し,ユニフォームバッファを使用しない一番単純なシェーダを用意しました。まずは頂点シェーダからです。
00006:  #version 400
00007:  #extension GL_ARB_separate_shader_objects : enable
00008:  #extension GL_ARB_shading_language_420pack : enable
00009:  
00010:  //-------------------------------------------------------------------------------------------------
00011:  // Input Definitions.
00012:  //-------------------------------------------------------------------------------------------------
00013:  layout(location = 0) in vec3 InputPosition;     // 位置座標.
00014:  layout(location = 1) in vec2 InputTexCoord;     // テクスチャ座標.
00015:  layout(location = 2) in vec4 InputColor;        // 頂点カラー.
00016:  
00017:  //-------------------------------------------------------------------------------------------------
00018:  // Output Defintions.
00019:  //-------------------------------------------------------------------------------------------------
00020:  layout(location = 0) out vec2 OutputTexCoord;   // テクスチャ座標.
00021:  layout(location = 1) out vec4 OutputColor;      // 頂点カラー.
00022:  out gl_PerVertex 
00023:  {
00024:      vec4 gl_Position;   // 位置座標.
00025:  };
00026:  
00027:  //-------------------------------------------------------------------------------------------------
00028:  //      頂点シェーダメインエントリーポイントです.
00029:  //-------------------------------------------------------------------------------------------------
00030:  void main()
00031:  {
00032:      vec4 localPos = vec4(InputPosition, 1.0f);
00033:  
00034:      gl_Position    = localPos;
00035:      OutputTexCoord = InputTexCoord;
00036:      OutputColor    = InputColor;
00037:  }
GLSLはバージョンによって書き方が異なったりするので,コンパイラにバージョンを伝えるため#versionを使いましょう。今回はOpenGL 4.0に対応しているものとして#version 400を書いておきました。あとは#extensionでOpenGLの拡張を伝えることができるようなので,Vulkan SDKのサンプルにあるGLSLのコードと同じものを指定しておきました。
CPU側からの入力との対応づけはlayout(location = 番号) in で行っています。先ほど設定した頂点属性をもう一度見ましょう。
00142:          // 頂点属性の設定.
00143:          m_Mesh.Attributes[0].binding    = 0;
00144:          m_Mesh.Attributes[0].location   = 0;
00145:          m_Mesh.Attributes[0].format     = VK_FORMAT_R32G32B32_SFLOAT;
00146:          m_Mesh.Attributes[0].offset     = offset;
00147:          offset += sizeof(asvk::Vector3);
00148:  
00149:          m_Mesh.Attributes[1].binding    = 0;
00150:          m_Mesh.Attributes[1].location   = 1;
00151:          m_Mesh.Attributes[1].format     = VK_FORMAT_R32G32_SFLOAT;
00152:          m_Mesh.Attributes[1].offset     = offset;
00153:          offset += sizeof(asvk::Vector2);
00154:  
00155:          m_Mesh.Attributes[2].binding    = 0;
00156:          m_Mesh.Attributes[2].location   = 2;
00157:          m_Mesh.Attributes[2].format     = VK_FORMAT_R32G32B32A32_SFLOAT;
00158:          m_Mesh.Attributes[2].offset     = offset;
00159:          offset += sizeof(asvk::Vector4);
…となっています。GLSL側のlocation番号に対応するのは,144行目,150行目,156行目の値になります。formatで指定している値もGLSL側と合うようにvec3→VK_FORMAT_R32G32B32_SFLOAT,vec2→VK_FORMAT_R32G32_SFLOT,vec4→VK_FORMAT_R32G32B32A32_SFLOTとなってデータがあっていることを確認しましょう。今回の頂点シェーダの処理は入力された値をそのまま出力に流して終了しています。
続いて,フラグメントシェーダ側の処理です。フラグメントシェーダでは頂点シェーダから出力されたカラーをそのまま出して終わりという単純な処理になっています。
00006:  #version 400
00007:  #extension GL_ARB_separate_shader_objects : enable
00008:  #extension GL_ARB_shading_language_420pack : enable
00009:  
00010:  //-------------------------------------------------------------------------------------------------
00011:  // Input Definitions.
00012:  //-------------------------------------------------------------------------------------------------
00013:  layout(location = 0) in vec2 InputTexCoord;
00014:  layout(location = 1) in vec4 InputColor;
00015:  
00016:  
00017:  //-------------------------------------------------------------------------------------------------
00018:  // Output Definitions.
00019:  //-------------------------------------------------------------------------------------------------
00020:  layout(location = 0) out vec4 OutputColor;
00021:  
00022:  
00023:  //-------------------------------------------------------------------------------------------------
00024:  //      フラグメントシェーダメインエントリーポイントです.
00025:  //-------------------------------------------------------------------------------------------------
00026:  void main()
00027:  {
00028:      OutputColor = InputColor;
00029:  }
シェーダ自体は大したこと全くやっていないので問題ないでしょう。次はこれらのコードを中間バイナリに変換します。
中間バイナリへの変換はVulkan SDKに付属するglslangValidator.exeを使って行います。
実行ファイルは,%VK_SDK_PATH%\Bin または %VK_SDK_PATH%\Bin32 にあります。コマンドプロンプトからコマンド引数を設定せずに実行すると下記のようにヘルプが表示されます。

上記をみると分かるように,頂点シェーダは拡張子 *.vert,フラグメントシェーダは拡張 *.fragをつければ良さそうなことが分かります。今回のサンプルでは以下のバッチ処理でシェーダコンパイルをするようにしました。
set SHADER_COMPILER=%VK_SDK_PATH%\Bin\glslangValidator.exe
%SHADER_COMPILER% -V -l -o SimpleVS.spv SimpleVS.vert
%SHADER_COMPILER% -V -l -o SimpleFS.spv SimpleFS.frag
Vulkan向けなので"-V"を指定します。あとは出力ファイル名を変えたかったので"-o ファイル名"で出力ファイル名を指定しました。Vulkan SDKでは出力ファイルに *.spv をつけていたので同じようにしました。こんな感じでglslangValidator.exeを叩いてやれば中間バイナリが得られます。あとはこの出来上がった中間バイナリをプログラムで読み込み,VkShaderModuleを作成します。作成処理は下記のような感じです。
00197:      VkShaderModule vs = VK_NULL_HANDLE;
00198:      VkShaderModule fs = VK_NULL_HANDLE;
00199:  
00200:      // 頂点シェーダの生成.
00201:      {
00202:          asvk::RefPtr<asvk::IBlob> blob;
00203:          std::wstring path;
00204:  
00205:          // ファイルパスを検索.
00206:          if (!asvk::SearchFilePath(L"/res/SimpleVS.spv", path))
00207:          {
00208:              ELOG( "Error : File Not Found." );
00209:              return false;
00210:          }
00211:  
00212:          // コンパイル済みバイナリを読み込み.
00213:          if (!asvk::ReadFileToBlob(path.c_str(), blob.GetAddress()))
00214:          {
00215:              ELOG( "Error : Vertex Shader Load Failed." );
00216:              return false;
00217:          }
00218:  
00219:          // シェーダモジュールの生成設定.
00220:          VkShaderModuleCreateInfo info = {};
00221:          info.sType      = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
00222:          info.pNext      = nullptr;
00223:          info.flags      = 0;
00224:          info.codeSize   = blob->GetBufferSize();
00225:          info.pCode      = reinterpret_cast<uint32_t*>(blob->GetBufferPointer());
00226:  
00227:          // シェーダモジュールを生成.
00228:          auto result = vkCreateShaderModule(device, &info, nullptr, &vs);
00229:          if (result != VK_SUCCESS)
00230:          {
00231:              ELOG( "Error : vkCreateShaderModule() Failed." );
00232:              return false;
00233:          }
00234:      }
00235:  
00236:      // ピクセルシェーダの生成.
00237:      {
00238:          asvk::RefPtr<asvk::IBlob> blob;
00239:          std::wstring path;
00240:  
00241:          // ファイルパスを検索.
00242:          if (!asvk::SearchFilePath(L"res/SimpleFS.spv", path))
00243:          {
00244:              ELOG( "Error : File Not Found." );
00245:              vkDestroyShaderModule(device, vs, nullptr);
00246:              return false;
00247:          }
00248:       
00249:          // コンパイル済みバイナリを読み込み.
00250:          if (!asvk::ReadFileToBlob(path.c_str(), blob.GetAddress()))
00251:          {
00252:              ELOG( "Error : Fragment Shader Load Failed." );
00253:              vkDestroyShaderModule(device, vs, nullptr);
00254:              return false;
00255:          }
00256:  
00257:          // シェーダモジュールの生成設定.
00258:          VkShaderModuleCreateInfo info = {};
00259:          info.sType      = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
00260:          info.pNext      = nullptr;
00261:          info.flags      = 0;
00262:          info.codeSize   = blob->GetBufferSize();
00263:          info.pCode      = reinterpret_cast<uint32_t*>(blob->GetBufferPointer());
00264:  
00265:          // シェーダモジュールを生成.
00266:          auto result = vkCreateShaderModule(device, &info, nullptr, &fs);
00267:          if (result != VK_SUCCESS)
00268:          {
00269:              ELOG( "Error : vkCreateShaderModule() Failed." );
00270:              vkDestroyShaderModule(device, vs, nullptr);
00271:              return false;
00272:          }
00273:      }
生成したVkShaderModuleは次のセクションで説明するパイプラインの生成に使用します。


 6.パイプラインの生成
次は,描画に使用するグラフィックスパイプラインの生成について説明します。
パイプラインの生成は
  • パイプラインレイアウトの生成
  • パイプラインキャッシュの生成
  • パイプラインの生成
…という順番で行います。まずパイプラインレイアウトから。
00162:      // パイプラインレイアウトの生成.
00163:      {
00164:          VkPipelineLayoutCreateInfo info = {};
00165:          info.sType                  = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
00166:          info.pNext                  = nullptr;
00167:          info.pushConstantRangeCount = 0;
00168:          info.pPushConstantRanges    = nullptr;
00169:          info.setLayoutCount         = 0;
00170:          info.pSetLayouts            = nullptr;
00171:  
00172:          auto result = vkCreatePipelineLayout(device, &info, nullptr, &m_PipelineLayout);
00173:          if (result != VK_SUCCESS)
00174:          {
00175:              ELOG( "Error : vkCreatePipelineLayout() Failed." );
00176:              return false;
00177:          }
00178:      }
パイプラインレイアウトの生成は,VkPipelineLayoutCreateInfo構造体に値を設定して,vkCreatePipelineLayout()によって行います。
sType は VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFOを設定することが必須であるため,これを指定します。
pNext は 拡張仕様構造体へのポインタを設定できるようになりますが,現状ではnullptrであることが必須のため,設定します。
flags は 将来の利用のための予約領域で,現在は0を設定することが必須です。
setLayoutCount はパイプラインレイアウト上に含めるディスクリプタセットの数を指定します。今回のサンプルではディスクリプタセットを使わないので0を指定します。
pSetLayouts はVkDescriptorSetLayotuオブジェクトの配列へのポインタを指定します。今回は使わないのでnullptrを設定しました。
pushConstantRangeCount は パイプラインレイアウト上に含めるプッシュコンスタントレンジの数を指定します。今回は使わないので0を設定します。
pPushConstatnRanges は VkPushConstantRange構造体の配列へのポインタを指定します。今回は使わないのでnullptrを設定します。

パイプラインキャッシュの生成は,VkPipelineCacheCreateInfo構造体に値を設定して行います。
00180:      // パイプラインキャッシュの生成.
00181:      {
00182:          VkPipelineCacheCreateInfo info = {};
00183:          info.sType              = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
00184:          info.pNext              = nullptr;
00185:          info.flags              = 0;
00186:          info.initialDataSize    = 0;
00187:          info.pInitialData       = nullptr;
00188:  
00189:          auto result = vkCreatePipelineCache(device, &info, nullptr, &m_PipelineCache);
00190:          if (result != VK_SUCCESS)
00191:          {
00192:              ELOG( "Error : vkCreatePipelineCache() Failed." );
00193:              return false;
00194:          }
00195:      }
sType は VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFOを設定することが必須であるため,これを設定します。
pNext は nullptrであることが必須であるため,nullptrを入れます。
flags も 0であることが必須であるため0を入れます。
initialDataSIze は 初期化データのサイズをバイト単位で指定します。今回は使わないので0を入れます。
pInitialData は 前に読み出したパイプラインキャッシュデータへのポインタを指定します。今回はキャッシュデータを使わないのでnullptrを指定します。
設定が終わったら,vkCreatePipelineCache()でパイプラインキャッシュを生成します。

最後にパイプラインを生成します。まずはVkPipelineCreateInfo構造体に設定するためのデータを用意していきます。
まずはVkPipelineShaderStateCreateInfo構造体から。
00277:          // シェーダステージの設定.
00278:          VkPipelineShaderStageCreateInfo stageInfo[2];
00279:          stageInfo[0].sType               = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
00280:          stageInfo[0].pNext               = nullptr;
00281:          stageInfo[0].flags               = 0;
00282:          stageInfo[0].stage               = VK_SHADER_STAGE_VERTEX_BIT;
00283:          stageInfo[0].module              = vs;
00284:          stageInfo[0].pName               = "main";
00285:          stageInfo[0].pSpecializationInfo = nullptr;
00286:  
00287:          stageInfo[1].sType               = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
00288:          stageInfo[1].pNext               = nullptr;
00289:          stageInfo[1].flags               = 0;
00290:          stageInfo[1].stage               = VK_SHADER_STAGE_FRAGMENT_BIT;
00291:          stageInfo[1].module              = fs;
00292:          stageInfo[1].pName               = "main";
00293:          stageInfo[1].pSpecializationInfo = nullptr;
今回は,頂点シェーダとフラグメントシェーダを使うので,VkPipelineShaderStageCreateInfoを2つ用意します。
sType には VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFOを設定することが必須であるため,これを指定します。
pNext は nullptrを指定するのが必須であるため指定します。
flags は 将来の利用のための予約領域で,0を設定することが必須であるため0を入れます。
stage は VkShaderStageFlagBitsを設定することが必須です。VkShaderStageFlagBits列挙体は以下のように定義されています。
  • VK_SHADER_STAGE_VERTEX_BIT
  • VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT
  • VK_SHADER_STAGE_TESSELLATION_EVALUTATION_BIT
  • VK_SHADER_STAGE_GEOMETRY_BIT
  • VK_SHADER_STAGE_FRAGMENT_BIT
  • VK_SHADER_STAGE_COMPUTE_BIT
  • VK_SHADER_STAGE_ALL_GRAPHICS
  • VK_SHADER_STAGE_ALL
それぞれ上から,頂点シェーダ,テッセレーション制御シェーダ,テッセレーション評価シェーダ,ジオメトリシェーダ,フラグメントシェーダ,コンピュートシェーダに対応しています。この構造体では,VK_SHADER_STAGE_ALL_GRAPHICSとVK_SHADER_STAGE_ALLの指定は禁止と仕様書に書いてあるので,設定できません。今回は頂点シェーダとフラグメントシェーダを使うので,VK_SHADER_STAGE_VERTEX_BITとVK_SHADER_STAGE_FRAGMENT_BITを指定します。
module はこのステージに対するVkShaderModuleオブジェクトを設定します。今回は先ほど用意した,vsとfsをそれぞれ設定しています。
pName は このステージに対するシェーダのエントリーポイント名をNULL終端のUTF-8文字列で指定します。今回はそれぞれmainと名前を付けたので,"main"を指定します。
pSpecializationInfo は VkSpecializationInfoへのポインタを設定できます。SPIR-VにあるconstantIDやoffset,sizeなどを取るための構造体みたいですが,今回は使いませんのでnullptrを指定します。

続いて,VkPipelineVertexInputStateCreateInfo構造体です。
00295:          // 頂点入力の設定.
00296:          VkPipelineVertexInputStateCreateInfo vertexInputInfo = {};
00297:          vertexInputInfo.sType                           = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
00298:          vertexInputInfo.pNext                           = nullptr;
00299:          vertexInputInfo.flags                           = 0;
00300:          vertexInputInfo.vertexBindingDescriptionCount   = 1;
00301:          vertexInputInfo.pVertexBindingDescriptions      = &m_Mesh.Bindings;
00302:          vertexInputInfo.vertexAttributeDescriptionCount = 3;
00303:          vertexInputInfo.pVertexAttributeDescriptions    = m_Mesh.Attributes;
sType にはこの構造体のタイプを設定します。VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFOの設定が必須です。
pNext は拡張仕様構造体へのポインタですが,現在はnullptrを設定することが必須なので,これを指定します。
flags は将来の利用のための予約領域で,現在は0を指定することが必須なので0を入れます。
vertexBindingDescriptorCountはpVertexBindingDescriptionで設定する頂点バインディングディスクリプションの数を設定します。今回は1つ設定するので1を入れます。
pVertexBindingDescriptorsはVkVertexInputBindingDescriptor構造体の配列へのポインタを指定します。今回は先ほど用意したm_Mesh.Bindingsを設定します。
vertexAttributeDescriptionCount は頂点属性の数を指定します。今回は位置座標・テクスチャ・頂点カラーの3つなので3を入れます。
pVertexAttributeDescriptons はVkVertexInputAttributeDescription構造体の配列へのポインタを指定します。今回はm_Mesh.Attributesになります。

次は,VkPipelineInputAssemblyStateCreateInfoの設定です。
00305:          // 入力アセンブリの設定.
00306:          VkPipelineInputAssemblyStateCreateInfo inputAssemblyInfo = {};
00307:          inputAssemblyInfo.sType                     = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
00308:          inputAssemblyInfo.pNext                     = nullptr;
00309:          inputAssemblyInfo.flags                     = 0;
00310:          inputAssemblyInfo.topology                  = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
00311:          inputAssemblyInfo.primitiveRestartEnable    = VK_FALSE;
sType はこの構造体のタイプである,VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFOを設定します。
pNext は拡張仕様構造体へのポインタですが,現在はnullptrの設定が必須なのでnullptrを指定します。
flags は将来の利用のための予約領域で,現在は0を設定するのが必須です。
topology はVkPrimitiveTopologyの値を指定します。VkPrimitiveTopology列挙体は以下のように定義されています。
  • VK_PRIMITIVE_TOPOLOGY_POINT_LIST
  • VK_PRIMITIVE_TOPOLOGY_LINE_LIST
  • VK_PRIMITIVE_TOPOLOGY_LINE_STRIP
  • VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST
  • VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP
  • VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN
  • VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY
  • VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY
  • VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY
  • VK_PRIMITIVE_TOPOLOGY_TRINAGLE_STRIP_WITH_ADJACENCY
  • VK_PRIMITIVE_TOPOLOGY_PATCH_LIST
OpenGLでDirectXでも見たことあるやつだと思うので,特に説明はいらないでしょう。今回は三角形1枚を書くので,VK_PRIMITIVE_TRIANGLE_LISTを指定しました。あと,上記列挙体の隣接系やストリップ系のもののつながり方については仕様書の方に図がついていて分かりやすいので,仕様書を参照してください。

次は,ビューポートステート生成情報についてです。下記の様に設定します。
00313:          // ビューポートステートの設定.
00314:          VkPipelineViewportStateCreateInfo viewportInfo = {};
00315:          viewportInfo.sType          = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
00316:          viewportInfo.pNext          = nullptr;
00317:          viewportInfo.flags          = 0;
00318:          viewportInfo.viewportCount  = 1;
00319:          viewportInfo.pViewports     = &m_Viewport;
00320:          viewportInfo.scissorCount   = 1;
00321:          viewportInfo.pScissors      = &m_Scissor;
設定はVkPipelineViewportStateCreateInfo構造体を使って行います。ビューポートの数と配列,シザー矩形の数と配列をそれぞれ渡してやれば良いです。sType は VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CRATE_INFOで,pNextはnullptr,flagsは0を設定しておきます。

次はラスタライゼイション―ステート生成情報の設定です。カリングやポリゴンオフセットなどの設定はVkPipelineRasterizationStateCreateInfo構造体を通じて設定します。
00323:          // ラスタライザ―ステートの設定.
00324:          VkPipelineRasterizationStateCreateInfo rasterizationInfo = {};
00325:          rasterizationInfo.sType                     = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
00326:          rasterizationInfo.pNext                     = nullptr;
00327:          rasterizationInfo.flags                     = 0;
00328:          rasterizationInfo.depthClampEnable          = VK_FALSE;
00329:          rasterizationInfo.rasterizerDiscardEnable   = VK_FALSE;
00330:          rasterizationInfo.polygonMode               = VK_POLYGON_MODE_FILL;
00331:          rasterizationInfo.cullMode                  = VK_CULL_MODE_BACK_BIT;
00332:          rasterizationInfo.frontFace                 = VK_FRONT_FACE_COUNTER_CLOCKWISE;
00333:          rasterizationInfo.depthBiasEnable           = VK_FALSE;
00334:          rasterizationInfo.depthBiasConstantFactor   = 0.0f;
00335:          rasterizationInfo.depthBiasClamp            = 0.0f;
00336:          rasterizationInfo.depthBiasSlopeFactor      = 0.0f;
00337:          rasterizationInfo.lineWidth                 = 1.0f;
sType はこの構造体のタイプを指定します。VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFOの設定が必須です。
pNext は現在nullptrを設定することが必須なのでnullptrを指定します。
flags は将来の利用のための予約領域で0を設定することが必須なので0を入れます。
depthClampEnable は錘台のz平面へとクリッピングされたプリミティブの代わりにフラグメントの深度値をクランプするかどうかを制御します。今回は特に利用しないので,VK_FALSEを指定します。
rasterizerDiscardEnable はラスタライゼイションステージの前にすぐにプリミティブが無効化されるかどうかを制御します。今回は使わないので,VK_FALSEを入れます。
polygonMode はポリゴンの描画モードを指定します。以下の3種類が設定できます。
  • VK_POLYGON_MODE_FILL
  • VK_POLYGON_MODE_LINE
  • VK_POLYGON_MODE_POINT
通常のポリゴン描画したい場合はVK_POLYGON_MODE_FILLを,ワイヤーフレーム描画したい場合はVK_POLYGON_MODE_LINEを,ポイント描画した場合はVK_POLYGON_MODE_POINTを指定します。今回は通常描画するのでVK_POLYGON_MODE_FILLを指定します。
cullMode はプリミティブのカリングに対して使用する面の方向をVkCullModeFlagBitsで設定します。設定できる値は以下の通りです。
  • VK_CULL_MODE_NONE
  • VK_CULL_MODE_FRONT_BIT
  • VK_CULL_MODE_BACK_BIT
  • VK_CULL_MODE_FRONT_AND_BACK
今回は背面カリングしたいので,VK_CULL_MODE_BACK_BITを指定します。
frontFace はカリングに対して使用される前面のポリゴンの向きをVkFrontFaceで指定します。設定できる値は次の2つです。
  • VK_FRONT_FACE_COUNTER_CLOCKWISE
  • VK_FRONT_FACE_CLOCWISE
反時計回りか,時計回りかという設定ですね。今回は一応OpenGLの後継ということで,VK_FRONT_FACE_COUNTER_CLOCKWISEを設定しておきました。
depthBiasEnable はフラグメントの深度値のバイアスをするかどうかを制御します。今回は使わないのでVK_FALSEを指定します。
depthBiasConstantFactor は各フラグメントに対して加算される定数深度値を制御するスカラー値です。今回は特に制御の必要はないので0.0fを入れます。
depthBiasClamp はフラグメントの最大(最小)深度バイアスです。これも今回は使わないので0.0fを設定します。
depthBiasSlopeFactor は深度バイアスの計算におけるフラグメントの傾斜に適用されるスカラー値です。いわゆる深度傾斜バイアス値になりますが,これも今回は使わないので0.0fを入れておきます。
lineWidth はラスタライズされたラインセグメントの幅を指定します。今回はライン描画しませんが,念のために1.0fあたりを入れておきました。

まだまだ続きます。次はマルチサンプルステート生成情報の設定です。
00339:          // マルチサンプルステートの設定.
00340:          VkPipelineMultisampleStateCreateInfo multisampleInfo = {};
00341:          multisampleInfo.sType                   = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
00342:          multisampleInfo.pNext                   = nullptr;
00343:          multisampleInfo.flags                   = 0;
00344:          multisampleInfo.rasterizationSamples    = VK_SAMPLE_COUNT_1_BIT;
00345:          multisampleInfo.sampleShadingEnable     = VK_FALSE;
00346:          multisampleInfo.minSampleShading        = 0.0f;
00347:          multisampleInfo.pSampleMask             = nullptr;
00348:          multisampleInfo.alphaToCoverageEnable   = VK_FALSE;
00349:          multisampleInfo.alphaToOneEnable        = VK_FALSE;
VkPipelineMultisampleStateCreateInfo構造体に設定値を入れていきます。
sType はこの構造体のタイプであるVK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFOを指定します。
pNext は nullptr,flags は0を指定します。
rasterizationSamples はラスタライゼーションの際に使用されるピクセルごとのサンプルの数をVkSampleCountFlagBitsで指定します。今回はマルチサンプルを使わないので,VK_SAMPLE_COUNT_1_BITを入れました。
sampleShadingEnable はサンプルごとのフラグメントシェーディングを実行するかどうかを指定します。今回は使わないのでVK_FALSEを指定します。
minSampleShading は各フラグメントについて一意なシェーディングするためのサンプルの最小数です。今回は使わないので0.0fを入れました。
pSampleMask は静的なカバレッジ情報のビットマスクです。今回は使わないのでnullptrを指定します。
alphaToCoverageEnable はアルファトゥカバレッジを有効にするかどうかの値です。今回は使わないのでVK_FALSEを指定します。
alphatToOneEnable はフラグメントの最初のカラー出力のアルファコンポネントの値を1で置き換えるかどうかを制御します。今回は置き換えの必要はないのでVK_FALSEを指定します。

続いて,ステンシル操作です。ステンシル操作はVkStencilOpStateで設定します。
00351:          // ステンシル操作の設定.
00352:          VkStencilOpState stencilOp = {};
00353:          stencilOp.failOp        = VK_STENCIL_OP_KEEP;
00354:          stencilOp.passOp        = VK_STENCIL_OP_KEEP;
00355:          stencilOp.depthFailOp   = VK_STENCIL_OP_KEEP;
00356:          stencilOp.compareOp     = VK_COMPARE_OP_NEVER;
00357:          stencilOp.compareMask   = 0;
00358:          stencilOp.writeMask     = 0;
00359:          stencilOp.reference     = 0;
failOp はステンシルテストが失敗したときにサンプルについて実行するアクションを指定します。今回はステンシルテストを使わず,何もする必要がないのでVK_STENCIL_OP_KEEPを指定します。
passOp は深度テストとステンシルテストが合格したときに実行するアクションを指定します。今回はこれも同じくVK_STENCL_OP_KEEPを指定します。
depthFailOp はステンシルテストが合格して深度テストが失敗したときに実行するアクションを指定します。今回はこれも同じくVK_STENCIL_OP_KEEPを指定します。
compareOp はステンシルテストの際に使われる比較操作を指定します。今回は比較の必要がないので,VK_COMPARE_OP_NEVERを指定します。
compareMask,writeMask,reference は今回は使わないのでそれぞれ0を設定しました。
ここで設定した,VkStencilOpStateはVkPipelineDepthStencilStateCreateInfo構造体に設定するために使用します。

次に深度ステンシルステートの生成情報について設定していきます。
00361:          // 深度・ステンシルステートの設定.
00362:          VkPipelineDepthStencilStateCreateInfo depthInfo = {};
00363:          depthInfo.sType                 = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
00364:          depthInfo.pNext                 = nullptr;
00365:          depthInfo.flags                 = 0;
00366:          depthInfo.depthTestEnable       = VK_TRUE;
00367:          depthInfo.depthWriteEnable      = VK_TRUE;
00368:          depthInfo.depthCompareOp        = VK_COMPARE_OP_LESS_OR_EQUAL;
00369:          depthInfo.depthBoundsTestEnable = VK_FALSE;
00370:          depthInfo.stencilTestEnable     = VK_FALSE;
00371:          depthInfo.front                 = stencilOp;
00372:          depthInfo.back                  = stencilOp;
00373:          depthInfo.minDepthBounds        = 0.0f;
00374:          depthInfo.maxDepthBounds        = 0.0f;
sType はこの構造体のタイプであるVK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFOを指定します。
pNext は拡張仕様構造体へのポインタですが,現在はnullptrを設定することが必須のためnullptrを入れます。
flags は将来の利用のための予約領域なので0を入れます。
depthTestEnable は深度テストを有効にするかどうかを制御します。今回はテストを一応行いたいのでVK_TRUEを指定します。
depthWriteEnable は深度書き込みをするかどうかを指定します。今回は深度バッファに書き込みしたいのでVK_TRUEを指定します。
depthCompareOp は深度テストの際に使われる比較操作を指定します。今回は比較操作としてVK_COMPARE_OP_LESS_OR_EQUALを指定しました。OpenGLで言うところの glDeptuFunc()に設定する,GL_LEQUALと同じです。
depthBoundsTestEnable は今回使わないのでVK_FALSEを指定します。
stencilTestEnable はステンシルテストを有効にするかどうかを指定します。今回は使わないのでVK_FALSEを入れます。
front と back はステンシルテストの制御パラメータです。これは先ほど設定したstencilOpを指定します。
minDepthBounds と maxDepthBounds は深度バウンドテストに使われる値の範囲を指定します。今回はテストを行わず使わないので,それぞれ0を入れます。

次はブレンド設定です。VkPipelineColorBlendAttachmentState構造体に値を設定します。
00376:          // ブレンドアタッチメントの設定.
00377:          VkPipelineColorBlendAttachmentState blendState = {};
00378:          blendState.blendEnable          = VK_FALSE;
00379:          blendState.srcColorBlendFactor  = VK_BLEND_FACTOR_ZERO;
00380:          blendState.dstColorBlendFactor  = VK_BLEND_FACTOR_ZERO;
00381:          blendState.colorBlendOp         = VK_BLEND_OP_ADD;
00382:          blendState.srcAlphaBlendFactor  = VK_BLEND_FACTOR_ZERO;
00383:          blendState.dstAlphaBlendFactor  = VK_BLEND_FACTOR_ZERO;
00384:          blendState.alphaBlendOp         = VK_BLEND_OP_ADD;
00385:          blendState.colorWriteMask       = 0xf;
今回はブレンディングしないので,適当な値を設定しました。
次は,VkPipelineColorBlendStateCreateInfo構造体の設定です。
00387:          // ブレンドステートの設定.
00388:          VkPipelineColorBlendStateCreateInfo blendInfo = {};
00389:          blendInfo.sType             = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
00390:          blendInfo.pNext             = nullptr;
00391:          blendInfo.flags             = 0;
00392:          blendInfo.logicOpEnable     = VK_FALSE;
00393:          blendInfo.logicOp           = VK_LOGIC_OP_CLEAR;
00394:          blendInfo.attachmentCount   = 1;
00395:          blendInfo.pAttachments      = &blendState;
00396:          blendInfo.blendConstants[0] = 0.0f;
00397:          blendInfo.blendConstants[1] = 0.0f;
00398:          blendInfo.blendConstants[2] = 0.0f;
00399:          blendInfo.blendConstants[3] = 0.0f;
先ほど設定した,VkPipelineColorBlendAttachmentStateを1つ設定するので,attachmentCountは1,pAttachmentsにはblendStateを設定しています。

次は動的ステートです。基本は上記の設定で1度作ったら変更できないのですが,さすがにそれだと不便ということなのでしょうか,いくつか動的に変更できる仕組みが提供されているようです。
00401:          // 動的ステート.
00402:          VkDynamicState dynamicState[2] = {
00403:              VK_DYNAMIC_STATE_VIEWPORT,
00404:              VK_DYNAMIC_STATE_SCISSOR,
00405:          };
00406:  
00407:          // 動的ステートの生成.
00408:          VkPipelineDynamicStateCreateInfo dynamicInfo = {};
00409:          dynamicInfo.sType               = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
00410:          dynamicInfo.pNext               = nullptr;
00411:          dynamicInfo.flags               = 0;
00412:          dynamicInfo.dynamicStateCount   = 2;
00413:          dynamicInfo.pDynamicStates      = dynamicState;
VkDynamicState列挙体は以下のものが用意されています。
  • VK_DYNAMIC_STATE_VIEWPORT
  • VK_DYNAMIC_STATE_SCISSOR
  • VK_DYNAMIC_STATE_LINE_WIDTH
  • VK_DYNAMIC_STATE_DEPTH_BIAS
  • VK_DYNAMIC_STATE_BLEND_CONSTANTS
  • VK_DYNAMIC_STATE_DEPTH_BOUNDS
  • VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK
  • VK_DYNAMIC_STATE_STENCIL_WRITE_MASK
  • VK_DYNAMIC_STATE_STENCIL_REFERENCE
上記の列挙体に対応するメソッドはそれぞれ下記の様になるようです。
  • vkCmdSetViewport()
  • vkCmdSetScissor()
  • vkCmdSetLineWidth()
  • vkCmdSetDepthBias()
  • vkCmdSetBlendConstants()
  • vkCmdSetDepthBounds()
  • vkCmdSetStencilCompareMask()
  • vkCmdSetStencilWriteMask()
  • vkCmdSetStencilReference()
今回のサンプルでは,ビューポートとシザー矩形だけ動的に変更できるようにしました。

非常に長かったですが,これでようやくパイプラインの生成に必要なデータが揃いました。今回のサンプルでは描画にパイプラインを設定するので,VkGraphicsPipelineCreateInfo構造体にこれまで設定してきた値をいれてやって,パイプラインを生成します。値の設定は下記の様になります。
00415:          // グラフィックスパイプラインの設定.
00416:          VkGraphicsPipelineCreateInfo pipelineInfo = {};
00417:          pipelineInfo.sType                  = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
00418:          pipelineInfo.pNext                  = nullptr;
00419:          pipelineInfo.stageCount             = 2;
00420:          pipelineInfo.pStages                = stageInfo;
00421:          pipelineInfo.pVertexInputState      = &vertexInputInfo;
00422:          pipelineInfo.pInputAssemblyState    = &inputAssemblyInfo;
00423:          pipelineInfo.pTessellationState     = nullptr;
00424:          pipelineInfo.pViewportState         = &viewportInfo;
00425:          pipelineInfo.pRasterizationState    = &rasterizationInfo;
00426:          pipelineInfo.pMultisampleState      = &multisampleInfo;
00427:          pipelineInfo.pDepthStencilState     = &depthInfo;
00428:          pipelineInfo.pColorBlendState       = &blendInfo;
00429:          pipelineInfo.pDynamicState          = &dynamicInfo;
00430:          pipelineInfo.layout                 = m_PipelineLayout;
00431:          pipelineInfo.renderPass             = m_RenderPass;
00432:          pipelineInfo.subpass                = 0;
00433:          pipelineInfo.basePipelineHandle     = null_handle;
00434:          pipelineInfo.basePipelineIndex      = 0;
00435:  
00436:          // グラフィックスパイプラインの生成.
00437:          auto result = vkCreateGraphicsPipelines(device, m_PipelineCache, 1, &pipelineInfo, nullptr, &m_Pipeline);
00438:          if (result != VK_SUCCESS)
00439:          {
00440:              ELOG( "Error : vkCreateGraphicsPipelines() Failed." );
00441:              vkDestroyShaderModule(device, vs, nullptr);
00442:              vkDestroyShaderModule(device, fs, nullptr);
00443:              return false;
00444:          }
00445:  
00446:          // 不要なオブジェクトを破棄.
00447:          vkDestroyShaderModule(device, vs, nullptr);
00448:          vkDestroyShaderModule(device, fs, nullptr);
sType はこの構造体のタイプであるVK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFOを指定します。
pNext は nullptrを指定します。
flags は パイプラインがどのように生成されるかを制御するVkPipelineCreateFlagsBitのビットフィールドで指定しますが,今回は特に指定しませんでした。
stageCount はシェーダステージの数を設定します。今回は頂点シェーダとフラグメントシェーダの2つを使うので2を設定します。
pStage は シェーダステージとして設定するVkPipelineShaderStageCreateInfo構造体の配列を指定します。今回は上記で設定したstageInfoになります。
pVertexInputeState はVkPipelineVertexInputStateCreateInfo構造体のインスタンスへのポインタを設定します。今回は上記で設定したvertexInputInfoになります。
pInputAssemblyState はVkPipelineInputAssemblyStateCreateInfo構造体のインスタンスへのポインタを設定します。今回は上記で設定したinputAssemblyInfoになります。
pTessellationSate はVkPipelineTessellationStateCreateInfo構造体のインスタンスへのポインタかテッセレーション制御シェーダとテッセレーション評価シェーダを含めない場合はnullptrを指定します。今回はテッセレーションを使わないためnullptrを指定します。
pViewportState はVkPipelineViewportStateCreateInfo構造体のインスタンスへのポインタかラスタライゼーションを無効化する場合はnullptrを指定します。今回は上記で設定したviewportInfoを設定します。
pRasterState は VkPipelineRasterizationStateCreateInfo構造体のインスタンスへのポインタを設定します。今回は上記で設定したrasterizationInfoを設定します。
pMultisampleState は VkPipelineMultisampleStateCreateInfo構造体のインスタンスへのポインタからラスタライゼーションを無効化する場合はnullptrを指定します。今回は上記で設定したmultisampleInfoを設定します。
pDepthStencilState は VkPipelineDepthStencilStateCreateInfo構造体のインスタンスへのポインタか,ラスタライゼーションが無効化あるいはレンダーパスのサブパスが深度・ステンシルアタッチメントを使用せずに生成されている場合はnullptrを指定します。今回は上記で設定したdepthInfoを設定します。
pColorBlendState はVkPipelineColorBlendStateCreateInfo構造体のインスタンスへのポインタか,ラスタライゼーションが無効化あるいはレンダーパスのサブパスがカラーアタッチメントを使用せずに生成されている場合はnullptrを指定します。今回は上記で設定したblendInfoを設定します。
pDynamicState は 動的ステートを利用する場合はVkPipelineDynamicStateCreateInfo構造体へのポインタを指定,動的ステートを考慮する必要が無い場合はnullptrを指定します。今回は上記で設定したdynamicInfoを設定します。
layout はパイプラインレイアウトを指定します。これは前述したm_PipelineLayoutを指定します。
renderPass はレンダーパスのハンドルを指定します。これは前述したm_RenderPassを指定します。
subpass はこのパイプラインで使用されるレンダーパスのサブパスのインデックスを指定します。今回は0番を指定します。
basePipelineHandle は派生元のパイプラインを指定します。今回は使わないので,VK_NULL_HANDLEに対応するnull_handleを指定しました。
basePipelineIndex は派生元のパイプラインとして使用するためのpCreateInfoパラメータ内のインデックスを指定します。今回は使わないので0を指定しました。
これでグラフィックスパイプラインの生成情報が埋まったので,vkCreateGraphicsPipelines()でパイプラインオブジェクトを生成します。生成後は,シェーダモジュールが不要になるので,vkDestroyShaderModule()で生成したシェーダモジュールを忘れずに破棄しておきます。

めちゃくちゃ長かったですが,これでフレーム描画に必要なオブジェクトが揃いました。
次のセクションでフレーム描画処理を説明します。


 7.フレーム描画処理
ようやくフレーム描画処理です。処理コードは下記の様になります。
00513:  //-------------------------------------------------------------------------------------------------
00514:  //      フレーム描画時の処理です.
00515:  //-------------------------------------------------------------------------------------------------
00516:  void SampleApp::OnFrameRender(const asvk::FrameEventArgs& args)
00517:  {
00518:      ASVK_UNUSED(args);
00519:  
00520:      // コマンドの記録を開始.
00521:      m_CommandList.Reset();
00522:  
00523:      // 現在のコマンドリストを取得.
00524:      auto cmd = m_CommandList.GetCurrentCommandBuffer();
00525:  
00526:      VkDeviceSize offset = 0;
00527:  
00528:      // 描画処理.
00529:      {
00530:          // Present ---> Attachment へのパイプラインバリアを張る.
00531:          ResourceBarrier(
00532:              cmd,
00533:              VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
00534:              VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
00535:              VK_ACCESS_MEMORY_READ_BIT,
00536:              VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
00537:              VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
00538:              VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
00539:  
00540:          // レンダーパスを開始.
00541:          BeginRenderPass(cmd);
00542:  
00543:          // パイプラインをバインドする.
00544:          vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, m_Pipeline);
00545:  
00546:          // ビューポート・シザー矩形の設定.
00547:          vkCmdSetViewport(cmd, 0, 1, &m_Viewport);
00548:          vkCmdSetScissor (cmd, 0, 1, &m_Scissor);
00549:  
00550:          // 頂点バッファの設定.
00551:          vkCmdBindVertexBuffers(cmd, 0, 1, &m_Mesh.Buffer, &offset);
00552:  
00553:          // 描画コマンドを積む.
00554:          vkCmdDraw(cmd, 3, 1, 0, 0);
00555:  
00556:          // レンダーパスを終了.
00557:          EndRenderPass(cmd);
00558:  
00559:          // Attachment ---> Present へのパイプラインバリアを張る.
00560:          ResourceBarrier(
00561:              cmd,
00562:              VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
00563:              VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
00564:              VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
00565:              VK_ACCESS_MEMORY_READ_BIT,
00566:              VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
00567:              VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
00568:      }
00569:  
00570:      // コマンドの記録を終了.
00571:      m_CommandList.Close();
00572:  
00573:      // コマンドを実行.
00574:      m_pQueue->Execute(1, &cmd);
00575:  
00576:      // コマンドの完了を待機.
00577:      m_pQueue->Wait(TimeOut);
00578:  
00579:      // 画面に表示.
00580:      m_SwapChain.Present(TimeOut);
00581:  }
初期化コードに比べると大分短いです。
まずは,m_CommandList.Reset()を使ってvkBeginCommandBufffer()を呼び出しておきます。次にポリゴン描画を行うので,カラーバッファのステートを表示状態から,書き込み状態に変更を行うパイプラインバリアを張っておきます。ResourceBarrier()メソッドは下記の様に実装しました。
00583:  //-------------------------------------------------------------------------------------------------
00584:  //      リソースバリアを設定します.
00585:  //-------------------------------------------------------------------------------------------------
00586:  void SampleApp::ResourceBarrier
00587:  (
00588:      VkCommandBuffer         commandBuffer,
00589:      VkPipelineStageFlags    srcStageFlags,
00590:      VkPipelineStageFlags    dstStageFlags,
00591:      VkAccessFlags           srcAccessMask,
00592:      VkAccessFlags           dstAccessMask,
00593:      VkImageLayout           oldLayout,
00594:      VkImageLayout           newLayout
00595:  )
00596:  {
00597:      VkImageMemoryBarrier barrier;
00598:      barrier.sType               = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
00599:      barrier.pNext               = nullptr;
00600:      barrier.srcAccessMask       = srcAccessMask;
00601:      barrier.dstAccessMask       = dstAccessMask;
00602:      barrier.oldLayout           = oldLayout;
00603:      barrier.newLayout           = newLayout;
00604:      barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
00605:      barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
00606:      barrier.subresourceRange    = m_SwapChain.GetRange();
00607:      barrier.image               = m_SwapChain.GetCurrentBuffer()->Image;
00608:  
00609:      vkCmdPipelineBarrier(
00610:          commandBuffer,
00611:          srcStageFlags,
00612:          dstStageFlags,
00613:          0,
00614:          0,
00615:          nullptr,
00616:          0,
00617:          nullptr,
00618:          1,
00619:          &barrier);
00620:  }
パイプラインバリアが張り終わったら,フレームバッファをバインドするためにレンダーパスを開始します。
開始処理は下記のようにBeginRenderPass()メソッドで行っています。
00622:  //-------------------------------------------------------------------------------------------------
00623:  //      レンダーパスを開始します.
00624:  //-------------------------------------------------------------------------------------------------
00625:  void SampleApp::BeginRenderPass(VkCommandBuffer commandBuffer)
00626:  {
00627:      // カラーバッファのクリアカラーの設定.
00628:      VkClearColorValue clearColor = {};
00629:      clearColor.float32[0] = 0.392156899f;
00630:      clearColor.float32[1] = 0.584313750f;
00631:      clearColor.float32[2] = 0.929411829f;
00632:      clearColor.float32[3] = 1.0f;
00633:  
00634:      // 深度・ステンシルのクリア値の設定.
00635:      VkClearDepthStencilValue clearDepthStencil = {};
00636:      clearDepthStencil.depth   = 1.0f;
00637:      clearDepthStencil.stencil = 0;
00638:  
00639:      VkClearValue clearValues[2];
00640:      clearValues[0].color        = clearColor;
00641:      clearValues[1].depthStencil = clearDepthStencil;
00642:  
00643:      auto idx = m_SwapChain.GetBufferIndex();
00644:      auto frameBuffer = m_FrameBuffer[idx];
00645:  
00646:      // レンダーパスの開始設定.
00647:      VkRenderPassBeginInfo info = {};
00648:      info.sType                      = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
00649:      info.pNext                      = nullptr;
00650:      info.renderPass                 = m_RenderPass;
00651:      info.framebuffer                = frameBuffer;
00652:      info.renderArea.offset.x        = 0;
00653:      info.renderArea.offset.y        = 0;
00654:      info.renderArea.extent.width    = m_Width;
00655:      info.renderArea.extent.height   = m_Height;
00656:      info.clearValueCount            = 2;
00657:      info.pClearValues               = clearValues;
00658:  
00659:      // レンダーパス開始コマンドを積む.
00660:      vkCmdBeginRenderPass(commandBuffer, &info, VK_SUBPASS_CONTENTS_INLINE);
00661:  }
レンダーパスを開始したら,GPUで処理するグラフィックスパイプラインのためにパイプラインをvkCmdBindPipeline()でバインドします。今回は描画用途なのでバインドポイントはVK_PIPELIINE_BIND_POINT_GRAPHICSを指定します。これでシェーダ等のバインドがされます。
パイプラインをバインドしたら,今回は動的にビューポートとシザー矩形を設定できるようにしてあるので,vkCmdSetViewport()とvkCmdSetScissor()でビューポートとシザー矩形の設定を行います。

次はポリゴン描画のための頂点バッファをvkCmdBindVertexBuffers()でバインドします。第2引数は最初にバインドする頂点のインデックスで,第3引数は頂点入力バインディングの数を指定します。第4引数は頂点バッファで,第5引数はバッファオフセットの配列へのポインタを設定します。今回は1個の頂点バッファをバインドするので上記のコードの様にしました。
頂点バッファをバインドしたら,vkCmdDraw()でドローコールを積みます。第2引数は描画する頂点数で今回は三角形を描画したいので3を指定します。第3引数はインスタンスの数です。今回は1つ描画したいので1を指定します。第4引数は最初に描画する頂点のインデックスです。今回は0番目から順に描くので0を指定します。第5引数は最初に描画するインスタンスIDを設定します。今回は0を指定します。
これで描画したいことは終わったので,レンダーパスを終了します。下記の様にvkCmdEndRenderPass()を呼び出します。
00663:  //-------------------------------------------------------------------------------------------------
00664:  //      レンダーパスを終了します.
00665:  //-------------------------------------------------------------------------------------------------
00666:  void SampleApp::EndRenderPass(VkCommandBuffer commandBuffer)
00667:  {
00668:      // レンダーパス終了コマンドを積む.
00669:      vkCmdEndRenderPass(commandBuffer);
00670:  }
レンダーパスの終了コマンドを積んだらコマンドバッファへの書き込みを終了する前に,カラーバッファのステートをカラー書き込み状態から表示用に変更するためResourceBarrier()メソッドを呼び出します。
パイプラインバリアを張り終わったら,m_CommdnList.Close()を使って,vkEndCommandBuffer()を呼び出します。呼び出し終わったら,m_pQueue->Execute(1, &cmd)で1個のコマンドバッファを実行します。このメソッドでは内部で,vkQueueSubmit()が実行されます。
コマンドを実行したら,m_pQueue->Wait()でコマンドの完了を待機します。このメソッドではvkWaitForFences()で完了を待機して,その後にvkResetFences()が呼ばれるようになっています。
最後にm_SwapChain.Present()で画面への表示を行います。このメソッドでは,vkQueuePresentKHR()とvkAcquireNextImageKHR()がそれぞれ呼び出されます。フレーム描画処理は以上です。


 8.後片付け
プログラムを終了するために生成したオブジェクトをvkDestroyXXXXのメソッドで破棄します。
00455:  //-------------------------------------------------------------------------------------------------
00456:  //      終了時の処理です.
00457:  //-------------------------------------------------------------------------------------------------
00458:  void SampleApp::OnTerm()
00459:  {
00460:      auto device = m_DeviceMgr.GetDevice();
00461:      assert(device != nullptr);
00462:  
00463:      // メッシュの破棄処理.
00464:      {
00465:          if (m_Mesh.Memory != null_handle)
00466:          {
00467:              vkFreeMemory(device, m_Mesh.Memory, nullptr); 
00468:              m_Mesh.Memory = null_handle;
00469:          }
00470:  
00471:          if (m_Mesh.Buffer != null_handle)
00472:          {
00473:              vkDestroyBuffer(device, m_Mesh.Buffer, nullptr);
00474:              m_Mesh.Buffer = null_handle;
00475:          }
00476:  
00477:          memset(&m_Mesh.Bindings,   0, sizeof(m_Mesh.Bindings));
00478:          memset(&m_Mesh.Attributes, 0, sizeof(m_Mesh.Attributes));
00479:      }
00480:  
00481:      // パイプライン破棄.
00482:      if (m_Pipeline != null_handle)
00483:      {
00484:          vkDestroyPipeline(device, m_Pipeline, nullptr);
00485:          m_Pipeline = null_handle;
00486:      }
00487:  
00488:      // パイプラインレイアウト破棄.
00489:      if (m_PipelineLayout != null_handle)
00490:      {
00491:          vkDestroyPipelineLayout(device, m_PipelineLayout, nullptr);
00492:          m_PipelineLayout = null_handle;
00493:      }
00494:  
00495:      // パイプラインキャッシュ破棄.
00496:      if (m_PipelineCache != null_handle)
00497:      {
00498:          vkDestroyPipelineCache(device, m_PipelineCache, nullptr);
00499:          m_PipelineCache = null_handle;
00500:      }
00501:  
00502:      m_pQueue = nullptr;
00503:  }
m_pQueueについてはDeviceMgr側で破棄処理を行うので,ここではポインタクリアのみ行っています。


 9.最後に
頑張って書いたコードの描画結果ですが,下記の様になります。


あれ?と思われる方もいるかもしれません。見て気づくように何故か上下逆になっています。
ちなみに頂点バッファの座標設定を見直すと下記の様になっています。
00067:          // 頂点データ.
00068:          Vertex vertices[3] = {
00069:              { asvk::Vector3(-0.5f, -0.75f, 0.0f), asvk::Vector2(0.0f, 0.0f), asvk::Vector4(1.0f, 0.0f, 0.0f, 1.0f) },
00070:              { asvk::Vector3( 0.0f,  0.75f, 0.0f), asvk::Vector2(0.5f, 1.0f), asvk::Vector4(0.0f, 1.0f, 0.0f, 1.0f) },
00071:              { asvk::Vector3( 0.5f, -0.75f, 0.0f), asvk::Vector2(1.0f, 0.0f), asvk::Vector4(0.0f, 0.0f, 1.0f, 1.0f) }
00072:          };
頂点シェーダも見直してみましょう。
00027:  //-------------------------------------------------------------------------------------------------
00028:  //      頂点シェーダメインエントリーポイントです.
00029:  //-------------------------------------------------------------------------------------------------
00030:  void main()
00031:  {
00032:      vec4 localPos = vec4(InputPosition, 1.0f);
00033:  
00034:      gl_Position    = localPos;
00035:      OutputTexCoord = InputTexCoord;
00036:      OutputColor    = InputColor;
00037:  }
どうやら設定ミスではないようです。
OpenGLやDirectXで描画した際はこれで上向きの三角形がきちんと表示されます。
ここでグラフィックスに詳しい人は,NDC(正規化デバイス座標系)がVulkanの場合はY方向が異なるということを疑うのではないでしょうか?どうやらVulkanのNDCはTop=-1, Bottom=1で,OpenGLやDirectXと逆でウィンドウ座標系と一致するようになっているようなのです。そんなわけで,表示する場合はOpenGLとDirectXと異なり上下逆転するので注意しましょう。

今回はディスクリプタなどを使わない一番単純なポリゴン描画について説明してみました。
次回は,ディスクリプタの説明のために行列を使って三角形を回してみようかなと思っています。


 Download
本ソースコードおよびプログラムはMIT Licenseに準じます。
プログラムの作成にはMicrosoft Visual Studio Community 2015, 及び Vulkan SDK 1.0.3.1を用いています。
動作確認環境は,Windows 10 64bit, GPUはNVIDIA GeForce GTX 650,ドライバーバージョンは,356.43です。