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 }