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 Rc!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 physicalDevice.unload(); 201 if (window) { 202 window.close(); 203 } 204 display.unload(); 205 instance.unload(); 206 } 207 } 208 209 /// Return a format suitable for the surface. 210 /// - if supported by the surface Format.rgba8_uNorm 211 /// - otherwise the first format with uNorm numeric format 212 /// - otherwise the first format 213 Format chooseFormat(PhysicalDevice pd, Surface surface) 214 { 215 const formats = pd.surfaceFormats(surface); 216 enforce(formats.length, "Could not get surface formats"); 217 if (formats.length == 1 && formats[0] == Format.undefined) { 218 return Format.rgba8_uNorm; 219 } 220 foreach(f; formats) { 221 if (f == Format.rgba8_uNorm) { 222 return f; 223 } 224 } 225 foreach(f; formats) { 226 if (f.formatDesc.numFormat == NumFormat.uNorm) { 227 return f; 228 } 229 } 230 return formats[0]; 231 } 232 233 PresentMode choosePresentMode(PhysicalDevice pd, Surface surface) 234 { 235 // auto modes = pd.surfacePresentModes(surface); 236 // if (modes.canFind(PresentMode.mailbox)) { 237 // return PresentMode.mailbox; 238 // } 239 assert(pd.surfacePresentModes(surface).canFind(PresentMode.fifo)); 240 return PresentMode.fifo; 241 } 242 243 int main() { 244 245 try { 246 auto example = new SwapchainExample(); 247 example.prepare(); 248 scope(exit) example.dispose(); 249 250 example.window.onMouseOn = (uint, uint) { 251 example.window.closeFlag = true; 252 }; 253 254 import std.datetime.stopwatch : StopWatch; 255 256 size_t frameCount; 257 size_t lastUs; 258 StopWatch sw; 259 sw.start(); 260 261 enum reportFreq = 100; 262 263 while (!example.window.closeFlag) { 264 example.render(); 265 ++ frameCount; 266 if ((frameCount % reportFreq) == 0) { 267 const us = sw.peek().total!"usecs"; 268 writeln("FPS: ", 1000_000.0 * reportFreq / (us - lastUs)); 269 lastUs = us; 270 } 271 example.display.pollAndDispatch(); 272 } 273 274 return 0; 275 } 276 catch(Exception ex) { 277 stderr.writeln("error occured: ", ex.msg); 278 return 1; 279 } 280 }