AndroidでRay Tracingできた
はじめに
ついにAndroidでリアルタイムray tracingできる時代が来た!ということでハローワールド的にray tracingで立方体を8つ描いた図がこちら。
スマホは最近手に取れるようになった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
${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が小さすぎて何書いているかわからない部分は今後の改善点。
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にも書かれている通り、AndroidのJava側で実行する関数の登録と呼び出しを実装しなければならなさそう。
これらの部分も今後調査して、実装していきたい。
私の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表示をさせている。
あとがき
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)
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は描かれないことがわかる。
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.
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.
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;
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の位置(, )に格納することである。
つまり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.
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 , , or 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.
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 |
and represent the first source color R, G, B, and A components
and represent the second source color R, G, B, and A components, used in dual source blending modes
and represent the R, G, B, and A components of the destination color
and 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 | ||
VK_BLEND_OP_SUBTRACT | ||
VK_BLEND_OP_REVERSE_SUBTRACT | ||
VK_BLEND_OP_MIN | ||
VK_BLEND_OP_MAX |
出典 : Table 1. Basic Blend OperationsVkBlendOp(3)
: Source Blend Factor
: 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の値を指定する。