mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 02:51:10 +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.
494 lines
25 KiB
C
494 lines
25 KiB
C
/*
|
|
* VK_LAYER_DROIDDECK_xclipse: workarounds for Samsung's Xclipse driver loaded through libhybris.
|
|
*
|
|
* 1. bionic environ. A glibc process never initializes bionic's `environ`; the driver's
|
|
* AMD-derived shader compiler walks it for AMD_SC_* settings and crashed on the NULL.
|
|
* Once instance creation has loaded the driver, point it at a snapshot of ours.
|
|
* 2. YCbCr immutable samplers in compute. Compute pipelines whose layout binds an array of
|
|
* immutable YCbCr-conversion samplers fail to compile (VK_ERROR_UNKNOWN); gamescope's
|
|
* composite shaders all bind one. Such bindings get a plain linear sampler instead, so only
|
|
* multi-planar (NV12) layers sample incorrectly.
|
|
* 3. Calibrated timestamps. The driver has only VK_EXT_calibrated_timestamps, but the bridge
|
|
* lists the KHR names too, and Wine calls those; the loader then aborts (it took down Wine's
|
|
* explorer.exe and with it every game window). The KHR entry points forward to the EXT ones.
|
|
* 4. BC4/BC5 filtering. The libhybris compat layer emulates BC textures; BC4/BC5 decode into
|
|
* R16/R16G16 images, for which this driver advertises no linear filtering, so DXVK would
|
|
* treat them as unfilterable. The layer reports linear filtering for BC4/BC5 where they are
|
|
* sampled at all (RDNA filters 16-bit normalized formats; at worst they sample unfiltered).
|
|
* 5. textureCompressionBC. The compat layer emulates BC formats but keeps the feature bit off;
|
|
* DXVK before 3.0 (the last that fits this driver's 128 bytes of push constants) requires
|
|
* it. The layer reports it while BC formats are sampleable and clears it at device creation.
|
|
* Everything else passes straight through.
|
|
*/
|
|
#define _GNU_SOURCE
|
|
#include <vulkan/vk_layer.h>
|
|
#include <vulkan/vulkan.h>
|
|
#include <dlfcn.h>
|
|
#include <pthread.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define EXPORT __attribute__((visibility("default")))
|
|
#define MAX_OBJECTS 64
|
|
|
|
extern char **environ;
|
|
|
|
/* The loader's dispatch table pointer heads every dispatchable object: an instance shares it
|
|
* with its physical devices, a device with its queues and command buffers. */
|
|
typedef void *dispatch_key;
|
|
static dispatch_key key_of(const void *object) { return *(dispatch_key *)object; }
|
|
|
|
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
|
|
static int debug;
|
|
|
|
struct instance_data {
|
|
dispatch_key key;
|
|
VkInstance handle;
|
|
PFN_vkGetInstanceProcAddr next;
|
|
PFN_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT time_domains;
|
|
PFN_vkGetPhysicalDeviceFeatures features;
|
|
PFN_vkGetPhysicalDeviceFeatures2 features2;
|
|
PFN_vkGetPhysicalDeviceFormatProperties format_properties;
|
|
PFN_vkGetPhysicalDeviceFormatProperties2 format_properties2;
|
|
};
|
|
struct device_data {
|
|
dispatch_key key;
|
|
VkDevice handle;
|
|
PFN_vkGetDeviceProcAddr next;
|
|
PFN_vkCreateSampler create_sampler;
|
|
PFN_vkDestroySampler destroy_sampler;
|
|
PFN_vkCreateDescriptorSetLayout create_set_layout;
|
|
PFN_vkDestroyDevice destroy_device;
|
|
PFN_vkGetCalibratedTimestampsEXT timestamps;
|
|
VkSampler fallback;
|
|
};
|
|
static struct instance_data instances[MAX_OBJECTS];
|
|
static struct device_data devices[MAX_OBJECTS];
|
|
/* The live YCbCr samplers each device created, so set layouts can recognise them. Guarded by lock. */
|
|
static struct ycbcr_sampler { VkDevice device; VkSampler sampler; } *ycbcr_samplers;
|
|
static size_t ycbcr_count, ycbcr_capacity;
|
|
|
|
static struct instance_data *instance_by_key(dispatch_key key) {
|
|
for (int i = 0; i < MAX_OBJECTS; i++)
|
|
if (instances[i].handle && instances[i].key == key) return &instances[i];
|
|
return NULL;
|
|
}
|
|
|
|
static struct device_data *device_by_key(dispatch_key key) {
|
|
for (int i = 0; i < MAX_OBJECTS; i++)
|
|
if (devices[i].handle && devices[i].key == key) return &devices[i];
|
|
return NULL;
|
|
}
|
|
|
|
/* Entries are never freed while their object lives, so a looked-up copy of the pointers is safe. */
|
|
static struct instance_data instance_of(const void *object) {
|
|
struct instance_data copy = {0};
|
|
pthread_mutex_lock(&lock);
|
|
struct instance_data *data = instance_by_key(key_of(object));
|
|
if (data) copy = *data;
|
|
pthread_mutex_unlock(&lock);
|
|
return copy;
|
|
}
|
|
|
|
static struct device_data device_of(const void *object) {
|
|
struct device_data copy = {0};
|
|
pthread_mutex_lock(&lock);
|
|
struct device_data *data = device_by_key(key_of(object));
|
|
if (data) copy = *data;
|
|
pthread_mutex_unlock(&lock);
|
|
return copy;
|
|
}
|
|
|
|
static void fix_bionic_environ(void) {
|
|
void *common = dlopen("libhybris-common.so.1", RTLD_NOW | RTLD_NOLOAD);
|
|
if (!common) return; /* not a libhybris driver */
|
|
void *(*h_dlopen)(const char *, int) = (void *(*)(const char *, int))dlsym(common, "hybris_dlopen");
|
|
void *(*h_dlsym)(void *, const char *) = (void *(*)(void *, const char *))dlsym(common, "hybris_dlsym");
|
|
if (!h_dlopen || !h_dlsym) return;
|
|
void *libc = h_dlopen("libc.so", RTLD_NOW);
|
|
char ***bionic_environ = libc ? (char ***)h_dlsym(libc, "environ") : NULL;
|
|
if (!bionic_environ || *bionic_environ) return;
|
|
size_t count = 0;
|
|
while (environ && environ[count]) count++;
|
|
char **snapshot = calloc(count + 1, sizeof(*snapshot));
|
|
if (!snapshot) return;
|
|
for (size_t i = 0; i < count; i++) snapshot[i] = strdup(environ[i]);
|
|
*bionic_environ = snapshot;
|
|
if (debug) fprintf(stderr, "xclipse layer: bionic environ set (%zu entries)\n", count);
|
|
}
|
|
|
|
/* ---- instance ---- */
|
|
|
|
static VkResult VKAPI_CALL create_instance(const VkInstanceCreateInfo *info, const VkAllocationCallbacks *allocator,
|
|
VkInstance *instance) {
|
|
VkLayerInstanceCreateInfo *chain = (VkLayerInstanceCreateInfo *)info->pNext;
|
|
while (chain && !(chain->sType == VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO && chain->function == VK_LAYER_LINK_INFO))
|
|
chain = (VkLayerInstanceCreateInfo *)chain->pNext;
|
|
if (!chain || !chain->u.pLayerInfo) return VK_ERROR_INITIALIZATION_FAILED;
|
|
PFN_vkGetInstanceProcAddr next = chain->u.pLayerInfo->pfnNextGetInstanceProcAddr;
|
|
chain->u.pLayerInfo = chain->u.pLayerInfo->pNext;
|
|
PFN_vkCreateInstance next_create = (PFN_vkCreateInstance)next(NULL, "vkCreateInstance");
|
|
if (!next_create) return VK_ERROR_INITIALIZATION_FAILED;
|
|
VkResult result = next_create(info, allocator, instance);
|
|
if (result != VK_SUCCESS) return result;
|
|
fix_bionic_environ();
|
|
struct instance_data data = {
|
|
.key = key_of(*instance),
|
|
.handle = *instance,
|
|
.next = next,
|
|
.time_domains = (PFN_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT)
|
|
next(*instance, "vkGetPhysicalDeviceCalibrateableTimeDomainsEXT"),
|
|
.features = (PFN_vkGetPhysicalDeviceFeatures)next(*instance, "vkGetPhysicalDeviceFeatures"),
|
|
.features2 = (PFN_vkGetPhysicalDeviceFeatures2)next(*instance, "vkGetPhysicalDeviceFeatures2"),
|
|
.format_properties = (PFN_vkGetPhysicalDeviceFormatProperties)next(*instance, "vkGetPhysicalDeviceFormatProperties"),
|
|
.format_properties2 = (PFN_vkGetPhysicalDeviceFormatProperties2)next(*instance, "vkGetPhysicalDeviceFormatProperties2"),
|
|
};
|
|
pthread_mutex_lock(&lock);
|
|
for (int i = 0; i < MAX_OBJECTS; i++)
|
|
if (!instances[i].handle) { instances[i] = data; break; }
|
|
pthread_mutex_unlock(&lock);
|
|
return result;
|
|
}
|
|
|
|
static void VKAPI_CALL destroy_instance(VkInstance instance, const VkAllocationCallbacks *allocator) {
|
|
PFN_vkGetInstanceProcAddr next = instance_of(instance).next;
|
|
pthread_mutex_lock(&lock);
|
|
struct instance_data *data = instance_by_key(key_of(instance));
|
|
if (data) memset(data, 0, sizeof(*data));
|
|
pthread_mutex_unlock(&lock);
|
|
PFN_vkDestroyInstance next_destroy = next ? (PFN_vkDestroyInstance)next(instance, "vkDestroyInstance") : NULL;
|
|
if (next_destroy) next_destroy(instance, allocator);
|
|
}
|
|
|
|
static VkResult VKAPI_CALL time_domains_khr(VkPhysicalDevice physical, uint32_t *count, VkTimeDomainEXT *domains) {
|
|
PFN_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT ext = instance_of(physical).time_domains;
|
|
if (!ext) { *count = 0; return VK_SUCCESS; }
|
|
return ext(physical, count, domains);
|
|
}
|
|
|
|
/* BC formats sampleable below us although the driver's feature bit is off: the compat layer emulates them. */
|
|
static int bc_emulated(VkPhysicalDevice physical) {
|
|
struct instance_data data = instance_of(physical);
|
|
if (!data.features || !data.format_properties) return 0;
|
|
VkPhysicalDeviceFeatures native;
|
|
data.features(physical, &native);
|
|
if (native.textureCompressionBC) return 0;
|
|
VkFormatProperties bc1, bc7;
|
|
data.format_properties(physical, VK_FORMAT_BC1_RGBA_UNORM_BLOCK, &bc1);
|
|
data.format_properties(physical, VK_FORMAT_BC7_UNORM_BLOCK, &bc7);
|
|
return (bc1.optimalTilingFeatures & bc7.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) != 0;
|
|
}
|
|
|
|
static void VKAPI_CALL get_features(VkPhysicalDevice physical, VkPhysicalDeviceFeatures *features) {
|
|
PFN_vkGetPhysicalDeviceFeatures next = instance_of(physical).features;
|
|
if (next) next(physical, features);
|
|
if (bc_emulated(physical)) features->textureCompressionBC = VK_TRUE;
|
|
}
|
|
|
|
static void VKAPI_CALL get_features2(VkPhysicalDevice physical, VkPhysicalDeviceFeatures2 *features) {
|
|
PFN_vkGetPhysicalDeviceFeatures2 next = instance_of(physical).features2;
|
|
if (next) next(physical, features);
|
|
if (bc_emulated(physical)) features->features.textureCompressionBC = VK_TRUE;
|
|
}
|
|
|
|
static int bc45(VkFormat format) {
|
|
return format >= VK_FORMAT_BC4_UNORM_BLOCK && format <= VK_FORMAT_BC5_SNORM_BLOCK;
|
|
}
|
|
|
|
static void VKAPI_CALL get_format_properties(VkPhysicalDevice physical, VkFormat format, VkFormatProperties *out) {
|
|
PFN_vkGetPhysicalDeviceFormatProperties next = instance_of(physical).format_properties;
|
|
if (next) next(physical, format, out);
|
|
if (bc45(format) && (out->optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT))
|
|
out->optimalTilingFeatures |= VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
|
|
}
|
|
|
|
static void VKAPI_CALL get_format_properties2(VkPhysicalDevice physical, VkFormat format, VkFormatProperties2 *out) {
|
|
PFN_vkGetPhysicalDeviceFormatProperties2 next = instance_of(physical).format_properties2;
|
|
if (next) next(physical, format, out);
|
|
if (!bc45(format)) return;
|
|
if (out->formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)
|
|
out->formatProperties.optimalTilingFeatures |= VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
|
|
for (VkBaseOutStructure *p = (VkBaseOutStructure *)out->pNext; p; p = p->pNext) {
|
|
if (p->sType != VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3) continue;
|
|
VkFormatProperties3 *f3 = (VkFormatProperties3 *)p;
|
|
if (f3->optimalTilingFeatures & VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_BIT)
|
|
f3->optimalTilingFeatures |= VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
|
|
}
|
|
}
|
|
|
|
/* ---- device ---- */
|
|
|
|
/* The size of each structure a VkDeviceCreateInfo chain may hold (device_chain.inc, generated from the
|
|
* Vulkan registry), plus the loader's own; 0 for anything else. */
|
|
static size_t chain_size(VkStructureType type) {
|
|
switch (type) {
|
|
#define DEVICE_CHAIN(stype, name) case stype: return sizeof(name);
|
|
#include "device_chain.inc"
|
|
#undef DEVICE_CHAIN
|
|
case VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO: return sizeof(VkLayerDeviceCreateInfo);
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
struct chain_copy { void **nodes; size_t count; };
|
|
|
|
static void free_chain_copy(struct chain_copy *copy) {
|
|
for (size_t i = 0; i < copy->count; i++) free(copy->nodes[i]);
|
|
free(copy->nodes);
|
|
copy->nodes = NULL;
|
|
copy->count = 0;
|
|
}
|
|
|
|
/* The chain with textureCompressionBC cleared in a copy of the VkPhysicalDeviceFeatures2 that enables
|
|
* it: every structure up to it is copied into [copy], the rest is shared. The caller's structures are
|
|
* never written. *head_out stays [head] when nothing needs clearing, or when a structure ahead of it
|
|
* is unknown to this build (newer than its Vulkan headers); the compat layer then refuses the bit. */
|
|
static VkResult chain_without_bc(const void *head, struct chain_copy *copy, const void **head_out) {
|
|
*head_out = head;
|
|
const VkBaseInStructure *target = NULL;
|
|
size_t count = 0;
|
|
for (const VkBaseInStructure *p = head; p && !target; p = p->pNext) {
|
|
if (!chain_size(p->sType)) return VK_SUCCESS;
|
|
count++;
|
|
if (p->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 &&
|
|
((const VkPhysicalDeviceFeatures2 *)p)->features.textureCompressionBC) target = p;
|
|
}
|
|
if (!target) return VK_SUCCESS;
|
|
if (!(copy->nodes = calloc(count, sizeof(*copy->nodes)))) return VK_ERROR_OUT_OF_HOST_MEMORY;
|
|
VkBaseOutStructure *first = NULL, *last = NULL;
|
|
for (const VkBaseInStructure *p = head; ; p = p->pNext) {
|
|
size_t size = chain_size(p->sType);
|
|
VkBaseOutStructure *node = malloc(size);
|
|
if (!node) { free_chain_copy(copy); return VK_ERROR_OUT_OF_HOST_MEMORY; }
|
|
copy->nodes[copy->count++] = node;
|
|
memcpy(node, p, size);
|
|
if (last) last->pNext = node; else first = node;
|
|
last = node;
|
|
if (p == target) {
|
|
((VkPhysicalDeviceFeatures2 *)node)->features.textureCompressionBC = VK_FALSE;
|
|
*head_out = first; /* its pNext still leads to the caller's remaining structures */
|
|
return VK_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
|
|
static VkResult VKAPI_CALL create_device(VkPhysicalDevice physical, const VkDeviceCreateInfo *info,
|
|
const VkAllocationCallbacks *allocator, VkDevice *device) {
|
|
VkLayerDeviceCreateInfo *chain = (VkLayerDeviceCreateInfo *)info->pNext;
|
|
while (chain && !(chain->sType == VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO && chain->function == VK_LAYER_LINK_INFO))
|
|
chain = (VkLayerDeviceCreateInfo *)chain->pNext;
|
|
if (!chain || !chain->u.pLayerInfo) return VK_ERROR_INITIALIZATION_FAILED;
|
|
PFN_vkGetInstanceProcAddr next_gipa = chain->u.pLayerInfo->pfnNextGetInstanceProcAddr;
|
|
PFN_vkGetDeviceProcAddr next_gdpa = chain->u.pLayerInfo->pfnNextGetDeviceProcAddr;
|
|
chain->u.pLayerInfo = chain->u.pLayerInfo->pNext;
|
|
PFN_vkCreateDevice next_create = (PFN_vkCreateDevice)next_gipa(NULL, "vkCreateDevice");
|
|
if (!next_create) return VK_ERROR_INITIALIZATION_FAILED;
|
|
|
|
VkDeviceCreateInfo patched = *info;
|
|
/* An emulated textureCompressionBC never reaches the compat layer, which refuses the bit. */
|
|
int strip_bc = bc_emulated(physical);
|
|
VkPhysicalDeviceFeatures plain_features;
|
|
if (strip_bc && info->pEnabledFeatures && info->pEnabledFeatures->textureCompressionBC) {
|
|
plain_features = *info->pEnabledFeatures;
|
|
plain_features.textureCompressionBC = VK_FALSE;
|
|
patched.pEnabledFeatures = &plain_features;
|
|
}
|
|
struct chain_copy copy = {0};
|
|
if (strip_bc) {
|
|
VkResult copied = chain_without_bc(info->pNext, ©, &patched.pNext);
|
|
if (copied != VK_SUCCESS) return copied;
|
|
}
|
|
VkResult result = next_create(physical, &patched, allocator, device);
|
|
free_chain_copy(©);
|
|
if (result != VK_SUCCESS) return result;
|
|
|
|
PFN_vkCreateSampler create_sampler = (PFN_vkCreateSampler)next_gdpa(*device, "vkCreateSampler");
|
|
VkSamplerCreateInfo plain = {
|
|
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
|
.magFilter = VK_FILTER_LINEAR, .minFilter = VK_FILTER_LINEAR,
|
|
.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
|
|
.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
|
|
.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
|
|
.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK,
|
|
};
|
|
VkSampler fallback = VK_NULL_HANDLE;
|
|
if (create_sampler) create_sampler(*device, &plain, NULL, &fallback);
|
|
struct device_data data = {
|
|
.key = key_of(*device),
|
|
.handle = *device,
|
|
.next = next_gdpa,
|
|
.create_sampler = create_sampler,
|
|
.destroy_sampler = (PFN_vkDestroySampler)next_gdpa(*device, "vkDestroySampler"),
|
|
.create_set_layout = (PFN_vkCreateDescriptorSetLayout)next_gdpa(*device, "vkCreateDescriptorSetLayout"),
|
|
.destroy_device = (PFN_vkDestroyDevice)next_gdpa(*device, "vkDestroyDevice"),
|
|
.timestamps = (PFN_vkGetCalibratedTimestampsEXT)next_gdpa(*device, "vkGetCalibratedTimestampsEXT"),
|
|
.fallback = fallback,
|
|
};
|
|
pthread_mutex_lock(&lock);
|
|
for (int i = 0; i < MAX_OBJECTS; i++)
|
|
if (!devices[i].handle) { devices[i] = data; break; }
|
|
pthread_mutex_unlock(&lock);
|
|
return result;
|
|
}
|
|
|
|
static void VKAPI_CALL destroy_device(VkDevice device, const VkAllocationCallbacks *allocator) {
|
|
struct device_data data = device_of(device);
|
|
pthread_mutex_lock(&lock);
|
|
struct device_data *entry = device_by_key(key_of(device));
|
|
if (entry) memset(entry, 0, sizeof(*entry));
|
|
for (size_t k = 0; k < ycbcr_count;)
|
|
if (ycbcr_samplers[k].device == device) ycbcr_samplers[k] = ycbcr_samplers[--ycbcr_count];
|
|
else k++;
|
|
pthread_mutex_unlock(&lock);
|
|
if (data.fallback && data.destroy_sampler) data.destroy_sampler(device, data.fallback, NULL);
|
|
if (data.destroy_device) data.destroy_device(device, allocator);
|
|
}
|
|
|
|
static VkResult VKAPI_CALL timestamps_khr(VkDevice device, uint32_t count, const VkCalibratedTimestampInfoEXT *infos,
|
|
uint64_t *timestamps, uint64_t *deviation) {
|
|
PFN_vkGetCalibratedTimestampsEXT ext = device_of(device).timestamps;
|
|
return ext ? ext(device, count, infos, timestamps, deviation) : VK_ERROR_FEATURE_NOT_PRESENT;
|
|
}
|
|
|
|
static VkResult VKAPI_CALL create_sampler(VkDevice device, const VkSamplerCreateInfo *info,
|
|
const VkAllocationCallbacks *allocator, VkSampler *sampler) {
|
|
PFN_vkCreateSampler next = device_of(device).create_sampler;
|
|
if (!next) return VK_ERROR_INITIALIZATION_FAILED;
|
|
VkResult result = next(device, info, allocator, sampler);
|
|
if (result != VK_SUCCESS) return result;
|
|
int ycbcr = 0;
|
|
for (const VkBaseInStructure *p = info->pNext; p && !ycbcr; p = p->pNext)
|
|
ycbcr = p->sType == VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO;
|
|
if (!ycbcr) return result;
|
|
pthread_mutex_lock(&lock);
|
|
if (ycbcr_count == ycbcr_capacity) {
|
|
size_t capacity = ycbcr_capacity ? ycbcr_capacity * 2 : 16;
|
|
struct ycbcr_sampler *grown = realloc(ycbcr_samplers, capacity * sizeof(*grown));
|
|
if (!grown) {
|
|
pthread_mutex_unlock(&lock);
|
|
/* An untracked YCbCr sampler would reach a compute layout unreplaced: fail instead. */
|
|
PFN_vkDestroySampler destroy = device_of(device).destroy_sampler;
|
|
if (destroy) destroy(device, *sampler, allocator);
|
|
*sampler = VK_NULL_HANDLE;
|
|
return VK_ERROR_OUT_OF_HOST_MEMORY;
|
|
}
|
|
ycbcr_samplers = grown;
|
|
ycbcr_capacity = capacity;
|
|
}
|
|
ycbcr_samplers[ycbcr_count++] = (struct ycbcr_sampler){device, *sampler};
|
|
pthread_mutex_unlock(&lock);
|
|
return result;
|
|
}
|
|
|
|
static void VKAPI_CALL destroy_sampler(VkDevice device, VkSampler sampler, const VkAllocationCallbacks *allocator) {
|
|
pthread_mutex_lock(&lock);
|
|
for (size_t k = 0; k < ycbcr_count; k++)
|
|
if (ycbcr_samplers[k].device == device && ycbcr_samplers[k].sampler == sampler) {
|
|
ycbcr_samplers[k] = ycbcr_samplers[--ycbcr_count];
|
|
break;
|
|
}
|
|
pthread_mutex_unlock(&lock);
|
|
PFN_vkDestroySampler next = device_of(device).destroy_sampler;
|
|
if (next) next(device, sampler, allocator);
|
|
}
|
|
|
|
/* Called with lock held. */
|
|
static int is_ycbcr(VkDevice device, VkSampler sampler) {
|
|
for (size_t k = 0; sampler && k < ycbcr_count; k++)
|
|
if (ycbcr_samplers[k].device == device && ycbcr_samplers[k].sampler == sampler) return 1;
|
|
return 0;
|
|
}
|
|
|
|
static VkResult VKAPI_CALL create_set_layout(VkDevice device, const VkDescriptorSetLayoutCreateInfo *info,
|
|
const VkAllocationCallbacks *allocator, VkDescriptorSetLayout *layout) {
|
|
struct device_data data = device_of(device);
|
|
if (!data.create_set_layout) return VK_ERROR_INITIALIZATION_FAILED;
|
|
int replace = 0;
|
|
pthread_mutex_lock(&lock);
|
|
for (uint32_t b = 0; data.fallback && b < info->bindingCount && !replace; b++) {
|
|
const VkDescriptorSetLayoutBinding *binding = &info->pBindings[b];
|
|
if (!(binding->stageFlags & VK_SHADER_STAGE_COMPUTE_BIT) || !binding->pImmutableSamplers) continue;
|
|
for (uint32_t s = 0; s < binding->descriptorCount; s++)
|
|
if (is_ycbcr(device, binding->pImmutableSamplers[s])) { replace = 1; break; }
|
|
}
|
|
pthread_mutex_unlock(&lock);
|
|
if (!replace) return data.create_set_layout(device, info, allocator, layout);
|
|
|
|
VkResult result = VK_ERROR_OUT_OF_HOST_MEMORY;
|
|
VkDescriptorSetLayoutBinding *bindings = calloc(info->bindingCount, sizeof(*bindings));
|
|
VkSampler **arrays = calloc(info->bindingCount, sizeof(*arrays));
|
|
if (!bindings || !arrays) goto done;
|
|
for (uint32_t b = 0; b < info->bindingCount; b++) {
|
|
bindings[b] = info->pBindings[b];
|
|
if (!(bindings[b].stageFlags & VK_SHADER_STAGE_COMPUTE_BIT) || !bindings[b].pImmutableSamplers) continue;
|
|
/* Without the replacement the driver would see the samplers it cannot compile: fail instead. */
|
|
if (!(arrays[b] = malloc(bindings[b].descriptorCount * sizeof(VkSampler)))) goto done;
|
|
pthread_mutex_lock(&lock);
|
|
for (uint32_t s = 0; s < bindings[b].descriptorCount; s++) {
|
|
VkSampler original = bindings[b].pImmutableSamplers[s];
|
|
arrays[b][s] = is_ycbcr(device, original) ? data.fallback : original;
|
|
}
|
|
pthread_mutex_unlock(&lock);
|
|
bindings[b].pImmutableSamplers = arrays[b];
|
|
}
|
|
VkDescriptorSetLayoutCreateInfo patched = *info;
|
|
patched.pBindings = bindings;
|
|
result = data.create_set_layout(device, &patched, allocator, layout);
|
|
if (debug) fprintf(stderr, "xclipse layer: compute YCbCr immutable samplers replaced (%d)\n", result);
|
|
done:
|
|
for (uint32_t b = 0; arrays && b < info->bindingCount; b++) free(arrays[b]);
|
|
free(arrays);
|
|
free(bindings);
|
|
return result;
|
|
}
|
|
|
|
/* ---- dispatch ---- */
|
|
|
|
static PFN_vkVoidFunction device_hook(const char *name) {
|
|
if (!strcmp(name, "vkGetDeviceProcAddr")) return NULL; /* handled by the caller */
|
|
if (!strcmp(name, "vkDestroyDevice")) return (PFN_vkVoidFunction)destroy_device;
|
|
if (!strcmp(name, "vkCreateSampler")) return (PFN_vkVoidFunction)create_sampler;
|
|
if (!strcmp(name, "vkDestroySampler")) return (PFN_vkVoidFunction)destroy_sampler;
|
|
if (!strcmp(name, "vkCreateDescriptorSetLayout")) return (PFN_vkVoidFunction)create_set_layout;
|
|
if (!strcmp(name, "vkGetCalibratedTimestampsKHR")) return (PFN_vkVoidFunction)timestamps_khr;
|
|
return NULL;
|
|
}
|
|
|
|
EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkDevice device, const char *name) {
|
|
if (!strcmp(name, "vkGetDeviceProcAddr")) return (PFN_vkVoidFunction)vkGetDeviceProcAddr;
|
|
PFN_vkVoidFunction hook = device_hook(name);
|
|
if (hook) return hook;
|
|
struct device_data data = device ? device_of(device) : (struct device_data){0};
|
|
return data.next ? data.next(device, name) : NULL;
|
|
}
|
|
|
|
EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(VkInstance instance, const char *name) {
|
|
if (!strcmp(name, "vkGetInstanceProcAddr")) return (PFN_vkVoidFunction)vkGetInstanceProcAddr;
|
|
if (!strcmp(name, "vkCreateInstance")) return (PFN_vkVoidFunction)create_instance;
|
|
if (!strcmp(name, "vkDestroyInstance")) return (PFN_vkVoidFunction)destroy_instance;
|
|
if (!strcmp(name, "vkCreateDevice")) return (PFN_vkVoidFunction)create_device;
|
|
if (!strcmp(name, "vkGetDeviceProcAddr")) return (PFN_vkVoidFunction)vkGetDeviceProcAddr;
|
|
if (!strcmp(name, "vkGetPhysicalDeviceFeatures")) return (PFN_vkVoidFunction)get_features;
|
|
if (!strcmp(name, "vkGetPhysicalDeviceFeatures2") || !strcmp(name, "vkGetPhysicalDeviceFeatures2KHR"))
|
|
return (PFN_vkVoidFunction)get_features2;
|
|
if (!strcmp(name, "vkGetPhysicalDeviceCalibrateableTimeDomainsKHR")) return (PFN_vkVoidFunction)time_domains_khr;
|
|
if (!strcmp(name, "vkGetPhysicalDeviceFormatProperties")) return (PFN_vkVoidFunction)get_format_properties;
|
|
if (!strcmp(name, "vkGetPhysicalDeviceFormatProperties2") || !strcmp(name, "vkGetPhysicalDeviceFormatProperties2KHR"))
|
|
return (PFN_vkVoidFunction)get_format_properties2;
|
|
PFN_vkVoidFunction hook = device_hook(name);
|
|
if (hook) return hook;
|
|
PFN_vkGetInstanceProcAddr next = instance ? instance_of(instance).next : NULL;
|
|
return next ? next(instance, name) : NULL;
|
|
}
|
|
|
|
EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkNegotiateLoaderLayerInterfaceVersion(VkNegotiateLayerInterface *negotiate) {
|
|
if (!negotiate || negotiate->loaderLayerInterfaceVersion < 2) return VK_ERROR_INITIALIZATION_FAILED;
|
|
debug = getenv("DROIDDECK_XCLIPSE_LAYER_DEBUG") != NULL;
|
|
negotiate->loaderLayerInterfaceVersion = 2;
|
|
negotiate->pfnGetInstanceProcAddr = vkGetInstanceProcAddr;
|
|
negotiate->pfnGetDeviceProcAddr = vkGetDeviceProcAddr;
|
|
negotiate->pfnGetPhysicalDeviceProcAddr = NULL;
|
|
return VK_SUCCESS;
|
|
}
|