// Modified by vii from komaruworld/mocktail. See README "About this fork". // Android Vulkan loader ABI -> host Vulkan loader + SDL3 WSI. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mocktail/graphics/vulkan_barrier_adjustment.h" #include "mocktail/graphics/vulkan_foveation_device_policy.h" #include "mocktail/graphics/android_vulkan_wsi_adapter.h" #include "mocktail/graphics/chrome_trace_writer.h" #include "mocktail/graphics/present_mode_policy.h" #include "mocktail/graphics/vulkan_etc2_emulation.h" #include "mocktail/graphics/vulkan_text_overlay_compositor.h" #include "mocktail/platform/platform_runtime.h" #include "mocktail/vr/foveation_focus.h" #include "mocktail/vr/foveation_msaa_contract.h" #include "mocktail/vr/foveation_tile_bounds.h" #include "mocktail/vr/fsr3_scene_inputs.h" extern "C" { VKAPI_ATTR VkResult VKAPI_CALL mocktail_vulkan_idle_synchronized(PFN_vkDeviceWaitIdle raw, VkDevice device); VKAPI_ATTR std::int32_t mocktail_vulkan_run_queue_synchronized( VkDevice device, VkQueue queue, std::int32_t (*operation)(void *), void *context); VKAPI_ATTR VkResult VKAPI_CALL mocktail_vulkan_submit_synchronized( PFN_vkQueueSubmit raw, VkDevice device, VkQueue queue, uint32_t count, const VkSubmitInfo *submits, VkFence fence); VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(const VkInstanceCreateInfo *create_info, const VkAllocationCallbacks *allocator, VkInstance *instance); VKAPI_ATTR void VKAPI_CALL vkDestroyInstance(VkInstance instance, const VkAllocationCallbacks* allocator); VKAPI_ATTR VkResult VKAPI_CALL vkCreateAndroidSurfaceKHR( VkInstance instance, const void* create_info, const VkAllocationCallbacks* allocator, VkSurfaceKHR* surface); VKAPI_ATTR void VKAPI_CALL vkDestroySurfaceKHR(VkInstance instance, VkSurfaceKHR surface, const VkAllocationCallbacks* allocator); VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceExtensionProperties( const char* layer_name, std::uint32_t* property_count, VkExtensionProperties* properties); VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceLayerProperties( std::uint32_t* property_count, VkLayerProperties* properties); VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(VkInstance instance, const char* name); VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkDevice device, const char* name); VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice( VkPhysicalDevice physical_device, const VkDeviceCreateInfo* create_info, const VkAllocationCallbacks* allocator, VkDevice* device); VKAPI_ATTR void VKAPI_CALL vkDestroyDevice(VkDevice device, const VkAllocationCallbacks* allocator); VKAPI_ATTR void VKAPI_CALL vkGetDeviceQueue(VkDevice device, std::uint32_t queue_family_index, std::uint32_t queue_index, VkQueue* queue); VKAPI_ATTR void VKAPI_CALL vkGetDeviceQueue2( VkDevice device, const VkDeviceQueueInfo2* queue_info, VkQueue* queue); VKAPI_ATTR VkResult VKAPI_CALL vkCreateSwapchainKHR( VkDevice device, const VkSwapchainCreateInfoKHR* create_info, const VkAllocationCallbacks* allocator, VkSwapchainKHR* swapchain); VKAPI_ATTR void VKAPI_CALL vkDestroySwapchainKHR(VkDevice device, VkSwapchainKHR swapchain, const VkAllocationCallbacks* allocator); VKAPI_ATTR VkResult VKAPI_CALL vkAcquireNextImageKHR( VkDevice device, VkSwapchainKHR swapchain, std::uint64_t timeout, VkSemaphore semaphore, VkFence fence, std::uint32_t* image_index); VKAPI_ATTR VkResult VKAPI_CALL vkAcquireNextImage2KHR( VkDevice device, const VkAcquireNextImageInfoKHR *acquire_info, std::uint32_t *image_index); VKAPI_ATTR VkResult VKAPI_CALL vkWaitForFences(VkDevice device, std::uint32_t fence_count, const VkFence *fences, VkBool32 wait_all, std::uint64_t timeout); VKAPI_ATTR VkResult VKAPI_CALL vkResetFences(VkDevice device, std::uint32_t fence_count, const VkFence *fences); VKAPI_ATTR VkResult VKAPI_CALL vkCreateCommandPool( VkDevice device, const VkCommandPoolCreateInfo *info, const VkAllocationCallbacks *allocator, VkCommandPool *pool); VKAPI_ATTR void VKAPI_CALL vkCmdPipelineBarrier( VkCommandBuffer command, VkPipelineStageFlags src, VkPipelineStageFlags dst, VkDependencyFlags flags, uint32_t memory_count, const VkMemoryBarrier *memory, uint32_t buffer_count, const VkBufferMemoryBarrier *buffers, uint32_t image_count, const VkImageMemoryBarrier *images); VKAPI_ATTR void VKAPI_CALL vkCmdPipelineBarrier2(VkCommandBuffer command, const VkDependencyInfo *info); VKAPI_ATTR void VKAPI_CALL vkCmdPipelineBarrier2KHR(VkCommandBuffer command, const VkDependencyInfo *info); VKAPI_ATTR VkResult VKAPI_CALL vkResetCommandPool( VkDevice device, VkCommandPool command_pool, VkCommandPoolResetFlags flags); VKAPI_ATTR VkResult VKAPI_CALL vkGetQueryPoolResults( VkDevice device, VkQueryPool query_pool, std::uint32_t first_query, std::uint32_t query_count, std::size_t data_size, void* data, VkDeviceSize stride, VkQueryResultFlags flags); VKAPI_ATTR VkResult VKAPI_CALL vkAllocateCommandBuffers( VkDevice device, const VkCommandBufferAllocateInfo* allocate_info, VkCommandBuffer* command_buffers); VKAPI_ATTR void VKAPI_CALL vkFreeCommandBuffers( VkDevice device, VkCommandPool command_pool, std::uint32_t command_buffer_count, const VkCommandBuffer* command_buffers); VKAPI_ATTR void VKAPI_CALL vkDestroyCommandPool( VkDevice device, VkCommandPool command_pool, const VkAllocationCallbacks* allocator); VKAPI_ATTR VkResult VKAPI_CALL vkBeginCommandBuffer( VkCommandBuffer command_buffer, const VkCommandBufferBeginInfo* begin_info); VKAPI_ATTR VkResult VKAPI_CALL vkEndCommandBuffer(VkCommandBuffer command_buffer); VKAPI_ATTR VkResult VKAPI_CALL vkResetCommandBuffer( VkCommandBuffer command_buffer, VkCommandBufferResetFlags flags); VKAPI_ATTR VkResult VKAPI_CALL vkWaitSemaphores( VkDevice device, const VkSemaphoreWaitInfo* wait_info, std::uint64_t timeout); VKAPI_ATTR VkResult VKAPI_CALL vkWaitSemaphoresKHR( VkDevice device, const VkSemaphoreWaitInfo* wait_info, std::uint64_t timeout); VKAPI_ATTR VkResult VKAPI_CALL vkQueueSubmit( VkQueue queue, std::uint32_t submit_count, const VkSubmitInfo* submits, VkFence fence); VKAPI_ATTR VkResult VKAPI_CALL vkQueueSubmit2( VkQueue queue, std::uint32_t submit_count, const VkSubmitInfo2* submits, VkFence fence); VKAPI_ATTR VkResult VKAPI_CALL vkQueueSubmit2KHR( VkQueue queue, std::uint32_t submit_count, const VkSubmitInfo2* submits, VkFence fence); VKAPI_ATTR VkResult VKAPI_CALL vkQueueBindSparse( VkQueue queue, std::uint32_t bind_info_count, const VkBindSparseInfo* bind_info, VkFence fence); VKAPI_ATTR VkResult VKAPI_CALL vkQueueWaitIdle(VkQueue queue); VKAPI_ATTR VkResult VKAPI_CALL vkDeviceWaitIdle(VkDevice device); VKAPI_ATTR VkResult VKAPI_CALL vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info); VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceCapabilitiesKHR( VkPhysicalDevice physical_device, VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR* capabilities); VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfacePresentModesKHR( VkPhysicalDevice physical_device, VkSurfaceKHR surface, std::uint32_t* present_mode_count, VkPresentModeKHR* present_modes); VKAPI_ATTR VkResult VKAPI_CALL vkCreateImage( VkDevice device, const VkImageCreateInfo* create_info, const VkAllocationCallbacks* allocator, VkImage* image); VKAPI_ATTR void VKAPI_CALL vkDestroyImage(VkDevice device, VkImage image, const VkAllocationCallbacks* allocator); VKAPI_ATTR VkResult VKAPI_CALL vkCreateImageView( VkDevice device, const VkImageViewCreateInfo* create_info, const VkAllocationCallbacks* allocator, VkImageView* view); VKAPI_ATTR void VKAPI_CALL vkDestroyImageView(VkDevice device, VkImageView image_view, const VkAllocationCallbacks* allocator); VKAPI_ATTR VkResult VKAPI_CALL vkCreateFramebuffer( VkDevice device, const VkFramebufferCreateInfo* create_info, const VkAllocationCallbacks* allocator, VkFramebuffer* framebuffer); VKAPI_ATTR void VKAPI_CALL vkDestroyFramebuffer(VkDevice device, VkFramebuffer framebuffer, const VkAllocationCallbacks* allocator); VKAPI_ATTR VkResult VKAPI_CALL vkCreateRenderPass( VkDevice device, const VkRenderPassCreateInfo* create_info, const VkAllocationCallbacks* allocator, VkRenderPass* render_pass); VKAPI_ATTR void VKAPI_CALL vkDestroyRenderPass(VkDevice device, VkRenderPass render_pass, const VkAllocationCallbacks* allocator); VKAPI_ATTR void VKAPI_CALL vkCmdBeginRenderPass(VkCommandBuffer command_buffer, const VkRenderPassBeginInfo* begin_info, VkSubpassContents contents); VKAPI_ATTR void VKAPI_CALL vkCmdEndRenderPass(VkCommandBuffer command_buffer); VKAPI_ATTR void VKAPI_CALL vkCmdBindPipeline(VkCommandBuffer,VkPipelineBindPoint,VkPipeline); VKAPI_ATTR void VKAPI_CALL vkCmdDraw(VkCommandBuffer,uint32_t,uint32_t,uint32_t,uint32_t); VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndexed(VkCommandBuffer,uint32_t,uint32_t,uint32_t,int32_t,uint32_t); VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndirect(VkCommandBuffer,VkBuffer,VkDeviceSize,uint32_t,uint32_t); VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndexedIndirect(VkCommandBuffer,VkBuffer,VkDeviceSize,uint32_t,uint32_t); VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndirectCount(VkCommandBuffer,VkBuffer,VkDeviceSize,VkBuffer,VkDeviceSize,uint32_t,uint32_t); VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndexedIndirectCount(VkCommandBuffer,VkBuffer,VkDeviceSize,VkBuffer,VkDeviceSize,uint32_t,uint32_t); VKAPI_ATTR void VKAPI_CALL vkDestroyPipeline(VkDevice,VkPipeline,const VkAllocationCallbacks*); VKAPI_ATTR void VKAPI_CALL vkDestroyShaderModule(VkDevice,VkShaderModule,const VkAllocationCallbacks*); } namespace { bool FovTraceEnabled(); bool Fsr3InputsAvailable(); bool Fsr3InputsEnabled(); bool Fsr3ShaderDumpEnabled(); void Fsr3DumpShader(VkDevice,VkShaderModule,const VkShaderModuleCreateInfo*); void Fsr3DumpGraphicsPipelines(VkDevice,unsigned,const VkGraphicsPipelineCreateInfo*,const VkPipeline*); void Fsr3ResetCommand(VkCommandBuffer); void FovTraceComplete(VkDevice,VkQueue); using BackendWindowFn = void* (*)(); using UsesDirectVulkanFn = bool (*)(); using NotePresentFn = void (*)(); using NoteHostPresentBeginFn = void (*)(); using NoteHostPresentEndFn = void (*)(std::int32_t); using NoteVrPresentBeginFn = void (*)(); using VrXrCreateInstanceFn = bool (*)(const VkInstanceCreateInfo*, const VkAllocationCallbacks*, VkInstance*, VkResult*); using VrXrCreateDeviceFn = bool (*)(VkPhysicalDevice, const VkDeviceCreateInfo*, const VkAllocationCallbacks*, VkDevice*, VkResult*); using VrXrNoteDeviceDestroyedFn = bool (*)(VkDevice); using VrXrNoteQueueFn = void (*)(VkDevice, VkQueue, std::uint32_t, std::uint32_t); using VrXrRecordImageFn = void (*)(VkDevice, VkImage, const VkImageCreateInfo*); using VrXrRecordImageViewFn = void (*)(VkImageView, VkImage); using VrXrRecordFramebufferFn = void (*)(VkFramebuffer, const VkFramebufferCreateInfo*); using VrXrRecordRenderPassFn = void (*)(VkRenderPass, const VkRenderPassCreateInfo*); using VrXrNoteDestroyImageFn = void (*)(VkImage); using VrXrNoteDestroyImageViewFn = void (*)(VkImageView); using VrXrNoteDestroyFramebufferFn = void (*)(VkFramebuffer); using VrXrNoteRenderPassBeginFn = void (*)(VkCommandBuffer, const VkRenderPassBeginInfo*); using VrXrNotePresentFn = void (*)(VkQueue, VkDevice); using NoteVulkanCallBeginFn = std::uint64_t (*)(const char*); using NoteVulkanCallEndFn = void (*)(std::uint64_t, std::int32_t); using NoteSurfaceOutOfDateFn = void (*)(); using WindowDimensionFn = int (*)(); struct AdapterState { struct DeviceDispatch { VkDevice device = VK_NULL_HANDLE; VkPhysicalDevice physical_device = VK_NULL_HANDLE; PFN_vkQueuePresentKHR queue_present = nullptr; PFN_vkQueueSubmit queue_submit = nullptr; PFN_vkQueueSubmit2 queue_submit2 = nullptr; PFN_vkQueueSubmit2KHR queue_submit2_khr = nullptr; PFN_vkQueueBindSparse queue_bind_sparse = nullptr; PFN_vkQueueWaitIdle queue_wait_idle = nullptr; PFN_vkAcquireNextImageKHR acquire_next_image = nullptr; PFN_vkAcquireNextImage2KHR acquire_next_image2 = nullptr; PFN_vkWaitForFences wait_for_fences = nullptr; PFN_vkWaitSemaphores wait_semaphores = nullptr; PFN_vkWaitSemaphoresKHR wait_semaphores_khr = nullptr; PFN_vkDeviceWaitIdle device_wait_idle = nullptr; PFN_vkResetFences reset_fences = nullptr; PFN_vkResetCommandPool reset_command_pool = nullptr; PFN_vkGetQueryPoolResults get_query_pool_results = nullptr; PFN_vkAllocateCommandBuffers allocate_command_buffers = nullptr; PFN_vkFreeCommandBuffers free_command_buffers = nullptr; PFN_vkDestroyCommandPool destroy_command_pool = nullptr; PFN_vkBeginCommandBuffer begin_command_buffer = nullptr; PFN_vkEndCommandBuffer end_command_buffer = nullptr; PFN_vkResetCommandBuffer reset_command_buffer = nullptr; PFN_vkCreateImage create_image = nullptr; PFN_vkDestroyImage destroy_image = nullptr; PFN_vkCreateImageView create_image_view = nullptr; PFN_vkDestroyImageView destroy_image_view = nullptr; PFN_vkCreateFramebuffer create_framebuffer = nullptr; PFN_vkDestroyFramebuffer destroy_framebuffer = nullptr; PFN_vkCreateRenderPass create_render_pass = nullptr; PFN_vkDestroyRenderPass destroy_render_pass = nullptr; PFN_vkCmdPipelineBarrier cmd_pipeline_barrier = nullptr; PFN_vkCmdPipelineBarrier2 cmd_pipeline_barrier2 = nullptr; PFN_vkCmdPipelineBarrier2KHR cmd_pipeline_barrier2_khr = nullptr; PFN_vkCmdBeginRenderPass cmd_begin_render_pass = nullptr; PFN_vkCmdEndRenderPass cmd_end_render_pass = nullptr; PFN_vkCmdBindPipeline cmd_bind_pipeline = nullptr; PFN_vkCmdDraw cmd_draw = nullptr; PFN_vkCmdDrawIndexed cmd_draw_indexed = nullptr; PFN_vkCmdDrawIndirect cmd_draw_indirect = nullptr; PFN_vkCmdDrawIndexedIndirect cmd_draw_indexed_indirect = nullptr; PFN_vkCmdDrawIndirectCount cmd_draw_indirect_count = nullptr; PFN_vkCmdDrawIndexedIndirectCount cmd_draw_indexed_indirect_count = nullptr; PFN_vkDestroyPipeline destroy_pipeline = nullptr; PFN_vkDestroyShaderModule destroy_shader_module = nullptr; }; struct QueueBinding { VkQueue queue = VK_NULL_HANDLE; VkDevice device = VK_NULL_HANDLE; }; struct CommandBufferBinding { VkCommandBuffer command_buffer = VK_NULL_HANDLE; VkDevice device = VK_NULL_HANDLE; VkCommandPool command_pool = VK_NULL_HANDLE; }; std::mutex mutex; mocktail::graphics::SdlVulkanWsi host_wsi; mocktail::graphics::AndroidVulkanWsiAdapter android_wsi; PFN_vkGetInstanceProcAddr host_get_instance_proc_addr = nullptr; std::atomic host_get_device_proc_addr{nullptr}; std::vector device_dispatches; std::vector queue_bindings; std::vector command_buffer_bindings; PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR host_surface_capabilities = nullptr; PFN_vkGetPhysicalDeviceSurfacePresentModesKHR host_surface_present_modes = nullptr; VkInstance latest_instance = VK_NULL_HANDLE; size_t active_instance_count = 0; mocktail::graphics::VulkanTextOverlayCompositor text_overlay; mocktail::graphics::VulkanEtc2Emulation etc2; std::atomic note_present{nullptr}; std::atomic note_host_present_begin{nullptr}; std::atomic note_host_present_end{nullptr}; std::atomic note_vr_present_begin{nullptr}; std::atomic vr_create_instance{nullptr}; std::atomic vr_create_device{nullptr}; std::atomic vr_note_device_destroyed{nullptr}; std::atomic vr_note_queue{nullptr}; std::atomic vr_record_image{nullptr}; std::atomic vr_record_image_view{nullptr}; std::atomic vr_record_framebuffer{nullptr}; std::atomic vr_record_render_pass{nullptr}; std::atomic vr_note_destroy_image{nullptr}; std::atomic vr_note_destroy_image_view{nullptr}; std::atomic vr_note_destroy_framebuffer{nullptr}; std::atomic vr_note_render_pass_begin{nullptr}; std::atomic vr_note_present{nullptr}; std::atomic note_vulkan_call_begin{nullptr}; std::atomic note_vulkan_call_end{nullptr}; std::atomic note_surface_out_of_date{nullptr}; bool extent_translation_logged = false; bool present_policy_logged = false; bool initialized = false; }; static std::atomic g_vulkan_call_observation_active{false}; AdapterState& State() { static AdapterState state; return state; } template Function ResolveProcessFunction(const char* name) { return reinterpret_cast(dlsym(RTLD_DEFAULT, name)); } // The guest synchronizes its own queue use. An armed OpenXR backend adds // another user of the same queue (eye copies, and the runtime inside // xrEndFrame), so only then must every queue call be serialized: queue calls // share a per-device gate and a per-queue mutex, and device idle takes the // gate exclusively. Without VR (or in a build without it) nothing is locked. // Latched once the VR backend reports it armed; never cleared, since the // runtime may use the queue until the process ends. The backend lives in the // executable and arms before this library is loaded, so it is asked here. std::atomic g_queue_sharing{false}; bool QueueSharingActive() { if (g_queue_sharing.load(std::memory_order_acquire)) return true; static const auto requested = ResolveProcessFunction( "mocktail_vr_xr_queue_sharing_requested"); if (requested == nullptr || !requested()) return false; g_queue_sharing.store(true, std::memory_order_release); return true; } std::shared_ptr DeviceIdleGate(VkDevice device) { static std::mutex registry_mutex; static std::unordered_map> registry; std::lock_guard lock(registry_mutex); auto &gate = registry[device]; if (!gate) gate = std::make_shared(); return gate; } std::shared_ptr QueueCallMutex(VkQueue queue) { static std::mutex registry_mutex; static std::unordered_map> registry; std::lock_guard lock(registry_mutex); auto &mutex = registry[queue]; if (!mutex) mutex = std::make_shared(); return mutex; } // Held for one queue call while VR shares the queue; a no-op otherwise. // Release it before fence waits and backend callbacks. class QueueCallGuard { public: QueueCallGuard(VkDevice device, VkQueue queue) { if (!QueueSharingActive()) return; gate_ = DeviceIdleGate(device); gate_lock_ = std::shared_lock(*gate_); mutex_ = QueueCallMutex(queue); lock_ = std::unique_lock(*mutex_); } private: std::shared_ptr gate_; std::shared_lock gate_lock_; std::shared_ptr mutex_; std::unique_lock lock_; }; // Exclusive device idle while VR shares the queue; a no-op otherwise. class DeviceIdleGuard { public: explicit DeviceIdleGuard(VkDevice device) { if (!QueueSharingActive()) return; gate_ = DeviceIdleGate(device); lock_ = std::unique_lock(*gate_); } private: std::shared_ptr gate_; std::unique_lock lock_; }; void NoteVrSubmitted(VkQueue queue, uint32_t count, const VkSubmitInfo *submits) { static const auto note = ResolveProcessFunction("mocktail_vr_xr_submitted"); if (note && submits) for (uint32_t i = 0; i < count; ++i) note(queue, submits[i].commandBufferCount, submits[i].pCommandBuffers); } void NoteVrSubmitted2(VkQueue queue, uint32_t count, const VkSubmitInfo2 *submits) { static const auto note = ResolveProcessFunction("mocktail_vr_xr_submitted"); if (note && submits) for (uint32_t i = 0; i < count; ++i) for (uint32_t j = 0; j < submits[i].commandBufferInfoCount; ++j) note(queue, 1, &submits[i].pCommandBufferInfos[j].commandBuffer); } bool EnsureInitialized() { AdapterState& state = State(); std::lock_guard lock(state.mutex); if (state.initialized) { return true; } const auto register_submit = ResolveProcessFunction("mocktail_vr_xr_queue_submit_adapter"); if (register_submit) register_submit(mocktail_vulkan_submit_synchronized); const auto register_operation = ResolveProcessFunction( "mocktail_vr_xr_queue_operation_adapter"); if (register_operation) register_operation(mocktail_vulkan_run_queue_synchronized); const auto register_idle = ResolveProcessFunction( "mocktail_vr_xr_device_idle_adapter"); if (register_idle) register_idle(mocktail_vulkan_idle_synchronized); if(FovTraceEnabled()) { const auto report=ResolveProcessFunction("mocktail_vr_xr_pass_timing_reporter"); if(report)report(FovTraceComplete); } const BackendWindowFn backend_window = ResolveProcessFunction("mocktail_window_backend_window"); const UsesDirectVulkanFn uses_direct_vulkan = ResolveProcessFunction( "mocktail_window_uses_direct_vulkan"); state.note_present.store(ResolveProcessFunction( "mocktail_window_note_vulkan_present"), std::memory_order_release); state.note_host_present_begin.store( ResolveProcessFunction( "mocktail_window_note_vulkan_host_present_begin"), std::memory_order_release); state.note_host_present_end.store( ResolveProcessFunction( "mocktail_window_note_vulkan_host_present_end"), std::memory_order_release); state.note_vr_present_begin.store( ResolveProcessFunction( "mocktail_vr_note_host_present_begin"), std::memory_order_release); state.vr_create_instance.store( ResolveProcessFunction( "mocktail_vr_xr_create_vulkan_instance"), std::memory_order_release); state.vr_create_device.store( ResolveProcessFunction( "mocktail_vr_xr_create_vulkan_device"), std::memory_order_release); state.vr_note_device_destroyed.store( ResolveProcessFunction( "mocktail_vr_xr_note_device_destroyed"), std::memory_order_release); state.vr_note_queue.store( ResolveProcessFunction("mocktail_vr_xr_note_queue"), std::memory_order_release); state.vr_record_image.store( ResolveProcessFunction("mocktail_vr_xr_record_image"), std::memory_order_release); state.vr_record_image_view.store( ResolveProcessFunction( "mocktail_vr_xr_record_image_view"), std::memory_order_release); state.vr_record_framebuffer.store( ResolveProcessFunction( "mocktail_vr_xr_record_framebuffer"), std::memory_order_release); state.vr_record_render_pass.store( ResolveProcessFunction( "mocktail_vr_xr_record_render_pass"), std::memory_order_release); state.vr_note_destroy_image.store( ResolveProcessFunction( "mocktail_vr_xr_note_destroy_image"), std::memory_order_release); state.vr_note_destroy_image_view.store( ResolveProcessFunction( "mocktail_vr_xr_note_destroy_image_view"), std::memory_order_release); state.vr_note_destroy_framebuffer.store( ResolveProcessFunction( "mocktail_vr_xr_note_destroy_framebuffer"), std::memory_order_release); state.vr_note_render_pass_begin.store( ResolveProcessFunction( "mocktail_vr_xr_note_render_pass_begin"), std::memory_order_release); state.vr_note_present.store( ResolveProcessFunction("mocktail_vr_xr_note_present"), std::memory_order_release); const auto call_begin = ResolveProcessFunction( "mocktail_window_note_vulkan_call_begin"); const auto call_end = ResolveProcessFunction( "mocktail_window_note_vulkan_call_end"); state.note_vulkan_call_begin.store(call_begin, std::memory_order_release); state.note_vulkan_call_end.store(call_end, std::memory_order_release); g_vulkan_call_observation_active.store( call_begin != nullptr && call_end != nullptr, std::memory_order_release); state.note_surface_out_of_date.store( ResolveProcessFunction( "mocktail_window_note_vulkan_surface_out_of_date"), std::memory_order_release); if (backend_window == nullptr || uses_direct_vulkan == nullptr || !uses_direct_vulkan() || backend_window() == nullptr) { std::fprintf(stderr, " [vulkan] direct SDL Vulkan window is unavailable\n"); return false; } mocktail::platform::NativeWindowDescriptor descriptor; descriptor.surface_api = mocktail::platform::WindowSurfaceApi::kDirectVulkan; descriptor.backend_window = backend_window(); mocktail::Status status = state.host_wsi.Initialize(descriptor); if (!status.ok()) { std::fprintf(stderr, " [vulkan] SDL WSI initialization failed: %s\n", status.message().c_str()); return false; } status = state.android_wsi.Initialize(&state.host_wsi); if (!status.ok()) { std::fprintf(stderr, " [vulkan] Android WSI initialization failed: %s\n", status.message().c_str()); state.host_wsi.Shutdown(); return false; } state.host_get_instance_proc_addr = reinterpret_cast( state.host_wsi.GetInstanceProcAddress()); if (state.host_get_instance_proc_addr == nullptr) { state.android_wsi.Shutdown(); state.host_wsi.Shutdown(); return false; } state.initialized = true; std::fprintf(stderr, " [vulkan] Android WSI -> SDL3 host adapter ready\n"); return true; } void NoteSuboptimalTranslation() { static std::atomic logged{false}; if (!logged.exchange(true, std::memory_order_relaxed)) { std::fprintf(stderr, " [vulkan] accepted VK_SUBOPTIMAL_KHR; the acquired image " "remains usable\n"); } } VkResult NormalizeSwapchainResult(VkResult result) { if (result == VK_SUBOPTIMAL_KHR) { NoteSuboptimalTranslation(); } return mocktail::graphics::NormalizeAndroidSwapchainResult(result); } PFN_vkVoidFunction HostInstanceProc(VkInstance instance, const char* name) { if (!EnsureInitialized() || name == nullptr) { return nullptr; } return State().host_get_instance_proc_addr(instance, name); } PFN_vkVoidFunction HostDeviceProc(VkDevice device, const char* name) { if (!EnsureInitialized() || name == nullptr) { return nullptr; } const PFN_vkGetDeviceProcAddr host_get_device_proc_addr = State().host_get_device_proc_addr.load(std::memory_order_acquire); return host_get_device_proc_addr != nullptr ? host_get_device_proc_addr(device, name) : nullptr; } static std::atomic g_dispatch_generation{1}; struct ThreadQueueDispatchCache { uint64_t generation = 0; VkQueue queue = VK_NULL_HANDLE; AdapterState::DeviceDispatch dispatch{}; }; static thread_local ThreadQueueDispatchCache t_queue_cache; struct ThreadDeviceDispatchCache { uint64_t generation = 0; VkDevice device = VK_NULL_HANDLE; AdapterState::DeviceDispatch dispatch{}; }; static thread_local ThreadDeviceDispatchCache t_device_cache; struct ThreadCommandBufferDispatchCache { uint64_t generation = 0; VkCommandBuffer command_buffer = VK_NULL_HANDLE; AdapterState::DeviceDispatch dispatch{}; }; static thread_local ThreadCommandBufferDispatchCache t_command_buffer_cache; void InvalidateFastDispatchCaches() { g_dispatch_generation.fetch_add(1, std::memory_order_release); } static const AdapterState::DeviceDispatch kEmptyDeviceDispatch{}; const AdapterState::DeviceDispatch& HostDispatchForQueue(VkQueue queue) { const uint64_t gen = g_dispatch_generation.load(std::memory_order_acquire); if (__builtin_expect(queue != VK_NULL_HANDLE && t_queue_cache.queue == queue && t_queue_cache.generation == gen, 1)) { return t_queue_cache.dispatch; } AdapterState& state = State(); std::lock_guard lock(state.mutex); const auto binding = std::find_if(state.queue_bindings.begin(), state.queue_bindings.end(), [queue](const AdapterState::QueueBinding& candidate) { return candidate.queue == queue; }); if (binding == state.queue_bindings.end()) { return kEmptyDeviceDispatch; } const auto dispatch = std::find_if( state.device_dispatches.begin(), state.device_dispatches.end(), [binding](const AdapterState::DeviceDispatch& candidate) { return candidate.device == binding->device; }); if (dispatch != state.device_dispatches.end()) { t_queue_cache.generation = gen; t_queue_cache.queue = queue; t_queue_cache.dispatch = *dispatch; return t_queue_cache.dispatch; } return kEmptyDeviceDispatch; } const AdapterState::DeviceDispatch& HostDispatchForDevice(VkDevice device) { const uint64_t gen = g_dispatch_generation.load(std::memory_order_acquire); if (__builtin_expect(device != VK_NULL_HANDLE && t_device_cache.device == device && t_device_cache.generation == gen, 1)) { return t_device_cache.dispatch; } AdapterState& state = State(); std::lock_guard lock(state.mutex); const auto dispatch = std::find_if( state.device_dispatches.begin(), state.device_dispatches.end(), [device](const AdapterState::DeviceDispatch& candidate) { return candidate.device == device; }); if (dispatch != state.device_dispatches.end()) { t_device_cache.generation = gen; t_device_cache.device = device; t_device_cache.dispatch = *dispatch; return t_device_cache.dispatch; } return kEmptyDeviceDispatch; } const AdapterState::DeviceDispatch& HostDispatchForCommandBuffer( VkCommandBuffer command_buffer) { const uint64_t gen = g_dispatch_generation.load(std::memory_order_acquire); if (__builtin_expect(command_buffer != VK_NULL_HANDLE && t_command_buffer_cache.command_buffer == command_buffer && t_command_buffer_cache.generation == gen, 1)) { return t_command_buffer_cache.dispatch; } AdapterState& state = State(); std::lock_guard lock(state.mutex); const auto binding = std::find_if( state.command_buffer_bindings.begin(), state.command_buffer_bindings.end(), [command_buffer](const AdapterState::CommandBufferBinding& candidate) { return candidate.command_buffer == command_buffer; }); if (binding == state.command_buffer_bindings.end()) { return kEmptyDeviceDispatch; } const auto dispatch = std::find_if( state.device_dispatches.begin(), state.device_dispatches.end(), [binding](const AdapterState::DeviceDispatch& candidate) { return candidate.device == binding->device; }); if (dispatch != state.device_dispatches.end()) { t_command_buffer_cache.generation = gen; t_command_buffer_cache.command_buffer = command_buffer; t_command_buffer_cache.dispatch = *dispatch; return t_command_buffer_cache.dispatch; } return kEmptyDeviceDispatch; } VkResult VKAPI_CALL LockedHostSubmit(VkQueue queue, uint32_t count, const VkSubmitInfo *submits, VkFence fence) { return mocktail_vulkan_submit_synchronized( HostDispatchForQueue(queue).queue_submit, HostDispatchForQueue(queue).device, queue, count, submits, fence); } VkResult VKAPI_CALL LockedHostSubmit2(VkQueue queue, uint32_t count, const VkSubmitInfo2 *submits, VkFence fence) { const auto raw = HostDispatchForQueue(queue).queue_submit2; const QueueCallGuard guard(HostDispatchForQueue(queue).device, queue); return raw(queue, count, submits, fence); } VkResult VKAPI_CALL LockedHostSubmit2KHR(VkQueue queue, uint32_t count, const VkSubmitInfo2 *submits, VkFence fence) { const auto raw = HostDispatchForQueue(queue).queue_submit2_khr; const QueueCallGuard guard(HostDispatchForQueue(queue).device, queue); return raw(queue, count, submits, fence); } VkResult VKAPI_CALL LockedHostBindSparse(VkQueue queue, uint32_t count, const VkBindSparseInfo *binds, VkFence fence) { const auto raw = HostDispatchForQueue(queue).queue_bind_sparse; const QueueCallGuard guard(HostDispatchForQueue(queue).device, queue); return raw(queue, count, binds, fence); } VkResult VKAPI_CALL LockedHostQueueWaitIdle(VkQueue queue) { const auto raw = HostDispatchForQueue(queue).queue_wait_idle; const QueueCallGuard guard(HostDispatchForQueue(queue).device, queue); return raw(queue); } VkResult VKAPI_CALL LockedHostDeviceWaitIdle(VkDevice device) { return mocktail_vulkan_idle_synchronized( HostDispatchForDevice(device).device_wait_idle, device); } void RegisterHostDeviceDispatch(VkDevice device, VkPhysicalDevice physical_device, PFN_vkGetDeviceProcAddr get_device_proc_addr) { if (device == VK_NULL_HANDLE || get_device_proc_addr == nullptr) { return; } AdapterState::DeviceDispatch dispatch; dispatch.device = device; dispatch.physical_device = physical_device; dispatch.queue_present = reinterpret_cast( get_device_proc_addr(device, "vkQueuePresentKHR")); dispatch.queue_submit = reinterpret_cast( get_device_proc_addr(device, "vkQueueSubmit")); dispatch.queue_submit2 = reinterpret_cast( get_device_proc_addr(device, "vkQueueSubmit2")); dispatch.queue_submit2_khr = reinterpret_cast( get_device_proc_addr(device, "vkQueueSubmit2KHR")); dispatch.queue_bind_sparse = reinterpret_cast( get_device_proc_addr(device, "vkQueueBindSparse")); dispatch.queue_wait_idle = reinterpret_cast( get_device_proc_addr(device, "vkQueueWaitIdle")); dispatch.acquire_next_image = reinterpret_cast( get_device_proc_addr(device, "vkAcquireNextImageKHR")); dispatch.acquire_next_image2 = reinterpret_cast( get_device_proc_addr(device, "vkAcquireNextImage2KHR")); dispatch.wait_for_fences = reinterpret_cast( get_device_proc_addr(device, "vkWaitForFences")); dispatch.wait_semaphores = reinterpret_cast( get_device_proc_addr(device, "vkWaitSemaphores")); dispatch.wait_semaphores_khr = reinterpret_cast( get_device_proc_addr(device, "vkWaitSemaphoresKHR")); dispatch.device_wait_idle = reinterpret_cast( get_device_proc_addr(device, "vkDeviceWaitIdle")); dispatch.reset_fences = reinterpret_cast( get_device_proc_addr(device, "vkResetFences")); dispatch.reset_command_pool = reinterpret_cast( get_device_proc_addr(device, "vkResetCommandPool")); dispatch.get_query_pool_results = reinterpret_cast( get_device_proc_addr(device, "vkGetQueryPoolResults")); dispatch.allocate_command_buffers = reinterpret_cast( get_device_proc_addr(device, "vkAllocateCommandBuffers")); dispatch.free_command_buffers = reinterpret_cast( get_device_proc_addr(device, "vkFreeCommandBuffers")); dispatch.destroy_command_pool = reinterpret_cast( get_device_proc_addr(device, "vkDestroyCommandPool")); dispatch.begin_command_buffer = reinterpret_cast( get_device_proc_addr(device, "vkBeginCommandBuffer")); dispatch.end_command_buffer = reinterpret_cast( get_device_proc_addr(device, "vkEndCommandBuffer")); dispatch.reset_command_buffer = reinterpret_cast( get_device_proc_addr(device, "vkResetCommandBuffer")); dispatch.create_image = reinterpret_cast( get_device_proc_addr(device, "vkCreateImage")); dispatch.destroy_image = reinterpret_cast( get_device_proc_addr(device, "vkDestroyImage")); dispatch.create_image_view = reinterpret_cast( get_device_proc_addr(device, "vkCreateImageView")); dispatch.destroy_image_view = reinterpret_cast( get_device_proc_addr(device, "vkDestroyImageView")); dispatch.create_framebuffer = reinterpret_cast( get_device_proc_addr(device, "vkCreateFramebuffer")); dispatch.destroy_framebuffer = reinterpret_cast( get_device_proc_addr(device, "vkDestroyFramebuffer")); dispatch.create_render_pass = reinterpret_cast( get_device_proc_addr(device, "vkCreateRenderPass")); dispatch.destroy_render_pass = reinterpret_cast( get_device_proc_addr(device, "vkDestroyRenderPass")); dispatch.cmd_pipeline_barrier = reinterpret_cast( get_device_proc_addr(device, "vkCmdPipelineBarrier")); dispatch.cmd_pipeline_barrier2 = reinterpret_cast( get_device_proc_addr(device, "vkCmdPipelineBarrier2")); dispatch.cmd_pipeline_barrier2_khr = reinterpret_cast( get_device_proc_addr(device, "vkCmdPipelineBarrier2KHR")); dispatch.cmd_begin_render_pass = reinterpret_cast( get_device_proc_addr(device, "vkCmdBeginRenderPass")); dispatch.cmd_end_render_pass = reinterpret_cast( get_device_proc_addr(device, "vkCmdEndRenderPass")); dispatch.cmd_bind_pipeline = reinterpret_cast( get_device_proc_addr(device, "vkCmdBindPipeline")); dispatch.cmd_draw = reinterpret_cast( get_device_proc_addr(device, "vkCmdDraw")); dispatch.cmd_draw_indexed = reinterpret_cast( get_device_proc_addr(device, "vkCmdDrawIndexed")); dispatch.cmd_draw_indirect = reinterpret_cast( get_device_proc_addr(device, "vkCmdDrawIndirect")); dispatch.cmd_draw_indexed_indirect = reinterpret_cast( get_device_proc_addr(device, "vkCmdDrawIndexedIndirect")); dispatch.cmd_draw_indirect_count = reinterpret_cast( get_device_proc_addr(device, "vkCmdDrawIndirectCount")); dispatch.cmd_draw_indexed_indirect_count = reinterpret_cast( get_device_proc_addr(device, "vkCmdDrawIndexedIndirectCount")); dispatch.destroy_pipeline = reinterpret_cast( get_device_proc_addr(device, "vkDestroyPipeline")); dispatch.destroy_shader_module = reinterpret_cast( get_device_proc_addr(device, "vkDestroyShaderModule")); if(!dispatch.cmd_draw_indirect_count)dispatch.cmd_draw_indirect_count=reinterpret_cast( get_device_proc_addr(device,"vkCmdDrawIndirectCountKHR")); if(!dispatch.cmd_draw_indirect_count)dispatch.cmd_draw_indirect_count=reinterpret_cast( get_device_proc_addr(device,"vkCmdDrawIndirectCountAMD")); if(!dispatch.cmd_draw_indexed_indirect_count)dispatch.cmd_draw_indexed_indirect_count=reinterpret_cast( get_device_proc_addr(device,"vkCmdDrawIndexedIndirectCountKHR")); if(!dispatch.cmd_draw_indexed_indirect_count)dispatch.cmd_draw_indexed_indirect_count=reinterpret_cast( get_device_proc_addr(device,"vkCmdDrawIndexedIndirectCountAMD")); AdapterState& state = State(); std::lock_guard lock(state.mutex); const auto existing = std::find_if( state.device_dispatches.begin(), state.device_dispatches.end(), [device](const AdapterState::DeviceDispatch& candidate) { return candidate.device == device; }); if (existing != state.device_dispatches.end()) { *existing = dispatch; } else { state.device_dispatches.push_back(dispatch); } InvalidateFastDispatchCaches(); } void RegisterHostQueueBinding(VkDevice device, VkQueue queue) { if (device == VK_NULL_HANDLE || queue == VK_NULL_HANDLE) { return; } AdapterState& state = State(); std::lock_guard lock(state.mutex); const auto existing = std::find_if(state.queue_bindings.begin(), state.queue_bindings.end(), [queue](const AdapterState::QueueBinding& candidate) { return candidate.queue == queue; }); if (existing != state.queue_bindings.end()) { existing->device = device; } else { state.queue_bindings.push_back({queue, device}); } InvalidateFastDispatchCaches(); } void RegisterHostCommandBuffers(VkDevice device, VkCommandPool command_pool, std::uint32_t command_buffer_count, const VkCommandBuffer* command_buffers) { if (device == VK_NULL_HANDLE || command_buffers == nullptr) { return; } AdapterState& state = State(); std::lock_guard lock(state.mutex); for (std::uint32_t index = 0; index < command_buffer_count; ++index) { const VkCommandBuffer command_buffer = command_buffers[index]; if (command_buffer == VK_NULL_HANDLE) { continue; } const auto existing = std::find_if( state.command_buffer_bindings.begin(), state.command_buffer_bindings.end(), [command_buffer](const AdapterState::CommandBufferBinding& candidate) { return candidate.command_buffer == command_buffer; }); if (existing != state.command_buffer_bindings.end()) { existing->device = device; existing->command_pool = command_pool; } else { state.command_buffer_bindings.push_back( {command_buffer, device, command_pool}); } } InvalidateFastDispatchCaches(); } void RemoveHostCommandBuffers(VkDevice device, VkCommandPool command_pool, std::uint32_t command_buffer_count, const VkCommandBuffer* command_buffers) { if (command_buffers == nullptr || command_buffer_count == 0) { return; } AdapterState& state = State(); std::lock_guard lock(state.mutex); state.command_buffer_bindings.erase( std::remove_if( state.command_buffer_bindings.begin(), state.command_buffer_bindings.end(), [device, command_pool, command_buffer_count, command_buffers]( const AdapterState::CommandBufferBinding& binding) { return binding.device == device && binding.command_pool == command_pool && std::find(command_buffers, command_buffers + command_buffer_count, binding.command_buffer) != command_buffers + command_buffer_count; }), state.command_buffer_bindings.end()); InvalidateFastDispatchCaches(); } void RemoveHostCommandPoolBindings(VkDevice device, VkCommandPool command_pool) { AdapterState& state = State(); std::lock_guard lock(state.mutex); state.command_buffer_bindings.erase( std::remove_if( state.command_buffer_bindings.begin(), state.command_buffer_bindings.end(), [device, command_pool]( const AdapterState::CommandBufferBinding& binding) { return binding.device == device && binding.command_pool == command_pool; }), state.command_buffer_bindings.end()); InvalidateFastDispatchCaches(); } void RemoveHostDeviceDispatch(VkDevice device) { AdapterState& state = State(); std::lock_guard lock(state.mutex); state.queue_bindings.erase( std::remove_if(state.queue_bindings.begin(), state.queue_bindings.end(), [device](const AdapterState::QueueBinding& binding) { return binding.device == device; }), state.queue_bindings.end()); state.command_buffer_bindings.erase( std::remove_if( state.command_buffer_bindings.begin(), state.command_buffer_bindings.end(), [device](const AdapterState::CommandBufferBinding& binding) { return binding.device == device; }), state.command_buffer_bindings.end()); state.device_dispatches.erase( std::remove_if( state.device_dispatches.begin(), state.device_dispatches.end(), [device](const AdapterState::DeviceDispatch& dispatch) { return dispatch.device == device; }), state.device_dispatches.end()); InvalidateFastDispatchCaches(); } bool IsHostWsiExtension(const char* name) { if (name == nullptr) { return false; } const auto& extensions = State().host_wsi.required_instance_extensions(); return std::find(extensions.begin(), extensions.end(), name) != extensions.end(); } std::vector AndroidVisibleExtensions( const char* layer_name, VkResult* result) { std::vector output; const auto host_enumerate = reinterpret_cast( HostInstanceProc(VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties")); if (host_enumerate == nullptr) { *result = VK_ERROR_INITIALIZATION_FAILED; return output; } std::uint32_t count = 0; *result = host_enumerate(layer_name, &count, nullptr); if (*result != VK_SUCCESS || count == 0) { return output; } output.resize(count); *result = host_enumerate(layer_name, &count, output.data()); if (*result != VK_SUCCESS && *result != VK_INCOMPLETE) { output.clear(); return output; } output.resize(count); if (layer_name != nullptr) { return output; } output.erase( std::remove_if(output.begin(), output.end(), [](const auto& property) { return IsHostWsiExtension(property.extensionName) && std::strcmp(property.extensionName, VK_KHR_SURFACE_EXTENSION_NAME) != 0; }), output.end()); const bool has_android = std::any_of(output.begin(), output.end(), [](const auto& property) { return std::strcmp(property.extensionName, mocktail::graphics::kAndroidSurfaceExtension) == 0; }); if (!has_android) { VkExtensionProperties android{}; std::strncpy(android.extensionName, mocktail::graphics::kAndroidSurfaceExtension, sizeof(android.extensionName) - 1); android.specVersion = 6; output.push_back(android); } *result = VK_SUCCESS; return output; } PFN_vkVoidFunction AdapterProc(const char* name) { if (name == nullptr) { return nullptr; } #define MOCKTAIL_VK_PROC(function) \ if (std::strcmp(name, #function) == 0) { \ return reinterpret_cast(function); \ } MOCKTAIL_VK_PROC(vkCreateInstance) MOCKTAIL_VK_PROC(vkDestroyInstance) MOCKTAIL_VK_PROC(vkCreateAndroidSurfaceKHR) MOCKTAIL_VK_PROC(vkDestroySurfaceKHR) MOCKTAIL_VK_PROC(vkEnumerateInstanceExtensionProperties) MOCKTAIL_VK_PROC(vkEnumerateInstanceLayerProperties) MOCKTAIL_VK_PROC(vkGetInstanceProcAddr) MOCKTAIL_VK_PROC(vkGetDeviceProcAddr) MOCKTAIL_VK_PROC(vkCreateDevice) MOCKTAIL_VK_PROC(vkDestroyDevice) MOCKTAIL_VK_PROC(vkGetDeviceQueue) MOCKTAIL_VK_PROC(vkGetDeviceQueue2) MOCKTAIL_VK_PROC(vkCreateSwapchainKHR) MOCKTAIL_VK_PROC(vkDestroySwapchainKHR) MOCKTAIL_VK_PROC(vkAcquireNextImageKHR) MOCKTAIL_VK_PROC(vkAcquireNextImage2KHR) MOCKTAIL_VK_PROC(vkWaitForFences) MOCKTAIL_VK_PROC(vkResetFences) MOCKTAIL_VK_PROC(vkCreateCommandPool) MOCKTAIL_VK_PROC(vkCmdPipelineBarrier) MOCKTAIL_VK_PROC(vkCmdPipelineBarrier2) MOCKTAIL_VK_PROC(vkCmdPipelineBarrier2KHR) MOCKTAIL_VK_PROC(vkResetCommandPool) MOCKTAIL_VK_PROC(vkGetQueryPoolResults) MOCKTAIL_VK_PROC(vkAllocateCommandBuffers) MOCKTAIL_VK_PROC(vkFreeCommandBuffers) MOCKTAIL_VK_PROC(vkDestroyCommandPool) MOCKTAIL_VK_PROC(vkBeginCommandBuffer) MOCKTAIL_VK_PROC(vkEndCommandBuffer) MOCKTAIL_VK_PROC(vkResetCommandBuffer) MOCKTAIL_VK_PROC(vkWaitSemaphores) MOCKTAIL_VK_PROC(vkWaitSemaphoresKHR) MOCKTAIL_VK_PROC(vkQueueSubmit) MOCKTAIL_VK_PROC(vkQueueSubmit2) MOCKTAIL_VK_PROC(vkQueueSubmit2KHR) MOCKTAIL_VK_PROC(vkQueueBindSparse) MOCKTAIL_VK_PROC(vkQueueWaitIdle) MOCKTAIL_VK_PROC(vkDeviceWaitIdle) MOCKTAIL_VK_PROC(vkQueuePresentKHR) MOCKTAIL_VK_PROC(vkGetPhysicalDeviceFeatures) MOCKTAIL_VK_PROC(vkGetPhysicalDeviceFeatures2) MOCKTAIL_VK_PROC(vkGetPhysicalDeviceFormatProperties) MOCKTAIL_VK_PROC(vkGetPhysicalDeviceFormatProperties2) MOCKTAIL_VK_PROC(vkGetPhysicalDeviceImageFormatProperties) MOCKTAIL_VK_PROC(vkGetPhysicalDeviceImageFormatProperties2) MOCKTAIL_VK_PROC(vkCreateImage) MOCKTAIL_VK_PROC(vkDestroyImage) MOCKTAIL_VK_PROC(vkCreateImageView) MOCKTAIL_VK_PROC(vkDestroyImageView) MOCKTAIL_VK_PROC(vkCreateFramebuffer) MOCKTAIL_VK_PROC(vkDestroyFramebuffer) MOCKTAIL_VK_PROC(vkCreateRenderPass) MOCKTAIL_VK_PROC(vkDestroyRenderPass) MOCKTAIL_VK_PROC(vkCmdBeginRenderPass) if (FovTraceEnabled() || Fsr3InputsAvailable()) { MOCKTAIL_VK_PROC(vkCmdEndRenderPass) } if (Fsr3InputsAvailable()) { MOCKTAIL_VK_PROC(vkCmdBindPipeline) MOCKTAIL_VK_PROC(vkCmdDraw) MOCKTAIL_VK_PROC(vkCmdDrawIndexed) MOCKTAIL_VK_PROC(vkCmdDrawIndirect) MOCKTAIL_VK_PROC(vkCmdDrawIndexedIndirect) MOCKTAIL_VK_PROC(vkCmdDrawIndirectCount) MOCKTAIL_VK_PROC(vkCmdDrawIndexedIndirectCount) MOCKTAIL_VK_PROC(vkDestroyPipeline) MOCKTAIL_VK_PROC(vkDestroyShaderModule) } MOCKTAIL_VK_PROC(vkBindBufferMemory) MOCKTAIL_VK_PROC(vkBindBufferMemory2) MOCKTAIL_VK_PROC(vkMapMemory) MOCKTAIL_VK_PROC(vkMapMemory2) MOCKTAIL_VK_PROC(vkUnmapMemory) MOCKTAIL_VK_PROC(vkUnmapMemory2) MOCKTAIL_VK_PROC(vkFreeMemory) MOCKTAIL_VK_PROC(vkDestroyBuffer) MOCKTAIL_VK_PROC(vkCmdCopyBufferToImage) MOCKTAIL_VK_PROC(vkCmdCopyImage) MOCKTAIL_VK_PROC(vkCmdCopyImage2) MOCKTAIL_VK_PROC(vkCmdCopyBufferToImage2) MOCKTAIL_VK_PROC(vkCmdExecuteCommands) #undef MOCKTAIL_VK_PROC #define MOCKTAIL_VK_ALIAS(alias, function) \ if (std::strcmp(name, #alias) == 0) { \ return reinterpret_cast(function); \ } MOCKTAIL_VK_ALIAS(vkGetPhysicalDeviceFeatures2KHR, vkGetPhysicalDeviceFeatures2) MOCKTAIL_VK_ALIAS(vkGetPhysicalDeviceFormatProperties2KHR, vkGetPhysicalDeviceFormatProperties2) MOCKTAIL_VK_ALIAS(vkGetPhysicalDeviceImageFormatProperties2KHR, vkGetPhysicalDeviceImageFormatProperties2) MOCKTAIL_VK_ALIAS(vkBindBufferMemory2KHR, vkBindBufferMemory2) MOCKTAIL_VK_ALIAS(vkCmdCopyImage2KHR, vkCmdCopyImage2) if(Fsr3InputsAvailable()) { MOCKTAIL_VK_ALIAS(vkCmdDrawIndirectCountKHR,vkCmdDrawIndirectCount) MOCKTAIL_VK_ALIAS(vkCmdDrawIndexedIndirectCountKHR,vkCmdDrawIndexedIndirectCount) MOCKTAIL_VK_ALIAS(vkCmdDrawIndirectCountAMD,vkCmdDrawIndirectCount) MOCKTAIL_VK_ALIAS(vkCmdDrawIndexedIndirectCountAMD,vkCmdDrawIndexedIndirectCount) } MOCKTAIL_VK_ALIAS(vkMapMemory2KHR, vkMapMemory2) MOCKTAIL_VK_ALIAS(vkUnmapMemory2KHR, vkUnmapMemory2) MOCKTAIL_VK_ALIAS(vkCmdCopyBufferToImage2KHR, vkCmdCopyBufferToImage2) #undef MOCKTAIL_VK_ALIAS return nullptr; } bool NameInList(const char* name, const char* const* names, std::size_t count) { return name != nullptr && std::any_of(names, names + count, [name](const char* candidate) { return std::strcmp(name, candidate) == 0; }); } // Device commands wrapped for ETC2/EAC emulation. bool IsEtc2DeviceProc(const char* name) { static constexpr const char* kNames[] = { "vkCreateImage", "vkDestroyImage", "vkCreateImageView", "vkBindBufferMemory", "vkBindBufferMemory2", "vkBindBufferMemory2KHR", "vkMapMemory", "vkMapMemory2", "vkMapMemory2KHR", "vkUnmapMemory", "vkUnmapMemory2", "vkUnmapMemory2KHR", "vkFreeMemory", "vkDestroyBuffer", "vkCmdCopyBufferToImage", "vkCmdCopyBufferToImage2", "vkCmdCopyImage", "vkCmdCopyImage2", "vkCmdCopyImage2KHR", "vkCmdCopyBufferToImage2KHR", "vkCmdExecuteCommands", }; return NameInList(name, kNames, sizeof(kNames) / sizeof(kNames[0])); } // Physical-device queries that report emulated ETC2/EAC support. bool IsEtc2PhysicalDeviceProc(const char* name) { static constexpr const char* kNames[] = { "vkGetPhysicalDeviceFeatures", "vkGetPhysicalDeviceFeatures2", "vkGetPhysicalDeviceFeatures2KHR", "vkGetPhysicalDeviceFormatProperties", "vkGetPhysicalDeviceFormatProperties2", "vkGetPhysicalDeviceFormatProperties2KHR", "vkGetPhysicalDeviceImageFormatProperties", "vkGetPhysicalDeviceImageFormatProperties2", "vkGetPhysicalDeviceImageFormatProperties2KHR", }; return NameInList(name, kNames, sizeof(kNames) / sizeof(kNames[0])); } bool IsDeviceAdapterProc(const char* name) { return name != nullptr && (IsEtc2DeviceProc(name) || std::strcmp(name, "vkDestroyDevice") == 0 || std::strcmp(name, "vkGetDeviceQueue") == 0 || std::strcmp(name, "vkGetDeviceQueue2") == 0 || std::strcmp(name, "vkCreateSwapchainKHR") == 0 || std::strcmp(name, "vkDestroySwapchainKHR") == 0 || std::strcmp(name, "vkAcquireNextImageKHR") == 0 || std::strcmp(name, "vkAcquireNextImage2KHR") == 0 || std::strcmp(name, "vkWaitForFences") == 0 || std::strcmp(name, "vkResetFences") == 0 || std::strcmp(name, "vkCreateCommandPool") == 0 || std::strcmp(name, "vkCmdPipelineBarrier") == 0 || std::strcmp(name, "vkCmdPipelineBarrier2") == 0 || std::strcmp(name, "vkCmdPipelineBarrier2KHR") == 0 || std::strcmp(name, "vkResetCommandPool") == 0 || std::strcmp(name, "vkGetQueryPoolResults") == 0 || std::strcmp(name, "vkAllocateCommandBuffers") == 0 || std::strcmp(name, "vkFreeCommandBuffers") == 0 || std::strcmp(name, "vkDestroyCommandPool") == 0 || std::strcmp(name, "vkBeginCommandBuffer") == 0 || std::strcmp(name, "vkEndCommandBuffer") == 0 || std::strcmp(name, "vkResetCommandBuffer") == 0 || std::strcmp(name, "vkWaitSemaphores") == 0 || std::strcmp(name, "vkWaitSemaphoresKHR") == 0 || std::strcmp(name, "vkQueueSubmit") == 0 || std::strcmp(name, "vkQueueSubmit2") == 0 || std::strcmp(name, "vkQueueSubmit2KHR") == 0 || std::strcmp(name, "vkQueueBindSparse") == 0 || std::strcmp(name, "vkQueueWaitIdle") == 0 || std::strcmp(name, "vkDeviceWaitIdle") == 0 || std::strcmp(name, "vkQueuePresentKHR") == 0 || std::strcmp(name, "vkDestroyImageView") == 0 || std::strcmp(name, "vkCreateFramebuffer") == 0 || std::strcmp(name, "vkDestroyFramebuffer") == 0 || std::strcmp(name, "vkCreateRenderPass") == 0 || std::strcmp(name, "vkDestroyRenderPass") == 0 || std::strcmp(name, "vkCmdBeginRenderPass") == 0 || (Fsr3InputsAvailable() && std::strcmp(name,"vkCmdBindPipeline")==0) || (Fsr3InputsAvailable() && (std::strcmp(name,"vkCmdDraw")==0 || std::strcmp(name,"vkCmdDrawIndexed")==0 || std::strcmp(name,"vkCmdDrawIndirect")==0 || std::strcmp(name,"vkCmdDrawIndexedIndirect")==0 || std::strcmp(name,"vkCmdDrawIndirectCount")==0 || std::strcmp(name,"vkCmdDrawIndexedIndirectCount")==0 || std::strcmp(name,"vkCmdDrawIndirectCountKHR")==0 || std::strcmp(name,"vkCmdDrawIndexedIndirectCountKHR")==0 || std::strcmp(name,"vkCmdDrawIndirectCountAMD")==0 || std::strcmp(name,"vkCmdDrawIndexedIndirectCountAMD")==0)) || (Fsr3InputsAvailable() && (std::strcmp(name,"vkDestroyPipeline")==0 || std::strcmp(name,"vkDestroyShaderModule")==0)) || ((FovTraceEnabled() || Fsr3InputsAvailable()) && std::strcmp(name, "vkCmdEndRenderPass") == 0)); } bool IsGlobalAdapterProc(const char* name) { return name != nullptr && (std::strcmp(name, "vkCreateInstance") == 0 || std::strcmp(name, "vkEnumerateInstanceExtensionProperties") == 0 || std::strcmp(name, "vkEnumerateInstanceLayerProperties") == 0 || std::strcmp(name, "vkGetInstanceProcAddr") == 0); } const VkBaseInStructure* FindFeature(const void* chain, VkStructureType type) { const auto* current = static_cast(chain); while (current != nullptr) { if (current->sType == type) { return current; } current = current->pNext; } return nullptr; } struct EnabledDeviceFeatures { VkPhysicalDeviceFeatures2 root{}; VkPhysicalDeviceVulkan11Features vulkan11{}; VkPhysicalDeviceVulkan12Features vulkan12{}; bool placebo_required = false; EnabledDeviceFeatures() { root.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; vulkan11.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; vulkan12.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; root.pNext = &vulkan11; vulkan11.pNext = &vulkan12; } }; EnabledDeviceFeatures InspectEnabledFeatures( const VkDeviceCreateInfo& create_info) { EnabledDeviceFeatures enabled; if (create_info.pEnabledFeatures != nullptr) { enabled.root.features = *create_info.pEnabledFeatures; } if (const auto* features2 = reinterpret_cast(FindFeature( create_info.pNext, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2)); features2 != nullptr) { enabled.root.features = features2->features; } bool timeline = false; bool host_query_reset = false; if (const auto* vulkan12 = reinterpret_cast(FindFeature( create_info.pNext, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES)); vulkan12 != nullptr) { timeline = vulkan12->timelineSemaphore == VK_TRUE; host_query_reset = vulkan12->hostQueryReset == VK_TRUE; } if (const auto* timeline_features = reinterpret_cast< const VkPhysicalDeviceTimelineSemaphoreFeatures*>(FindFeature( create_info.pNext, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES)); timeline_features != nullptr) { timeline = timeline_features->timelineSemaphore == VK_TRUE; } if (const auto* host_query_features = reinterpret_cast( FindFeature( create_info.pNext, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_QUERY_RESET_FEATURES)); host_query_features != nullptr) { host_query_reset = host_query_features->hostQueryReset == VK_TRUE; } enabled.vulkan12.timelineSemaphore = timeline; enabled.vulkan12.hostQueryReset = host_query_reset; enabled.placebo_required = timeline && host_query_reset; return enabled; } bool HasRequiredFeatureStructs(const VkDeviceCreateInfo& create_info) { return FindFeature(create_info.pNext, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES) != nullptr || FindFeature( create_info.pNext, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES) != nullptr || FindFeature( create_info.pNext, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_QUERY_RESET_FEATURES) != nullptr; } bool ShouldLogNegativeVulkanResult(VkResult result, std::uint64_t* occurrence) { static std::atomic device_lost_count{0}; static std::atomic other_error_count{0}; std::atomic& counter = result == VK_ERROR_DEVICE_LOST ? device_lost_count : other_error_count; const std::uint64_t count = counter.fetch_add(1, std::memory_order_relaxed) + 1; if (occurrence != nullptr) { *occurrence = count; } return count <= 4 || (count & (count - 1)) == 0; } class VulkanCallObservation final { public: explicit VulkanCallObservation(const char* call_name) : call_name_(call_name) { if (__builtin_expect( g_vulkan_call_observation_active.load(std::memory_order_relaxed), 0)) { AdapterState& state = State(); begin_ = state.note_vulkan_call_begin.load(std::memory_order_acquire); end_ = state.note_vulkan_call_end.load(std::memory_order_acquire); if (begin_ != nullptr && end_ != nullptr) { sequence_ = begin_(call_name); } } } ~VulkanCallObservation() { if (__builtin_expect(sequence_ != 0 && end_ != nullptr, 0)) { end_(sequence_, static_cast(result_)); } } VulkanCallObservation(const VulkanCallObservation&) = delete; VulkanCallObservation& operator=(const VulkanCallObservation&) = delete; void SetResult(VkResult result) { result_ = result; if (__builtin_expect( result < VK_SUCCESS && result != VK_ERROR_OUT_OF_DATE_KHR, 0)) { std::uint64_t occurrence = 0; if (ShouldLogNegativeVulkanResult(result, &occurrence)) { std::fprintf(stderr, " [vulkan] unexpected negative result: call=%s " "result=%d device_lost=%u occurrence=%llu\n", call_name_ != nullptr ? call_name_ : "unknown", static_cast(result), result == VK_ERROR_DEVICE_LOST ? 1U : 0U, static_cast(occurrence)); } } } private: NoteVulkanCallBeginFn begin_ = nullptr; NoteVulkanCallEndFn end_ = nullptr; const char* call_name_ = nullptr; std::uint64_t sequence_ = 0; VkResult result_ = VK_ERROR_UNKNOWN; }; VkResult WaitForSemaphoresObserved(const char* call_name, PFN_vkWaitSemaphores host_wait, VkDevice device, const VkSemaphoreWaitInfo* wait_info, std::uint64_t timeout, VulkanCallObservation* observation) { if (host_wait == nullptr) { if (observation != nullptr) { observation->SetResult(VK_ERROR_INITIALIZATION_FAILED); } return VK_ERROR_INITIALIZATION_FAILED; } mocktail::graphics::TraceScope scope(mocktail::graphics::ActiveProfileTrace(), call_name, "wait"); const VkResult result = host_wait(device, wait_info, timeout); if (observation != nullptr) { observation->SetResult(result); } return result; } bool FpsTraceEnabled() { static const bool enabled = [] { const char* value = std::getenv("MOCKTAIL_TRACE_FPS"); return value != nullptr && value[0] != '\0' && std::strcmp(value, "0") != 0; }(); return enabled; } std::uint64_t MonotonicNanos() { timespec ts{}; if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) { return 0; } return static_cast(ts.tv_sec) * 1000000000ULL + static_cast(ts.tv_nsec); } struct FpsWaitTrace { const char* name = nullptr; std::atomic samples{0}; std::atomic total_ns{0}; std::atomic max_ns{0}; std::atomic window_start_ns{0}; // Longest time between consecutive call starts; for present, a frame time. std::atomic max_gap_ns{0}; std::atomic last_start_ns{0}; void Record(std::uint64_t start_ns) { if (name == nullptr || start_ns == 0) { return; } const std::uint64_t previous_start = last_start_ns.exchange(start_ns, std::memory_order_relaxed); if (previous_start != 0 && start_ns > previous_start) { const std::uint64_t gap_ns = start_ns - previous_start; std::uint64_t max_gap = max_gap_ns.load(std::memory_order_relaxed); while (gap_ns > max_gap && !max_gap_ns.compare_exchange_weak(max_gap, gap_ns, std::memory_order_relaxed)) { } } const std::uint64_t wait_ns = MonotonicNanos() - start_ns; total_ns.fetch_add(wait_ns, std::memory_order_relaxed); std::uint64_t max_wait = max_ns.load(std::memory_order_relaxed); while (wait_ns > max_wait && !max_ns.compare_exchange_weak(max_wait, wait_ns, std::memory_order_relaxed)) { } const std::uint64_t n = samples.fetch_add(1, std::memory_order_relaxed) + 1; std::uint64_t window = window_start_ns.load(std::memory_order_relaxed); if (window == 0) { window_start_ns.compare_exchange_strong(window, start_ns, std::memory_order_relaxed); window = window_start_ns.load(std::memory_order_relaxed); } if (start_ns - window < 1000000000ULL || n == 0) { return; } const std::uint64_t total = total_ns.exchange(0, std::memory_order_relaxed); const std::uint64_t peak = max_ns.exchange(0, std::memory_order_relaxed); const std::uint64_t count = samples.exchange(0, std::memory_order_relaxed); const std::uint64_t gap_peak = max_gap_ns.exchange(0, std::memory_order_relaxed); window_start_ns.store(start_ns, std::memory_order_relaxed); if (count == 0) { return; } std::fprintf(stderr, " [fps] %s n=%llu avg=%llu us max=%llu us gap_max=%llu us\n", name, static_cast(count), static_cast(total / count / 1000ULL), static_cast(peak / 1000ULL), static_cast(gap_peak / 1000ULL)); } }; FpsWaitTrace& PresentWaitTrace() { static FpsWaitTrace trace{"vkQueuePresentKHR"}; return trace; } FpsWaitTrace& AcquireWaitTrace() { static FpsWaitTrace trace{"vkAcquireNextImageKHR"}; return trace; } FpsWaitTrace& FenceWaitTrace() { static FpsWaitTrace trace{"vkWaitForFences"}; return trace; } FpsWaitTrace& QueueIdleWaitTrace() { static FpsWaitTrace trace{"vkQueueWaitIdle"}; return trace; } VkResult VKAPI_CALL ObservedHostQueuePresent(VkQueue queue, const VkPresentInfoKHR* present_info) { AdapterState& state = State(); const PFN_vkQueuePresentKHR host_present = HostDispatchForQueue(queue).queue_present; if (host_present == nullptr) { return VK_ERROR_INITIALIZATION_FAILED; } const NoteHostPresentBeginFn note_begin = state.note_host_present_begin.load(std::memory_order_acquire); if (note_begin != nullptr) { note_begin(); } const NoteVrPresentBeginFn vr_note_begin = state.note_vr_present_begin.load(std::memory_order_acquire); if (vr_note_begin != nullptr) { vr_note_begin(); } const VrXrNotePresentFn vr_note_present = state.vr_note_present.load(std::memory_order_acquire); VkPresentInfoKHR synchronized{}; VkSemaphore ready = VK_NULL_HANDLE; if (vr_note_present != nullptr) { static const auto prepare = ResolveProcessFunction( "mocktail_vr_xr_prepare_present"); // Mirror/overlay may have replaced waits; use the final dependency list. const VkResult prepared = prepare ? prepare(queue, HostDispatchForQueue(queue).device, present_info, &ready) : VK_NOT_READY; if (prepared < 0) return prepared; if (prepared == VK_SUCCESS) { vr_note_present(queue, HostDispatchForQueue(queue).device); // The waits went to the GPU; present after the semaphore they signal. synchronized = *present_info; synchronized.waitSemaphoreCount = ready != VK_NULL_HANDLE ? 1 : 0; synchronized.pWaitSemaphores = ready != VK_NULL_HANDLE ? &ready : nullptr; present_info = &synchronized; } } const bool fps_trace = FpsTraceEnabled(); const std::uint64_t present_start_ns = fps_trace ? MonotonicNanos() : 0; const VkResult result = [&] { const QueueCallGuard guard(HostDispatchForQueue(queue).device, queue); mocktail::graphics::TraceScope scope( mocktail::graphics::ActiveProfileTrace(), "host present", "present"); return host_present(queue, present_info); }(); if (fps_trace) { PresentWaitTrace().Record(present_start_ns); } const NoteHostPresentEndFn note_end = state.note_host_present_end.load(std::memory_order_acquire); if (note_end != nullptr) { note_end(static_cast(result)); } return result; } // Samples the time between successive adapter present calls, and the CPU // time the presenting thread and the whole process used in it. void RecordProfiledFrame(mocktail::graphics::ChromeTraceWriter* trace, std::uint64_t present_start_ns) { static std::atomic previous_start_ns{0}; static mocktail::graphics::FrameCpuSampler cpu_sampler; const std::uint64_t previous = previous_start_ns.exchange(present_start_ns, std::memory_order_relaxed); if (previous != 0 && present_start_ns > previous) { trace->Counter("frame interval (ms)", present_start_ns, static_cast(present_start_ns - previous) / 1e6); } mocktail::graphics::FrameCpuSample cpu; if (cpu_sampler.Sample(static_cast(pthread_self()), mocktail::graphics::TraceThreadCpuNanos(), mocktail::graphics::TraceProcessCpuNanos(), &cpu)) { trace->Counter("process cpu (ms)", present_start_ns, cpu.process_ms); if (cpu.has_thread) { trace->Counter("present thread cpu (ms)", present_start_ns, cpu.thread_ms); } } } // Pipeline and shader creation is wrapped only while profiling, so a normal // session calls the host driver directly. VkResult VKAPI_CALL ProfiledCreateGraphicsPipelines( VkDevice device, VkPipelineCache cache, std::uint32_t count, const VkGraphicsPipelineCreateInfo* infos, const VkAllocationCallbacks* allocator, VkPipeline* pipelines) { const auto host = reinterpret_cast( HostDeviceProc(device, "vkCreateGraphicsPipelines")); if (host == nullptr) { return VK_ERROR_INITIALIZATION_FAILED; } mocktail::graphics::TraceScope scope(mocktail::graphics::ActiveProfileTrace(), "vkCreateGraphicsPipelines", "pipeline"); const VkResult result = host(device, cache, count, infos, allocator, pipelines); if(result==VK_SUCCESS)Fsr3DumpGraphicsPipelines(device,count,infos,pipelines); scope.Arg("count", count); scope.Arg("cache", cache != VK_NULL_HANDLE); scope.Arg("result", result); return result; } VkResult VKAPI_CALL ProfiledCreateComputePipelines( VkDevice device, VkPipelineCache cache, std::uint32_t count, const VkComputePipelineCreateInfo* infos, const VkAllocationCallbacks* allocator, VkPipeline* pipelines) { const auto host = reinterpret_cast( HostDeviceProc(device, "vkCreateComputePipelines")); if (host == nullptr) { return VK_ERROR_INITIALIZATION_FAILED; } mocktail::graphics::TraceScope scope(mocktail::graphics::ActiveProfileTrace(), "vkCreateComputePipelines", "pipeline"); const VkResult result = host(device, cache, count, infos, allocator, pipelines); scope.Arg("count", count); scope.Arg("cache", cache != VK_NULL_HANDLE); scope.Arg("result", result); return result; } VkResult VKAPI_CALL ProfiledCreateShaderModule( VkDevice device, const VkShaderModuleCreateInfo* create_info, const VkAllocationCallbacks* allocator, VkShaderModule* shader_module) { const auto host = reinterpret_cast( HostDeviceProc(device, "vkCreateShaderModule")); if (host == nullptr) { return VK_ERROR_INITIALIZATION_FAILED; } mocktail::graphics::TraceScope scope(mocktail::graphics::ActiveProfileTrace(), "vkCreateShaderModule", "pipeline"); const VkResult result = host(device, create_info, allocator, shader_module); if(result==VK_SUCCESS && shader_module)Fsr3DumpShader(device,*shader_module,create_info); scope.Arg("bytes", create_info != nullptr ? static_cast(create_info->codeSize) : 0); scope.Arg("result", result); return result; } VkResult VKAPI_CALL ProfiledCreatePipelineCache( VkDevice device, const VkPipelineCacheCreateInfo* create_info, const VkAllocationCallbacks* allocator, VkPipelineCache* cache) { const auto host = reinterpret_cast( HostDeviceProc(device, "vkCreatePipelineCache")); if (host == nullptr) { return VK_ERROR_INITIALIZATION_FAILED; } mocktail::graphics::TraceScope scope(mocktail::graphics::ActiveProfileTrace(), "vkCreatePipelineCache", "pipeline"); const VkResult result = host(device, create_info, allocator, cache); scope.Arg("initial_bytes", create_info != nullptr ? static_cast(create_info->initialDataSize) : 0); scope.Arg("result", result); return result; } VkResult VKAPI_CALL ProfiledAllocateMemory( VkDevice device, const VkMemoryAllocateInfo* allocate_info, const VkAllocationCallbacks* allocator, VkDeviceMemory* memory) { const auto host = reinterpret_cast( HostDeviceProc(device, "vkAllocateMemory")); if (host == nullptr) { return VK_ERROR_INITIALIZATION_FAILED; } mocktail::graphics::TraceScope scope(mocktail::graphics::ActiveProfileTrace(), "vkAllocateMemory", "memory"); const VkResult result = host(device, allocate_info, allocator, memory); scope.Arg("bytes", allocate_info != nullptr ? static_cast( allocate_info->allocationSize) : 0); scope.Arg("type", allocate_info != nullptr ? static_cast( allocate_info->memoryTypeIndex) : -1); scope.Arg("result", result); return result; } // Null unless profiling is on and `name` is a profiled device command. // These take precedence over the ETC2 wrappers, so a command both wrap is // timed inside its adapter function instead. PFN_vkVoidFunction ProfileAdapterProc(const char* name) { if (name == nullptr) return nullptr; if (std::strcmp(name, "vkCreateShaderModule") == 0 && (Fsr3ShaderDumpEnabled() || Fsr3InputsAvailable())) return reinterpret_cast(ProfiledCreateShaderModule); if (std::strcmp(name, "vkCreateGraphicsPipelines") == 0 && (Fsr3ShaderDumpEnabled() || Fsr3InputsAvailable())) return reinterpret_cast(ProfiledCreateGraphicsPipelines); if (mocktail::graphics::ActiveProfileTrace() == nullptr) return nullptr; if (std::strcmp(name, "vkCreateGraphicsPipelines") == 0) { return reinterpret_cast( ProfiledCreateGraphicsPipelines); } if (std::strcmp(name, "vkCreateComputePipelines") == 0) { return reinterpret_cast( ProfiledCreateComputePipelines); } if (std::strcmp(name, "vkCreateShaderModule") == 0) { return reinterpret_cast(ProfiledCreateShaderModule); } if (std::strcmp(name, "vkCreatePipelineCache") == 0) { return reinterpret_cast(ProfiledCreatePipelineCache); } if (std::strcmp(name, "vkAllocateMemory") == 0) { return reinterpret_cast(ProfiledAllocateMemory); } return nullptr; } template Function HostPhysicalDeviceProc(const char* name, const char* alias) { VkInstance instance = VK_NULL_HANDLE; { AdapterState& state = State(); std::lock_guard lock(state.mutex); instance = state.latest_instance; } PFN_vkVoidFunction proc = HostInstanceProc(instance, name); if (proc == nullptr && alias != nullptr) { proc = HostInstanceProc(instance, alias); } return reinterpret_cast(proc); } bool Etc2EmulatedPhysicalDevice(VkPhysicalDevice physical_device) { return State().etc2.PhysicalDeviceNeedsEmulation( physical_device, HostPhysicalDeviceProc( "vkGetPhysicalDeviceFeatures", nullptr)); } void ReleaseEtc2PoolCommandBuffers(VkDevice device, VkCommandPool command_pool) { std::vector command_buffers; { AdapterState& state = State(); std::lock_guard lock(state.mutex); for (const auto& binding : state.command_buffer_bindings) { if (binding.device == device && binding.command_pool == command_pool) { command_buffers.push_back(binding.command_buffer); } } } for (VkCommandBuffer command_buffer : command_buffers) { State().etc2.ReleaseCommandBuffer(command_buffer); Fsr3ResetCommand(command_buffer); } } // The stage the decoded staging bytes are read at. Every emulated upload is // a buffer-to-image copy, so the transfer stage is the earliest point the GPU // may touch them. constexpr VkPipelineStageFlags kEtc2WaitStage = VK_PIPELINE_STAGE_TRANSFER_BIT; // Holds the arrays a rewritten submit points at, so they outlive the call. // Deques keep earlier elements addressable as later ones are appended. struct Etc2SubmitRewrite { std::deque> wait_semaphores; std::deque> wait_stages; std::deque> wait_values; std::deque> signal_values; std::deque timelines; std::deque> wait_infos; std::vector rewritten; std::vector rewritten2; }; // Adds each submit's decode wait to its wait list. A submit whose pNext chain // already sizes arrays by its own semaphore counts is decoded on this thread // instead: a VkTimelineSemaphoreSubmitInfo would have to be rebuilt, and a // VkDeviceGroupSubmitInfo's pWaitSemaphoreDeviceIndices would be left one // entry short of the grown wait list. const VkSubmitInfo* PrepareEtc2Submit(std::uint32_t submit_count, const VkSubmitInfo* submits, Etc2SubmitRewrite* rewrite) { if (submits == nullptr || submit_count == 0) { return submits; } bool rewritten_any = false; for (std::uint32_t index = 0; index < submit_count; ++index) { const VkSubmitInfo& original = submits[index]; const bool fixed_wait_list = FindFeature(original.pNext, VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO) != nullptr || FindFeature(original.pNext, VK_STRUCTURE_TYPE_DEVICE_GROUP_SUBMIT_INFO) != nullptr; const mocktail::graphics::Etc2SubmitWait wait = State().etc2.PrepareSubmit( original.pCommandBuffers, original.commandBufferCount, !fixed_wait_list); if (wait.semaphore == VK_NULL_HANDLE) { continue; } if (!rewritten_any) { rewrite->rewritten.assign(submits, submits + submit_count); rewritten_any = true; } std::vector& semaphores = rewrite->wait_semaphores.emplace_back( original.pWaitSemaphores, original.pWaitSemaphores + original.waitSemaphoreCount); semaphores.push_back(wait.semaphore); std::vector& stages = rewrite->wait_stages.emplace_back( original.pWaitDstStageMask, original.pWaitDstStageMask + original.waitSemaphoreCount); stages.push_back(kEtc2WaitStage); // Binary semaphores ignore their value; only the appended entry is read. std::vector& values = rewrite->wait_values.emplace_back( original.waitSemaphoreCount, 0); values.push_back(wait.value); VkTimelineSemaphoreSubmitInfo& timeline = rewrite->timelines.emplace_back(); timeline.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO; timeline.pNext = original.pNext; timeline.waitSemaphoreValueCount = static_cast(values.size()); timeline.pWaitSemaphoreValues = values.data(); if (original.signalSemaphoreCount != 0) { std::vector& signals = rewrite->signal_values.emplace_back(original.signalSemaphoreCount, 0); timeline.signalSemaphoreValueCount = static_cast(signals.size()); timeline.pSignalSemaphoreValues = signals.data(); } VkSubmitInfo& target = rewrite->rewritten[index]; target.pNext = &timeline; target.waitSemaphoreCount = static_cast(semaphores.size()); target.pWaitSemaphores = semaphores.data(); target.pWaitDstStageMask = stages.data(); } return rewritten_any ? rewrite->rewritten.data() : submits; } const VkSubmitInfo2* PrepareEtc2Submit2(std::uint32_t submit_count, const VkSubmitInfo2* submits, Etc2SubmitRewrite* rewrite) { if (submits == nullptr || submit_count == 0) { return submits; } bool rewritten_any = false; for (std::uint32_t index = 0; index < submit_count; ++index) { const VkSubmitInfo2& original = submits[index]; std::vector command_buffers; command_buffers.reserve(original.commandBufferInfoCount); for (std::uint32_t info = 0; info < original.commandBufferInfoCount; ++info) { command_buffers.push_back( original.pCommandBufferInfos[info].commandBuffer); } const mocktail::graphics::Etc2SubmitWait wait = State().etc2.PrepareSubmit( command_buffers.data(), static_cast(command_buffers.size()), true); if (wait.semaphore == VK_NULL_HANDLE) { continue; } if (!rewritten_any) { rewrite->rewritten2.assign(submits, submits + submit_count); rewritten_any = true; } std::vector& waits = rewrite->wait_infos.emplace_back( original.pWaitSemaphoreInfos, original.pWaitSemaphoreInfos + original.waitSemaphoreInfoCount); VkSemaphoreSubmitInfo decoded{}; decoded.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; decoded.semaphore = wait.semaphore; decoded.value = wait.value; // Matches kEtc2WaitStage: the staging bytes are read by transfer // commands, which includes the blits an upscaled upload records. decoded.stageMask = VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT; waits.push_back(decoded); VkSubmitInfo2& target = rewrite->rewritten2[index]; target.waitSemaphoreInfoCount = static_cast(waits.size()); target.pWaitSemaphoreInfos = waits.data(); } return rewritten_any ? rewrite->rewritten2.data() : submits; } #include "vulkan_foveation_adapter.inc" #include "vulkan_temporal_inputs_adapter.inc" using DesktopQueueNormalizer = VkImageLayout (*)(VkCommandBuffer, VkImage, unsigned*, unsigned*, VkImageLayout*, VkImageLayout); DesktopQueueNormalizer DesktopBarrierNormalizer() { static const auto normalize = ResolveProcessFunction( "mocktail_vr_xr_desktop_barrier"); return normalize; } template static void NormalizeDesktopQueueBarriers(VkCommandBuffer command, std::uint32_t count, Barrier* barriers) { const auto normalize = DesktopBarrierNormalizer(); if (!normalize) return; for (std::uint32_t i = 0; i < count; ++i) { auto& image = barriers[i]; if (mocktail::graphics::internal::NeedsDesktopQueueNormalization( image.srcQueueFamilyIndex, image.dstQueueFamilyIndex)) image.newLayout = normalize(command, image.image, &image.srcQueueFamilyIndex, &image.dstQueueFamilyIndex, &image.oldLayout, image.newLayout); } } } // namespace extern "C" { VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(const VkInstanceCreateInfo* create_info, const VkAllocationCallbacks* allocator, VkInstance* instance) { if (!EnsureInitialized() || create_info == nullptr || instance == nullptr) { return VK_ERROR_INITIALIZATION_FAILED; } std::vector requested; requested.reserve(create_info->enabledExtensionCount); for (std::uint32_t index = 0; index < create_info->enabledExtensionCount; ++index) { const char* extension = create_info->ppEnabledExtensionNames[index]; if (extension == nullptr) { return VK_ERROR_EXTENSION_NOT_PRESENT; } requested.emplace_back(extension); } std::vector translated; const mocktail::Status status = State().android_wsi.TranslateInstanceExtensions(requested, &translated); if (!status.ok()) { std::fprintf(stderr, " [vulkan] instance extension rewrite failed: %s\n", status.message().c_str()); return VK_ERROR_EXTENSION_NOT_PRESENT; } std::vector translated_names; translated_names.reserve(translated.size()); for (const std::string& extension : translated) { translated_names.push_back(extension.c_str()); } VkInstanceCreateInfo host_info = *create_info; host_info.enabledExtensionCount = static_cast(translated_names.size()); host_info.ppEnabledExtensionNames = translated_names.data(); VkApplicationInfo host_application_info{}; if (create_info->pApplicationInfo != nullptr) { host_application_info = *create_info->pApplicationInfo; } else { host_application_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; } const std::uint32_t requested_api = host_application_info.apiVersion; const auto host_enumerate_version = reinterpret_cast( HostInstanceProc(VK_NULL_HANDLE, "vkEnumerateInstanceVersion")); std::uint32_t host_api = VK_API_VERSION_1_0; if (host_enumerate_version != nullptr) { (void)host_enumerate_version(&host_api); } if (host_api >= VK_API_VERSION_1_2 && requested_api < VK_API_VERSION_1_2) { host_application_info.apiVersion = VK_API_VERSION_1_2; host_info.pApplicationInfo = &host_application_info; } const auto host_create = reinterpret_cast( HostInstanceProc(VK_NULL_HANDLE, "vkCreateInstance")); if (host_create == nullptr) { return VK_ERROR_INITIALIZATION_FAILED; } VkResult result = VK_ERROR_INITIALIZATION_FAILED; bool vr_handled = false; const auto vr_create_instance = State().vr_create_instance.load(std::memory_order_acquire); if (vr_create_instance != nullptr) { // The XR backend creates the instance through xrCreateVulkanInstanceKHR // so the runtime's required instance extensions are enabled on the very // instance Roblox renders with. vr_handled = vr_create_instance(&host_info, allocator, instance, &result); } if (!vr_handled) { result = host_create(&host_info, allocator, instance); } if (result == VK_SUCCESS) { AdapterState& state = State(); std::lock_guard lock(state.mutex); ++state.active_instance_count; state.latest_instance = *instance; state.host_get_device_proc_addr.store( reinterpret_cast( state.host_get_instance_proc_addr(*instance, "vkGetDeviceProcAddr")), std::memory_order_release); std::fprintf(stderr, " [vulkan] host VkInstance created\n"); if (host_application_info.apiVersion != requested_api) { std::fprintf(stderr, " [vulkan] instance API raised to 1.2 for imported " "libplacebo device interop\n"); } } return result; } VKAPI_ATTR void VKAPI_CALL vkDestroyInstance(VkInstance instance, const VkAllocationCallbacks* allocator) { const auto note = ResolveProcessFunction( "mocktail_vr_xr_note_instance_destroyed"); const bool retained_by_backend = note != nullptr && note(instance); const auto host_destroy = retained_by_backend ? nullptr : reinterpret_cast( HostInstanceProc(instance, "vkDestroyInstance")); if (host_destroy != nullptr) { host_destroy(instance, allocator); } AdapterState& state = State(); std::lock_guard lock(state.mutex); if (state.active_instance_count != 0) { --state.active_instance_count; } if (state.active_instance_count == 0 && state.initialized) { state.android_wsi.Shutdown(); state.host_wsi.Shutdown(); state.host_get_instance_proc_addr = nullptr; state.host_get_device_proc_addr.store(nullptr, std::memory_order_release); state.device_dispatches.clear(); state.queue_bindings.clear(); state.command_buffer_bindings.clear(); InvalidateFastDispatchCaches(); state.host_surface_capabilities = nullptr; state.host_surface_present_modes = nullptr; state.latest_instance = VK_NULL_HANDLE; state.note_present.store(nullptr, std::memory_order_release); state.note_host_present_begin.store(nullptr, std::memory_order_release); state.note_host_present_end.store(nullptr, std::memory_order_release); state.note_vr_present_begin.store(nullptr, std::memory_order_release); state.vr_create_instance.store(nullptr, std::memory_order_release); state.vr_create_device.store(nullptr, std::memory_order_release); state.vr_note_device_destroyed.store(nullptr, std::memory_order_release); state.vr_note_queue.store(nullptr, std::memory_order_release); state.vr_record_image.store(nullptr, std::memory_order_release); state.vr_record_image_view.store(nullptr, std::memory_order_release); state.vr_record_framebuffer.store(nullptr, std::memory_order_release); state.vr_record_render_pass.store(nullptr, std::memory_order_release); state.vr_note_destroy_image.store(nullptr, std::memory_order_release); state.vr_note_destroy_image_view.store(nullptr, std::memory_order_release); state.vr_note_destroy_framebuffer.store(nullptr, std::memory_order_release); state.vr_note_render_pass_begin.store(nullptr, std::memory_order_release); state.vr_note_present.store(nullptr, std::memory_order_release); state.note_vulkan_call_begin.store(nullptr, std::memory_order_release); state.note_vulkan_call_end.store(nullptr, std::memory_order_release); g_vulkan_call_observation_active.store(false, std::memory_order_release); state.note_surface_out_of_date.store(nullptr, std::memory_order_release); state.extent_translation_logged = false; state.present_policy_logged = false; state.initialized = false; std::fprintf(stderr, " [vulkan] SDL WSI adapter shut down\n"); } } VKAPI_ATTR VkResult VKAPI_CALL vkCreateAndroidSurfaceKHR( VkInstance instance, const void* create_info, const VkAllocationCallbacks* allocator, VkSurfaceKHR* surface) { if (!EnsureInitialized()) { return VK_ERROR_INITIALIZATION_FAILED; } const auto result = State().android_wsi.CreateAndroidSurface( instance, create_info, allocator, surface); if (result == VK_SUCCESS) { std::fprintf(stderr, " [vulkan] Android surface mapped to SDL WSI\n"); } return static_cast(result); } VKAPI_ATTR void VKAPI_CALL vkDestroySurfaceKHR(VkInstance instance, VkSurfaceKHR surface, const VkAllocationCallbacks* allocator) { if (EnsureInitialized()) { State().android_wsi.DestroySurface(instance, surface, allocator); } } VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceExtensionProperties( const char* layer_name, std::uint32_t* property_count, VkExtensionProperties* properties) { if (property_count == nullptr) { return VK_ERROR_INITIALIZATION_FAILED; } VkResult result = VK_SUCCESS; const std::vector visible = AndroidVisibleExtensions(layer_name, &result); if (result != VK_SUCCESS) { return result; } if (properties == nullptr) { *property_count = static_cast(visible.size()); return VK_SUCCESS; } const std::uint32_t capacity = *property_count; const std::uint32_t copied = std::min(capacity, static_cast(visible.size())); std::copy_n(visible.begin(), copied, properties); *property_count = copied; return copied < visible.size() ? VK_INCOMPLETE : VK_SUCCESS; } VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceLayerProperties( std::uint32_t* property_count, VkLayerProperties* properties) { const auto host_enumerate = reinterpret_cast(HostInstanceProc( VK_NULL_HANDLE, "vkEnumerateInstanceLayerProperties")); return host_enumerate != nullptr ? host_enumerate(property_count, properties) : VK_ERROR_INITIALIZATION_FAILED; } VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(VkInstance instance, const char* name) { if (instance == VK_NULL_HANDLE) { const PFN_vkVoidFunction adapter = AdapterProc(name); return adapter != nullptr && IsGlobalAdapterProc(name) ? adapter : HostInstanceProc(VK_NULL_HANDLE, name); } if (name != nullptr && std::strcmp(name, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR") == 0) { const PFN_vkVoidFunction host = HostInstanceProc(instance, name); AdapterState& state = State(); std::lock_guard lock(state.mutex); state.host_surface_capabilities = reinterpret_cast(host); return host != nullptr ? reinterpret_cast( vkGetPhysicalDeviceSurfaceCapabilitiesKHR) : nullptr; } if (name != nullptr && std::strcmp(name, "vkGetPhysicalDeviceSurfacePresentModesKHR") == 0) { const PFN_vkVoidFunction host = HostInstanceProc(instance, name); AdapterState& state = State(); std::lock_guard lock(state.mutex); state.host_surface_present_modes = reinterpret_cast(host); return host != nullptr ? reinterpret_cast( vkGetPhysicalDeviceSurfacePresentModesKHR) : nullptr; } if (const PFN_vkVoidFunction profiled = ProfileAdapterProc(name); profiled != nullptr) { return HostInstanceProc(instance, name) != nullptr ? profiled : nullptr; } if (const PFN_vkVoidFunction adapter = AdapterProc(name); adapter != nullptr) { if ((IsDeviceAdapterProc(name) || IsEtc2PhysicalDeviceProc(name)) && HostInstanceProc(instance, name) == nullptr) { return nullptr; } return adapter; } return HostInstanceProc(instance, name); } VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkDevice device, const char* name) { if (name == nullptr || !EnsureInitialized()) { return nullptr; } const PFN_vkGetDeviceProcAddr host_get_device_proc_addr = State().host_get_device_proc_addr.load(std::memory_order_acquire); const PFN_vkVoidFunction host = host_get_device_proc_addr != nullptr ? host_get_device_proc_addr(device, name) : nullptr; if (const PFN_vkVoidFunction profiled = ProfileAdapterProc(name); profiled != nullptr) { return host != nullptr ? profiled : nullptr; } if (!IsDeviceAdapterProc(name)) { return host; } return host != nullptr ? AdapterProc(name) : nullptr; } VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice( VkPhysicalDevice physical_device, const VkDeviceCreateInfo* create_info, const VkAllocationCallbacks* allocator, VkDevice* device) { if (create_info == nullptr || device == nullptr) { return VK_ERROR_INITIALIZATION_FAILED; } VkInstance instance = VK_NULL_HANDLE; PFN_vkGetInstanceProcAddr host_get_instance_proc_addr = nullptr; PFN_vkGetDeviceProcAddr host_get_device_proc_addr = nullptr; { AdapterState& state = State(); std::lock_guard lock(state.mutex); instance = state.latest_instance; host_get_instance_proc_addr = state.host_get_instance_proc_addr; host_get_device_proc_addr = state.host_get_device_proc_addr.load(std::memory_order_acquire); } const auto host_create = reinterpret_cast( HostInstanceProc(instance, "vkCreateDevice")); if (host_create == nullptr || host_get_instance_proc_addr == nullptr || host_get_device_proc_addr == nullptr) { return VK_ERROR_INITIALIZATION_FAILED; } VkPhysicalDeviceProperties properties{}; const auto host_get_properties = reinterpret_cast( HostInstanceProc(instance, "vkGetPhysicalDeviceProperties")); if (host_get_properties != nullptr) { host_get_properties(physical_device, &properties); } VkDeviceCreateInfo host_info = *create_info; EnabledDeviceFeatures enabled = InspectEnabledFeatures(*create_info); const bool etc2_emulated = State().etc2.PhysicalDeviceNeedsEmulation( physical_device, reinterpret_cast( HostInstanceProc(instance, "vkGetPhysicalDeviceFeatures"))); VkPhysicalDeviceFeatures host_enabled_features{}; VkPhysicalDeviceFeatures2* requested_features2 = nullptr; VkBool32 requested_features2_etc2 = VK_FALSE; if (etc2_emulated) { enabled.root.features.textureCompressionETC2 = VK_FALSE; if (create_info->pEnabledFeatures != nullptr) { host_enabled_features = *create_info->pEnabledFeatures; host_enabled_features.textureCompressionETC2 = VK_FALSE; host_info.pEnabledFeatures = &host_enabled_features; } // The feature chain is caller-owned; clear the emulated bit only for the // host call and restore it afterwards. requested_features2 = const_cast( reinterpret_cast(FindFeature( create_info->pNext, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2))); if (requested_features2 != nullptr) { requested_features2_etc2 = requested_features2->features.textureCompressionETC2; requested_features2->features.textureCompressionETC2 = VK_FALSE; } } enabled.root.pNext = &enabled.vulkan11; enabled.vulkan11.pNext = &enabled.vulkan12; enabled.vulkan12.pNext = nullptr; VkPhysicalDeviceVulkan12Features injected{}; injected.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; bool injected_required_features = false; if (properties.apiVersion >= VK_API_VERSION_1_2 && !enabled.placebo_required && !HasRequiredFeatureStructs(*create_info)) { VkPhysicalDeviceFeatures2 supported{}; supported.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; VkPhysicalDeviceVulkan12Features supported_vulkan12{}; supported_vulkan12.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; supported.pNext = &supported_vulkan12; const auto host_get_features2 = reinterpret_cast( HostInstanceProc(instance, "vkGetPhysicalDeviceFeatures2")); if (host_get_features2 != nullptr) { host_get_features2(physical_device, &supported); if (supported_vulkan12.timelineSemaphore == VK_TRUE && supported_vulkan12.hostQueryReset == VK_TRUE) { injected.timelineSemaphore = VK_TRUE; injected.hostQueryReset = VK_TRUE; injected.pNext = const_cast(host_info.pNext); host_info.pNext = &injected; enabled.vulkan12.timelineSemaphore = VK_TRUE; enabled.vulkan12.hostQueryReset = VK_TRUE; enabled.placebo_required = true; injected_required_features = true; } } } // Opt-in shader-work counters for real guest render passes. Preserve the // caller's core feature record after the synchronous device-creation call. bool trace_statistics=false; auto* statistics_features2=const_cast( reinterpret_cast(FindFeature( host_info.pNext,VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2))); const VkBool32 original_statistics=statistics_features2 ? statistics_features2->features.pipelineStatisticsQuery : VK_FALSE; if(FovTraceEnabled()) { VkPhysicalDeviceFeatures supported{}; const auto get_features=reinterpret_cast( HostInstanceProc(instance,"vkGetPhysicalDeviceFeatures")); if(get_features)get_features(physical_device,&supported); trace_statistics=supported.pipelineStatisticsQuery==VK_TRUE; if(trace_statistics) { if(statistics_features2)statistics_features2->features.pipelineStatisticsQuery=VK_TRUE; else { if(host_info.pEnabledFeatures)host_enabled_features=*host_info.pEnabledFeatures; host_enabled_features.pipelineStatisticsQuery=VK_TRUE; host_info.pEnabledFeatures=&host_enabled_features; } } std::fprintf(stderr," [vr-pass-gpu] fragment shader counters %s\n",trace_statistics?"enabled":"unsupported"); } const auto foveation_policy = mocktail::graphics::internal::SnapshotFoveationDevicePolicy(&host_info); VkResult result = VK_ERROR_INITIALIZATION_FAILED; bool vr_handled = false; const auto vr_create_device = State().vr_create_device.load(std::memory_order_acquire); if (vr_create_device != nullptr) { // The XR backend creates the device through xrCreateVulkanDeviceKHR and // binds the OpenXR session to it; falling back afterwards would create a // second, XR-incompatible device, so the hook result is final. vr_handled = vr_create_device(physical_device, &host_info, allocator, device, &result); } if (!vr_handled) { result = host_create(physical_device, &host_info, allocator, device); } if(trace_statistics && statistics_features2)statistics_features2->features.pipelineStatisticsQuery=original_statistics; if (requested_features2 != nullptr) { requested_features2->features.textureCompressionETC2 = requested_features2_etc2; } if (result != VK_SUCCESS) { return result; } RegisterHostDeviceDispatch(*device, physical_device, host_get_device_proc_addr); VkPhysicalDeviceMemoryProperties memory_properties{}; if (const auto host_memory_properties = reinterpret_cast( HostInstanceProc(instance, "vkGetPhysicalDeviceMemoryProperties")); host_memory_properties != nullptr) { host_memory_properties(physical_device, &memory_properties); } State().etc2.RegisterDevice(*device, physical_device, etc2_emulated, memory_properties, host_get_device_proc_addr, enabled.vulkan12.timelineSemaphore == VK_TRUE); std::uint32_t queue_family_count = 0; const auto host_get_queue_families = reinterpret_cast( HostInstanceProc(instance, "vkGetPhysicalDeviceQueueFamilyProperties")); if (host_get_queue_families != nullptr) { host_get_queue_families(physical_device, &queue_family_count, nullptr); } std::vector queue_families(queue_family_count); if (host_get_queue_families != nullptr && queue_family_count != 0) { host_get_queue_families(physical_device, &queue_family_count, queue_families.data()); queue_families.resize(queue_family_count); } std::uint32_t graphics_family = UINT32_MAX; std::uint32_t graphics_count = 0; for (std::uint32_t index = 0; index < create_info->queueCreateInfoCount; ++index) { const VkDeviceQueueCreateInfo& queue_info = create_info->pQueueCreateInfos[index]; if (queue_info.queueFamilyIndex >= queue_families.size() || queue_info.queueCount == 0 || (queue_families[queue_info.queueFamilyIndex].queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0) { continue; } graphics_family = queue_info.queueFamilyIndex; graphics_count = 1; break; } FovRegisterDevice(*device, physical_device, memory_properties, graphics_family, trace_statistics, foveation_policy); const bool registered = enabled.placebo_required && graphics_family != UINT32_MAX && State().text_overlay.RegisterDevice( instance, physical_device, *device, VK_API_VERSION_1_2, create_info->ppEnabledExtensionNames, create_info->enabledExtensionCount, graphics_family, graphics_count, &enabled.root, host_get_instance_proc_addr, host_get_device_proc_addr); if (!registered) { std::fprintf(stderr, " [vulkan] same-surface text compositor unavailable for " "this VkDevice\n"); } else if (injected_required_features) { std::fprintf(stderr, " [vulkan] enabled Vulkan 1.2 timeline/host-query features " "for libplacebo interop\n"); } return result; } VKAPI_ATTR void VKAPI_CALL vkDestroyDevice(VkDevice device, const VkAllocationCallbacks* allocator) { const auto host_destroy = reinterpret_cast( HostDeviceProc(device, "vkDestroyDevice")); const auto vr_note_device_destroyed = State().vr_note_device_destroyed.load(std::memory_order_acquire); // Guest helper resources must always be released, even when the native // device stays alive for the next renderer and the existing XR session. Fsr3ForgetDevice(device); FovDestroyDevice(device); const bool retained = vr_note_device_destroyed != nullptr && vr_note_device_destroyed(device); State().text_overlay.DestroyDevice(device); State().etc2.DestroyDevice(device); if (!retained && host_destroy != nullptr) { host_destroy(device, allocator); } RemoveHostDeviceDispatch(device); if (auto* trace = mocktail::graphics::ActiveProfileTrace(); trace != nullptr) { trace->Flush(); } } VKAPI_ATTR void VKAPI_CALL vkGetDeviceQueue(VkDevice device, std::uint32_t queue_family_index, std::uint32_t queue_index, VkQueue* queue) { const auto host_get_queue = reinterpret_cast( HostDeviceProc(device, "vkGetDeviceQueue")); if (host_get_queue == nullptr || queue == nullptr) { return; } host_get_queue(device, queue_family_index, queue_index, queue); if (*queue != VK_NULL_HANDLE) { RegisterHostQueueBinding(device, *queue); (void)State().text_overlay.RegisterQueue(device, *queue, queue_family_index, queue_index); const auto vr_note_queue = State().vr_note_queue.load(std::memory_order_acquire); if (vr_note_queue != nullptr) { vr_note_queue(device, *queue, queue_family_index, queue_index); } } } VKAPI_ATTR void VKAPI_CALL vkGetDeviceQueue2( VkDevice device, const VkDeviceQueueInfo2* queue_info, VkQueue* queue) { const auto host_get_queue = reinterpret_cast( HostDeviceProc(device, "vkGetDeviceQueue2")); if (host_get_queue == nullptr || queue_info == nullptr || queue == nullptr) { return; } host_get_queue(device, queue_info, queue); if (*queue != VK_NULL_HANDLE) { RegisterHostQueueBinding(device, *queue); (void)State().text_overlay.RegisterQueue( device, *queue, queue_info->queueFamilyIndex, queue_info->queueIndex); const auto vr_note_queue = State().vr_note_queue.load(std::memory_order_acquire); if (vr_note_queue != nullptr) { vr_note_queue(device, *queue, queue_info->queueFamilyIndex, queue_info->queueIndex); } } } VKAPI_ATTR VkResult VKAPI_CALL vkCreateSwapchainKHR( VkDevice device, const VkSwapchainCreateInfoKHR* create_info, const VkAllocationCallbacks* allocator, VkSwapchainKHR* swapchain) { const auto host_create = reinterpret_cast( HostDeviceProc(device, "vkCreateSwapchainKHR")); if (host_create == nullptr || create_info == nullptr || swapchain == nullptr) { return VK_ERROR_INITIALIZATION_FAILED; } VkSwapchainCreateInfoKHR host_info = *create_info; unsigned vr_families[256]{}; bool converted_to_concurrent = false; static const auto shared_families = ResolveProcessFunction( "mocktail_vr_desktop_queue_families"); if (shared_families && host_info.imageSharingMode == VK_SHARING_MODE_EXCLUSIVE) { const unsigned count = shared_families(device, 256, vr_families); if (count > 1) { host_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; host_info.queueFamilyIndexCount = count; host_info.pQueueFamilyIndices = vr_families; converted_to_concurrent = true; } } static const auto vr_desktop_enabled = ResolveProcessFunction("mocktail_vr_desktop_enabled"); if (vr_desktop_enabled && vr_desktop_enabled()) { PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR caps; { std::lock_guard lock(State().mutex); caps = State().host_surface_capabilities; } VkSurfaceCapabilitiesKHR supported{}; if (caps && caps(HostDispatchForDevice(device).physical_device, host_info.surface, &supported) == VK_SUCCESS && (supported.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT)) host_info.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; } const mocktail::graphics::PresentModePolicy present_policy = mocktail::graphics::CachedPresentModePolicy(); if (present_policy != mocktail::graphics::PresentModePolicy::kHostDefault) { PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR host_caps = nullptr; PFN_vkGetPhysicalDeviceSurfacePresentModesKHR host_modes_query = nullptr; { AdapterState& state = State(); std::lock_guard lock(state.mutex); host_caps = state.host_surface_capabilities; host_modes_query = state.host_surface_present_modes; } const VkPhysicalDevice physical_device = HostDispatchForDevice(device).physical_device; if (host_modes_query != nullptr && physical_device != VK_NULL_HANDLE) { std::uint32_t host_count = 0; if (host_modes_query(physical_device, create_info->surface, &host_count, nullptr) == VK_SUCCESS && host_count != 0) { std::vector host_modes(host_count); const VkResult modes_result = host_modes_query( physical_device, create_info->surface, &host_count, host_modes.data()); if (modes_result == VK_SUCCESS || modes_result == VK_INCOMPLETE) { host_modes.resize(host_count); const std::vector selected = mocktail::graphics::FilterPresentModes(present_policy, host_modes); if (!selected.empty()) { static std::atomic mode_logged{false}; if (!mode_logged.exchange(true, std::memory_order_relaxed)) { std::fprintf( stderr, " [vulkan] swapchain presentMode requested=%s applied=%s\n", mocktail::graphics::PresentModeKhrName( create_info->presentMode), mocktail::graphics::PresentModeKhrName(selected.front())); } host_info.presentMode = selected.front(); } } } } VkSurfaceCapabilitiesKHR capabilities{}; if (host_caps != nullptr && physical_device != VK_NULL_HANDLE && host_caps(physical_device, create_info->surface, &capabilities) == VK_SUCCESS) { const std::uint32_t preferred = mocktail::graphics::PreferSwapchainMinImageCount( present_policy, create_info->minImageCount, capabilities.minImageCount, capabilities.maxImageCount); if (preferred != host_info.minImageCount) { host_info.minImageCount = preferred; static std::atomic logged{false}; if (!logged.exchange(true, std::memory_order_relaxed)) { std::fprintf(stderr, " [vulkan] present policy=%s swapchain " "minImageCount=%u (requested=%u max=%u)\n", mocktail::graphics::PresentModePolicyName( present_policy), preferred, create_info->minImageCount, capabilities.maxImageCount); } } } } const VkResult result = mocktail::graphics::CreateSwapchainWithPresentModeFallback( host_info, create_info->presentMode, [&](const VkSwapchainCreateInfoKHR& attempt) { return host_create(device, &attempt, allocator, swapchain); }); if (result != VK_SUCCESS) { return result; } const auto host_get_images = reinterpret_cast( HostDeviceProc(device, "vkGetSwapchainImagesKHR")); std::uint32_t image_count = 0; if (host_get_images == nullptr || host_get_images(device, *swapchain, &image_count, nullptr) != VK_SUCCESS || image_count == 0) { return result; } std::vector images(image_count); const VkResult images_result = host_get_images(device, *swapchain, &image_count, images.data()); if (images_result == VK_SUCCESS || images_result == VK_INCOMPLETE) { images.resize(image_count); static const auto record = ResolveProcessFunction( "mocktail_vr_desktop_swapchain_shared"); if (record) record(device, *swapchain, &host_info, images.data(), image_count, converted_to_concurrent); (void)State().text_overlay.RegisterSwapchain(device, *swapchain, host_info, images.data(), image_count); } return result; } VKAPI_ATTR void VKAPI_CALL vkDestroySwapchainKHR(VkDevice device, VkSwapchainKHR swapchain, const VkAllocationCallbacks* allocator) { const auto host_destroy = reinterpret_cast( HostDeviceProc(device, "vkDestroySwapchainKHR")); static const auto record = ResolveProcessFunction("mocktail_vr_desktop_swapchain"); if (record) record(device, swapchain, nullptr, nullptr, 0); State().text_overlay.DestroySwapchain(device, swapchain); if (host_destroy != nullptr) { host_destroy(device, swapchain, allocator); } } VKAPI_ATTR VkResult VKAPI_CALL vkAcquireNextImageKHR( VkDevice device, VkSwapchainKHR swapchain, std::uint64_t timeout, VkSemaphore semaphore, VkFence fence, std::uint32_t* image_index) { VulkanCallObservation observation("vkAcquireNextImageKHR"); const PFN_vkAcquireNextImageKHR host_acquire = HostDispatchForDevice(device).acquire_next_image; if (host_acquire == nullptr) { observation.SetResult(VK_ERROR_INITIALIZATION_FAILED); return VK_ERROR_INITIALIZATION_FAILED; } mocktail::graphics::TraceScope scope(mocktail::graphics::ActiveProfileTrace(), "vkAcquireNextImageKHR", "wait"); const bool fps_trace = FpsTraceEnabled(); const std::uint64_t acquire_start_ns = fps_trace ? MonotonicNanos() : 0; const VkResult host_result = host_acquire( device, swapchain, timeout, semaphore, fence, image_index); if (fps_trace) { AcquireWaitTrace().Record(acquire_start_ns); } const VkResult result = NormalizeSwapchainResult(host_result); if (result == VK_ERROR_OUT_OF_DATE_KHR) { const NoteSurfaceOutOfDateFn note_surface_out_of_date = State().note_surface_out_of_date.load(std::memory_order_acquire); if (note_surface_out_of_date != nullptr) { note_surface_out_of_date(); } } observation.SetResult(result); return result; } VKAPI_ATTR VkResult VKAPI_CALL vkAcquireNextImage2KHR( VkDevice device, const VkAcquireNextImageInfoKHR* acquire_info, std::uint32_t* image_index) { VulkanCallObservation observation("vkAcquireNextImage2KHR"); const PFN_vkAcquireNextImage2KHR host_acquire = HostDispatchForDevice(device).acquire_next_image2; if (host_acquire == nullptr || acquire_info == nullptr) { observation.SetResult(VK_ERROR_INITIALIZATION_FAILED); return VK_ERROR_INITIALIZATION_FAILED; } mocktail::graphics::TraceScope scope(mocktail::graphics::ActiveProfileTrace(), "vkAcquireNextImage2KHR", "wait"); const VkResult host_result = host_acquire(device, acquire_info, image_index); const VkResult result = NormalizeSwapchainResult(host_result); if (result == VK_ERROR_OUT_OF_DATE_KHR) { const NoteSurfaceOutOfDateFn note_surface_out_of_date = State().note_surface_out_of_date.load(std::memory_order_acquire); if (note_surface_out_of_date != nullptr) { note_surface_out_of_date(); } } observation.SetResult(result); return result; } VKAPI_ATTR VkResult VKAPI_CALL vkWaitForFences( VkDevice device, std::uint32_t fence_count, const VkFence* fences, VkBool32 wait_all, std::uint64_t timeout) { VulkanCallObservation observation("vkWaitForFences"); const PFN_vkWaitForFences host_wait = HostDispatchForDevice(device).wait_for_fences; if (host_wait == nullptr) { observation.SetResult(VK_ERROR_INITIALIZATION_FAILED); return VK_ERROR_INITIALIZATION_FAILED; } mocktail::graphics::TraceScope scope(mocktail::graphics::ActiveProfileTrace(), "vkWaitForFences", "wait"); const bool fps_trace = FpsTraceEnabled(); const std::uint64_t fence_start_ns = fps_trace ? MonotonicNanos() : 0; const VkResult result = host_wait(device, fence_count, fences, wait_all, timeout); if (fps_trace) { FenceWaitTrace().Record(fence_start_ns); } observation.SetResult(result); return result; } VKAPI_ATTR VkResult VKAPI_CALL vkResetFences( VkDevice device, std::uint32_t fence_count, const VkFence* fences) { VulkanCallObservation observation("vkResetFences"); const PFN_vkResetFences host_reset = HostDispatchForDevice(device).reset_fences; if (host_reset == nullptr) { observation.SetResult(VK_ERROR_INITIALIZATION_FAILED); return VK_ERROR_INITIALIZATION_FAILED; } const VkResult result = host_reset(device, fence_count, fences); observation.SetResult(result); return result; } VKAPI_ATTR VkResult VKAPI_CALL vkResetCommandPool( VkDevice device, VkCommandPool command_pool, VkCommandPoolResetFlags flags) { VulkanCallObservation observation("vkResetCommandPool"); const PFN_vkResetCommandPool host_reset = HostDispatchForDevice(device).reset_command_pool; if (host_reset == nullptr) { observation.SetResult(VK_ERROR_INITIALIZATION_FAILED); return VK_ERROR_INITIALIZATION_FAILED; } ReleaseEtc2PoolCommandBuffers(device, command_pool); const VkResult result = host_reset(device, command_pool, flags); static const auto note = ResolveProcessFunction( "mocktail_vr_xr_reset_pool"); if (result == VK_SUCCESS && note) note(command_pool); observation.SetResult(result); return result; } VKAPI_ATTR VkResult VKAPI_CALL vkGetQueryPoolResults( VkDevice device, VkQueryPool query_pool, std::uint32_t first_query, std::uint32_t query_count, std::size_t data_size, void* data, VkDeviceSize stride, VkQueryResultFlags flags) { VulkanCallObservation observation("vkGetQueryPoolResults"); const PFN_vkGetQueryPoolResults host_get_results = HostDispatchForDevice(device).get_query_pool_results; if (host_get_results == nullptr) { observation.SetResult(VK_ERROR_INITIALIZATION_FAILED); return VK_ERROR_INITIALIZATION_FAILED; } const VkResult result = host_get_results(device, query_pool, first_query, query_count, data_size, data, stride, flags); observation.SetResult(result); return result; } VKAPI_ATTR VkResult VKAPI_CALL vkAllocateCommandBuffers( VkDevice device, const VkCommandBufferAllocateInfo* allocate_info, VkCommandBuffer* command_buffers) { VulkanCallObservation observation("vkAllocateCommandBuffers"); const PFN_vkAllocateCommandBuffers host_allocate = HostDispatchForDevice(device).allocate_command_buffers; if (host_allocate == nullptr) { observation.SetResult(VK_ERROR_INITIALIZATION_FAILED); return VK_ERROR_INITIALIZATION_FAILED; } const VkResult result = host_allocate(device, allocate_info, command_buffers); if (result == VK_SUCCESS && allocate_info != nullptr && command_buffers != nullptr) { RegisterHostCommandBuffers(device, allocate_info->commandPool, allocate_info->commandBufferCount, command_buffers); static const auto note = ResolveProcessFunction( "mocktail_vr_xr_command_buffers"); if (note) note(device, allocate_info->commandPool, allocate_info->commandBufferCount, command_buffers); } observation.SetResult(result); return result; } VKAPI_ATTR void VKAPI_CALL vkFreeCommandBuffers( VkDevice device, VkCommandPool command_pool, std::uint32_t command_buffer_count, const VkCommandBuffer* command_buffers) { VulkanCallObservation observation("vkFreeCommandBuffers"); const PFN_vkFreeCommandBuffers host_free = HostDispatchForDevice(device).free_command_buffers; if (host_free == nullptr) { observation.SetResult(VK_ERROR_INITIALIZATION_FAILED); return; } if (command_buffers != nullptr) { for (std::uint32_t index = 0; index < command_buffer_count; ++index) { State().etc2.ReleaseCommandBuffer(command_buffers[index]); Fsr3ResetCommand(command_buffers[index]); } } static const auto note = ResolveProcessFunction( "mocktail_vr_xr_command_buffers"); if (note) note(device, VK_NULL_HANDLE, command_buffer_count, command_buffers); host_free(device, command_pool, command_buffer_count, command_buffers); RemoveHostCommandBuffers(device, command_pool, command_buffer_count, command_buffers); observation.SetResult(VK_SUCCESS); } VKAPI_ATTR void VKAPI_CALL vkDestroyCommandPool( VkDevice device, VkCommandPool command_pool, const VkAllocationCallbacks* allocator) { VulkanCallObservation observation("vkDestroyCommandPool"); const PFN_vkDestroyCommandPool host_destroy = HostDispatchForDevice(device).destroy_command_pool; if (host_destroy == nullptr) { observation.SetResult(VK_ERROR_INITIALIZATION_FAILED); return; } ReleaseEtc2PoolCommandBuffers(device, command_pool); static const auto note = ResolveProcessFunction( "mocktail_vr_xr_command_pool"); if (note) note(device, command_pool, UINT32_MAX); host_destroy(device, command_pool, allocator); RemoveHostCommandPoolBindings(device, command_pool); observation.SetResult(VK_SUCCESS); } VKAPI_ATTR VkResult VKAPI_CALL vkBeginCommandBuffer( VkCommandBuffer command_buffer, const VkCommandBufferBeginInfo* begin_info) { VulkanCallObservation observation("vkBeginCommandBuffer"); const PFN_vkBeginCommandBuffer host_begin = HostDispatchForCommandBuffer(command_buffer).begin_command_buffer; if (host_begin == nullptr) { observation.SetResult(VK_ERROR_INITIALIZATION_FAILED); return VK_ERROR_INITIALIZATION_FAILED; } State().etc2.ReleaseCommandBuffer(command_buffer); Fsr3ResetCommand(command_buffer); const VkResult result = host_begin(command_buffer, begin_info); if(result==VK_SUCCESS)FovTraceReset(command_buffer); static const auto note = ResolveProcessFunction( "mocktail_vr_xr_reset_command"); if (result == VK_SUCCESS && note) note(command_buffer); observation.SetResult(result); return result; } VKAPI_ATTR VkResult VKAPI_CALL vkEndCommandBuffer(VkCommandBuffer command_buffer) { VulkanCallObservation observation("vkEndCommandBuffer"); const PFN_vkEndCommandBuffer host_end = HostDispatchForCommandBuffer(command_buffer).end_command_buffer; if (host_end == nullptr) { observation.SetResult(VK_ERROR_INITIALIZATION_FAILED); return VK_ERROR_INITIALIZATION_FAILED; } const VkResult result = host_end(command_buffer); observation.SetResult(result); return result; } VKAPI_ATTR VkResult VKAPI_CALL vkResetCommandBuffer( VkCommandBuffer command_buffer, VkCommandBufferResetFlags flags) { VulkanCallObservation observation("vkResetCommandBuffer"); const PFN_vkResetCommandBuffer host_reset = HostDispatchForCommandBuffer(command_buffer).reset_command_buffer; if (host_reset == nullptr) { observation.SetResult(VK_ERROR_INITIALIZATION_FAILED); return VK_ERROR_INITIALIZATION_FAILED; } State().etc2.ReleaseCommandBuffer(command_buffer); Fsr3ResetCommand(command_buffer); const VkResult result = host_reset(command_buffer, flags); static const auto note = ResolveProcessFunction( "mocktail_vr_xr_reset_command"); if (result == VK_SUCCESS && note) note(command_buffer); observation.SetResult(result); return result; } VKAPI_ATTR VkResult VKAPI_CALL vkWaitSemaphores( VkDevice device, const VkSemaphoreWaitInfo* wait_info, std::uint64_t timeout) { VulkanCallObservation observation("vkWaitSemaphores"); const PFN_vkWaitSemaphores host_wait = HostDispatchForDevice(device).wait_semaphores; return WaitForSemaphoresObserved("vkWaitSemaphores", host_wait, device, wait_info, timeout, &observation); } VKAPI_ATTR VkResult VKAPI_CALL vkWaitSemaphoresKHR( VkDevice device, const VkSemaphoreWaitInfo* wait_info, std::uint64_t timeout) { VulkanCallObservation observation("vkWaitSemaphoresKHR"); const PFN_vkWaitSemaphoresKHR host_wait = HostDispatchForDevice(device).wait_semaphores_khr; return WaitForSemaphoresObserved( "vkWaitSemaphoresKHR", reinterpret_cast(host_wait), device, wait_info, timeout, &observation); } VKAPI_ATTR VkResult VKAPI_CALL vkQueueSubmit( VkQueue queue, std::uint32_t submit_count, const VkSubmitInfo* submits, VkFence fence) { VulkanCallObservation observation("vkQueueSubmit"); const PFN_vkQueueSubmit host_submit = HostDispatchForQueue(queue).queue_submit; if (host_submit == nullptr) { observation.SetResult(VK_ERROR_INITIALIZATION_FAILED); return VK_ERROR_INITIALIZATION_FAILED; } mocktail::graphics::TraceScope scope(mocktail::graphics::ActiveProfileTrace(), "vkQueueSubmit", "submit"); scope.Arg("submits", submit_count); Etc2SubmitRewrite rewrite; const VkSubmitInfo* prepared = PrepareEtc2Submit(submit_count, submits, &rewrite); const VkResult result = State().text_overlay.QueueSubmit( queue, submit_count, prepared, fence, LockedHostSubmit); if (result == VK_SUCCESS) { NoteVrSubmitted(queue, submit_count, prepared); FovTraceSubmitted(queue,submit_count,prepared); } observation.SetResult(result); return result; } VKAPI_ATTR VkResult VKAPI_CALL vkQueueSubmit2( VkQueue queue, std::uint32_t submit_count, const VkSubmitInfo2* submits, VkFence fence) { VulkanCallObservation observation("vkQueueSubmit2"); const PFN_vkQueueSubmit2 host_submit = HostDispatchForQueue(queue).queue_submit2; if (host_submit == nullptr) { observation.SetResult(VK_ERROR_INITIALIZATION_FAILED); return VK_ERROR_INITIALIZATION_FAILED; } mocktail::graphics::TraceScope scope(mocktail::graphics::ActiveProfileTrace(), "vkQueueSubmit2", "submit"); scope.Arg("submits", submit_count); Etc2SubmitRewrite rewrite; const VkSubmitInfo2* prepared = PrepareEtc2Submit2(submit_count, submits, &rewrite); const VkResult result = State().text_overlay.QueueSubmit2( queue, submit_count, prepared, fence, LockedHostSubmit2); if (result == VK_SUCCESS) { NoteVrSubmitted2(queue, submit_count, prepared); FovTraceSubmitted(queue,submit_count,prepared); } observation.SetResult(result); return result; } VKAPI_ATTR VkResult VKAPI_CALL vkQueueSubmit2KHR( VkQueue queue, std::uint32_t submit_count, const VkSubmitInfo2* submits, VkFence fence) { VulkanCallObservation observation("vkQueueSubmit2KHR"); const PFN_vkQueueSubmit2KHR host_submit = HostDispatchForQueue(queue).queue_submit2_khr; if (host_submit == nullptr) { observation.SetResult(VK_ERROR_INITIALIZATION_FAILED); return VK_ERROR_INITIALIZATION_FAILED; } mocktail::graphics::TraceScope scope(mocktail::graphics::ActiveProfileTrace(), "vkQueueSubmit2KHR", "submit"); scope.Arg("submits", submit_count); Etc2SubmitRewrite rewrite; const VkSubmitInfo2* prepared = PrepareEtc2Submit2(submit_count, submits, &rewrite); const VkResult result = State().text_overlay.QueueSubmit2( queue, submit_count, prepared, fence, LockedHostSubmit2KHR); if (result == VK_SUCCESS) { NoteVrSubmitted2(queue, submit_count, prepared); FovTraceSubmitted(queue,submit_count,prepared); } observation.SetResult(result); return result; } VKAPI_ATTR VkResult VKAPI_CALL vkQueueBindSparse( VkQueue queue, std::uint32_t bind_info_count, const VkBindSparseInfo* bind_info, VkFence fence) { VulkanCallObservation observation("vkQueueBindSparse"); const PFN_vkQueueBindSparse host_bind = HostDispatchForQueue(queue).queue_bind_sparse; if (host_bind == nullptr) { observation.SetResult(VK_ERROR_INITIALIZATION_FAILED); return VK_ERROR_INITIALIZATION_FAILED; } const VkResult result = State().text_overlay.QueueBindSparse( queue, bind_info_count, bind_info, fence, LockedHostBindSparse); observation.SetResult(result); return result; } VKAPI_ATTR VkResult VKAPI_CALL vkQueueWaitIdle(VkQueue queue) { VulkanCallObservation observation("vkQueueWaitIdle"); const PFN_vkQueueWaitIdle host_wait = HostDispatchForQueue(queue).queue_wait_idle; if (host_wait == nullptr) { observation.SetResult(VK_ERROR_INITIALIZATION_FAILED); return VK_ERROR_INITIALIZATION_FAILED; } mocktail::graphics::TraceScope scope(mocktail::graphics::ActiveProfileTrace(), "vkQueueWaitIdle", "wait"); const bool fps_trace = FpsTraceEnabled(); const std::uint64_t idle_start_ns = fps_trace ? MonotonicNanos() : 0; const VkResult result = State().text_overlay.QueueWaitIdle(queue, LockedHostQueueWaitIdle); if (fps_trace) { QueueIdleWaitTrace().Record(idle_start_ns); } observation.SetResult(result); return result; } VKAPI_ATTR VkResult VKAPI_CALL vkDeviceWaitIdle(VkDevice device) { VulkanCallObservation observation("vkDeviceWaitIdle"); const PFN_vkDeviceWaitIdle host_wait = HostDispatchForDevice(device).device_wait_idle; if (host_wait == nullptr) { observation.SetResult(VK_ERROR_INITIALIZATION_FAILED); return VK_ERROR_INITIALIZATION_FAILED; } mocktail::graphics::TraceScope scope(mocktail::graphics::ActiveProfileTrace(), "vkDeviceWaitIdle", "wait"); const VkResult result = State().text_overlay.DeviceWaitIdle(device, LockedHostDeviceWaitIdle); observation.SetResult(result); return result; } VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceCapabilitiesKHR( VkPhysicalDevice physical_device, VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR* capabilities) { PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR host_capabilities = nullptr; { AdapterState& state = State(); std::lock_guard lock(state.mutex); host_capabilities = state.host_surface_capabilities; } if (host_capabilities == nullptr || capabilities == nullptr) { return VK_ERROR_INITIALIZATION_FAILED; } const VkResult result = host_capabilities(physical_device, surface, capabilities); if (result != VK_SUCCESS || (capabilities->currentExtent.width != UINT32_MAX && capabilities->currentExtent.height != UINT32_MAX)) { return result; } const WindowDimensionFn window_width = ResolveProcessFunction("mocktail_window_width"); const WindowDimensionFn window_height = ResolveProcessFunction("mocktail_window_height"); if (window_width == nullptr || window_height == nullptr || window_width() <= 0 || window_height() <= 0) { return result; } capabilities->currentExtent.width = std::clamp( static_cast(window_width()), capabilities->minImageExtent.width, capabilities->maxImageExtent.width); capabilities->currentExtent.height = std::clamp( static_cast(window_height()), capabilities->minImageExtent.height, capabilities->maxImageExtent.height); AdapterState& state = State(); std::lock_guard lock(state.mutex); if (!state.extent_translation_logged) { state.extent_translation_logged = true; std::fprintf( stderr, " [vulkan] Android currentExtent translated to %ux%u\n", capabilities->currentExtent.width, capabilities->currentExtent.height); } return result; } VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfacePresentModesKHR( VkPhysicalDevice physical_device, VkSurfaceKHR surface, std::uint32_t* present_mode_count, VkPresentModeKHR* present_modes) { if (present_mode_count == nullptr) { return VK_ERROR_INITIALIZATION_FAILED; } PFN_vkGetPhysicalDeviceSurfacePresentModesKHR host_query = nullptr; { AdapterState& state = State(); std::lock_guard lock(state.mutex); host_query = state.host_surface_present_modes; } if (host_query == nullptr) { return VK_ERROR_INITIALIZATION_FAILED; } std::uint32_t host_count = 0; VkResult result = host_query(physical_device, surface, &host_count, nullptr); if (result != VK_SUCCESS) { return result; } std::vector host_modes(host_count); if (host_count != 0) { result = host_query(physical_device, surface, &host_count, host_modes.data()); if (result != VK_SUCCESS && result != VK_INCOMPLETE) { return result; } host_modes.resize(host_count); } const mocktail::graphics::PresentModePolicy policy = mocktail::graphics::CachedPresentModePolicy(); const std::vector visible = mocktail::graphics::FilterPresentModes(policy, host_modes); { AdapterState& state = State(); std::lock_guard lock(state.mutex); if (!state.present_policy_logged) { state.present_policy_logged = true; std::fprintf(stderr, " [vulkan] present policy=%s modes=%zu\n", mocktail::graphics::PresentModePolicyName(policy), visible.size()); } } if (present_modes == nullptr) { *present_mode_count = static_cast(visible.size()); return VK_SUCCESS; } const std::uint32_t capacity = *present_mode_count; const std::uint32_t copied = std::min(capacity, static_cast(visible.size())); std::copy_n(visible.begin(), copied, present_modes); *present_mode_count = copied; return copied < visible.size() ? VK_INCOMPLETE : VK_SUCCESS; } VKAPI_ATTR VkResult VKAPI_CALL vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) { VulkanCallObservation observation("vkQueuePresentKHR(adapter)"); AdapterState& state = State(); const PFN_vkQueuePresentKHR host_present = HostDispatchForQueue(queue).queue_present; const NotePresentFn note_present = state.note_present.load(std::memory_order_acquire); if (host_present == nullptr) { observation.SetResult(VK_ERROR_INITIALIZATION_FAILED); return VK_ERROR_INITIALIZATION_FAILED; } mocktail::graphics::ChromeTraceWriter* const trace = mocktail::graphics::ActiveProfileTrace(); mocktail::graphics::TraceScope scope(trace, "vkQueuePresentKHR", "present"); if (trace != nullptr) { RecordProfiledFrame(trace, mocktail::graphics::TraceClockNanos()); } static const auto mirror = ResolveProcessFunction( "mocktail_vr_desktop_present"); VkPresentInfoKHR mirrored{}; VkSemaphore mirror_ready = VK_NULL_HANDLE; if (mirror && present_info) { const VkResult mirror_result = mirror(queue, present_info, &mirror_ready); if (mirror_result < 0) return mirror_result; if (mirror_result == VK_SUCCESS) { // The blit consumed the guest's waits and signals mirror_ready. mirrored = *present_info; mirrored.waitSemaphoreCount = 1; mirrored.pWaitSemaphores = &mirror_ready; present_info = &mirrored; } } const VkResult result = state.text_overlay.QueuePresent( queue, present_info, ObservedHostQueuePresent); scope.Arg("result", result); const VkResult normalized_result = NormalizeSwapchainResult(result); if (normalized_result == VK_ERROR_OUT_OF_DATE_KHR) { const NoteSurfaceOutOfDateFn note_surface_out_of_date = state.note_surface_out_of_date.load(std::memory_order_acquire); if (note_surface_out_of_date != nullptr) { note_surface_out_of_date(); } } if (normalized_result == VK_SUCCESS && note_present != nullptr) { note_present(); } if (present_info != nullptr && present_info->pResults != nullptr) { for (std::uint32_t index = 0; index < present_info->swapchainCount; ++index) { present_info->pResults[index] = NormalizeSwapchainResult(present_info->pResults[index]); } } observation.SetResult(normalized_result); return normalized_result; } VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFeatures( VkPhysicalDevice physical_device, VkPhysicalDeviceFeatures* features) { const auto host = HostPhysicalDeviceProc( "vkGetPhysicalDeviceFeatures", nullptr); if (host == nullptr || features == nullptr) { return; } host(physical_device, features); if (Etc2EmulatedPhysicalDevice(physical_device) && mocktail::graphics::Etc2SupportAdvertised()) { features->textureCompressionETC2 = VK_TRUE; } } VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFeatures2( VkPhysicalDevice physical_device, VkPhysicalDeviceFeatures2* features) { const auto host = HostPhysicalDeviceProc( "vkGetPhysicalDeviceFeatures2", "vkGetPhysicalDeviceFeatures2KHR"); if (host == nullptr || features == nullptr) { return; } host(physical_device, features); if (Etc2EmulatedPhysicalDevice(physical_device) && mocktail::graphics::Etc2SupportAdvertised()) { features->features.textureCompressionETC2 = VK_TRUE; } } VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFormatProperties( VkPhysicalDevice physical_device, VkFormat format, VkFormatProperties* properties) { const auto host = HostPhysicalDeviceProc( "vkGetPhysicalDeviceFormatProperties", nullptr); if (host == nullptr || properties == nullptr) { return; } mocktail::graphics::Etc2EmulatedFormat mapped; if (!mocktail::graphics::LookupEmulatedEtc2Format(format, &mapped) || !Etc2EmulatedPhysicalDevice(physical_device)) { host(physical_device, format, properties); return; } host(physical_device, mapped.host_format, properties); properties->linearTilingFeatures = 0; properties->optimalTilingFeatures = mocktail::graphics::EmulatedEtc2FormatFeatures( properties->optimalTilingFeatures); properties->bufferFeatures = 0; } VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFormatProperties2( VkPhysicalDevice physical_device, VkFormat format, VkFormatProperties2* properties) { const auto host = HostPhysicalDeviceProc( "vkGetPhysicalDeviceFormatProperties2", "vkGetPhysicalDeviceFormatProperties2KHR"); if (host == nullptr || properties == nullptr) { return; } mocktail::graphics::Etc2EmulatedFormat mapped; if (!mocktail::graphics::LookupEmulatedEtc2Format(format, &mapped) || !Etc2EmulatedPhysicalDevice(physical_device)) { host(physical_device, format, properties); return; } host(physical_device, mapped.host_format, properties); VkFormatProperties& base = properties->formatProperties; base.linearTilingFeatures = 0; base.optimalTilingFeatures = mocktail::graphics::EmulatedEtc2FormatFeatures(base.optimalTilingFeatures); base.bufferFeatures = 0; if (auto* extended = const_cast( reinterpret_cast(FindFeature( properties->pNext, VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3))); extended != nullptr) { extended->linearTilingFeatures = 0; extended->optimalTilingFeatures = mocktail::graphics::EmulatedEtc2FormatFeatures( static_cast( extended->optimalTilingFeatures)); extended->bufferFeatures = 0; } } VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceImageFormatProperties( VkPhysicalDevice physical_device, VkFormat format, VkImageType type, VkImageTiling tiling, VkImageUsageFlags usage, VkImageCreateFlags flags, VkImageFormatProperties* properties) { const auto host = HostPhysicalDeviceProc( "vkGetPhysicalDeviceImageFormatProperties", nullptr); if (host == nullptr) { return VK_ERROR_INITIALIZATION_FAILED; } mocktail::graphics::Etc2EmulatedFormat mapped; if (!mocktail::graphics::LookupEmulatedEtc2Format(format, &mapped) || !Etc2EmulatedPhysicalDevice(physical_device)) { return host(physical_device, format, type, tiling, usage, flags, properties); } if (tiling != VK_IMAGE_TILING_OPTIMAL || type != VK_IMAGE_TYPE_2D) { return VK_ERROR_FORMAT_NOT_SUPPORTED; } return host(physical_device, mapped.host_format, type, tiling, usage, flags & ~static_cast( VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT), properties); } VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceImageFormatProperties2( VkPhysicalDevice physical_device, const VkPhysicalDeviceImageFormatInfo2* info, VkImageFormatProperties2* properties) { const auto host = HostPhysicalDeviceProc( "vkGetPhysicalDeviceImageFormatProperties2", "vkGetPhysicalDeviceImageFormatProperties2KHR"); if (host == nullptr || info == nullptr) { return VK_ERROR_INITIALIZATION_FAILED; } mocktail::graphics::Etc2EmulatedFormat mapped; if (!mocktail::graphics::LookupEmulatedEtc2Format(info->format, &mapped) || !Etc2EmulatedPhysicalDevice(physical_device)) { return host(physical_device, info, properties); } if (info->tiling != VK_IMAGE_TILING_OPTIMAL || info->type != VK_IMAGE_TYPE_2D) { return VK_ERROR_FORMAT_NOT_SUPPORTED; } VkPhysicalDeviceImageFormatInfo2 host_info = *info; host_info.format = mapped.host_format; host_info.flags &= ~static_cast( VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT); return host(physical_device, &host_info, properties); } // ---- VR backend resource provenance wrappers ------------------------------- // Thin pass-throughs to the host dispatch with optional notifications for the // experimental OpenXR backend. Creation/destruction calls are rare; the only // per-frame entry (vkCmdBeginRenderPass) costs one atomic load and stops // doing work once both eye bindings are established. VKAPI_ATTR VkResult VKAPI_CALL vkCreateImage( VkDevice device, const VkImageCreateInfo* create_info, const VkAllocationCallbacks* allocator, VkImage* image) { mocktail::graphics::TraceScope scope(mocktail::graphics::ActiveProfileTrace(), "vkCreateImage", "memory"); VkImageCreateInfo effective{}; const VkResult result = Fsr3CreateImage(device, create_info, allocator, image, &effective); const auto record = State().vr_record_image.load(std::memory_order_acquire); if (result == VK_SUCCESS && record != nullptr && image != nullptr) { // Preserve the guest's original provenance contract. Adding transfer // usage for temporal capture must not make a scene intermediate appear // to the existing backend as a new final-eye target. record(device, *image, create_info); } return result; } VKAPI_ATTR void VKAPI_CALL vkDestroyImage( VkDevice device, VkImage image, const VkAllocationCallbacks* allocator) { mocktail::graphics::TraceScope scope(mocktail::graphics::ActiveProfileTrace(), "vkDestroyImage", "memory"); const auto note = State().vr_note_destroy_image.load(std::memory_order_acquire); if (note != nullptr && image != VK_NULL_HANDLE) { note(image); } Fsr3ForgetImage(image); State().etc2.DestroyImage(device, image, allocator); } VKAPI_ATTR VkResult VKAPI_CALL vkCreateImageView( VkDevice device, const VkImageViewCreateInfo* create_info, const VkAllocationCallbacks* allocator, VkImageView* view) { const VkResult result = State().etc2.CreateImageView(device, create_info, allocator, view); const auto record = State().vr_record_image_view.load(std::memory_order_acquire); if (result == VK_SUCCESS && record != nullptr && create_info != nullptr && view != nullptr) { record(*view, create_info->image); } if(result==VK_SUCCESS && view)Fsr3RecordView(*view,create_info); return result; } VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize offset) { return State().etc2.BindBufferMemory(device, buffer, memory, offset); } VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory2( VkDevice device, std::uint32_t count, const VkBindBufferMemoryInfo* infos) { return State().etc2.BindBufferMemory2(device, count, infos); } VKAPI_ATTR VkResult VKAPI_CALL vkMapMemory(VkDevice device, VkDeviceMemory memory, VkDeviceSize offset, VkDeviceSize size, VkMemoryMapFlags flags, void** data) { mocktail::graphics::TraceScope scope(mocktail::graphics::ActiveProfileTrace(), "vkMapMemory", "memory"); return State().etc2.MapMemory(device, memory, offset, size, flags, data); } VKAPI_ATTR VkResult VKAPI_CALL vkMapMemory2(VkDevice device, const VkMemoryMapInfo* info, void** data) { return State().etc2.MapMemory2(device, info, data); } VKAPI_ATTR void VKAPI_CALL vkUnmapMemory(VkDevice device, VkDeviceMemory memory) { // Includes waiting for ETC2 decodes that still read this memory. mocktail::graphics::TraceScope scope(mocktail::graphics::ActiveProfileTrace(), "vkUnmapMemory", "memory"); State().etc2.UnmapMemory(device, memory); } VKAPI_ATTR VkResult VKAPI_CALL vkUnmapMemory2(VkDevice device, const VkMemoryUnmapInfo* info) { return State().etc2.UnmapMemory2(device, info); } VKAPI_ATTR void VKAPI_CALL vkFreeMemory( VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks* allocator) { // Includes waiting for ETC2 decodes that still read this memory. mocktail::graphics::TraceScope scope(mocktail::graphics::ActiveProfileTrace(), "vkFreeMemory", "memory"); State().etc2.FreeMemory(device, memory, allocator); } VKAPI_ATTR void VKAPI_CALL vkDestroyBuffer( VkDevice device, VkBuffer buffer, const VkAllocationCallbacks* allocator) { State().etc2.DestroyBuffer(device, buffer, allocator); } VKAPI_ATTR void VKAPI_CALL vkCmdCopyBufferToImage( VkCommandBuffer command_buffer, VkBuffer source, VkImage destination, VkImageLayout layout, std::uint32_t region_count, const VkBufferImageCopy* regions) { State().etc2.CmdCopyBufferToImage( HostDispatchForCommandBuffer(command_buffer).device, command_buffer, source, destination, layout, region_count, regions); } VKAPI_ATTR void VKAPI_CALL vkCmdCopyBufferToImage2( VkCommandBuffer command_buffer, const VkCopyBufferToImageInfo2* info) { State().etc2.CmdCopyBufferToImage2( HostDispatchForCommandBuffer(command_buffer).device, command_buffer, info); } VKAPI_ATTR void VKAPI_CALL vkCmdCopyImage( VkCommandBuffer command_buffer, VkImage source, VkImageLayout source_layout, VkImage destination, VkImageLayout destination_layout, std::uint32_t region_count, const VkImageCopy* regions) { State().etc2.CmdCopyImage( HostDispatchForCommandBuffer(command_buffer).device, command_buffer, source, source_layout, destination, destination_layout, region_count, regions); } VKAPI_ATTR void VKAPI_CALL vkCmdCopyImage2(VkCommandBuffer command_buffer, const VkCopyImageInfo2* info) { State().etc2.CmdCopyImage2( HostDispatchForCommandBuffer(command_buffer).device, command_buffer, info); } VKAPI_ATTR void VKAPI_CALL vkCmdExecuteCommands( VkCommandBuffer command_buffer, std::uint32_t count, const VkCommandBuffer* secondaries) { State().etc2.CmdExecuteCommands( HostDispatchForCommandBuffer(command_buffer).device, command_buffer, count, secondaries); } VKAPI_ATTR void VKAPI_CALL vkDestroyImageView(VkDevice device, VkImageView image_view, const VkAllocationCallbacks* allocator) { const auto note = State().vr_note_destroy_image_view.load(std::memory_order_acquire); if (note != nullptr && image_view != VK_NULL_HANDLE) { note(image_view); } Fsr3ForgetView(image_view); const PFN_vkDestroyImageView host_destroy = HostDispatchForDevice(device).destroy_image_view; if (host_destroy != nullptr) { host_destroy(device, image_view, allocator); } } VKAPI_ATTR VkResult VKAPI_CALL vkCreateFramebuffer( VkDevice device, const VkFramebufferCreateInfo* create_info, const VkAllocationCallbacks* allocator, VkFramebuffer* framebuffer) { const PFN_vkCreateFramebuffer host_create = HostDispatchForDevice(device).create_framebuffer; if (host_create == nullptr) { return VK_ERROR_INITIALIZATION_FAILED; } const VkResult result = FovCreateFramebuffer(device, create_info, allocator, framebuffer); const auto record = State().vr_record_framebuffer.load(std::memory_order_acquire); if (result == VK_SUCCESS && record != nullptr) { record(*framebuffer, create_info); } if(result==VK_SUCCESS && framebuffer)Fsr3RecordFramebuffer(device,*framebuffer,create_info); return result; } VKAPI_ATTR void VKAPI_CALL vkDestroyFramebuffer(VkDevice device, VkFramebuffer framebuffer, const VkAllocationCallbacks* allocator) { Fsr3ForgetFramebuffer(framebuffer); FovForgetFramebuffer(device, framebuffer); const auto note = State().vr_note_destroy_framebuffer.load(std::memory_order_acquire); if (note != nullptr && framebuffer != VK_NULL_HANDLE) { note(framebuffer); } const PFN_vkDestroyFramebuffer host_destroy = HostDispatchForDevice(device).destroy_framebuffer; if (host_destroy != nullptr) { host_destroy(device, framebuffer, allocator); } } VKAPI_ATTR VkResult VKAPI_CALL vkCreateRenderPass( VkDevice device, const VkRenderPassCreateInfo* create_info, const VkAllocationCallbacks* allocator, VkRenderPass* render_pass) { const PFN_vkCreateRenderPass host_create = HostDispatchForDevice(device).create_render_pass; if (host_create == nullptr) { return VK_ERROR_INITIALIZATION_FAILED; } const VkResult result = Fsr3CreateRenderPass(device, create_info, allocator, render_pass); if(result==VK_SUCCESS && render_pass)Fsr3RecordDiagnosticPass(*render_pass,create_info); const auto record = State().vr_record_render_pass.load(std::memory_order_acquire); if (result == VK_SUCCESS && record != nullptr) { record(*render_pass, create_info); } return result; } VKAPI_ATTR void VKAPI_CALL vkDestroyRenderPass(VkDevice device, VkRenderPass render_pass, const VkAllocationCallbacks* allocator) { Fsr3ForgetPass(render_pass); FovForgetRenderPass(render_pass); const PFN_vkDestroyRenderPass host_destroy = HostDispatchForDevice(device).destroy_render_pass; if (host_destroy != nullptr) { host_destroy(device, render_pass, allocator); } } VKAPI_ATTR void VKAPI_CALL vkCmdBeginRenderPass(VkCommandBuffer command_buffer, const VkRenderPassBeginInfo* begin_info, VkSubpassContents contents) { const auto note = State().vr_note_render_pass_begin.load(std::memory_order_acquire); if (note != nullptr) { note(command_buffer, begin_info); } const PFN_vkCmdBeginRenderPass host_begin = HostDispatchForCommandBuffer(command_buffer).cmd_begin_render_pass; if (host_begin != nullptr) { if (begin_info) { auto adjusted = *begin_info; adjusted.framebuffer = FovFramebufferForBegin(HostDispatchForCommandBuffer(command_buffer).device, begin_info); FovTraceBegin(command_buffer,HostDispatchForCommandBuffer(command_buffer).device,begin_info,adjusted.framebuffer!=begin_info->framebuffer,contents==VK_SUBPASS_CONTENTS_INLINE); Fsr3SceneBegin(HostDispatchForCommandBuffer(command_buffer).device,command_buffer,begin_info,contents); adjusted.renderPass=Fsr3RenderPassForBegin(HostDispatchForCommandBuffer(command_buffer).device,begin_info->renderPass); host_begin(command_buffer, &adjusted, contents); FovTraceDrawScope(command_buffer,HostDispatchForCommandBuffer(command_buffer).device,true); } else { host_begin(command_buffer, begin_info, contents); } } } VKAPI_ATTR void VKAPI_CALL vkCmdDraw(VkCommandBuffer command,uint32_t vertex_count, uint32_t instance_count,uint32_t first_vertex,uint32_t first_instance) { const auto host=HostDispatchForCommandBuffer(command).cmd_draw; if(host){Fsr3NoteDraw(command);host(command,vertex_count,instance_count,first_vertex,first_instance);} } VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndexed(VkCommandBuffer command,uint32_t count, uint32_t instances,uint32_t first,int32_t offset,uint32_t first_instance) { const auto host=HostDispatchForCommandBuffer(command).cmd_draw_indexed; if(host){Fsr3NoteDraw(command);host(command,count,instances,first,offset,first_instance);} } VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndirect(VkCommandBuffer command,VkBuffer buffer, VkDeviceSize offset,uint32_t count,uint32_t stride) { const auto host=HostDispatchForCommandBuffer(command).cmd_draw_indirect; if(host){Fsr3NoteDraw(command);host(command,buffer,offset,count,stride);} } VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndexedIndirect(VkCommandBuffer command,VkBuffer buffer, VkDeviceSize offset,uint32_t count,uint32_t stride) { const auto host=HostDispatchForCommandBuffer(command).cmd_draw_indexed_indirect; if(host){Fsr3NoteDraw(command);host(command,buffer,offset,count,stride);} } VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndirectCount(VkCommandBuffer command,VkBuffer buffer, VkDeviceSize offset,VkBuffer count_buffer,VkDeviceSize count_offset,uint32_t max_count,uint32_t stride) { const auto host=HostDispatchForCommandBuffer(command).cmd_draw_indirect_count; if(host){Fsr3NoteDraw(command);host(command,buffer,offset,count_buffer,count_offset,max_count,stride);} } VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndexedIndirectCount(VkCommandBuffer command,VkBuffer buffer, VkDeviceSize offset,VkBuffer count_buffer,VkDeviceSize count_offset,uint32_t max_count,uint32_t stride) { const auto host=HostDispatchForCommandBuffer(command).cmd_draw_indexed_indirect_count; if(host){Fsr3NoteDraw(command);host(command,buffer,offset,count_buffer,count_offset,max_count,stride);} } VKAPI_ATTR void VKAPI_CALL vkCmdBindPipeline(VkCommandBuffer command, VkPipelineBindPoint point,VkPipeline pipeline) { const auto& dispatch=HostDispatchForCommandBuffer(command); const auto host=dispatch.cmd_bind_pipeline; if(host){host(command,point,pipeline);Fsr3NotePipeline(dispatch.device,command,point,pipeline);} } VKAPI_ATTR void VKAPI_CALL vkDestroyPipeline(VkDevice device,VkPipeline pipeline, const VkAllocationCallbacks* allocator) { Fsr3ForgetPipeline(pipeline); const auto host=HostDispatchForDevice(device).destroy_pipeline; if(host)host(device,pipeline,allocator); } VKAPI_ATTR void VKAPI_CALL vkDestroyShaderModule(VkDevice device,VkShaderModule shader, const VkAllocationCallbacks* allocator) { Fsr3ForgetShader(shader); const auto destroy=HostDispatchForDevice(device).destroy_shader_module; if(destroy)destroy(device,shader,allocator); } VKAPI_ATTR void VKAPI_CALL vkCmdEndRenderPass(VkCommandBuffer command_buffer) { const auto& dispatch=HostDispatchForCommandBuffer(command_buffer); const auto device=dispatch.device; const auto end=dispatch.cmd_end_render_pass; if(end) {FovTraceDrawScope(command_buffer,device,false);end(command_buffer);Fsr3SceneEnd(device,command_buffer);FovTraceEnd(command_buffer,device);} } VKAPI_ATTR VkResult VKAPI_CALL vkCreateCommandPool( VkDevice device, const VkCommandPoolCreateInfo *info, const VkAllocationCallbacks *allocator, VkCommandPool *pool) { const auto create = reinterpret_cast( HostDeviceProc(device, "vkCreateCommandPool")); if (!create) return VK_ERROR_INITIALIZATION_FAILED; const VkResult result = create(device, info, allocator, pool); static const auto note = ResolveProcessFunction( "mocktail_vr_xr_command_pool"); if (result == VK_SUCCESS && note && info && pool) note(device, *pool, info->queueFamilyIndex); return result; } VKAPI_ATTR void VKAPI_CALL vkCmdPipelineBarrier( VkCommandBuffer command, VkPipelineStageFlags src, VkPipelineStageFlags dst, VkDependencyFlags flags, uint32_t memory_count, const VkMemoryBarrier *memory, uint32_t buffer_count, const VkBufferMemoryBarrier *buffers, uint32_t image_count, const VkImageMemoryBarrier *images) { const auto barrier = HostDispatchForCommandBuffer(command).cmd_pipeline_barrier; if (!barrier) return; static const auto normalize_batch = ResolveProcessFunction( "mocktail_vr_xr_desktop_barriers"); if (!normalize_batch && !DesktopBarrierNormalizer()) { barrier(command, src, dst, flags, memory_count, memory, buffer_count, buffers, image_count, images); return; } mocktail::graphics::internal::WithDesktopQueueBarriers(image_count, images, [&](unsigned count, VkImageMemoryBarrier* adjusted) { if (normalize_batch) normalize_batch(command, count, adjusted); else NormalizeDesktopQueueBarriers(command, count, adjusted); }, [&](const VkImageMemoryBarrier* adjusted) { barrier(command, src, dst, flags, memory_count, memory, buffer_count, buffers, image_count, adjusted); }); } static void ForwardVrBarrier2(VkCommandBuffer command, const VkDependencyInfo *info, PFN_vkCmdPipelineBarrier2 barrier) { if (!barrier || !info) return; static const auto normalize_batch = ResolveProcessFunction( "mocktail_vr_xr_desktop_barriers2"); if (!normalize_batch && !DesktopBarrierNormalizer()) { barrier(command, info); return; } mocktail::graphics::internal::WithDesktopQueueBarriers( info->imageMemoryBarrierCount, info->pImageMemoryBarriers, [&](unsigned count, VkImageMemoryBarrier2* adjusted) { if (normalize_batch) normalize_batch(command, count, adjusted); else NormalizeDesktopQueueBarriers(command, count, adjusted); }, [&](const VkImageMemoryBarrier2* adjusted) { if (adjusted == info->pImageMemoryBarriers) barrier(command, info); else { auto adjusted_info = *info; adjusted_info.pImageMemoryBarriers = adjusted; barrier(command, &adjusted_info); } }); } VKAPI_ATTR void VKAPI_CALL vkCmdPipelineBarrier2(VkCommandBuffer command, const VkDependencyInfo *info) { ForwardVrBarrier2(command, info, HostDispatchForCommandBuffer(command).cmd_pipeline_barrier2); } VKAPI_ATTR void VKAPI_CALL vkCmdPipelineBarrier2KHR( VkCommandBuffer command, const VkDependencyInfo *info) { ForwardVrBarrier2(command, info, HostDispatchForCommandBuffer(command).cmd_pipeline_barrier2_khr); } VKAPI_ATTR std::int32_t mocktail_vulkan_run_queue_synchronized( VkDevice device, VkQueue queue, std::int32_t (*operation)(void *), void *context) { const QueueCallGuard guard(device, queue); return operation(context); } VKAPI_ATTR VkResult VKAPI_CALL mocktail_vulkan_submit_synchronized( PFN_vkQueueSubmit raw, VkDevice device, VkQueue queue, uint32_t count, const VkSubmitInfo *submits, VkFence fence) { if (!raw) return VK_ERROR_INITIALIZATION_FAILED; const QueueCallGuard guard(device, queue); return raw(queue, count, submits, fence); } VKAPI_ATTR VkResult VKAPI_CALL mocktail_vulkan_idle_synchronized(PFN_vkDeviceWaitIdle raw, VkDevice device) { if (!raw) return VK_ERROR_INITIALIZATION_FAILED; const DeviceIdleGuard guard(device); return raw(device); } } // extern "C"