Files
DroidDeck/tools/vkbridge/client/physdev.c
T
The412Banner ee0fb08268 Mali bridge (#517, by ASEbrahim) on the non-Adreno test branch
Squash of Droid-Deck/DroidDeck#517 (ASEbrahim/DroidDeck mali-bridge @ 6b823341): the Vulkan
bridge (tools/vkbridge) that runs the guest's Vulkan on the phone's own driver, with BC1-BC7,
clip/cull distance, vertex-divisor and depth-clip emulation and dma-heap memory sharing for Mali.
All of the bridge is ASEbrahim's work; squashed only to carry it on this test branch.

Combined with #570 on this branch, a device draws through exactly one path:
- Adreno: Turnip, unchanged (checked first; the bridge setting is ignored there).
- PowerVR / Xclipse with the system HAL and a render node: the system-Vulkan path (#570).
- Anything else with the Setup toggle on (Mali): the bridge.
2026-10-12 18:34:49 -04:00

436 lines
21 KiB
C

/* SPDX-License-Identifier: GPL-3.0-or-later
*
* Physical device queries: what the app sees is the server's GPU, with the bridge's memory
* model, its extension list and the features the server emulates.
*/
#define _GNU_SOURCE
#include "vkb_client.h"
#include "gen/vkb_client_gen.h"
#include "emu.h"
#include <stdio.h>
#include <string.h>
const char *const *vkb_client_device_exts(uint32_t *n);
/* ------------------------------------------------------------------ memory model */
/*
* Images and GPU-only buffers live in memory the client never maps; host-visible memory must be
* shared pages (server_memory.c), which only buffers (and the rare linear image) can use. So the
* app gets a discrete-GPU-like view: device-local types without host access, and host-visible
* types (shared) that only resources able to use them list in their memoryTypeBits.
*/
static void build_memory_model(vkb_physdev *pd)
{
const VkPhysicalDeviceMemoryProperties *s = &pd->server_mem;
VkPhysicalDeviceMemoryProperties *c = &pd->client_mem;
memset(c, 0, sizeof(*c));
uint32_t share = pd->info.strategy != VKB_MEM_NONE ? pd->info.shareable_types : 0;
int pure_dl = -1;
for (uint32_t t = 0; t < s->memoryTypeCount; t++) {
VkMemoryPropertyFlags f = s->memoryTypes[t].propertyFlags;
if ((f & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) && !(f & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) &&
!(f & VK_MEMORY_PROPERTY_PROTECTED_BIT)) {
pure_dl = (int)t;
break;
}
}
int uma = pure_dl < 0;
/* Heaps: the server's, plus (UMA) a host heap for shared types so they are not "device local". */
for (uint32_t h = 0; h < s->memoryHeapCount; h++) {
c->memoryHeaps[h] = s->memoryHeaps[h];
pd->heap_map[h] = h;
}
c->memoryHeapCount = s->memoryHeapCount;
uint32_t host_heap = UINT32_MAX;
struct cand {
VkMemoryPropertyFlags flags;
uint32_t heap, server;
uint8_t shared;
} cands[VK_MAX_MEMORY_TYPES * 2];
uint32_t nc = 0;
if (uma) {
/* A device-local type with no host access, backed by the first device-local type. */
for (uint32_t t = 0; t < s->memoryTypeCount; t++) {
VkMemoryPropertyFlags f = s->memoryTypes[t].propertyFlags;
if ((f & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) && !(f & VK_MEMORY_PROPERTY_PROTECTED_BIT)) {
cands[nc++] = (struct cand){VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, s->memoryTypes[t].heapIndex, t, 0};
break;
}
}
}
for (uint32_t t = 0; t < s->memoryTypeCount && nc < VK_MAX_MEMORY_TYPES; t++) {
VkMemoryPropertyFlags f = s->memoryTypes[t].propertyFlags;
uint32_t heap = s->memoryTypes[t].heapIndex;
if (!(f & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) {
cands[nc++] = (struct cand){f, heap, t, 0};
continue;
}
if (!(share & (1u << t))) continue; /* host-visible but not shareable: not offered */
VkMemoryPropertyFlags cf = f;
if (!(pd->info.coherent_types & (1u << t))) cf &= ~VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
else cf |= VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; /* coherent in practice (the self-test says so) */
/* dma-heap pages come from the uncached heap: not what an app asking for cached reads wants. */
if (pd->info.strategy == VKB_MEM_DMAHEAP) cf &= ~VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
if (uma) {
cf &= ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
if (host_heap == UINT32_MAX && c->memoryHeapCount < VK_MAX_MEMORY_HEAPS) {
host_heap = c->memoryHeapCount++;
c->memoryHeaps[host_heap] = s->memoryHeaps[heap];
c->memoryHeaps[host_heap].flags &= ~VK_MEMORY_HEAP_DEVICE_LOCAL_BIT;
pd->heap_map[host_heap] = heap;
}
if (host_heap != UINT32_MAX) heap = host_heap;
}
/* Two client types with identical flags on one heap are pointless: keep the first. */
int dup = 0;
for (uint32_t i = 0; i < nc; i++) dup |= cands[i].flags == cf && cands[i].heap == heap;
if (!dup) cands[nc++] = (struct cand){cf, heap, t, 1};
}
/* Order: fewer property bits first, which satisfies the spec's subset ordering. */
for (uint32_t i = 1; i < nc; i++) {
struct cand x = cands[i];
uint32_t j = i;
while (j > 0 && __builtin_popcount(cands[j - 1].flags) > __builtin_popcount(x.flags)) {
cands[j] = cands[j - 1];
j--;
}
cands[j] = x;
}
if (nc > VK_MAX_MEMORY_TYPES) nc = VK_MAX_MEMORY_TYPES;
c->memoryTypeCount = nc;
for (uint32_t i = 0; i < nc; i++) {
c->memoryTypes[i].propertyFlags = cands[i].flags;
c->memoryTypes[i].heapIndex = cands[i].heap;
pd->type_map[i].server_type = cands[i].server;
pd->type_map[i].shared = cands[i].shared;
}
for (uint32_t i = 0; i < nc; i++)
VKB_DBG("memory type %u: flags 0x%x heap %u -> server type %u%s", i, c->memoryTypes[i].propertyFlags,
c->memoryTypes[i].heapIndex, pd->type_map[i].server_type, pd->type_map[i].shared ? " (shared)" : "");
}
uint32_t vkb_mem_bits_to_client(vkb_physdev *pd, uint32_t server_bits, int allow_shared)
{
uint32_t bits = 0;
for (uint32_t i = 0; i < pd->client_mem.memoryTypeCount; i++) {
if (!(server_bits & (1u << pd->type_map[i].server_type))) continue;
if (pd->type_map[i].shared && !allow_shared) continue;
bits |= 1u << i;
}
return bits;
}
/* ------------------------------------------------------------------ setup */
/* The render node the session presents for this GPU (VKBRIDGE_DRM_RENDER="major:minor"). */
static int drm_node(int64_t *major, int64_t *minor)
{
const char *v = getenv("VKBRIDGE_DRM_RENDER");
long long a, b;
if (!v || sscanf(v, "%lld:%lld", &a, &b) != 2) return 0;
if (major) *major = a;
if (minor) *minor = b;
return 1;
}
/* VKBRIDGE_HIDE_EXTS=a,b,c: pretend the GPU lacks these (testing; imitating another driver). */
static int hidden_ext(const char *name)
{
const char *h = getenv("VKBRIDGE_HIDE_EXTS");
if (!h) return 0;
size_t n = strlen(name);
for (const char *p = h; (p = strstr(p, name)); p += n)
if ((p == h || p[-1] == ',') && (p[n] == ',' || p[n] == 0)) return 1;
return 0;
}
static int bridged_device_ext_for(vkb_physdev *pd, const char *name)
{
/* Clip distance emulation rewrites the vertex and fragment shaders together, at pipeline
* creation: pipeline libraries and shader identifiers would split them. */
if ((pd->info.emu & VKB_EMU_CLIP_DISTANCE) &&
(!strcmp(name, "VK_EXT_graphics_pipeline_library") || !strcmp(name, "VK_EXT_shader_module_identifier")))
return 0;
return 1;
}
static int bridged_device_ext(const char *name)
{
if (hidden_ext(name)) return 0;
/* The server GPU's DRM node means nothing on this side: the session's stand-in node is
* reported instead (drm_node), or the extension is hidden. VKBRIDGE_PASS_DRM=1 passes the
* server's through (host tests, where both sides share one machine). */
if (!strcmp(name, "VK_EXT_physical_device_drm")) return getenv("VKBRIDGE_PASS_DRM") && !getenv("VKBRIDGE_DRM_RENDER");
for (int i = 0; i < VKB_EXT_COUNT; i++)
if (vkb_exts[i].device && !vkb_exts[i].client && !strcmp(vkb_exts[i].name, name)) return 1;
return 0;
}
int vkb_physdev_ready(vkb_physdev *pd)
{
if (pd->ready) return pd->ready > 0;
pthread_mutex_lock(&pd->lock);
if (pd->ready) {
pthread_mutex_unlock(&pd->lock);
return pd->ready > 0;
}
vkb_call c;
vkb_call_begin(&c, VKB_CMD_vkbQueryServer, pd->obj.table);
vkb_enc_u64(&c.e, pd->obj.remote);
if (!vkb_call_exec(&c)) {
pd->ready = -1;
pthread_mutex_unlock(&pd->lock);
return 0;
}
vkb_dec_raw_into(&c.d, &pd->info, sizeof(pd->info));
vkb_call_end(&c);
vkb_wire_vkGetPhysicalDeviceProperties((VkPhysicalDevice)pd, &pd->props);
vkb_wire_vkGetPhysicalDeviceMemoryProperties((VkPhysicalDevice)pd, &pd->server_mem);
build_memory_model(pd);
uint32_t n = 0;
vkb_wire_vkEnumerateDeviceExtensionProperties((VkPhysicalDevice)pd, NULL, &n, NULL);
VkExtensionProperties *srv = calloc(n + 1, sizeof(*srv));
vkb_wire_vkEnumerateDeviceExtensionProperties((VkPhysicalDevice)pd, NULL, &n, srv);
uint32_t ncl = 0;
const char *const *cl = vkb_client_device_exts(&ncl);
uint32_t nemu = 0;
const VkExtensionProperties *emu = vkb_emu_extensions(pd, &nemu);
pd->exts = calloc(n + ncl + nemu + 2, sizeof(VkExtensionProperties));
pd->next = 0;
for (uint32_t i = 0; i < n; i++)
if (bridged_device_ext(srv[i].extensionName) && bridged_device_ext_for(pd, srv[i].extensionName)) pd->exts[pd->next++] = srv[i];
for (uint32_t i = 0; i < nemu; i++) {
int dup = 0;
for (uint32_t j = 0; j < pd->next; j++) dup |= !strcmp(pd->exts[j].extensionName, emu[i].extensionName);
if (!dup) pd->exts[pd->next++] = emu[i];
}
if (drm_node(NULL, NULL)) {
VkExtensionProperties *e = &pd->exts[pd->next++];
snprintf(e->extensionName, sizeof(e->extensionName), "VK_EXT_physical_device_drm");
e->specVersion = 1;
}
for (uint32_t i = 0; i < ncl; i++) {
VkExtensionProperties *e = &pd->exts[pd->next++];
snprintf(e->extensionName, sizeof(e->extensionName), "%s", cl[i]);
e->specVersion = !strcmp(cl[i], "VK_KHR_swapchain") ? 70 : !strcmp(cl[i], "VK_KHR_incremental_present") ? 2 : 1;
}
free(srv);
VKB_INFO("%s: %u extensions offered, memory sharing %s, emulation 0x%x", pd->props.deviceName, pd->next,
vkb_mem_strategy_name(pd->info.strategy), pd->info.emu);
pd->ready = 1;
pthread_mutex_unlock(&pd->lock);
return 1;
}
/* ------------------------------------------------------------------ properties */
uint32_t vkb_max_api(void)
{
/* VKBRIDGE_MAX_API=1.3 imitates an older driver (testing). */
const char *v = getenv("VKBRIDGE_MAX_API");
unsigned ma, mi;
if (v && sscanf(v, "%u.%u", &ma, &mi) == 2) return VK_MAKE_API_VERSION(0, ma, mi, 0xfff);
return VKB_API_VERSION;
}
static void patch_props(vkb_physdev *pd, VkPhysicalDeviceProperties *p)
{
if (p->apiVersion > vkb_max_api()) p->apiVersion = vkb_max_api();
(void)pd;
}
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties *pProperties)
{
vkb_physdev *pd = vkb_pd(physicalDevice);
if (vkb_physdev_ready(pd)) *pProperties = pd->props;
else vkb_wire_vkGetPhysicalDeviceProperties(physicalDevice, pProperties);
patch_props(pd, pProperties);
vkb_emu_patch_limits(pd, pProperties);
}
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceProperties2(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties2 *pProperties)
{
vkb_physdev *pd = vkb_pd(physicalDevice);
vkb_wire_vkGetPhysicalDeviceProperties2(physicalDevice, pProperties);
patch_props(pd, &pProperties->properties);
vkb_emu_patch_limits(pd, &pProperties->properties);
for (VkBaseOutStructure *b = (VkBaseOutStructure *)pProperties->pNext; b; b = b->pNext) {
if (b->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES) {
VkPhysicalDeviceDriverProperties *d = (VkPhysicalDeviceDriverProperties *)b;
size_t l = strlen(d->driverInfo);
if (l + 12 < sizeof(d->driverInfo)) strcat(d->driverInfo, " (vkbridge)");
}
if (b->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRM_PROPERTIES_EXT) {
VkPhysicalDeviceDrmPropertiesEXT *d = (VkPhysicalDeviceDrmPropertiesEXT *)b;
int64_t ma, mi;
if (drm_node(&ma, &mi)) {
/* gamescope insists on a primary node; the stand-in serves as both, as with
* KGSL on an Adreno. */
d->hasPrimary = VK_TRUE;
d->primaryMajor = ma;
d->primaryMinor = mi;
d->hasRender = VK_TRUE;
d->renderMajor = ma;
d->renderMinor = mi;
}
}
vkb_emu_patch_properties_chain(pd, b);
}
}
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceFeatures(VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures *pFeatures)
{
vkb_wire_vkGetPhysicalDeviceFeatures(physicalDevice, pFeatures);
vkb_emu_patch_features(vkb_pd(physicalDevice), pFeatures);
}
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceFeatures2(VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures2 *pFeatures)
{
vkb_wire_vkGetPhysicalDeviceFeatures2(physicalDevice, pFeatures);
vkb_emu_patch_features(vkb_pd(physicalDevice), &pFeatures->features);
for (VkBaseOutStructure *b = (VkBaseOutStructure *)pFeatures->pNext; b; b = b->pNext)
vkb_emu_patch_features_chain(vkb_pd(physicalDevice), b);
}
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceMemoryProperties(VkPhysicalDevice physicalDevice,
VkPhysicalDeviceMemoryProperties *pMemoryProperties)
{
vkb_physdev *pd = vkb_pd(physicalDevice);
if (!vkb_physdev_ready(pd)) {
memset(pMemoryProperties, 0, sizeof(*pMemoryProperties));
return;
}
*pMemoryProperties = pd->client_mem;
}
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceMemoryProperties2(VkPhysicalDevice physicalDevice,
VkPhysicalDeviceMemoryProperties2 *pMemoryProperties)
{
vkb_physdev *pd = vkb_pd(physicalDevice);
VkPhysicalDeviceMemoryBudgetPropertiesEXT *budget = NULL;
for (VkBaseOutStructure *b = (VkBaseOutStructure *)pMemoryProperties->pNext; b; b = b->pNext)
if (b->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT) budget = (void *)b;
vkb_wire_vkGetPhysicalDeviceMemoryProperties2(physicalDevice, pMemoryProperties);
if (!vkb_physdev_ready(pd)) return;
pMemoryProperties->memoryProperties = pd->client_mem;
if (budget) {
VkDeviceSize b[VK_MAX_MEMORY_HEAPS], u[VK_MAX_MEMORY_HEAPS];
memcpy(b, budget->heapBudget, sizeof(b));
memcpy(u, budget->heapUsage, sizeof(u));
memset(budget->heapBudget, 0, sizeof(budget->heapBudget));
memset(budget->heapUsage, 0, sizeof(budget->heapUsage));
for (uint32_t h = 0; h < pd->client_mem.memoryHeapCount; h++) {
budget->heapBudget[h] = b[pd->heap_map[h]];
budget->heapUsage[h] = u[pd->heap_map[h]];
}
}
}
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceQueueFamilyProperties(VkPhysicalDevice physicalDevice, uint32_t *pCount,
VkQueueFamilyProperties *pProps)
{
vkb_wire_vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, pCount, pProps);
}
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceQueueFamilyProperties2(VkPhysicalDevice physicalDevice, uint32_t *pCount,
VkQueueFamilyProperties2 *pProps)
{
vkb_wire_vkGetPhysicalDeviceQueueFamilyProperties2(physicalDevice, pCount, pProps);
}
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceFormatProperties(VkPhysicalDevice physicalDevice, VkFormat format,
VkFormatProperties *pFormatProperties)
{
if (vkb_emu_format_properties(vkb_pd(physicalDevice), format, pFormatProperties, NULL)) return;
vkb_wire_vkGetPhysicalDeviceFormatProperties(physicalDevice, format, pFormatProperties);
}
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceFormatProperties2(VkPhysicalDevice physicalDevice, VkFormat format,
VkFormatProperties2 *pFormatProperties)
{
if (vkb_emu_format_properties(vkb_pd(physicalDevice), format, &pFormatProperties->formatProperties, pFormatProperties)) return;
vkb_wire_vkGetPhysicalDeviceFormatProperties2(physicalDevice, format, pFormatProperties);
}
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetPhysicalDeviceImageFormatProperties(VkPhysicalDevice physicalDevice, VkFormat format,
VkImageType type, VkImageTiling tiling,
VkImageUsageFlags usage, VkImageCreateFlags flags,
VkImageFormatProperties *pProps)
{
VkFormat real = vkb_emu_real_format(vkb_pd(physicalDevice), format, &usage, &flags);
return vkb_wire_vkGetPhysicalDeviceImageFormatProperties(physicalDevice, real, type, tiling, usage, flags, pProps);
}
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetPhysicalDeviceImageFormatProperties2(VkPhysicalDevice physicalDevice,
const VkPhysicalDeviceImageFormatInfo2 *pInfo,
VkImageFormatProperties2 *pProps)
{
VkPhysicalDeviceImageFormatInfo2 info = *pInfo;
info.format = vkb_emu_real_format(vkb_pd(physicalDevice), pInfo->format, &info.usage, &info.flags);
return vkb_wire_vkGetPhysicalDeviceImageFormatProperties2(physicalDevice, &info, pProps);
}
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceSparseImageFormatProperties(VkPhysicalDevice physicalDevice, VkFormat format,
VkImageType type, VkSampleCountFlagBits samples,
VkImageUsageFlags usage, VkImageTiling tiling,
uint32_t *pCount, VkSparseImageFormatProperties *pProps)
{
if (vkb_emu_is_emulated_format(vkb_pd(physicalDevice), format)) {
*pCount = 0;
return;
}
vkb_wire_vkGetPhysicalDeviceSparseImageFormatProperties(physicalDevice, format, type, samples, usage, tiling, pCount, pProps);
}
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceSparseImageFormatProperties2(VkPhysicalDevice physicalDevice,
const VkPhysicalDeviceSparseImageFormatInfo2 *pInfo,
uint32_t *pCount, VkSparseImageFormatProperties2 *pProps)
{
if (vkb_emu_is_emulated_format(vkb_pd(physicalDevice), pInfo->format)) {
*pCount = 0;
return;
}
vkb_wire_vkGetPhysicalDeviceSparseImageFormatProperties2(physicalDevice, pInfo, pCount, pProps);
}
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetPhysicalDeviceToolProperties(VkPhysicalDevice physicalDevice, uint32_t *pToolCount,
VkPhysicalDeviceToolProperties *pToolProperties)
{
(void)physicalDevice;
(void)pToolProperties;
*pToolCount = 0;
return VK_SUCCESS;
}
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceExternalBufferProperties(VkPhysicalDevice physicalDevice,
const VkPhysicalDeviceExternalBufferInfo *pInfo,
VkExternalBufferProperties *pProps)
{
vkb_wire_vkGetPhysicalDeviceExternalBufferProperties(physicalDevice, pInfo, pProps);
}
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceExternalSemaphoreProperties(VkPhysicalDevice physicalDevice,
const VkPhysicalDeviceExternalSemaphoreInfo *pInfo,
VkExternalSemaphoreProperties *pProps)
{
vkb_wire_vkGetPhysicalDeviceExternalSemaphoreProperties(physicalDevice, pInfo, pProps);
}
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceExternalFenceProperties(VkPhysicalDevice physicalDevice,
const VkPhysicalDeviceExternalFenceInfo *pInfo,
VkExternalFenceProperties *pProps)
{
vkb_wire_vkGetPhysicalDeviceExternalFenceProperties(physicalDevice, pInfo, pProps);
}
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceMultisamplePropertiesEXT(VkPhysicalDevice physicalDevice,
VkSampleCountFlagBits samples,
VkMultisamplePropertiesEXT *pProps)
{
vkb_wire_vkGetPhysicalDeviceMultisamplePropertiesEXT(physicalDevice, samples, pProps);
}