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 }