Aqoole_Hateenaの技術日記

vulkan+raytraceで色々描いてます

AndroidでRay Tracingできた

はじめに

ついにAndroidでリアルタイムray tracingできる時代が来た!ということでハローワールド的にray tracingで立方体を8つ描いた図がこちら。

f:id:Aqoole_Hateena:20220403233936p:plain
白背景に立方体4つをray tracingで描いてみた図

スマホは最近手に取れるようになったGalaxy S22を使っていますが、なんだか地味な画像だなと思われるかもしれません。ray tracingといえば高級なグラフィクスのゲームを連想される方がほとんどでしょうが、本来ray traceとはレンダリング手法のひとつで、反射や屈折といった目に見える景色をそのまま描画することに長けている描画方法のことです。上図はその手法を用いて簡単な図をレンダリングしてみました、ということになります。

Galaxy S22について

注意点ですが、日本で発売されるS22を購入しても(おそらく)ray tracingできません。この記事を見てS22が欲しくなる人はまれでしょうが、間違えて購入しないようにお願いします。
S22は欧州向けとそれ以外の地域向けで載っているSoCが異なっており、欧州向けは「Exynos2200(GPU : Xclipse920)」、それ以外は「Snapdragon」です。Xclipse920がAMDのRDNA2アーキテクチャーなので、ray tracingが可能です。
私はAmazon UKで購入しました。

Android Studioでの注意点

まだ描画に成功しただけで、プログラム的に細かい不備がたくさんある状態です。
開発環境はAndroid Studioです。
色々まとまってくれば、Qiitaにも久しぶりに投稿したいと思ってますが、主な注意点としては以下です。

  • 最新のndkが必要。(2022/4/3時点)

これ以上でないと、ray tracing pipelineの作成に必要な拡張機能が入っていないようです。

android {
    ndkVersion "25.0.8221429-beta2"
}

NDK のダウンロード  |  Android NDK  |  Android Developers

  • main/shaders配下にshaderファイルを置いても自動でコンパイルされない。

glslcで手動でコンパイルし、spvファイルを以下に格納する必要がある。

${PROJECT_DIR}\app\build\intermediates\assets\debug\mergeDebugAssets\shaders

Android の Vulkan シェーダー コンパイラ  |  Android NDK  |  Android Developers

  • validation layerとGoogleから出ているvulkan_wrapperを併用する場合、以下にvk_sdk_platform.hの追加が必要
${NDK_DIR}\toolchains\llvm\prebuilt\windows-x86_64\sysroot\usr\include\vulkan

今後に向けて

色の持ち方がBGRAになっているので、RGBAに直したいところ。
まだまだ画面に何か描けましたという状態ですが、ここまでくれば色々できるようになると思うので、スマホでどこまでできるか楽しみです。
2022/4/4追記
validation layerを入れると、拡張機能を用いているところのほとんどすべてでエラーのメッセージが出力される。実際にはエラーなしに動いているように見えるが、これはいったい?
調査が必要。
2022/4/5追記
enableしているはずなのに、vkGetDeviceAddressの拡張機能を有効にしろというメッセージが出力される。

