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