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 }