Open3

C-WebGL: Vulkanバックエンドの計画

okuokuokuoku

OpenGLES2でWebGLを実装することはできたので、次にVulkanにバックエンド部分を移植していく。WebGL1には大量のコマンドが存在するが、バックエンドで直接的に実装しなければならないのはわずか 20 コマンド。それに同期機構やらシェーダーの管理やらを付け加えれば絵が出る。。。はず。。。

okuokuokuoku

描画系コマンド

WebGL Vulkan
cwgl_drawArrays, cwgl_drawElements vkCmdDraw
cwgl_clear vkCmdClearColorImage, vkCmdClearDepthStencilImage
cwgl_readPixels vkCmdBlitImage または vkCmdCopyImage してから vkMapMemoryしてコピー
cwgl_renderbufferStorage , cwgl_checkFramebufferStatus vkCreateRenderPass 、 vkCreateFramebuffer と vkCreateImageView
cwgl_finish vkWaitForFences
cwgl_flush なし(明示的に vkQueueSubmit )

実際には、当然 これら以外の drawstate 設定 も移植する必要がある。

オブジェクト

Vulkan-GuideのDecoder ringhttps://www.khronos.org/assets/uploads/developers/library/2016-cedec/Khronos-and-Vulkan-CEDEC-Aug16.pdf にマッピングがあるが、正確な対応はしない。例えば、RenderbufferとTextureは同じ VkImage のフラグ、ビュー違いになる。

WebGL Vulkan
Buffer VkBuffer
Shader VkShaderModule
Program pipline内のShader stageにModuleを入れる
Texture Samplerつきの VkImage
Renderbuffer VkImage
VertexArrayObject (エミュレートする)
okuokuokuoku

Draw

基本的には Dynamic state を可能な限り使用する。ただ、それでも color write enable等拡張に逃がされてしまっているものがあり、モバイルを考えると原則的にコア側でdynamic stateになっていないものは使用できない。

Dynamic stateで表現できないステートが変更された場合は、pipelineを作りなおす必要がある。まぁOpenGLネイティブでもたまにそういう構造を持っているGPUは存在する。

Dynamic state

WebGL Vulkan
glViewport VK_DYNAMIC_STATE_VIEWPORT, vkCmdSetViewport
glLineWidth VK_DYNAMIC_STATE_LINE_WIDTH, vkCmdSetLineWidth
glDepthRangef ★ VK_DYNAMIC_STATE_DEPTH_BOUNDS, vkCmdSetDepthBounds
glPolygonOffset ★ VK_DYNAMIC_STATE_DEPTH_BIAS, vkCmdSetDepthBias
glBlendColor ★ VK_DYNAMIC_STATE_BLEND_CONSTANTS, vkCmdSetBlendConstants
glStencilFuncSeparate ▲ VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK, VK_DYNAMIC_STATE_STENCIL_REFERENCE, vkCmdSetStencilCompareMask, vkCmdSetStencilReference
GL_SCISSOR_TEST, glScissor ● VK_DYNAMIC_STATE_SCISSOR, vkCmdSetScissor
GL_DITHER 無い
glStencilMaskSeparate VK_DYNAMIC_STATE_STENCIL_WRITE_MASK, vkCmdSetStencilWriteMask

★: フラグメントの書き込みを制御(test)する要素は基本的にpipelineの生成時に有効/無効を設定する。このため、dynamic stateではそのパラメタだけを変更できる。

●: ただし Scissor は例外的にそもそもDisableが無いためdynamic stateで完結する。

▲: StencilFuncSeparateではCompare操作も設定できるが、これはpipeline stateなのでdynamicには変更できない。残りのMaskとRefは標準のVulkanで変更できる。

Pipeline state

WebGL Vulkan
プリミティブタイプ VkPipelineInputAssemblyStateCreateInfo + VkPrimitiveTopology
GL_CULL_FACE, glCullFace, glFrontFace VkPipelineRasterizationStateCreateInfo
GL_BLEND, glBlendFuncSeparate, glBlendEquationSeparate VkPipelineColorBlendStateCreateInfo + VkPipelineColorBlendAttachmentState
GL_DEPTH_TEST, glDepthFunc VkPipelineDepthStencilStateCreateInfo
GL_STENCIL_TEST, glStencilOpSeparate, glStencilFuncSeparate VkPipelineDepthStencilStateCreateInfo + VkStencilOpState
GL_POLYGON_OFFSET_FILL VkPipelineRasterizationStateCreateInfo
glColorMask VkPipelineColorBlendAttachmentState
glDepthMask VkPipelineDepthStencilStateCreateInfo
GL_SAMPLE_ALPHA_TO_COVERAGE, glSampleCoverage
GL_SAMPLE_COVERAGE, glSampleCoverage

そもそもVulkanではプリミティブタイプをdynamicに指定できない( VK_EXT_extended_dynamic_state 拡張)

Pipelineには、更にバッファとRenderPassを接続する必要がある。バッファについてはTextureとShaderで後述。

RenderPass

Render pass は出力先であるframebufferとPipelineを接続するために使用する。

Textureで後述。RenderbufferとTextureそれぞれでRenderPassに設定すべき内容が異なる。

描画のキック

WebGL Vulkan
GL_ARRAY_BUFFER, glVertexAttribPointer vkCmdBindVertexBuffers
glDrawArrays vkCmdDraw
GL_ELEMENT_ARRAY_BUFFER, glDrawElements vkCmdBindIndexBuffer, vkCmdDrawIndexed

インデックスを使用した描画には追加のバッファをbindする必要がある。このBindはWebGLでもVulkanでも同様に必要となる。

strideの動的な変更には VK_EXT_extended_dynamic_state が必要。つまり、頂点属性のレイアウト自体はPipeline stateとして事前に VkPipelineVertexInputStateCreateInfo で設定しておく必要がある。

Clear

VulkanのClear操作にはRenderPass外部/内部の2種類がある。WebGL1ではClearは描画の一種になるのでRenderPass内部で処理することになる。

更に、現状のC-WebGLではサポートしていないが preserveDrawingBuffer をFalseに設定することに相当する機能がRenderPass自体に存在し多くのVulkan実装ではそちらが推奨されている。

WebGL Vulkan
glClear vkCmdClearAttachments
glClearColor VkClearColorValue
glClearDepthf, glClearStencil VkClearDepthStencilValue