1 /*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "swapchain_vk.h"
17
18 #include <algorithm>
19 #include <cstddef>
20 #include <cstdint>
21 #include <vulkan/vulkan_core.h>
22
23 #include <base/containers/vector.h>
24 #include <base/math/mathf.h>
25 #include <base/util/formats.h>
26 #include <core/intf_engine.h>
27 #include <render/device/gpu_resource_desc.h>
28 #include <render/namespace.h>
29 #include <render/vulkan/intf_device_vk.h>
30
31 #include "util/log.h"
32 #include "vulkan/create_functions_vk.h"
33 #include "vulkan/device_vk.h"
34 #include "vulkan/validate_vk.h"
35
36 using namespace BASE_NS;
37
38 RENDER_BEGIN_NAMESPACE()
39 namespace {
GetValidDepthFormat(const DeviceVk & deviceVk)40 Format GetValidDepthFormat(const DeviceVk& deviceVk)
41 {
42 constexpr uint32_t PREFERRED_FORMAT_COUNT { 3 };
43 constexpr Format preferredFormats[PREFERRED_FORMAT_COUNT] = { BASE_FORMAT_D24_UNORM_S8_UINT, BASE_FORMAT_D32_SFLOAT,
44 BASE_FORMAT_D16_UNORM };
45 #ifndef NDEBUG
46 constexpr string_view PREFERRED_FORMAT_NAMES[PREFERRED_FORMAT_COUNT] = { "BASE_FORMAT_D24_UNORM_S8_UINT",
47 "BASE_FORMAT_D32_SFLOAT", "BASE_FORMAT_D16_UNORM" };
48 #endif
49 Format finalFormat = BASE_FORMAT_UNDEFINED;
50 const auto& devPlat = deviceVk.GetPlatformInternalDataVk();
51 for (uint32_t idx = 0; idx < PREFERRED_FORMAT_COUNT; ++idx) {
52 finalFormat = preferredFormats[idx];
53 for (const auto& supportedDepthFormat : devPlat.supportedDepthFormats) {
54 if (finalFormat == supportedDepthFormat) {
55 #ifndef NDEBUG
56 PLUGIN_LOG_D(
57 "selected CORE_DEFAULT_BACKBUFFER format: %s", string(PREFERRED_FORMAT_NAMES[idx]).c_str());
58 #endif
59 idx = PREFERRED_FORMAT_COUNT;
60 break;
61 }
62 }
63 }
64 return finalFormat;
65 }
66
67 struct ColorInfo {
68 VkFormat format { VK_FORMAT_UNDEFINED };
69 VkColorSpaceKHR colorSpace { VK_COLOR_SPACE_MAX_ENUM_KHR };
70 };
71
GetColorFormat(const uint32_t flags,const vector<VkSurfaceFormatKHR> & surfaceFormats)72 VkFormat GetColorFormat(const uint32_t flags, const vector<VkSurfaceFormatKHR>& surfaceFormats)
73 {
74 constexpr uint32_t preferredFormatCount { 4u };
75 constexpr VkFormat srgbFormats[preferredFormatCount] = { VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_B8G8R8A8_SRGB,
76 VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8A8_UNORM };
77 constexpr VkFormat nonSrgbFormats[preferredFormatCount] = { VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8A8_UNORM,
78 VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_B8G8R8A8_SRGB };
79
80 const bool preferSrgbFormat = (flags & SwapchainFlagBits::CORE_SWAPCHAIN_SRGB_BIT);
81 const array_view<const VkFormat> formats =
82 (preferSrgbFormat) ? array_view<const VkFormat> { srgbFormats, preferredFormatCount }
83 : array_view<const VkFormat> { nonSrgbFormats, preferredFormatCount };
84
85 // If pSurfaceFormats includes just one entry, whose value for format is VK_FORMAT_UNDEFINED,
86 // surface has no preferred format. In this case, the application can use any valid VkFormat value.
87 if (surfaceFormats[0].format == VK_FORMAT_UNDEFINED && surfaceFormats.size() == 1) {
88 return VK_FORMAT_R8G8B8A8_SRGB;
89 }
90
91 for (auto format : formats) {
92 for (auto surfaceFormat : surfaceFormats) {
93 if (format == surfaceFormat.format) {
94 return surfaceFormat.format;
95 }
96 }
97 }
98 return VK_FORMAT_UNDEFINED;
99 }
100
GetColorInfo(const VkPhysicalDevice physicalDevice,const VkSurfaceKHR surface,const uint32_t flags)101 ColorInfo GetColorInfo(const VkPhysicalDevice physicalDevice, const VkSurfaceKHR surface, const uint32_t flags)
102 {
103 // Pick a color format for the swapchain.
104 uint32_t surfaceFormatsCount = 0;
105 VALIDATE_VK_RESULT(vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &surfaceFormatsCount, nullptr));
106
107 vector<VkSurfaceFormatKHR> surfaceFormats(surfaceFormatsCount);
108 VALIDATE_VK_RESULT(
109 vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &surfaceFormatsCount, surfaceFormats.data()));
110
111 ColorInfo ci;
112 ci.format = GetColorFormat(flags, surfaceFormats);
113 ci.colorSpace = VK_COLOR_SPACE_MAX_ENUM_KHR;
114 for (auto& surfaceFormat : surfaceFormats) {
115 if (surfaceFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
116 ci.colorSpace = surfaceFormat.colorSpace;
117 break;
118 }
119 }
120 PLUGIN_ASSERT_MSG(ci.colorSpace != VK_COLOR_SPACE_MAX_ENUM_KHR, "colorspace not correct");
121
122 PLUGIN_ASSERT_MSG(ci.format != VK_FORMAT_UNDEFINED, "colorformat not correct");
123 PLUGIN_LOG_E("swapchainColorFormat: %u swapchainColorSpace %u", ci.format, ci.colorSpace);
124
125 return ci;
126 }
127
GetPresentMode(const VkPhysicalDevice physicalDevice,const VkSurfaceKHR surface,const uint32_t flags)128 VkPresentModeKHR GetPresentMode(const VkPhysicalDevice physicalDevice, const VkSurfaceKHR surface, const uint32_t flags)
129 {
130 // Pick a present mode for the swapchain.
131 uint32_t presentModeCount;
132 VALIDATE_VK_RESULT(vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, nullptr));
133
134 vector<VkPresentModeKHR> presentModes(presentModeCount);
135 VALIDATE_VK_RESULT(
136 vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, presentModes.data()));
137
138 VkPresentModeKHR swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR; // FIFO must be supported by the specification.
139 if ((flags & SwapchainFlagBits::CORE_SWAPCHAIN_VSYNC_BIT) != SwapchainFlagBits::CORE_SWAPCHAIN_VSYNC_BIT) {
140 // immediate is really without vsync, but it might not be supported, so we also check for mailbox.
141 if (std::any_of(presentModes.cbegin(), presentModes.cend(),
142 [](const VkPresentModeKHR& supported) { return supported == VK_PRESENT_MODE_IMMEDIATE_KHR; })) {
143 swapchainPresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
144 } else if (std::any_of(presentModes.cbegin(), presentModes.cend(),
145 [](const VkPresentModeKHR& supported) { return supported == VK_PRESENT_MODE_MAILBOX_KHR; })) {
146 swapchainPresentMode = VK_PRESENT_MODE_MAILBOX_KHR;
147 }
148 }
149
150 #if (RENDER_DEV_ENABLED == 1)
151 constexpr uint32_t strArraySize { 4 };
152 constexpr string_view presentModeStrings[strArraySize] = {
153 "VK_PRESENT_MODE_IMMEDIATE_KHR",
154 "VK_PRESENT_MODE_MAILBOX_KHR",
155 "VK_PRESENT_MODE_FIFO_KHR",
156 "VK_PRESENT_MODE_FIFO_RELAXED_KHR",
157 };
158
159 PLUGIN_LOG_I("Available swapchain present modes:");
160 for (auto const presentMode : presentModes) {
161 if ((uint32_t)presentMode < strArraySize) {
162 PLUGIN_LOG_I(" %s", presentModeStrings[presentMode].data());
163 }
164 }
165 PLUGIN_LOG_I("Selected swapchain present modes:");
166 if ((uint32_t)swapchainPresentMode < strArraySize) {
167 PLUGIN_LOG_I(" %s", presentModeStrings[swapchainPresentMode].data());
168 }
169 #else
170 PLUGIN_LOG_D("swapchainPresentMode: %x", swapchainPresentMode);
171 #endif
172
173 return swapchainPresentMode;
174 }
175
ClampSwapchainExtent(const VkSurfaceCapabilitiesKHR & surfaceCapabilities,VkExtent2D & extent)176 void ClampSwapchainExtent(const VkSurfaceCapabilitiesKHR& surfaceCapabilities, VkExtent2D& extent)
177 {
178 extent.width =
179 std::clamp(extent.width, surfaceCapabilities.minImageExtent.width, surfaceCapabilities.maxImageExtent.width);
180 extent.height =
181 std::clamp(extent.height, surfaceCapabilities.minImageExtent.height, surfaceCapabilities.maxImageExtent.height);
182
183 PLUGIN_LOG_D("swapchainExtent: %u x %u", extent.width, extent.height);
184 if ((extent.width == 0) || (extent.height == 0)) {
185 PLUGIN_LOG_E(
186 "zero sized swapchain cannot be created in vulkan (width: %u, height: %u)", extent.width, extent.height);
187 PLUGIN_LOG_E("using 1x1 swapchain");
188 PLUGIN_ASSERT(false);
189 extent.width = 1;
190 extent.height = 1;
191 }
192 }
193
GetColorDesc(const uint32_t width,const uint32_t height,const Format format,const ImageUsageFlags imageUsageFlags)194 constexpr GpuImageDesc GetColorDesc(
195 const uint32_t width, const uint32_t height, const Format format, const ImageUsageFlags imageUsageFlags)
196 {
197 return {
198 ImageType::CORE_IMAGE_TYPE_2D, // imageType
199 ImageViewType::CORE_IMAGE_VIEW_TYPE_2D, // imageViewType
200 format, // format
201 ImageTiling::CORE_IMAGE_TILING_OPTIMAL, // imageTiling
202 imageUsageFlags, // usageFlags
203 MemoryPropertyFlagBits::CORE_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, // memoryPropertyFlags
204 0, // createFlags
205 EngineImageCreationFlagBits::CORE_ENGINE_IMAGE_CREATION_DYNAMIC_BARRIERS |
206 EngineImageCreationFlagBits::CORE_ENGINE_IMAGE_CREATION_RESET_STATE_ON_FRAME_BORDERS, // engineCreationFlags
207 width, // width
208 height, // height
209 1, // depth
210 1, // mipCount
211 1, // layerCount
212 SampleCountFlagBits::CORE_SAMPLE_COUNT_1_BIT, // sampleCountFlags
213 {}, // componentMapping
214 };
215 }
216
GetDepthDesc(const uint32_t width,const uint32_t height,const Format format)217 constexpr GpuImageDesc GetDepthDesc(const uint32_t width, const uint32_t height, const Format format)
218 {
219 return {
220 ImageType::CORE_IMAGE_TYPE_2D, // imageType
221 ImageViewType::CORE_IMAGE_VIEW_TYPE_2D, // imageViewType
222 format, // format
223 ImageTiling::CORE_IMAGE_TILING_OPTIMAL, // imageTiling
224 ImageUsageFlagBits::CORE_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT |
225 ImageUsageFlagBits::CORE_IMAGE_USAGE_INPUT_ATTACHMENT_BIT |
226 ImageUsageFlagBits::CORE_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT, // usageFlags
227 MemoryPropertyFlagBits::CORE_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
228 MemoryPropertyFlagBits::CORE_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT, // memoryPropertyFlags
229 0, // createFlags
230 EngineImageCreationFlagBits::CORE_ENGINE_IMAGE_CREATION_DYNAMIC_BARRIERS, // engineCreationFlags
231 width, // width
232 height, // height
233 1, // depth
234 1, // mipCount
235 1, // layerCount
236 SampleCountFlagBits::CORE_SAMPLE_COUNT_1_BIT, // sampleCountFlags
237 {}, // componentMapping
238 };
239 }
240 } // namespace
241
SwapchainVk(Device & device,const SwapchainCreateInfo & swapchainCreateInfo)242 SwapchainVk::SwapchainVk(Device& device, const SwapchainCreateInfo& swapchainCreateInfo)
243 : device_(device), flags_(swapchainCreateInfo.swapchainFlags)
244 {
245 const auto& devicePlatformData = (const DevicePlatformDataVk&)device_.GetPlatformData();
246 auto const physicalDevice = devicePlatformData.physicalDevice;
247 // check for surface creation automatically
248 if ((swapchainCreateInfo.surfaceHandle == 0) && swapchainCreateInfo.window.window) {
249 CreateFunctionsVk::Window win { swapchainCreateInfo.window.instance, swapchainCreateInfo.window.window };
250 surface_ = CreateFunctionsVk::CreateSurface(devicePlatformData.instance, win);
251 ownsSurface_ = true;
252 } else {
253 surface_ = VulkanHandleCast<VkSurfaceKHR>(swapchainCreateInfo.surfaceHandle);
254 }
255
256 if (surface_ != VK_NULL_HANDLE) {
257 auto const vkDevice = devicePlatformData.device;
258
259 // Sanity check that the device can use the surface.
260 // NOTE: queuFamilyIndex hardcoded, should come via devicePlatformData?
261 VkBool32 surfaceSupported = VK_FALSE;
262 VALIDATE_VK_RESULT(vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, 0, surface_, &surfaceSupported));
263 PLUGIN_ASSERT_MSG(surfaceSupported != VK_FALSE, "physicalDevice doesn't support given surface");
264
265 const ColorInfo ci = GetColorInfo(physicalDevice, surface_, flags_);
266
267 const VkPresentModeKHR swapchainPresentMode = GetPresentMode(physicalDevice, surface_, flags_);
268 // Pick an extent, image count, and transform for the swapchain.
269 VkSurfaceCapabilitiesKHR surfaceCapabilities;
270 VALIDATE_VK_RESULT(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface_, &surfaceCapabilities));
271
272 // NOTE: how do we handle the special case of 0xffffffffff which means the extent should be defined by the
273 // swapchain?
274 VkExtent2D swapchainExtent = surfaceCapabilities.currentExtent;
275 ClampSwapchainExtent(surfaceCapabilities, swapchainExtent);
276 plat_.swapchainImages.width = swapchainExtent.width;
277 plat_.swapchainImages.height = swapchainExtent.height;
278
279 const DeviceConfiguration deviceConfig = device_.GetDeviceConfiguration();
280 // surfaceCapabilities.maxImageCount of zero means that there is no limit
281 const uint32_t imageCount =
282 (surfaceCapabilities.maxImageCount == 0)
283 ? (Math::max(surfaceCapabilities.minImageCount, deviceConfig.swapchainImageCount))
284 : (Math::min(surfaceCapabilities.maxImageCount,
285 Math::max(surfaceCapabilities.minImageCount, deviceConfig.swapchainImageCount)));
286 PLUGIN_LOG_D("swapchainImageCount: %u", imageCount);
287
288 const VkSurfaceTransformFlagsKHR swapchainTransform =
289 (surfaceCapabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
290 ? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR
291 : surfaceCapabilities.currentTransform;
292
293 const auto desiredUsageFlags = static_cast<VkImageUsageFlags>(swapchainCreateInfo.imageUsageFlags);
294 const VkImageUsageFlags imageUsageFlags = desiredUsageFlags & surfaceCapabilities.supportedUsageFlags;
295 PLUGIN_LOG_D("swapchain usage flags, selected: %u, desired: %u, capabilities: %u", imageUsageFlags,
296 desiredUsageFlags, surfaceCapabilities.supportedUsageFlags);
297
298 VkCompositeAlphaFlagBitsKHR compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
299 if (surfaceCapabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) {
300 compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
301 }
302
303 VkSwapchainCreateInfoKHR const vkSwapchainCreateInfo {
304 VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, // sType
305 nullptr, // pNext
306 0, // flags
307 surface_, // surface
308 imageCount, // minImageCount
309 ci.format, // imageFormat
310 ci.colorSpace, // imageColorSpace
311 swapchainExtent, // imageExtent
312 1, // imageArrayLayers
313 imageUsageFlags, // imageUsage
314 VK_SHARING_MODE_EXCLUSIVE, // imageSharingMode
315 0, // queueFamilyIndexCount
316 nullptr, // pQueueFamilyIndices
317 (VkSurfaceTransformFlagBitsKHR)swapchainTransform, // preTransform
318 compositeAlpha, // compositeAlpha
319 swapchainPresentMode, // presentMode
320 VK_TRUE, // clipped
321 VK_NULL_HANDLE, // oldSwapchain
322 };
323
324 VALIDATE_VK_RESULT(vkCreateSwapchainKHR(vkDevice, &vkSwapchainCreateInfo, nullptr, &plat_.swapchain));
325
326 {
327 uint32_t realImageCount = 0;
328 VALIDATE_VK_RESULT(vkGetSwapchainImagesKHR(vkDevice, // device
329 plat_.swapchain, // swapchain
330 &realImageCount, // pSwapchainImageCount
331 nullptr)); // pSwapchainImages
332
333 PLUGIN_LOG_D("swapchain realImageCount: %u", realImageCount);
334
335 plat_.swapchainImages.images.resize(realImageCount);
336 plat_.swapchainImages.imageViews.resize(realImageCount);
337 plat_.swapchainImages.semaphores.resize(realImageCount);
338
339 VALIDATE_VK_RESULT(vkGetSwapchainImagesKHR(vkDevice, // device
340 plat_.swapchain, // swapchain
341 &realImageCount, // pSwapchainImageCount
342 plat_.swapchainImages.images.data())); // pSwapchainImages
343
344 constexpr VkComponentMapping componentMapping {
345 VK_COMPONENT_SWIZZLE_IDENTITY, // r
346 VK_COMPONENT_SWIZZLE_IDENTITY, // g
347 VK_COMPONENT_SWIZZLE_IDENTITY, // b
348 VK_COMPONENT_SWIZZLE_IDENTITY, // a
349 };
350 constexpr VkImageSubresourceRange imageSubresourceRange {
351 VK_IMAGE_ASPECT_COLOR_BIT, // aspectMask
352 0, // baseMipLevel
353 1, // levelCount
354 0, // baseArrayLayer
355 1, // layerCount
356 };
357
358 constexpr VkSemaphoreCreateFlags semaphoreCreateFlags { 0 };
359 const VkSemaphoreCreateInfo semaphoreCreateInfo {
360 VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, // sType
361 nullptr, // pNext
362 semaphoreCreateFlags, // flags
363 };
364 for (uint32_t idx = 0; idx < plat_.swapchainImages.imageViews.size(); ++idx) {
365 const VkImageViewCreateInfo imageViewCreateInfo {
366 VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, // sType
367 nullptr, // pNext
368 0, // flags
369 plat_.swapchainImages.images[idx], // image
370 VK_IMAGE_VIEW_TYPE_2D, // viewType
371 ci.format, // format
372 componentMapping, // components
373 imageSubresourceRange // subresourceRange;
374 };
375 VALIDATE_VK_RESULT(vkCreateImageView(vkDevice, // device
376 &imageViewCreateInfo, // pCreateInfo
377 nullptr, // pAllocator
378 &plat_.swapchainImages.imageViews[idx])); // pView
379 VALIDATE_VK_RESULT(vkCreateSemaphore(vkDevice, // device
380 &semaphoreCreateInfo, // pCreateInfo
381 nullptr, // pAllocator
382 &plat_.swapchainImages.semaphores[idx])); // pSemaphore
383 }
384 }
385
386 desc_ = GetColorDesc(plat_.swapchainImages.width, plat_.swapchainImages.height, (Format)ci.format,
387 (ImageUsageFlags)imageUsageFlags);
388
389 if (flags_ & 0x2) {
390 const Format depthFormat = GetValidDepthFormat((const DeviceVk&)device_);
391 descDepthBuffer_ = GetDepthDesc(plat_.swapchainImages.width, plat_.swapchainImages.height, depthFormat);
392 }
393 } else {
394 PLUGIN_LOG_E("Invalid surface in swapchain creation");
395 }
396 }
397
~SwapchainVk()398 SwapchainVk::~SwapchainVk()
399 {
400 const auto& devicePlatformData = (const DevicePlatformDataVk&)device_.GetPlatformData();
401 const VkDevice device = devicePlatformData.device;
402 for (auto const imageView : plat_.swapchainImages.imageViews) {
403 if (imageView) {
404 vkDestroyImageView(device, // device
405 imageView, // imageView
406 nullptr); // pAllocator
407 }
408 }
409 for (const auto semaphore : plat_.swapchainImages.semaphores) {
410 if (semaphore) {
411 vkDestroySemaphore(device, // device
412 semaphore, // semaphore
413 nullptr); // pAllocator
414 }
415 }
416
417 CreateFunctionsVk::DestroySwapchain(device, plat_.swapchain);
418 if (ownsSurface_ && surface_) {
419 CreateFunctionsVk::DestroySurface(devicePlatformData.instance, surface_);
420 }
421 }
422
GetPlatformData() const423 const SwapchainPlatformDataVk& SwapchainVk::GetPlatformData() const
424 {
425 return plat_;
426 }
427
GetDesc() const428 const GpuImageDesc& SwapchainVk::GetDesc() const
429 {
430 return desc_;
431 }
432
GetDescDepthBuffer() const433 const GpuImageDesc& SwapchainVk::GetDescDepthBuffer() const
434 {
435 return descDepthBuffer_;
436 }
437
GetFlags() const438 uint32_t SwapchainVk::GetFlags() const
439 {
440 return flags_;
441 }
442
GetSurfaceTransformFlags() const443 SurfaceTransformFlags SwapchainVk::GetSurfaceTransformFlags() const
444 {
445 return surfaceTransformFlags_;
446 }
447
GetSurfaceHandle() const448 uint64_t SwapchainVk::GetSurfaceHandle() const
449 {
450 return VulkanHandleCast<uint64_t>(surface_);
451 }
452
GetNextAcquireSwapchainSemaphoreIndex() const453 uint32_t SwapchainVk::GetNextAcquireSwapchainSemaphoreIndex() const
454 {
455 currSemaphoreIdx_ = (currSemaphoreIdx_ + 1) % plat_.swapchainImages.semaphores.size();
456 return currSemaphoreIdx_;
457 }
458 RENDER_END_NAMESPACE()
459