Mali compatibility layer: multiDrawIndirect, depthClipEnable and robustness2 for DXVK on Venus

A Galaxy A15 (Mali-G57) on Venus got gamescope and Steam up, but DXVK refused the GPU:
"Device does not support required feature 'multiDrawIndirect'". DXVK 3.1 stops at the first
missing feature, so the rest of its required list (dxvk_device_info.cpp) was checked against what
the testers' Mali drivers cannot have, and covered in the same pass, on top of David Cimo's six:
- multiDrawIndirect: reported, taken off the device, and an indirect draw with drawCount > 1 is
  recorded as single draws at offset + i * stride; maxDrawIndirectCount reported as 2^30.
  (drawIndirectCount is optional for DXVK and stays as the driver says.)
- VK_EXT_depth_clip_enable (required): offered when the driver lacks it; depthClipEnable off in a
  pipeline becomes depthClampEnable, and the extension's structures never reach the driver.
- VK_EXT_robustness2 missing entirely (gamescope saw the same on these phones): offered with
  robustBufferAccess2 (as robustBufferAccess, as before) and nullDescriptor, emulated by small
  stand-ins (a zeroed buffer, a 1x1 image) for VK_NULL_HANDLE buffers, image views and vertex
  buffers in plain, template and push descriptor writes and vertex-buffer binds. A null
  texel-buffer view stays null (its format is not known).
The layer logs every feature it emulates per device.
This commit is contained in:
The412Banner committed 2026-10-13 07:31:17 -04:00
1 parent 6c7242aa64
commit 22694a380d
4 files changed
+631 -6

No files matched your search

