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