1 /// Vulkan Window System Integration module
2 ///
3 /// The Vulkan backend supports integration with the following windowing systems:
4 ///
5 /// $(UL
6 ///   $(LI Linux)
7 ///   $(UL
8 ///     $(LI <a href="https://code.dlang.org/packages/wayland">Wayland</a>)
9 ///     $(LI <a href="https://code.dlang.org/packages/xcb-d">XCB</a>)
10 ///   )
11 ///   $(LI Windows)
12 ///   $(LI <a href="https://www.glfw.org/docs/3.3/vulkan_guide.html">GLFW</a> via <a href="https://code.dlang.org/packages/bindbc-glfw">bindbc-glfw</a>)
13 /// )
14 module gfx.vulkan.wsi;
15 
16 import core.time : Duration;
17 
18 import gfx.bindings.vulkan;
19 
20 import gfx.core.rc;
21 import gfx.graal;
22 import gfx.graal.format;
23 import gfx.graal.image;
24 import gfx.graal.sync;
25 import gfx.vulkan;
26 import gfx.vulkan.image;
27 import gfx.vulkan.error;
28 import gfx.vulkan.sync;
29 
30 import std.exception : enforce;
31 
32 // instance level extensions
33 
34 enum surfaceInstanceExtension = "VK_KHR_surface";
35 
36 version(Windows) {
37     enum win32SurfaceInstanceExtension = "VK_KHR_win32_surface";
38 }
39 version(linux) {
40     enum waylandSurfaceInstanceExtension = "VK_KHR_wayland_surface";
41     enum xcbSurfaceInstanceExtension = "VK_KHR_xcb_surface";
42 }
43 
44 // device level extensions
45 
46 enum swapChainDeviceExtension = "VK_KHR_swapchain";
47 
48 /// Extensions necessary to open Vulkan surfaces on the platform window system
49 @property immutable(string[]) surfaceInstanceExtensions()
50 {
51     version(GfxOffscreen) {
52         return [];
53     }
54     else version (glfw) {
55         return glfwInstanceExtensions;
56     }
57     else version (linux) {
58         import std.process : environment;
59 
60         const sessionType = environment.get("XDG_SESSION_TYPE");
61         if (sessionType == "wayland") {
62             return [
63                 surfaceInstanceExtension, waylandSurfaceInstanceExtension, xcbSurfaceInstanceExtension
64             ];
65         }
66         else {
67             return [
68                 surfaceInstanceExtension, xcbSurfaceInstanceExtension
69             ];
70         }
71     }
72     else version(Windows) {
73         return [
74             surfaceInstanceExtension, win32SurfaceInstanceExtension
75         ];
76     }
77 }
78 
79 
80 version(VkWayland) {
81     import wayland.client : WlDisplay, WlSurface;
82 
83     /// Extensions necessary to open a Wayland Vulkan surface
84     immutable string[] waylandSurfaceInstanceExtensions = [
85         surfaceInstanceExtension, waylandSurfaceInstanceExtension
86     ];
87 
88     Surface createVulkanWaylandSurface(Instance graalInst, WlDisplay wlDpy, WlSurface wlSurf)
89     {
90         auto inst = enforce(
91             cast(VulkanInstance)graalInst,
92             "createVulkanWaylandSurface called with non-vulkan instance"
93         );
94 
95         VkWaylandSurfaceCreateInfoKHR sci;
96         sci.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
97         sci.display = wlDpy.native;
98         sci.surface = wlSurf.proxy;
99 
100         VkSurfaceKHR vkSurf;
101         vulkanEnforce(
102             inst.vk.CreateWaylandSurfaceKHR(inst.vkObj, &sci, null, &vkSurf),
103             "Could not create Vulkan Wayland Surface"
104         );
105 
106         return new VulkanSurface(vkSurf, inst);
107     }
108 }
109 
110 version(VkXcb) {
111     import xcb.xcb : xcb_connection_t, xcb_window_t;
112 
113     /// Extensions necessary to open an XCB Vulkan surface
114     immutable string[] xcbSurfaceInstanceExtensions = [
115         surfaceInstanceExtension, xcbSurfaceInstanceExtension
116     ];
117 
118     Surface createVulkanXcbSurface(Instance graalInst, xcb_connection_t* conn, xcb_window_t win)
119     {
120         auto inst = enforce(
121             cast(VulkanInstance)graalInst,
122             "createVulkanXcbSurface called with non-vulkan instance"
123         );
124 
125         VkXcbSurfaceCreateInfoKHR sci;
126         sci.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR;
127         sci.connection = conn;
128         sci.window = win;
129 
130         VkSurfaceKHR vkSurf;
131         vulkanEnforce(
132             inst.vk.CreateXcbSurfaceKHR(inst.vkObj, &sci, null, &vkSurf),
133             "Could not create Vulkan Xcb Surface"
134         );
135 
136         return new VulkanSurface(vkSurf, inst);
137     }
138 }
139 
140 version(Windows) {
141     import core.sys.windows.windef : HINSTANCE, HWND;
142 
143     /// Extensions necessary to open a Win32 Vulkan surface
144     immutable string[] win32SurfaceInstanceExtensions = [
145         surfaceInstanceExtension, win32SurfaceInstanceExtension
146     ];
147 
148     Surface createVulkanWin32Surface(Instance graalInst, HINSTANCE hinstance, HWND hwnd) {
149         auto inst = enforce(
150             cast(VulkanInstance)graalInst,
151             "createVulkanXcbSurface called with non-vulkan instance"
152         );
153 
154         VkWin32SurfaceCreateInfoKHR sci;
155         sci.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
156         sci.hinstance = hinstance;
157         sci.hwnd = hwnd;
158 
159         VkSurfaceKHR vkSurf;
160         vulkanEnforce(
161             inst.vk.CreateWin32SurfaceKHR(inst.vkObj, &sci, null, &vkSurf),
162             "Could not create Vulkan Xcb Surface"
163         );
164 
165         return new VulkanSurface(vkSurf, inst);
166     }
167 }
168 
169 version(glfw) {
170     /// Extensions necessary to open a GLFW Vulkan surface
171     @property immutable(string[]) glfwSurfaceInstanceExtensions() {
172         return glfwInstanceExtensions;
173     }
174 
175     Surface createVulkanGlfwSurface(Instance graalInst, GLFWwindow* window) {
176         auto inst = enforce(
177             cast(VulkanInstance)graalInst,
178             "createVulkanGlfwSurface called with non-vulkan instance"
179         );
180 
181         VkSurfaceKHR vkSurf;
182         vulkanEnforce(
183             glfwCreateWindowSurface(inst.vkObj, window, null, &vkSurf),
184             "Could not create Vulkan GLFW Surface"
185         );
186 
187         return new VulkanSurface(vkSurf, inst);
188     }
189 
190     // TODO: Add createGlfwGlSurface
191 }
192 
193 
194 package:
195 
196 version(glfw) {
197     import bindbc.glfw : GLFWwindow;
198     import gfx.bindings.vulkan : VkInstance;
199 
200     extern(C) @nogc nothrow {
201         const(char)** glfwGetRequiredInstanceExtensions(uint*);
202         VkResult glfwCreateWindowSurface(
203             VkInstance, GLFWwindow*, const(VkAllocationCallbacks)*, VkSurfaceKHR*
204         );
205     }
206 
207     @property immutable(string[]) glfwInstanceExtensions() {
208         import std.algorithm.iteration : map;
209         import std.array : array;
210         import std.string : fromStringz;
211 
212         uint extensionCount;
213         const glfwRequiredInstanceExtensions =
214             glfwGetRequiredInstanceExtensions(&extensionCount)[0..extensionCount];
215         immutable extensions = glfwRequiredInstanceExtensions.map!(extension => extension.fromStringz).array;
216         return extensions;
217     }
218 }
219 
220 class VulkanSurface : VulkanInstObj!(VkSurfaceKHR), Surface
221 {
222     mixin(atomicRcCode);
223 
224     this(VkSurfaceKHR vkObj, VulkanInstance inst)
225     {
226         super(vkObj, inst);
227     }
228 
229     override void dispose() {
230         inst.vk.DestroySurfaceKHR(vkInst, vkObj, null);
231         super.dispose();
232     }
233 }
234 
235 class VulkanSwapchain : VulkanDevObj!(VkSwapchainKHR, "DestroySwapchainKHR"), Swapchain
236 {
237     mixin(atomicRcCode);
238 
239     this(VkSwapchainKHR vkObj, VulkanDevice dev, Surface surf, uint[2] size,
240             Format format, ImageUsage usage)
241     {
242         super(vkObj, dev);
243         _surf = surf;
244         _size = size;
245         _format = format;
246         _usage = usage;
247     }
248 
249     override void dispose() {
250         super.dispose();
251         _surf.unload();
252     }
253 
254     override @property Device device() {
255         return dev;
256     }
257 
258     override @property Surface surface() {
259         return _surf;
260     }
261 
262     override @property Format format() {
263         return _format;
264     }
265 
266     // not releasing images on purpose, the lifetime is owned by implementation
267 
268     override @property ImageBase[] images() {
269 
270         if (!_images.length) {
271             uint count;
272             vulkanEnforce(
273                 vk.GetSwapchainImagesKHR(vkDev, vkObj, &count, null),
274                 "Could not get vulkan swap chain images"
275             );
276             auto vkImgs = new VkImage[count];
277             vulkanEnforce(
278                 vk.GetSwapchainImagesKHR(vkDev, vkObj, &count, &vkImgs[0]),
279                 "Could not get vulkan swap chain images"
280             );
281 
282             import std.algorithm : map;
283             import std.array : array;
284             _images = vkImgs
285                     .map!((VkImage vkImg) {
286                         const info = ImageInfo.d2(_size[0], _size[1])
287                             .withFormat(_format)
288                             .withUsage(_usage);
289                         auto img = new VulkanImageBase(vkImg, dev, info);
290                         return cast(ImageBase)img;
291                     })
292                     .array;
293         }
294 
295         return _images;
296     }
297 
298     override ImageAcquisition acquireNextImage(Semaphore graalSem,
299                                                Duration timeout)
300     {
301         auto sem = enforce(
302             cast(VulkanSemaphore)graalSem,
303             "a non vulkan semaphore was passed acquireNextImage"
304         );
305 
306         ulong vkTimeout = timeout.total!"nsecs";
307         import core.time : dur;
308         if (timeout < dur!"nsecs"(0)) {
309             vkTimeout = ulong.max;
310         }
311 
312         uint img;
313         const res = vk.AcquireNextImageKHR(vkDev, vkObj, vkTimeout, sem.vkObj, VK_NULL_ND_HANDLE, &img);
314 
315         switch (res)
316         {
317         case VK_SUCCESS:
318             return ImageAcquisition.makeOk(img);
319         case VK_SUBOPTIMAL_KHR:
320             return ImageAcquisition.makeSuboptimal(img);
321         case VK_NOT_READY:
322         case VK_TIMEOUT:
323             return ImageAcquisition.makeNotReady();
324         case VK_ERROR_OUT_OF_DATE_KHR:
325             return ImageAcquisition.makeOutOfDate();
326         default:
327             vulkanEnforce(res, "Could not acquire next vulkan image");
328             assert(false);
329         }
330     }
331 
332     private Rc!Surface _surf;
333     private ImageBase[] _images;
334     private uint[2] _size;
335     private Format _format;
336     private ImageUsage _usage;
337 }