1 module example;
2 
3 import gfx.core.rc;
4 import gfx.graal;
5 import gfx.graal.cmd;
6 import gfx.graal.device;
7 import gfx.graal.queue;
8 import gfx.graal.sync;
9 import gfx.vulkan;
10 import gfx.window;
11 
12 import std.algorithm;
13 import std.exception;
14 import std.typecons;
15 
16 class Example : Disposable
17 {
18     string title;
19     Rc!Instance instance;
20     Window window;
21     uint graphicsQueueIndex;
22     uint presentQueueIndex;
23     Rc!PhysicalDevice physicalDevice;
24     Rc!Device device;
25     Queue graphicsQueue;
26     Queue presentQueue;
27     uint[2] surfaceSize;
28     bool hasAlpha;
29     Rc!Swapchain swapchain;
30     Image[] scImages;
31     Rc!Semaphore imageAvailableSem;
32     Rc!Semaphore renderingFinishSem;
33     Rc!CommandPool cmdPool;
34     CommandBuffer[] cmdBufs;
35     Fence[] fences;
36 
37     enum numCmdBufs=2;
38     size_t cmdBufInd;
39 
40     this (string title)
41     {
42         this.title = title;
43     }
44 
45     override void dispose() {
46         if (device) {
47             device.waitIdle();
48         }
49         releaseArray(fences);
50         if (cmdPool && cmdBufs.length) {
51             cmdPool.free(cmdBufs);
52             cmdPool.unload();
53         }
54         // the rest is checked with Rc, so it is safe to call unload even
55         // if object is invalid
56         imageAvailableSem.unload();
57         renderingFinishSem.unload();
58         swapchain.unload();
59         device.unload();
60         physicalDevice.unload();
61         if (window) {
62             window.close();
63         }
64         instance.unload();
65     }
66 
67     void prepare()
68     {
69         import std.format : format;
70         // initialize vulkan library
71         vulkanInit();
72         // create a vulkan instance
73         instance = createVulkanInstance(
74             format("Gfx-d %s Example", title),
75             VulkanVersion(0, 0, 1)
76         ).rc;
77         // create a window for the running platform
78         // the window surface is created during this process
79         window = createWindow(instance);
80 
81         // the rest of the preparation
82         prepareDevice();
83         prepareSwapchain(null);
84         prepareSync();
85         prepareCmds();
86     }
87 
88     void prepareDevice()
89     {
90         bool checkDevice(PhysicalDevice dev) {
91             graphicsQueueIndex = uint.max;
92             presentQueueIndex = uint.max;
93             if (dev.softwareRendering) return false;
94             foreach (uint i, qf; dev.queueFamilies) {
95                 const graphics = qf.cap & QueueCap.graphics;
96                 const present = dev.supportsSurface(i, window.surface);
97                 if (graphics && present) {
98                     graphicsQueueIndex = i;
99                     presentQueueIndex = i;
100                     return true;
101                 }
102                 if (graphics) graphicsQueueIndex = i;
103                 if (present) presentQueueIndex = i;
104             }
105             return graphicsQueueIndex != uint.max && presentQueueIndex != uint.max;
106         }
107         foreach (pd; instance.devices) {
108             if (checkDevice(pd)) {
109                 auto qrs = [ QueueRequest(graphicsQueueIndex, [ 0.5f ]) ];
110                 if (graphicsQueueIndex != presentQueueIndex) {
111                     qrs ~= QueueRequest(presentQueueIndex, [ 0.5f ]);
112                 }
113                 physicalDevice = pd;
114                 device = pd.open(qrs);
115                 graphicsQueue = device.getQueue(graphicsQueueIndex, 0);
116                 presentQueue = device.getQueue(presentQueueIndex, 0);
117                 break;
118             }
119         }
120     }
121 
122     void prepareSwapchain(Swapchain former=null) {
123         const surfCaps = physicalDevice.surfaceCaps(window.surface);
124         enforce(surfCaps.usage & ImageUsage.transferDst, "TransferDst not supported by surface");
125         const usage = ImageUsage.transferDst | ImageUsage.colorAttachment;
126         const numImages = max(2, surfCaps.minImages);
127         enforce(surfCaps.maxImages == 0 || surfCaps.maxImages >= numImages);
128         const f = chooseFormat(physicalDevice, window.surface);
129         hasAlpha = (surfCaps.supportedAlpha & CompositeAlpha.preMultiplied) == CompositeAlpha.preMultiplied;
130         const ca = hasAlpha ? CompositeAlpha.preMultiplied : CompositeAlpha.opaque;
131         surfaceSize = [ 640, 480 ];
132         foreach (i; 0..2) {
133             surfaceSize[i] = clamp(surfaceSize[i], surfCaps.minSize[i], surfCaps.maxSize[i]);
134         }
135         const pm = choosePresentMode(physicalDevice, window.surface);
136 
137         swapchain = device.createSwapchain(window.surface, pm, numImages, f, surfaceSize, usage, ca, former);
138         scImages = swapchain.images;
139     }
140 
141     void prepareSync() {
142         imageAvailableSem = device.createSemaphore();
143         renderingFinishSem = device.createSemaphore();
144         fences = new Fence[numCmdBufs];
145         foreach (i; 0 .. numCmdBufs) {
146             fences[i] = device.createFence(Yes.signaled);
147         }
148         retainArray(fences);
149     }
150 
151     void prepareCmds() {
152         cmdPool = device.createCommandPool(graphicsQueueIndex);
153         cmdBufs = cmdPool.allocate(numCmdBufs);
154     }
155 
156     abstract void recordCmds(size_t cmdBufInd, size_t imgInd);
157 
158     size_t nextCmdBuf() {
159         const ind = cmdBufInd++;
160         if (cmdBufInd == numCmdBufs) {
161             cmdBufInd = 0;
162         }
163         return ind;
164     }
165 
166     void render()
167     {
168         import core.time : dur;
169 
170         bool needReconstruction;
171         const imgInd = swapchain.acquireNextImage(dur!"seconds"(-1), imageAvailableSem, needReconstruction);
172         const cmdBufInd = nextCmdBuf();
173 
174         fences[cmdBufInd].wait();
175         fences[cmdBufInd].reset();
176 
177         recordCmds(cmdBufInd, imgInd);
178 
179         presentQueue.submit([
180             Submission (
181                 [ StageWait(imageAvailableSem, PipelineStage.transfer) ],
182                 [ renderingFinishSem ], [ cmdBufs[cmdBufInd] ]
183             )
184         ], fences[cmdBufInd] );
185 
186         presentQueue.present(
187             [ renderingFinishSem ],
188             [ PresentRequest(swapchain, imgInd) ]
189         );
190 
191         // if (needReconstruction) {
192         //     prepareSwapchain(swapchain);
193         //     presentPool.reset();
194         // }
195     }
196 }
197 
198 /// Return a format suitable for the surface.
199 ///  - if supported by the surface Format.rgba8_uNorm
200 ///  - otherwise the first format with uNorm numeric format
201 ///  - otherwise the first format
202 Format chooseFormat(PhysicalDevice pd, Surface surface)
203 {
204     const formats = pd.surfaceFormats(surface);
205     enforce(formats.length, "Could not get surface formats");
206     if (formats.length == 1 && formats[0] == Format.undefined) {
207         return Format.rgba8_uNorm;
208     }
209     foreach(f; formats) {
210         if (f == Format.rgba8_uNorm) {
211             return f;
212         }
213     }
214     foreach(f; formats) {
215         if (f.formatDesc.numFormat == NumFormat.uNorm) {
216             return f;
217         }
218     }
219     return formats[0];
220 }
221 
222 PresentMode choosePresentMode(PhysicalDevice pd, Surface surface)
223 {
224     // auto modes = pd.surfacePresentModes(surface);
225     // if (modes.canFind(PresentMode.mailbox)) {
226     //     return PresentMode.mailbox;
227     // }
228     assert(pd.surfacePresentModes(surface).canFind(PresentMode.fifo));
229     return PresentMode.fifo;
230 }