Validation Error: [ VUID-vkGetBufferDeviceAddress-bufferDeviceAddress-03324 ] Object 0: handle = 0xea7170000000031, type = VK_OBJECT_TYPE_BUFFER; | MessageID = 0xa641531b | vkGetBufferDeviceAddressKHR: The bufferDeviceAddress feature must: be enabled. The Vulkan spec states: The bufferDeviceAddress or VkPhysicalDeviceBufferDeviceAddressFeaturesEXT::bufferDeviceAddress feature must be enabled (https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#VUID-vkGetBufferDeviceAddress-bufferDeviceAddress-03324)2754439499

ただ、有効化していないとそもそもbuffer addressをとってこれないし、debugで値を見てみるときちんと値が入っていることがわかる。
試しにvkGetBufferAddressEXTの関数を用いると、関数が見つからず使えなかった。
Vulkanのバージョンが上がるかvalidation layerのバージョンが上がるまで、callback関数の中でメッセージをフィルターする方向で進める。
ray tracing関係のextensionは軒並みvalidation errorが出ているように見えるので無視したいが、たまに実装が本当によくないことがあるのが事態をややこしくしている。

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

popup windowの表示方法

動機

C/C++コード出身の人がndkを用いてandroidでコーディングする際に、追加情報などを画面に表示したいという需要はあると思う。
とりあえずコードがコンパイルできてプログラムが実行できれば、そこからは何とか勉強できる、というのが持論なので、ちょうどよさそうなサンプルコードを探していた。

サンプルコード

ndk samplesという、androidの公式が出しているサンプルコード集がある。
その中のteapotsというプロジェクトで、現在のFPSをpopup windowで表示するということを行っている。
Sample: Teapot  |  Android NDK  |  Android Developers
Giuhub Sampleはこちら
ndk-samples/teapots at main · android/ndk-samples · GitHub
おそらく権限の関係で公式のrepositoryから直接cloneできないので、私は一旦自分のrepositoryにforkしてからgit cloneした。

TeapotNativeActivity.java

このクラスに含まれている

public void showUI()

という関数がある。これがpopup windowを表示させる関数であるが、これをC++のコード側から呼び出す。

TeapotNativeActivity.cpp

ここでEngineというクラスの中でShowUI()という関数が用意されている。

void Engine::ShowUI() {
  JNIEnv* jni;
  app_->activity->vm->AttachCurrentThread(&jni, NULL);

  // Default class retrieval
  jclass clazz = jni->GetObjectClass(app_->activity->clazz);
  jmethodID methodID = jni->GetMethodID(clazz, "showUI", "()V");
  jni->CallVoidMethod(app_->activity->clazz, methodID);

  app_->activity->vm->DetachCurrentThread();
  return;
}

このような形で、これをmain関数の中で呼び出す。

updateFPS()

popup windowでwindowを作成した後は、そこに値を書き込む準備が必要である。
showUIと同じ要領でupdateFPSを、C++で呼び出して値を渡す。
今回はupdateなので、main loopの中で呼び出して値を更新する。

表示例

vulkanで試しているが、このように表示させることができる。
画面では、たくさんのcubesと左上にFPS表示をさせている。

f:id:Aqoole_Hateena:20220321194623p:plain
cubes + fps

あとがき

popup windowについて調査した理由

最初はimguiでデバッグ情報やinteract部分を担えるようなボタンやwindowを作成しようと思っていたが、これがなかなかに難航した。
single windowで、ユーザ定義の図形とimguiの図形の両方をレンダリングしようとすると、imageが交互に切り替わるような感じになり、非常に画面がチラつく。
ひとつのwindowにはひとつのswapchainしか作成できないので、swapchainを複数作成するという方法も不可能である。
複数作成しようとすると、VK_ERROR_NATIVE_WINDOW_IN_USE_KHRというエラーが表示される。
案としてはview portを変えて、ユーザ定義の領域とimguiのボタン用の領域を分けるか、新たにwindowを作成するかがあると思ったが、window作成の方が色々小回りが利くと思い、popup windowの作成方法を調べてみた。
このwindow領域にimguiのレンダリングが可能かどうかは、今後調べる必要がある。

git hub repository

上記のようなandroid + vulkanで色々描いている私のrepositoryはこちら
https://github.com/kodai731/Aqoole-Engine-Android-Vulkan-Rendering-Engine-

重力センサーの有効化と取得方法

結論

Androidの重力センサーの有効化と値の取得は、公式サンプルコードのままではうまくいかない。
サンプル: native-activity  |  Android NDK  |  Android Developers
先に結論を書くと、以下の過程が必要
(※ASensorEventQueue_enableSensor()は必須だが、ASensorEventQueue_registerSensor()は任意)

//initialize sensors
gSensors.gSensorManager = ASensorManager_getInstance();
gSensors.gSensorAccelerometer = ASensorManager_getDefaultSensor(gSensors.gSensorManager, ASENSOR_TYPE_ACCELEROMETER);
gSensors.gSensorGravity = ASensorManager_getDefaultSensor(gSensors.gSensorManager, ASENSOR_TYPE_GRAVITY);
gSensors.gSensorQueue = ASensorManager_createEventQueue(gSensors.gSensorManager, app->looper, LOOPER_ID_USER, NULL, NULL);
__android_log_print(ANDROID_LOG_DEBUG, "register sensor test",
                    std::to_string(ASensorEventQueue_registerSensor(gSensors.gSensorQueue, gSensors.gSensorAccelerometer, 10, 10)).c_str(), 0);
__android_log_print(ANDROID_LOG_DEBUG, "register sensor test gravity",
                    std::to_string(ASensorEventQueue_registerSensor(gSensors.gSensorQueue, gSensors.gSensorGravity, 10, 10)).c_str(), 0);
__android_log_print(ANDROID_LOG_DEBUG, "sensor test",
                    std::to_string(ASensorEventQueue_enableSensor(gSensors.gSensorQueue, gSensors.gSensorAccelerometer)).c_str(), 0);
__android_log_print(ANDROID_LOG_DEBUG, "sensor test gravity",
                    std::to_string(ASensorEventQueue_enableSensor(gSensors.gSensorQueue, gSensors.gSensorGravity)).c_str(), 0);

//main loop part
do {
  if ((ident = ALooper_pollAll(IsVulkanReady() ? 160 : -1, nullptr,
                      &events, (void**)&source)) >= 0) {
    if (source != NULL) source->process(app, source);
    if(ident == LOOPER_ID_USER)
    {
        __android_log_print(ANDROID_LOG_DEBUG, "gravity event count in loop = ", std::to_string(ASensorEventQueue_hasEvents(gSensors.gSensorQueue)).c_str(), 0);
        while(ASensorEventQueue_getEvents(gSensors.gSensorQueue, tempSensorEvent, 1) > 0) {
            gravityData.x = tempSensorEvent->vector.x;
            gravityData.y = tempSensorEvent->vector.y;
            gravityData.z = tempSensorEvent->vector.z;
            __android_log_print(ANDROID_LOG_DEBUG, "gravity2 :  ", (std::to_string(gravityData.x) + " " + std::to_string(gravityData.y) +
                                                                   " " + std::to_string(gravityData.z)).c_str(), 0);
        }
    }
  }
  __android_log_print(ANDROID_LOG_DEBUG, "gravity event count = ", std::to_string(ASensorEventQueue_hasEvents(gSensors.gSensorQueue)).c_str(),
                      0);
  // render if vulkan is ready
  if (IsVulkanReady()) {
    VulkanDrawFrame(currentFrame, isTouched, isFocused, touchPositions);
    currentFrame = (currentFrame + 1) % MAX_IN_FLIGHT;
  }
} while (app->destroyRequested == 0);

Androidのセンサーについて

使えるセンサーの一覧などがまとめられている。
モーション センサー  |  Android デベロッパー  |  Android Developers
ただNDKの観点では書かれていないので、C++で実装したい場合には別にまとめられているページを参照する。

Androidセンサー NDK

NDKの関数などが書かれている。
Sensor  |  Android NDK  |  Android Developers

注意事項

  • ASensorEventQueue_registerSensor()を用いる場合は、GradleのminSdkVersionが26以上であることが必要。

c++ - Android NDK undefined reference to ASensorEventQueue_registerSensor - Stack Overflow

The other possibility is that your minSdkVersion is lower than 26. ASensorEventQueue_registerSensor was not added until O, so it can't be linked unless your minSdkVersion is at least 26.

  • includeファイルは<android/sensor.h>

雑感とか

まとめるとたったこれだけのことなのだが、公式のコードでうまく動かないので、たどり着くまでにかなり時間がかかってしまった。
本当はlooperなど、色々調べたことも残したいが、とりあえずはこれで良いか。
公式を参照した後に、このメモを見てサクッとセンサーが使える人が増えることを願っています。

Depth Imageの初期値設定時の注意点

初期値(clear value)より奥のfragmentは描画されないので注意が必要

Depth Value

depthの値はVkPipelineDepthStencilStateCreateInfoのminDepthBoundsからmaxDepthBoundsの値に制限される。
Vulkan® 1.3.205 - A Specification (with all registered Vulkan extensions)

(参考)Depth Valueの計算式(OpenGL)

LearnOpenGL - Depth testing

F_{depth}=\frac{z - near}{far - near}
の式によって、z座標の値を[near, far]の範囲内に制限する。

Clear Value

VkRenderPassBeginInfoにVkClearValueで設定する。
VkClearDepthStencilValue(3)

depth is the clear value for the depth aspect of the depth/stencil attachment. It is a floating-point value which is automatically converted to the attachment’s format.

初期値の意味

OpenGLの数式を例にして、zにnearとfarを入れるとわかる通り、

0.0f <-> 1.0f
手前 <->

の関係になっている。例えばdepthを0.0fで初期化してしまうと、すべてのfragmentが手前で初期化されてしまうので、どの図形も見えなくなる。
必ず初期値は奥になるようにしなければ、何も見えない。


以下は縦軸と横軸のdepthが異なるひし形を2枚描いた図である。
上図が図形のすべてが描画されている状態。
下図は0.5fで初期化した場合で、depthの値が0.5fより大きいfragmentは描かれないことがわかる。

f:id:Aqoole_Hateena:20220206203710p:plain
四角形2枚(clear value = 1.01f)
f:id:Aqoole_Hateena:20220206203800p:plain
四角形2枚(clear value = 0.5f)

Input Assemble Stageの指定

Input Assembler Stage

Input Assembler Stageでの処理の記述について調べる。
graphics pipelineのstageの概要は以下に記載。
aqoole-hateena.hatenablog.com

Vertex Input State

レンダリングを行うためには、pipelineにデータを入力する必要がある。
データを入力する際に、どのbufferからデータを取ってくるのかを指定するために、vertex indexやinstance indexを使用できる。

VkPipelineVertexInputStateCreateInfo

shaderがデータを扱えるようにvertex inputの情報を記述する。

// Provided by VK_VERSION_1_0
typedef struct VkPipelineVertexInputStateCreateInfo {
    VkStructureType                             sType;
    const void*                                 pNext;
    VkPipelineVertexInputStateCreateFlags       flags;
    uint32_t                                    vertexBindingDescriptionCount;
    const VkVertexInputBindingDescription*      pVertexBindingDescriptions;
    uint32_t                                    vertexAttributeDescriptionCount;
    const VkVertexInputAttributeDescription*    pVertexAttributeDescriptions;
} VkPipelineVertexInputStateCreateInfo;

VertexInputAttributesとVertexInputBindings

VertexInputAttributes : Vertex shader variablesがbufferにどのように存在しているかを表す。variablesがvkbufferにboundされる際に、variablesとVertexInputAttributesのnumberが紐づく。
VertexInputBindings : vertex dataにbufferをbindする。VertexInputAttributesがVertexInputBindingsに紐づき、VertexInputBindingsはvkbufferに紐づく。

Vertex shaders can define input variables, which receive vertex attribute data transferred from one or more VkBuffer(s) by drawing commands. Vertex shader input variables are bound to buffers via an indirect binding where the vertex shader associates a vertex input attribute number with each variable, vertex input attributes are associated to vertex input bindings on a per-pipeline basis, and vertex input bindings are associated with specific buffers on a per-draw basis via the vkCmdBindVertexBuffers command.
Vulkan® 1.2.203 - A Specification (with all registered Vulkan extensions)

VkVertexInputAttributeDescription

vertex bufferにある各vertexに対して、vertex attributeがどこにあるのかを表す。

// Provided by VK_VERSION_1_0
typedef struct VkVertexInputAttributeDescription {
    uint32_t    location;
    uint32_t    binding;
    VkFormat    format;
    uint32_t    offset;
} VkVertexInputAttributeDescription;

  • location is the shader input location number for this attribute.
  • binding is the binding number which this attribute takes its data from.
  • format is the size and type of the vertex attribute data.
  • offset is a byte offset of this attribute relative to the start of an element in the vertex input binding.

VkVertexInputAttributeDescription(3)

location
vertex shader内で使われるindex。locationは連続である必要もなければ、すべてのlocationがパイプライン内で使用される必要もない。

binding
どのbufferからbindされているのか、このattributeがどこからデータを取ってくるのかを表す。対応するVkVertexInputBindingDescriptionのbindingと一致する。

VkVertexInputBindingDescription

bindしているbufferの詳細を表す。

// Provided by VK_VERSION_1_0
typedef struct VkVertexInputBindingDescription {
    uint32_t             binding;
    uint32_t             stride;
    VkVertexInputRate    inputRate;
} VkVertexInputBindingDescription;

  • binding is the binding number that this structure describes.
  • stride is the byte stride between consecutive elements within the buffer.
  • inputRate is a VkVertexInputRate value specifying whether vertex attribute addressing is a function of the vertex index or of the instance index.

VkVertexInputBindingDescription(3)

binding
bindのindexを表す。

stride
bindされている情報がbufferの先頭からどの距離にあるかを示す(bytes)。

inputRate
vertex attributesのデータがvertex indexかinstance indexであるかを指定する。

VkPipelineInputAssemblyStateCreateInfo


Drawing can be achieved in two modes:

  • Programmable Mesh Shading, the mesh shader assembles primitives, or
  • Programmable Primitive Shading, the input primitives are assembled

as follows.
Each draw is made up of zero or more vertices and zero or more instances, which are processed by the device and result in the assembly of primitives. Primitives are assembled according to the pInputAssemblyState member of the VkGraphicsPipelineCreateInfo structure, which is of type VkPipelineInputAssemblyStateCreateInfo:

mesh shading :
Vulkan® 1.2.203 - A Specification (with all registered Vulkan extensions)
primitive shading :
Vulkan® 1.2.203 - A Specification (with all registered Vulkan extensions)

描画には大きく2つのアプローチがあり、

  • mesh shading : 図形がmesh shaderでassembleされる(組み立てられる)。
  • primitive shading : 頂点やinstanceをもとにassembleされる。

VkPipelineInputAssemblyStateCreateInfoは後者の方法で実行する。

// Provided by VK_VERSION_1_0
typedef struct VkPipelineInputAssemblyStateCreateInfo {
    VkStructureType                            sType;
    const void*                                pNext;
    VkPipelineInputAssemblyStateCreateFlags    flags;
    VkPrimitiveTopology                        topology;
    VkBool32                                   primitiveRestartEnable;
} VkPipelineInputAssemblyStateCreateInfo;

VkPipelineInputAssemblyStateCreateInfo(3)

topology

topology is a VkPrimitiveTopology defining the primitive topology

VkPrimitiveTopology
// Provided by VK_VERSION_1_0
typedef enum VkPrimitiveTopology {
    VK_PRIMITIVE_TOPOLOGY_POINT_LIST = 0,
    VK_PRIMITIVE_TOPOLOGY_LINE_LIST = 1,
    VK_PRIMITIVE_TOPOLOGY_LINE_STRIP = 2,
    VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST = 3,
    VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP = 4,
    VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN = 5,
    VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY = 6,
    VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY = 7,
    VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY = 8,
    VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY = 9,
    VK_PRIMITIVE_TOPOLOGY_PATCH_LIST = 10,
} VkPrimitiveTopology;

VkPrimitiveTopology(3)

primitiveRestartEnable

primitiveRestartEnable controls whether a special vertex index value is treated as restarting the assembly of primitives. This enable only applies to indexed draws (vkCmdDrawIndexed, vkCmdDrawMultiIndexedEXT, and vkCmdDrawIndexedIndirect), and the special index value is either 0xFFFFFFFF when the indexType parameter of vkCmdBindIndexBuffer is equal to VK_INDEX_TYPE_UINT32, 0xFF when indexType is equal to VK_INDEX_TYPE_UINT8_EXT, or 0xFFFF when indexType is equal to VK_INDEX_TYPE_UINT16. Primitive restart is not allowed for “list” topologies, unless one of the features primitiveTopologyPatchListRestart (for VK_PRIMITIVE_TOPOLOGY_PATCH_LIST) or primitiveTopologyListRestart (for all other list topologies) is enabled.

特別なvertex indexで図形のassembleをrestartするかどうかを決める。topoligyがlist系のものでは有効にできない。

Special Value IndexType of vkCmdBindIndexBuffer
0xFFFFFFFF VK_INDEX_TYPE_UINT32
0xFFFF VK_INDEX_TYPE_UINT16
0xFF VK_INDEX_TYPE_UINT8_EXT

更新履歴

2022/1/23 Vertex Input State 新規追加

Blending Stageの指定

Blending Stage

Blending Stageではcolor blendの指定を行う。
aqoole-hateena.hatenablog.com

color blendとは

Blending combines the incoming source fragment’s R, G, B, and A values with the destination R, G, B, and A values of each sample stored in the framebuffer at the fragment’s (xf,yf) location. Blending is performed for each color sample covered by the fragment, rather than just once for each fragment.
Vulkan® 1.2.203 - A Specification (with all registered Vulkan extensions)

color blendとは、source colorとdst colorのfragments(RGBA)を混ぜ合わせて、対応するframe bufferの位置(x_f, y_f)に格納することである。
つまりsource color, dst colorの選び方と、色の混ぜ方を決めることが重要となる。

VkPipelineColorBlendStateCreateInfo

VkPipelineColorBlendStateCreateInfo - Structure specifying parameters of a newly created pipeline color blend state

// Provided by VK_VERSION_1_0
typedef struct VkPipelineColorBlendStateCreateInfo {
    VkStructureType                               sType;
    const void*                                   pNext;
    VkPipelineColorBlendStateCreateFlags          flags;
    VkBool32                                      logicOpEnable;
    VkLogicOp                                     logicOp;
    uint32_t                                      attachmentCount;
    const VkPipelineColorBlendAttachmentState*    pAttachments;
    float                                         blendConstants[4];
} VkPipelineColorBlendStateCreateInfo;

  • flags is a bitmask of VkPipelineColorBlendStateCreateFlagBits specifying additional color blending information.
  • logicOpEnable controls whether to apply Logical Operations.
  • logicOp selects which logical operation to apply.
  • attachmentCount is the number of VkPipelineColorBlendAttachmentState elements in pAttachments.
  • pAttachments is a pointer to an array of VkPipelineColorBlendAttachmentState structures defining blend state for each color attachment.
  • blendConstants is a pointer to an array of four values used as the R, G, B, and A components of the blend constant that are used in blending, depending on the blend factor.

VkPipelineColorBlendStateCreateInfo(3)

flags

現状、設定できるパラメータは1種類だけのよう。

// Provided by VK_ARM_rasterization_order_attachment_access
typedef enum VkPipelineColorBlendStateCreateFlagBits {
  // Provided by VK_ARM_rasterization_order_attachment_access
    VK_PIPELINE_COLOR_BLEND_STATE_CREATE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_BIT_ARM = 0x00000001,
} VkPipelineColorBlendStateCreateFlagBits;

VK_PIPELINE_COLOR_BLEND_STATE_CREATE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_BIT_ARM indicates that access to color and input attachments will have implicit framebuffer-local memory dependencies, allowing applications to express custom blending operations in a fragment shader. See renderpass feedback loops for more information.
VkPipelineColorBlendStateCreateFlagBits(3)

内容が理解できないので、仕様書の拡張機能について書かれてある部分を見てみる。

VK_ARM_rasterization_order_attachment_access

Renderpasses, and specifically subpass self-dependencies enable much of the same functionality as the framebuffer fetch and pixel local storage extensions did for OpenGL ES. But certain techniques such as programmable blending are awkward or impractical to implement with these alone, in part because a self-dependency is required every time a fragment will read a value at a given sample coordinate.

This extension extends the mechanism of input attachments to allow access to framebuffer attachments when used as both input and color, or depth/stencil, attachments from one fragment to the next, in rasterization order, without explicit synchronization.
Vulkan® 1.2.203 - A Specification (with all registered Vulkan extensions)

「fragmentがsample coordinateを読み込む場合に、常に自己依存が要求されることもあり、programmable blendingのような技術をRenderpassesとspecifically subpass self-dependenciesだけで実装するのは、あまりよくない」とある。color blendingをprogrammableにするには、通常のrenderpassとsubpassだけでは足りないようだ。
そこでこの拡張機能では、input attachmentsとcolor or depth/stencial attachments)の両方が使用されている場合に、明示的なsynchroなしでinput attachmentがframebuffer attachmentにアクセスすることを可能にする、とある。

