1 module whiteTriangle.entry; 2 3 import std; 4 import std.file : fremove = remove; 5 import erupted; 6 import erupted.vulkan_lib_loader; 7 import sbylib.wrapper.vulkan; 8 import sbylib.wrapper.vulkan.util : compileShader; 9 import sbylib.wrapper.glfw : GLFW, Window, WindowBuilder, ClientAPI; 10 11 void entryPoint() { 12 /* 13 GLFW初期化 14 */ 15 GLFW.initialize(); 16 scope (exit) 17 GLFW.terminate(); 18 19 20 /* 21 Window作成 22 */ 23 Window window; 24 with (WindowBuilder()) { 25 width = 800; 26 height = 600; 27 title = "title"; 28 clientAPI = ClientAPI.NoAPI; 29 resizable = false; 30 window = buildWindow(); 31 } 32 scope (exit) 33 window.destroy(); 34 35 36 /* 37 GLFWのVulkanサポートチェック 38 */ 39 assert(GLFW.hasVulkanSupport()); 40 41 42 /* 43 VulkanのGlobal Functionを読み込む 44 */ 45 const globalFunctionLoaded = loadGlobalLevelFunctions(); 46 assert(globalFunctionLoaded); 47 48 49 /* 50 Intanceの作成 51 52 Validation Layerを有効化する場合はここで設定する。 53 (ただし利用可能かどうかチェックする) 54 55 GLFWが要求する拡張機能も有効化する。 56 57 */ 58 Instance instance; 59 { 60 Instance.CreateInfo instanceCreateInfo = { 61 applicationInfo: { 62 applicationName: "Vulkan Test", 63 applicationVersion: VK_MAKE_VERSION(1,0,0), 64 engineName: "No Engine", 65 engineVersion: VK_MAKE_VERSION(1,0,0), 66 apiVersion : VK_API_VERSION_1_0 67 }, 68 enabledLayerNames: [ 69 "VK_LAYER_LUNARG_standard_validation", 70 "VK_LAYER_KHRONOS_validation", 71 ], 72 enabledExtensionNames: GLFW.getRequiredInstanceExtensions() 73 }; 74 instance = new Instance(instanceCreateInfo); 75 enforce(instanceCreateInfo.enabledLayerNames.all!(n => 76 LayerProperties.getAvailableInstanceLayerProperties().canFind!(l => l.layerName == n))); 77 } 78 scope (exit) 79 instance.destroy(); 80 81 82 /* 83 Surface作成 84 85 SurfaceとはVulkanのAPIとWindow Systemをつなぐものらしい 86 */ 87 auto surface = window.createSurface(instance); 88 scope (exit) 89 surface.destroy(); 90 91 92 /* 93 GPUの選択 94 95 GPUはPhysical DeviceとVulkan内では呼ばれている。 96 利用可能なGPUの中で、Surface機能をサポートしているものを採用。 97 */ 98 auto gpu = instance.findPhysicalDevice!((PhysicalDevice gpu) => gpu.getSurfaceSupport(surface)); 99 100 101 /* 102 Queue Familyの選択 103 104 Vulkanの命令の多くは 105 1. Command Bufferに命令を記録 106 2. Command BufferをQueueに追加 107 というプロセスをたどる。 108 Command Bufferに記録する命令には"Supported Queue Type"とかいう属性が1つ定められており、 109 Queueに突っ込む際はその属性をサポートしているQueueに突っ込まなければならない。 110 Queue Familyというのは要はこのQueueの種類のことであり、今回はGraphics属性をサポートしているQueueを採用する。 111 */ 112 auto graphicsQueueFamilyIndex = gpu.findQueueFamilyIndex!(prop => 113 prop.supports(QueueFamilyProperties.Flags.Graphics)); 114 115 116 /* 117 Deviceの作成 118 119 選択した"物理的な"Deviceを基に"論理的な"Deviceを作成する 120 DeviceにもInstanceと同様、LayerとExtensionが付けられる。 121 今回はSwapchainのExtensionのみ有効化する。 122 ついでに先程選んだQueue FamilyでQueueを1つ作っておく。 123 */ 124 Device.DeviceCreateInfo deviceCreateInfo = { 125 queueCreateInfos: [{ 126 queuePriorities: [0.0f], 127 queueFamilyIndex: graphicsQueueFamilyIndex, 128 }], 129 enabledExtensionNames: ["VK_KHR_swapchain"] 130 }; 131 auto device = new Device(gpu, deviceCreateInfo); 132 scope (exit) 133 device.destroy(); 134 135 auto queue = device.getQueue(graphicsQueueFamilyIndex, 0); 136 137 138 /* 139 Command Poolの作成 140 141 Command PoolとはCommand Bufferを確保する元の領域らしい。 142 flagsにTransient, ResetCommandBuffer, Protectedを0個以上選んで入れられる。 143 144 Transient: 145 このCommand Poolから確保されるCommand Bufferは短命(比較的すぐResetされるか解放される)。 146 メモリ確保の戦略に使われるんじゃね?とのこと。 147 ResetCommandBuffer: 148 このCommand Poolから確保されるCommand Bufferは個別にReset可能になる。 149 ResetはvkResetCommandBufferかvkBeginCommandBufferでできる。 150 逆にこのフラグ立てずに上記命令を呼び出してはいけない(vkResetCommandPoolなる命令でまとめてやる)。 151 Protected: 152 このCommand Poolから確保されるCommand Bufferは(Memory) Protectedになる。 153 Protectedになると、よくないメモリアクセスでちゃんと怒ってくられるようになるっぽい。 154 155 */ 156 CommandPool commandPool; 157 { 158 CommandPool.CreateInfo commandPoolCreateInfo = { 159 flags: CommandPool.CreateInfo.Flags.ResetCommandBuffer 160 | CommandPool.CreateInfo.Flags.Protected, 161 queueFamilyIndex: graphicsQueueFamilyIndex 162 }; 163 commandPool = new CommandPool(device, commandPoolCreateInfo); 164 } 165 scope (exit) 166 commandPool.destroy(); 167 168 169 /* 170 Swapchainの作成 171 172 Swapchainとはスクリーンに表示されるImageのQueueみたいなもん。 173 Swapchainを作るとき、内部に含まれるImageも併せて作成される。 174 presentModeは表示(同期)の方法。 175 176 Immediate: 177 垂直同期すらせず、来た瞬間表示 178 Mailbox: 179 垂直同期をする。 180 内部的に用意された容量1のQueueを用い、画像が来たら溜め込み、垂直同期の瞬間が来たら取り出して表示する。 181 画像が既にあるのに来ちゃったら古いほうを破棄。 182 FIFO: 183 Mailboxの容量いっぱい版。 184 FIFORelaxed: 185 FIFO + 「垂直同期のタイミングなのに次の画像がまだ来ていなかった場合、次のが来次第すぐ表示」。 186 SharedDemandRefresh: 187 表示エンジンとアプリケーションの双方からの並列なImageへのアクセスを許可する。 188 アプリケーションはアクセスしているImageの更新が必要なときは必ず更新リクエストを投げなければならない。 189 表示タイミングについてはいつかわからない。 190 SharedContinuousRefresh 191 表示エンジンとアプリケーションの双方からの並列なImageへのアクセスを許可する。 192 Imageの更新は通常のサイクルと同様に周期的に起きる。 193 アプリケーションは一番最初だけ更新リクエストを投げなければならない。 194 */ 195 const surfaceCapabilities = gpu.getSurfaceCapabilities(surface); 196 197 const surfaceFormats = gpu.getSurfaceFormats(surface); 198 const surfaceFormat = surfaceFormats.find!(f => f.format == VK_FORMAT_B8G8R8A8_UNORM).front; 199 200 Swapchain.CreateInfo swapchainCreateInfo = { 201 surface: surface, 202 minImageCount: surfaceCapabilities.minImageCount, 203 imageFormat: surfaceFormat.format, 204 imageColorSpace: surfaceFormat.colorSpace, 205 imageExtent: surfaceCapabilities.currentExtent, 206 imageArrayLayers: 1, 207 imageUsage: ImageUsage.ColorAttachment, 208 imageSharingMode: SharingMode.Exclusive, 209 compositeAlpha: CompositeAlpha.Opaque, 210 preTransform: SurfaceTransform.Identity, 211 presentMode: PresentMode.FIFO, 212 clipped: true, 213 }; 214 enforce(surfaceCapabilities.supports(swapchainCreateInfo.imageUsage)); 215 enforce(surfaceCapabilities.supports(swapchainCreateInfo.compositeAlpha)); 216 enforce(surfaceCapabilities.supports(swapchainCreateInfo.preTransform)); 217 enforce(gpu.getSurfacePresentModes(surface).canFind(swapchainCreateInfo.presentMode)); 218 219 auto swapchain = new Swapchain(device, swapchainCreateInfo); 220 scope (exit) 221 swapchain.destroy(); 222 223 224 /* 225 SwapchainからImageViewを作成 226 227 ImageViewとはその名の通り、Swapchain内のImageに対するView(Slice)である。 228 */ 229 auto swapchainImageViews = swapchain.getImages().map!((Image image) { 230 ImageView.CreateInfo info = { 231 image: image, 232 viewType: ImageViewType.Type2D, 233 format: surfaceFormat.format, 234 subresourceRange: { 235 aspectMask: ImageAspect.Color, 236 baseMipLevel: 0, 237 levelCount: 1, 238 baseArrayLayer: 0, 239 layerCount: 1, 240 } 241 }; 242 return new ImageView(device, info); 243 }).array; 244 scope (exit) 245 foreach (imageView; swapchainImageViews) 246 imageView.destroy(); 247 248 249 /* 250 RenderPassの作成 251 252 RenderPassはFramebufferをどうやって処理するかという設定集。 253 設定のみで、具体的な処理は書いてない。 254 255 RenderPassはAttachmentとSubpassとDependencyから成る。 256 257 Attachmentは計算中に現れる資源がどんなもんかという設定。 258 ここではAttachmentはSwapchain内に作ったImage。 259 loadOp, storeOpは計算の開始/終了時にデータをどう処理するかを書く。 260 各資源にはlayoutなる状態が定義されているらしく、開始/終了時のlayoutを決めておく。 261 資源の終了時のLayoutは必ずPRESENT_SRCにしておかないと描画(present)できないっぽい。 262 263 Subpassは計算中に資源をどういうふうに使うかの設定。 264 「Attachment0番をCOLOR_ATTACH_MENT_OPTIMALレイアウト状態でcolor attachmentとして使う」というSubpassを1つ定義している。 265 266 DependencyはSubpass間の資源の依存性に関する設定。 267 ここでは書いていない。(Subpassが1つしかないから依存もクソもない) 268 */ 269 RenderPass.CreateInfo renderPassCreateInfo = { 270 attachments: [{ 271 format: surfaceFormat.format, 272 samples: SampleCount.Count1, 273 loadOp: AttachmentLoadOp.Clear, 274 storeOp: AttachmentStoreOp.Store, 275 initialLayout: ImageLayout.Undefined, 276 finalLayout: ImageLayout.PresentSrc, 277 }], 278 subpasses: [{ 279 pipelineBindPoint: PipelineBindPoint.Graphics, 280 colorAttachments: [{ 281 attachment: 0, 282 layout: ImageLayout.ColorAttachmentOptimal 283 }] 284 }] 285 }; 286 auto renderPass = new RenderPass(device, renderPassCreateInfo); 287 scope (exit) 288 renderPass.destroy(); 289 290 291 /* 292 Framebufferの作成 293 294 いつもの。 295 */ 296 auto framebuffers = swapchainImageViews.map!((imageView) { 297 Framebuffer.CreateInfo info = { 298 renderPass: renderPass, 299 attachments: [imageView], 300 width: 800, 301 height: 600, 302 layers: 1, 303 }; 304 return new Framebuffer(device, info); 305 }).array; 306 scope (exit) 307 foreach (framebuffer; framebuffers) 308 framebuffer.destroy(); 309 310 311 /* 312 CommandBufferの確保 313 314 さっきつくったCommandPoolからCommandBufferを確保する。 315 確保とかいうからサイズとかを指定するかと思ったけど、そうでもないっぽい。 316 一気にいっぱい確保できるが、とりあえず1個確保。 317 */ 318 CommandBuffer.AllocateInfo commandbufferAllocInfo = { 319 commandPool: commandPool, 320 level: CommandBufferLevel.Primary, 321 commandBufferCount: 1, 322 }; 323 auto commandBuffer = CommandBuffer.allocate(device, commandbufferAllocInfo)[0]; 324 scope (exit) 325 commandBuffer.destroy(); 326 327 328 /* 329 頂点データの作成 330 331 好きにやればいいっぽい。 332 */ 333 struct VertexData { 334 float[2] pos; 335 } 336 337 static VertexData[3] vertices = [ 338 VertexData([ 0.0f, -0.5f]), 339 VertexData([+0.5f, +0.5f]), 340 VertexData([-0.5f, +0.5f]), 341 ]; 342 343 344 /* 345 Bufferの作成 346 347 今回の用途は当然Vertex Buffer。 348 */ 349 Buffer.CreateInfo bufferInfo = { 350 usage: BufferUsage.VertexBuffer, 351 size: vertices.sizeof, 352 sharingMode: SharingMode.Exclusive, 353 }; 354 auto buffer = new Buffer(device, bufferInfo); 355 scope (exit) 356 buffer.destroy(); 357 358 359 /* 360 Device Memory確保 361 362 Vertex Buffer用のメモリ確保。 363 */ 364 const requirements = device.getBufferMemoryRequirements(buffer); 365 DeviceMemory.AllocateInfo deviceMemoryAllocInfo = { 366 allocationSize: requirements.size, 367 memoryTypeIndex: cast(uint)gpu.getMemoryProperties().memoryTypes.enumerate 368 .countUntil!(p => requirements.acceptable(p.index)) 369 }; 370 enforce(deviceMemoryAllocInfo.memoryTypeIndex != -1); 371 auto deviceMemory = new DeviceMemory(device, deviceMemoryAllocInfo); 372 scope (exit) 373 deviceMemory.destroy(); 374 375 376 /* 377 Device Memoryへのデータ転送 378 */ 379 auto mappedMemory = deviceMemory.map(0, bufferInfo.size, 0); 380 mappedMemory[] = (cast(ubyte[])vertices)[]; 381 deviceMemory.unmap(); 382 383 384 /* 385 Device MemoryとBufferの紐づけ 386 */ 387 deviceMemory.bindBuffer(buffer, 0); 388 389 390 /* 391 Shader Module作成 392 */ 393 compileShader(__FILE_FULL_PATH__.dirName.buildPath("test.vert")); 394 scope (exit) fremove("vert.spv"); 395 396 compileShader(__FILE_FULL_PATH__.dirName.buildPath("test.frag")); 397 scope (exit) fremove("frag.spv"); 398 399 ShaderModule.CreateInfo vsShaderCreateInfo = { 400 code: cast(ubyte[])read("vert.spv") 401 }; 402 auto vsMod = new ShaderModule(device, vsShaderCreateInfo); 403 scope (exit) 404 vsMod.destroy(); 405 406 ShaderModule.CreateInfo fsShaderCreateInfo = { 407 code: cast(ubyte[])read("frag.spv") 408 }; 409 auto fsMod = new ShaderModule(device, fsShaderCreateInfo); 410 scope (exit) 411 fsMod.destroy(); 412 413 414 /* 415 PipelineLayout作成 416 417 Pipelineが使う資源のレイアウトに関する情報らしい。 418 今回は虚無。 419 */ 420 PipelineLayout.CreateInfo pipelineLayoutCreateInfo; 421 auto pipelineLayout = new PipelineLayout(device, pipelineLayoutCreateInfo); 422 scope (exit) 423 pipelineLayout.destroy(); 424 425 426 /* 427 Pipeline作成 428 429 Pipelineとは、その名の通り描画パイプラインそのもの。 430 設定自体は長いが、全部自明。 431 */ 432 Pipeline.GraphicsCreateInfo pipelineCreateInfo = { 433 stages: [{ 434 stage: ShaderStage.Vertex, 435 _module: vsMod, 436 pName: "main", 437 }, { 438 stage: ShaderStage.Fragment, 439 _module: fsMod, 440 pName: "main", 441 }], 442 vertexInputState: { 443 vertexBindingDescriptions: [{ 444 binding: 0, 445 stride: (float[2]).sizeof, 446 inputRate: VertexInputRate.Vertex 447 }], 448 vertexAttributeDescriptions: [{ 449 location: 0, 450 binding: 0, 451 format: VK_FORMAT_R32G32_SFLOAT, 452 offset: 0 453 }] 454 }, 455 inputAssemblyState: { 456 topology: PrimitiveTopology.TriangleList, 457 }, 458 viewportState: { 459 viewports: [{ 460 x: 0.0f, 461 y: 0.0f, 462 width: window.width, 463 height: window.height, 464 minDepth: 0.0f, 465 maxDepth: 1.0f 466 }], 467 scissors: [{ 468 offset: { 469 x: 0, 470 y: 0 471 }, 472 extent: { 473 width: window.width, 474 height: window.height 475 } 476 }] 477 }, 478 rasterizationState: { 479 depthClampEnable: false, 480 rasterizerDiscardEnable: false, 481 polygonMode: PolygonMode.Fill, 482 cullMode: CullMode.None, 483 frontFace: FrontFace.CounterClockwise, 484 depthBiasEnable: false, 485 depthBiasConstantFactor: 0.0f, 486 depthBiasClamp: 0.0f, 487 depthBiasSlopeFactor: 0.0f, 488 lineWidth: 1.0f, 489 }, 490 multisampleState: { 491 rasterizationSamples: SampleCount.Count1, 492 sampleShadingEnable: false, 493 alphaToCoverageEnable: false, 494 alphaToOneEnable: false, 495 }, 496 colorBlendState: { 497 logicOpEnable: false, 498 attachments: [{ 499 blendEnable: false, 500 colorWriteMask: ColorComponent.R 501 | ColorComponent.G 502 | ColorComponent.B 503 | ColorComponent.A, 504 }] 505 }, 506 layout: pipelineLayout, 507 renderPass: renderPass, 508 subpass: 0, 509 }; 510 auto pipeline = Pipeline.create(device, [pipelineCreateInfo])[0]; 511 scope (exit) 512 pipeline.destroy(); 513 514 515 /* 516 Fenceの作成 517 518 Fenceとは同期用のアイテムで、Vulkanにおける非同期命令はだいたいこのFenceと一緒に投げて、Fenceを使って待つ。 519 */ 520 Fence.CreateInfo fenceCreatInfo; 521 auto fence = new Fence(device, fenceCreatInfo); 522 scope (exit) 523 fence.destroy(); 524 525 526 /* 527 Image Indexの取得 528 529 Swapchain内のImageの何番を次叩くべきかを得る。 530 Fenceと一緒に投げる理由は、返ってくるIndexのImageが解放されているかがわからないためらしい。 531 Fenceは使い終わったら必ずResetすること。 532 */ 533 auto currentImageIndex = swapchain.acquireNextImageIndex(ulong.max, null, fence); 534 Fence.wait([fence], false, ulong.max); 535 Fence.reset([fence]); 536 537 538 while (window.shouldClose is false) { 539 auto framebuffer = framebuffers[currentImageIndex]; 540 541 /* 542 Command Bufferへの記録 543 544 CommandBufferへCommandを記録するときは必ずbeginしてendする。 545 ここではRenderPassの実行を記録している。 546 RenderPassの実行は 547 1. PipelineのBind (OpenGLでいうusePipeline) 548 2. Vertex BufferのBind (OpenGLでいうbindBuffer) 549 3. Draw call (OpenGLでいうdrawArrays) 550 となっている。 551 */ 552 CommandBuffer.BeginInfo beginInfo; 553 commandBuffer.begin(beginInfo); 554 555 CommandBuffer.RenderPassBeginInfo renderPassBeginInfo = { 556 renderPass: renderPass, 557 framebuffer: framebuffers[currentImageIndex], 558 renderArea: { 559 extent: VkExtent2D(window.width, window.height) 560 }, 561 clearValues: [{ 562 color: { 563 float32: [0.0f, 0.0f, 0.0f, 1.0f] 564 } 565 }] 566 }; 567 commandBuffer.cmdBeginRenderPass(renderPassBeginInfo, SubpassContents.Inline); 568 commandBuffer.cmdBindPipeline(PipelineBindPoint.Graphics, pipeline); 569 commandBuffer.cmdBindVertexBuffers(0, [buffer], [0]); 570 commandBuffer.cmdDraw(3, 1, 0, 0); 571 commandBuffer.cmdEndRenderPass(); 572 commandBuffer.end(); 573 574 575 /* 576 QueueへのCommand Bufferの投函 577 578 これも完了を待っている。 579 */ 580 Queue.SubmitInfo submitInfo = { 581 commandBuffers: [commandBuffer] 582 }; 583 queue.submit([submitInfo], fence); 584 Fence.wait([fence], true, ulong.max); 585 Fence.reset([fence]); 586 587 588 /* 589 画面への反映 590 */ 591 Queue.PresentInfo presentInfo = { 592 swapchains: [swapchain], 593 imageIndices: [currentImageIndex] 594 }; 595 queue.present(presentInfo); 596 597 598 /* 599 次の Image Indexの取得 600 */ 601 currentImageIndex = swapchain.acquireNextImageIndex(ulong.max, null, fence); 602 Fence.wait([fence], true, ulong.max); 603 Fence.reset([fence]); 604 605 606 /* 607 GLFWのEvent Polling 608 */ 609 GLFW.pollEvents(); 610 } 611 }