1 module swapchain; 2 3 import core.time : Duration; 4 5 import gfx.core.rc; 6 import gfx.graal; 7 import gfx.graal.cmd; 8 import gfx.graal.device; 9 import gfx.graal.queue; 10 import gfx.graal.sync; 11 import gfx.vulkan; 12 import gfx.window; 13 14 import std.algorithm; 15 import std.exception; 16 import std.stdio; 17 import std.typecons; 18 19 class SwapchainExample : Disposable 20 { 21 Rc!Instance instance; 22 Window window; 23 uint graphicsQueueIndex; 24 uint presentQueueIndex; 25 Rc!PhysicalDevice physicalDevice; 26 Rc!Device device; 27 Queue graphicsQueue; 28 Queue presentQueue; 29 uint[2] surfaceSize; 30 bool hasAlpha; 31 Rc!Swapchain swapchain; 32 Image[] scImages; 33 Rc!Semaphore imageAvailableSem; 34 Rc!Semaphore renderingFinishSem; 35 Rc!CommandPool presentPool; 36 CommandBuffer[] presentCmdBufs; 37 38 39 void prepare() { 40 // initialize vulkan library 41 vulkanInit(); 42 // create a vulkan instance 43 instance = createVulkanInstance("Triangle", VulkanVersion(0, 0, 1)).rc; 44 // create a window for the running platform 45 // the window surface is created during this process 46 window = createWindow(instance); 47 48 // the rest of the preparation 49 prepareDevice(); 50 prepareSwapchain(null); 51 prepareSync(); 52 prepareCmds(); 53 recordCmds(); 54 } 55 56 void prepareDevice() 57 { 58 bool checkDevice(PhysicalDevice dev) { 59 graphicsQueueIndex = uint.max; 60 presentQueueIndex = uint.max; 61 if (dev.softwareRendering) return false; 62 foreach (uint i, qf; dev.queueFamilies) { 63 const graphics = qf.cap & QueueCap.graphics; 64 const present = dev.supportsSurface(i, window.surface); 65 if (graphics && present) { 66 graphicsQueueIndex = i; 67 presentQueueIndex = i; 68 return true; 69 } 70 if (graphics) graphicsQueueIndex = i; 71 if (present) presentQueueIndex = i; 72 } 73 return graphicsQueueIndex != uint.max && presentQueueIndex != uint.max; 74 } 75 foreach (pd; instance.devices) { 76 if (checkDevice(pd)) { 77 auto qrs = [ QueueRequest(graphicsQueueIndex, [ 0.5f ]) ]; 78 if (graphicsQueueIndex != presentQueueIndex) { 79 qrs ~= QueueRequest(presentQueueIndex, [ 0.5f ]); 80 } 81 physicalDevice = pd; 82 device = pd.open(qrs); 83 graphicsQueue = device.getQueue(graphicsQueueIndex, 0); 84 presentQueue = device.getQueue(presentQueueIndex, 0); 85 break; 86 } 87 } 88 } 89 90 void prepareSwapchain(Swapchain former) { 91 const surfCaps = physicalDevice.surfaceCaps(window.surface); 92 enforce(surfCaps.usage & ImageUsage.transferDst, "TransferDst not supported by surface"); 93 const usage = ImageUsage.transferDst | ImageUsage.colorAttachment; 94 const numImages = max(2, surfCaps.minImages); 95 enforce(surfCaps.maxImages == 0 || surfCaps.maxImages >= numImages); 96 const f = chooseFormat(physicalDevice, window.surface); 97 hasAlpha = (surfCaps.supportedAlpha & CompositeAlpha.preMultiplied) == CompositeAlpha.preMultiplied; 98 const ca = hasAlpha ? CompositeAlpha.preMultiplied : CompositeAlpha.opaque; 99 surfaceSize = [ 640, 480 ]; 100 foreach (i; 0..2) { 101 surfaceSize[i] = clamp(surfaceSize[i], surfCaps.minSize[i], surfCaps.maxSize[i]); 102 } 103 const pm = choosePresentMode(physicalDevice, window.surface); 104 105 swapchain = device.createSwapchain(window.surface, pm, numImages, f, surfaceSize, usage, ca, former); 106 scImages = swapchain.images; 107 } 108 109 void prepareSync() { 110 imageAvailableSem = device.createSemaphore(); 111 renderingFinishSem = device.createSemaphore(); 112 } 113 114 void prepareCmds() { 115 presentPool = device.createCommandPool(presentQueueIndex); 116 presentCmdBufs = presentPool.allocate(scImages.length); 117 } 118 119 void recordCmds() { 120 121 import gfx.core.typecons : trans; 122 123 const clearValues = ClearColorValues(0.6f, 0.6f, 0.6f, hasAlpha ? 0.5f : 1f); 124 auto subrange = ImageSubresourceRange(ImageAspect.color, 0, 1, 0, 1); 125 126 foreach (i, buf; presentCmdBufs) { 127 buf.begin(Yes.persistent); 128 129 buf.pipelineBarrier( 130 trans(PipelineStage.transfer, PipelineStage.transfer), [], 131 [ ImageMemoryBarrier( 132 trans(Access.memoryRead, Access.transferWrite), 133 trans(ImageLayout.undefined, ImageLayout.transferDstOptimal), 134 trans(graphicsQueueIndex, presentQueueIndex), 135 scImages[i], subrange 136 ) ] 137 ); 138 139 buf.clearColorImage( 140 scImages[i], ImageLayout.transferDstOptimal, clearValues, [ subrange ] 141 ); 142 143 buf.pipelineBarrier( 144 trans(PipelineStage.transfer, PipelineStage.transfer), [], 145 [ ImageMemoryBarrier( 146 trans(Access.transferWrite, Access.memoryRead), 147 trans(ImageLayout.transferDstOptimal, ImageLayout.presentSrc), 148 trans(graphicsQueueIndex, presentQueueIndex), 149 scImages[i], subrange 150 ) ] 151 ); 152 153 buf.end(); 154 } 155 } 156 157 void render() 158 { 159 import core.time : dur; 160 161 bool needReconstruction; 162 const imgInd = swapchain.acquireNextImage(dur!"seconds"(-1), imageAvailableSem, needReconstruction); 163 164 presentQueue.submit([ 165 Submission ( 166 [ StageWait(imageAvailableSem, PipelineStage.transfer) ], 167 [ renderingFinishSem ], [ presentCmdBufs[imgInd] ] 168 ) 169 ], null); 170 171 presentQueue.present( 172 [ renderingFinishSem ], 173 [ PresentRequest(swapchain, imgInd) ] 174 ); 175 176 if (needReconstruction) { 177 writeln("need to rebuild swapchain"); 178 prepareSwapchain(swapchain); 179 presentPool.reset(); 180 recordCmds(); 181 } 182 } 183 184 override void dispose() { 185 if (device) { 186 device.waitIdle(); 187 } 188 if (presentPool && presentCmdBufs.length) { 189 presentPool.free(presentCmdBufs); 190 presentPool.unload(); 191 } 192 // the rest is checked with Rc, so it is safe to call unload even 193 // if object is invalid 194 imageAvailableSem.unload(); 195 renderingFinishSem.unload(); 196 swapchain.unload(); 197 device.unload(); 198 physicalDevice.unload(); 199 if (window) { 200 window.close(); 201 } 202 instance.unload(); 203 } 204 } 205 206 /// Return a format suitable for the surface. 207 /// - if supported by the surface Format.rgba8_uNorm 208 /// - otherwise the first format with uNorm numeric format 209 /// - otherwise the first format 210 Format chooseFormat(PhysicalDevice pd, Surface surface) 211 { 212 const formats = pd.surfaceFormats(surface); 213 enforce(formats.length, "Could not get surface formats"); 214 if (formats.length == 1 && formats[0] == Format.undefined) { 215 return Format.rgba8_uNorm; 216 } 217 foreach(f; formats) { 218 if (f == Format.rgba8_uNorm) { 219 return f; 220 } 221 } 222 foreach(f; formats) { 223 if (f.formatDesc.numFormat == NumFormat.uNorm) { 224 return f; 225 } 226 } 227 return formats[0]; 228 } 229 230 PresentMode choosePresentMode(PhysicalDevice pd, Surface surface) 231 { 232 // auto modes = pd.surfacePresentModes(surface); 233 // if (modes.canFind(PresentMode.mailbox)) { 234 // return PresentMode.mailbox; 235 // } 236 assert(pd.surfacePresentModes(surface).canFind(PresentMode.fifo)); 237 return PresentMode.fifo; 238 } 239 240 int main() { 241 242 try { 243 auto example = new SwapchainExample(); 244 example.prepare(); 245 scope(exit) example.dispose(); 246 247 bool exitFlag; 248 example.window.mouseOn = (uint, uint) { 249 exitFlag = true; 250 }; 251 252 import std.datetime.stopwatch : StopWatch; 253 254 size_t frameCount; 255 size_t lastUs; 256 StopWatch sw; 257 sw.start(); 258 259 enum reportFreq = 100; 260 261 while (!exitFlag) { 262 example.window.pollAndDispatch(); 263 example.render(); 264 ++ frameCount; 265 if ((frameCount % reportFreq) == 0) { 266 const us = sw.peek().total!"usecs"; 267 writeln("FPS: ", 1000_000.0 * reportFreq / (us - lastUs)); 268 lastUs = us; 269 } 270 } 271 272 return 0; 273 } 274 catch(Exception ex) { 275 stderr.writeln("error occured: ", ex.msg); 276 return 1; 277 } 278 }