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