mirror of
https://github.com/InstanceBrick/nightcap.git
synced 2026-10-29 04:01:45 +01:00
1006 lines
39 KiB
C++
1006 lines
39 KiB
C++
// Test-only implicit Vulkan layer that produces GPU-level evidence for the
|
|
// experimental native stereo bring-up:
|
|
// - records images only inside the bridge's guest eye initializer;
|
|
// - identifies left/right from the guest eye getter and the subsequent
|
|
// render pass's actual attachments, checking the initializer's owner;
|
|
// - counts vkCmdBeginRenderPass invocations per eye framebuffer (proof that
|
|
// the Roblox renderer issues separate GPU passes into both eyes);
|
|
// - at a configurable present index, reads both eye images back on the
|
|
// presenting queue and writes eye0.ppm / eye1.ppm plus summary.txt into
|
|
// MOCKTAIL_VR_EVIDENCE_DIR, including a pixel-difference verdict that
|
|
// distinguishes real per-eye cameras from a duplicated single image.
|
|
//
|
|
// The layer is inert unless MOCKTAIL_VR_EVIDENCE_DIR is set. It never aborts
|
|
// the application: every evidence operation is best-effort and logged with
|
|
// the [vr-evidence-layer] prefix.
|
|
#include <vulkan/vk_layer.h>
|
|
#include <vulkan/vulkan.h>
|
|
|
|
#include <dlfcn.h>
|
|
|
|
#include <cstdarg>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
#if defined(__GNUC__)
|
|
#define MOCKTAIL_VR_LAYER_EXPORT __attribute__((visibility("default")))
|
|
#else
|
|
#define MOCKTAIL_VR_LAYER_EXPORT
|
|
#endif
|
|
|
|
namespace {
|
|
|
|
void Log(const char *format, ...) {
|
|
va_list arguments;
|
|
va_start(arguments, format);
|
|
std::vfprintf(stderr, format, arguments);
|
|
va_end(arguments);
|
|
std::fflush(stderr);
|
|
}
|
|
|
|
std::string EvidenceDirectory() {
|
|
const char *value = std::getenv("MOCKTAIL_VR_EVIDENCE_DIR");
|
|
return value != nullptr ? std::string(value) : std::string();
|
|
}
|
|
|
|
uint32_t EnvUnsigned(const char *name, uint32_t fallback) {
|
|
const char *value = std::getenv(name);
|
|
if (value == nullptr || value[0] == '\0') {
|
|
return fallback;
|
|
}
|
|
const long parsed = std::strtol(value, nullptr, 10);
|
|
return parsed > 0 ? static_cast<uint32_t>(parsed) : fallback;
|
|
}
|
|
|
|
const void *FindInChain(const void *chain, VkStructureType type) {
|
|
// The loader inserts several nodes sharing the same sType (link info,
|
|
// data callbacks, feature flags); only the VK_LAYER_LINK_INFO variant
|
|
// carries the pLayerInfo chain a layer must consume.
|
|
for (const auto *header = static_cast<const VkBaseInStructure *>(chain);
|
|
header != nullptr; header = header->pNext) {
|
|
if (header->sType != type) {
|
|
continue;
|
|
}
|
|
const auto *link =
|
|
reinterpret_cast<const VkLayerInstanceCreateInfo *>(header);
|
|
if (link->function == VK_LAYER_LINK_INFO) {
|
|
return header;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
struct DeviceDispatch {
|
|
VkDevice device = VK_NULL_HANDLE;
|
|
VkPhysicalDevice physical_device = VK_NULL_HANDLE;
|
|
PFN_vkGetDeviceProcAddr get_device_proc_addr = nullptr;
|
|
PFN_vkDestroyDevice destroy_device = nullptr;
|
|
PFN_vkGetDeviceQueue get_device_queue = nullptr;
|
|
PFN_vkCreateImage create_image = nullptr;
|
|
PFN_vkDestroyImage destroy_image = nullptr;
|
|
PFN_vkDestroyFramebuffer destroy_framebuffer = nullptr;
|
|
PFN_vkCreateImageView create_image_view = nullptr;
|
|
PFN_vkCreateFramebuffer create_framebuffer = nullptr;
|
|
PFN_vkCreateRenderPass create_render_pass = nullptr;
|
|
PFN_vkCmdBeginRenderPass cmd_begin_render_pass = nullptr;
|
|
PFN_vkQueuePresentKHR queue_present = nullptr;
|
|
PFN_vkQueueSubmit queue_submit = nullptr;
|
|
PFN_vkCreateCommandPool create_command_pool = nullptr;
|
|
PFN_vkAllocateCommandBuffers allocate_command_buffers = nullptr;
|
|
PFN_vkBeginCommandBuffer begin_command_buffer = nullptr;
|
|
PFN_vkEndCommandBuffer end_command_buffer = nullptr;
|
|
PFN_vkCmdPipelineBarrier cmd_pipeline_barrier = nullptr;
|
|
PFN_vkCmdCopyImageToBuffer cmd_copy_image_to_buffer = nullptr;
|
|
PFN_vkCreateFence create_fence = nullptr;
|
|
PFN_vkWaitForFences wait_for_fences = nullptr;
|
|
PFN_vkDestroyFence destroy_fence = nullptr;
|
|
PFN_vkCreateBuffer create_buffer = nullptr;
|
|
PFN_vkDestroyBuffer destroy_buffer = nullptr;
|
|
PFN_vkGetBufferMemoryRequirements get_buffer_memory_requirements = nullptr;
|
|
PFN_vkAllocateMemory allocate_memory = nullptr;
|
|
PFN_vkFreeMemory free_memory = nullptr;
|
|
PFN_vkBindBufferMemory bind_buffer_memory = nullptr;
|
|
PFN_vkMapMemory map_memory = nullptr;
|
|
PFN_vkUnmapMemory unmap_memory = nullptr;
|
|
PFN_vkDestroyCommandPool destroy_command_pool = nullptr;
|
|
PFN_vkGetPhysicalDeviceMemoryProperties memory_properties = nullptr;
|
|
};
|
|
|
|
struct ImageRecord {
|
|
VkImage image = VK_NULL_HANDLE;
|
|
DeviceDispatch *dispatch = nullptr;
|
|
uint32_t width = 0;
|
|
uint32_t height = 0;
|
|
VkFormat format = VK_FORMAT_UNDEFINED;
|
|
VkImageUsageFlags usage = 0;
|
|
void *owner = nullptr;
|
|
};
|
|
|
|
struct EyeTarget {
|
|
bool valid = false;
|
|
ImageRecord image{};
|
|
VkFramebuffer framebuffer = VK_NULL_HANDLE;
|
|
VkImageLayout last_final_layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
std::uint64_t render_pass_count = 0;
|
|
};
|
|
|
|
bool g_active = false;
|
|
uint32_t g_capture_present = 60;
|
|
uint32_t g_capture_render_passes = 10;
|
|
std::mutex g_mutex;
|
|
std::unordered_map<void *, DeviceDispatch> g_dispatches;
|
|
std::unordered_map<void *, uint32_t> g_queue_families;
|
|
std::unordered_map<VkRenderPass, VkImageLayout> g_render_pass_final_layouts;
|
|
std::unordered_map<VkImageView, VkImage> g_image_views;
|
|
std::unordered_map<VkImage, ImageRecord> g_images;
|
|
std::unordered_map<VkFramebuffer, std::vector<VkImage>> g_framebuffer_images;
|
|
using InitializingObjectFn = void *(*)();
|
|
using CurrentEyeFn = bool (*)(void **, int *, void **);
|
|
using ConsumeEyeFn = void (*)();
|
|
InitializingObjectFn g_initializing_object = nullptr;
|
|
CurrentEyeFn g_current_eye = nullptr;
|
|
ConsumeEyeFn g_consume_eye = nullptr;
|
|
EyeTarget g_eyes[2];
|
|
std::uint64_t g_present_count = 0;
|
|
bool g_capture_attempted = false;
|
|
PFN_vkGetInstanceProcAddr g_instance_gipa = nullptr;
|
|
VkInstance g_instance = VK_NULL_HANDLE;
|
|
|
|
void *DispatchKey(void *handle) {
|
|
return handle != nullptr ? *reinterpret_cast<void **>(handle) : nullptr;
|
|
}
|
|
|
|
DeviceDispatch *DispatchFor(void *handle) {
|
|
void *key = DispatchKey(handle);
|
|
if (key == nullptr) {
|
|
return nullptr;
|
|
}
|
|
std::lock_guard<std::mutex> lock(g_mutex);
|
|
const auto entry = g_dispatches.find(key);
|
|
return entry != g_dispatches.end() ? &entry->second : nullptr;
|
|
}
|
|
|
|
bool IsColorFormat(VkFormat format) {
|
|
switch (format) {
|
|
case VK_FORMAT_D16_UNORM:
|
|
case VK_FORMAT_D16_UNORM_S8_UINT:
|
|
case VK_FORMAT_D24_UNORM_S8_UINT:
|
|
case VK_FORMAT_D32_SFLOAT:
|
|
case VK_FORMAT_D32_SFLOAT_S8_UINT:
|
|
case VK_FORMAT_X8_D24_UNORM_PACK32:
|
|
return false;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
uint32_t BytesPerPixel(VkFormat format) {
|
|
switch (format) {
|
|
case VK_FORMAT_R8G8B8A8_UNORM:
|
|
case VK_FORMAT_R8G8B8A8_SRGB:
|
|
case VK_FORMAT_B8G8R8A8_UNORM:
|
|
case VK_FORMAT_B8G8R8A8_SRGB:
|
|
case VK_FORMAT_R8G8B8A8_UINT:
|
|
case VK_FORMAT_R32_SFLOAT:
|
|
return 4;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
bool IsBgra(VkFormat format) {
|
|
return format == VK_FORMAT_B8G8R8A8_UNORM ||
|
|
format == VK_FORMAT_B8G8R8A8_SRGB;
|
|
}
|
|
|
|
bool WritePpm(const std::string &path, const uint8_t *pixels, uint32_t width,
|
|
uint32_t height, bool bgra) {
|
|
std::FILE *file = std::fopen(path.c_str(), "wb");
|
|
if (file == nullptr) {
|
|
return false;
|
|
}
|
|
std::fprintf(file, "P6\n%u %u\n255\n", width, height);
|
|
for (uint32_t row = 0; row < height; ++row) {
|
|
for (uint32_t column = 0; column < width; ++column) {
|
|
const uint8_t *source =
|
|
pixels + (static_cast<size_t>(row) * width + column) * 4;
|
|
const uint8_t red = bgra ? source[2] : source[0];
|
|
const uint8_t green = source[1];
|
|
const uint8_t blue = bgra ? source[0] : source[2];
|
|
const uint8_t rgb[3] = {red, green, blue};
|
|
std::fwrite(rgb, 1, sizeof(rgb), file);
|
|
}
|
|
}
|
|
std::fclose(file);
|
|
return true;
|
|
}
|
|
|
|
int FindMemoryType(const DeviceDispatch &dispatch, uint32_t requirements,
|
|
VkMemoryPropertyFlags properties) {
|
|
if (dispatch.memory_properties == nullptr) {
|
|
return -1;
|
|
}
|
|
VkPhysicalDeviceMemoryProperties memory{};
|
|
dispatch.memory_properties(dispatch.physical_device, &memory);
|
|
for (uint32_t index = 0; index < memory.memoryTypeCount; ++index) {
|
|
if ((requirements & (1u << index)) != 0 &&
|
|
(memory.memoryTypes[index].propertyFlags & properties) == properties) {
|
|
return static_cast<int>(index);
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
uint32_t QueueFamilyFor(VkQueue queue) {
|
|
std::lock_guard<std::mutex> lock(g_mutex);
|
|
const auto entry = g_queue_families.find(DispatchKey(queue));
|
|
return entry != g_queue_families.end() ? entry->second : 0;
|
|
}
|
|
|
|
void CaptureEyes(DeviceDispatch &dispatch, VkQueue queue) {
|
|
// Runs on the presenting (render) thread after the frame that filled both
|
|
// eyes was submitted; g_mutex is NOT held here except through helpers.
|
|
EyeTarget eyes[2];
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_mutex);
|
|
eyes[0] = g_eyes[0];
|
|
eyes[1] = g_eyes[1];
|
|
}
|
|
if (!eyes[0].valid || !eyes[1].valid || eyes[0].image.dispatch != &dispatch ||
|
|
eyes[1].image.dispatch != &dispatch ||
|
|
eyes[0].image.owner != eyes[1].image.owner ||
|
|
eyes[0].image.width != eyes[1].image.width ||
|
|
eyes[0].image.height != eyes[1].image.height) {
|
|
Log(" [vr-evidence-layer] capture skipped: eye targets unknown\n");
|
|
return;
|
|
}
|
|
if (BytesPerPixel(eyes[0].image.format) != 4 ||
|
|
BytesPerPixel(eyes[1].image.format) != 4) {
|
|
Log(" [vr-evidence-layer] capture skipped: unsupported eye formats "
|
|
"%d/%d\n",
|
|
static_cast<int>(eyes[0].image.format),
|
|
static_cast<int>(eyes[1].image.format));
|
|
return;
|
|
}
|
|
const uint32_t width = eyes[0].image.width;
|
|
const uint32_t height = eyes[0].image.height;
|
|
const VkDeviceSize buffer_size =
|
|
static_cast<VkDeviceSize>(width) * height * 4;
|
|
|
|
VkCommandPool pool = VK_NULL_HANDLE;
|
|
VkCommandPoolCreateInfo pool_info{};
|
|
pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
|
pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
|
pool_info.queueFamilyIndex = QueueFamilyFor(queue);
|
|
if (dispatch.create_command_pool(dispatch.device, &pool_info, nullptr,
|
|
&pool) != VK_SUCCESS) {
|
|
Log(" [vr-evidence-layer] capture: command pool creation failed\n");
|
|
return;
|
|
}
|
|
|
|
VkCommandBuffer command = VK_NULL_HANDLE;
|
|
VkCommandBufferAllocateInfo allocate_info{};
|
|
allocate_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
|
allocate_info.commandPool = pool;
|
|
allocate_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
|
allocate_info.commandBufferCount = 1;
|
|
if (dispatch.allocate_command_buffers(dispatch.device, &allocate_info,
|
|
&command) != VK_SUCCESS) {
|
|
dispatch.destroy_command_pool(dispatch.device, pool, nullptr);
|
|
Log(" [vr-evidence-layer] capture: command buffer allocation failed\n");
|
|
return;
|
|
}
|
|
|
|
VkBuffer buffers[2] = {VK_NULL_HANDLE, VK_NULL_HANDLE};
|
|
VkDeviceMemory memories[2] = {VK_NULL_HANDLE, VK_NULL_HANDLE};
|
|
void *mapped[2] = {nullptr, nullptr};
|
|
VkFence fence = VK_NULL_HANDLE;
|
|
VkFenceCreateInfo fence_info{};
|
|
fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
|
bool prepared = dispatch.create_fence(dispatch.device, &fence_info, nullptr,
|
|
&fence) == VK_SUCCESS;
|
|
for (int index = 0; prepared && index < 2; ++index) {
|
|
VkBufferCreateInfo buffer_info{};
|
|
buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
|
buffer_info.size = buffer_size;
|
|
buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
|
prepared = dispatch.create_buffer(dispatch.device, &buffer_info, nullptr,
|
|
&buffers[index]) == VK_SUCCESS;
|
|
if (!prepared) {
|
|
break;
|
|
}
|
|
VkMemoryRequirements requirements{};
|
|
dispatch.get_buffer_memory_requirements(dispatch.device, buffers[index],
|
|
&requirements);
|
|
const int memory_type =
|
|
FindMemoryType(dispatch, requirements.memoryTypeBits,
|
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
|
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
|
if (memory_type < 0) {
|
|
prepared = false;
|
|
break;
|
|
}
|
|
VkMemoryAllocateInfo memory_info{};
|
|
memory_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
|
memory_info.allocationSize = requirements.size;
|
|
memory_info.memoryTypeIndex = static_cast<uint32_t>(memory_type);
|
|
prepared = dispatch.allocate_memory(dispatch.device, &memory_info, nullptr,
|
|
&memories[index]) == VK_SUCCESS;
|
|
if (!prepared) {
|
|
break;
|
|
}
|
|
prepared = dispatch.bind_buffer_memory(dispatch.device, buffers[index],
|
|
memories[index], 0) == VK_SUCCESS;
|
|
}
|
|
|
|
if (prepared) {
|
|
VkCommandBufferBeginInfo begin_info{};
|
|
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
|
begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
|
prepared =
|
|
dispatch.begin_command_buffer(command, &begin_info) == VK_SUCCESS;
|
|
}
|
|
if (prepared) {
|
|
for (int index = 0; index < 2; ++index) {
|
|
VkImageMemoryBarrier to_source{};
|
|
to_source.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
|
to_source.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
to_source.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
to_source.oldLayout = eyes[index].last_final_layout;
|
|
to_source.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
|
to_source.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
to_source.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
to_source.image = eyes[index].image.image;
|
|
to_source.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
|
dispatch.cmd_pipeline_barrier(
|
|
command, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1,
|
|
&to_source);
|
|
VkBufferImageCopy region{};
|
|
region.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
|
region.imageExtent = {width, height, 1};
|
|
dispatch.cmd_copy_image_to_buffer(command, eyes[index].image.image,
|
|
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|
buffers[index], 1, ®ion);
|
|
VkImageMemoryBarrier restore = to_source;
|
|
restore.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
restore.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
restore.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
|
restore.newLayout = eyes[index].last_final_layout;
|
|
dispatch.cmd_pipeline_barrier(
|
|
command, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, nullptr, 0,
|
|
nullptr, 1, &restore);
|
|
}
|
|
prepared = dispatch.end_command_buffer(command) == VK_SUCCESS;
|
|
}
|
|
if (prepared) {
|
|
VkSubmitInfo submit{};
|
|
submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
|
submit.commandBufferCount = 1;
|
|
submit.pCommandBuffers = &command;
|
|
prepared = dispatch.queue_submit(queue, 1, &submit, fence) == VK_SUCCESS &&
|
|
dispatch.wait_for_fences(dispatch.device, 1, &fence, VK_TRUE,
|
|
2000000000ULL) == VK_SUCCESS;
|
|
}
|
|
if (prepared) {
|
|
for (int index = 0; index < 2; ++index) {
|
|
prepared =
|
|
dispatch.map_memory(dispatch.device, memories[index], 0, buffer_size,
|
|
0, &mapped[index]) == VK_SUCCESS;
|
|
if (!prepared) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const std::string directory = EvidenceDirectory();
|
|
bool wrote_images = false;
|
|
if (prepared && mapped[0] != nullptr && mapped[1] != nullptr) {
|
|
const auto *pixels0 = static_cast<const uint8_t *>(mapped[0]);
|
|
const auto *pixels1 = static_cast<const uint8_t *>(mapped[1]);
|
|
wrote_images = WritePpm(directory + "/eye0.ppm", pixels0, width, height,
|
|
IsBgra(eyes[0].image.format)) &&
|
|
WritePpm(directory + "/eye1.ppm", pixels1, width, height,
|
|
IsBgra(eyes[1].image.format));
|
|
std::size_t differing = 0;
|
|
std::uint64_t difference_sum = 0;
|
|
uint32_t maximum_difference = 0;
|
|
const std::size_t total = static_cast<std::size_t>(width) * height;
|
|
for (std::size_t pixel = 0; pixel < total; ++pixel) {
|
|
const uint8_t *a = pixels0 + pixel * 4;
|
|
const uint8_t *b = pixels1 + pixel * 4;
|
|
uint32_t largest = 0;
|
|
for (int channel = 0; channel < 3; ++channel) {
|
|
const uint32_t difference = a[channel] > b[channel]
|
|
? a[channel] - b[channel]
|
|
: b[channel] - a[channel];
|
|
largest = difference > largest ? difference : largest;
|
|
}
|
|
if (largest > 0) {
|
|
++differing;
|
|
difference_sum += largest;
|
|
maximum_difference =
|
|
largest > maximum_difference ? largest : maximum_difference;
|
|
}
|
|
}
|
|
std::FILE *summary = std::fopen((directory + "/summary.txt").c_str(), "w");
|
|
if (summary != nullptr) {
|
|
std::fprintf(summary,
|
|
"provenance=guest_initializer_and_eye_getter owner=%p\n",
|
|
eyes[0].image.owner);
|
|
std::fprintf(summary, "capture_present=%llu\n",
|
|
static_cast<unsigned long long>(g_present_count));
|
|
for (int index = 0; index < 2; ++index) {
|
|
const EyeTarget &eye = eyes[index];
|
|
std::fprintf(
|
|
summary,
|
|
"eye%d_image=%p eye%d_format=%d eye%d_width=%u eye%d_height=%u "
|
|
"eye%d_usage=0x%x eye%d_framebuffer=%p "
|
|
"eye%d_render_pass_count=%llu eye%d_final_layout=%d\n",
|
|
index, static_cast<void *>(eye.image.image), index,
|
|
static_cast<int>(eye.image.format), index, eye.image.width, index,
|
|
eye.image.height, index, eye.image.usage, index,
|
|
static_cast<void *>(eye.framebuffer), index,
|
|
static_cast<unsigned long long>(eye.render_pass_count), index,
|
|
static_cast<int>(eye.last_final_layout));
|
|
}
|
|
std::fprintf(summary,
|
|
"pixels_total=%zu pixels_differing=%zu max_abs_diff=%u "
|
|
"mean_abs_diff=%.4f\n",
|
|
total, differing, maximum_difference,
|
|
differing > 0 ? static_cast<double>(difference_sum) /
|
|
static_cast<double>(differing)
|
|
: 0.0);
|
|
std::fprintf(summary, "EYES_DIFFER=%s\n", differing > 0 ? "yes" : "no");
|
|
std::fclose(summary);
|
|
}
|
|
Log(" [vr-evidence-layer] capture done: differing=%zu/%u max_diff=%u "
|
|
"eye0_rp=%llu eye1_rp=%llu wrote_ppm=%d\n",
|
|
differing, static_cast<uint32_t>(total), maximum_difference,
|
|
static_cast<unsigned long long>(eyes[0].render_pass_count),
|
|
static_cast<unsigned long long>(eyes[1].render_pass_count),
|
|
wrote_images ? 1 : 0);
|
|
} else {
|
|
Log(" [vr-evidence-layer] capture FAILED (prepared=%d)\n",
|
|
prepared ? 1 : 0);
|
|
}
|
|
|
|
for (int index = 0; index < 2; ++index) {
|
|
if (mapped[index] != nullptr) {
|
|
dispatch.unmap_memory(dispatch.device, memories[index]);
|
|
}
|
|
if (buffers[index] != VK_NULL_HANDLE) {
|
|
dispatch.destroy_buffer(dispatch.device, buffers[index], nullptr);
|
|
}
|
|
if (memories[index] != VK_NULL_HANDLE) {
|
|
dispatch.free_memory(dispatch.device, memories[index], nullptr);
|
|
}
|
|
}
|
|
if (fence != VK_NULL_HANDLE) {
|
|
dispatch.destroy_fence(dispatch.device, fence, nullptr);
|
|
}
|
|
dispatch.destroy_command_pool(dispatch.device, pool, nullptr);
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL
|
|
LayerQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR *info) {
|
|
DeviceDispatch *dispatch = DispatchFor(queue);
|
|
if (dispatch == nullptr || dispatch->queue_present == nullptr) {
|
|
return VK_ERROR_INITIALIZATION_FAILED;
|
|
}
|
|
if (!g_active) {
|
|
return dispatch->queue_present(queue, info);
|
|
}
|
|
std::uint64_t presents = 0;
|
|
bool should_capture = false;
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_mutex);
|
|
presents = ++g_present_count;
|
|
// Only capture once BOTH eyes have actually been rendered into at least
|
|
// g_capture_render_passes times; otherwise the readback could show
|
|
// uninitialized contents and a bogus pixel difference.
|
|
should_capture = !g_capture_attempted && presents >= g_capture_present &&
|
|
g_eyes[0].valid && g_eyes[1].valid &&
|
|
g_eyes[0].image.dispatch == dispatch &&
|
|
g_eyes[1].image.dispatch == dispatch &&
|
|
g_eyes[0].framebuffer != VK_NULL_HANDLE &&
|
|
g_eyes[1].framebuffer != VK_NULL_HANDLE &&
|
|
g_eyes[0].render_pass_count >= g_capture_render_passes &&
|
|
g_eyes[1].render_pass_count >= g_capture_render_passes;
|
|
if (should_capture) {
|
|
g_capture_attempted = true;
|
|
}
|
|
}
|
|
if (should_capture) {
|
|
CaptureEyes(*dispatch, queue);
|
|
}
|
|
if (presents == 1 || presents % 120 == 0) {
|
|
std::lock_guard<std::mutex> lock(g_mutex);
|
|
Log(" [vr-evidence-layer] presents=%llu eye0_rp=%llu eye1_rp=%llu\n",
|
|
static_cast<unsigned long long>(presents),
|
|
static_cast<unsigned long long>(g_eyes[0].render_pass_count),
|
|
static_cast<unsigned long long>(g_eyes[1].render_pass_count));
|
|
}
|
|
return dispatch->queue_present(queue, info);
|
|
}
|
|
|
|
VKAPI_ATTR void VKAPI_CALL LayerCmdBeginRenderPass(
|
|
VkCommandBuffer command, const VkRenderPassBeginInfo *info,
|
|
VkSubpassContents contents) {
|
|
DeviceDispatch *dispatch = DispatchFor(command);
|
|
if (dispatch == nullptr || dispatch->cmd_begin_render_pass == nullptr) {
|
|
return;
|
|
}
|
|
if (g_active && info != nullptr) {
|
|
std::lock_guard<std::mutex> lock(g_mutex);
|
|
void *owner = nullptr;
|
|
void *guest_framebuffer = nullptr;
|
|
int index = -1;
|
|
const bool requested = g_current_eye != nullptr &&
|
|
g_current_eye(&owner, &index, &guest_framebuffer) &&
|
|
index >= 0 && index < 2;
|
|
const auto attachments = g_framebuffer_images.find(info->framebuffer);
|
|
if (requested && attachments != g_framebuffer_images.end()) {
|
|
const ImageRecord *target = nullptr;
|
|
bool ambiguous = false;
|
|
for (VkImage image : attachments->second) {
|
|
const auto found = g_images.find(image);
|
|
if (found != g_images.end() && found->second.owner == owner &&
|
|
found->second.dispatch == dispatch &&
|
|
IsColorFormat(found->second.format) &&
|
|
(found->second.usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) != 0) {
|
|
if (target != nullptr)
|
|
ambiguous = true;
|
|
target = &found->second;
|
|
}
|
|
}
|
|
if (target != nullptr && !ambiguous) {
|
|
if ((g_eyes[0].valid && g_eyes[0].image.owner != owner) ||
|
|
(g_eyes[1].valid && g_eyes[1].image.owner != owner)) {
|
|
g_eyes[0] = {};
|
|
g_eyes[1] = {};
|
|
g_capture_attempted = false;
|
|
}
|
|
EyeTarget &eye = g_eyes[index];
|
|
if (!eye.valid || eye.image.image != target->image ||
|
|
eye.framebuffer != info->framebuffer) {
|
|
eye = {};
|
|
eye.valid = true;
|
|
eye.image = *target;
|
|
eye.framebuffer = info->framebuffer;
|
|
Log(" [vr-evidence-layer] eye framebuffer bound: eye=%d owner=%p "
|
|
"guest_fb=%p fb=%p image=%p "
|
|
"provenance=guest_initializer_and_eye_getter\n",
|
|
index, owner, guest_framebuffer,
|
|
static_cast<void *>(info->framebuffer),
|
|
static_cast<void *>(target->image));
|
|
}
|
|
++eye.render_pass_count;
|
|
const auto layout = g_render_pass_final_layouts.find(info->renderPass);
|
|
if (layout != g_render_pass_final_layouts.end())
|
|
eye.last_final_layout = layout->second;
|
|
if (g_consume_eye != nullptr)
|
|
g_consume_eye();
|
|
}
|
|
}
|
|
}
|
|
dispatch->cmd_begin_render_pass(command, info, contents);
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL
|
|
LayerCreateImage(VkDevice device, const VkImageCreateInfo *info,
|
|
const VkAllocationCallbacks *allocator, VkImage *image) {
|
|
DeviceDispatch *dispatch = DispatchFor(device);
|
|
if (dispatch == nullptr || dispatch->create_image == nullptr) {
|
|
return VK_ERROR_INITIALIZATION_FAILED;
|
|
}
|
|
const VkResult result =
|
|
dispatch->create_image(device, info, allocator, image);
|
|
void *owner =
|
|
g_initializing_object != nullptr ? g_initializing_object() : nullptr;
|
|
if (g_active && owner != nullptr && result == VK_SUCCESS && info != nullptr &&
|
|
image != nullptr) {
|
|
std::lock_guard<std::mutex> lock(g_mutex);
|
|
ImageRecord record{};
|
|
record.image = *image;
|
|
record.dispatch = dispatch;
|
|
record.width = info->extent.width;
|
|
record.height = info->extent.height;
|
|
record.format = info->format;
|
|
record.usage = info->usage;
|
|
record.owner = owner;
|
|
g_images[*image] = record;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL LayerCreateImageView(
|
|
VkDevice device, const VkImageViewCreateInfo *info,
|
|
const VkAllocationCallbacks *allocator, VkImageView *view) {
|
|
DeviceDispatch *dispatch = DispatchFor(device);
|
|
if (dispatch == nullptr || dispatch->create_image_view == nullptr) {
|
|
return VK_ERROR_INITIALIZATION_FAILED;
|
|
}
|
|
const VkResult result =
|
|
dispatch->create_image_view(device, info, allocator, view);
|
|
if (g_active && result == VK_SUCCESS && info != nullptr && view != nullptr) {
|
|
std::lock_guard<std::mutex> lock(g_mutex);
|
|
g_image_views[*view] = info->image;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL LayerCreateFramebuffer(
|
|
VkDevice device, const VkFramebufferCreateInfo *info,
|
|
const VkAllocationCallbacks *allocator, VkFramebuffer *framebuffer) {
|
|
DeviceDispatch *dispatch = DispatchFor(device);
|
|
if (dispatch == nullptr || dispatch->create_framebuffer == nullptr) {
|
|
return VK_ERROR_INITIALIZATION_FAILED;
|
|
}
|
|
const VkResult result =
|
|
dispatch->create_framebuffer(device, info, allocator, framebuffer);
|
|
if (g_active && result == VK_SUCCESS && info != nullptr &&
|
|
framebuffer != nullptr) {
|
|
std::lock_guard<std::mutex> lock(g_mutex);
|
|
auto &images = g_framebuffer_images[*framebuffer];
|
|
images.clear();
|
|
for (uint32_t index = 0; index < info->attachmentCount; ++index) {
|
|
const auto source = g_image_views.find(info->pAttachments[index]);
|
|
if (source != g_image_views.end())
|
|
images.push_back(source->second);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
VKAPI_ATTR void VKAPI_CALL LayerDestroyImage(
|
|
VkDevice device, VkImage image, const VkAllocationCallbacks *allocator) {
|
|
auto *dispatch = DispatchFor(device);
|
|
if (dispatch == nullptr)
|
|
return;
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_mutex);
|
|
g_images.erase(image);
|
|
for (auto it = g_image_views.begin(); it != g_image_views.end();) {
|
|
if (it->second == image)
|
|
it = g_image_views.erase(it);
|
|
else
|
|
++it;
|
|
}
|
|
if (g_eyes[0].image.image == image || g_eyes[1].image.image == image) {
|
|
g_eyes[0] = {};
|
|
g_eyes[1] = {};
|
|
g_capture_attempted = false;
|
|
}
|
|
}
|
|
dispatch->destroy_image(device, image, allocator);
|
|
}
|
|
|
|
VKAPI_ATTR void VKAPI_CALL
|
|
LayerDestroyFramebuffer(VkDevice device, VkFramebuffer framebuffer,
|
|
const VkAllocationCallbacks *allocator) {
|
|
auto *dispatch = DispatchFor(device);
|
|
if (dispatch == nullptr)
|
|
return;
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_mutex);
|
|
g_framebuffer_images.erase(framebuffer);
|
|
if (g_eyes[0].framebuffer == framebuffer ||
|
|
g_eyes[1].framebuffer == framebuffer) {
|
|
g_eyes[0] = {};
|
|
g_eyes[1] = {};
|
|
g_capture_attempted = false;
|
|
}
|
|
}
|
|
dispatch->destroy_framebuffer(device, framebuffer, allocator);
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL LayerCreateRenderPass(
|
|
VkDevice device, const VkRenderPassCreateInfo *info,
|
|
const VkAllocationCallbacks *allocator, VkRenderPass *render_pass) {
|
|
DeviceDispatch *dispatch = DispatchFor(device);
|
|
if (dispatch == nullptr || dispatch->create_render_pass == nullptr) {
|
|
return VK_ERROR_INITIALIZATION_FAILED;
|
|
}
|
|
const VkResult result =
|
|
dispatch->create_render_pass(device, info, allocator, render_pass);
|
|
if (g_active && result == VK_SUCCESS && info != nullptr &&
|
|
render_pass != nullptr && info->attachmentCount > 0) {
|
|
std::lock_guard<std::mutex> lock(g_mutex);
|
|
g_render_pass_final_layouts[*render_pass] =
|
|
info->pAttachments[0].finalLayout;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
VKAPI_ATTR void VKAPI_CALL LayerGetDeviceQueue(VkDevice device, uint32_t family,
|
|
uint32_t index, VkQueue *queue) {
|
|
DeviceDispatch *dispatch = DispatchFor(device);
|
|
if (dispatch == nullptr || dispatch->get_device_queue == nullptr) {
|
|
return;
|
|
}
|
|
dispatch->get_device_queue(device, family, index, queue);
|
|
if (g_active && queue != nullptr && *queue != VK_NULL_HANDLE) {
|
|
std::lock_guard<std::mutex> lock(g_mutex);
|
|
g_queue_families[DispatchKey(*queue)] = family;
|
|
}
|
|
}
|
|
|
|
VKAPI_ATTR void VKAPI_CALL
|
|
LayerDestroyDevice(VkDevice device, const VkAllocationCallbacks *allocator) {
|
|
DeviceDispatch *dispatch = DispatchFor(device);
|
|
if (dispatch == nullptr) {
|
|
return;
|
|
}
|
|
PFN_vkDestroyDevice destroy = dispatch->destroy_device;
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_mutex);
|
|
g_dispatches.erase(DispatchKey(device));
|
|
}
|
|
if (destroy != nullptr) {
|
|
destroy(device, allocator);
|
|
}
|
|
}
|
|
|
|
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL
|
|
LayerGetDeviceProcAddr(VkDevice device, const char *name) {
|
|
if (name == nullptr) {
|
|
return nullptr;
|
|
}
|
|
if (g_active) {
|
|
if (std::strcmp(name, "vkGetDeviceProcAddr") == 0) {
|
|
return reinterpret_cast<PFN_vkVoidFunction>(LayerGetDeviceProcAddr);
|
|
}
|
|
if (std::strcmp(name, "vkDestroyImage") == 0)
|
|
return reinterpret_cast<PFN_vkVoidFunction>(LayerDestroyImage);
|
|
if (std::strcmp(name, "vkDestroyFramebuffer") == 0)
|
|
return reinterpret_cast<PFN_vkVoidFunction>(LayerDestroyFramebuffer);
|
|
if (std::strcmp(name, "vkDestroyDevice") == 0) {
|
|
return reinterpret_cast<PFN_vkVoidFunction>(LayerDestroyDevice);
|
|
}
|
|
if (std::strcmp(name, "vkGetDeviceQueue") == 0) {
|
|
return reinterpret_cast<PFN_vkVoidFunction>(LayerGetDeviceQueue);
|
|
}
|
|
if (std::strcmp(name, "vkCreateImage") == 0) {
|
|
return reinterpret_cast<PFN_vkVoidFunction>(LayerCreateImage);
|
|
}
|
|
if (std::strcmp(name, "vkCreateImageView") == 0) {
|
|
return reinterpret_cast<PFN_vkVoidFunction>(LayerCreateImageView);
|
|
}
|
|
if (std::strcmp(name, "vkCreateFramebuffer") == 0) {
|
|
return reinterpret_cast<PFN_vkVoidFunction>(LayerCreateFramebuffer);
|
|
}
|
|
if (std::strcmp(name, "vkCreateRenderPass") == 0) {
|
|
return reinterpret_cast<PFN_vkVoidFunction>(LayerCreateRenderPass);
|
|
}
|
|
if (std::strcmp(name, "vkCmdBeginRenderPass") == 0) {
|
|
return reinterpret_cast<PFN_vkVoidFunction>(LayerCmdBeginRenderPass);
|
|
}
|
|
if (std::strcmp(name, "vkQueuePresentKHR") == 0) {
|
|
return reinterpret_cast<PFN_vkVoidFunction>(LayerQueuePresentKHR);
|
|
}
|
|
}
|
|
std::lock_guard<std::mutex> lock(g_mutex);
|
|
const auto entry = g_dispatches.find(DispatchKey(device));
|
|
if (entry == g_dispatches.end() ||
|
|
entry->second.get_device_proc_addr == nullptr) {
|
|
return nullptr;
|
|
}
|
|
return entry->second.get_device_proc_addr(device, name);
|
|
}
|
|
|
|
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL
|
|
LayerGetInstanceProcAddr(VkInstance instance, const char *name);
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL LayerCreateDevice(
|
|
VkPhysicalDevice physical_device, const VkDeviceCreateInfo *info,
|
|
const VkAllocationCallbacks *allocator, VkDevice *device) {
|
|
auto *link = const_cast<VkLayerDeviceCreateInfo *>(
|
|
static_cast<const VkLayerDeviceCreateInfo *>(
|
|
FindInChain(info != nullptr ? info->pNext : nullptr,
|
|
VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO)));
|
|
if (link == nullptr || link->u.pLayerInfo == nullptr) {
|
|
return VK_ERROR_INITIALIZATION_FAILED;
|
|
}
|
|
const VkLayerDeviceLink *device_link = link->u.pLayerInfo;
|
|
PFN_vkGetInstanceProcAddr next_gipa = device_link->pfnNextGetInstanceProcAddr;
|
|
PFN_vkGetDeviceProcAddr next_gdpa = device_link->pfnNextGetDeviceProcAddr;
|
|
PFN_vkCreateDevice next_create = reinterpret_cast<PFN_vkCreateDevice>(
|
|
next_gipa(VK_NULL_HANDLE, "vkCreateDevice"));
|
|
if (next_create == nullptr) {
|
|
return VK_ERROR_INITIALIZATION_FAILED;
|
|
}
|
|
link->u.pLayerInfo = link->u.pLayerInfo->pNext;
|
|
const VkResult result = next_create(physical_device, info, allocator, device);
|
|
if (result != VK_SUCCESS || device == nullptr) {
|
|
return result;
|
|
}
|
|
DeviceDispatch dispatch{};
|
|
dispatch.device = *device;
|
|
dispatch.physical_device = physical_device;
|
|
dispatch.get_device_proc_addr = next_gdpa;
|
|
#define MOCKTAIL_LAYER_DEVICE_FN(field, name) \
|
|
dispatch.field = reinterpret_cast<PFN_##name>(next_gdpa(*device, #name));
|
|
MOCKTAIL_LAYER_DEVICE_FN(destroy_device, vkDestroyDevice)
|
|
MOCKTAIL_LAYER_DEVICE_FN(get_device_queue, vkGetDeviceQueue)
|
|
MOCKTAIL_LAYER_DEVICE_FN(create_image, vkCreateImage)
|
|
MOCKTAIL_LAYER_DEVICE_FN(destroy_image, vkDestroyImage)
|
|
MOCKTAIL_LAYER_DEVICE_FN(destroy_framebuffer, vkDestroyFramebuffer)
|
|
MOCKTAIL_LAYER_DEVICE_FN(create_image_view, vkCreateImageView)
|
|
MOCKTAIL_LAYER_DEVICE_FN(create_framebuffer, vkCreateFramebuffer)
|
|
MOCKTAIL_LAYER_DEVICE_FN(create_render_pass, vkCreateRenderPass)
|
|
MOCKTAIL_LAYER_DEVICE_FN(cmd_begin_render_pass, vkCmdBeginRenderPass)
|
|
MOCKTAIL_LAYER_DEVICE_FN(queue_present, vkQueuePresentKHR)
|
|
MOCKTAIL_LAYER_DEVICE_FN(queue_submit, vkQueueSubmit)
|
|
MOCKTAIL_LAYER_DEVICE_FN(create_command_pool, vkCreateCommandPool)
|
|
MOCKTAIL_LAYER_DEVICE_FN(allocate_command_buffers, vkAllocateCommandBuffers)
|
|
MOCKTAIL_LAYER_DEVICE_FN(begin_command_buffer, vkBeginCommandBuffer)
|
|
MOCKTAIL_LAYER_DEVICE_FN(end_command_buffer, vkEndCommandBuffer)
|
|
MOCKTAIL_LAYER_DEVICE_FN(cmd_pipeline_barrier, vkCmdPipelineBarrier)
|
|
MOCKTAIL_LAYER_DEVICE_FN(cmd_copy_image_to_buffer, vkCmdCopyImageToBuffer)
|
|
MOCKTAIL_LAYER_DEVICE_FN(create_fence, vkCreateFence)
|
|
MOCKTAIL_LAYER_DEVICE_FN(wait_for_fences, vkWaitForFences)
|
|
MOCKTAIL_LAYER_DEVICE_FN(destroy_fence, vkDestroyFence)
|
|
MOCKTAIL_LAYER_DEVICE_FN(create_buffer, vkCreateBuffer)
|
|
MOCKTAIL_LAYER_DEVICE_FN(destroy_buffer, vkDestroyBuffer)
|
|
MOCKTAIL_LAYER_DEVICE_FN(get_buffer_memory_requirements,
|
|
vkGetBufferMemoryRequirements)
|
|
MOCKTAIL_LAYER_DEVICE_FN(allocate_memory, vkAllocateMemory)
|
|
MOCKTAIL_LAYER_DEVICE_FN(free_memory, vkFreeMemory)
|
|
MOCKTAIL_LAYER_DEVICE_FN(bind_buffer_memory, vkBindBufferMemory)
|
|
MOCKTAIL_LAYER_DEVICE_FN(map_memory, vkMapMemory)
|
|
MOCKTAIL_LAYER_DEVICE_FN(unmap_memory, vkUnmapMemory)
|
|
MOCKTAIL_LAYER_DEVICE_FN(destroy_command_pool, vkDestroyCommandPool)
|
|
#undef MOCKTAIL_LAYER_DEVICE_FN
|
|
dispatch.memory_properties =
|
|
reinterpret_cast<PFN_vkGetPhysicalDeviceMemoryProperties>(
|
|
next_gipa(g_instance, "vkGetPhysicalDeviceMemoryProperties"));
|
|
if (g_active) {
|
|
Log(" [vr-evidence-layer] CreateDevice: g_instance=%p chain-gipa "
|
|
"memory-props=%p physdev-props=%p enumerate-pd=%p pd=%p\n",
|
|
static_cast<void *>(g_instance),
|
|
reinterpret_cast<void *>(dispatch.memory_properties),
|
|
reinterpret_cast<void *>(
|
|
next_gipa(g_instance, "vkGetPhysicalDeviceProperties")),
|
|
reinterpret_cast<void *>(
|
|
next_gipa(g_instance, "vkEnumeratePhysicalDevices")),
|
|
static_cast<void *>(physical_device));
|
|
}
|
|
if (dispatch.memory_properties == nullptr) {
|
|
// NOTE: dlsym(RTLD_DEFAULT) returns the loader's top-level trampoline,
|
|
// which rejects below-chain physical device handles; do not use it here.
|
|
}
|
|
if (dispatch.memory_properties == nullptr) {
|
|
Log(" [vr-evidence-layer] warning: memory-properties entry unresolved; "
|
|
"pixel readback will be unavailable\n");
|
|
}
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_mutex);
|
|
g_dispatches[DispatchKey(*device)] = dispatch;
|
|
}
|
|
if (g_active) {
|
|
Log(" [vr-evidence-layer] device dispatch registered (%p)\n",
|
|
static_cast<void *>(*device));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL LayerCreateInstance(
|
|
const VkInstanceCreateInfo *info, const VkAllocationCallbacks *allocator,
|
|
VkInstance *instance) {
|
|
auto *link = const_cast<VkLayerInstanceCreateInfo *>(
|
|
static_cast<const VkLayerInstanceCreateInfo *>(
|
|
FindInChain(info != nullptr ? info->pNext : nullptr,
|
|
VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO)));
|
|
if (link == nullptr || link->u.pLayerInfo == nullptr) {
|
|
if (g_active) {
|
|
Log(" [vr-evidence-layer] CreateInstance: loader chain info missing "
|
|
"(link=%p)\n",
|
|
static_cast<const void *>(link));
|
|
}
|
|
return VK_ERROR_INITIALIZATION_FAILED;
|
|
}
|
|
const VkLayerInstanceLink *instance_link = link->u.pLayerInfo;
|
|
PFN_vkGetInstanceProcAddr next_gipa =
|
|
instance_link->pfnNextGetInstanceProcAddr;
|
|
PFN_vkCreateInstance next_create = reinterpret_cast<PFN_vkCreateInstance>(
|
|
next_gipa(VK_NULL_HANDLE, "vkCreateInstance"));
|
|
if (next_create == nullptr) {
|
|
Log(" [vr-evidence-layer] CreateInstance: next chain entry is null\n");
|
|
return VK_ERROR_INITIALIZATION_FAILED;
|
|
}
|
|
link->u.pLayerInfo = link->u.pLayerInfo->pNext;
|
|
const VkResult result = next_create(info, allocator, instance);
|
|
if (g_active) {
|
|
Log(" [vr-evidence-layer] CreateInstance result=%d api=%u\n",
|
|
static_cast<int>(result),
|
|
info != nullptr && info->pApplicationInfo != nullptr
|
|
? info->pApplicationInfo->apiVersion
|
|
: 0u);
|
|
}
|
|
if (result == VK_SUCCESS) {
|
|
g_instance_gipa = next_gipa;
|
|
if (instance != nullptr) {
|
|
g_instance = *instance;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL
|
|
LayerGetInstanceProcAddr(VkInstance instance, const char *name) {
|
|
if (name == nullptr) {
|
|
return nullptr;
|
|
}
|
|
if (std::strcmp(name, "vkGetInstanceProcAddr") == 0) {
|
|
return reinterpret_cast<PFN_vkVoidFunction>(LayerGetInstanceProcAddr);
|
|
}
|
|
if (std::strcmp(name, "vkCreateInstance") == 0) {
|
|
return reinterpret_cast<PFN_vkVoidFunction>(LayerCreateInstance);
|
|
}
|
|
if (std::strcmp(name, "vkCreateDevice") == 0) {
|
|
return reinterpret_cast<PFN_vkVoidFunction>(LayerCreateDevice);
|
|
}
|
|
if (g_instance_gipa != nullptr) {
|
|
return g_instance_gipa(instance, name);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
extern "C" {
|
|
|
|
MOCKTAIL_VR_LAYER_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL
|
|
vkGetInstanceProcAddr(VkInstance instance, const char *name) {
|
|
return LayerGetInstanceProcAddr(instance, name);
|
|
}
|
|
|
|
MOCKTAIL_VR_LAYER_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL
|
|
vkGetDeviceProcAddr(VkDevice device, const char *name) {
|
|
return LayerGetDeviceProcAddr(device, name);
|
|
}
|
|
|
|
MOCKTAIL_VR_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL
|
|
vkNegotiateLoaderLayerInterfaceVersion(VkNegotiateLayerInterface *version) {
|
|
if (version == nullptr ||
|
|
version->sType != LAYER_NEGOTIATE_INTERFACE_STRUCT) {
|
|
return VK_ERROR_INITIALIZATION_FAILED;
|
|
}
|
|
if (!g_active) {
|
|
const std::string directory = EvidenceDirectory();
|
|
if (!directory.empty()) {
|
|
g_active = true;
|
|
g_initializing_object = reinterpret_cast<InitializingObjectFn>(
|
|
dlsym(RTLD_DEFAULT, "mocktail_vr_initializing_object"));
|
|
g_current_eye = reinterpret_cast<CurrentEyeFn>(
|
|
dlsym(RTLD_DEFAULT, "mocktail_vr_current_eye"));
|
|
g_consume_eye = reinterpret_cast<ConsumeEyeFn>(
|
|
dlsym(RTLD_DEFAULT, "mocktail_vr_consume_eye"));
|
|
g_capture_present =
|
|
EnvUnsigned("MOCKTAIL_VR_EVIDENCE_CAPTURE_PRESENT", 60);
|
|
g_capture_render_passes =
|
|
EnvUnsigned("MOCKTAIL_VR_EVIDENCE_CAPTURE_RENDER_PASSES", 10);
|
|
Log(" [vr-evidence-layer] activated: dir=%s "
|
|
"capture_present=%u capture_render_passes=%u\n",
|
|
directory.c_str(), g_capture_present, g_capture_render_passes);
|
|
}
|
|
}
|
|
if (version->loaderLayerInterfaceVersion >= 2) {
|
|
version->pfnGetInstanceProcAddr = LayerGetInstanceProcAddr;
|
|
version->pfnGetDeviceProcAddr = LayerGetDeviceProcAddr;
|
|
version->pfnGetPhysicalDeviceProcAddr = nullptr;
|
|
version->loaderLayerInterfaceVersion = 2;
|
|
}
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
} // extern "C"
|