mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 11:01:07 +00:00
Xclipse layer: copy the device chain and track samplers per device
- Clear textureCompressionBC in a copy of the device-create chain (every structure up to the VkPhysicalDeviceFeatures2 that enables it) instead of writing the caller's structure; a chain led by a structure of unknown size is passed on unchanged. - Track YCbCr samplers per device in a growable list, drop them on vkDestroySampler and device teardown, and fail sampler creation rather than leave one untracked when the list cannot grow. - Fail descriptor set layout creation with VK_ERROR_OUT_OF_HOST_MEMORY when a replacement sampler array cannot be allocated, instead of passing the original YCbCr samplers to the driver.
This commit is contained in:
1 parent
44866db692
commit
db034d6598
1 file changed
+102
-28
@@ -31,7 +31,6 @@
|
||||
|
||||
#define EXPORT __attribute__((visibility("default")))
|
||||
#define MAX_OBJECTS 64
|
||||
#define MAX_YCBCR_SAMPLERS 256
|
||||
|
||||
extern char **environ;
|
||||
|
||||
@@ -66,8 +65,9 @@ struct device_data {
|
||||
};
|
||||
static struct instance_data instances[MAX_OBJECTS];
|
||||
static struct device_data devices[MAX_OBJECTS];
|
||||
/* YCbCr samplers by handle; non-dispatchable handles are unique per device on 64-bit. */
|
||||
static VkSampler ycbcr_samplers[MAX_YCBCR_SAMPLERS];
|
||||
/* 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++)
|
||||
@@ -219,6 +219,54 @@ static void VKAPI_CALL get_format_properties2(VkPhysicalDevice physical, VkForma
|
||||
|
||||
/* ---- device ---- */
|
||||
|
||||
/* Structures that may precede VkPhysicalDeviceFeatures2 in a device chain, by size. Only these are
|
||||
* copied; a chain with anything else first is passed on unchanged. */
|
||||
static size_t copyable_size(VkStructureType type) {
|
||||
switch (type) {
|
||||
case VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO: return sizeof(VkLayerDeviceCreateInfo);
|
||||
case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2: return sizeof(VkPhysicalDeviceFeatures2);
|
||||
case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES: return sizeof(VkPhysicalDeviceVulkan11Features);
|
||||
case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES: return sizeof(VkPhysicalDeviceVulkan12Features);
|
||||
case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES: return sizeof(VkPhysicalDeviceVulkan13Features);
|
||||
case VK_STRUCTURE_TYPE_DEVICE_GROUP_DEVICE_CREATE_INFO: return sizeof(VkDeviceGroupDeviceCreateInfo);
|
||||
case VK_STRUCTURE_TYPE_DEVICE_PRIVATE_DATA_CREATE_INFO: return sizeof(VkDevicePrivateDataCreateInfo);
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#define MAX_COPIED 16
|
||||
#define COPY_SIZE 512
|
||||
_Static_assert(sizeof(VkPhysicalDeviceVulkan12Features) <= COPY_SIZE &&
|
||||
sizeof(VkPhysicalDeviceVulkan13Features) <= COPY_SIZE &&
|
||||
sizeof(VkLayerDeviceCreateInfo) <= COPY_SIZE, "COPY_SIZE too small");
|
||||
struct chain_copy { _Alignas(16) unsigned char nodes[MAX_COPIED][COPY_SIZE]; };
|
||||
|
||||
/* 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. Returns [head] when nothing needs clearing or a structure ahead of it is unknown. */
|
||||
static const void *chain_without_bc(const void *head, struct chain_copy *copy) {
|
||||
const VkBaseInStructure *target = NULL;
|
||||
int count = 0;
|
||||
for (const VkBaseInStructure *p = head; p && !target; p = p->pNext) {
|
||||
if (!copyable_size(p->sType) || ++count > MAX_COPIED) return head;
|
||||
if (p->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 &&
|
||||
((const VkPhysicalDeviceFeatures2 *)p)->features.textureCompressionBC) target = p;
|
||||
}
|
||||
if (!target) return head;
|
||||
VkBaseOutStructure *first = NULL, *last = NULL;
|
||||
int index = 0;
|
||||
for (const VkBaseInStructure *p = head; ; p = p->pNext) {
|
||||
VkBaseOutStructure *node = (VkBaseOutStructure *)copy->nodes[index++];
|
||||
memcpy(node, p, copyable_size(p->sType));
|
||||
if (last) last->pNext = node; else first = node;
|
||||
last = node;
|
||||
if (p == target) {
|
||||
((VkPhysicalDeviceFeatures2 *)node)->features.textureCompressionBC = VK_FALSE;
|
||||
return first; /* its pNext still leads to the caller's remaining structures */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static VkResult VKAPI_CALL create_device(VkPhysicalDevice physical, const VkDeviceCreateInfo *info,
|
||||
const VkAllocationCallbacks *allocator, VkDevice *device) {
|
||||
VkLayerDeviceCreateInfo *chain = (VkLayerDeviceCreateInfo *)info->pNext;
|
||||
@@ -240,15 +288,9 @@ static VkResult VKAPI_CALL create_device(VkPhysicalDevice physical, const VkDevi
|
||||
plain_features.textureCompressionBC = VK_FALSE;
|
||||
patched.pEnabledFeatures = &plain_features;
|
||||
}
|
||||
VkPhysicalDeviceFeatures2 *features2 = NULL;
|
||||
for (VkBaseOutStructure *p = (VkBaseOutStructure *)info->pNext; p && strip_bc; p = p->pNext)
|
||||
if (p->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 &&
|
||||
((VkPhysicalDeviceFeatures2 *)p)->features.textureCompressionBC) {
|
||||
features2 = (VkPhysicalDeviceFeatures2 *)p;
|
||||
features2->features.textureCompressionBC = VK_FALSE;
|
||||
}
|
||||
struct chain_copy copy;
|
||||
if (strip_bc) patched.pNext = chain_without_bc(info->pNext, ©);
|
||||
VkResult result = next_create(physical, &patched, allocator, device);
|
||||
if (features2) features2->features.textureCompressionBC = VK_TRUE;
|
||||
if (result != VK_SUCCESS) return result;
|
||||
|
||||
PFN_vkCreateSampler create_sampler = (PFN_vkCreateSampler)next_gdpa(*device, "vkCreateSampler");
|
||||
@@ -285,6 +327,9 @@ static void VKAPI_CALL destroy_device(VkDevice device, const VkAllocationCallbac
|
||||
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);
|
||||
@@ -302,20 +347,46 @@ static VkResult VKAPI_CALL create_sampler(VkDevice device, const VkSamplerCreate
|
||||
if (!next) return VK_ERROR_INITIALIZATION_FAILED;
|
||||
VkResult result = next(device, info, allocator, sampler);
|
||||
if (result != VK_SUCCESS) return result;
|
||||
for (const VkBaseInStructure *p = info->pNext; p; p = p->pNext) {
|
||||
if (p->sType != VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO) continue;
|
||||
pthread_mutex_lock(&lock);
|
||||
for (int k = 0; k < MAX_YCBCR_SAMPLERS; k++)
|
||||
if (!ycbcr_samplers[k]) { ycbcr_samplers[k] = *sampler; break; }
|
||||
pthread_mutex_unlock(&lock);
|
||||
break;
|
||||
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 int is_ycbcr(VkSampler sampler) {
|
||||
for (int k = 0; k < MAX_YCBCR_SAMPLERS; k++)
|
||||
if (sampler && ycbcr_samplers[k] == sampler) return 1;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -329,32 +400,34 @@ static VkResult VKAPI_CALL create_set_layout(VkDevice device, const VkDescriptor
|
||||
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(binding->pImmutableSamplers[s])) { replace = 1; break; }
|
||||
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) { free(bindings); free(arrays); return VK_ERROR_OUT_OF_HOST_MEMORY; }
|
||||
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;
|
||||
arrays[b] = malloc(bindings[b].descriptorCount * sizeof(VkSampler));
|
||||
if (!arrays[b]) 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(original) ? data.fallback : original;
|
||||
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;
|
||||
VkResult result = data.create_set_layout(device, &patched, allocator, layout);
|
||||
result = data.create_set_layout(device, &patched, allocator, layout);
|
||||
if (debug) fprintf(stderr, "xclipse layer: compute YCbCr immutable samplers replaced (%d)\n", result);
|
||||
for (uint32_t b = 0; b < info->bindingCount; b++) free(arrays[b]);
|
||||
done:
|
||||
for (uint32_t b = 0; arrays && b < info->bindingCount; b++) free(arrays[b]);
|
||||
free(arrays);
|
||||
free(bindings);
|
||||
return result;
|
||||
@@ -366,6 +439,7 @@ 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;
|
||||
|
||||
Reference in new issue
Block a user