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 }