mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-12 11:31:08 +00:00
The chain copy that clears the emulated textureCompressionBC only knew a handful of structure sizes, so a valid chain such as VkPhysicalDeviceTimelineSemaphoreFeatures -> VkPhysicalDeviceFeatures2 was passed on unchanged and the compat layer refused the bit. Size every structure that may extend VkDeviceCreateInfo from a table generated from the Vulkan registry (gen_device_chain.py, Vulkan-Headers 1.3.275, the headers the build compiles with), and copy onto the heap instead of a fixed buffer. Only a structure newer than those headers still passes the chain unchanged. Add a regression test that runs the layer between a simulated loader and a mock driver: the timeline-semaphore chain, pEnabledFeatures, an unknown structure, the caller's structures left untouched, per-device YCbCr sampler tracking with handle reuse, and the KHR calibrated-timestamp forwarding. CI installs libvulkan-dev for it.
218 lines
12 KiB
C
218 lines
12 KiB
C
/* The Xclipse layer between a simulated loader and a mock driver. The driver refuses what the compat
|
|
* layer and Samsung's driver refuse: textureCompressionBC, and a compute layout holding an immutable
|
|
* YCbCr sampler. Built by test_xclipse_layer.py with the layer source included directly. */
|
|
#include "xclipse_layer.c"
|
|
#include <assert.h>
|
|
|
|
struct object { void *dispatch; };
|
|
static int instance_table, device_table[4];
|
|
static struct object mock_instance = {&instance_table}, mock_physical = {&instance_table};
|
|
static struct object mock_devices[4] = {{&device_table[0]}, {&device_table[1]}, {&device_table[2]}, {&device_table[3]}};
|
|
static int devices_created;
|
|
|
|
/* What the driver last saw. */
|
|
static int driver_saw_bc, driver_saw_layout_sampler_count;
|
|
static VkSampler driver_layout_samplers[16];
|
|
static uint64_t next_sampler = 0x100;
|
|
static VkSampler driver_ycbcr[16];
|
|
static int driver_ycbcr_count;
|
|
|
|
static VkResult VKAPI_CALL mock_create_instance(const VkInstanceCreateInfo *info, const VkAllocationCallbacks *allocator,
|
|
VkInstance *instance) {
|
|
*instance = (VkInstance)&mock_instance;
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
static void VKAPI_CALL mock_features(VkPhysicalDevice physical, VkPhysicalDeviceFeatures *features) {
|
|
memset(features, 0, sizeof(*features));
|
|
}
|
|
|
|
static void VKAPI_CALL mock_features2(VkPhysicalDevice physical, VkPhysicalDeviceFeatures2 *features) {
|
|
mock_features(physical, &features->features);
|
|
}
|
|
|
|
/* The compat layer's emulation: BC formats sample, the feature bit stays off. */
|
|
static void VKAPI_CALL mock_format_properties(VkPhysicalDevice physical, VkFormat format, VkFormatProperties *out) {
|
|
memset(out, 0, sizeof(*out));
|
|
if (format >= VK_FORMAT_BC1_RGB_UNORM_BLOCK && format <= VK_FORMAT_BC7_SRGB_BLOCK)
|
|
out->optimalTilingFeatures = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;
|
|
}
|
|
|
|
static void VKAPI_CALL mock_format_properties2(VkPhysicalDevice physical, VkFormat format, VkFormatProperties2 *out) {
|
|
mock_format_properties(physical, format, &out->formatProperties);
|
|
}
|
|
|
|
static VkResult VKAPI_CALL mock_time_domains(VkPhysicalDevice physical, uint32_t *count, VkTimeDomainEXT *domains) {
|
|
if (domains) { domains[0] = VK_TIME_DOMAIN_DEVICE_EXT; domains[1] = VK_TIME_DOMAIN_CLOCK_MONOTONIC_EXT; }
|
|
*count = 2;
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
static VkResult VKAPI_CALL mock_create_device(VkPhysicalDevice physical, const VkDeviceCreateInfo *info,
|
|
const VkAllocationCallbacks *allocator, VkDevice *device) {
|
|
driver_saw_bc = info->pEnabledFeatures && info->pEnabledFeatures->textureCompressionBC;
|
|
for (const VkBaseInStructure *p = info->pNext; p; p = p->pNext)
|
|
if (p->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 &&
|
|
((const VkPhysicalDeviceFeatures2 *)p)->features.textureCompressionBC) driver_saw_bc = 1;
|
|
if (driver_saw_bc) return VK_ERROR_FEATURE_NOT_PRESENT;
|
|
assert(devices_created < 4);
|
|
*device = (VkDevice)&mock_devices[devices_created++];
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
static VkResult VKAPI_CALL mock_create_sampler(VkDevice device, const VkSamplerCreateInfo *info,
|
|
const VkAllocationCallbacks *allocator, VkSampler *sampler) {
|
|
*sampler = (VkSampler)next_sampler++;
|
|
for (const VkBaseInStructure *p = info->pNext; p; p = p->pNext)
|
|
if (p->sType == VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO) driver_ycbcr[driver_ycbcr_count++] = *sampler;
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
static void VKAPI_CALL mock_destroy_sampler(VkDevice device, VkSampler sampler, const VkAllocationCallbacks *allocator) {
|
|
for (int i = 0; i < driver_ycbcr_count; i++)
|
|
if (driver_ycbcr[i] == sampler) driver_ycbcr[i] = driver_ycbcr[--driver_ycbcr_count];
|
|
}
|
|
|
|
static VkResult VKAPI_CALL mock_create_set_layout(VkDevice device, const VkDescriptorSetLayoutCreateInfo *info,
|
|
const VkAllocationCallbacks *allocator, VkDescriptorSetLayout *layout) {
|
|
driver_saw_layout_sampler_count = 0;
|
|
for (uint32_t b = 0; b < info->bindingCount; b++) {
|
|
const VkDescriptorSetLayoutBinding *binding = &info->pBindings[b];
|
|
for (uint32_t s = 0; binding->pImmutableSamplers && s < binding->descriptorCount; s++) {
|
|
VkSampler sampler = binding->pImmutableSamplers[s];
|
|
driver_layout_samplers[driver_saw_layout_sampler_count++] = sampler;
|
|
for (int i = 0; i < driver_ycbcr_count; i++)
|
|
if ((binding->stageFlags & VK_SHADER_STAGE_COMPUTE_BIT) && driver_ycbcr[i] == sampler)
|
|
return VK_ERROR_UNKNOWN;
|
|
}
|
|
}
|
|
*layout = (VkDescriptorSetLayout)0x5000;
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
static void VKAPI_CALL mock_destroy_device(VkDevice device, const VkAllocationCallbacks *allocator) {}
|
|
|
|
static VkResult VKAPI_CALL mock_timestamps(VkDevice device, uint32_t count, const VkCalibratedTimestampInfoEXT *infos,
|
|
uint64_t *timestamps, uint64_t *deviation) {
|
|
for (uint32_t i = 0; i < count; i++) timestamps[i] = 42;
|
|
*deviation = 1;
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
#define MOCK(name, function) if (!strcmp(requested, name)) return (PFN_vkVoidFunction)function
|
|
static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL mock_gipa(VkInstance instance, const char *requested) {
|
|
MOCK("vkCreateInstance", mock_create_instance);
|
|
MOCK("vkCreateDevice", mock_create_device);
|
|
MOCK("vkGetPhysicalDeviceFeatures", mock_features);
|
|
MOCK("vkGetPhysicalDeviceFeatures2", mock_features2);
|
|
MOCK("vkGetPhysicalDeviceFormatProperties", mock_format_properties);
|
|
MOCK("vkGetPhysicalDeviceFormatProperties2", mock_format_properties2);
|
|
MOCK("vkGetPhysicalDeviceCalibrateableTimeDomainsEXT", mock_time_domains);
|
|
return NULL;
|
|
}
|
|
|
|
static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL mock_gdpa(VkDevice device, const char *requested) {
|
|
MOCK("vkCreateSampler", mock_create_sampler);
|
|
MOCK("vkDestroySampler", mock_destroy_sampler);
|
|
MOCK("vkCreateDescriptorSetLayout", mock_create_set_layout);
|
|
MOCK("vkDestroyDevice", mock_destroy_device);
|
|
MOCK("vkGetCalibratedTimestampsEXT", mock_timestamps);
|
|
return NULL;
|
|
}
|
|
|
|
/* The loader's link for one vkCreateDevice call, then the caller's own chain. */
|
|
static VkResult create(const void *chain, const VkPhysicalDeviceFeatures *enabled, VkDevice *device) {
|
|
VkLayerDeviceLink link = {NULL, mock_gipa, mock_gdpa};
|
|
VkLayerDeviceCreateInfo loader = {VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO, chain, VK_LAYER_LINK_INFO, {&link}};
|
|
VkDeviceCreateInfo info = {VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, &loader, 0, 0, NULL, 0, NULL, 0, NULL, enabled};
|
|
PFN_vkCreateDevice create_device = (PFN_vkCreateDevice)vkGetInstanceProcAddr((VkInstance)&mock_instance, "vkCreateDevice");
|
|
return create_device((VkPhysicalDevice)&mock_physical, &info, NULL, device);
|
|
}
|
|
|
|
int main(void) {
|
|
VkNegotiateLayerInterface negotiate = {LAYER_NEGOTIATE_INTERFACE_STRUCT, NULL, 2, NULL, NULL, NULL};
|
|
assert(vkNegotiateLoaderLayerInterfaceVersion(&negotiate) == VK_SUCCESS);
|
|
|
|
VkLayerInstanceLink instance_link = {NULL, mock_gipa, NULL};
|
|
VkLayerInstanceCreateInfo instance_loader = {VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO, NULL, VK_LAYER_LINK_INFO,
|
|
{&instance_link}};
|
|
VkInstanceCreateInfo instance_info = {.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pNext = &instance_loader};
|
|
VkInstance instance;
|
|
PFN_vkCreateInstance create_instance_fn = (PFN_vkCreateInstance)vkGetInstanceProcAddr(NULL, "vkCreateInstance");
|
|
assert(create_instance_fn(&instance_info, NULL, &instance) == VK_SUCCESS);
|
|
|
|
/* The emulated bit is reported... */
|
|
VkPhysicalDeviceFeatures features;
|
|
((PFN_vkGetPhysicalDeviceFeatures)vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceFeatures"))(
|
|
(VkPhysicalDevice)&mock_physical, &features);
|
|
assert(features.textureCompressionBC);
|
|
|
|
/* ...and cleared before the driver, through pEnabledFeatures... */
|
|
VkDevice device;
|
|
VkPhysicalDeviceFeatures legacy = {.textureCompressionBC = VK_TRUE};
|
|
assert(create(NULL, &legacy, &device) == VK_SUCCESS && !driver_saw_bc && legacy.textureCompressionBC);
|
|
|
|
/* ...and through a VkPhysicalDeviceFeatures2 behind another structure, leaving the caller's chain intact. */
|
|
VkPhysicalDeviceVulkan12Features vk12 = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES};
|
|
VkPhysicalDeviceFeatures2 features2 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &vk12,
|
|
{.textureCompressionBC = VK_TRUE}};
|
|
VkPhysicalDeviceTimelineSemaphoreFeatures timeline = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES,
|
|
&features2, VK_TRUE};
|
|
VkPhysicalDeviceFeatures2 features2_before = features2;
|
|
VkPhysicalDeviceTimelineSemaphoreFeatures timeline_before = timeline;
|
|
VkDevice chained;
|
|
assert(create(&timeline, NULL, &chained) == VK_SUCCESS && !driver_saw_bc);
|
|
assert(!memcmp(&features2, &features2_before, sizeof(features2)));
|
|
assert(!memcmp(&timeline, &timeline_before, sizeof(timeline)));
|
|
|
|
/* A structure newer than the headers this was built with cannot be copied: the chain passes as it
|
|
* is and the driver refuses the bit, without the caller's structures being written. */
|
|
struct { VkStructureType sType; const void *pNext; uint32_t payload; } future = {(VkStructureType)0x7ffffff0, &features2, 7};
|
|
VkDevice unused;
|
|
assert(create(&future, NULL, &unused) == VK_ERROR_FEATURE_NOT_PRESENT);
|
|
assert(!memcmp(&features2, &features2_before, sizeof(features2)) && future.pNext == &features2);
|
|
|
|
/* Compute layouts get a plain sampler in place of a YCbCr one. */
|
|
PFN_vkCreateSampler create_sampler_fn = (PFN_vkCreateSampler)vkGetDeviceProcAddr(device, "vkCreateSampler");
|
|
PFN_vkDestroySampler destroy_sampler_fn = (PFN_vkDestroySampler)vkGetDeviceProcAddr(device, "vkDestroySampler");
|
|
PFN_vkCreateDescriptorSetLayout create_layout = (PFN_vkCreateDescriptorSetLayout)vkGetDeviceProcAddr(device, "vkCreateDescriptorSetLayout");
|
|
VkSamplerYcbcrConversionInfo conversion = {VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO, NULL, (VkSamplerYcbcrConversion)1};
|
|
VkSamplerCreateInfo ycbcr_info = {.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .pNext = &conversion};
|
|
VkSamplerCreateInfo plain_info = {.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
|
|
VkSampler ycbcr;
|
|
assert(create_sampler_fn(device, &ycbcr_info, NULL, &ycbcr) == VK_SUCCESS);
|
|
VkSampler immutable[2] = {ycbcr, ycbcr};
|
|
VkDescriptorSetLayoutBinding binding = {4, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, VK_SHADER_STAGE_COMPUTE_BIT, immutable};
|
|
VkDescriptorSetLayoutCreateInfo layout_info = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, NULL, 0, 1, &binding};
|
|
VkDescriptorSetLayout layout;
|
|
assert(create_layout(device, &layout_info, NULL, &layout) == VK_SUCCESS);
|
|
assert(driver_layout_samplers[0] != ycbcr && immutable[0] == ycbcr);
|
|
|
|
/* A destroyed YCbCr sampler's handle, reused for a plain sampler, is left alone. */
|
|
destroy_sampler_fn(device, ycbcr, NULL);
|
|
next_sampler = (uint64_t)ycbcr;
|
|
VkSampler reused;
|
|
assert(create_sampler_fn(device, &plain_info, NULL, &reused) == VK_SUCCESS && reused == ycbcr);
|
|
immutable[0] = immutable[1] = reused;
|
|
assert(create_layout(device, &layout_info, NULL, &layout) == VK_SUCCESS && driver_layout_samplers[0] == reused);
|
|
|
|
/* Registrations belong to their device and go with it. */
|
|
VkSampler other;
|
|
assert(create_sampler_fn(chained, &ycbcr_info, NULL, &other) == VK_SUCCESS && ycbcr_count == 1);
|
|
((PFN_vkDestroyDevice)vkGetDeviceProcAddr(chained, "vkDestroyDevice"))(chained, NULL);
|
|
assert(ycbcr_count == 0);
|
|
|
|
/* The KHR calibrated-timestamp entry points reach the EXT ones. */
|
|
uint32_t domains = 0;
|
|
PFN_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT time_domains = (PFN_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT)
|
|
vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceCalibrateableTimeDomainsKHR");
|
|
assert(time_domains((VkPhysicalDevice)&mock_physical, &domains, NULL) == VK_SUCCESS && domains == 2);
|
|
VkCalibratedTimestampInfoEXT stamp = {VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_EXT, NULL, VK_TIME_DOMAIN_DEVICE_EXT};
|
|
uint64_t value = 0, deviation = 0;
|
|
PFN_vkGetCalibratedTimestampsEXT timestamps = (PFN_vkGetCalibratedTimestampsEXT)vkGetDeviceProcAddr(device, "vkGetCalibratedTimestampsKHR");
|
|
assert(timestamps(device, 1, &stamp, &value, &deviation) == VK_SUCCESS && value == 42);
|
|
|
|
puts("xclipse layer ok");
|
|
return 0;
|
|
}
|