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 }