Files
InstanceBrick--nightcap/src/graphics/bionic_vulkan_loader_adapter.cc
T
vii 3bba795ea3 feat(profile): add --profile Chrome trace of Vulkan adapter work
Records presents, GPU waits, submits, ETC2 decode, and pipeline and shader creation for ui.perfetto.dev. Adds a benchmark guide and a trace summary script.
2026-09-16 21:22:30 -05:00

2642 lines
107 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 <time.h>
#include <vulkan/vulkan.h>
#include <algorithm>
#include <atomic>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <string>
#include <utility>
#include <vector>
#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"
extern "C" {
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 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);
}
namespace {
using BackendWindowFn = void* (*)();
using UsesDirectVulkanFn = bool (*)();
using NotePresentFn = void (*)();
using NoteHostPresentBeginFn = void (*)();
using NoteHostPresentEndFn = void (*)(std::int32_t);
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;
};
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<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));
}
bool EnsureInitialized() {
AdapterState& state = State();
std::lock_guard<std::mutex> lock(state.mutex);
if (state.initialized) {
return true;
}
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);
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;
}
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"));
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(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(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)
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, "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);
}
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 bool fps_trace = FpsTraceEnabled();
const std::uint64_t present_start_ns = fps_trace ? MonotonicNanos() : 0;
VkResult result = VK_SUCCESS;
{
mocktail::graphics::TraceScope scope(
mocktail::graphics::ActiveProfileTrace(), "host present", "present");
result = 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.
void RecordProfiledFrame(mocktail::graphics::ChromeTraceWriter* trace,
std::uint64_t present_start_ns) {
static std::atomic<std::uint64_t> previous_start_ns{0};
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);
}
}
// 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);
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);
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;
}
// Null unless profiling is on and `name` is a profiled device command.
PFN_vkVoidFunction ProfileAdapterProc(const char* name) {
if (name == nullptr || 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);
}
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);
}
}
void PrepareEtc2Submit2(std::uint32_t submit_count,
const VkSubmitInfo2* submits) {
if (submits == nullptr) {
return;
}
for (std::uint32_t index = 0; index < submit_count; ++index) {
for (std::uint32_t info = 0; info < submits[index].commandBufferInfoCount;
++info) {
State().etc2.PrepareSubmit(
&submits[index].pCommandBufferInfos[info].commandBuffer, 1);
}
}
}
} // 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;
}
const VkResult 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 host_destroy = 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_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;
}
}
}
const VkResult result =
host_create(physical_device, &host_info, allocator, device);
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);
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;
}
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"));
State().text_overlay.DestroyDevice(device);
State().etc2.DestroyDevice(device);
if (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);
}
}
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);
}
}
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;
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 =
host_create(device, &host_info, 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);
(void)State().text_overlay.RegisterSwapchain(
device, *swapchain, *create_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"));
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);
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);
}
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]);
}
}
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);
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);
const VkResult result = host_begin(command_buffer, begin_info);
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);
const VkResult result = host_reset(command_buffer, flags);
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);
if (submits != nullptr) {
for (std::uint32_t index = 0; index < submit_count; ++index) {
State().etc2.PrepareSubmit(submits[index].pCommandBuffers,
submits[index].commandBufferCount);
}
}
const VkResult result = State().text_overlay.QueueSubmit(
queue, submit_count, submits, fence, host_submit);
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);
PrepareEtc2Submit2(submit_count, submits);
const VkResult result = State().text_overlay.QueueSubmit2(
queue, submit_count, submits, fence, host_submit);
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);
PrepareEtc2Submit2(submit_count, submits);
const VkResult result = State().text_overlay.QueueSubmit2(
queue, submit_count, submits, fence,
reinterpret_cast<PFN_vkQueueSubmit2>(host_submit));
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, host_bind);
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, host_wait);
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, host_wait);
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());
}
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);
}
VKAPI_ATTR VkResult VKAPI_CALL vkCreateImage(
VkDevice device, const VkImageCreateInfo* create_info,
const VkAllocationCallbacks* allocator, VkImage* image) {
return State().etc2.CreateImage(device, create_info, allocator, image);
}
VKAPI_ATTR void VKAPI_CALL vkDestroyImage(
VkDevice device, VkImage image, const VkAllocationCallbacks* allocator) {
State().etc2.DestroyImage(device, image, allocator);
}
VKAPI_ATTR VkResult VKAPI_CALL vkCreateImageView(
VkDevice device, const VkImageViewCreateInfo* create_info,
const VkAllocationCallbacks* allocator, VkImageView* view) {
return State().etc2.CreateImageView(device, create_info, allocator, view);
}
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) {
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) {
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) {
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);
}
} // extern "C"