Branch data Line data Source code
1 : : /*
2 : : * This file is part of libplacebo.
3 : : *
4 : : * libplacebo is free software; you can redistribute it and/or
5 : : * modify it under the terms of the GNU Lesser General Public
6 : : * License as published by the Free Software Foundation; either
7 : : * version 2.1 of the License, or (at your option) any later version.
8 : : *
9 : : * libplacebo is distributed in the hope that it will be useful,
10 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : : * GNU Lesser General Public License for more details.
13 : : *
14 : : * You should have received a copy of the GNU Lesser General Public
15 : : * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
16 : : */
17 : :
18 : : #include "common.h"
19 : : #include "command.h"
20 : : #include "formats.h"
21 : : #include "utils.h"
22 : : #include "gpu.h"
23 : : #include "swapchain.h"
24 : : #include "pl_thread.h"
25 : :
26 : : struct sem_pair {
27 : : VkSemaphore in;
28 : : VkSemaphore out;
29 : : };
30 : :
31 : : struct priv {
32 : : struct pl_sw_fns impl;
33 : :
34 : : pl_mutex lock;
35 : : struct vk_ctx *vk;
36 : : VkSurfaceKHR surf;
37 : : PL_ARRAY(VkSurfaceFormatKHR) formats;
38 : :
39 : : // current swapchain and metadata:
40 : : struct pl_vulkan_swapchain_params params;
41 : : VkSwapchainCreateInfoKHR protoInfo; // partially filled-in prototype
42 : : VkSwapchainKHR swapchain;
43 : : int cur_width, cur_height;
44 : : int swapchain_depth;
45 : : pl_rc_t frames_in_flight; // number of frames currently queued
46 : : bool suboptimal; // true once VK_SUBOPTIMAL_KHR is returned
47 : : bool needs_recreate; // swapchain needs to be recreated
48 : : struct pl_color_repr color_repr;
49 : : struct pl_color_space color_space;
50 : : struct pl_hdr_metadata hdr_metadata;
51 : :
52 : : // state of the images:
53 : : PL_ARRAY(pl_tex) images; // pl_tex wrappers for the VkImages
54 : : PL_ARRAY(struct sem_pair) sems; // pool of semaphores used to synchronize images
55 : : int idx_sems; // index of next free semaphore pair
56 : : int last_imgidx; // the image index last acquired (for submit)
57 : : };
58 : :
59 : : static const struct pl_sw_fns vulkan_swapchain;
60 : :
61 : 4 : static bool map_color_space(VkColorSpaceKHR space, struct pl_color_space *out)
62 : : {
63 [ + - - - : 4 : switch (space) {
- - - - -
- - - - ]
64 : : // Note: This is technically against the spec, but more often than not
65 : : // it's the correct result since `SRGB_NONLINEAR` is just a catch-all
66 : : // for any sort of typical SDR curve, which is better approximated by
67 : : // `pl_color_space_monitor`.
68 : 4 : case VK_COLOR_SPACE_SRGB_NONLINEAR_KHR:
69 : 4 : *out = pl_color_space_monitor;
70 : 4 : return true;
71 : :
72 : 0 : case VK_COLOR_SPACE_BT709_NONLINEAR_EXT:
73 : 0 : *out = pl_color_space_monitor;
74 : 0 : return true;
75 : 0 : case VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT:
76 : 0 : *out = (struct pl_color_space) {
77 : : .primaries = PL_COLOR_PRIM_DISPLAY_P3,
78 : : .transfer = PL_COLOR_TRC_BT_1886,
79 : : };
80 : 0 : return true;
81 : 0 : case VK_COLOR_SPACE_DCI_P3_LINEAR_EXT:
82 : 0 : *out = (struct pl_color_space) {
83 : : .primaries = PL_COLOR_PRIM_DCI_P3,
84 : : .transfer = PL_COLOR_TRC_LINEAR,
85 : : };
86 : 0 : return true;
87 : 0 : case VK_COLOR_SPACE_DCI_P3_NONLINEAR_EXT:
88 : 0 : *out = (struct pl_color_space) {
89 : : .primaries = PL_COLOR_PRIM_DCI_P3,
90 : : .transfer = PL_COLOR_TRC_BT_1886,
91 : : };
92 : 0 : return true;
93 : : case VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT:
94 : : case VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT:
95 : : // TODO
96 : : return false;
97 : 0 : case VK_COLOR_SPACE_BT709_LINEAR_EXT:
98 : 0 : *out = (struct pl_color_space) {
99 : : .primaries = PL_COLOR_PRIM_BT_709,
100 : : .transfer = PL_COLOR_TRC_LINEAR,
101 : : };
102 : 0 : return true;
103 : 0 : case VK_COLOR_SPACE_BT2020_LINEAR_EXT:
104 : 0 : *out = (struct pl_color_space) {
105 : : .primaries = PL_COLOR_PRIM_BT_2020,
106 : : .transfer = PL_COLOR_TRC_LINEAR,
107 : : };
108 : 0 : return true;
109 : 0 : case VK_COLOR_SPACE_HDR10_ST2084_EXT:
110 : 0 : *out = (struct pl_color_space) {
111 : : .primaries = PL_COLOR_PRIM_BT_2020,
112 : : .transfer = PL_COLOR_TRC_PQ,
113 : : };
114 : 0 : return true;
115 : : case VK_COLOR_SPACE_DOLBYVISION_EXT:
116 : : // Unlikely to ever be implemented
117 : : return false;
118 : 0 : case VK_COLOR_SPACE_HDR10_HLG_EXT:
119 : 0 : *out = (struct pl_color_space) {
120 : : .primaries = PL_COLOR_PRIM_BT_2020,
121 : : .transfer = PL_COLOR_TRC_HLG,
122 : : };
123 : 0 : return true;
124 : 0 : case VK_COLOR_SPACE_ADOBERGB_LINEAR_EXT:
125 : 0 : *out = (struct pl_color_space) {
126 : : .primaries = PL_COLOR_PRIM_ADOBE,
127 : : .transfer = PL_COLOR_TRC_LINEAR,
128 : : };
129 : 0 : return true;
130 : 0 : case VK_COLOR_SPACE_ADOBERGB_NONLINEAR_EXT:
131 : 0 : *out = (struct pl_color_space) {
132 : : .primaries = PL_COLOR_PRIM_ADOBE,
133 : : .transfer = PL_COLOR_TRC_GAMMA22,
134 : : };
135 : 0 : return true;
136 : 0 : case VK_COLOR_SPACE_PASS_THROUGH_EXT:
137 : 0 : *out = pl_color_space_unknown;
138 : 0 : return true;
139 : :
140 : : #ifdef VK_AMD_display_native_hdr
141 : : case VK_COLOR_SPACE_DISPLAY_NATIVE_AMD:
142 : : // TODO
143 : : return false;
144 : : #endif
145 : :
146 : : default: return false;
147 : : }
148 : : }
149 : :
150 : 1 : static bool pick_surf_format(pl_swapchain sw, const struct pl_color_space *hint)
151 : : {
152 : 1 : struct priv *p = PL_PRIV(sw);
153 : 1 : struct vk_ctx *vk = p->vk;
154 : 1 : pl_gpu gpu = sw->gpu;
155 : :
156 : : int best_score = 0, best_id;
157 : 1 : bool wide_gamut = pl_color_primaries_is_wide_gamut(hint->primaries);
158 : 1 : bool prefer_hdr = pl_color_transfer_is_hdr(hint->transfer);
159 : :
160 [ + + ]: 3 : for (int i = 0; i < p->formats.num; i++) {
161 : : // Color space / format whitelist
162 : : struct pl_color_space space;
163 [ - + ]: 2 : if (!map_color_space(p->formats.elem[i].colorSpace, &space))
164 : 0 : continue;
165 : :
166 [ + - ]: 2 : bool disable10 = !pl_color_transfer_is_hdr(space.transfer) &&
167 [ + - ]: 2 : p->params.disable_10bit_sdr;
168 : :
169 [ - + - - : 2 : switch (p->formats.elem[i].format) {
- ]
170 : : // Only accept floating point formats for linear curves
171 : 0 : case VK_FORMAT_R16G16B16_SFLOAT:
172 : : case VK_FORMAT_R16G16B16A16_SFLOAT:
173 : : case VK_FORMAT_R32G32B32_SFLOAT:
174 : : case VK_FORMAT_R32G32B32A32_SFLOAT:
175 : : case VK_FORMAT_R64G64B64_SFLOAT:
176 : : case VK_FORMAT_R64G64B64A64_SFLOAT:
177 [ # # ]: 0 : if (space.transfer == PL_COLOR_TRC_LINEAR)
178 : : break; // accept
179 : 0 : continue;
180 : :
181 : : // Only accept 8 bit for non-HDR curves
182 : 2 : case VK_FORMAT_R8G8B8_UNORM:
183 : : case VK_FORMAT_B8G8R8_UNORM:
184 : : case VK_FORMAT_R8G8B8A8_UNORM:
185 : : case VK_FORMAT_B8G8R8A8_UNORM:
186 : : case VK_FORMAT_A8B8G8R8_UNORM_PACK32:
187 [ + - ]: 2 : if (!pl_color_transfer_is_hdr(space.transfer))
188 : : break; // accept
189 : 0 : continue;
190 : :
191 : : // Only accept 10 bit formats for non-linear curves
192 : 0 : case VK_FORMAT_A2R10G10B10_UNORM_PACK32:
193 : : case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
194 [ # # # # ]: 0 : if (space.transfer != PL_COLOR_TRC_LINEAR && !disable10)
195 : : break; // accept
196 : 0 : continue;
197 : :
198 : : // Accept 16-bit formats for everything
199 : 0 : case VK_FORMAT_R16G16B16_UNORM:
200 : : case VK_FORMAT_R16G16B16A16_UNORM:
201 [ # # ]: 0 : if (!disable10)
202 : : break; // accept
203 : 0 : continue;
204 : :
205 : 0 : default: continue;
206 : : }
207 : :
208 : : // Make sure we can wrap this format to a meaningful, valid pl_fmt
209 [ + + ]: 108 : for (int n = 0; n < gpu->num_formats; n++) {
210 : 107 : pl_fmt plfmt = gpu->formats[n];
211 : 107 : const struct vk_format **pvkfmt = PL_PRIV(plfmt);
212 [ + + ]: 107 : if ((*pvkfmt)->tfmt != p->formats.elem[i].format)
213 : 105 : continue;
214 : :
215 : : enum pl_fmt_caps render_caps = 0;
216 : : render_caps |= PL_FMT_CAP_RENDERABLE;
217 : : render_caps |= PL_FMT_CAP_BLITTABLE;
218 [ - + ]: 2 : if ((plfmt->caps & render_caps) != render_caps)
219 : 0 : continue;
220 : :
221 : : // format valid, use it if it has a higher score
222 : : int score = 0;
223 [ + + ]: 8 : for (int c = 0; c < 3; c++)
224 : 6 : score += plfmt->component_depth[c];
225 [ + - ]: 2 : if (pl_color_primaries_is_wide_gamut(space.primaries) == wide_gamut)
226 : 2 : score += 1000;
227 [ - + ]: 2 : if (space.primaries == hint->primaries)
228 : 0 : score += 2000;
229 [ + - ]: 2 : if (pl_color_transfer_is_hdr(space.transfer) == prefer_hdr)
230 : 2 : score += 10000;
231 [ + - ]: 2 : if (space.transfer == hint->transfer)
232 : 2 : score += 20000;
233 : :
234 [ + - - - : 2 : switch (plfmt->type) {
- ]
235 : : case PL_FMT_UNKNOWN: break;
236 : : case PL_FMT_UINT: break;
237 : : case PL_FMT_SINT: break;
238 : 2 : case PL_FMT_UNORM: score += 500; break;
239 : 0 : case PL_FMT_SNORM: score += 400; break;
240 : 0 : case PL_FMT_FLOAT: score += 300; break;
241 : 0 : case PL_FMT_TYPE_COUNT: pl_unreachable();
242 : : };
243 : :
244 [ + + ]: 2 : if (score > best_score) {
245 : : best_score = score;
246 : : best_id = i;
247 : : break;
248 : : }
249 : : }
250 : : }
251 : :
252 [ - + ]: 1 : if (!best_score) {
253 : 0 : PL_ERR(vk, "Failed picking any valid, renderable surface format!");
254 : 0 : return false;
255 : : }
256 : :
257 : 1 : VkSurfaceFormatKHR new_sfmt = p->formats.elem[best_id];
258 [ - + ]: 1 : if (p->protoInfo.imageFormat != new_sfmt.format ||
259 [ # # ]: 0 : p->protoInfo.imageColorSpace != new_sfmt.colorSpace)
260 : : {
261 : 1 : PL_INFO(vk, "Picked surface configuration %d: %s + %s", best_id,
262 : : vk_fmt_name(new_sfmt.format),
263 : : vk_csp_name(new_sfmt.colorSpace));
264 : :
265 : 1 : p->protoInfo.imageFormat = new_sfmt.format;
266 : 1 : p->protoInfo.imageColorSpace = new_sfmt.colorSpace;
267 : 1 : p->needs_recreate = true;
268 : : }
269 : :
270 : : return true;
271 : : }
272 : :
273 : 2 : static void set_hdr_metadata(struct priv *p, const struct pl_hdr_metadata *metadata)
274 : : {
275 : 2 : struct vk_ctx *vk = p->vk;
276 [ - + ]: 2 : if (!vk->SetHdrMetadataEXT)
277 : 2 : return;
278 : :
279 : : // Whitelist only values that we support signalling metadata for
280 : 0 : struct pl_hdr_metadata fix = {
281 : : .prim = metadata->prim,
282 : 0 : .min_luma = metadata->min_luma,
283 : 0 : .max_luma = metadata->max_luma,
284 : 0 : .max_cll = metadata->max_cll,
285 : 0 : .max_fall = metadata->max_fall,
286 : : };
287 : :
288 : : // Ignore no-op changes
289 [ # # ]: 0 : if (pl_hdr_metadata_equal(&fix, &p->hdr_metadata))
290 : : return;
291 : :
292 : : // Remember the metadata so we can re-apply it after swapchain recreation
293 : 0 : p->hdr_metadata = fix;
294 : :
295 : : // Ignore HDR metadata requests for SDR swapchains
296 [ # # ]: 0 : if (!pl_color_transfer_is_hdr(p->color_space.transfer))
297 : : return;
298 : :
299 [ # # ]: 0 : if (!p->swapchain)
300 : : return;
301 : :
302 : 0 : vk->SetHdrMetadataEXT(vk->dev, 1, &p->swapchain, &(VkHdrMetadataEXT) {
303 : : .sType = VK_STRUCTURE_TYPE_HDR_METADATA_EXT,
304 : 0 : .displayPrimaryRed = { fix.prim.red.x, fix.prim.red.y },
305 : 0 : .displayPrimaryGreen = { fix.prim.green.x, fix.prim.green.y },
306 : 0 : .displayPrimaryBlue = { fix.prim.blue.x, fix.prim.blue.y },
307 : 0 : .whitePoint = { fix.prim.white.x, fix.prim.white.y },
308 : 0 : .maxLuminance = fix.max_luma,
309 : 0 : .minLuminance = fix.min_luma,
310 : 0 : .maxContentLightLevel = fix.max_cll,
311 : 0 : .maxFrameAverageLightLevel = fix.max_fall,
312 : : });
313 : :
314 : : // Keep track of applied HDR colorimetry metadata
315 : 0 : p->color_space.hdr = p->hdr_metadata;
316 : : }
317 : :
318 : 1 : pl_swapchain pl_vulkan_create_swapchain(pl_vulkan plvk,
319 : : const struct pl_vulkan_swapchain_params *params)
320 : : {
321 : 1 : struct vk_ctx *vk = PL_PRIV(plvk);
322 : 1 : pl_gpu gpu = plvk->gpu;
323 : :
324 [ - + ]: 1 : if (!vk->CreateSwapchainKHR) {
325 : 0 : PL_ERR(gpu, VK_KHR_SWAPCHAIN_EXTENSION_NAME " not enabled!");
326 : 0 : return NULL;
327 : : }
328 : :
329 : 1 : struct pl_swapchain_t *sw = pl_zalloc_obj(NULL, sw, struct priv);
330 : 1 : sw->log = vk->log;
331 : 1 : sw->gpu = gpu;
332 : :
333 : 1 : struct priv *p = PL_PRIV(sw);
334 [ - + ]: 1 : pl_mutex_init(&p->lock);
335 : 1 : p->impl = vulkan_swapchain;
336 : 1 : p->params = *params;
337 : 1 : p->vk = vk;
338 : 1 : p->surf = params->surface;
339 [ - + ]: 1 : p->swapchain_depth = PL_DEF(params->swapchain_depth, 3);
340 [ - + ]: 1 : pl_assert(p->swapchain_depth > 0);
341 : 1 : atomic_init(&p->frames_in_flight, 0);
342 : 1 : p->last_imgidx = -1;
343 : 1 : p->protoInfo = (VkSwapchainCreateInfoKHR) {
344 : : .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
345 : 1 : .surface = p->surf,
346 : : .imageArrayLayers = 1, // non-stereoscopic
347 : : .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
348 : 1 : .minImageCount = p->swapchain_depth + 1, // +1 for the FB
349 : 1 : .presentMode = params->present_mode,
350 : : .clipped = true,
351 : : };
352 : :
353 : : // These fields will be updated by `vk_sw_recreate`
354 : 1 : p->color_space = pl_color_space_unknown;
355 : 1 : p->color_repr = (struct pl_color_repr) {
356 : : .sys = PL_COLOR_SYSTEM_RGB,
357 : : .levels = PL_COLOR_LEVELS_FULL,
358 : : .alpha = PL_ALPHA_UNKNOWN,
359 : : };
360 : :
361 : : // Make sure the swapchain present mode is supported
362 : : VkPresentModeKHR *modes = NULL;
363 : 1 : uint32_t num_modes = 0;
364 [ - + ]: 1 : VK(vk->GetPhysicalDeviceSurfacePresentModesKHR(vk->physd, p->surf, &num_modes, NULL));
365 : 1 : modes = pl_calloc_ptr(NULL, num_modes, modes);
366 [ - + ]: 1 : VK(vk->GetPhysicalDeviceSurfacePresentModesKHR(vk->physd, p->surf, &num_modes, modes));
367 : :
368 : : bool supported = false;
369 [ + + ]: 3 : for (int i = 0; i < num_modes; i++)
370 : 2 : supported |= (modes[i] == p->protoInfo.presentMode);
371 : 1 : pl_free_ptr(&modes);
372 : :
373 [ + - ]: 1 : if (!supported) {
374 : 1 : PL_WARN(vk, "Requested swap mode unsupported by this device, falling "
375 : : "back to VK_PRESENT_MODE_FIFO_KHR");
376 : 1 : p->protoInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR;
377 : : }
378 : :
379 : : // Enumerate the supported surface color spaces
380 : 1 : uint32_t num_formats = 0;
381 [ - + ]: 1 : VK(vk->GetPhysicalDeviceSurfaceFormatsKHR(vk->physd, p->surf, &num_formats, NULL));
382 [ + - ]: 1 : PL_ARRAY_RESIZE(sw, p->formats, num_formats);
383 [ - + ]: 1 : VK(vk->GetPhysicalDeviceSurfaceFormatsKHR(vk->physd, p->surf, &num_formats, p->formats.elem));
384 : 1 : p->formats.num = num_formats;
385 : :
386 : 1 : PL_INFO(gpu, "Available surface configurations:");
387 [ + + ]: 3 : for (int i = 0; i < p->formats.num; i++) {
388 : 2 : PL_INFO(gpu, " %d: %-40s %s", i,
389 : : vk_fmt_name(p->formats.elem[i].format),
390 : : vk_csp_name(p->formats.elem[i].colorSpace));
391 : : }
392 : :
393 : : // Ensure there exists at least some valid renderable surface format
394 : 1 : struct pl_color_space hint = {0};
395 [ - + ]: 1 : if (!pick_surf_format(sw, &hint))
396 : 0 : goto error;
397 : :
398 : : return sw;
399 : :
400 : 0 : error:
401 : 0 : pl_free(modes);
402 : 0 : pl_free(sw);
403 : 0 : return NULL;
404 : : }
405 : :
406 : 1 : static void vk_sw_destroy(pl_swapchain sw)
407 : : {
408 : 1 : pl_gpu gpu = sw->gpu;
409 : 1 : struct priv *p = PL_PRIV(sw);
410 : 1 : struct vk_ctx *vk = p->vk;
411 : :
412 : 1 : pl_gpu_flush(gpu);
413 : 1 : vk_wait_idle(vk);
414 : :
415 : : // Vulkan offers no way to know when a queue presentation command is done,
416 : : // leading to spec-mandated undefined behavior when destroying resources
417 : : // tied to the swapchain. Use an extra `vkQueueWaitIdle` on all of the
418 : : // queues we may have oustanding presentation calls on, to hopefully inform
419 : : // the driver that we want to wait until the device is truly idle.
420 [ + + ]: 2 : for (int i = 0; i < vk->pool_graphics->num_queues; i++)
421 : 1 : vk->QueueWaitIdle(vk->pool_graphics->queues[i]);
422 : :
423 [ + + ]: 5 : for (int i = 0; i < p->images.num; i++)
424 : 4 : pl_tex_destroy(gpu, &p->images.elem[i]);
425 [ + + ]: 5 : for (int i = 0; i < p->sems.num; i++) {
426 : 4 : vk->DestroySemaphore(vk->dev, p->sems.elem[i].in, PL_VK_ALLOC);
427 : 4 : vk->DestroySemaphore(vk->dev, p->sems.elem[i].out, PL_VK_ALLOC);
428 : : }
429 : :
430 : 1 : vk->DestroySwapchainKHR(vk->dev, p->swapchain, PL_VK_ALLOC);
431 : 1 : pl_mutex_destroy(&p->lock);
432 : 1 : pl_free((void *) sw);
433 : 1 : }
434 : :
435 : 0 : static int vk_sw_latency(pl_swapchain sw)
436 : : {
437 : 0 : struct priv *p = PL_PRIV(sw);
438 : 0 : return p->swapchain_depth;
439 : : }
440 : :
441 : 2 : static bool update_swapchain_info(struct priv *p, VkSwapchainCreateInfoKHR *info,
442 : : int w, int h)
443 : : {
444 : 2 : struct vk_ctx *vk = p->vk;
445 : :
446 : : // Query the supported capabilities and update this struct as needed
447 : 2 : VkSurfaceCapabilitiesKHR caps = {0};
448 [ - + ]: 2 : VK(vk->GetPhysicalDeviceSurfaceCapabilitiesKHR(vk->physd, p->surf, &caps));
449 : :
450 : : // Check for hidden/invisible window
451 [ + - - + ]: 2 : if (!caps.currentExtent.width || !caps.currentExtent.height) {
452 : 0 : PL_DEBUG(vk, "maxImageExtent reported as 0x0, hidden window? skipping");
453 : 0 : return false;
454 : : }
455 : :
456 : : // Sorted by preference
457 : : static const struct { VkCompositeAlphaFlagsKHR vk_mode;
458 : : enum pl_alpha_mode pl_mode;
459 : : } alphaModes[] = {
460 : : {VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR, PL_ALPHA_INDEPENDENT},
461 : : {VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR, PL_ALPHA_PREMULTIPLIED},
462 : : {VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, PL_ALPHA_UNKNOWN},
463 : : {VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR, PL_ALPHA_UNKNOWN},
464 : : };
465 : :
466 [ + - ]: 4 : for (int i = 0; i < PL_ARRAY_SIZE(alphaModes); i++) {
467 [ + + ]: 4 : if (caps.supportedCompositeAlpha & alphaModes[i].vk_mode) {
468 : 2 : info->compositeAlpha = alphaModes[i].vk_mode;
469 : 2 : p->color_repr.alpha = alphaModes[i].pl_mode;
470 : 2 : PL_DEBUG(vk, "Requested alpha compositing mode: %s",
471 : : vk_alpha_mode(info->compositeAlpha));
472 : 2 : break;
473 : : }
474 : : }
475 : :
476 [ - + ]: 2 : if (!info->compositeAlpha) {
477 : 0 : PL_ERR(vk, "Failed picking alpha compositing mode (caps: 0x%x)",
478 : : caps.supportedCompositeAlpha);
479 : 0 : goto error;
480 : : }
481 : :
482 : : // Note: We could probably also allow picking a surface transform that
483 : : // flips the framebuffer and set `pl_swapchain_frame.flipped`, but this
484 : : // doesn't appear to be necessary for any vulkan implementations.
485 : : static const VkSurfaceTransformFlagsKHR rotModes[] = {
486 : : VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
487 : : VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR,
488 : : };
489 : :
490 [ + - ]: 2 : for (int i = 0; i < PL_ARRAY_SIZE(rotModes); i++) {
491 [ + - ]: 2 : if (caps.supportedTransforms & rotModes[i]) {
492 : 2 : info->preTransform = rotModes[i];
493 : 2 : PL_DEBUG(vk, "Requested surface transform: %s",
494 : : vk_surface_transform(info->preTransform));
495 : 2 : break;
496 : : }
497 : : }
498 : :
499 [ - + ]: 2 : if (!info->preTransform) {
500 : 0 : PL_ERR(vk, "Failed picking surface transform mode (caps: 0x%x)",
501 : : caps.supportedTransforms);
502 : 0 : goto error;
503 : : }
504 : :
505 : : // Image count as required
506 : 2 : PL_DEBUG(vk, "Requested image count: %d (min %d max %d)",
507 : : (int) info->minImageCount, (int) caps.minImageCount,
508 : : (int) caps.maxImageCount);
509 : :
510 : 2 : info->minImageCount = PL_MAX(info->minImageCount, caps.minImageCount);
511 [ - + ]: 2 : if (caps.maxImageCount)
512 : 0 : info->minImageCount = PL_MIN(info->minImageCount, caps.maxImageCount);
513 : :
514 : 2 : PL_DEBUG(vk, "Requested image size: %dx%d (min %dx%d < cur %dx%d < max %dx%d)",
515 : : w, h, caps.minImageExtent.width, caps.minImageExtent.height,
516 : : caps.currentExtent.width, caps.currentExtent.height,
517 : : caps.maxImageExtent.width, caps.maxImageExtent.height);
518 : :
519 : : // Default the requested size based on the reported extent
520 [ - + ]: 2 : if (caps.currentExtent.width != 0xFFFFFFFF)
521 [ # # ]: 0 : w = PL_DEF(w, caps.currentExtent.width);
522 [ - + ]: 2 : if (caps.currentExtent.height != 0xFFFFFFFF)
523 [ # # ]: 0 : h = PL_DEF(h, caps.currentExtent.height);
524 : :
525 : : // Otherwise, re-use the existing size if available
526 [ - + ]: 2 : w = PL_DEF(w, info->imageExtent.width);
527 [ - + ]: 2 : h = PL_DEF(h, info->imageExtent.height);
528 : :
529 [ - + ]: 2 : if (!w || !h) {
530 : 0 : PL_ERR(vk, "Failed resizing swapchain: unknown size?");
531 : 0 : goto error;
532 : : }
533 : :
534 : : // Clamp the extent based on the supported limits
535 [ - + ]: 2 : w = PL_CLAMP(w, caps.minImageExtent.width, caps.maxImageExtent.width);
536 [ - + ]: 2 : h = PL_CLAMP(h, caps.minImageExtent.height, caps.maxImageExtent.height);
537 : 2 : info->imageExtent = (VkExtent2D) { w, h };
538 : :
539 : : // We just request whatever makes sense, and let the pl_vk decide what
540 : : // pl_tex_params that translates to. That said, we still need to intersect
541 : : // the swapchain usage flags with the format usage flags
542 : : VkImageUsageFlags req_flags = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
543 : : VK_IMAGE_USAGE_TRANSFER_DST_BIT;
544 : : VkImageUsageFlags opt_flags = VK_IMAGE_USAGE_STORAGE_BIT;
545 : :
546 : 2 : info->imageUsage = caps.supportedUsageFlags & (req_flags | opt_flags);
547 : 2 : VkFormatProperties fmtprop = {0};
548 : 2 : vk->GetPhysicalDeviceFormatProperties(vk->physd, info->imageFormat, &fmtprop);
549 : :
550 : : #define CHECK(usage, feature) \
551 : : if (!((fmtprop.optimalTilingFeatures & VK_FORMAT_FEATURE_##feature##_BIT))) \
552 : : info->imageUsage &= ~VK_IMAGE_USAGE_##usage##_BIT
553 : :
554 [ - + ]: 2 : CHECK(COLOR_ATTACHMENT, COLOR_ATTACHMENT);
555 [ - + ]: 2 : CHECK(TRANSFER_DST, TRANSFER_DST);
556 [ - + ]: 2 : CHECK(STORAGE, STORAGE_IMAGE);
557 : :
558 [ - + ]: 2 : if ((info->imageUsage & req_flags) != req_flags) {
559 : 0 : PL_ERR(vk, "The swapchain doesn't support rendering and blitting!");
560 : 0 : goto error;
561 : : }
562 : :
563 : : return true;
564 : :
565 : : error:
566 : : return false;
567 : : }
568 : :
569 : : static void destroy_swapchain(struct vk_ctx *vk, void *swapchain)
570 : : {
571 : 2 : vk->DestroySwapchainKHR(vk->dev, vk_unwrap_handle(swapchain), PL_VK_ALLOC);
572 : : }
573 : :
574 : 2 : VK_CB_FUNC_DEF(destroy_swapchain);
575 : :
576 : 2 : static bool vk_sw_recreate(pl_swapchain sw, int w, int h)
577 : : {
578 : 2 : pl_gpu gpu = sw->gpu;
579 : 2 : struct priv *p = PL_PRIV(sw);
580 : 2 : struct vk_ctx *vk = p->vk;
581 : :
582 : : VkImage *vkimages = NULL;
583 : 2 : uint32_t num_images = 0;
584 : :
585 [ - + ]: 2 : if (!update_swapchain_info(p, &p->protoInfo, w, h))
586 : : return false;
587 : :
588 : 2 : VkSwapchainCreateInfoKHR sinfo = p->protoInfo;
589 : : #ifdef VK_EXT_full_screen_exclusive
590 : : // Explicitly disallow full screen exclusive mode if possible
591 : : static const VkSurfaceFullScreenExclusiveInfoEXT fsinfo = {
592 : : .sType = VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT,
593 : : .fullScreenExclusive = VK_FULL_SCREEN_EXCLUSIVE_DISALLOWED_EXT,
594 : : };
595 : : if (vk->AcquireFullScreenExclusiveModeEXT)
596 : : vk_link_struct(&sinfo, &fsinfo);
597 : : #endif
598 : :
599 : 2 : p->suboptimal = false;
600 : 2 : p->needs_recreate = false;
601 : 2 : p->cur_width = sinfo.imageExtent.width;
602 : 2 : p->cur_height = sinfo.imageExtent.height;
603 : :
604 : 2 : PL_DEBUG(sw, "(Re)creating swapchain of size %dx%d",
605 : : sinfo.imageExtent.width,
606 : : sinfo.imageExtent.height);
607 : :
608 : : #ifdef PL_HAVE_UNIX
609 [ - + ]: 2 : if (vk->props.vendorID == VK_VENDOR_ID_NVIDIA) {
610 : 0 : vk->DeviceWaitIdle(vk->dev);
611 : 0 : vk_wait_idle(vk);
612 : : }
613 : : #endif
614 : :
615 : : // Calling `vkCreateSwapchainKHR` puts sinfo.oldSwapchain into a retired
616 : : // state whether the call succeeds or not, so we always need to garbage
617 : : // collect it afterwards - asynchronously as it may still be in use
618 : 2 : sinfo.oldSwapchain = p->swapchain;
619 : 2 : p->swapchain = VK_NULL_HANDLE;
620 : 2 : VkResult res = vk->CreateSwapchainKHR(vk->dev, &sinfo, PL_VK_ALLOC, &p->swapchain);
621 : 2 : vk_dev_callback(vk, VK_CB_FUNC(destroy_swapchain), vk, vk_wrap_handle(sinfo.oldSwapchain));
622 [ - + ]: 2 : PL_VK_ASSERT(res, "vk->CreateSwapchainKHR(...)");
623 : :
624 : : // Get the new swapchain images
625 [ - + ]: 2 : VK(vk->GetSwapchainImagesKHR(vk->dev, p->swapchain, &num_images, NULL));
626 : 2 : vkimages = pl_calloc_ptr(NULL, num_images, vkimages);
627 [ - + ]: 2 : VK(vk->GetSwapchainImagesKHR(vk->dev, p->swapchain, &num_images, vkimages));
628 : :
629 [ + + ]: 10 : for (int i = 0; i < num_images; i++)
630 [ + - ]: 8 : PL_VK_NAME(IMAGE, vkimages[i], "swapchain");
631 : :
632 : : // If needed, allocate some more semaphores
633 [ + + ]: 6 : while (num_images > p->sems.num) {
634 : 4 : VkSemaphore sem_in = VK_NULL_HANDLE, sem_out = VK_NULL_HANDLE;
635 : : static const VkSemaphoreCreateInfo seminfo = {
636 : : .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
637 : : };
638 [ - + ]: 4 : VK(vk->CreateSemaphore(vk->dev, &seminfo, PL_VK_ALLOC, &sem_in));
639 [ - + ]: 4 : VK(vk->CreateSemaphore(vk->dev, &seminfo, PL_VK_ALLOC, &sem_out));
640 [ + - ]: 4 : PL_VK_NAME(SEMAPHORE, sem_in, "swapchain in");
641 [ + - ]: 4 : PL_VK_NAME(SEMAPHORE, sem_out, "swapchain out");
642 : :
643 [ + + - + : 4 : PL_ARRAY_APPEND(sw, p->sems, (struct sem_pair) {
- + ]
644 : : .in = sem_in,
645 : : .out = sem_out,
646 : : });
647 : : }
648 : :
649 : : // Recreate the pl_tex wrappers
650 [ + + ]: 6 : for (int i = 0; i < p->images.num; i++)
651 : 4 : pl_tex_destroy(gpu, &p->images.elem[i]);
652 : 2 : p->images.num = 0;
653 : :
654 [ + + ]: 10 : for (int i = 0; i < num_images; i++) {
655 : : const VkExtent2D *ext = &sinfo.imageExtent;
656 : 8 : pl_tex tex = pl_vulkan_wrap(gpu, pl_vulkan_wrap_params(
657 : : .image = vkimages[i],
658 : : .width = ext->width,
659 : : .height = ext->height,
660 : : .format = sinfo.imageFormat,
661 : : .usage = sinfo.imageUsage,
662 : : ));
663 [ - + ]: 8 : if (!tex)
664 : 0 : goto error;
665 [ + + - + : 8 : PL_ARRAY_APPEND(sw, p->images, tex);
- + ]
666 : : }
667 : :
668 [ - + ]: 2 : pl_assert(num_images > 0);
669 : : int bits = 0;
670 : :
671 : : // The channel with the most bits is probably the most authoritative about
672 : : // the actual color information (consider e.g. a2bgr10). Slight downside
673 : : // in that it results in rounding r/b for e.g. rgb565, but we don't pick
674 : : // surfaces with fewer than 8 bits anyway, so let's not care for now.
675 : 2 : pl_fmt fmt = p->images.elem[0]->params.format;
676 [ + + ]: 10 : for (int i = 0; i < fmt->num_components; i++)
677 : 8 : bits = PL_MAX(bits, fmt->component_depth[i]);
678 : :
679 : 2 : p->color_repr.bits.sample_depth = bits;
680 : 2 : p->color_repr.bits.color_depth = bits;
681 : :
682 : : // Note: `p->color_space.hdr` is (re-)applied by `set_hdr_metadata`
683 : 2 : map_color_space(sinfo.imageColorSpace, &p->color_space);
684 : :
685 : : // Forcibly re-apply HDR metadata, bypassing the no-op check
686 : 2 : struct pl_hdr_metadata metadata = p->hdr_metadata;
687 : 2 : p->hdr_metadata = pl_hdr_metadata_empty;
688 : 2 : set_hdr_metadata(p, &metadata);
689 : :
690 : 2 : pl_free(vkimages);
691 : 2 : return true;
692 : :
693 : 0 : error:
694 : 0 : PL_ERR(vk, "Failed (re)creating swapchain!");
695 : 0 : pl_free(vkimages);
696 : 0 : vk->DestroySwapchainKHR(vk->dev, p->swapchain, PL_VK_ALLOC);
697 : 0 : p->swapchain = VK_NULL_HANDLE;
698 : 0 : p->cur_width = p->cur_height = 0;
699 : 0 : return false;
700 : : }
701 : :
702 : 10 : static bool vk_sw_start_frame(pl_swapchain sw,
703 : : struct pl_swapchain_frame *out_frame)
704 : : {
705 : 10 : struct priv *p = PL_PRIV(sw);
706 : 10 : struct vk_ctx *vk = p->vk;
707 : 10 : pl_mutex_lock(&p->lock);
708 : :
709 [ + - - + ]: 10 : bool recreate = !p->swapchain || p->needs_recreate;
710 [ - + - - ]: 10 : if (p->suboptimal && !p->params.allow_suboptimal)
711 : : recreate = true;
712 : :
713 [ - + - - ]: 10 : if (recreate && !vk_sw_recreate(sw, 0, 0)) {
714 : 0 : pl_mutex_unlock(&p->lock);
715 : 0 : return false;
716 : : }
717 : :
718 : 10 : VkSemaphore sem_in = p->sems.elem[p->idx_sems].in;
719 : 10 : PL_TRACE(vk, "vkAcquireNextImageKHR signals 0x%"PRIx64, (uint64_t) sem_in);
720 : :
721 [ + - ]: 10 : for (int attempts = 0; attempts < 2; attempts++) {
722 : 10 : uint32_t imgidx = 0;
723 : 10 : VkResult res = vk->AcquireNextImageKHR(vk->dev, p->swapchain, UINT64_MAX,
724 : : sem_in, VK_NULL_HANDLE, &imgidx);
725 : :
726 [ - + - - ]: 10 : switch (res) {
727 : 0 : case VK_SUBOPTIMAL_KHR:
728 : 0 : p->suboptimal = true;
729 : : // fall through
730 : 10 : case VK_SUCCESS:
731 : 10 : p->last_imgidx = imgidx;
732 : 10 : pl_vulkan_release_ex(sw->gpu, pl_vulkan_release_params(
733 : : .tex = p->images.elem[imgidx],
734 : : .layout = VK_IMAGE_LAYOUT_UNDEFINED,
735 : : .qf = VK_QUEUE_FAMILY_IGNORED,
736 : : .semaphore = { sem_in },
737 : : ));
738 : 10 : *out_frame = (struct pl_swapchain_frame) {
739 : 10 : .fbo = p->images.elem[imgidx],
740 : : .flipped = false,
741 : : .color_repr = p->color_repr,
742 : : .color_space = p->color_space,
743 : : };
744 : : // keep lock held
745 : 10 : return true;
746 : :
747 : 0 : case VK_ERROR_OUT_OF_DATE_KHR: {
748 : : // In these cases try recreating the swapchain
749 [ # # ]: 0 : if (!vk_sw_recreate(sw, 0, 0)) {
750 : 0 : pl_mutex_unlock(&p->lock);
751 : 0 : return false;
752 : : }
753 : 0 : continue;
754 : : }
755 : :
756 : 0 : default:
757 : 0 : PL_ERR(vk, "Failed acquiring swapchain image: %s", vk_res_str(res));
758 : 0 : pl_mutex_unlock(&p->lock);
759 : 0 : return false;
760 : : }
761 : : }
762 : :
763 : : // If we've exhausted the number of attempts to recreate the swapchain,
764 : : // just give up silently and let the user retry some time later.
765 : 0 : pl_mutex_unlock(&p->lock);
766 : 0 : return false;
767 : : }
768 : :
769 : : static void present_cb(struct priv *p, void *arg)
770 : : {
771 : 10 : (void) pl_rc_deref(&p->frames_in_flight);
772 : : }
773 : :
774 : 10 : VK_CB_FUNC_DEF(present_cb);
775 : :
776 : 10 : static bool vk_sw_submit_frame(pl_swapchain sw)
777 : : {
778 : 10 : pl_gpu gpu = sw->gpu;
779 : 10 : struct priv *p = PL_PRIV(sw);
780 : 10 : struct vk_ctx *vk = p->vk;
781 [ - + ]: 10 : pl_assert(p->last_imgidx >= 0);
782 [ - + ]: 10 : pl_assert(p->swapchain);
783 : 10 : uint32_t idx = p->last_imgidx;
784 : 10 : VkSemaphore sem_out = p->sems.elem[p->idx_sems++].out;
785 : 10 : p->idx_sems %= p->sems.num;
786 : 10 : p->last_imgidx = -1;
787 : :
788 : 10 : bool held = pl_vulkan_hold_ex(gpu, pl_vulkan_hold_params(
789 : : .tex = p->images.elem[idx],
790 : : .layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
791 : : .qf = VK_QUEUE_FAMILY_IGNORED,
792 : : .semaphore = { sem_out },
793 : : ));
794 : :
795 [ - + ]: 10 : if (!held) {
796 : 0 : PL_ERR(gpu, "Failed holding swapchain image for presentation");
797 : 0 : pl_mutex_unlock(&p->lock);
798 : 0 : return false;
799 : : }
800 : :
801 : 10 : struct vk_cmd *cmd = pl_vk_steal_cmd(gpu);
802 [ - + ]: 10 : if (!cmd) {
803 : 0 : pl_mutex_unlock(&p->lock);
804 : 0 : return false;
805 : : }
806 : :
807 : 10 : pl_rc_ref(&p->frames_in_flight);
808 : 10 : vk_cmd_callback(cmd, VK_CB_FUNC(present_cb), p, NULL);
809 [ - + ]: 10 : if (!vk_cmd_submit(&cmd)) {
810 : 0 : pl_mutex_unlock(&p->lock);
811 : 0 : return false;
812 : : }
813 : :
814 : 10 : struct vk_cmdpool *pool = vk->pool_graphics;
815 : 10 : int qidx = pool->idx_queues;
816 : 10 : VkQueue queue = pool->queues[qidx];
817 : :
818 : 10 : vk_rotate_queues(p->vk);
819 : 10 : vk_malloc_garbage_collect(vk->ma);
820 : :
821 : 10 : VkPresentInfoKHR pinfo = {
822 : : .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
823 : : .waitSemaphoreCount = 1,
824 : : .pWaitSemaphores = &sem_out,
825 : : .swapchainCount = 1,
826 : 10 : .pSwapchains = &p->swapchain,
827 : : .pImageIndices = &idx,
828 : : };
829 : :
830 : 10 : PL_TRACE(vk, "vkQueuePresentKHR waits on 0x%"PRIx64, (uint64_t) sem_out);
831 : 10 : vk->lock_queue(vk->queue_ctx, pool->qf, qidx);
832 : 10 : VkResult res = vk->QueuePresentKHR(queue, &pinfo);
833 : 10 : vk->unlock_queue(vk->queue_ctx, pool->qf, qidx);
834 : 10 : pl_mutex_unlock(&p->lock);
835 : :
836 [ - - + ]: 10 : switch (res) {
837 : 0 : case VK_SUBOPTIMAL_KHR:
838 : 0 : p->suboptimal = true;
839 : : // fall through
840 : : case VK_SUCCESS:
841 : : return true;
842 : :
843 : : case VK_ERROR_OUT_OF_DATE_KHR:
844 : : // We can silently ignore this error, since the next start_frame will
845 : : // recreate the swapchain automatically.
846 : : return true;
847 : :
848 : 0 : default:
849 : 0 : PL_ERR(vk, "Failed presenting to queue %p: %s", (void *) queue,
850 : : vk_res_str(res));
851 : 0 : return false;
852 : : }
853 : : }
854 : :
855 : 10 : static void vk_sw_swap_buffers(pl_swapchain sw)
856 : : {
857 : 10 : struct priv *p = PL_PRIV(sw);
858 : :
859 : 10 : pl_mutex_lock(&p->lock);
860 [ - + ]: 10 : while (pl_rc_count(&p->frames_in_flight) >= p->swapchain_depth) {
861 : 0 : pl_mutex_unlock(&p->lock); // don't hold mutex while blocking
862 : 0 : vk_poll_commands(p->vk, UINT64_MAX);
863 : 0 : pl_mutex_lock(&p->lock);
864 : : }
865 : 10 : pl_mutex_unlock(&p->lock);
866 : 10 : }
867 : :
868 : 2 : static bool vk_sw_resize(pl_swapchain sw, int *width, int *height)
869 : : {
870 : 2 : struct priv *p = PL_PRIV(sw);
871 : : bool ok = true;
872 : :
873 : 2 : pl_mutex_lock(&p->lock);
874 : :
875 [ + - - + ]: 2 : bool width_changed = *width && *width != p->cur_width,
876 [ + - - + ]: 2 : height_changed = *height && *height != p->cur_height;
877 : :
878 [ + + + - ]: 2 : if (p->suboptimal || p->needs_recreate || width_changed || height_changed)
879 : 2 : ok = vk_sw_recreate(sw, *width, *height);
880 : :
881 : 2 : *width = p->cur_width;
882 : 2 : *height = p->cur_height;
883 : :
884 : 2 : pl_mutex_unlock(&p->lock);
885 : 2 : return ok;
886 : : }
887 : :
888 : 0 : static void vk_sw_colorspace_hint(pl_swapchain sw, const struct pl_color_space *csp)
889 : : {
890 : 0 : struct priv *p = PL_PRIV(sw);
891 : 0 : pl_mutex_lock(&p->lock);
892 : :
893 : : // This should never fail if the swapchain already exists
894 : 0 : bool ok = pick_surf_format(sw, csp);
895 : 0 : set_hdr_metadata(p, &csp->hdr);
896 [ # # ]: 0 : pl_assert(ok);
897 : :
898 : 0 : pl_mutex_unlock(&p->lock);
899 : 0 : }
900 : :
901 : 0 : bool pl_vulkan_swapchain_suboptimal(pl_swapchain sw)
902 : : {
903 : 0 : struct priv *p = PL_PRIV(sw);
904 : 0 : return p->suboptimal;
905 : : }
906 : :
907 : : static const struct pl_sw_fns vulkan_swapchain = {
908 : : .destroy = vk_sw_destroy,
909 : : .latency = vk_sw_latency,
910 : : .resize = vk_sw_resize,
911 : : .colorspace_hint = vk_sw_colorspace_hint,
912 : : .start_frame = vk_sw_start_frame,
913 : : .submit_frame = vk_sw_submit_frame,
914 : : .swap_buffers = vk_sw_swap_buffers,
915 : : };
|