1 /// Vulkan Window System Integration module
2 module gfx.vulkan.wsi;
3 
4 import core.time : Duration;
5 
6 import gfx.bindings.vulkan;
7 
8 import gfx.core.rc;
9 import gfx.graal;
10 import gfx.graal.format;
11 import gfx.graal.image;
12 import gfx.graal.sync;
13 import gfx.vulkan;
14 import gfx.vulkan.image;
15 import gfx.vulkan.error;
16 import gfx.vulkan.sync;
17 
18 import std.exception : enforce;
19 
20 // instance level extensions
21 
22 enum surfaceExtension = "VK_KHR_surface";
23 
24 version(Windows) {
25     enum win32SurfaceExtension = "VK_KHR_win32_surface";
26 }
27 version(linux) {
28     enum waylandSurfaceExtension = "VK_KHR_wayland_surface";
29     enum xcbSurfaceExtension = "VK_KHR_xcb_surface";
30 }
31 
32 // device level extensions
33 
34 enum swapChainExtension = "VK_KHR_swapchain";
35 
36 version(linux) {
37     import wayland.client : WlDisplay, WlSurface;
38     import xcb.xcb : xcb_connection_t, xcb_window_t;
39 
40     // TODO: fall back from wayland to XCB
41     immutable string[] surfaceInstanceExtensions = [
42         surfaceExtension, waylandSurfaceExtension, xcbSurfaceExtension
43     ];
44 
45     Surface createVulkanWaylandSurface(Instance graalInst, WlDisplay wlDpy, WlSurface wlSurf)
46     {
47         auto inst = enforce(
48             cast(VulkanInstance)graalInst,
49             "createVulkanWaylandSurface called with non-vulkan instance"
50         );
51 
52         VkWaylandSurfaceCreateInfoKHR sci;
53         sci.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
54         sci.display = wlDpy.native;
55         sci.surface = wlSurf.proxy;
56 
57         VkSurfaceKHR vkSurf;
58         vulkanEnforce(
59             inst.vk.CreateWaylandSurfaceKHR(inst.vkObj, &sci, null, &vkSurf),
60             "Could not create Vulkan Wayland Surface"
61         );
62 
63         return new VulkanSurface(vkSurf, inst);
64     }
65 
66     Surface createVulkanXcbSurface(Instance graalInst, xcb_connection_t* conn, xcb_window_t win)
67     {
68         auto inst = enforce(
69             cast(VulkanInstance)graalInst,
70             "createVulkanXcbSurface called with non-vulkan instance"
71         );
72 
73         VkXcbSurfaceCreateInfoKHR sci;
74         sci.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR;
75         sci.connection = conn;
76         sci.window = win;
77 
78         VkSurfaceKHR vkSurf;
79         vulkanEnforce(
80             inst.vk.CreateXcbSurfaceKHR(inst.vkObj, &sci, null, &vkSurf),
81             "Could not create Vulkan Xcb Surface"
82         );
83 
84         return new VulkanSurface(vkSurf, inst);
85     }
86 }
87 
88 version(Windows) {
89 
90     import core.sys.windows.windef : HINSTANCE, HWND;
91 
92     immutable string[] surfaceInstanceExtensions = [
93         surfaceExtension, win32SurfaceExtension
94     ];
95 
96     Surface createVulkanWin32Surface(Instance graalInst, HINSTANCE hinstance, HWND hwnd) {
97         auto inst = enforce(
98             cast(VulkanInstance)graalInst,
99             "createVulkanXcbSurface called with non-vulkan instance"
100         );
101 
102         VkWin32SurfaceCreateInfoKHR sci;
103         sci.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
104         sci.hinstance = hinstance;
105         sci.hwnd = hwnd;
106 
107         VkSurfaceKHR vkSurf;
108         vulkanEnforce(
109             inst.vk.CreateWin32SurfaceKHR(inst.vkObj, &sci, null, &vkSurf),
110             "Could not create Vulkan Xcb Surface"
111         );
112 
113         return new VulkanSurface(vkSurf, inst);
114     }
115 }
116 version(GfxOffscreen) {
117     immutable string[] surfaceInstanceExtensions = [];
118 }
119 
120 
121 package:
122 
123 class VulkanSurface : VulkanInstObj!(VkSurfaceKHR), Surface
124 {
125     mixin(atomicRcCode);
126 
127     this(VkSurfaceKHR vkObj, VulkanInstance inst)
128     {
129         super(vkObj, inst);
130     }
131 
132     override void dispose() {
133         inst.vk.DestroySurfaceKHR(vkInst, vkObj, null);
134         super.dispose();
135     }
136 }
137 
138 class VulkanSwapchain : VulkanDevObj!(VkSwapchainKHR, "DestroySwapchainKHR"), Swapchain
139 {
140     mixin(atomicRcCode);
141 
142     this(VkSwapchainKHR vkObj, VulkanDevice dev, Surface surf, uint[2] size, Format format)
143     {
144         super(vkObj, dev);
145         _surf = surf;
146         _size = size;
147         _format = format;
148     }
149 
150     override void dispose() {
151         super.dispose();
152         _surf.unload();
153     }
154 
155     override @property Device device() {
156         return dev;
157     }
158 
159     override @property Surface surface() {
160         return _surf;
161     }
162 
163     override @property Format format() {
164         return _format;
165     }
166 
167     // not releasing images on purpose, the lifetime is owned by implementation
168 
169     override @property ImageBase[] images() {
170 
171         if (!_images.length) {
172             uint count;
173             vulkanEnforce(
174                 vk.GetSwapchainImagesKHR(vkDev, vkObj, &count, null),
175                 "Could not get vulkan swap chain images"
176             );
177             auto vkImgs = new VkImage[count];
178             vulkanEnforce(
179                 vk.GetSwapchainImagesKHR(vkDev, vkObj, &count, &vkImgs[0]),
180                 "Could not get vulkan swap chain images"
181             );
182 
183             import std.algorithm : map;
184             import std.array : array;
185             _images = vkImgs
186                     .map!((VkImage vkImg) {
187                         const info = ImageInfo.d2(_size[0], _size[1]).withFormat(_format);
188                         auto img = new VulkanImageBase(vkImg, dev, info);
189                         return cast(ImageBase)img;
190                     })
191                     .array;
192         }
193 
194         return _images;
195     }
196 
197     override uint acquireNextImage(Duration timeout, Semaphore graalSemaphore, out bool suboptimal)
198     {
199         auto sem = enforce(
200             cast(VulkanSemaphore)graalSemaphore,
201             "a non vulkan semaphore was passed acquireNextImage"
202         );
203 
204         ulong vkTimeout = timeout.total!"nsecs";
205         import core.time : dur;
206         if (timeout < dur!"nsecs"(0)) {
207             vkTimeout = ulong.max;
208         }
209 
210         uint img;
211         const res = vk.AcquireNextImageKHR(vkDev, vkObj, vkTimeout, sem.vkObj, VK_NULL_ND_HANDLE, &img);
212 
213         if (res == VK_SUBOPTIMAL_KHR) {
214             suboptimal = true;
215         }
216         else {
217             vulkanEnforce(res, "Could not acquire next vulkan image");
218         }
219 
220         return img;
221     }
222 
223     private Rc!Surface _surf;
224     private ImageBase[] _images;
225     private uint[2] _size;
226     private Format _format;
227 }