Feedback loops

If a subpass uses the same attachment as both an input attachment and either a color attachment or a depth/stencil attachment, writes via the color or depth/stencil attachment are not automatically made visible to reads via the input attachment, causing a feedback loop, except in any of the following conditions:
Vulkan® 1.2.203 - A Specification (with all registered Vulkan extensions)

subpassがinput attachmentとcolor or depth/stencial attachmentの両方を持つとき、color or depth/stencial attachmentからの書き込みは、input attachmentから自動的に読み込めるようにvisibleの状態にはならない、とある。そしてこれがfeedback loopを生み出すとある。

Rendering within a subpass containing a feedback loop creates a data race, except in the following cases:

  • If the attachment is used as color and input attachment, and the pipeline performing the read was created with VK_PIPELINE_COLOR_BLEND_STATE_CREATE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_BIT_ARM included in the flags member of the pColorBlendState member of its VkGraphicsPipelineCreateInfo. This creates a framebuffer-local memory dependency for each fragment generated by draw commands using this pipeline with the following properties:
    • The first synchronization scope includes the VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT pipeline stage executed by all previous fragments (as defined by primitive order) in the corresponding framebuffer regions including those generated by the same draw command.
    • The second synchronization scope includes the VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT pipeline stage executed by the generated fragment.
    • The first access scope includes all writes to color attachments.
    • The second access scope includes all reads from input attachments.

