Vulkan Schnee 0.0.1
High-performance rendering engine
Loading...
Searching...
No Matches
MirrorView.cpp
Go to the documentation of this file.
2
7#include <glm/common.hpp>
8#include <glm/ext/vector_float2.hpp>
9#include <plog/Log.h>
10#include <sstream>
11#include <stdexcept>
12#include <unordered_set>
13#include <vulkan/vulkan.h>
14
15namespace
16{
17 std::unordered_set<VkFormat> preferredFormats = {VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_R8G8B8A8_SRGB};
18 constexpr VkFormat depthFormat = VK_FORMAT_D32_SFLOAT;
19 constexpr size_t mirrorEyeIndex = 1u; // Eye index to mirror, 0 = left, 1 = right
20} // namespace
21
22namespace EngineCore
23{
24
25 constexpr const char * windowTitle = "Vulkan Schnee";
26
28 bool MirrorView::hasBeenRestored = false;
29
31 {
32 if ( !context )
33 {
34 throw std::runtime_error( "No valid context pointer provided to mirror surface!" );
35 }
36
38
39 // create full screen desktop window
40 {
41 GLFWmonitor * monitor = glfwGetPrimaryMonitor();
42
43 int width, height;
44 glfwGetMonitorWorkarea( monitor, nullptr, nullptr, &width, &height );
45
46 #ifdef IS_IN_DEBUG
47 // half the size of the window on both axis in debug mode.
48 width /= 2;
49 height /= 2;
50 monitor = nullptr;
51 #endif
52
53 if ( monitor )
54 const GLFWvidmode * mode = glfwGetVideoMode( monitor );
55
56 glfwWindowHint( GLFW_CLIENT_API, GLFW_NO_API );
57 window = glfwCreateWindow( width, height, windowTitle, monitor, nullptr );
58 if ( !window )
59 {
60 std::stringstream error;
61 error << width << "x" << height << ( monitor ? " fullscreen" : " windowed" );
62 throw std::runtime_error( error.str() );
63 }
64
65 glfwSetWindowUserPointer( window, this );
66 glfwSetFramebufferSizeCallback( window, framebufferResizeCallback );
67 glfwSetKeyCallback( window, keyCallback );
68 glfwSetWindowIconifyCallback( window, MirrorView::iconifyCallback );
69
70 // Hide the mouse cursor
71 glfwSetInputMode( window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN );
72
73 VkResult createWindowSurfaceResult =
74 glfwCreateWindowSurface( context->getVkInstance(), window, nullptr, &mirrorSurface );
75 if ( createWindowSurfaceResult != VK_SUCCESS )
76 {
77 std::stringstream error;
78 error << "Failed to create a surface for the mirror";
79 PLOGE_VK << error.str();
80 }
81 }
82 }
83
85 {
86 if ( swapchain )
87 {
88 vkDestroySwapchainKHR( context->getVkDevice(), swapchain, nullptr );
89 }
90 if ( mirrorSurface )
91 {
92 vkDestroySurfaceKHR( context->getVkInstance(), mirrorSurface, nullptr );
93 }
94 }
95
97 {
98 this->headset = headset;
99 this->renderer = renderer;
100
102 }
103
104 MirrorView::RenderResult MirrorView::render( uint32_t swapchainImageIndex )
105 {
106 if ( glfwGetWindowAttrib( window, GLFW_ICONIFIED ) )
107 {
108 return { RenderResult::Status::Invisible, VK_NULL_HANDLE };
109 };
110
111 // Handle window restored from minimized state
112 if ( hasBeenRestored )
113 {
114 hasBeenRestored = false;
116 }
117
118 if ( swapchainResolution.width == 0u || swapchainResolution.height == 0u )
119 {
121 {
122 hasWindowBeenResized = false;
124 }
125 else
126 {
127 return { RenderResult::Status::Invisible, VK_NULL_HANDLE };
128 }
129 }
130
131 // Store XR swapchain index for present() to use when looking up the correct semaphore
132 xrSwapchainImageIndex = swapchainImageIndex;
133
134 VkSemaphore imageAvailableSemaphore = renderer->getCurrentMirrorViewSemaphore();
135
136 // Use a short timeout to avoid blocking when window is minimized or swapchain is stale
137 // 0 = non-blocking, returns VK_NOT_READY if no image available
138 VkResult swapchainAcquireResult = vkAcquireNextImageKHR(
139 context->getVkDevice(),
140 swapchain,
141 0, // Non-blocking acquire
142 imageAvailableSemaphore,
143 VK_NULL_HANDLE,
145 );
146 if ( swapchainAcquireResult == VK_ERROR_OUT_OF_DATE_KHR )
147 {
149 return { RenderResult::Status::Invisible, VK_NULL_HANDLE };
150 }
151 else if ( swapchainAcquireResult == VK_NOT_READY || swapchainAcquireResult == VK_TIMEOUT )
152 {
153 // No image available right now, skip this frame's mirror view
154 return { RenderResult::Status::Invisible, VK_NULL_HANDLE };
155 }
156 else if ( swapchainAcquireResult != VK_SUBOPTIMAL_KHR && swapchainAcquireResult != VK_SUCCESS )
157 {
158 return { RenderResult::Status::Invisible, VK_NULL_HANDLE };
159 }
160
161 // Convert the incoming image from undef to transfer source
162
163 VkCommandBuffer currentCommandBuffer = renderer->getCurrentRenderingCommandBuffer();
164 VkImage sourceImage = headset->getRenderTarget( swapchainImageIndex )->image;
165 VkImage targetImage = mirrorSwapchainImages.at( mirrorDestinationImageIndex );
166 VkExtent2D eyeResolution = headset->getEyeResolution( mirrorEyeIndex );
167
168 VkImageMemoryBarrier2 sourceToTransferBarrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2 };
169 sourceToTransferBarrier.pNext = nullptr;
170 // Wait for render pass color attachment writes AND any copy/transfer operations
171 // that SteamVR's compositor might perform. Use BLIT_BIT | COPY_BIT for explicit coverage.
172 sourceToTransferBarrier.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
173 sourceToTransferBarrier.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
174 sourceToTransferBarrier.dstStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT | VK_PIPELINE_STAGE_2_COPY_BIT;
175 sourceToTransferBarrier.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT;
176 sourceToTransferBarrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
177 sourceToTransferBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
178 sourceToTransferBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
179 sourceToTransferBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
180 sourceToTransferBarrier.image = sourceImage;
181 // Transition ALL array layers to maintain consistent validation layer tracking
182 // with multiview render passes. The render pass affects both eyes (layers 0 and 1),
183 // so we must transition both layers even though we only blit from one.
184 sourceToTransferBarrier.subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
185 .baseMipLevel = 0u,
186 .levelCount = 1u,
187 .baseArrayLayer = 0u,
188 .layerCount = 2u };
189
190 VkImageMemoryBarrier2 targetToTransferBarrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2 };
191 targetToTransferBarrier.pNext = nullptr;
192 targetToTransferBarrier.srcStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT;
193 targetToTransferBarrier.srcAccessMask = 0;
194 targetToTransferBarrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
195 targetToTransferBarrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
196 // Use UNDEFINED since we're overwriting the entire image with a blit.
197 // First frame the image is in UNDEFINED, subsequent frames in PRESENT_SRC_KHR.
198 targetToTransferBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
199 targetToTransferBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
200 targetToTransferBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
201 targetToTransferBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
202 targetToTransferBarrier.image = targetImage;
203 targetToTransferBarrier.subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
204 .baseMipLevel = 0u,
205 .levelCount = 1u,
206 .baseArrayLayer = 0u,
207 .layerCount = 1u };
208
209 std::vector<VkImageMemoryBarrier2> preBlitBarriers = { sourceToTransferBarrier,
210 targetToTransferBarrier };
211 VkDependencyInfo dependencyInfo{ VK_STRUCTURE_TYPE_DEPENDENCY_INFO };
212 dependencyInfo.pNext = nullptr;
213 // Don't use BY_REGION_BIT - the blit spans the entire image
214 dependencyInfo.dependencyFlags = 0;
215 dependencyInfo.imageMemoryBarrierCount = 2u;
216 dependencyInfo.pImageMemoryBarriers = preBlitBarriers.data();
217 vkCmdPipelineBarrier2( currentCommandBuffer, &dependencyInfo );
218
219 const glm::vec2 sourceResolution = { static_cast<float>( eyeResolution.width ),
220 static_cast<float>( eyeResolution.height ) };
221 const glm::vec2 targetResolution = { static_cast<float>( swapchainResolution.width ),
222 static_cast<float>( swapchainResolution.height ) };
223
224 const float sourceAspectRatio = sourceResolution.x / sourceResolution.y;
225 const float targetAspectRatio = targetResolution.x / targetResolution.y;
226
227 glm::vec2 cropResolution = sourceResolution;
228 glm::vec2 cropOffset = { 0.0f, 0.0f };
229 if ( sourceAspectRatio < targetAspectRatio )
230 {
231 cropResolution.y = sourceResolution.x / targetAspectRatio;
232 cropOffset.y = ( sourceResolution.y - cropResolution.y ) / 2.0f;
233 }
234 else if ( sourceAspectRatio > targetAspectRatio )
235 {
236 cropResolution.x = sourceResolution.y * targetAspectRatio;
237 cropOffset.x = ( sourceResolution.x - cropResolution.x ) / 2.0f;
238 }
239
240 // Perform the blit operation
241 VkImageBlit imageBlit{};
242 imageBlit.srcOffsets[0] = { static_cast<int32_t>( cropOffset.x ), static_cast<int32_t>( cropOffset.y ), 0 };
243 imageBlit.srcOffsets[1] = { static_cast<int32_t>( cropOffset.x + cropResolution.x ), static_cast<int32_t>( cropOffset.y + cropResolution.y ), 1 };
244 imageBlit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
245 imageBlit.srcSubresource.mipLevel = 0u;
246 imageBlit.srcSubresource.baseArrayLayer = mirrorEyeIndex;
247 imageBlit.srcSubresource.layerCount = 1u;
248
249 imageBlit.dstOffsets[0] = { 0, 0, 0 };
250 imageBlit.dstOffsets[1] = { static_cast<int32_t>( targetResolution.x ), static_cast<int32_t>( targetResolution.y ), 1 };
251 imageBlit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
252 imageBlit.dstSubresource.layerCount = 1u;
253 imageBlit.dstSubresource.baseArrayLayer = 0u;
254 imageBlit.dstSubresource.mipLevel = 0u;
255
256 vkCmdBlitImage(
257 currentCommandBuffer,
258 sourceImage,
259 VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
260 targetImage,
261 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
262 1u,
263 &imageBlit,
264 VK_FILTER_LINEAR
265 );
266
267 // Transition XR swapchain image back to COLOR_ATTACHMENT_OPTIMAL as required by OpenXR spec.
268 // This barrier synchronizes with:
269 // 1. Next frame's render pass (COLOR_ATTACHMENT_OUTPUT / COLOR_ATTACHMENT_WRITE)
270 // 2. SteamVR compositor's copy operation (COPY_BIT / TRANSFER_READ)
271 //
272 // NOTE: Validation errors may still appear due to SteamVR's internal descriptors expecting
273 // TRANSFER_SRC_OPTIMAL layout. This is a documented SteamVR spec violation (GitHub #1822),
274 // not a bug in our synchronization.
275 VkImageMemoryBarrier2 sourceBackBarrier{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2 };
276 sourceBackBarrier.pNext = nullptr;
277 // Wait for blit read to complete
278 sourceBackBarrier.srcStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT;
279 sourceBackBarrier.srcAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT;
280 // Synchronize with both next frame's render pass AND SteamVR compositor's copy.
281 // The copy happens after xrReleaseSwapchainImage when the command buffer has been submitted.
282 sourceBackBarrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT |
283 VK_PIPELINE_STAGE_2_COPY_BIT;
284 sourceBackBarrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT |
285 VK_ACCESS_2_TRANSFER_READ_BIT;
286 sourceBackBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
287 sourceBackBarrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
288 sourceBackBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
289 sourceBackBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
290 sourceBackBarrier.image = sourceImage;
291 // Transition ALL array layers back to COLOR_ATTACHMENT_OPTIMAL
292 // to match the multiview render pass finalLayout and maintain consistent tracking.
293 sourceBackBarrier.subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
294 .baseMipLevel = 0u,
295 .levelCount = 1u,
296 .baseArrayLayer = 0u,
297 .layerCount = 2u };
298
299 VkImageMemoryBarrier2 targetToPresentBarrier{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2 };
300 targetToPresentBarrier.pNext = nullptr;
301 targetToPresentBarrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
302 targetToPresentBarrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
303 targetToPresentBarrier.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT;
304 targetToPresentBarrier.dstAccessMask = 0;
305 targetToPresentBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
306 targetToPresentBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
307 targetToPresentBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
308 targetToPresentBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
309 targetToPresentBarrier.image = targetImage;
310 targetToPresentBarrier.subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
311 .baseMipLevel = 0u,
312 .levelCount = 1u,
313 .baseArrayLayer = 0u,
314 .layerCount = 1u };
315
316 // Submit post-blit barriers for both XR source and mirror target
317 std::vector<VkImageMemoryBarrier2> postBlitBarriers = { sourceBackBarrier, targetToPresentBarrier };
318 VkDependencyInfo postBlitDependency{
319 .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
320 .pNext = nullptr,
321 // Don't use BY_REGION_BIT - the operations span the entire images
322 .dependencyFlags = 0,
323 .imageMemoryBarrierCount = static_cast<uint32_t>( postBlitBarriers.size() ),
324 .pImageMemoryBarriers = postBlitBarriers.data(),
325 };
326
327 vkCmdPipelineBarrier2( currentCommandBuffer, &postBlitDependency );
328
329 return { RenderResult::Status::Visible, imageAvailableSemaphore };
330 }
331
333 {
334 // Use XR swapchain index (not mirror swapchain index) to look up the semaphore
335 // that was signaled by the graphics queue for this frame
336 const VkSemaphore presentableSemaphore = renderer->getCurrentPresentableSemaphore( xrSwapchainImageIndex );
337
338 // Add debug logging about semaphore
339 PLOGI << "Present using semaphore: " << presentableSemaphore
340 << " (XR image: " << xrSwapchainImageIndex << ", mirror image: " << mirrorDestinationImageIndex << ")";
341
342 VkPresentInfoKHR presentInfo{ .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
343 .pNext = nullptr,
344 .waitSemaphoreCount = 1u,
345 .pWaitSemaphores = &presentableSemaphore,
346 .swapchainCount = 1u,
347 .pSwapchains = &swapchain,
348 .pImageIndices = &mirrorDestinationImageIndex };
349
350 const VkResult result = vkQueuePresentKHR( context->getPresentQueue(), &presentInfo );
351 if ( result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR )
352 {
354 }
355 else if ( result != VK_SUCCESS )
356 {
357 PLOGE << "Failed to present swapchain image. Result: " << result;
358 return;
359 }
360 }
361
362 void MirrorView::framebufferResizeCallback( GLFWwindow * window, int width, int height )
363 {
365 }
366
367 void MirrorView::keyCallback( GLFWwindow * window, int key, int scancode, int action, int mods )
368 {
369 if ( action == GLFW_RELEASE && key == GLFW_KEY_ESCAPE )
370 {
371 glfwSetWindowShouldClose( window, 1 );
372 }
373 }
374
375 void MirrorView::iconifyCallback( GLFWwindow * window, int iconified )
376 {
377 if ( !iconified )
378 {
379 // Don't call recreateSwapchain here - it can deadlock if called while
380 // the render loop is in progress. Set a flag and handle it in render().
381 hasBeenRestored = true;
382 }
383 }
384
385 VkSurfaceKHR MirrorView::getSurface() const
386 {
387 return mirrorSurface;
388 }
389
390 GLFWwindow * MirrorView::getGlfwWindow() const
391 {
392 return window;
393 }
394
396 {
397 vkDeviceWaitIdle( context->getVkDevice() );
398
399 VkSurfaceCapabilities2KHR surfaceCapabilities{ VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR };
400 {
401 VkPhysicalDeviceSurfaceInfo2KHR physicalDeviceSurfaceInfo{
402 VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR
403 };
404 physicalDeviceSurfaceInfo.surface = mirrorSurface;
405 physicalDeviceSurfaceInfo.pNext = nullptr;
406
407 PLOG_THROW_FN_VK( vkGetPhysicalDeviceSurfaceCapabilities2KHR(
408 context->getVkPhysicalDevice(), &physicalDeviceSurfaceInfo, &surfaceCapabilities
409 ) );
410 if ( !( surfaceCapabilities.surfaceCapabilities.supportedUsageFlags &
411 VK_IMAGE_USAGE_TRANSFER_DST_BIT ) )
412 {
413 THROW_ERROR(
414 "Cannot transfer the final image to mirror plane as the feature is not supported!"
415 );
416 }
417
418 if ( surfaceCapabilities.surfaceCapabilities.currentExtent.width !=
419 std::numeric_limits<uint32_t>::max() &&
420 surfaceCapabilities.surfaceCapabilities.currentExtent.height !=
421 std::numeric_limits<uint32_t>::max() )
422 {
423 swapchainResolution = surfaceCapabilities.surfaceCapabilities.currentExtent;
424 }
425 else
426 {
427 int width, height;
428 glfwGetFramebufferSize( window, &width, &height );
429 swapchainResolution.width = glm::clamp(
430 width,
431 static_cast<int>( surfaceCapabilities.surfaceCapabilities.minImageExtent.width ),
432 static_cast<int>( surfaceCapabilities.surfaceCapabilities.maxImageExtent.width )
433 );
434 swapchainResolution.height = glm::clamp(
435 height,
436 static_cast<int>( surfaceCapabilities.surfaceCapabilities.minImageExtent.height ),
437 static_cast<int>( surfaceCapabilities.surfaceCapabilities.maxImageExtent.height )
438 );
439 }
440
441 if ( swapchainResolution.width == 0u || swapchainResolution.height == 0u )
442 {
443 return;
444 }
445 }
446
447 // pick desktop color format
448 VkSurfaceFormat2KHR surfaceFormat{ VK_STRUCTURE_TYPE_SURFACE_FORMAT_2_KHR };
449 {
450 VkPhysicalDeviceSurfaceInfo2KHR physicalDeviceSurfaceInfo{
451 VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR
452 };
453 physicalDeviceSurfaceInfo.surface = mirrorSurface;
454 physicalDeviceSurfaceInfo.pNext = nullptr;
455
456 uint32_t surfaceFormatCount = 0u;
457 PLOG_THROW_FN_VK( vkGetPhysicalDeviceSurfaceFormats2KHR(
458 context->getVkPhysicalDevice(), &physicalDeviceSurfaceInfo, &surfaceFormatCount, nullptr
459 ) );
460
461 std::vector<VkSurfaceFormat2KHR> surfaceFormats( surfaceFormatCount );
462 for ( VkSurfaceFormat2KHR & format : surfaceFormats )
463 {
464 format.sType = VK_STRUCTURE_TYPE_SURFACE_FORMAT_2_KHR;
465 }
466 PLOG_THROW_FN_VK( vkGetPhysicalDeviceSurfaceFormats2KHR(
467 context->getVkPhysicalDevice(),
468 &physicalDeviceSurfaceInfo,
469 &surfaceFormatCount,
470 surfaceFormats.data()
471 ) );
472
473 bool surfaceFormatFound = false;
474 for ( const VkSurfaceFormat2KHR & surfaceFormatCandidate : surfaceFormats )
475 {
476 PLOGI << "Checking color format: " << surfaceFormatCandidate.surfaceFormat.format;
477 if ( preferredFormats.contains( surfaceFormatCandidate.surfaceFormat.format ) )
478 {
479 surfaceFormat = surfaceFormatCandidate;
480 surfaceFormatFound = true;
481 break;
482 }
483 }
484
485 if ( !surfaceFormatFound )
486 {
487 THROW_ERROR( "Could not find mirror surface format" );
488 }
489 }
490
491 // Select the best available vsync present mode
492 VkPresentModeKHR presentMode =
493 VK_PRESENT_MODE_FIFO_KHR; // Default fallback (guaranteed to be available)
494 {
495 uint32_t presentModeCount = 0u;
496 PLOG_THROW_FN_VK( vkGetPhysicalDeviceSurfacePresentModesKHR(
497 context->getVkPhysicalDevice(), mirrorSurface, &presentModeCount, nullptr
498 ) );
499
500 std::vector<VkPresentModeKHR> presentModes( presentModeCount );
501 PLOG_THROW_FN_VK( vkGetPhysicalDeviceSurfacePresentModesKHR(
502 context->getVkPhysicalDevice(), mirrorSurface, &presentModeCount, presentModes.data()
503 ) );
504
505 // Prefer MAILBOX (triple buffering with vsync) over FIFO (double buffering with vsync)
506 for ( VkPresentModeKHR availableMode : presentModes )
507 {
508 if ( availableMode == VK_PRESENT_MODE_MAILBOX_KHR )
509 {
510 presentMode = VK_PRESENT_MODE_MAILBOX_KHR;
511 PLOGI << "Mirror view using MAILBOX present mode (triple buffering with vsync)";
512 break;
513 }
514 }
515
516 if ( presentMode == VK_PRESENT_MODE_FIFO_KHR )
517 {
518 PLOGI << "Mirror view using FIFO present mode (double buffering with vsync)";
519 }
520 }
521
522 if ( swapchain )
523 {
524 vkDestroySwapchainKHR( context->getVkDevice(), swapchain, nullptr );
525 }
526
527 VkSwapchainCreateInfoKHR swapchainCreateInfo{
528 .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
529 .pNext = nullptr,
530 .flags = 0,
531 .surface = mirrorSurface,
532 .minImageCount = surfaceCapabilities.surfaceCapabilities.minImageCount + 1u,
533 .imageFormat = surfaceFormat.surfaceFormat.format,
534 .imageColorSpace = surfaceFormat.surfaceFormat.colorSpace,
535 .imageExtent = swapchainResolution,
536 .imageArrayLayers = 1u,
537 .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
538 .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
539 .preTransform = surfaceCapabilities.surfaceCapabilities.currentTransform,
540 .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
541 .presentMode = presentMode,
542 .clipped = VK_TRUE
543 };
544 PLOG_THROW_FN_VK(
545 vkCreateSwapchainKHR( context->getVkDevice(), &swapchainCreateInfo, nullptr, &swapchain )
546 );
547
548 uint32_t swapchainImageCount;
549 PLOG_THROW_FN_VK(
550 vkGetSwapchainImagesKHR( context->getVkDevice(), swapchain, &swapchainImageCount, nullptr )
551 );
552
553 mirrorSwapchainImages.resize( swapchainImageCount );
554 PLOG_THROW_FN_VK( vkGetSwapchainImagesKHR(
555 context->getVkDevice(), swapchain, &swapchainImageCount, mirrorSwapchainImages.data()
556 ) );
557 }
558
560 {
561 if ( context && context->getVkDevice() )
562 {
563 if ( swapchain )
564 {
565 vkDestroySwapchainKHR( context->getVkDevice(), swapchain, nullptr );
566 swapchain = nullptr;
567 }
568 }
569
570 if ( context && context->getVkInstance() )
571 {
572 if ( mirrorSurface )
573 {
574 vkDestroySurfaceKHR( context->getVkInstance(), mirrorSurface, nullptr );
575 mirrorSurface = nullptr;
576 }
577 }
578
579 if ( window )
580 {
581 glfwDestroyWindow( window );
582 window = nullptr;
583 }
584 }
585
586} // namespace EngineCore
#define TRACY_ZONE_SCOPED_FUNCTION
The application context is the core class which stores the basic openxr and vulkan objects.
void connect(const Headset *headset, const Renderer *renderer)
Connects the mirror view to the headset pointer and the renderer pointer.
const Renderer * renderer
Definition MirrorView.h:124
void recreateSwapchain()
Recreates the swapchain. Can be called when no swapchain exists yet to create one.
VkSurfaceKHR mirrorSurface
Definition MirrorView.h:107
MirrorView::RenderResult render(uint32_t swapchainImageIndex)
Renders the given swapchainImageIndex.
static bool hasBeenRestored
Definition MirrorView.h:118
uint32_t mirrorDestinationImageIndex
Definition MirrorView.h:137
uint32_t xrSwapchainImageIndex
Definition MirrorView.h:138
VkSwapchainKHR swapchain
The swapchain on which the images to display are swapped.
Definition MirrorView.h:112
void cleanup()
Cleanup resources allocated by MirrorView.
static bool hasWindowBeenResized
Definition MirrorView.h:115
ApplicationContext * context
A pointer to the engines application context.
Definition MirrorView.h:134
GLFWwindow * getGlfwWindow() const
Gets glfw window.
VkSurfaceKHR getSurface() const
Gets the surface.
static void iconifyCallback(GLFWwindow *window, int iconified)
Callback, called when the window gets minimized or gets unfocused.
static void framebufferResizeCallback(GLFWwindow *window, int width, int height)
Callback, called when the framebuffer gets resized. Aka the window gets resized.
const Headset * headset
Definition MirrorView.h:123
MirrorView(ApplicationContext *context=nullptr)
VkExtent2D swapchainResolution
The swapchain resolution.
Definition MirrorView.h:129
static void keyCallback(GLFWwindow *window, int key, int scancode, int action, int mods)
Callback, called when glfw detects a key press.
std::vector< VkImage > mirrorSwapchainImages
The mirror swapchain images.
Definition MirrorView.h:166
Log category system implementation.
constexpr const char * windowTitle
Values that represent mirror view render results.
Definition MirrorView.h:48