mirror of
https://github.com/InstanceBrick/nightcap.git
synced 2026-10-18 10:00:46 +02:00
Integrate Frame OpenXR input, SteamVR video controls, gaze foveation, native audio, frame pacing and source-based packaging.
3821 lines
163 KiB
C++
3821 lines
163 KiB
C++
// Modified by vii from komaruworld/mocktail. See README "About this fork".
|
|
// Android Vulkan loader ABI -> host Vulkan loader + SDL3 WSI.
|
|
|
|
#include <dlfcn.h>
|
|
#include <pthread.h>
|
|
#include <time.h>
|
|
#include <vulkan/vulkan.h>
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <cstddef>
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <deque>
|
|
#include <fstream>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <shared_mutex>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#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<PFN_vkGetDeviceProcAddr> host_get_device_proc_addr{nullptr};
|
|
std::vector<DeviceDispatch> device_dispatches;
|
|
std::vector<QueueBinding> queue_bindings;
|
|
std::vector<CommandBufferBinding> 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<NotePresentFn> note_present{nullptr};
|
|
std::atomic<NoteHostPresentBeginFn> note_host_present_begin{nullptr};
|
|
std::atomic<NoteHostPresentEndFn> note_host_present_end{nullptr};
|
|
std::atomic<NoteVrPresentBeginFn> note_vr_present_begin{nullptr};
|
|
std::atomic<VrXrCreateInstanceFn> vr_create_instance{nullptr};
|
|
std::atomic<VrXrCreateDeviceFn> vr_create_device{nullptr};
|
|
std::atomic<VrXrNoteDeviceDestroyedFn> vr_note_device_destroyed{nullptr};
|
|
std::atomic<VrXrNoteQueueFn> vr_note_queue{nullptr};
|
|
std::atomic<VrXrRecordImageFn> vr_record_image{nullptr};
|
|
std::atomic<VrXrRecordImageViewFn> vr_record_image_view{nullptr};
|
|
std::atomic<VrXrRecordFramebufferFn> vr_record_framebuffer{nullptr};
|
|
std::atomic<VrXrRecordRenderPassFn> vr_record_render_pass{nullptr};
|
|
std::atomic<VrXrNoteDestroyImageFn> vr_note_destroy_image{nullptr};
|
|
std::atomic<VrXrNoteDestroyImageViewFn> vr_note_destroy_image_view{nullptr};
|
|
std::atomic<VrXrNoteDestroyFramebufferFn>
|
|
vr_note_destroy_framebuffer{nullptr};
|
|
std::atomic<VrXrNoteRenderPassBeginFn> vr_note_render_pass_begin{nullptr};
|
|
std::atomic<VrXrNotePresentFn> vr_note_present{nullptr};
|
|
std::atomic<NoteVulkanCallBeginFn> note_vulkan_call_begin{nullptr};
|
|
std::atomic<NoteVulkanCallEndFn> note_vulkan_call_end{nullptr};
|
|
std::atomic<NoteSurfaceOutOfDateFn> note_surface_out_of_date{nullptr};
|
|
bool extent_translation_logged = false;
|
|
bool present_policy_logged = false;
|
|
bool initialized = false;
|
|
};
|
|
|
|
static std::atomic<bool> g_vulkan_call_observation_active{false};
|
|
|
|
AdapterState& State() {
|
|
static AdapterState state;
|
|
return state;
|
|
}
|
|
|
|
template <typename Function>
|
|
Function ResolveProcessFunction(const char* name) {
|
|
return reinterpret_cast<Function>(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<bool> g_queue_sharing{false};
|
|
|
|
bool QueueSharingActive() {
|
|
if (g_queue_sharing.load(std::memory_order_acquire))
|
|
return true;
|
|
static const auto requested = ResolveProcessFunction<bool (*)()>(
|
|
"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<std::shared_mutex> DeviceIdleGate(VkDevice device) {
|
|
static std::mutex registry_mutex;
|
|
static std::unordered_map<VkDevice, std::shared_ptr<std::shared_mutex>>
|
|
registry;
|
|
std::lock_guard<std::mutex> lock(registry_mutex);
|
|
auto &gate = registry[device];
|
|
if (!gate)
|
|
gate = std::make_shared<std::shared_mutex>();
|
|
return gate;
|
|
}
|
|
std::shared_ptr<std::mutex> QueueCallMutex(VkQueue queue) {
|
|
static std::mutex registry_mutex;
|
|
static std::unordered_map<VkQueue, std::shared_ptr<std::mutex>> registry;
|
|
std::lock_guard<std::mutex> lock(registry_mutex);
|
|
auto &mutex = registry[queue];
|
|
if (!mutex)
|
|
mutex = std::make_shared<std::mutex>();
|
|
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<std::shared_mutex>(*gate_);
|
|
mutex_ = QueueCallMutex(queue);
|
|
lock_ = std::unique_lock<std::mutex>(*mutex_);
|
|
}
|
|
|
|
private:
|
|
std::shared_ptr<std::shared_mutex> gate_;
|
|
std::shared_lock<std::shared_mutex> gate_lock_;
|
|
std::shared_ptr<std::mutex> mutex_;
|
|
std::unique_lock<std::mutex> 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<std::shared_mutex>(*gate_);
|
|
}
|
|
|
|
private:
|
|
std::shared_ptr<std::shared_mutex> gate_;
|
|
std::unique_lock<std::shared_mutex> lock_;
|
|
};
|
|
|
|
void NoteVrSubmitted(VkQueue queue, uint32_t count,
|
|
const VkSubmitInfo *submits) {
|
|
static const auto note = ResolveProcessFunction<void (*)(
|
|
VkQueue, unsigned, const VkCommandBuffer *)>("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<void (*)(
|
|
VkQueue, unsigned, const VkCommandBuffer *)>("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<std::mutex> lock(state.mutex);
|
|
if (state.initialized) {
|
|
return true;
|
|
}
|
|
|
|
const auto register_submit =
|
|
ResolveProcessFunction<void (*)(VkResult(VKAPI_PTR *)(
|
|
PFN_vkQueueSubmit, VkDevice, VkQueue, uint32_t, const VkSubmitInfo *,
|
|
VkFence))>("mocktail_vr_xr_queue_submit_adapter");
|
|
if (register_submit)
|
|
register_submit(mocktail_vulkan_submit_synchronized);
|
|
const auto register_operation = ResolveProcessFunction<void (*)(
|
|
std::int32_t (*)(VkDevice, VkQueue, std::int32_t (*)(void *), void *))>(
|
|
"mocktail_vr_xr_queue_operation_adapter");
|
|
if (register_operation)
|
|
register_operation(mocktail_vulkan_run_queue_synchronized);
|
|
const auto register_idle = ResolveProcessFunction<void (*)(
|
|
VkResult(VKAPI_PTR *)(PFN_vkDeviceWaitIdle, VkDevice))>(
|
|
"mocktail_vr_xr_device_idle_adapter");
|
|
if (register_idle)
|
|
register_idle(mocktail_vulkan_idle_synchronized);
|
|
if(FovTraceEnabled()) {
|
|
const auto report=ResolveProcessFunction<void(*)(void(*)(VkDevice,VkQueue))>("mocktail_vr_xr_pass_timing_reporter");
|
|
if(report)report(FovTraceComplete);
|
|
}
|
|
|
|
const BackendWindowFn backend_window =
|
|
ResolveProcessFunction<BackendWindowFn>("mocktail_window_backend_window");
|
|
const UsesDirectVulkanFn uses_direct_vulkan =
|
|
ResolveProcessFunction<UsesDirectVulkanFn>(
|
|
"mocktail_window_uses_direct_vulkan");
|
|
state.note_present.store(ResolveProcessFunction<NotePresentFn>(
|
|
"mocktail_window_note_vulkan_present"),
|
|
std::memory_order_release);
|
|
state.note_host_present_begin.store(
|
|
ResolveProcessFunction<NoteHostPresentBeginFn>(
|
|
"mocktail_window_note_vulkan_host_present_begin"),
|
|
std::memory_order_release);
|
|
state.note_host_present_end.store(
|
|
ResolveProcessFunction<NoteHostPresentEndFn>(
|
|
"mocktail_window_note_vulkan_host_present_end"),
|
|
std::memory_order_release);
|
|
state.note_vr_present_begin.store(
|
|
ResolveProcessFunction<NoteVrPresentBeginFn>(
|
|
"mocktail_vr_note_host_present_begin"),
|
|
std::memory_order_release);
|
|
state.vr_create_instance.store(
|
|
ResolveProcessFunction<VrXrCreateInstanceFn>(
|
|
"mocktail_vr_xr_create_vulkan_instance"),
|
|
std::memory_order_release);
|
|
state.vr_create_device.store(
|
|
ResolveProcessFunction<VrXrCreateDeviceFn>(
|
|
"mocktail_vr_xr_create_vulkan_device"),
|
|
std::memory_order_release);
|
|
state.vr_note_device_destroyed.store(
|
|
ResolveProcessFunction<VrXrNoteDeviceDestroyedFn>(
|
|
"mocktail_vr_xr_note_device_destroyed"),
|
|
std::memory_order_release);
|
|
state.vr_note_queue.store(
|
|
ResolveProcessFunction<VrXrNoteQueueFn>("mocktail_vr_xr_note_queue"),
|
|
std::memory_order_release);
|
|
state.vr_record_image.store(
|
|
ResolveProcessFunction<VrXrRecordImageFn>("mocktail_vr_xr_record_image"),
|
|
std::memory_order_release);
|
|
state.vr_record_image_view.store(
|
|
ResolveProcessFunction<VrXrRecordImageViewFn>(
|
|
"mocktail_vr_xr_record_image_view"),
|
|
std::memory_order_release);
|
|
state.vr_record_framebuffer.store(
|
|
ResolveProcessFunction<VrXrRecordFramebufferFn>(
|
|
"mocktail_vr_xr_record_framebuffer"),
|
|
std::memory_order_release);
|
|
state.vr_record_render_pass.store(
|
|
ResolveProcessFunction<VrXrRecordRenderPassFn>(
|
|
"mocktail_vr_xr_record_render_pass"),
|
|
std::memory_order_release);
|
|
state.vr_note_destroy_image.store(
|
|
ResolveProcessFunction<VrXrNoteDestroyImageFn>(
|
|
"mocktail_vr_xr_note_destroy_image"),
|
|
std::memory_order_release);
|
|
state.vr_note_destroy_image_view.store(
|
|
ResolveProcessFunction<VrXrNoteDestroyImageViewFn>(
|
|
"mocktail_vr_xr_note_destroy_image_view"),
|
|
std::memory_order_release);
|
|
state.vr_note_destroy_framebuffer.store(
|
|
ResolveProcessFunction<VrXrNoteDestroyFramebufferFn>(
|
|
"mocktail_vr_xr_note_destroy_framebuffer"),
|
|
std::memory_order_release);
|
|
state.vr_note_render_pass_begin.store(
|
|
ResolveProcessFunction<VrXrNoteRenderPassBeginFn>(
|
|
"mocktail_vr_xr_note_render_pass_begin"),
|
|
std::memory_order_release);
|
|
state.vr_note_present.store(
|
|
ResolveProcessFunction<VrXrNotePresentFn>("mocktail_vr_xr_note_present"),
|
|
std::memory_order_release);
|
|
const auto call_begin = ResolveProcessFunction<NoteVulkanCallBeginFn>(
|
|
"mocktail_window_note_vulkan_call_begin");
|
|
const auto call_end = ResolveProcessFunction<NoteVulkanCallEndFn>(
|
|
"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<NoteSurfaceOutOfDateFn>(
|
|
"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<PFN_vkGetInstanceProcAddr>(
|
|
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<bool> 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<uint64_t> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<PFN_vkQueuePresentKHR>(
|
|
get_device_proc_addr(device, "vkQueuePresentKHR"));
|
|
dispatch.queue_submit = reinterpret_cast<PFN_vkQueueSubmit>(
|
|
get_device_proc_addr(device, "vkQueueSubmit"));
|
|
dispatch.queue_submit2 = reinterpret_cast<PFN_vkQueueSubmit2>(
|
|
get_device_proc_addr(device, "vkQueueSubmit2"));
|
|
dispatch.queue_submit2_khr = reinterpret_cast<PFN_vkQueueSubmit2KHR>(
|
|
get_device_proc_addr(device, "vkQueueSubmit2KHR"));
|
|
dispatch.queue_bind_sparse = reinterpret_cast<PFN_vkQueueBindSparse>(
|
|
get_device_proc_addr(device, "vkQueueBindSparse"));
|
|
dispatch.queue_wait_idle = reinterpret_cast<PFN_vkQueueWaitIdle>(
|
|
get_device_proc_addr(device, "vkQueueWaitIdle"));
|
|
dispatch.acquire_next_image = reinterpret_cast<PFN_vkAcquireNextImageKHR>(
|
|
get_device_proc_addr(device, "vkAcquireNextImageKHR"));
|
|
dispatch.acquire_next_image2 = reinterpret_cast<PFN_vkAcquireNextImage2KHR>(
|
|
get_device_proc_addr(device, "vkAcquireNextImage2KHR"));
|
|
dispatch.wait_for_fences = reinterpret_cast<PFN_vkWaitForFences>(
|
|
get_device_proc_addr(device, "vkWaitForFences"));
|
|
dispatch.wait_semaphores = reinterpret_cast<PFN_vkWaitSemaphores>(
|
|
get_device_proc_addr(device, "vkWaitSemaphores"));
|
|
dispatch.wait_semaphores_khr = reinterpret_cast<PFN_vkWaitSemaphoresKHR>(
|
|
get_device_proc_addr(device, "vkWaitSemaphoresKHR"));
|
|
dispatch.device_wait_idle = reinterpret_cast<PFN_vkDeviceWaitIdle>(
|
|
get_device_proc_addr(device, "vkDeviceWaitIdle"));
|
|
dispatch.reset_fences = reinterpret_cast<PFN_vkResetFences>(
|
|
get_device_proc_addr(device, "vkResetFences"));
|
|
dispatch.reset_command_pool = reinterpret_cast<PFN_vkResetCommandPool>(
|
|
get_device_proc_addr(device, "vkResetCommandPool"));
|
|
dispatch.get_query_pool_results =
|
|
reinterpret_cast<PFN_vkGetQueryPoolResults>(
|
|
get_device_proc_addr(device, "vkGetQueryPoolResults"));
|
|
dispatch.allocate_command_buffers =
|
|
reinterpret_cast<PFN_vkAllocateCommandBuffers>(
|
|
get_device_proc_addr(device, "vkAllocateCommandBuffers"));
|
|
dispatch.free_command_buffers = reinterpret_cast<PFN_vkFreeCommandBuffers>(
|
|
get_device_proc_addr(device, "vkFreeCommandBuffers"));
|
|
dispatch.destroy_command_pool = reinterpret_cast<PFN_vkDestroyCommandPool>(
|
|
get_device_proc_addr(device, "vkDestroyCommandPool"));
|
|
dispatch.begin_command_buffer = reinterpret_cast<PFN_vkBeginCommandBuffer>(
|
|
get_device_proc_addr(device, "vkBeginCommandBuffer"));
|
|
dispatch.end_command_buffer = reinterpret_cast<PFN_vkEndCommandBuffer>(
|
|
get_device_proc_addr(device, "vkEndCommandBuffer"));
|
|
dispatch.reset_command_buffer = reinterpret_cast<PFN_vkResetCommandBuffer>(
|
|
get_device_proc_addr(device, "vkResetCommandBuffer"));
|
|
dispatch.create_image = reinterpret_cast<PFN_vkCreateImage>(
|
|
get_device_proc_addr(device, "vkCreateImage"));
|
|
dispatch.destroy_image = reinterpret_cast<PFN_vkDestroyImage>(
|
|
get_device_proc_addr(device, "vkDestroyImage"));
|
|
dispatch.create_image_view = reinterpret_cast<PFN_vkCreateImageView>(
|
|
get_device_proc_addr(device, "vkCreateImageView"));
|
|
dispatch.destroy_image_view = reinterpret_cast<PFN_vkDestroyImageView>(
|
|
get_device_proc_addr(device, "vkDestroyImageView"));
|
|
dispatch.create_framebuffer = reinterpret_cast<PFN_vkCreateFramebuffer>(
|
|
get_device_proc_addr(device, "vkCreateFramebuffer"));
|
|
dispatch.destroy_framebuffer = reinterpret_cast<PFN_vkDestroyFramebuffer>(
|
|
get_device_proc_addr(device, "vkDestroyFramebuffer"));
|
|
dispatch.create_render_pass = reinterpret_cast<PFN_vkCreateRenderPass>(
|
|
get_device_proc_addr(device, "vkCreateRenderPass"));
|
|
dispatch.destroy_render_pass = reinterpret_cast<PFN_vkDestroyRenderPass>(
|
|
get_device_proc_addr(device, "vkDestroyRenderPass"));
|
|
dispatch.cmd_pipeline_barrier = reinterpret_cast<PFN_vkCmdPipelineBarrier>(
|
|
get_device_proc_addr(device, "vkCmdPipelineBarrier"));
|
|
dispatch.cmd_pipeline_barrier2 = reinterpret_cast<PFN_vkCmdPipelineBarrier2>(
|
|
get_device_proc_addr(device, "vkCmdPipelineBarrier2"));
|
|
dispatch.cmd_pipeline_barrier2_khr = reinterpret_cast<PFN_vkCmdPipelineBarrier2KHR>(
|
|
get_device_proc_addr(device, "vkCmdPipelineBarrier2KHR"));
|
|
dispatch.cmd_begin_render_pass = reinterpret_cast<PFN_vkCmdBeginRenderPass>(
|
|
get_device_proc_addr(device, "vkCmdBeginRenderPass"));
|
|
dispatch.cmd_end_render_pass = reinterpret_cast<PFN_vkCmdEndRenderPass>(
|
|
get_device_proc_addr(device, "vkCmdEndRenderPass"));
|
|
dispatch.cmd_bind_pipeline = reinterpret_cast<PFN_vkCmdBindPipeline>(
|
|
get_device_proc_addr(device, "vkCmdBindPipeline"));
|
|
dispatch.cmd_draw = reinterpret_cast<PFN_vkCmdDraw>(
|
|
get_device_proc_addr(device, "vkCmdDraw"));
|
|
dispatch.cmd_draw_indexed = reinterpret_cast<PFN_vkCmdDrawIndexed>(
|
|
get_device_proc_addr(device, "vkCmdDrawIndexed"));
|
|
dispatch.cmd_draw_indirect = reinterpret_cast<PFN_vkCmdDrawIndirect>(
|
|
get_device_proc_addr(device, "vkCmdDrawIndirect"));
|
|
dispatch.cmd_draw_indexed_indirect = reinterpret_cast<PFN_vkCmdDrawIndexedIndirect>(
|
|
get_device_proc_addr(device, "vkCmdDrawIndexedIndirect"));
|
|
dispatch.cmd_draw_indirect_count = reinterpret_cast<PFN_vkCmdDrawIndirectCount>(
|
|
get_device_proc_addr(device, "vkCmdDrawIndirectCount"));
|
|
dispatch.cmd_draw_indexed_indirect_count = reinterpret_cast<PFN_vkCmdDrawIndexedIndirectCount>(
|
|
get_device_proc_addr(device, "vkCmdDrawIndexedIndirectCount"));
|
|
dispatch.destroy_pipeline = reinterpret_cast<PFN_vkDestroyPipeline>(
|
|
get_device_proc_addr(device, "vkDestroyPipeline"));
|
|
dispatch.destroy_shader_module = reinterpret_cast<PFN_vkDestroyShaderModule>(
|
|
get_device_proc_addr(device, "vkDestroyShaderModule"));
|
|
if(!dispatch.cmd_draw_indirect_count)dispatch.cmd_draw_indirect_count=reinterpret_cast<PFN_vkCmdDrawIndirectCount>(
|
|
get_device_proc_addr(device,"vkCmdDrawIndirectCountKHR"));
|
|
if(!dispatch.cmd_draw_indirect_count)dispatch.cmd_draw_indirect_count=reinterpret_cast<PFN_vkCmdDrawIndirectCount>(
|
|
get_device_proc_addr(device,"vkCmdDrawIndirectCountAMD"));
|
|
if(!dispatch.cmd_draw_indexed_indirect_count)dispatch.cmd_draw_indexed_indirect_count=reinterpret_cast<PFN_vkCmdDrawIndexedIndirectCount>(
|
|
get_device_proc_addr(device,"vkCmdDrawIndexedIndirectCountKHR"));
|
|
if(!dispatch.cmd_draw_indexed_indirect_count)dispatch.cmd_draw_indexed_indirect_count=reinterpret_cast<PFN_vkCmdDrawIndexedIndirectCount>(
|
|
get_device_proc_addr(device,"vkCmdDrawIndexedIndirectCountAMD"));
|
|
|
|
|
|
AdapterState& state = State();
|
|
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<VkExtensionProperties> AndroidVisibleExtensions(
|
|
const char* layer_name, VkResult* result) {
|
|
std::vector<VkExtensionProperties> output;
|
|
const auto host_enumerate =
|
|
reinterpret_cast<PFN_vkEnumerateInstanceExtensionProperties>(
|
|
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<PFN_vkVoidFunction>(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<PFN_vkVoidFunction>(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<const VkBaseInStructure*>(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<const VkPhysicalDeviceFeatures2*>(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<const VkPhysicalDeviceVulkan12Features*>(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<const VkPhysicalDeviceHostQueryResetFeatures*>(
|
|
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<std::uint64_t> device_lost_count{0};
|
|
static std::atomic<std::uint64_t> other_error_count{0};
|
|
std::atomic<std::uint64_t>& 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<std::int32_t>(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<int>(result),
|
|
result == VK_ERROR_DEVICE_LOST ? 1U : 0U,
|
|
static_cast<unsigned long long>(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<std::uint64_t>(ts.tv_sec) * 1000000000ULL +
|
|
static_cast<std::uint64_t>(ts.tv_nsec);
|
|
}
|
|
|
|
struct FpsWaitTrace {
|
|
const char* name = nullptr;
|
|
std::atomic<std::uint64_t> samples{0};
|
|
std::atomic<std::uint64_t> total_ns{0};
|
|
std::atomic<std::uint64_t> max_ns{0};
|
|
std::atomic<std::uint64_t> window_start_ns{0};
|
|
// Longest time between consecutive call starts; for present, a frame time.
|
|
std::atomic<std::uint64_t> max_gap_ns{0};
|
|
std::atomic<std::uint64_t> 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<unsigned long long>(count),
|
|
static_cast<unsigned long long>(total / count / 1000ULL),
|
|
static_cast<unsigned long long>(peak / 1000ULL),
|
|
static_cast<unsigned long long>(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<VkResult (*)(
|
|
VkQueue, VkDevice, const VkPresentInfoKHR *, VkSemaphore *)>(
|
|
"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<std::int32_t>(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<std::uint64_t> 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<double>(present_start_ns - previous) / 1e6);
|
|
}
|
|
mocktail::graphics::FrameCpuSample cpu;
|
|
if (cpu_sampler.Sample(static_cast<std::uint64_t>(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<PFN_vkCreateGraphicsPipelines>(
|
|
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<PFN_vkCreateComputePipelines>(
|
|
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<PFN_vkCreateShaderModule>(
|
|
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<std::int64_t>(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<PFN_vkCreatePipelineCache>(
|
|
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<std::int64_t>(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<PFN_vkAllocateMemory>(
|
|
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<std::int64_t>(
|
|
allocate_info->allocationSize)
|
|
: 0);
|
|
scope.Arg("type", allocate_info != nullptr
|
|
? static_cast<std::int64_t>(
|
|
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<PFN_vkVoidFunction>(ProfiledCreateShaderModule);
|
|
if (std::strcmp(name, "vkCreateGraphicsPipelines") == 0 && (Fsr3ShaderDumpEnabled() || Fsr3InputsAvailable()))
|
|
return reinterpret_cast<PFN_vkVoidFunction>(ProfiledCreateGraphicsPipelines);
|
|
if (mocktail::graphics::ActiveProfileTrace() == nullptr) return nullptr;
|
|
if (std::strcmp(name, "vkCreateGraphicsPipelines") == 0) {
|
|
return reinterpret_cast<PFN_vkVoidFunction>(
|
|
ProfiledCreateGraphicsPipelines);
|
|
}
|
|
if (std::strcmp(name, "vkCreateComputePipelines") == 0) {
|
|
return reinterpret_cast<PFN_vkVoidFunction>(
|
|
ProfiledCreateComputePipelines);
|
|
}
|
|
if (std::strcmp(name, "vkCreateShaderModule") == 0) {
|
|
return reinterpret_cast<PFN_vkVoidFunction>(ProfiledCreateShaderModule);
|
|
}
|
|
if (std::strcmp(name, "vkCreatePipelineCache") == 0) {
|
|
return reinterpret_cast<PFN_vkVoidFunction>(ProfiledCreatePipelineCache);
|
|
}
|
|
if (std::strcmp(name, "vkAllocateMemory") == 0) {
|
|
return reinterpret_cast<PFN_vkVoidFunction>(ProfiledAllocateMemory);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
template <typename Function>
|
|
Function HostPhysicalDeviceProc(const char* name, const char* alias) {
|
|
VkInstance instance = VK_NULL_HANDLE;
|
|
{
|
|
AdapterState& state = State();
|
|
std::lock_guard<std::mutex> 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<Function>(proc);
|
|
}
|
|
|
|
bool Etc2EmulatedPhysicalDevice(VkPhysicalDevice physical_device) {
|
|
return State().etc2.PhysicalDeviceNeedsEmulation(
|
|
physical_device, HostPhysicalDeviceProc<PFN_vkGetPhysicalDeviceFeatures>(
|
|
"vkGetPhysicalDeviceFeatures", nullptr));
|
|
}
|
|
|
|
void ReleaseEtc2PoolCommandBuffers(VkDevice device,
|
|
VkCommandPool command_pool) {
|
|
std::vector<VkCommandBuffer> command_buffers;
|
|
{
|
|
AdapterState& state = State();
|
|
std::lock_guard<std::mutex> 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<std::vector<VkSemaphore>> wait_semaphores;
|
|
std::deque<std::vector<VkPipelineStageFlags>> wait_stages;
|
|
std::deque<std::vector<std::uint64_t>> wait_values;
|
|
std::deque<std::vector<std::uint64_t>> signal_values;
|
|
std::deque<VkTimelineSemaphoreSubmitInfo> timelines;
|
|
std::deque<std::vector<VkSemaphoreSubmitInfo>> wait_infos;
|
|
std::vector<VkSubmitInfo> rewritten;
|
|
std::vector<VkSubmitInfo2> 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<VkSemaphore>& semaphores = rewrite->wait_semaphores.emplace_back(
|
|
original.pWaitSemaphores,
|
|
original.pWaitSemaphores + original.waitSemaphoreCount);
|
|
semaphores.push_back(wait.semaphore);
|
|
std::vector<VkPipelineStageFlags>& 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<std::uint64_t>& 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<std::uint32_t>(values.size());
|
|
timeline.pWaitSemaphoreValues = values.data();
|
|
if (original.signalSemaphoreCount != 0) {
|
|
std::vector<std::uint64_t>& signals =
|
|
rewrite->signal_values.emplace_back(original.signalSemaphoreCount, 0);
|
|
timeline.signalSemaphoreValueCount =
|
|
static_cast<std::uint32_t>(signals.size());
|
|
timeline.pSignalSemaphoreValues = signals.data();
|
|
}
|
|
|
|
VkSubmitInfo& target = rewrite->rewritten[index];
|
|
target.pNext = &timeline;
|
|
target.waitSemaphoreCount = static_cast<std::uint32_t>(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<VkCommandBuffer> 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<std::uint32_t>(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<VkSemaphoreSubmitInfo>& 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<std::uint32_t>(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<DesktopQueueNormalizer>(
|
|
"mocktail_vr_xr_desktop_barrier");
|
|
return normalize;
|
|
}
|
|
|
|
template <class Barrier>
|
|
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<std::string> 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<std::string> 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<const char*> 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<std::uint32_t>(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<PFN_vkEnumerateInstanceVersion>(
|
|
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<PFN_vkCreateInstance>(
|
|
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<std::mutex> lock(state.mutex);
|
|
++state.active_instance_count;
|
|
state.latest_instance = *instance;
|
|
state.host_get_device_proc_addr.store(
|
|
reinterpret_cast<PFN_vkGetDeviceProcAddr>(
|
|
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<bool (*)(VkInstance)>(
|
|
"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<PFN_vkDestroyInstance>(
|
|
HostInstanceProc(instance, "vkDestroyInstance"));
|
|
if (host_destroy != nullptr) {
|
|
host_destroy(instance, allocator);
|
|
}
|
|
AdapterState& state = State();
|
|
std::lock_guard<std::mutex> 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<VkResult>(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<VkExtensionProperties> visible =
|
|
AndroidVisibleExtensions(layer_name, &result);
|
|
if (result != VK_SUCCESS) {
|
|
return result;
|
|
}
|
|
if (properties == nullptr) {
|
|
*property_count = static_cast<std::uint32_t>(visible.size());
|
|
return VK_SUCCESS;
|
|
}
|
|
const std::uint32_t capacity = *property_count;
|
|
const std::uint32_t copied =
|
|
std::min(capacity, static_cast<std::uint32_t>(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<PFN_vkEnumerateInstanceLayerProperties>(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<std::mutex> lock(state.mutex);
|
|
state.host_surface_capabilities =
|
|
reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR>(host);
|
|
return host != nullptr ? reinterpret_cast<PFN_vkVoidFunction>(
|
|
vkGetPhysicalDeviceSurfaceCapabilitiesKHR)
|
|
: nullptr;
|
|
}
|
|
if (name != nullptr &&
|
|
std::strcmp(name, "vkGetPhysicalDeviceSurfacePresentModesKHR") == 0) {
|
|
const PFN_vkVoidFunction host = HostInstanceProc(instance, name);
|
|
AdapterState& state = State();
|
|
std::lock_guard<std::mutex> lock(state.mutex);
|
|
state.host_surface_present_modes =
|
|
reinterpret_cast<PFN_vkGetPhysicalDeviceSurfacePresentModesKHR>(host);
|
|
return host != nullptr ? reinterpret_cast<PFN_vkVoidFunction>(
|
|
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<std::mutex> 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<PFN_vkCreateDevice>(
|
|
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<PFN_vkGetPhysicalDeviceProperties>(
|
|
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<PFN_vkGetPhysicalDeviceFeatures>(
|
|
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<VkPhysicalDeviceFeatures2*>(
|
|
reinterpret_cast<const VkPhysicalDeviceFeatures2*>(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<PFN_vkGetPhysicalDeviceFeatures2>(
|
|
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<void*>(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<VkPhysicalDeviceFeatures2*>(
|
|
reinterpret_cast<const VkPhysicalDeviceFeatures2*>(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<PFN_vkGetPhysicalDeviceFeatures>(
|
|
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<PFN_vkGetPhysicalDeviceMemoryProperties>(
|
|
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<PFN_vkGetPhysicalDeviceQueueFamilyProperties>(
|
|
HostInstanceProc(instance,
|
|
"vkGetPhysicalDeviceQueueFamilyProperties"));
|
|
if (host_get_queue_families != nullptr) {
|
|
host_get_queue_families(physical_device, &queue_family_count, nullptr);
|
|
}
|
|
std::vector<VkQueueFamilyProperties> 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<PFN_vkDestroyDevice>(
|
|
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<PFN_vkGetDeviceQueue>(
|
|
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<PFN_vkGetDeviceQueue2>(
|
|
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<PFN_vkCreateSwapchainKHR>(
|
|
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<unsigned (*)(VkDevice, unsigned, unsigned *)>(
|
|
"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<bool (*)()>("mocktail_vr_desktop_enabled");
|
|
if (vr_desktop_enabled && vr_desktop_enabled()) {
|
|
PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR caps;
|
|
{ std::lock_guard<std::mutex> 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<std::mutex> 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<VkPresentModeKHR> 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<VkPresentModeKHR> selected =
|
|
mocktail::graphics::FilterPresentModes(present_policy,
|
|
host_modes);
|
|
if (!selected.empty()) {
|
|
static std::atomic<bool> 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<bool> 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<PFN_vkGetSwapchainImagesKHR>(
|
|
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<VkImage> 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<void (*)(
|
|
VkDevice, VkSwapchainKHR, const VkSwapchainCreateInfoKHR *,
|
|
const VkImage *, unsigned, bool)>(
|
|
"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<PFN_vkDestroySwapchainKHR>(
|
|
HostDeviceProc(device, "vkDestroySwapchainKHR"));
|
|
static const auto record = ResolveProcessFunction<void (*)(VkDevice, VkSwapchainKHR,
|
|
const VkSwapchainCreateInfoKHR*, const VkImage*, unsigned)>("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<void (*)(VkCommandPool)>(
|
|
"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<void (*)(
|
|
VkDevice, VkCommandPool, unsigned, const VkCommandBuffer *)>(
|
|
"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<void (*)(
|
|
VkDevice, VkCommandPool, unsigned, const VkCommandBuffer *)>(
|
|
"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<void (*)(VkDevice, VkCommandPool, unsigned)>(
|
|
"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<void (*)(VkCommandBuffer)>(
|
|
"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<void (*)(VkCommandBuffer)>(
|
|
"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<PFN_vkWaitSemaphores>(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<std::mutex> 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<WindowDimensionFn>("mocktail_window_width");
|
|
const WindowDimensionFn window_height =
|
|
ResolveProcessFunction<WindowDimensionFn>("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<std::uint32_t>(window_width()),
|
|
capabilities->minImageExtent.width, capabilities->maxImageExtent.width);
|
|
capabilities->currentExtent.height = std::clamp(
|
|
static_cast<std::uint32_t>(window_height()),
|
|
capabilities->minImageExtent.height, capabilities->maxImageExtent.height);
|
|
|
|
AdapterState& state = State();
|
|
std::lock_guard<std::mutex> 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<std::mutex> 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<VkPresentModeKHR> 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<VkPresentModeKHR> visible =
|
|
mocktail::graphics::FilterPresentModes(policy, host_modes);
|
|
{
|
|
AdapterState& state = State();
|
|
std::lock_guard<std::mutex> 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<std::uint32_t>(visible.size());
|
|
return VK_SUCCESS;
|
|
}
|
|
const std::uint32_t capacity = *present_mode_count;
|
|
const std::uint32_t copied =
|
|
std::min(capacity, static_cast<std::uint32_t>(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<VkResult (*)(
|
|
VkQueue, const VkPresentInfoKHR*, VkSemaphore*)>(
|
|
"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<PFN_vkGetPhysicalDeviceFeatures>(
|
|
"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<PFN_vkGetPhysicalDeviceFeatures2>(
|
|
"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<PFN_vkGetPhysicalDeviceFormatProperties>(
|
|
"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<PFN_vkGetPhysicalDeviceFormatProperties2>(
|
|
"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<VkFormatProperties3*>(
|
|
reinterpret_cast<const VkFormatProperties3*>(FindFeature(
|
|
properties->pNext, VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3)));
|
|
extended != nullptr) {
|
|
extended->linearTilingFeatures = 0;
|
|
extended->optimalTilingFeatures =
|
|
mocktail::graphics::EmulatedEtc2FormatFeatures(
|
|
static_cast<VkFormatFeatureFlags>(
|
|
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<PFN_vkGetPhysicalDeviceImageFormatProperties>(
|
|
"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<VkImageCreateFlags>(
|
|
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<PFN_vkGetPhysicalDeviceImageFormatProperties2>(
|
|
"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<VkImageCreateFlags>(
|
|
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<PFN_vkCreateCommandPool>(
|
|
HostDeviceProc(device, "vkCreateCommandPool"));
|
|
if (!create)
|
|
return VK_ERROR_INITIALIZATION_FAILED;
|
|
const VkResult result = create(device, info, allocator, pool);
|
|
static const auto note =
|
|
ResolveProcessFunction<void (*)(VkDevice, VkCommandPool, unsigned)>(
|
|
"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<void (*)(
|
|
VkCommandBuffer, unsigned, VkImageMemoryBarrier*)>(
|
|
"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<void (*)(
|
|
VkCommandBuffer, unsigned, VkImageMemoryBarrier2*)>(
|
|
"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"
|