feedback loopsをもつsubpassでのrenderingではdata race(データ競合)が発生してしまうが、拡張機能を設定しておけば回避できる。拡張を設定することにより、各fragmentについてlocal memory依存のframebufferを作成できる。sync scopeとaccess scopeは書かれてある通りである。

logicOpEnable

The application can enable a logical operation between the fragment’s color values and the existing value in the framebuffer attachment. This logical operation is applied prior to updating the framebuffer attachment. Logical operations are applied only for signed and unsigned integer and normalized integer framebuffers. Logical operations are not applied to floating-point or sRGB format color attachments.
Vulkan® 1.2.203 - A Specification (with all registered Vulkan extensions)

logical operationsとは、fragmentの色とframe bufferに保存されている色の論理演算のことである。この演算はframe buffer attachmentが更新されるよりも前に行われる。logical operationsは整数型(signed, unsigned)のframe bufferにのみ適用される。
logicOpEnableはこの論理演算を行うかどうかを設定する。

logicOp

// Provided by VK_VERSION_1_0
typedef enum VkLogicOp {
    VK_LOGIC_OP_CLEAR = 0,
    VK_LOGIC_OP_AND = 1,
    VK_LOGIC_OP_AND_REVERSE = 2,
    VK_LOGIC_OP_COPY = 3,
    VK_LOGIC_OP_AND_INVERTED = 4,
    VK_LOGIC_OP_NO_OP = 5,
    VK_LOGIC_OP_XOR = 6,
    VK_LOGIC_OP_OR = 7,
    VK_LOGIC_OP_NOR = 8,
    VK_LOGIC_OP_EQUIVALENT = 9,
    VK_LOGIC_OP_INVERT = 10,
    VK_LOGIC_OP_OR_REVERSE = 11,
    VK_LOGIC_OP_COPY_INVERTED = 12,
    VK_LOGIC_OP_OR_INVERTED = 13,
    VK_LOGIC_OP_NAND = 14,
    VK_LOGIC_OP_SET = 15,
} VkLogicOp;
Mode Operation
VK_LOGIC_OP_CLEAR 0
VK_LOGIC_OP_AND s ∧ d
VK_LOGIC_OP_AND_REVERSE s ∧ ¬ d
VK_LOGIC_OP_COPY s
VK_LOGIC_OP_AND_INVERTED ¬ s ∧ d
VK_LOGIC_OP_NO_OP d
VK_LOGIC_OP_XOR s ⊕ d
VK_LOGIC_OP_OR s ∨ d
VK_LOGIC_OP_NOR ¬ (s ∨ d)
VK_LOGIC_OP_EQUIVALENT ¬ (s ⊕ d)
VK_LOGIC_OP_INVERT ¬ d
VK_LOGIC_OP_OR_REVERSE s ∨ ¬ d
VK_LOGIC_OP_COPY_INVERTED ¬ s
VK_LOGIC_OP_OR_INVERTED ¬ s ∨ d
VK_LOGIC_OP_NAND ¬ (s ∧ d)
VK_LOGIC_OP_SET all 1s
  • s is the fragment’s R_{s0}, G_{s0}, B_{s0} or A_{s0} component value for the fragment output corresponding to the color attachment being updated, and
  • d is the color attachment’s R, G, B or A component value

