@@ -39,6 +39,7 @@ VulkanRendererContext::~VulkanRendererContext() {
3939 if (wt.stg != VK_NULL_HANDLE ) { vk_.DestroyBuffer (device, wt.stg , nullptr ); vk_.FreeMemory (device, wt.stgMem , nullptr ); }
4040 }
4141 deleteQueue.clear ();
42+ destroyXrTargetResources ();
4243 cleanupSwapchain (); cleanupCursorTex ();
4344
4445 vk_.DestroySampler (device, sampler, nullptr );
@@ -746,14 +747,22 @@ void VulkanRendererContext::recordCmdBuf(VkCommandBuffer cb, uint32_t imgIdx,
746747 0 , 0 , nullptr , 0 , nullptr , (uint32_t )postUpload.size (), postUpload.data ());
747748
748749
750+ bool toXr = xrTargetActive.load () && xrFb!=VK_NULL_HANDLE ;
751+ VkExtent2D tgtExt = toXr ? xrExt : swapchainExt;
749752 VkRenderPassBeginInfo rpi{}; rpi.sType =VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO ;
750- rpi.renderPass =renderPass; rpi.framebuffer =swapchainFBs[imgIdx]; rpi.renderArea ={{0 ,0 },swapchainExt};
753+ rpi.renderPass = toXr ? xrRp : renderPass;
754+ rpi.framebuffer = toXr ? xrFb : swapchainFBs[imgIdx];
755+ rpi.renderArea ={{0 ,0 },tgtExt};
751756 VkClearValue clr={{{0 .f ,0 .f ,0 .f ,1 .f }}}; rpi.clearValueCount =1 ; rpi.pClearValues =&clr;
752757
753758 vk_.CmdBeginRenderPass (cb, &rpi, VK_SUBPASS_CONTENTS_INLINE );
754- VkViewport vp{0 ,0 ,(float )swapchainExt.width ,(float )swapchainExt.height ,0 ,1 };
759+ // The immersive quad flips vertically to suit the per-window game buffers, so the scene
760+ // target must match their orientation: render it upside down via a negative viewport.
761+ VkViewport vp = toXr
762+ ? VkViewport{0 ,(float )tgtExt.height ,(float )tgtExt.width ,-(float )tgtExt.height ,0 ,1 }
763+ : VkViewport{0 ,0 ,(float )tgtExt.width ,(float )tgtExt.height ,0 ,1 };
755764 vk_.CmdSetViewport (cb, 0 , 1 , &vp);
756- VkRect2D sc{{0 ,0 },swapchainExt }; vk_.CmdSetScissor (cb, 0 , 1 , &sc);
765+ VkRect2D sc{{0 ,0 },tgtExt }; vk_.CmdSetScissor (cb, 0 , 1 , &sc);
757766
758767 vk_.CmdBindPipeline (cb, VK_PIPELINE_BIND_POINT_GRAPHICS , pipeline);
759768 for (auto & d : draws) {
@@ -773,8 +782,8 @@ void VulkanRendererContext::recordCmdBuf(VkCommandBuffer cb, uint32_t imgIdx,
773782 pc.brightness = activeBrightness;
774783 pc.contrast = activeContrast;
775784 pc.gamma = activeGamma;
776- pc.outW = std::max (1 .0f , (float )d.w * sx / cw * (float )swapchainExt .width );
777- pc.outH = std::max (1 .0f , (float )d.h * sy / ch * (float )swapchainExt .height );
785+ pc.outW = std::max (1 .0f , (float )d.w * sx / cw * (float )tgtExt .width );
786+ pc.outH = std::max (1 .0f , (float )d.h * sy / ch * (float )tgtExt .height );
778787 vk_.CmdPushConstants (cb, pipeLayout, VK_SHADER_STAGE_VERTEX_BIT |VK_SHADER_STAGE_FRAGMENT_BIT , 0 , sizeof (pc), &pc);
779788 vk_.CmdDraw (cb, 4 , 1 , 0 , 0 );
780789 }
@@ -810,6 +819,142 @@ void VulkanRendererContext::recordCmdBuf(VkCommandBuffer cb, uint32_t imgIdx,
810819 }
811820}
812821
822+
823+ bool VulkanRendererContext::createXrTargetResources (uint32_t w, uint32_t h) {
824+ AHardwareBuffer_Desc d{};
825+ d.width =w; d.height =h; d.layers =1 ;
826+ d.format =AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM ;
827+ d.usage =AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE |AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER ;
828+ if (AHardwareBuffer_allocate (&d,&xrAhb)!=0 ||!xrAhb) { RLOG_E (" xrTarget: AHB alloc %ux%u failed" ,w,h); return false ; }
829+
830+ VkAndroidHardwareBufferPropertiesANDROID props{};
831+ props.sType =VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID ;
832+ if (!vk_.GetAndroidHardwareBufferPropertiesANDROID ||
833+ vk_.GetAndroidHardwareBufferPropertiesANDROID (device,xrAhb,&props)!=VK_SUCCESS ) {
834+ RLOG_E (" xrTarget: AHB props failed" ); destroyXrTargetResources (); return false ;
835+ }
836+
837+ VkExternalMemoryImageCreateInfo emi{};
838+ emi.sType =VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO ;
839+ emi.handleTypes =VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID ;
840+ VkImageCreateInfo ii{};
841+ ii.sType =VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO ; ii.pNext =&emi;
842+ ii.imageType =VK_IMAGE_TYPE_2D ; ii.format =VK_FORMAT_R8G8B8A8_UNORM ;
843+ ii.extent ={w,h,1 }; ii.mipLevels =1 ; ii.arrayLayers =1 ; ii.samples =VK_SAMPLE_COUNT_1_BIT ;
844+ ii.tiling =VK_IMAGE_TILING_OPTIMAL ;
845+ ii.usage =VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |VK_IMAGE_USAGE_SAMPLED_BIT ;
846+ ii.sharingMode =VK_SHARING_MODE_EXCLUSIVE ; ii.initialLayout =VK_IMAGE_LAYOUT_UNDEFINED ;
847+ if (vk_.CreateImage (device,&ii,nullptr ,&xrImg)!=VK_SUCCESS ) { RLOG_E (" xrTarget: image" ); destroyXrTargetResources (); return false ; }
848+
849+ VkImportAndroidHardwareBufferInfoANDROID imp{};
850+ imp.sType =VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID ; imp.buffer =xrAhb;
851+ VkMemoryDedicatedAllocateInfo ded{};
852+ ded.sType =VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO ; ded.pNext =&imp; ded.image =xrImg;
853+ uint32_t idx=0 ; while (idx<32 && !(props.memoryTypeBits &(1u <<idx))) idx++;
854+ if (idx>=32 ) { RLOG_E (" xrTarget: no compatible memory type (bits=0x%x)" , props.memoryTypeBits ); destroyXrTargetResources (); return false ; }
855+ VkMemoryAllocateInfo mai{};
856+ mai.sType =VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO ; mai.pNext =&ded;
857+ mai.allocationSize =props.allocationSize ; mai.memoryTypeIndex =idx;
858+ if (vk_.AllocateMemory (device,&mai,nullptr ,&xrMem)!=VK_SUCCESS ||
859+ vk_.BindImageMemory (device,xrImg,xrMem,0 )!=VK_SUCCESS ) {
860+ RLOG_E (" xrTarget: memory" ); destroyXrTargetResources (); return false ;
861+ }
862+
863+ VkImageViewCreateInfo vi{};
864+ vi.sType =VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO ;
865+ vi.image =xrImg; vi.viewType =VK_IMAGE_VIEW_TYPE_2D ; vi.format =VK_FORMAT_R8G8B8A8_UNORM ;
866+ vi.subresourceRange ={VK_IMAGE_ASPECT_COLOR_BIT ,0 ,1 ,0 ,1 };
867+ if (vk_.CreateImageView (device,&vi,nullptr ,&xrView)!=VK_SUCCESS ) { RLOG_E (" xrTarget: view" ); destroyXrTargetResources (); return false ; }
868+
869+ VkAttachmentDescription att{};
870+ att.format =VK_FORMAT_R8G8B8A8_UNORM ; att.samples =VK_SAMPLE_COUNT_1_BIT ;
871+ att.loadOp =VK_ATTACHMENT_LOAD_OP_CLEAR ; att.storeOp =VK_ATTACHMENT_STORE_OP_STORE ;
872+ att.stencilLoadOp =VK_ATTACHMENT_LOAD_OP_DONT_CARE ; att.stencilStoreOp =VK_ATTACHMENT_STORE_OP_DONT_CARE ;
873+ att.initialLayout =VK_IMAGE_LAYOUT_UNDEFINED ; att.finalLayout =VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL ;
874+ VkAttachmentReference ref{0 ,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
875+ VkSubpassDescription sub{}; sub.pipelineBindPoint =VK_PIPELINE_BIND_POINT_GRAPHICS ;
876+ sub.colorAttachmentCount =1 ; sub.pColorAttachments =&ref;
877+ VkSubpassDependency dep{}; dep.srcSubpass =VK_SUBPASS_EXTERNAL ; dep.dstSubpass =0 ;
878+ dep.srcStageMask =VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT ; dep.srcAccessMask =0 ;
879+ dep.dstStageMask =VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT ;
880+ dep.dstAccessMask =VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT ;
881+ VkRenderPassCreateInfo rci{}; rci.sType =VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO ;
882+ rci.attachmentCount =1 ; rci.pAttachments =&att; rci.subpassCount =1 ; rci.pSubpasses =⊂
883+ rci.dependencyCount =1 ; rci.pDependencies =&dep;
884+ if (vk_.CreateRenderPass (device,&rci,nullptr ,&xrRp)!=VK_SUCCESS ) { RLOG_E (" xrTarget: renderpass" ); destroyXrTargetResources (); return false ; }
885+
886+ VkFramebufferCreateInfo fi{};
887+ fi.sType =VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO ;
888+ fi.renderPass =xrRp; fi.attachmentCount =1 ; fi.pAttachments =&xrView;
889+ fi.width =w; fi.height =h; fi.layers =1 ;
890+ if (vk_.CreateFramebuffer (device,&fi,nullptr ,&xrFb)!=VK_SUCCESS ) { RLOG_E (" xrTarget: framebuffer" ); destroyXrTargetResources (); return false ; }
891+
892+ xrExt={w,h};
893+ RLOG (" xrTarget: created %ux%u ahb=%p" , w, h, (void *)xrAhb);
894+ return true ;
895+ }
896+
897+ void VulkanRendererContext::destroyXrTargetResources () {
898+ if (xrFb !=VK_NULL_HANDLE ){vk_.DestroyFramebuffer (device,xrFb,nullptr ); xrFb=VK_NULL_HANDLE ;}
899+ if (xrRp !=VK_NULL_HANDLE ){vk_.DestroyRenderPass (device,xrRp,nullptr ); xrRp=VK_NULL_HANDLE ;}
900+ if (xrView!=VK_NULL_HANDLE ){vk_.DestroyImageView (device,xrView,nullptr ); xrView=VK_NULL_HANDLE ;}
901+ if (xrImg !=VK_NULL_HANDLE ){vk_.DestroyImage (device,xrImg,nullptr ); xrImg=VK_NULL_HANDLE ;}
902+ if (xrMem !=VK_NULL_HANDLE ){vk_.FreeMemory (device,xrMem,nullptr ); xrMem=VK_NULL_HANDLE ;}
903+ if (xrAhb){AHardwareBuffer_release (xrAhb); xrAhb=nullptr ;}
904+ xrExt={0 ,0 };
905+ }
906+
907+ int64_t VulkanRendererContext::enableXrTarget () {
908+ std::unique_lock<std::shared_mutex> fl (frameMutex);
909+ std::lock_guard<std::mutex> lk (renderMutex);
910+ if (device==VK_NULL_HANDLE ) return 0 ;
911+ // A surface resize may still be queued for the render loop; process it here so the
912+ // target is sized from the current swapchain extent, not the stale one.
913+ if (fbResized.load ()) {
914+ for (auto & f:inFlightFences) vk_.WaitForFences (device,1 ,&f,VK_TRUE ,UINT64_MAX );
915+ cleanupSwapchain ();
916+ try {
917+ createSwapchain (); createFramebuffers (); createCmdBufs ();
918+ imgInFlight.assign (swapchainImages.size (),VK_NULL_HANDLE );
919+ fbResized.store (false );
920+ } catch (...) { return 0 ; }
921+ }
922+ uint32_t w=swapchainExt.width , h=swapchainExt.height ;
923+ if (w==0 ||h==0 ){ w=(uint32_t )surfaceWidth; h=(uint32_t )surfaceHeight; }
924+ if (w==0 ||h==0 ) return 0 ;
925+ // The immersive quad swapchain follows the container screen size (min width 1280);
926+ // rendering the scene any larger only burns GPU on pixels the quad downsamples away.
927+ // Keep the surface aspect.
928+ float qw = (float )std::max (containerWidth, 1280 );
929+ float qh = (float )containerHeight * qw / (float )std::max (containerWidth, 1 );
930+ float fit = std::min ({qw/(float )w, qh/(float )h, 1 .f });
931+ w = std::max (16u , (uint32_t )((float )w*fit) & ~1u );
932+ h = std::max (16u , (uint32_t )((float )h*fit) & ~1u );
933+ if (xrTargetActive.load () && xrAhb!=nullptr && xrExt.width ==w && xrExt.height ==h)
934+ return (int64_t )(intptr_t )xrAhb;
935+ vk_.DeviceWaitIdle (device);
936+ if (xrAhb!=nullptr ) { xrTargetActive.store (false ); destroyXrTargetResources (); }
937+ if (!createXrTargetResources (w,h)) return 0 ;
938+ xrTargetActive.store (true );
939+ needsRender.store (true ); dirtyCV.notify_one ();
940+ return (int64_t )(intptr_t )xrAhb;
941+ }
942+
943+ int64_t VulkanRendererContext::xrTargetExtentPacked () {
944+ std::lock_guard<std::mutex> lk (renderMutex);
945+ return ((int64_t )xrExt.width <<32 ) | (int64_t )xrExt.height ;
946+ }
947+
948+ void VulkanRendererContext::disableXrTarget () {
949+ std::unique_lock<std::shared_mutex> fl (frameMutex);
950+ std::lock_guard<std::mutex> lk (renderMutex);
951+ if (!xrTargetActive.load () && xrAhb==nullptr ) return ;
952+ if (device!=VK_NULL_HANDLE ) vk_.DeviceWaitIdle (device);
953+ xrTargetActive.store (false );
954+ destroyXrTargetResources ();
955+ needsRender.store (true ); dirtyCV.notify_one ();
956+ }
957+
813958void VulkanRendererContext::renderLoop () {
814959
815960 while (isRunning) {
@@ -873,30 +1018,37 @@ ok=true;}catch(...){}
8731018 }
8741019
8751020 if (currentFrame >= cmdBufs.size () || cmdBufs[currentFrame] == VK_NULL_HANDLE ) return ;
1021+ bool toXr = xrTargetActive.load () && xrFb!=VK_NULL_HANDLE ;
8761022 bool currentFenceWaited = false ;
877- if (!vk_.GetFenceStatus || vk_.GetFenceStatus (device, inFlightFences[currentFrame]) == VK_NOT_READY ) {
1023+ if (toXr) {
1024+ for (auto & f:inFlightFences) vk_.WaitForFences (device,1 ,&f,VK_TRUE ,UINT64_MAX );
1025+ currentFenceWaited = true ;
1026+ } else if (!vk_.GetFenceStatus || vk_.GetFenceStatus (device, inFlightFences[currentFrame]) == VK_NOT_READY ) {
8781027 vk_.WaitForFences (device,1 ,&inFlightFences[currentFrame],VK_TRUE ,UINT64_MAX );
8791028 currentFenceWaited = true ;
8801029 }
8811030
882- uint32_t imgIdx;
883- VkResult res=vk_.AcquireNextImageKHR (device,swapchain,UINT64_MAX ,imgAvailSems[currentFrame],VK_NULL_HANDLE ,&imgIdx);
884- if (res==VK_ERROR_OUT_OF_DATE_KHR ||res==VK_ERROR_SURFACE_LOST_KHR ){fbResized.store (true );return ;}
885- if (res!=VK_SUCCESS &&res!=VK_SUBOPTIMAL_KHR ) return ;
886- if (imgIdx >= swapchainFBs.size () || imgIdx >= swapchainImages.size ()) {
887- RLOG_E (" renderFrame: invalid acquired image index=%u (fb=%zu images=%zu)" ,
888- imgIdx, swapchainFBs.size (), swapchainImages.size ());
889- return ;
890- }
1031+ uint32_t imgIdx = 0 ;
1032+ VkResult res = VK_SUCCESS ;
1033+ if (!toXr) {
1034+ res=vk_.AcquireNextImageKHR (device,swapchain,UINT64_MAX ,imgAvailSems[currentFrame],VK_NULL_HANDLE ,&imgIdx);
1035+ if (res==VK_ERROR_OUT_OF_DATE_KHR ||res==VK_ERROR_SURFACE_LOST_KHR ){fbResized.store (true );return ;}
1036+ if (res!=VK_SUCCESS &&res!=VK_SUBOPTIMAL_KHR ) return ;
1037+ if (imgIdx >= swapchainFBs.size () || imgIdx >= swapchainImages.size ()) {
1038+ RLOG_E (" renderFrame: invalid acquired image index=%u (fb=%zu images=%zu)" ,
1039+ imgIdx, swapchainFBs.size (), swapchainImages.size ());
1040+ return ;
1041+ }
8911042
892- if (imgInFlight.size ()!=swapchainImages.size ()) imgInFlight.assign (swapchainImages.size (),VK_NULL_HANDLE );
893- if (imgInFlight[imgIdx]!=VK_NULL_HANDLE &&
894- (!currentFenceWaited || imgInFlight[imgIdx] != inFlightFences[currentFrame])) {
895- if (!vk_.GetFenceStatus || vk_.GetFenceStatus (device, imgInFlight[imgIdx]) == VK_NOT_READY ) {
896- vk_.WaitForFences (device,1 ,&imgInFlight[imgIdx],VK_TRUE ,UINT64_MAX );
1043+ if (imgInFlight.size ()!=swapchainImages.size ()) imgInFlight.assign (swapchainImages.size (),VK_NULL_HANDLE );
1044+ if (imgInFlight[imgIdx]!=VK_NULL_HANDLE &&
1045+ (!currentFenceWaited || imgInFlight[imgIdx] != inFlightFences[currentFrame])) {
1046+ if (!vk_.GetFenceStatus || vk_.GetFenceStatus (device, imgInFlight[imgIdx]) == VK_NOT_READY ) {
1047+ vk_.WaitForFences (device,1 ,&imgInFlight[imgIdx],VK_TRUE ,UINT64_MAX );
1048+ }
8971049 }
1050+ imgInFlight[imgIdx]=inFlightFences[currentFrame];
8981051 }
899- imgInFlight[imgIdx]=inFlightFences[currentFrame];
9001052
9011053 vk_.ResetCommandBuffer (cmdBufs[currentFrame],0 );
9021054
@@ -965,9 +1117,11 @@ ok=true;}catch(...){}
9651117 VkSemaphore wSem[]={imgAvailSems[currentFrame]}, sSem []={renderDoneSems[currentFrame]};
9661118 VkPipelineStageFlags wStage[]={VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
9671119 VkSubmitInfo si{}; si.sType =VK_STRUCTURE_TYPE_SUBMIT_INFO ;
968- si.waitSemaphoreCount =1 ; si.pWaitSemaphores =wSem; si.pWaitDstStageMask =wStage;
1120+ if (!toXr) {
1121+ si.waitSemaphoreCount =1 ; si.pWaitSemaphores =wSem; si.pWaitDstStageMask =wStage;
1122+ si.signalSemaphoreCount =1 ; si.pSignalSemaphores =sSem ;
1123+ }
9691124 si.commandBufferCount =1 ; si.pCommandBuffers =&cmdBufs[currentFrame];
970- si.signalSemaphoreCount =1 ; si.pSignalSemaphores =sSem ;
9711125
9721126 vk_.ResetFences (device,1 ,&inFlightFences[currentFrame]);
9731127 if (vk_.QueueSubmit (graphicsQueue,1 ,&si,inFlightFences[currentFrame])!=VK_SUCCESS ) {
@@ -976,11 +1130,18 @@ ok=true;}catch(...){}
9761130 vk_.CreateFence (device,&fi,nullptr ,&inFlightFences[currentFrame]);
9771131 return ;
9781132 }
979- VkSwapchainKHR scs[]={swapchain};
980- VkPresentInfoKHR pi{}; pi.sType =VK_STRUCTURE_TYPE_PRESENT_INFO_KHR ;
981- pi.waitSemaphoreCount =1 ; pi.pWaitSemaphores =sSem ; pi.swapchainCount =1 ; pi.pSwapchains =scs; pi.pImageIndices =&imgIdx;
982- res=vk_.QueuePresentKHR (graphicsQueue,&pi);
983- if (res==VK_ERROR_OUT_OF_DATE_KHR ||res==VK_ERROR_SURFACE_LOST_KHR ) fbResized.store (true );
1133+ if (!toXr) {
1134+ VkSwapchainKHR scs[]={swapchain};
1135+ VkPresentInfoKHR pi{}; pi.sType =VK_STRUCTURE_TYPE_PRESENT_INFO_KHR ;
1136+ pi.waitSemaphoreCount =1 ; pi.pWaitSemaphores =sSem ; pi.swapchainCount =1 ; pi.pSwapchains =scs; pi.pImageIndices =&imgIdx;
1137+ res=vk_.QueuePresentKHR (graphicsQueue,&pi);
1138+ if (res==VK_ERROR_OUT_OF_DATE_KHR ||res==VK_ERROR_SURFACE_LOST_KHR ) fbResized.store (true );
1139+ } else {
1140+ // The XR session samples xrAhb from its own GL context with no fence handoff;
1141+ // blocking here means the buffer is fully written whenever this thread is idle,
1142+ // leaving only the active write window unsynchronized (a tear, not stale data).
1143+ vk_.WaitForFences (device,1 ,&inFlightFences[currentFrame],VK_TRUE ,UINT64_MAX );
1144+ }
9841145 currentFrame=(currentFrame+1 )%MAX_FRAMES_IN_FLIGHT ;
9851146}
9861147
0 commit comments