1 module example;
2 
3 import gfx.core.log;
4 import gfx.core.rc;
5 import gfx.graal;
6 import gfx.graal.buffer;
7 import gfx.graal.cmd;
8 import gfx.graal.device;
9 import gfx.graal.format;
10 import gfx.graal.image;
11 import gfx.graal.memory;
12 import gfx.graal.presentation;
13 import gfx.graal.queue;
14 import gfx.graal.renderpass;
15 import gfx.graal.sync;
16 import gfx.graal.types;
17 import gfx.window;
18 
19 import std.algorithm;
20 import std.exception;
21 import std.stdio;
22 import std.typecons;
23 import std.traits : isArray;
24 import std.datetime : Duration;
25 
26 immutable gfxExLog = LogTag("GFX-EX");
27 
28 struct FpsProbe
29 {
30     import std.datetime.stopwatch : StopWatch;
31 
32     private StopWatch sw;
33     private size_t lastUsecs;
34     private size_t lastFc;
35     private size_t fc;
36 
37     void start() { sw.start(); }
38 
39     bool running() { return sw.running(); }
40 
41     void stop()
42     {
43         sw.stop();
44         lastUsecs = 0;
45         lastFc = 0;
46         fc = 0;
47     }
48 
49     void tick() { fc += 1; }
50 
51     size_t framecount() { return fc; }
52 
53     float computeFps() {
54         const usecs = sw.peek().total!"usecs"();
55         const fps = 1000_000f * (fc - lastFc) / (usecs-lastUsecs);
56         lastFc = fc;
57         lastUsecs = usecs;
58         return fps;
59     }
60 }
61 
62 struct Timer
63 {
64     import std.datetime.stopwatch : StopWatch;
65 
66     private StopWatch sw;
67     private size_t fc;
68 
69     void start() { sw.start(); }
70     void stop() { sw.stop(); }
71 
72     auto frame() {
73         static struct FrameTimer
74         {
75             StopWatch* sw;
76             this(StopWatch* sw)
77             {
78                 this.sw = sw;
79                 sw.start();
80             }
81             ~this()
82             {
83                 if (this.sw) {
84                     sw.stop();
85                 }
86             }
87         }
88         ++fc;
89         return FrameTimer(&sw);
90     }
91 
92     @property Duration totalDur()
93     {
94         return sw.peek;
95     }
96 
97     @property Duration avgDur()
98     {
99         return sw.peek / fc;
100     }
101 
102     @property size_t framecount()
103     {
104         return fc;
105     }
106 }
107 
108 shared static this()
109 {
110     import gfx.core.log : Severity, severity;
111     severity = Severity.trace;
112 
113     debug(rc) {
114         import gfx.core.rc : rcPrintStack, rcTypeRegex;
115         rcPrintStack = true;
116         rcTypeRegex = "^GlDevice$";
117     }
118 }
119 
120 class Example : Disposable
121 {
122     import gfx.math.proj : NDC;
123 
124     string title;
125     string[] args;
126     Rc!Display display;
127     Window window;
128     Rc!Instance instance;
129     NDC ndc;
130     PhysicalDevice physicalDevice;
131     Rc!Device device;
132     Queue graphicsQueue;
133     Queue presentQueue;
134     uint[2] surfaceSize;
135     bool hasAlpha;
136     Rc!Swapchain swapchain;
137     Rc!Semaphore imageAvailableSem;
138     Rc!Semaphore renderingFinishSem;
139     uint frameNumber;
140     FrameData[] frameDatas;
141     FpsProbe probe;
142 
143     // garbage collection
144     // it is sometimes desirable to delete objects still in use in a command
145     // buffer (this happens when rebuilding the swapchain for example)
146     // each entry is given an optional fence to check for the command buffer
147     // completion, and if the fence is null, it checks that the garbage
148     // was not emitted more than maxFcAge frames ago.
149     GarbageEntry garbageEntries;
150     GarbageEntry lastGarbageEntry;
151     enum maxFcAge = 4;
152 
153     class GarbageEntry
154     {
155         GarbageEntry next;
156         uint fc;
157         Fence fence;
158         IAtomicRefCounted resource;
159     }
160 
161     this (string title, string[] args=[])
162     {
163         this.title = title;
164         this.args = args;
165     }
166 
167     override void dispose()
168     {
169         probe.stop();
170         if (device) {
171             device.waitIdle();
172         }
173         while (garbageEntries) {
174             if (garbageEntries.fence) {
175                 garbageEntries.fence.wait();
176                 releaseObj(garbageEntries.fence);
177             }
178             releaseObj(garbageEntries.resource);
179             garbageEntries = garbageEntries.next;
180         }
181         releaseArr(frameDatas);
182         // the rest is checked with Rc, so it is safe to call unload even
183         // if object is invalid
184         imageAvailableSem.unload();
185         renderingFinishSem.unload();
186         swapchain.unload();
187         device.unload();
188         // if (window) window.close();
189         instance.unload();
190         display.unload();
191     }
192 
193     void prepare()
194     {
195         bool noVulkan = false;
196         bool noGl3 = false;
197         bool noWayland = false;
198         foreach (a; args) {
199             if (a == "--no-vulkan" || a == "nv") {
200                 noVulkan = true;
201             }
202             else if (a == "--no-gl3" || a == "ng") {
203                 noGl3 = true;
204             }
205             else if (a == "--no-wayland" || a == "nw") {
206                 noWayland = true;
207             }
208         }
209 
210         import std.algorithm : remove;
211 
212         DisplayCreateInfo createInfo;
213         if (noVulkan) {
214             createInfo.backendCreateOrder =
215                     createInfo.backendCreateOrder.remove(Backend.vulkan);
216         }
217         if (noGl3) {
218             createInfo.backendCreateOrder =
219                     createInfo.backendCreateOrder.remove(Backend.gl3);
220         }
221         if (noWayland) {
222             createInfo.linuxDisplayCreateOrder =
223                     createInfo.linuxDisplayCreateOrder.remove(LinuxDisplay.wayland);
224         }
225 
226         // Create a display for the running platform
227         // The instance is created by the display. Backend is chosen at runtime
228         // depending on detected API support. (i.e. Vulkan is preferred)
229         display = createDisplay(createInfo);
230         instance = display.instance;
231         ndc = instance.apiProps.ndc;
232 
233         // Create a window. The surface is created during the call to show.
234         window = display.createWindow(title);
235         window.show(640, 480);
236         surfaceSize = [640, 480];
237 
238         window.onResize = (uint w, uint h)
239         {
240             if (w != surfaceSize[0] || h != surfaceSize[1]) {
241                 surfaceSize = [ w, h ];
242                 rebuildSwapchain();
243             }
244         };
245 
246         debug {
247             alias Sev = gfx.graal.Severity;
248             instance.setDebugCallback((Sev sev, string msg) {
249                 import std.stdio : writefln;
250                 if (sev >= Sev.warning) {
251                     writefln("Gfx backend %s message: %s", sev, msg);
252                 }
253                 if (sev == Sev.error) {
254                     // debug break;
255                     asm { int 0x03; }
256                 }
257             });
258         }
259 
260         // The rest of the preparation.
261         prepareDevice();
262         prepareSync();
263         prepareSwapchain(null);
264         prepareRenderPass();
265         prepareFramebuffers();
266 
267         probe.start();
268     }
269 
270     Duration timeElapsed()
271     {
272         assert(probe.sw.running(), "stopwatch isn't running!!");
273         return probe.sw.peek();
274     }
275 
276     void prepareDevice()
277     {
278         auto graphicsQueueIndex = uint.max;
279         auto presentQueueIndex = uint.max;
280 
281         bool checkDevice(PhysicalDevice dev) {
282             if (dev.softwareRendering) return false;
283             foreach (size_t i, qf; dev.queueFamilies) {
284                 const qi = cast(uint)i;
285                 const graphics = qf.cap & QueueCap.graphics;
286                 const present = dev.supportsSurface(qi, window.surface);
287                 if (graphics && present) {
288                     graphicsQueueIndex = qi;
289                     presentQueueIndex = qi;
290                     return true;
291                 }
292                 if (graphics) graphicsQueueIndex = qi;
293                 if (present) presentQueueIndex = qi;
294             }
295             return graphicsQueueIndex != uint.max && presentQueueIndex != uint.max;
296         }
297         foreach (pd; instance.devices) {
298             if (checkDevice(pd)) {
299                 auto qrs = [ QueueRequest(graphicsQueueIndex, [ 0.5f ]) ];
300                 if (graphicsQueueIndex != presentQueueIndex) {
301                     qrs ~= QueueRequest(presentQueueIndex, [ 0.5f ]);
302                 }
303                 physicalDevice = pd;
304                 device = pd.open(qrs);
305                 graphicsQueue = device.getQueue(graphicsQueueIndex, 0);
306                 presentQueue = device.getQueue(presentQueueIndex, 0);
307                 break;
308             }
309         }
310     }
311 
312     void prepareSync()
313     {
314         imageAvailableSem = device.createSemaphore();
315         renderingFinishSem = device.createSemaphore();
316     }
317 
318     void prepareSwapchain(Swapchain former=null)
319     {
320         gfxExLog.infof("building swapchain %sx%s", surfaceSize[0], surfaceSize[1]);
321 
322         const surfCaps = physicalDevice.surfaceCaps(window.surface);
323         enforce(surfCaps.usage & ImageUsage.transferDst, "TransferDst not supported by surface");
324         enforce(surfCaps.usage & ImageUsage.colorAttachment, "ColorAttachment not supported by surface");
325         const usage = ImageUsage.colorAttachment | ImageUsage.transferDst;
326         const numImages = max(2, surfCaps.minImages);
327         enforce(surfCaps.maxImages == 0 || surfCaps.maxImages >= numImages);
328         const f = chooseFormat(physicalDevice, window.surface);
329 
330         CompositeAlpha ca;
331         if (surfCaps.supportedAlpha & CompositeAlpha.preMultiplied) {
332             ca = CompositeAlpha.preMultiplied;
333         }
334         else if (surfCaps.supportedAlpha & CompositeAlpha.inherit) {
335             ca = CompositeAlpha.inherit;
336         }
337         else if (surfCaps.supportedAlpha & CompositeAlpha.postMultiplied) {
338             ca = CompositeAlpha.postMultiplied;
339         }
340         else {
341             ca = CompositeAlpha.opaque;
342         }
343         hasAlpha = ca != CompositeAlpha.opaque;
344 
345         foreach (i; 0..2) {
346             surfaceSize[i] = clamp(surfaceSize[i], surfCaps.minSize[i], surfCaps.maxSize[i]);
347         }
348         const pm = choosePresentMode(physicalDevice, window.surface);
349 
350         swapchain = device.createSwapchain(window.surface, pm, numImages, f, surfaceSize, usage, ca, former);
351     }
352 
353     void prepareRenderPass()
354     {}
355 
356     /// Data that is duplicated for every frame in the swapchain
357     /// This typically include framebuffer and command pool.
358     abstract class FrameData : AtomicRefCounted
359     {
360         Rc!Fence fence; // to keep track of when command processing is done
361         Rc!CommandPool cmdPool;
362 
363         ImageBase swcColor;
364         uint[2] size;
365 
366         this(ImageBase swcColor)
367         {
368             this.fence = device.createFence(Yes.signaled);
369             this.cmdPool = device.createCommandPool(graphicsQueue.index);
370 
371             this.swcColor = swcColor;
372             const dims = swcColor.info.dims;
373             size = [dims.width, dims.height];
374         }
375 
376         override void dispose()
377         {
378             fence.unload();
379             cmdPool.unload();
380         }
381     }
382 
383     /// Instantiate FrameData implementation for the given swapchain color image.
384     /// tempBuf is a helper that can be used to transfer data, change images layout...
385     /// it is submitted and waited for shortly after FrameData is built.
386     abstract FrameData makeFrameData(ImageBase swcColor, CommandBuffer tempBuf);
387 
388     void prepareFramebuffers()
389     {
390         auto swcImages = swapchain.images;
391         frameDatas = new FrameData[swcImages.length];
392 
393         auto tempBuf = rc(new RaiiCmdBuf);
394 
395         foreach(i, img; swcImages) {
396             frameDatas[i] = retainObj(makeFrameData(img, tempBuf.get));
397         }
398     }
399 
400     /// Record buffer implementation for the current frame.
401     /// Returns the submissions for the graphics queue
402     /// the first submission that renders to the swapchain image must
403     /// wait for imageAvailableSem
404     /// the last submission must signal renderingFinishSem
405     abstract Submission[] recordCmds(FrameData frameData);
406 
407     /// build a submission for the simplest cases with one submission
408     final Submission[] simpleSubmission(PrimaryCommandBuffer[] cmdBufs)
409     {
410         return [
411             Submission (
412                 [ StageWait(imageAvailableSem, PipelineStage.transfer) ],
413                 [ renderingFinishSem.obj ], cmdBufs
414             )
415         ];
416     }
417 
418     void render()
419     {
420         import gfx.graal.error : OutOfDateException;
421 
422         const acq = swapchain.acquireNextImage(imageAvailableSem.obj);
423 
424         if (acq.hasIndex) {
425             auto frameData = frameDatas[acq.index];
426             frameData.fence.wait();
427             frameData.fence.reset();
428 
429             auto submissions = recordCmds(frameData);
430 
431             graphicsQueue.submit(submissions, frameData.fence);
432 
433             try {
434                 presentQueue.present(
435                     [ renderingFinishSem.obj ],
436                     [ PresentRequest(swapchain, acq.index) ]
437                 );
438             }
439             catch (OutOfDateException ex) {
440                 // The swapchain became out of date between acquire and present.
441                 // Rare, but can happen
442                 gfxExLog.errorf("error during presentation: %s", ex.msg);
443                 gfxExLog.errorf("acquisition was %s", acq.state);
444                 rebuildSwapchain();
445                 return;
446             }
447         }
448 
449         if (acq.swapchainNeedsRebuild) {
450             rebuildSwapchain();
451         }
452     }
453 
454     void rebuildSwapchain()
455     {
456         foreach (imgData; frameDatas) {
457             gc(imgData, imgData.fence);
458         }
459         releaseArr(frameDatas);
460         prepareSwapchain(swapchain.obj);
461         prepareFramebuffers();
462     }
463 
464     void frameTick()
465     {
466         frameNumber += 1;
467         collectGarbage();
468 
469         enum reportPeriod = 300;
470         probe.tick();
471         if (probe.framecount % reportPeriod == 0) {
472             gfxExLog.infof("FPS = %s", probe.computeFps());
473         }
474     }
475 
476     void gc(IAtomicRefCounted resource, Fence fence=null)
477     {
478         auto entry = new GarbageEntry;
479         entry.resource = retainObj(resource);
480         if (fence) entry.fence = retainObj(fence);
481         entry.fc = frameNumber;
482 
483         if (lastGarbageEntry) {
484             lastGarbageEntry.next = entry;
485             lastGarbageEntry = entry;
486         }
487         else {
488             assert(!garbageEntries);
489             garbageEntries = entry;
490             lastGarbageEntry = entry;
491         }
492     }
493 
494     void collectGarbage()
495     {
496         while (garbageEntries) {
497             if ((garbageEntries.fence && garbageEntries.fence.signaled) ||
498                 (garbageEntries.fc+maxFcAge < frameNumber))
499             {
500                 if (garbageEntries.fence) releaseObj(garbageEntries.fence);
501                 releaseObj(garbageEntries.resource);
502                 garbageEntries = garbageEntries.next;
503             }
504             else {
505                 break;
506             }
507         }
508         if (!garbageEntries) lastGarbageEntry = null;
509     }
510 
511     // Following functions are general utility that can be used by subclassing
512     // examples.
513 
514     /// Find a format supported by the device for the given tiling and features
515     Format findSupportedFormat(in Format[] candidates, in ImageTiling tiling, in FormatFeatures features)
516     {
517         foreach (f; candidates) {
518             const props = physicalDevice.formatProperties(f);
519             if (tiling == ImageTiling.optimal &&
520                     (props.optimalTiling & features) == features) {
521                 return f;
522             }
523             if (tiling == ImageTiling.linear &&
524                     (props.linearTiling & features) == features) {
525                 return f;
526             }
527         }
528         throw new Exception("could not find supported format");
529     }
530 
531     /// Find a supported depth format
532     Format findDepthFormat() {
533         return findSupportedFormat(
534             [ Format.d32_sFloat, Format.d32s8_sFloat, Format.d24s8_uNorm, Format.d16_uNorm, Format.d16s8_uNorm ],
535             ImageTiling.optimal, FormatFeatures.depthStencilAttachment
536         );
537     }
538 
539     /// Find a supported stencil format
540     Format findStencilFormat() {
541         return findSupportedFormat(
542             [ Format.s8_uInt, Format.d16s8_uNorm, Format.d24s8_uNorm, Format.d32s8_sFloat ],
543             ImageTiling.optimal, FormatFeatures.depthStencilAttachment
544         );
545     }
546 
547     /// Return the index of a memory type supporting all of props,
548     /// or uint.max if none was found.
549     uint findMemType(MemoryRequirements mr, MemProps props)
550     {
551         const devMemProps = physicalDevice.memoryProperties;
552         foreach (i, mt; devMemProps.types) {
553             if ((mr.memTypeMask & (1 << i)) != 0 && (mt.props & props) == props) {
554                 return cast(uint)i;
555             }
556         }
557         return uint.max;
558     }
559 
560 
561     /// Create a buffer for usage, bind memory of dataSize with memProps
562     /// Return null if no memory type can be found
563     final Buffer createBuffer(size_t dataSize, BufferUsage usage, MemProps props)
564     {
565         auto buf = device.createBuffer( usage, dataSize ).rc;
566 
567         const mr = buf.memoryRequirements;
568         const memTypeInd = findMemType(mr, props);
569         if (memTypeInd == uint.max) return null;
570 
571         auto mem = device.allocateMemory(memTypeInd, mr.size).rc;
572         buf.bindMemory(mem, 0);
573 
574         return buf.giveAway();
575     }
576 
577     /// Create a buffer, binds memory to it, and leave content undefined
578     /// The buffer will be host visible and host coherent such as content
579     /// can be updated without staging buffer
580     final Buffer createDynamicBuffer(size_t dataSize, BufferUsage usage)
581     {
582         return createBuffer(dataSize, usage, MemProps.hostVisible | MemProps.hostCoherent);
583     }
584 
585     /// Create a buffer, and bind it with memory filled with data.
586     /// The bound memory will be deviceLocal, without guarantee to be host visible.
587     final Buffer createStaticBuffer(const(void)[] data, BufferUsage usage)
588     {
589         const dataSize = data.length;
590 
591         // On embedded gpus, device local memory is often also host visible.
592         // Attempting to create one that way.
593         if (physicalDevice.type != DeviceType.discreteGpu) {
594             auto buf = createBuffer(
595                 dataSize, usage,
596                 MemProps.hostVisible | MemProps.hostCoherent | MemProps.deviceLocal
597             ).rc;
598             if (buf) {
599                 auto mm = buf.boundMemory.map(0, dataSize);
600                 mm[] = data;
601                 return buf.giveAway();
602             }
603         }
604 
605         // did not happen :-(
606         // will go the usual way: staging buffer then device local buffer
607 
608         // create staging buffer
609         auto stagingBuf = enforce(createBuffer(
610             dataSize, BufferUsage.transferSrc, MemProps.hostVisible | MemProps.hostCoherent
611         )).rc;
612 
613         // populate data
614         {
615             auto mm = stagingBuf.boundMemory.map(0, dataSize);
616             mm[] = data;
617         }
618 
619         // create actual buffer
620         auto buf = enforce(createBuffer(
621             dataSize, usage | BufferUsage.transferDst, MemProps.deviceLocal
622         )).rc;
623 
624         auto b = rc(new RaiiCmdBuf);
625 
626         // copy from staging buffer
627         copyBuffer(stagingBuf, buf, dataSize, b.cmdBuf);
628 
629         // return data
630         return buf.giveAway();
631     }
632 
633     /// ditto
634     Buffer createStaticBuffer(T)(const(T)[] data, BufferUsage usage)
635     if (!is(T == void))
636     {
637         return createStaticBuffer(untypeSlice(data), usage);
638     }
639 
640     /// ditto
641     Buffer createStaticBuffer(T)(in T data, BufferUsage usage)
642     if (!isArray!T)
643     {
644         const start = cast(const(void)*)&data;
645         return createStaticBuffer(start[0 .. data.sizeof], usage);
646     }
647 
648     bool bindImageMemory(Image img, MemProps props=MemProps.deviceLocal) {
649         const mr = img.memoryRequirements;
650         const memTypeInd = findMemType(mr, props);
651         if (memTypeInd == uint.max) return false;
652 
653         auto mem = device.allocateMemory(memTypeInd, mr.size).rc;
654         img.bindMemory(mem, 0);
655         return true;
656     }
657 
658     /// create an image to be used as texture
659     Image createTextureImage(const(void)[] data, in ImageInfo info)
660     {
661         const FormatFeatures requirement = FormatFeatures.sampledImage;
662         const formatProps = physicalDevice.formatProperties(info.format);
663         enforce( (formatProps.optimalTiling & requirement) == requirement );
664 
665         // create staging buffer
666         auto stagingBuf = enforce(createBuffer(
667             data.length, BufferUsage.transferSrc, MemProps.hostVisible | MemProps.hostCoherent
668         )).rc;
669 
670         // populate data to buffer
671         {
672             auto mm = stagingBuf.boundMemory.map(0, data.length);
673             mm[] = data;
674         }
675 
676         // create an image
677         auto img = enforce(device.createImage(
678             info.withUsage(info.usage | ImageUsage.sampled | ImageUsage.transferDst)
679         )).rc;
680 
681         // allocate memory image
682         if (!bindImageMemory(img)) return null;
683 
684         {
685             auto b = rc(new RaiiCmdBuf);
686 
687             b.cmdBuf.pipelineBarrier(
688                 trans(PipelineStage.topOfPipe, PipelineStage.transfer), [], [
689                     ImageMemoryBarrier(
690                         trans(Access.none, Access.transferWrite),
691                         trans(ImageLayout.undefined, ImageLayout.transferDstOptimal),
692                         trans(queueFamilyIgnored, queueFamilyIgnored),
693                         img, ImageSubresourceRange(ImageAspect.color)
694                     )
695                 ]
696             );
697             copyBufferToImage(stagingBuf, img, b.cmdBuf);
698         }
699 
700         return img.giveAway();
701     }
702 
703     /// Create an image for depth attachment usage
704     Image createDepthImage(uint width, uint height)
705     {
706         // find the format of the image
707         const f = findDepthFormat();
708 
709         // create an image
710         auto img = enforce(device.createImage(
711             ImageInfo.d2(width, height).withFormat(f).withUsage(ImageUsage.depthStencilAttachment)
712         )).rc;
713 
714         // allocate memory image
715         if (!bindImageMemory(img)) return null;
716 
717         return img.giveAway();
718     }
719 
720     /// Create an image for stencil attachment usage
721     Image createStencilImage(uint width, uint height)
722     {
723         // assume s8_uInt is supported
724         const f = findStencilFormat();
725         // create an image
726         auto img = enforce(device.createImage(
727             ImageInfo.d2(width, height).withFormat(f).withUsage(ImageUsage.depthStencilAttachment)
728         )).rc;
729 
730         // allocate memory image
731         if (!bindImageMemory(img)) return null;
732 
733         return img.giveAway();
734     }
735 
736     final void recordImageLayoutBarrier(CommandBuffer cmdBuf, ImageBase img, Trans!ImageLayout layout)
737     {
738         const info = img.info;
739 
740         if (info.usage & ImageUsage.colorAttachment)
741         {
742             cmdBuf.pipelineBarrier(
743                 trans(PipelineStage.colorAttachmentOutput, PipelineStage.colorAttachmentOutput), [],
744                 [ ImageMemoryBarrier(
745                     trans(Access.none, Access.colorAttachmentWrite),
746                     layout,
747                     trans(queueFamilyIgnored, queueFamilyIgnored),
748                     img, ImageSubresourceRange(ImageAspect.color)
749                 ) ]
750             );
751         }
752         else if (info.usage & ImageUsage.depthStencilAttachment)
753         {
754             const hasDepth = formatDesc(info.format).surfaceType.depthBits > 0;
755             const hasStencil = formatDesc(info.format).surfaceType.stencilBits > 0;
756             auto aspect = ImageAspect.none;
757             if (hasDepth) aspect |= ImageAspect.depth;
758             if (hasStencil) aspect |= ImageAspect.stencil;
759             cmdBuf.pipelineBarrier(
760                 trans(PipelineStage.topOfPipe, PipelineStage.earlyFragmentTests), [], [
761                     ImageMemoryBarrier(
762                         trans(
763                             Access.none,
764                             Access.depthStencilAttachmentRead | Access.depthStencilAttachmentWrite
765                         ),
766                         layout,
767                         trans(queueFamilyIgnored, queueFamilyIgnored),
768                         img, ImageSubresourceRange(aspect)
769                     )
770                 ]
771             );
772         }
773         else {
774             import std.format : format;
775             throw new Exception(
776                 format("don't know how to record memory barrier for image usage %s", info.usage)
777             );
778         }
779     }
780 
781     /// copy the content of one buffer to another
782     /// srcBuf and dstBuf must support transferSrc and transferDst respectively.
783     final void copyBuffer(Buffer srcBuf, Buffer dstBuf, size_t size, CommandBuffer cmdBuf)
784     {
785         cmdBuf.copyBuffer(trans(srcBuf, dstBuf), [CopyRegion(trans!size_t(0, 0), size)]);
786     }
787 
788     /// copy the content of one buffer to an image.
789     /// the image layout must be transferDstOptimal buffer the call
790     final void copyBufferToImage(Buffer srcBuf, Image dstImg, CommandBuffer cmdBuf)
791     {
792         const dims = dstImg.info.dims;
793 
794         BufferImageCopy region;
795         region.extent = [dims.width, dims.height, dims.depth];
796         const regions = (&region)[0 .. 1];
797         cmdBuf.copyBufferToImage(srcBuf, dstImg, ImageLayout.transferDstOptimal, regions);
798     }
799 
800     /// Utility command buffer for a one time submission that automatically submit
801     /// when disposed.
802     /// Generally used for transfer operations, or image layout change.
803     final class RaiiCmdBuf : AtomicRefCounted
804     {
805         Rc!CommandPool pool;
806         PrimaryCommandBuffer cmdBuf;
807 
808         this() {
809             this.pool = device.createCommandPool(graphicsQueue.index);
810             this.cmdBuf = this.pool.allocatePrimary(1)[0];
811             this.cmdBuf.begin(CommandBufferUsage.oneTimeSubmit);
812         }
813 
814         override void dispose() {
815             this.cmdBuf.end();
816             graphicsQueue.submit([
817                 Submission([], [], (&this.cmdBuf)[0 .. 1])
818             ], null);
819             graphicsQueue.waitIdle();
820             auto cb = cast(CommandBuffer)this.cmdBuf;
821             this.pool.free((&cb)[0 .. 1]);
822             this.pool.unload();
823         }
824 
825         @property CommandBuffer get()
826         {
827             return cmdBuf;
828         }
829     }
830 }
831 
832 /// Return a format suitable for the surface.
833 ///  - if supported by the surface Format.rgba8_uNorm
834 ///  - otherwise the first format with uNorm numeric format
835 ///  - otherwise the first format
836 Format chooseFormat(PhysicalDevice pd, Surface surface)
837 {
838     const formats = pd.surfaceFormats(surface);
839     enforce(formats.length, "Could not get surface formats");
840     if (formats.length == 1 && formats[0] == Format.undefined) {
841         return Format.rgba8_uNorm;
842     }
843     foreach(f; formats) {
844         if (f == Format.rgba8_uNorm) {
845             return f;
846         }
847     }
848     foreach(f; formats) {
849         if (f.formatDesc.numFormat == NumFormat.uNorm) {
850             return f;
851         }
852     }
853     return formats[0];
854 }
855 
856 PresentMode choosePresentMode(PhysicalDevice pd, Surface surface)
857 {
858     // auto modes = pd.surfacePresentModes(surface);
859     // if (modes.canFind(PresentMode.mailbox)) {
860     //     return PresentMode.mailbox;
861     // }
862     assert(pd.surfacePresentModes(surface).canFind(PresentMode.fifo));
863     return PresentMode.fifo;
864 }