Vulkan® 1.2.203 - A Specification (with all registered Vulkan extensions)

pAttachments

VkPipelineColorBlendAttachmentState - Structure specifying a pipeline color blend attachment state

// Provided by VK_VERSION_1_0
typedef struct VkPipelineColorBlendAttachmentState {
    VkBool32                 blendEnable;
    VkBlendFactor            srcColorBlendFactor;
    VkBlendFactor            dstColorBlendFactor;
    VkBlendOp                colorBlendOp;
    VkBlendFactor            srcAlphaBlendFactor;
    VkBlendFactor            dstAlphaBlendFactor;
    VkBlendOp                alphaBlendOp;
    VkColorComponentFlags    colorWriteMask;
} VkPipelineColorBlendAttachmentState;

  • blendEnable controls whether blending is enabled for the corresponding color attachment. If blending is not enabled, the source fragment’s color for that attachment is passed through unmodified.
  • srcColorBlendFactor selects which blend factor is used to determine the source factors (Sr,Sg,Sb).
  • dstColorBlendFactor selects which blend factor is used to determine the destination factors (Dr,Dg,Db).
  • colorBlendOp selects which blend operation is used to calculate the RGB values to write to the color attachment.
  • srcAlphaBlendFactor selects which blend factor is used to determine the source factor Sa.
  • dstAlphaBlendFactor selects which blend factor is used to determine the destination factor Da.
  • alphaBlendOp selects which blend operation is use to calculate the alpha values to write to the color attachment.
  • colorWriteMask is a bitmask of VkColorComponentFlagBits specifying which of the R, G, B, and/or A components are enabled for writing, as described for the Color Write Mask.

