1 module sbylib.graphics.core.presenter;
2 
3 import std;
4 import erupted;
5 import sbylib.event;
6 import sbylib.wrapper.glfw : Window;
7 import sbylib.wrapper.freeimage : FIImage = Image, FIImageType = ImageType;
8 import sbylib.wrapper.vulkan;
9 import sbylib.graphics.util.functions;
10 import sbylib.graphics.wrapper;
11 import sbylib.graphics.wrapper : Image;
12 
13 class Presenter {
14 
15     static {
16         private typeof(this)[Window] inst;
17 
18         auto opCall(Window window) {
19             if (window !in inst) {
20                 auto r = inst[window] = new typeof(this)(window);
21                 VDevice().pushReleaseCallback({
22                     r.destroy();
23                     inst.remove(window);
24                 });
25                 return r;
26             }
27             return inst[window];
28         }
29     }
30 
31     private Surface surface;
32     private Swapchain swapchain;
33     private ImageView[] imageViews;
34     private VFence imageIndexAcquireFence;
35     private int currentImageIndex;
36     private Fence[] presentFences;
37     private Window win;
38     private size_t width, height;
39     private VkFormat swapchainFormat;
40 
41     private this(Window window) {
42         with (VDevice()) {
43             this.win = window;
44             this.surface = window.createSurface(instance);
45             this.swapchain = createSwapchain(surface);
46             this.imageIndexAcquireFence = VFence.create("image index acquire fence");
47             this.presentFences = [imageIndexAcquireFence.fence];
48             this.currentImageIndex = -1;
49         }
50         when(Frame(91)).then({
51             present();
52         });
53     }
54 
55     ~this() {
56         if (currentImageIndex != -1) {
57             imageIndexAcquireFence.wait();
58         }
59         this.swapchain.destroy();
60         this.surface.destroy();
61         this.imageIndexAcquireFence.destroy();
62     }
63 
64     public Image[] getSwapchainImages() {
65         return swapchain.getImages();
66     }
67 
68     public void present() 
69         in (currentImageIndex != -1)
70     {
71         Fence.wait(presentFences, true, ulong.max);
72         Queue.PresentInfo presentInfo = {
73             swapchains: [swapchain],
74             imageIndices: [currentImageIndex]
75         };
76         VQueue(VQueue.Type.Graphics).present(presentInfo);
77         imageIndexAcquireFence.reset();
78         currentImageIndex = acquireNextImageIndex();
79     }
80 
81     public void pushPresentFence(Fence fence) {
82         this.presentFences ~= fence;
83     }
84 
85     public int getImageIndex() {
86         if (currentImageIndex == -1) {
87             imageIndexAcquireFence.reset();
88             return currentImageIndex = acquireNextImageIndex();
89         }
90         return currentImageIndex;
91     }
92 
93     public void screenShot(string filename) {
94         with (VDevice()) {
95             auto width = win.width;
96             auto height = win.height;
97             auto dstFormat = VK_FORMAT_R8G8B8A8_UNORM;
98 
99             // Check if the device supports blitting from optimal images (the swapchain images are in optimal format)
100             bool supportsBlit =
101                    gpu.getFormatProperties(swapchainFormat).optimalTiling.supports(FormatProperties.Flags.BlitSrc)
102                 && gpu.getFormatProperties(dstFormat).linearTiling.supports(FormatProperties.Flags.BlitDst);
103 
104             // Source for the copy is the last rendered swapchain image
105             auto srcImage = swapchain.getImages()[currentImageIndex];
106 
107             // Create the linear tiled destination image to copy to and to read the memory from
108             // Note that vkCmdBlitImage (if supported) will also do format conversions if the swapchain color format would differ
109             Image.CreateInfo imageCreateCI = {
110                 imageType: ImageType.Type2D,
111                 format: dstFormat,
112                 extent: {
113                     width: width,
114                     height: height,
115                     depth: 1,
116                 },
117                 arrayLayers: 1,
118                 mipLevels: 1,
119                 initialLayout: ImageLayout.Undefined,
120                 samples: SampleCount.Count1,
121                 tiling: ImageTiling.Linear,
122                 usage: ImageUsage.TransferDst,
123             };
124             // Create the image
125             auto dstImage = new VImage(new Image(device, imageCreateCI), 
126                   MemoryProperties.MemoryType.Flags.HostVisible
127                 | MemoryProperties.MemoryType.Flags.HostCoherent);
128             scope (exit) dstImage.destroy();
129 
130             // Do the actual blit from the swapchain image to our host visible destination image
131             auto copyCmd = VCommandBuffer.allocate(VCommandBuffer.Type.Graphics);
132             scope (exit) copyCmd.destroy();
133 
134             with (copyCmd(CommandBuffer.BeginInfo.Flags.OneTimeSubmit)) {
135                 // Transition destination image to transfer destination layout
136                 VkImageMemoryBarrier barrier0 = {
137                     srcAccessMask: 0,
138                     dstAccessMask: AccessFlags.TransferWrite,
139                     oldLayout: ImageLayout.Undefined,
140                     newLayout: ImageLayout.TransferDstOptimal,
141                     image: dstImage.image.image,
142                     subresourceRange: {
143                         aspectMask: ImageAspect.Color,
144                         baseMipLevel: 0,
145                         levelCount: 1,
146                         baseArrayLayer: 0,
147                         layerCount: 1
148                     }
149                 };
150                 cmdPipelineBarrier(PipelineStage.Transfer, PipelineStage.Transfer, 0, null, null, [barrier0]);
151 
152                 // Transition swapchain image from present to transfer source layout
153                 VkImageMemoryBarrier barrier1 = {
154                     srcAccessMask: AccessFlags.MemoryRead,
155                     dstAccessMask: AccessFlags.TransferRead,
156                     oldLayout: ImageLayout.PresentSrc,
157                     newLayout: ImageLayout.TransferSrcOptimal,
158                     image: srcImage.image,
159                     subresourceRange: {
160                         aspectMask: ImageAspect.Color,
161                         baseMipLevel: 0,
162                         levelCount: 1,
163                         baseArrayLayer: 0,
164                         layerCount: 1
165                     }
166                 };
167                 cmdPipelineBarrier(PipelineStage.Transfer, PipelineStage.Transfer, 0, null, null, [barrier1]);
168 
169                 // If source and destination support blit we'll blit as this also does automatic format conversion (e.g. from BGR to RGB)
170                 if (supportsBlit) {
171                     // Define the region to blit (we will blit the whole swapchain image)
172                     VkImageBlit imageBlitRegion = {
173                         srcSubresource: {
174                             aspectMask: ImageAspect.Color,
175                             layerCount: 1,
176                         },
177                         dstSubresource: {
178                             aspectMask: ImageAspect.Color,
179                             layerCount: 1,
180                         },
181                         srcOffsets: [{}, {
182                             x: width,
183                             y: height,
184                             z: 1,
185                         }],
186                         dstOffsets: [{}, {
187                             x: width,
188                             y: height,
189                             z: 1,
190                         }],
191                     };
192 
193                     // Issue the blit command
194                     cmdBlitImage(
195                         srcImage, ImageLayout.TransferSrcOptimal,
196                         dstImage.image, ImageLayout.TransferDstOptimal,
197                         [imageBlitRegion], SamplerFilter.Nearest);
198                 } else {
199                     // Otherwise use image copy (requires us to manually flip components)
200                     VkImageCopy imageCopyRegion = {
201                         srcSubresource: {
202                             aspectMask: ImageAspect.Color,
203                             layerCount: 1,
204                         },
205                         dstSubresource: {
206                             aspectMask: ImageAspect.Color,
207                             layerCount: 1,
208                         },
209                         extent: {
210                             width: width,
211                             height: height,
212                             depth: 1,
213                         }
214                     };
215 
216                     // Issue the copy command
217                     cmdCopyImage(
218                         srcImage, ImageLayout.TransferSrcOptimal,
219                         dstImage.image, ImageLayout.TransferDstOptimal,
220                         [imageCopyRegion]);
221                 }
222 
223                 // Transition destination image to general layout, which is the required layout for mapping the image memory later on
224                 VkImageMemoryBarrier barrier2 = {
225                     srcAccessMask: AccessFlags.TransferWrite,
226                     dstAccessMask: AccessFlags.MemoryRead,
227                     oldLayout: ImageLayout.TransferDstOptimal,
228                     newLayout: ImageLayout.General,
229                     image: dstImage.image.image,
230                     subresourceRange: {
231                         aspectMask: ImageAspect.Color,
232                         baseMipLevel: 0,
233                         levelCount: 1,
234                         baseArrayLayer: 0,
235                         layerCount: 1
236                     }
237                 };
238                 cmdPipelineBarrier(PipelineStage.Transfer, PipelineStage.Transfer, 0, null, null, [barrier2]);
239 
240                 // Transition back the swap chain image after the blit is done
241                 VkImageMemoryBarrier barrier3 = {
242                     srcAccessMask: AccessFlags.TransferRead,
243                     dstAccessMask: AccessFlags.MemoryRead,
244                     oldLayout: ImageLayout.TransferSrcOptimal,
245                     newLayout: ImageLayout.PresentSrc,
246                     image: srcImage.image,
247                     subresourceRange: {
248                         aspectMask: ImageAspect.Color,
249                         baseMipLevel: 0,
250                         levelCount: 1,
251                         baseArrayLayer: 0,
252                         layerCount: 1
253                     }
254                 };
255                 cmdPipelineBarrier(PipelineStage.Transfer, PipelineStage.Transfer, 0, null, null, [barrier3]);
256             }
257 
258             with (VQueue(VQueue.Type.Graphics)) {
259                 submit(copyCmd);
260                 waitIdle();
261             }
262 
263             // Get layout of the image (including row pitch)
264             VkImageSubresource subResource = { 
265                 aspectMask: VK_IMAGE_ASPECT_COLOR_BIT,
266                 mipLevel: 0,
267                 arrayLayer: 0
268             };
269             auto subResourceLayout = dstImage.image.getSubresourceLayout(subResource);
270 
271             // Map image memory so we can start copying from it
272             auto data = dstImage.memory.mapWhole(0, 0);
273             data += subResourceLayout.offset;
274 
275             auto img = new FIImage(width, height, 32);
276             img.dataArray[] = data[0..img.dataArray.length][];
277             img.flipVertical();
278             img.save(filename);
279 
280             // Clean up resources
281             dstImage.memory.unmap();
282         }
283     }
284 
285     private Swapchain createSwapchain(Surface surface) {
286         import erupted : VK_FORMAT_B8G8R8A8_UNORM;
287 
288         with (VDevice()) {
289             enforce(gpu.getSurfaceSupport(surface));
290             const surfaceCapabilities = gpu.getSurfaceCapabilities(surface);
291 
292             const surfaceFormats = gpu.getSurfaceFormats(surface);
293             const surfaceFormatCandidates = surfaceFormats.find!(f => f.format == VK_FORMAT_B8G8R8A8_UNORM);
294             enforce(surfaceFormatCandidates.empty is false, "Proper surface formats are not found.");
295             const surfaceFormat = surfaceFormatCandidates.front;
296             this.swapchainFormat = surfaceFormat.format;
297 
298             Swapchain.CreateInfo swapchainCreateInfo = {
299                 surface: surface,
300                 minImageCount: surfaceCapabilities.minImageCount,
301                 imageFormat: surfaceFormat.format,
302                 imageColorSpace: surfaceFormat.colorSpace,
303                 imageExtent: surfaceCapabilities.currentExtent,
304                 imageArrayLayers: 1,
305                 imageUsage: ImageUsage.ColorAttachment | ImageUsage.TransferSrc, // for screenshot
306                 imageSharingMode: SharingMode.Exclusive,
307                 compositeAlpha: CompositeAlpha.Opaque,
308                 preTransform: SurfaceTransform.Identity,
309                 presentMode: PresentMode.FIFO,
310                 clipped: true,
311             };
312             enforce(surfaceCapabilities.supports(swapchainCreateInfo.imageUsage));
313             enforce(surfaceCapabilities.supports(swapchainCreateInfo.compositeAlpha));
314             enforce(surfaceCapabilities.supports(swapchainCreateInfo.preTransform));
315             enforce(gpu.getSurfacePresentModes(surface).canFind(swapchainCreateInfo.presentMode));
316 
317             return pushResource(new Swapchain(device, swapchainCreateInfo));
318         }
319     }
320 
321     private uint acquireNextImageIndex() {
322         return swapchain.acquireNextImageIndex(ulong.max, null, imageIndexAcquireFence);
323     }
324 }