// SPDX-License-Identifier: GPL-3.0-only // Included after surface_projection.c. Snapshots scene images BEFORE release, // then composes decoded video + scene into one primary projection on the GPU. #include "surface_composite.h" #include "surface_composite_spv.h" #define SC_SCENES 4 typedef struct { XrSwapchain handle; VkImage image; VkImageView view; VkDeviceMemory memory; VkCommandBuffer commands[MAX_IMAGES]; VkFence fences[MAX_IMAGES]; int pending[MAX_IMAGES], ready; uint64_t serial,captured_release,releases; uint32_t width,height,layers; } surf_scene_snapshot; static surf_scene_snapshot sc_scenes[SC_SCENES]; static uint64_t sc_serial; static int64_t sc_capture_until; // Unity attaches both structures even when their transforms are identity. // Apply supported transforms in our shader; reject other chains rather than // silently discarding semantics when flattening the original scene. static int sc_scene_supported(const XrCompositionLayerProjection *scene) { int reason=0; if(!scene || scene->viewCount!=2 || !scene->views)return 0; int count=0; for(const XrBaseInStructure *n=scene->next;n;n=n->next) { if(++count>16){reason=-1;break;} if(n->type==XR_TYPE_COMPOSITION_LAYER_COLOR_SCALE_BIAS_KHR)continue; if(n->type==XR_TYPE_COMPOSITION_LAYER_IMAGE_LAYOUT_FB) { const XrCompositionLayerImageLayoutFB *layout=(const void*)n; if(!(layout->flags&~XR_COMPOSITION_LAYER_IMAGE_LAYOUT_VERTICAL_FLIP_BIT_FB))continue; } reason=n->type;break; } for(int e=0;e<2 && !reason;e++)if(scene->views[e].next) reason=((const XrBaseInStructure*)scene->views[e].next)->type; static int previous; if(reason && reason!=previous)LOG("surface_composite: unsupported scene extension %d; retaining ordinary composition",reason); previous=reason;return !reason; } typedef struct { XrSwapchain output; VkImage images[MAX_IMAGES]; VkImageView views[MAX_IMAGES]; VkFramebuffer framebuffers[MAX_IMAGES]; VkRenderPass render_pass; VkSampler sampler; VkDescriptorPool pool; VkDescriptorSetLayout set_layout; VkDescriptorSet sets[MAX_IMAGES][2]; VkPipelineLayout layout; VkPipeline pipeline; VkCommandBuffer commands[MAX_IMAGES]; VkFence fences[MAX_IMAGES]; int pending[MAX_IMAGES], ready, acquired, failed; uint32_t count,index; XrTime time; XrCompositionLayerProjection layer; XrCompositionLayerProjectionView eyes[2]; int64_t log_time; uint64_t frames,holds; } surf_composite; static surf_composite sc_outputs[SURF_MAX]; static void sc_barrier(VkCommandBuffer cmd,VkImage image,uint32_t layers,VkImageLayout from,VkImageLayout to, VkAccessFlags src,VkAccessFlags dst) { VkImageMemoryBarrier b={VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,NULL,src,dst,from,to, VK_QUEUE_FAMILY_IGNORED,VK_QUEUE_FAMILY_IGNORED,image,{VK_IMAGE_ASPECT_COLOR_BIT,0,1,0,layers}}; vk.CmdPipelineBarrier(cmd,VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0,0,NULL,0,NULL,1,&b); } static int sc_command(VkCommandBuffer *cmd,VkFence *fence) { VkCommandBufferAllocateInfo ai={VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,NULL,vk.pool,VK_COMMAND_BUFFER_LEVEL_PRIMARY,1}; VkFenceCreateInfo fi={VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; if(vk.AllocateCommandBuffers(vk.device,&ai,cmd)!=VK_SUCCESS)return 0; if(vk.CreateFence(vk.device,&fi,NULL,fence)==VK_SUCCESS)return 1; PFN_vkFreeCommandBuffers free_cmd=(PFN_vkFreeCommandBuffers)vk.GetDeviceProcAddr(vk.device,"vkFreeCommandBuffers"); free_cmd(vk.device,vk.pool,1,cmd);*cmd=VK_NULL_HANDLE;return 0; } static int sc_reuse(VkFence fence,int *pending) { if(!*pending)return 1; if(svk.GetFenceStatus(vk.device,fence)!=VK_SUCCESS)return 0; if(vk.ResetFences(vk.device,1,&fence)!=VK_SUCCESS)return 0; *pending=0;return 1; } static void sc_retire(VkCommandBuffer *commands,VkFence *fences,int *pending,uint32_t count) { PFN_vkDestroyFence destroy=(PFN_vkDestroyFence)vk.GetDeviceProcAddr(vk.device,"vkDestroyFence"); PFN_vkFreeCommandBuffers free_cmd=(PFN_vkFreeCommandBuffers)vk.GetDeviceProcAddr(vk.device,"vkFreeCommandBuffers"); for(uint32_t i=0;icommands,s->fences,s->pending,MAX_IMAGES); if(s->view)spvk.DestroyImageView(vk.device,s->view,NULL); if(s->image)spvk.DestroyImage(vk.device,s->image,NULL); if(s->memory)spvk.FreeMemory(vk.device,s->memory,NULL); memset(s,0,sizeof(*s)); } } static void surf_composite_destroy(surf_projection *p) { surf_composite *c=&sc_outputs[p-surf_projections]; sc_retire(c->commands,c->fences,c->pending,c->count); if(c->pipeline)spvk.DestroyPipeline(vk.device,c->pipeline,NULL); if(c->layout)spvk.DestroyPipelineLayout(vk.device,c->layout,NULL); if(c->pool)spvk.DestroyDescriptorPool(vk.device,c->pool,NULL); if(c->set_layout)spvk.DestroyDescriptorSetLayout(vk.device,c->set_layout,NULL); if(c->sampler)spvk.DestroySampler(vk.device,c->sampler,NULL); for(uint32_t i=0;icount;i++) { if(c->framebuffers[i])spvk.DestroyFramebuffer(vk.device,c->framebuffers[i],NULL); if(c->views[i])spvk.DestroyImageView(vk.device,c->views[i],NULL); } if(c->render_pass)spvk.DestroyRenderPass(vk.device,c->render_pass,NULL); if(c->output) { PFN_xrDestroySwapchain fn=(PFN_xrDestroySwapchain)lookup(active_instance,"xrDestroySwapchain"); if(c->acquired) { PFN_xrWaitSwapchainImage wait=(PFN_xrWaitSwapchainImage)lookup(active_instance,"xrWaitSwapchainImage"); PFN_xrReleaseSwapchainImage release=(PFN_xrReleaseSwapchainImage)lookup(active_instance,"xrReleaseSwapchainImage"); XrSwapchainImageWaitInfo wi={XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO,NULL,XR_INFINITE_DURATION}; XrSwapchainImageReleaseInfo ri={XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO}; if(wait(c->output,&wi)==XR_SUCCESS)release(c->output,&ri); } if(fn)fn(c->output); } memset(c,0,sizeof(*c)); } // Called while the application still owns this successfully waited image. // Queue ordering includes the application's rendering and this snapshot copy; // no CPU readback, post-release access, or deferred OpenXR release is needed. static void surf_scene_capture(tracked_swapchain *t,uint32_t index) { if(!surface_native || monotonic_ns()>sc_capture_until || !t || index>=t->image_count || spvk.ready<=0 || t->info.sampleCount!=1 || t->info.arraySize!=2 || !(t->info.usageFlags&XR_SWAPCHAIN_USAGE_TRANSFER_SRC_BIT) || !(t->info.usageFlags&XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT))return; surf_scene_snapshot *s=NULL; for(int i=0;ihandle){s=&sc_scenes[i];break;} if(!s)for(int i=0;ihandle=t->handle;break;} if(!s)return; if(!s->view) { s->width=t->info.width;s->height=t->info.height;s->layers=t->info.arraySize; if(!surf_private_image(s->width,s->height,s->layers,(VkFormat)t->info.format, VK_IMAGE_USAGE_TRANSFER_DST_BIT|VK_IMAGE_USAGE_SAMPLED_BIT,&s->image,&s->memory,&s->view)) { surf_scene_destroy(t->handle);return; } } if(!s->commands[index] && !sc_command(&s->commands[index],&s->fences[index]))return; if(!sc_reuse(s->fences[index],&s->pending[index]))return; VkCommandBuffer cmd=s->commands[index]; VkCommandBufferBeginInfo begin={VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,NULL,VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT}; if(vk.ResetCommandBuffer(cmd,0)!=VK_SUCCESS || vk.BeginCommandBuffer(cmd,&begin)!=VK_SUCCESS)return; sc_barrier(cmd,t->images[index],s->layers,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,VK_ACCESS_TRANSFER_READ_BIT); sc_barrier(cmd,s->image,s->layers,s->ready?VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,s->ready?VK_ACCESS_SHADER_READ_BIT:0,VK_ACCESS_TRANSFER_WRITE_BIT); VkImageCopy copy={{VK_IMAGE_ASPECT_COLOR_BIT,0,0,s->layers},{0,0,0}, {VK_IMAGE_ASPECT_COLOR_BIT,0,0,s->layers},{0,0,0},{s->width,s->height,1}}; spvk.CmdCopyImage(cmd,t->images[index],VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,s->image,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,1,©); sc_barrier(cmd,s->image,s->layers,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT,VK_ACCESS_SHADER_READ_BIT); sc_barrier(cmd,t->images[index],s->layers,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_ACCESS_TRANSFER_READ_BIT,VK_ACCESS_COLOR_ATTACHMENT_READ_BIT|VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT); if(vk.EndCommandBuffer(cmd)!=VK_SUCCESS)return; VkSubmitInfo submit={VK_STRUCTURE_TYPE_SUBMIT_INFO,NULL,0,NULL,NULL,1,&cmd}; if(vk.QueueSubmit(vk.queue,1,&submit,s->fences[index])!=VK_SUCCESS)return; s->pending[index]=1;s->ready=1;s->serial=sc_serial+1;s->captured_release=s->releases+1; } static int sc_setup(XrSession session,surf_projection *p,surf_composite *c) { if(c->pipeline)return 1; if(c->failed)return 0; PFN_xrCreateSwapchain create=(PFN_xrCreateSwapchain)lookup(active_instance,"xrCreateSwapchain"); PFN_xrEnumerateSwapchainImages enumerate=(PFN_xrEnumerateSwapchainImages)lookup(active_instance,"xrEnumerateSwapchainImages"); XrSwapchainCreateInfo ci={XR_TYPE_SWAPCHAIN_CREATE_INFO,NULL,0, XR_SWAPCHAIN_USAGE_TRANSFER_DST_BIT|XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT, VK_FORMAT_R8G8B8A8_SRGB,1,2*p->size,p->size,1,1,1}; if(!create || !enumerate || XR_FAILED(create(session,&ci,&c->output)))goto fail; surf_video_full_rate(session,c->output); XrSwapchainImageVulkanKHR images[MAX_IMAGES]; for(int i=0;ioutput,MAX_IMAGES,&c->count,(void*)images)) || !c->count || c->count>MAX_IMAGES) { c->count=0;goto fail; } for(uint32_t i=0;icount;i++) { c->images[i]=images[i].image; if(!sc_command(&c->commands[i],&c->fences[i]))goto fail; } VkAttachmentDescription attachment={0,VK_FORMAT_R8G8B8A8_SRGB,VK_SAMPLE_COUNT_1_BIT, VK_ATTACHMENT_LOAD_OP_DONT_CARE,VK_ATTACHMENT_STORE_OP_STORE,VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE,VK_IMAGE_LAYOUT_UNDEFINED,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; VkAttachmentReference reference={0,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; VkSubpassDescription subpass={0,VK_PIPELINE_BIND_POINT_GRAPHICS,0,NULL,1,&reference}; VkSubpassDependency dependency={VK_SUBPASS_EXTERNAL,0,VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT|VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_MEMORY_WRITE_BIT|VK_ACCESS_MEMORY_READ_BIT,VK_ACCESS_SHADER_READ_BIT|VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT}; VkRenderPassCreateInfo rpi={VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,NULL,0,1,&attachment,1,&subpass,1,&dependency}; if(spvk.CreateRenderPass(vk.device,&rpi,NULL,&c->render_pass)!=VK_SUCCESS)goto fail; for(uint32_t i=0;icount;i++) { VkImageViewCreateInfo vi={VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,NULL,0,c->images[i],VK_IMAGE_VIEW_TYPE_2D, VK_FORMAT_R8G8B8A8_SRGB,{0,0,0,0},{VK_IMAGE_ASPECT_COLOR_BIT,0,1,0,1}}; if(spvk.CreateImageView(vk.device,&vi,NULL,&c->views[i])!=VK_SUCCESS)goto fail; VkFramebufferCreateInfo fi={VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,NULL,0,c->render_pass,1,&c->views[i],2*p->size,p->size,1}; if(spvk.CreateFramebuffer(vk.device,&fi,NULL,&c->framebuffers[i])!=VK_SUCCESS)goto fail; } VkSamplerCreateInfo si={VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,NULL,0,VK_FILTER_LINEAR,VK_FILTER_LINEAR, VK_SAMPLER_MIPMAP_MODE_NEAREST,VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE}; if(spvk.CreateSampler(vk.device,&si,NULL,&c->sampler)!=VK_SUCCESS)goto fail; VkDescriptorSetLayoutBinding bindings[2]={{0,VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,1,VK_SHADER_STAGE_FRAGMENT_BIT}, {1,VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,1,VK_SHADER_STAGE_FRAGMENT_BIT}}; VkDescriptorSetLayoutCreateInfo li={VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,NULL,0,2,bindings}; if(spvk.CreateDescriptorSetLayout(vk.device,&li,NULL,&c->set_layout)!=VK_SUCCESS)goto fail; VkDescriptorPoolSize sizes[1]={{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,4*c->count}}; VkDescriptorPoolCreateInfo pi={VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,NULL,0,2*c->count,1,sizes}; if(spvk.CreateDescriptorPool(vk.device,&pi,NULL,&c->pool)!=VK_SUCCESS)goto fail; for(uint32_t i=0;icount;i++)for(int e=0;e<2;e++) { VkDescriptorSetAllocateInfo ai={VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,NULL,c->pool,1,&c->set_layout}; if(spvk.AllocateDescriptorSets(vk.device,&ai,&c->sets[i][e])!=VK_SUCCESS)goto fail; } VkPushConstantRange range={VK_SHADER_STAGE_FRAGMENT_BIT,0,sizeof(surf_composite_params)}; VkPipelineLayoutCreateInfo pli={VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,NULL,0,1,&c->set_layout,1,&range}; if(spvk.CreatePipelineLayout(vk.device,&pli,NULL,&c->layout)!=VK_SUCCESS)goto fail; VkShaderModule modules[2]={0}; VkShaderModuleCreateInfo mi={VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,NULL,0,sizeof(surface_composite_vert_spv),surface_composite_vert_spv}; if(spvk.CreateShaderModule(vk.device,&mi,NULL,&modules[0])!=VK_SUCCESS)goto fail; mi.codeSize=sizeof(surface_composite_frag_spv);mi.pCode=surface_composite_frag_spv; if(spvk.CreateShaderModule(vk.device,&mi,NULL,&modules[1])!=VK_SUCCESS) { spvk.DestroyShaderModule(vk.device,modules[0],NULL);goto fail; } VkPipelineShaderStageCreateInfo stages[2]={ {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,NULL,0,VK_SHADER_STAGE_VERTEX_BIT,modules[0],"main"}, {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,NULL,0,VK_SHADER_STAGE_FRAGMENT_BIT,modules[1],"main"}}; VkPipelineVertexInputStateCreateInfo vertices={VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO}; VkPipelineInputAssemblyStateCreateInfo assembly={VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,NULL,0,VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST}; VkPipelineViewportStateCreateInfo viewport={VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,NULL,0,1,NULL,1,NULL}; VkPipelineRasterizationStateCreateInfo raster={VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,NULL,0,VK_FALSE,VK_FALSE, VK_POLYGON_MODE_FILL,VK_CULL_MODE_NONE,VK_FRONT_FACE_COUNTER_CLOCKWISE,VK_FALSE,0,0,0,1}; VkPipelineMultisampleStateCreateInfo samples={VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,NULL,0,VK_SAMPLE_COUNT_1_BIT}; VkPipelineColorBlendAttachmentState blend={.colorWriteMask=VK_COLOR_COMPONENT_R_BIT|VK_COLOR_COMPONENT_G_BIT|VK_COLOR_COMPONENT_B_BIT|VK_COLOR_COMPONENT_A_BIT}; VkPipelineColorBlendStateCreateInfo blends={VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,NULL,0,VK_FALSE,VK_LOGIC_OP_COPY,1,&blend}; VkDynamicState dynamic_states[2]={VK_DYNAMIC_STATE_VIEWPORT,VK_DYNAMIC_STATE_SCISSOR}; VkPipelineDynamicStateCreateInfo dynamic={VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,NULL,0,2,dynamic_states}; VkGraphicsPipelineCreateInfo pci={VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,NULL,0,2,stages,&vertices,&assembly,NULL,&viewport, &raster,&samples,NULL,&blends,&dynamic,c->layout,c->render_pass,0,VK_NULL_HANDLE,-1}; VkResult result=spvk.CreateGraphicsPipelines(vk.device,VK_NULL_HANDLE,1,&pci,NULL,&c->pipeline); for(int k=0;k<2;k++)spvk.DestroyShaderModule(vk.device,modules[k],NULL); if(result!=VK_SUCCESS)goto fail; LOG("surface_composite: one primary full-rate raster stereo scene, %ux%u per eye",p->size,p->size);return 1; fail: surf_composite_destroy(p);c->failed=1; LOG("surface_composite: setup failed; retaining ordinary composition");return 0; } static const XrCompositionLayerBaseHeader *surf_composite_frame(XrSession session,const XrFrameEndInfo *info, const XrCompositionLayerProjection *scene,const XrCompositionLayerBaseHeader *video) { surf_projection *p=NULL; for(int i=0;icache_ready || !sc_scene_supported(scene))return NULL; if(p->presented_job.params[0][7]!=0 || p->presented_job.params[1][7]!=0)return NULL; sc_capture_until=monotonic_ns()+500000000ll; surf_composite *c=&sc_outputs[p-surf_projections]; surf_scene_snapshot *sources[2]={0}; for(int e=0;e<2;e++)for(int i=0;iviews[e].subImage.swapchain) { sources[e]=&sc_scenes[i];break; } if(p->cached_job.space!=surfs[p-surf_projections].video_job.space)return NULL; if(!sources[0] || !sources[1] || sources[0]->captured_release!=sources[0]->releases || sources[1]->captured_release!=sources[1]->releases)goto held; for(int e=0;e<2;e++)if(scene->views[e].next || scene->views[e].subImage.imageArrayIndex>=sources[e]->layers)goto held; if(!sc_setup(session,p,c))return NULL; PFN_xrAcquireSwapchainImage acquire=(PFN_xrAcquireSwapchainImage)lookup(active_instance,"xrAcquireSwapchainImage"); PFN_xrWaitSwapchainImage wait=(PFN_xrWaitSwapchainImage)lookup(active_instance,"xrWaitSwapchainImage"); PFN_xrReleaseSwapchainImage release=(PFN_xrReleaseSwapchainImage)lookup(active_instance,"xrReleaseSwapchainImage"); XrSwapchainImageAcquireInfo ai={XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO}; XrSwapchainImageWaitInfo wi={XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO,NULL,0}; XrSwapchainImageReleaseInfo ri={XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO}; if(!c->acquired) { if(XR_FAILED(acquire(c->output,&ai,&c->index)))goto held; c->acquired=1; } if(wait(c->output,&wi)!=XR_SUCCESS)goto held; if(c->index>=c->count) {release(c->output,&ri);c->acquired=0;goto held;} uint32_t index=c->index; if(!sc_reuse(c->fences[index],&c->pending[index]))goto held; surf_swapchain *s=&surfs[p-surf_projections]; pthread_mutex_lock(&surf_lock);surf_video_job current=s->video_job;pthread_mutex_unlock(&surf_lock); VkCommandBuffer cmd=c->commands[index]; VkCommandBufferBeginInfo begin={VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,NULL,VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT}; if(vk.ResetCommandBuffer(cmd,0)!=VK_SUCCESS || vk.BeginCommandBuffer(cmd,&begin)!=VK_SUCCESS)goto held; VkRenderPassBeginInfo rbi={VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,NULL,c->render_pass,c->framebuffers[index], {{0,0},{2*p->size,p->size}},0,NULL}; spvk.CmdBeginRenderPass(cmd,&rbi,VK_SUBPASS_CONTENTS_INLINE); spvk.CmdBindPipeline(cmd,VK_PIPELINE_BIND_POINT_GRAPHICS,c->pipeline); VkImageView video_view=s->native_current>=0?s->native_slots[s->native_current].srgb_view:p->video_cache_view; for(int e=0;e<2;e++) { VkDescriptorImageInfo di[2]={{c->sampler,video_view,VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}, {c->sampler,sources[e]->view,VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}}; VkWriteDescriptorSet writes[2]; for(int k=0;k<2;k++)writes[k]=(VkWriteDescriptorSet){VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,NULL,c->sets[index][e],k,0,1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,&di[k]}; spvk.UpdateDescriptorSets(vk.device,2,writes,0,NULL); spvk.CmdBindDescriptorSets(cmd,VK_PIPELINE_BIND_POINT_GRAPHICS,c->layout,0,1,&c->sets[index][e],0,NULL); surf_composite_params params=surf_composite_parameters(&p->cached_job,¤t,e, scene->views[e].subImage.imageRect,sources[e]->width,sources[e]->height,scene->views[e].subImage.imageArrayIndex); params.scene_options[0]=(scene->layerFlags&XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT)!=0; params.scene_options[1]=(scene->layerFlags&XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT)!=0; for(const XrBaseInStructure *n=scene->next;n;n=n->next) { if(n->type==XR_TYPE_COMPOSITION_LAYER_COLOR_SCALE_BIAS_KHR) { const XrCompositionLayerColorScaleBiasKHR *color=(const void*)n; memcpy(params.scene_scale,&color->colorScale,16);memcpy(params.scene_bias,&color->colorBias,16); } if(n->type==XR_TYPE_COMPOSITION_LAYER_IMAGE_LAYOUT_FB) params.scene_options[2]=(((const XrCompositionLayerImageLayoutFB*)n)->flags&XR_COMPOSITION_LAYER_IMAGE_LAYOUT_VERTICAL_FLIP_BIT_FB)!=0; } VkViewport viewport={(float)(e*p->size),0,(float)p->size,(float)p->size,0,1}; VkRect2D scissor={{e*(int32_t)p->size,0},{p->size,p->size}}; spvk.CmdSetViewport(cmd,0,1,&viewport);spvk.CmdSetScissor(cmd,0,1,&scissor); spvk.CmdPushConstants(cmd,c->layout,VK_SHADER_STAGE_FRAGMENT_BIT,0,sizeof(params),¶ms); spvk.CmdDraw(cmd,3,1,0,0); } spvk.CmdEndRenderPass(cmd); if(vk.EndCommandBuffer(cmd)!=VK_SUCCESS)goto held; VkSubmitInfo submit={VK_STRUCTURE_TYPE_SUBMIT_INFO,NULL,0,NULL,NULL,1,&cmd}; if(vk.QueueSubmit(vk.queue,1,&submit,c->fences[index])!=VK_SUCCESS)goto held; c->pending[index]=1; if(XR_FAILED(release(c->output,&ri)))goto held; c->acquired=0; for(int e=0;e<2;e++) { c->eyes[e]=scene->views[e];c->eyes[e].next=NULL; c->eyes[e].subImage=(XrSwapchainSubImage){c->output,{{e*(int32_t)p->size,0},{(int32_t)p->size,(int32_t)p->size}},0}; } c->layer=*scene;c->layer.layerFlags &= ~XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT;c->layer.next=NULL;c->layer.views=c->eyes;c->ready=1;c->time=info->displayTime;c->frames++; if(monotonic_ns()-c->log_time>5000000000ll) { LOG("surface_composite: %llu new scene frames, %llu held; no scene/overlay promotion",(unsigned long long)c->frames,(unsigned long long)c->holds); c->log_time=monotonic_ns(); } return (const void*)&c->layer; held: c->holds++; return c->ready && c->layer.space==scene->space?(const void*)&c->layer:NULL; } static int surf_composite_candidate(const XrFrameEndInfo *info,const XrCompositionLayerBaseHeader *video) { if(!info || !info->layerCount || !info->layers[0] || info->layers[0]->type!=XR_TYPE_COMPOSITION_LAYER_PROJECTION || !emul_is_equirect(video))return 0; const XrCompositionLayerProjection *scene=(const void*)info->layers[0]; if(!sc_scene_supported(scene))return 0; emul_common target=emul_common_of(video); for(uint32_t i=1;ilayerCount;i++) { const XrCompositionLayerBaseHeader *layer=info->layers[i]; if(layer==video)return 1; if(!layer)continue; if(!emul_is_equirect(layer))return 0; emul_common other=emul_common_of(layer); if(other.sub.swapchain!=target.sub.swapchain || other.space!=target.space)return 0; } return 0; } static void surf_scene_release_commit(XrSwapchain handle) { for(int i=0;ilayers[0]; return c->ready && !p->acquired && c->layer.space==scene->space && rendered->space==current->space && rendered->params[0][7]==0 && rendered->params[1][7]==0 && !c->failed; } static int surf_composite_active(surf_projection *p) {return sc_outputs[p-surf_projections].ready;}