VkPipelineColorBlendAttachmentState(3)

blendEnable

color blendingを実行するかどうかを決める。

srcColorBlendFactor

VkBlendFactor - Framebuffer blending factors

// Provided by VK_VERSION_1_0
typedef enum VkBlendFactor {
    VK_BLEND_FACTOR_ZERO = 0,
    VK_BLEND_FACTOR_ONE = 1,
    VK_BLEND_FACTOR_SRC_COLOR = 2,
    VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR = 3,
    VK_BLEND_FACTOR_DST_COLOR = 4,
    VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR = 5,
    VK_BLEND_FACTOR_SRC_ALPHA = 6,
    VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA = 7,
    VK_BLEND_FACTOR_DST_ALPHA = 8,
    VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA = 9,
    VK_BLEND_FACTOR_CONSTANT_COLOR = 10,
    VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR = 11,
    VK_BLEND_FACTOR_CONSTANT_ALPHA = 12,
    VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA = 13,
    VK_BLEND_FACTOR_SRC_ALPHA_SATURATE = 14,
    VK_BLEND_FACTOR_SRC1_COLOR = 15,
    VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR = 16,
    VK_BLEND_FACTOR_SRC1_ALPHA = 17,
    VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA = 18,
} VkBlendFactor;
VkBlendFactor RGB Blend Factors (Sr,Sg,Sb) or (Dr,Dg,Db) Alpha Blend Factor (Sa or Da)
VK_BLEND_FACTOR_ZERO (0,0,0) 0
VK_BLEND_FACTOR_ONE (1,1,1) 1
VK_BLEND_FACTOR_SRC_COLOR (Rs0,Gs0,Bs0) As0
VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR (1-Rs0,1-Gs0,1-Bs0) 1-As0
VK_BLEND_FACTOR_DST_COLOR (Rd,Gd,Bd) Ad
VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR (1-Rd,1-Gd,1-Bd) 1-Ad
VK_BLEND_FACTOR_SRC_ALPHA (As0,As0,As0) As0
VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA (1-As0,1-As0,1-As0) 1-As0
VK_BLEND_FACTOR_DST_ALPHA (Ad,Ad,Ad) Ad
VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA (1-Ad,1-Ad,1-Ad) 1-Ad
VK_BLEND_FACTOR_CONSTANT_COLOR (Rc,Gc,Bc) Ac
VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR (1-Rc,1-Gc,1-Bc) 1-Ac
VK_BLEND_FACTOR_CONSTANT_ALPHA (Ac,Ac,Ac) Ac
VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA (1-Ac,1-Ac,1-Ac) 1-Ac
VK_BLEND_FACTOR_SRC_ALPHA_SATURATE (f,f,f); f = min(As0,1-Ad) 1
VK_BLEND_FACTOR_SRC1_COLOR (Rs1,Gs1,Bs1) As1
VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR (1-Rs1,1-Gs1,1-Bs1) 1-As1
VK_BLEND_FACTOR_SRC1_ALPHA (As1,As1,As1) As1
VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA (1-As1,1-As1,1-As1) 1-As1

