Aqoole_Hateenaの技術日記

vulkan+raytraceで色々描いてます

Android + VulkanでImguiの導入

出力例

なぜか最初の1frame目だけはimguiの部分がrenderingされていないが、2frame目以降は表示される。
fontが小さすぎて何書いているかわからない部分は今後の改善点。

f:id:Aqoole_Hateena:20220327210954p:plain
1frame目
f:id:Aqoole_Hateena:20220327211038p:plain
1frame目以降

IMGUIの基本

こちらを参照ください。
一言で言えば、C++GUI環境を構築するためのネイティブツールです。
github.com
以下は過去に私が書いたimgui関連の記事。
aqoole-hateena.hatenablog.com
qiita.com

Android + Vulkan

examplesには含まれていないが、Android + Vulkanの組み合わせでもrendering自体は可能。
どこまで使い勝手よくできるかは、今後の調査次第。

Window

Androidでimguiを導入する場合、windowはAndroidのシステムが提供してくれているANativeWindowを使うしかない。
そしてこの部分が今後どうなるかは不明だが、今の私が調べた限り、Vulkanがwindowと認識するものをAndroidのアプリの中でもうひとつ生成する方法が見つからなかった。
なので、ひとつのwindow(image)の中に

  • renderingしたい図形
  • Imguiの領域

の両方をrenderingする必要がある。

サンプルコード

imguiのexampleを眺めてみると、メインループの中でコマンドへの登録とqueueへのsubmitを行っている。
なのでこの出力例でもrenderingしたい図形とは別のコマンドバッファーにimguiのrenderingコマンドを登録するとともにすぐにsubmitする。

imguiのコマンドを登録する部分

void RecordImguiCommand(uint32_t imageNum)
{
  VkCommandBuffer* cb = gImgui->GetCommandBuffer()->GetCommandBuffer();
  VkCommandBufferBeginInfo cmdBufferBeginInfo{
          .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
          .pNext = nullptr,
          .flags = 0,
          .pInheritanceInfo = nullptr,
  };
  CALL_VK(vkBeginCommandBuffer(*cb, &cmdBufferBeginInfo));
  VkClearValue clearVals[2]{ {.color { .float32 {0.0f, 0.34f, 0.90f, 1.0f}}},{.depthStencil{.depth = 1.0f}}};
  VkRenderPassBeginInfo renderPassBeginInfo{
          .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
          .pNext = nullptr,
          .renderPass = render.renderPass_,
          .framebuffer = swapchain.framebuffers_[imageNum],
          .renderArea = {.offset { .x = 0, .y = 0,},
                  .extent = {.width = 150, .height = 200}},
          .clearValueCount = 2,
          .pClearValues = clearVals};
  vkCmdBeginRenderPass(*cb, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
  ImGui_ImplAndroid_NewFrame();
  ImGui_ImplVulkan_NewFrame();
  ImGui::NewFrame();
  static float f = 0.0f;
  static int counter = 0;
  ImGui::Begin("Parameters");                          // Create a window called "Hello, world!" and append into it.
  if (ImGui::Button("\nreset time\n"))                            // Buttons return true when clicked (most widgets return true when edited/activated)
  {
        ResetCamera();
  }
  if (ImGui::Button("pause"))                            // Buttons return true when clicked (most widgets return true when edited/activated)
//      paused = !paused;
  ImGui::SameLine();
  ImGui::End();
  ImGui::Render();
  ImDrawData* drawData = ImGui::GetDrawData();
  ImGui_ImplVulkan_RenderDrawData(drawData, *cb);
  vkCmdEndRenderPass(*cb);
  vkEndCommandBuffer(*cb);
}

メインループ内で呼び出す部分

bool VulkanDrawFrame(android_app *app, uint32_t currentFrame, bool& isTouched, bool& isFocused, glm::vec2* touchPositions,
                     glm::vec3* gravityData, glm::vec3* lastGravityData) {
  :
 中略
  :
  uint32_t nextIndex;
  // Get the framebuffer index we should draw in
  CALL_VK(vkAcquireNextImageKHR(device.device_, swapchain.swapchain_,
                                UINT64_MAX, render.semaphore_, VK_NULL_HANDLE,
                                &nextIndex));
  CALL_VK(vkResetFences(device.device_, 1, &render.fence_));
  RecordImguiCommand(nextIndex);
    VkPipelineStageFlags waitStageMask =
      VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
  VkCommandBuffer cmdBuffers[2] = {render.cmdBuffer_[nextIndex], *gImgui->GetCommandBuffer()->GetCommandBuffer()};
  VkSubmitInfo submit_info = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
                              .pNext = nullptr,
                              .waitSemaphoreCount = 1,
                              .pWaitSemaphores = &render.semaphore_,
                              .pWaitDstStageMask = &waitStageMask,
                              .commandBufferCount = 2,
                              .pCommandBuffers = cmdBuffers,
                              .signalSemaphoreCount = 1,
                              .pSignalSemaphores = &render.presentSemaphore_,};
  CALL_VK(vkQueueSubmit(device.queue_, 1, &submit_info, render.fence_));
  CALL_VK(
      vkWaitForFences(device.device_, 1, &render.fence_, VK_TRUE, 100000000));

  LOGI("Drawing frames......");

  VkResult result;
  VkPresentInfoKHR presentInfo{
      .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
      .pNext = nullptr,
      .waitSemaphoreCount = 1,
      .pWaitSemaphores = &render.presentSemaphore_,
      .swapchainCount = 1,
      .pSwapchains = &swapchain.swapchain_,
      .pImageIndices = &nextIndex,
      .pResults = &result,
  };
  vkQueuePresentKHR(device.queue_, &presentInfo);
  double currentTime = GetTime();
  //UpdateUI(app, 1000.0f / (float)(currentTime - lastTime));
  lastTime = currentTime;
  return true;
}

最後に

imgui領域のrenderingには成功したが、字が小さすぎるので、ここは改善していきたい。
また、ボタンを押しても指定した関数は実行されない。
Android + OpenGLESのexamplesにも書かれている通り、AndroidJava側で実行する関数の登録と呼び出しを実装しなければならなさそう。
これらの部分も今後調査して、実装していきたい。
私のGitHubプロジェクトはこちら。(ライセンスはApache2.0)
何かの参考になれば幸いです。
GitHub - kodai731/Aqoole-Engine-Android-Vulkan-Rendering-Engine-: Android + Vulkan Rendering Engine