+411
View File
@@ -0,0 +1,411 @@
/*
* The Mali compatibility layer's draw and descriptor emulation (added to David Cimo's layer):
*
* - multiDrawIndirect: an indirect draw with drawCount > 1 is recorded as drawCount single draws
* at offset + i * stride. Mali-G57 (Bifrost/Valhall v9) has none, and DXVK refuses the device.
* - robustness2's nullDescriptor: VK_NULL_HANDLE buffers, image views and vertex buffers in
* descriptor writes (plain, template and push) and vertex-buffer binds are replaced with small
* stand-ins the layer owns. robustBufferAccess2 itself maps to robustBufferAccess (layer.c).
* A texel-buffer view stays null: its format is not known here.
*/
#include <stdlib.h>
#include <string.h>
#include "mali_compat.h"
/* --- multiDrawIndirect --- */
VKAPI_ATTR void VKAPI_CALL CmdDrawIndirect(VkCommandBuffer cmd, VkBuffer buffer, VkDeviceSize offset,
uint32_t draw_count, uint32_t stride)
{
struct device *dev = device_from_handle(cmd);
if (draw_count <= 1) {
dev->CmdDrawIndirect(cmd, buffer, offset, draw_count, stride);
return;
}
for (uint32_t i = 0; i < draw_count; i++)
dev->CmdDrawIndirect(cmd, buffer, offset + (VkDeviceSize)i * stride, 1, stride);
}
VKAPI_ATTR void VKAPI_CALL CmdDrawIndexedIndirect(VkCommandBuffer cmd, VkBuffer buffer, VkDeviceSize offset,
uint32_t draw_count, uint32_t stride)
{
struct device *dev = device_from_handle(cmd);
if (draw_count <= 1) {
dev->CmdDrawIndexedIndirect(cmd, buffer, offset, draw_count, stride);
return;
}
for (uint32_t i = 0; i < draw_count; i++)
dev->CmdDrawIndexedIndirect(cmd, buffer, offset + (VkDeviceSize)i * stride, 1, stride);
}
/* --- nullDescriptor stand-ins --- */
/* A template's entries, to find the null handles in the data it is given. */
struct template_info {
uint32_t count;
VkDescriptorUpdateTemplateEntry *entries;
size_t size; /* bytes the data spans */
};
#define NULL_BUFFER_SIZE 65536
static uint32_t memory_type(const struct device *dev, uint32_t bits, VkMemoryPropertyFlags want)
{
for (uint32_t i = 0; i < dev->memory_properties.memoryTypeCount; i++)
if ((bits & (1u << i)) && (dev->memory_properties.memoryTypes[i].propertyFlags & want) == want)
return i;
for (uint32_t i = 0; i < dev->memory_properties.memoryTypeCount; i++)
if (bits & (1u << i))
return i;
return 0;
}
/* Made once per device, under its lock, the first time a null descriptor is seen. */
static bool null_resources(struct device *dev)
{
if (dev->null_ready)
return dev->null_ready > 0;
dev->null_ready = -1;
VkBufferCreateInfo binfo = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = NULL_BUFFER_SIZE,
.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
};
if (dev->CreateBuffer(dev->handle, &binfo, NULL, &dev->null_buffer) != VK_SUCCESS)
return false;
VkMemoryRequirements breq;
dev->GetBufferMemoryRequirements(dev->handle, dev->null_buffer, &breq);
VkMemoryAllocateInfo balloc = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = breq.size,
.memoryTypeIndex = memory_type(dev, breq.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT),
};
if (dev->AllocateMemory(dev->handle, &balloc, NULL, &dev->null_buffer_memory) != VK_SUCCESS)
return false;
dev->BindBufferMemory(dev->handle, dev->null_buffer, dev->null_buffer_memory, 0);
/* A null buffer reads as zeros. */
void *data = NULL;
if ((dev->memory_properties.memoryTypes[balloc.memoryTypeIndex].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) &&
dev->MapMemory(dev->handle, dev->null_buffer_memory, 0, VK_WHOLE_SIZE, 0, &data) == VK_SUCCESS) {
memset(data, 0, (size_t)breq.size);
dev->UnmapMemory(dev->handle, dev->null_buffer_memory);
}
VkImageCreateInfo iinfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = VK_FORMAT_R8G8B8A8_UNORM,
.extent = {1, 1, 1},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};
if (dev->CreateImage(dev->handle, &iinfo, NULL, &dev->null_image) != VK_SUCCESS)
return false;
VkMemoryRequirements ireq;
dev->GetImageMemoryRequirements(dev->handle, dev->null_image, &ireq);
VkMemoryAllocateInfo ialloc = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = ireq.size,
.memoryTypeIndex = memory_type(dev, ireq.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT),
};
if (dev->AllocateMemory(dev->handle, &ialloc, NULL, &dev->null_image_memory) != VK_SUCCESS)
return false;
dev->BindImageMemory(dev->handle, dev->null_image, dev->null_image_memory, 0);
VkImageViewCreateInfo vinfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = dev->null_image,
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = VK_FORMAT_R8G8B8A8_UNORM,
.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1},
};
if (dev->CreateImageView(dev->handle, &vinfo, NULL, &dev->null_view) != VK_SUCCESS)
return false;
dev->null_ready = 1;
layer_log("nullDescriptor: stand-in buffer and 1x1 image ready");
return true;
}
void null_resources_destroy(struct device *dev)
{
for (uint32_t i = 0; i < dev->templates.capacity; i++) {
struct template_info *t = dev->templates.values ? dev->templates.values[i] : NULL;
if (t) {
free(t->entries);
free(t);
}
}
map_free(&dev->templates);
if (dev->null_view) dev->DestroyImageView(dev->handle, dev->null_view, NULL);
if (dev->null_image) dev->DestroyImage(dev->handle, dev->null_image, NULL);
if (dev->null_image_memory) dev->FreeMemory(dev->handle, dev->null_image_memory, NULL);
if (dev->null_buffer) dev->DestroyBuffer(dev->handle, dev->null_buffer, NULL);
if (dev->null_buffer_memory) dev->FreeMemory(dev->handle, dev->null_buffer_memory, NULL);
}
static bool is_buffer_type(VkDescriptorType t)
{
return t == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER || t == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER ||
t == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC || t == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC;
}
static bool is_image_type(VkDescriptorType t)
{
return t == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE || t == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE ||
t == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER || t == VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
}
static void fix_buffer(struct device *dev, VkDescriptorBufferInfo *info)
{
if (info->buffer != VK_NULL_HANDLE || !null_resources(dev))
return;
info->buffer = dev->null_buffer;
info->offset = 0;
info->range = VK_WHOLE_SIZE;
}
static void fix_image(struct device *dev, VkDescriptorImageInfo *info)
{
if (info->imageView != VK_NULL_HANDLE || !null_resources(dev))
return;
info->imageView = dev->null_view;
}
/* Copies of the writes with stand-ins for null handles; NULL when nothing needed changing. */
static VkWriteDescriptorSet *fix_writes(struct device *dev, uint32_t count, const VkWriteDescriptorSet *writes,
void ***scratch, uint32_t *scratch_count)
{
VkWriteDescriptorSet *copies = NULL;
for (uint32_t i = 0; i < count; i++) {
const VkWriteDescriptorSet *w = &writes[i];
bool null_found = false;
for (uint32_t j = 0; j < w->descriptorCount && !null_found; j++) {
if (is_buffer_type(w->descriptorType) && w->pBufferInfo && w->pBufferInfo[j].buffer == VK_NULL_HANDLE)
null_found = true;
if (is_image_type(w->descriptorType) && w->pImageInfo && w->pImageInfo[j].imageView == VK_NULL_HANDLE &&
w->descriptorType != VK_DESCRIPTOR_TYPE_SAMPLER)
null_found = true;
}
if (!null_found)
continue;
if (!copies) {
copies = malloc(count * sizeof(*copies));
memcpy(copies, writes, count * sizeof(*copies));
*scratch = calloc(count, sizeof(void *));
}
if (is_buffer_type(w->descriptorType)) {
VkDescriptorBufferInfo *b = malloc(w->descriptorCount * sizeof(*b));
memcpy(b, w->pBufferInfo, w->descriptorCount * sizeof(*b));
for (uint32_t j = 0; j < w->descriptorCount; j++)
fix_buffer(dev, &b[j]);
copies[i].pBufferInfo = b;
(*scratch)[(*scratch_count)++] = b;
} else {
VkDescriptorImageInfo *im = malloc(w->descriptorCount * sizeof(*im));
memcpy(im, w->pImageInfo, w->descriptorCount * sizeof(*im));
for (uint32_t j = 0; j < w->descriptorCount; j++)
fix_image(dev, &im[j]);
copies[i].pImageInfo = im;
(*scratch)[(*scratch_count)++] = im;
}
}
return copies;
}
static void free_scratch(void **scratch, uint32_t count)
{
for (uint32_t i = 0; i < count; i++)
free(scratch[i]);
free(scratch);
}
VKAPI_ATTR void VKAPI_CALL UpdateDescriptorSets(VkDevice device, uint32_t write_count, const VkWriteDescriptorSet *writes,
uint32_t copy_count, const VkCopyDescriptorSet *copies)
{
struct device *dev = device_from_handle(device);
pthread_mutex_lock(&dev->lock);
void **scratch = NULL;
uint32_t scratch_count = 0;
VkWriteDescriptorSet *fixed = fix_writes(dev, write_count, writes, &scratch, &scratch_count);
pthread_mutex_unlock(&dev->lock);
dev->UpdateDescriptorSets(device, write_count, fixed ? fixed : writes, copy_count, copies);
free(fixed);
free_scratch(scratch, scratch_count);
}
VKAPI_ATTR void VKAPI_CALL CmdPushDescriptorSet(VkCommandBuffer cmd, VkPipelineBindPoint bind_point,
VkPipelineLayout layout, uint32_t set, uint32_t write_count,
const VkWriteDescriptorSet *writes)
{
struct device *dev = device_from_handle(cmd);
pthread_mutex_lock(&dev->lock);
void **scratch = NULL;
uint32_t scratch_count = 0;
VkWriteDescriptorSet *fixed = fix_writes(dev, write_count, writes, &scratch, &scratch_count);
pthread_mutex_unlock(&dev->lock);
dev->CmdPushDescriptorSet(cmd, bind_point, layout, set, write_count, fixed ? fixed : writes);
free(fixed);
free_scratch(scratch, scratch_count);
}
static size_t entry_size(VkDescriptorType t)
{
if (is_buffer_type(t))
return sizeof(VkDescriptorBufferInfo);
if (is_image_type(t) || t == VK_DESCRIPTOR_TYPE_SAMPLER)
return sizeof(VkDescriptorImageInfo);
if (t == VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK)
return 1;
return sizeof(uint64_t);
}
VKAPI_ATTR VkResult VKAPI_CALL CreateDescriptorUpdateTemplate(VkDevice device,
const VkDescriptorUpdateTemplateCreateInfo *info,
const VkAllocationCallbacks *allocator,
VkDescriptorUpdateTemplate *tmpl)
{
struct device *dev = device_from_handle(device);
VkResult result = dev->CreateDescriptorUpdateTemplate(device, info, allocator, tmpl);
if (result != VK_SUCCESS)
return result;
struct template_info *t = calloc(1, sizeof(*t));
t->count = info->descriptorUpdateEntryCount;
t->entries = malloc(t->count * sizeof(*t->entries));
memcpy(t->entries, info->pDescriptorUpdateEntries, t->count * sizeof(*t->entries));
for (uint32_t i = 0; i < t->count; i++) {
const VkDescriptorUpdateTemplateEntry *e = &t->entries[i];
size_t end = e->descriptorType == VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK
? e->offset + e->descriptorCount
: e->offset + (e->descriptorCount ? (e->descriptorCount - 1) * e->stride : 0) + entry_size(e->descriptorType);
if (end > t->size)
t->size = end;
}
pthread_mutex_lock(&dev->lock);
map_put(&dev->templates, (uint64_t)*tmpl, t);
pthread_mutex_unlock(&dev->lock);
return VK_SUCCESS;
}
VKAPI_ATTR void VKAPI_CALL DestroyDescriptorUpdateTemplate(VkDevice device, VkDescriptorUpdateTemplate tmpl,
const VkAllocationCallbacks *allocator)
{
struct device *dev = device_from_handle(device);
pthread_mutex_lock(&dev->lock);
struct template_info *t = map_remove(&dev->templates, (uint64_t)tmpl);
pthread_mutex_unlock(&dev->lock);
if (t) {
free(t->entries);
free(t);
}
dev->DestroyDescriptorUpdateTemplate(device, tmpl, allocator);
}
/* A copy of the template's data with stand-ins for null handles; NULL when nothing changed. */
static void *fix_template_data(struct device *dev, VkDescriptorUpdateTemplate tmpl, const void *data)
{
pthread_mutex_lock(&dev->lock);
struct template_info *t = map_get(&dev->templates, (uint64_t)tmpl);
void *copy = NULL;
if (t && data) {
for (uint32_t i = 0; i < t->count; i++) {
const VkDescriptorUpdateTemplateEntry *e = &t->entries[i];
bool buffer = is_buffer_type(e->descriptorType);
bool image = is_image_type(e->descriptorType);
if (!buffer && !image)
continue;
for (uint32_t j = 0; j < e->descriptorCount; j++) {
size_t at = e->offset + (size_t)j * e->stride;
const char *src = (const char *)(copy ? copy : data) + at;
bool null_handle = buffer ? ((const VkDescriptorBufferInfo *)src)->buffer == VK_NULL_HANDLE
: ((const VkDescriptorImageInfo *)src)->imageView == VK_NULL_HANDLE;
if (!null_handle)
continue;
if (!copy) {
copy = malloc(t->size);
memcpy(copy, data, t->size);
}
if (buffer)
fix_buffer(dev, (VkDescriptorBufferInfo *)((char *)copy + at));
else
fix_image(dev, (VkDescriptorImageInfo *)((char *)copy + at));
}
}
}
pthread_mutex_unlock(&dev->lock);
return copy;
}
VKAPI_ATTR void VKAPI_CALL UpdateDescriptorSetWithTemplate(VkDevice device, VkDescriptorSet set,
VkDescriptorUpdateTemplate tmpl, const void *data)
{
struct device *dev = device_from_handle(device);
void *fixed = fix_template_data(dev, tmpl, data);
dev->UpdateDescriptorSetWithTemplate(device, set, tmpl, fixed ? fixed : data);
free(fixed);
}
VKAPI_ATTR void VKAPI_CALL CmdPushDescriptorSetWithTemplate(VkCommandBuffer cmd, VkDescriptorUpdateTemplate tmpl,
VkPipelineLayout layout, uint32_t set, const void *data)
{
struct device *dev = device_from_handle(cmd);
void *fixed = fix_template_data(dev, tmpl, data);
dev->CmdPushDescriptorSetWithTemplate(cmd, tmpl, layout, set, fixed ? fixed : data);
free(fixed);
}
/* Null vertex buffers read as zeros: bound to the stand-in at offset 0. */
VKAPI_ATTR void VKAPI_CALL CmdBindVertexBuffers(VkCommandBuffer cmd, uint32_t first, uint32_t count,
const VkBuffer *buffers, const VkDeviceSize *offsets)
{
struct device *dev = device_from_handle(cmd);
VkBuffer fixed[64];
VkDeviceSize fixed_offsets[64];
bool any = false;
for (uint32_t i = 0; i < count && i < 64; i++) any |= buffers[i] == VK_NULL_HANDLE;
if (!any || count > 64) {
dev->CmdBindVertexBuffers(cmd, first, count, buffers, offsets);
return;
}
pthread_mutex_lock(&dev->lock);
bool ready = null_resources(dev);
pthread_mutex_unlock(&dev->lock);
for (uint32_t i = 0; i < count; i++) {
fixed[i] = buffers[i] == VK_NULL_HANDLE && ready ? dev->null_buffer : buffers[i];
fixed_offsets[i] = buffers[i] == VK_NULL_HANDLE ? 0 : offsets[i];
}
dev->CmdBindVertexBuffers(cmd, first, count, fixed, fixed_offsets);
}
VKAPI_ATTR void VKAPI_CALL CmdBindVertexBuffers2(VkCommandBuffer cmd, uint32_t first, uint32_t count,
const VkBuffer *buffers, const VkDeviceSize *offsets,
const VkDeviceSize *sizes, const VkDeviceSize *strides)
{
struct device *dev = device_from_handle(cmd);
VkBuffer fixed[64];
VkDeviceSize fixed_offsets[64], fixed_sizes[64];
bool any = false;
for (uint32_t i = 0; i < count && i < 64; i++) any |= buffers[i] == VK_NULL_HANDLE;
if (!any || count > 64) {
dev->CmdBindVertexBuffers2(cmd, first, count, buffers, offsets, sizes, strides);
return;
}
pthread_mutex_lock(&dev->lock);
bool ready = null_resources(dev);
pthread_mutex_unlock(&dev->lock);
for (uint32_t i = 0; i < count; i++) {
bool null_buffer = buffers[i] == VK_NULL_HANDLE;
fixed[i] = null_buffer && ready ? dev->null_buffer : buffers[i];
fixed_offsets[i] = null_buffer ? 0 : offsets[i];
fixed_sizes[i] = sizes ? (null_buffer ? NULL_BUFFER_SIZE : sizes[i]) : 0;
}
dev->CmdBindVertexBuffers2(cmd, first, count, fixed, fixed_offsets, sizes ? fixed_sizes : NULL, strides);
}
+153 -4
View File
@@ -190,6 +190,14 @@ uint32_t physical_device_gaps(struct instance *inst, VkPhysicalDevice physical_d
gaps |= GAP_BC;
if (robustness2 && !robust.robustBufferAccess2)
gaps |= GAP_ROBUSTNESS2;
if (!features.features.multiDrawIndirect)
gaps |= GAP_MULTI_DRAW;
if (!has_extension(inst, physical_device, VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME))
gaps |= GAP_DEPTH_CLIP;
/* No robustness2 at all: the extension is offered with robustBufferAccess2 (as robustBufferAccess)
* and nullDescriptor (stand-ins, draw.c). */
if (!robustness2)
gaps |= GAP_ROBUSTNESS2 | GAP_NULL_DESCRIPTOR;
pthread_mutex_lock(&inst->lock);
if (inst->physical_device_count < sizeof(inst->physical_devices) / sizeof(inst->physical_devices[0])) {
@@ -200,6 +208,48 @@ uint32_t physical_device_gaps(struct instance *inst, VkPhysicalDevice physical_d
return gaps;
}
/* Structures of extensions the driver does not have are taken out of a chain for the call down and
* put back after it: the driver (and Venus) must not see them. */
struct unlinked {
VkBaseOutStructure *prev, *node;
};
static uint32_t unlink_types(void *head, const VkStructureType *types, uint32_t type_count,
struct unlinked *out, uint32_t max)
{
uint32_t n = 0;
VkBaseOutStructure *prev = head;
while (prev && prev->pNext && n < max) {
VkBaseOutStructure *s = prev->pNext;
bool drop = false;
for (uint32_t i = 0; i < type_count; i++)
drop |= s->sType == types[i];
if (drop) {
out[n++] = (struct unlinked){prev, s};
prev->pNext = s->pNext;
} else {
prev = s;
}
}
return n;
}
static void relink(struct unlinked *list, uint32_t n)
{
while (n--)
list[n].prev->pNext = list[n].node;
}
static uint32_t foreign_types(uint32_t gaps, VkStructureType *types)
{
uint32_t n = 0;
if (gaps & GAP_DEPTH_CLIP)
types[n++] = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_ENABLE_FEATURES_EXT;
if (gaps & GAP_NULL_DESCRIPTOR)
types[n++] = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT;
return n;
}
static void report_features(VkPhysicalDeviceFeatures *features, uint32_t gaps)
{
if (gaps & GAP_FILL_MODE)
@@ -212,6 +262,8 @@ static void report_features(VkPhysicalDeviceFeatures *features, uint32_t gaps)
features->shaderCullDistance = VK_TRUE;
if (gaps & GAP_BC)
features->textureCompressionBC = VK_TRUE;
if (gaps & GAP_MULTI_DRAW)
features->multiDrawIndirect = VK_TRUE;
}
/* Which made-up features the application asked for, and off with them before the driver sees them. */
@@ -238,6 +290,10 @@ static uint32_t take_features(VkPhysicalDeviceFeatures *features, uint32_t gaps)
requested |= GAP_BC;
features->textureCompressionBC = VK_FALSE;
}
if ((gaps & GAP_MULTI_DRAW) && features->multiDrawIndirect) {
requested |= GAP_MULTI_DRAW;
features->multiDrawIndirect = VK_FALSE;
}
return requested;
}
@@ -245,6 +301,8 @@ static void report_limits(VkPhysicalDeviceLimits *limits, uint32_t gaps)
{
if (gaps & GAP_MULTI_VIEWPORT)
limits->maxViewports = 16;
if ((gaps & GAP_MULTI_DRAW) && limits->maxDrawIndirectCount < (1u << 30))
limits->maxDrawIndirectCount = 1u << 30;
if (gaps & GAPS_SHADER) {
limits->maxClipDistances = 8;
limits->maxCullDistances = 8;
@@ -264,12 +322,21 @@ static VKAPI_ATTR void VKAPI_CALL GetPhysicalDeviceFeatures2(VkPhysicalDevice ph
VkPhysicalDeviceFeatures2 *features)
{
struct instance *inst = instance_from_handle(physical_device);
inst->GetPhysicalDeviceFeatures2(physical_device, features);
uint32_t gaps = physical_device_gaps(inst, physical_device);
VkStructureType types[4];
struct unlinked dropped[8];
uint32_t dropped_count = unlink_types(features, types, foreign_types(gaps, types), dropped, 8);
inst->GetPhysicalDeviceFeatures2(physical_device, features);
relink(dropped, dropped_count);
report_features(&features->features, gaps);
for (VkBaseOutStructure *s = features->pNext; s; s = s->pNext) {
if (s->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT && (gaps & GAP_ROBUSTNESS2))
if (s->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT && (gaps & GAP_ROBUSTNESS2)) {
((VkPhysicalDeviceRobustness2FeaturesEXT *)s)->robustBufferAccess2 = VK_TRUE;
if (gaps & GAP_NULL_DESCRIPTOR)
((VkPhysicalDeviceRobustness2FeaturesEXT *)s)->nullDescriptor = VK_TRUE;
}
if (s->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_ENABLE_FEATURES_EXT && (gaps & GAP_DEPTH_CLIP))
((VkPhysicalDeviceDepthClipEnableFeaturesEXT *)s)->depthClipEnable = VK_TRUE;
}
}
@@ -289,6 +356,40 @@ static VKAPI_ATTR void VKAPI_CALL GetPhysicalDeviceProperties2(VkPhysicalDevice
report_limits(&properties->properties.limits, physical_device_gaps(inst, physical_device));
}
/* The extensions the layer stands in for, added to the driver's list. */
static VKAPI_ATTR VkResult VKAPI_CALL EnumerateDeviceExtensionProperties(VkPhysicalDevice physical_device,
const char *layer_name, uint32_t *count,
VkExtensionProperties *properties)
{
struct instance *inst = instance_from_handle(physical_device);
if (layer_name)
return inst->EnumerateDeviceExtensionProperties(physical_device, layer_name, count, properties);
uint32_t gaps = physical_device_gaps(inst, physical_device);
VkExtensionProperties extra[2];
uint32_t extra_count = 0;
if (gaps & GAP_DEPTH_CLIP)
extra[extra_count++] = (VkExtensionProperties){VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME, 1};
if (gaps & GAP_NULL_DESCRIPTOR)
extra[extra_count++] = (VkExtensionProperties){VK_EXT_ROBUSTNESS_2_EXTENSION_NAME, 1};
uint32_t own = 0;
VkResult result = inst->EnumerateDeviceExtensionProperties(physical_device, NULL, &own, NULL);
if (result != VK_SUCCESS)
return result;
if (!properties) {
*count = own + extra_count;
return VK_SUCCESS;
}
VkExtensionProperties *all = calloc(own + extra_count, sizeof(*all));
inst->EnumerateDeviceExtensionProperties(physical_device, NULL, &own, all);
memcpy(all + own, extra, extra_count * sizeof(*extra));
uint32_t total = own + extra_count;
uint32_t n = *count < total ? *count : total;
memcpy(properties, all, n * sizeof(*all));
free(all);
*count = n;
return n < total ? VK_INCOMPLETE : VK_SUCCESS;
}
static VKAPI_ATTR VkResult VKAPI_CALL CreateInstance(const VkInstanceCreateInfo *info,
const VkAllocationCallbacks *allocator, VkInstance *instance)
{
@@ -410,8 +511,35 @@ static VKAPI_ATTR VkResult VKAPI_CALL CreateDevice(VkPhysicalDevice physical_dev
enabled->robustBufferAccess = VK_TRUE;
}
/* The extensions the layer stands in for leave the list, and their structures the chain. */
const char **names = NULL;
if (gaps & (GAP_DEPTH_CLIP | GAP_NULL_DESCRIPTOR)) {
names = malloc((info->enabledExtensionCount ? info->enabledExtensionCount : 1) * sizeof(*names));
copy.enabledExtensionCount = 0;
for (uint32_t i = 0; i < info->enabledExtensionCount; i++) {
const char *name = info->ppEnabledExtensionNames[i];
if ((gaps & GAP_DEPTH_CLIP) && !strcmp(name, VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME)) {
requested |= GAP_DEPTH_CLIP;
continue;
}
if ((gaps & GAP_NULL_DESCRIPTOR) && !strcmp(name, VK_EXT_ROBUSTNESS_2_EXTENSION_NAME)) {
requested |= GAP_NULL_DESCRIPTOR;
if (enabled)
enabled->robustBufferAccess = VK_TRUE;
continue;
}
names[copy.enabledExtensionCount++] = name;
}
copy.ppEnabledExtensionNames = names;
}
VkStructureType foreign[4];
struct unlinked dropped[8];
uint32_t dropped_count = unlink_types(&copy, foreign, foreign_types(gaps, foreign), dropped, 8);
VkResult result = create(physical_device, &copy, allocator, device);
relink(dropped, dropped_count);
free(names);
if (features2 && !info->pEnabledFeatures)
features2->features = saved_features;
if (robust)
@@ -447,12 +575,15 @@ static VKAPI_ATTR VkResult VKAPI_CALL CreateDevice(VkPhysicalDevice physical_dev
pthread_mutex_unlock(&global_lock);
if (requested) {
layer_log("emulating%s%s%s%s%s%s for %s", (requested & GAP_FILL_MODE) ? " fillModeNonSolid" : "",
layer_log("emulating%s%s%s%s%s%s%s%s%s for %s", (requested & GAP_FILL_MODE) ? " fillModeNonSolid" : "",
(requested & GAP_MULTI_VIEWPORT) ? " multiViewport" : "",
(requested & GAP_CLIP_DISTANCE) ? " shaderClipDistance" : "",
(requested & GAP_CULL_DISTANCE) ? " shaderCullDistance" : "",
(requested & GAP_ROBUSTNESS2) ? " robustBufferAccess2" : "",
(requested & GAP_BC) ? " textureCompressionBC" : "", properties.deviceName);
(requested & GAP_BC) ? " textureCompressionBC" : "",
(requested & GAP_MULTI_DRAW) ? " multiDrawIndirect" : "",
(requested & GAP_DEPTH_CLIP) ? " depthClipEnable" : "",
(requested & GAP_NULL_DESCRIPTOR) ? " nullDescriptor" : "", properties.deviceName);
}
return VK_SUCCESS;
}
@@ -471,6 +602,7 @@ static VKAPI_ATTR void VKAPI_CALL DestroyDevice(VkDevice device, const VkAllocat
if (!dev)
return;
bc_destroy_device(dev);
null_resources_destroy(dev);
dev->DestroyDevice(device, allocator);
for (uint32_t i = 0; i < dev->shader_modules.capacity; i++) {
struct spirv_code *code = dev->shader_modules.values[i];
@@ -538,6 +670,22 @@ static const struct hook device_hooks[] = {
HOOK(ResetCommandBuffer, GAP_BC),
HOOK(ResetCommandPool, GAP_BC),
HOOK(DestroyCommandPool, GAP_BC),
HOOK(CmdDrawIndirect, GAP_MULTI_DRAW),
HOOK(CmdDrawIndexedIndirect, GAP_MULTI_DRAW),
HOOK(UpdateDescriptorSets, GAP_NULL_DESCRIPTOR),
HOOK(CmdPushDescriptorSet, GAP_NULL_DESCRIPTOR),
HOOK_ALIAS(CmdPushDescriptorSet, CmdPushDescriptorSetKHR, GAP_NULL_DESCRIPTOR),
HOOK(CreateDescriptorUpdateTemplate, GAP_NULL_DESCRIPTOR),
HOOK_ALIAS(CreateDescriptorUpdateTemplate, CreateDescriptorUpdateTemplateKHR, GAP_NULL_DESCRIPTOR),
HOOK(DestroyDescriptorUpdateTemplate, GAP_NULL_DESCRIPTOR),
HOOK_ALIAS(DestroyDescriptorUpdateTemplate, DestroyDescriptorUpdateTemplateKHR, GAP_NULL_DESCRIPTOR),
HOOK(UpdateDescriptorSetWithTemplate, GAP_NULL_DESCRIPTOR),
HOOK_ALIAS(UpdateDescriptorSetWithTemplate, UpdateDescriptorSetWithTemplateKHR, GAP_NULL_DESCRIPTOR),
HOOK(CmdPushDescriptorSetWithTemplate, GAP_NULL_DESCRIPTOR),
HOOK_ALIAS(CmdPushDescriptorSetWithTemplate, CmdPushDescriptorSetWithTemplateKHR, GAP_NULL_DESCRIPTOR),
HOOK(CmdBindVertexBuffers, GAP_NULL_DESCRIPTOR),
HOOK(CmdBindVertexBuffers2, GAP_NULL_DESCRIPTOR),
HOOK_ALIAS(CmdBindVertexBuffers2, CmdBindVertexBuffers2EXT, GAP_NULL_DESCRIPTOR),
};
static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL GetDeviceProcAddr(VkDevice device, const char *name);
@@ -562,6 +710,7 @@ static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL GetDeviceProcAddr(VkDevice devic
}
static const struct hook physical_device_hooks[] = {
HOOK(EnumerateDeviceExtensionProperties, 0),
HOOK(GetPhysicalDeviceFeatures, 0),
HOOK(GetPhysicalDeviceFeatures2, 0),
HOOK_ALIAS(GetPhysicalDeviceFeatures2, GetPhysicalDeviceFeatures2KHR, 0),
+40 -2
View File
@@ -25,10 +25,14 @@ enum gap {
GAP_CULL_DISTANCE = 1u << 3,
GAP_ROBUSTNESS2 = 1u << 4,
GAP_BC = 1u << 5,
/* draw.c and pipeline.c (added for Mali-G57 and drivers without these, on top of David Cimo's six) */
GAP_MULTI_DRAW = 1u << 6, /* multiDrawIndirect: indirect draws looped one by one */
GAP_DEPTH_CLIP = 1u << 7, /* VK_EXT_depth_clip_enable: depthClipEnable off becomes depth clamp */
GAP_NULL_DESCRIPTOR = 1u << 8, /* VK_EXT_robustness2 missing entirely: nullDescriptor stand-ins */
};
#define GAPS_SHADER (GAP_CLIP_DISTANCE | GAP_CULL_DISTANCE)
#define GAPS_PIPELINE (GAP_FILL_MODE | GAP_MULTI_VIEWPORT | GAPS_SHADER)
#define GAPS_PIPELINE (GAP_FILL_MODE | GAP_MULTI_VIEWPORT | GAPS_SHADER | GAP_DEPTH_CLIP)
void layer_log(const char *format, ...) __attribute__((format(printf, 1, 2)));
@@ -109,7 +113,20 @@ struct instance {
X(BeginCommandBuffer) \
X(ResetCommandBuffer) \
X(ResetCommandPool) \
X(DestroyCommandPool)
X(DestroyCommandPool) \
X(CmdDrawIndirect) \
X(CmdDrawIndexedIndirect) \
X(GetImageMemoryRequirements) \
X(BindImageMemory) \
X(DestroyImageView) \
X(UpdateDescriptorSets) \
X(CmdPushDescriptorSet) \
X(CreateDescriptorUpdateTemplate) \
X(DestroyDescriptorUpdateTemplate) \
X(UpdateDescriptorSetWithTemplate) \
X(CmdPushDescriptorSetWithTemplate) \
X(CmdBindVertexBuffers) \
X(CmdBindVertexBuffers2)
struct staging_block;
@@ -132,6 +149,14 @@ struct device {
struct handle_map memories; /* VkDeviceMemory -> struct mapped_memory */
struct handle_map command_buffers; /* VkCommandBuffer -> struct command_buffer */
struct staging_block *free_staging;
struct handle_map templates; /* VkDescriptorUpdateTemplate -> struct template_info (draw.c) */
/* nullDescriptor stand-ins (draw.c): 0 not made yet, 1 ready, -1 failed. */
int null_ready;
VkBuffer null_buffer;
VkDeviceMemory null_buffer_memory;
VkImage null_image;
VkDeviceMemory null_image_memory;
VkImageView null_view;
struct device *next;
};
@@ -243,4 +268,17 @@ struct instance *instance_from_handle(const void *dispatchable);
/* What the GPU lacks of the features the layer can make up for. */
uint32_t physical_device_gaps(struct instance *inst, VkPhysicalDevice physical_device);
/* draw.c */
VKAPI_ATTR void VKAPI_CALL CmdDrawIndirect(VkCommandBuffer cmd, VkBuffer buffer, VkDeviceSize offset, uint32_t draw_count, uint32_t stride);
VKAPI_ATTR void VKAPI_CALL CmdDrawIndexedIndirect(VkCommandBuffer cmd, VkBuffer buffer, VkDeviceSize offset, uint32_t draw_count, uint32_t stride);
VKAPI_ATTR void VKAPI_CALL UpdateDescriptorSets(VkDevice device, uint32_t write_count, const VkWriteDescriptorSet *writes, uint32_t copy_count, const VkCopyDescriptorSet *copies);
VKAPI_ATTR void VKAPI_CALL CmdPushDescriptorSet(VkCommandBuffer cmd, VkPipelineBindPoint bind_point, VkPipelineLayout layout, uint32_t set, uint32_t write_count, const VkWriteDescriptorSet *writes);
VKAPI_ATTR VkResult VKAPI_CALL CreateDescriptorUpdateTemplate(VkDevice device, const VkDescriptorUpdateTemplateCreateInfo *info, const VkAllocationCallbacks *allocator, VkDescriptorUpdateTemplate *tmpl);
VKAPI_ATTR void VKAPI_CALL DestroyDescriptorUpdateTemplate(VkDevice device, VkDescriptorUpdateTemplate tmpl, const VkAllocationCallbacks *allocator);
VKAPI_ATTR void VKAPI_CALL UpdateDescriptorSetWithTemplate(VkDevice device, VkDescriptorSet set, VkDescriptorUpdateTemplate tmpl, const void *data);
VKAPI_ATTR void VKAPI_CALL CmdPushDescriptorSetWithTemplate(VkCommandBuffer cmd, VkDescriptorUpdateTemplate tmpl, VkPipelineLayout layout, uint32_t set, const void *data);
VKAPI_ATTR void VKAPI_CALL CmdBindVertexBuffers(VkCommandBuffer cmd, uint32_t first, uint32_t count, const VkBuffer *buffers, const VkDeviceSize *offsets);
VKAPI_ATTR void VKAPI_CALL CmdBindVertexBuffers2(VkCommandBuffer cmd, uint32_t first, uint32_t count, const VkBuffer *buffers, const VkDeviceSize *offsets, const VkDeviceSize *sizes, const VkDeviceSize *strides);
void null_resources_destroy(struct device *dev);
#endif
+27
View File
@@ -87,6 +87,8 @@ static void find_stage_code(struct device *dev, const VkPipelineShaderStageCreat
struct pipeline_rewrite {
VkPipelineRasterizationStateCreateInfo rasterization;
/* VK_EXT_depth_clip_enable's state, taken out of the application's chain for the call. */
VkBaseOutStructure *clip_prev, *clip_node;
VkPipelineViewportStateCreateInfo viewport;
VkPipelineShaderStageCreateInfo stages[MAX_STAGES];
VkShaderModuleCreateInfo modules[MAX_STAGES];
@@ -207,6 +209,29 @@ static void rewrite_pipeline(struct device *dev, VkGraphicsPipelineCreateInfo *i
rw->rasterization.polygonMode = VK_POLYGON_MODE_FILL;
info->pRasterizationState = &rw->rasterization;
}
/* No VK_EXT_depth_clip_enable: depth clipping off is the nearest the driver has as depth clamp. */
if ((dev->emulated & GAP_DEPTH_CLIP) && info->pRasterizationState) {
VkBaseOutStructure *prev = (VkBaseOutStructure *)info->pRasterizationState;
while (prev->pNext && prev->pNext->sType != VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_DEPTH_CLIP_STATE_CREATE_INFO_EXT)
prev = prev->pNext;
VkBaseOutStructure *node = prev->pNext;
if (node) {
bool clip = ((VkPipelineRasterizationDepthClipStateCreateInfoEXT *)node)->depthClipEnable;
if (info->pRasterizationState != &rw->rasterization) {
rw->rasterization = *info->pRasterizationState;
if (prev == (VkBaseOutStructure *)info->pRasterizationState)
prev = (VkBaseOutStructure *)&rw->rasterization;
info->pRasterizationState = &rw->rasterization;
}
if (!clip)
rw->rasterization.depthClampEnable = VK_TRUE;
if (prev != (VkBaseOutStructure *)&rw->rasterization) {
rw->clip_prev = prev;
rw->clip_node = node;
}
prev->pNext = node->pNext;
}
}
if ((dev->emulated & GAP_MULTI_VIEWPORT) && info->pViewportState &&
(info->pViewportState->viewportCount > 1 || info->pViewportState->scissorCount > 1)) {
rw->viewport = *info->pViewportState;
@@ -222,6 +247,8 @@ static void rewrite_pipeline(struct device *dev, VkGraphicsPipelineCreateInfo *i
static void undo_rewrite(struct device *dev, struct pipeline_rewrite *rw)
{
if (rw->clip_prev)
rw->clip_prev->pNext = rw->clip_node;
for (uint32_t i = 0; i < MAX_STAGES; i++) {
if (rw->in_place[i]) {
rw->in_place[i]->pCode = rw->in_place_code[i];