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 }