R_{s0},G_{s0},B_{s0} and A_{s0} represent the first source color R, G, B, and A components
R_{s1},G_{s1},B_{s1} and A_{s1} represent the second source color R, G, B, and A components, used in dual source blending modes
R_d,G_d,B_d and A_d represent the R, G, B, and A components of the destination color
R_c,G_c,B_c and A_c represent the blend constant R, G, B, and A components

VkBlendFactor(3)
source colorとして何を指定するのかを入力する。

colorBlendOp
// Provided by VK_VERSION_1_0
typedef enum VkBlendOp {
    VK_BLEND_OP_ADD = 0,
    VK_BLEND_OP_SUBTRACT = 1,
    VK_BLEND_OP_REVERSE_SUBTRACT = 2,
    VK_BLEND_OP_MIN = 3,
    VK_BLEND_OP_MAX = 4,
} VkBlendOp;

VK_EXT_blend_operation_advancedの拡張機能を指定すれば、さらに多くのオプションを選べるようになる。
VkBlendOp(3)
それぞれのblendOpによる計算は以下の通り。

VkBlendOp RGB Components Alpha Component
VK_BLEND_OP_ADD R = R_{s0} × S_r + R_d × D_r
G = G_{s0} × S_g + G_d × D_g
B = B_{s0} × S_b + B_d × D_b
A = A_{s0} × S_a + A_d × D_a
VK_BLEND_OP_SUBTRACT R = R_{s0} × S_r - R_d × D_r
G = G_{s0} × S_g - G_d × D_g
B = B_{s0} × S_b - B_d × D_b
A = A_{s0} × S_a - A_d × D_a
VK_BLEND_OP_REVERSE_SUBTRACT R = R_d × D_r - R_{s0} × S_r
G = G_d × D_g - G_{s0} × S_g
B = B_d × D_b - B_{s0} × S_b
A = A_d × D_a - A_{s0} × S_a
VK_BLEND_OP_MIN R = min(R_{s0},R_d)
G = min(G_{s0},G_d)
B = min(B_{s0},B_d)
A = min(A_{s0},A_d)
VK_BLEND_OP_MAX R = max(R_{s0},R_d)
G = max(G_{s0},G_d)
B = max(B_{s0},B_d)
A = max(A_{s0},A_d)

出典 : Table 1. Basic Blend OperationsVkBlendOp(3)
S : Source Blend Factor
D : Destination Blend Facror

colorWriteMask

VkColorComponentFlagBits - Bitmask controlling which components are written to the framebuffer
Bits which can be set in VkPipelineColorBlendAttachmentState::colorWriteMask to determine whether the final color values R, G, B and A are written to the framebuffer attachment are:

// Provided by VK_VERSION_1_0
typedef enum VkColorComponentFlagBits {
    VK_COLOR_COMPONENT_R_BIT = 0x00000001,
    VK_COLOR_COMPONENT_G_BIT = 0x00000002,
    VK_COLOR_COMPONENT_B_BIT = 0x00000004,
    VK_COLOR_COMPONENT_A_BIT = 0x00000008,
} VkColorComponentFlagBits;

The color write mask operation is applied regardless of whether blending is enabled.
VkColorComponentFlagBits(3)

color maskはどの色が最終的にframe bufferに格納されるかを決める。color maskはblendEnableの値に依らず参照される。もしmaskが指定されていなければ、frame bufferの値は更新されない。

blendConstants

srcColorBlendFactorの欄であるように、constant colorの値を指定する。