gallery/shim/vkshim.c
Greg Pomerantz 2f4768f354 Fix 120Hz scroll jank end-to-end: render-ahead present + full-cache eviction off the UI thread
Three root causes, all fixed and verified on the Pixel 9 Pro:

1. Present phase (backend + renderer): VSYNC_LOCKED_RENDER defers the
   render to the vsync wake; RENDER_AHEAD presents the previous frame at
   the start of render() (phase = wake + ~1.1 ms) and holds the new
   frame one slot. Crash fix: a held wgpu frame must be PRESENTED, never
   merely dropped, before the next acquire (AlreadyAcquired panic at
   launch otherwise); discard_pending_frame presents too.

2. Full-res prefetch churn (src/lib.rs): the 8-slot full cache cycled
   through a ~20-cell viewport forever (137 decodes/20 s) and each
   eviction free()'d a 50 MB Vec on the UI thread (20-52 ms CPU spikes,
   ~7/s = the residual SLOWEV NonMain jank). Fix: 60 s tombstone
   cooldown on evicted paths (idle prefetch skips, urgent tap bypasses)
   + off-thread reaper for the freed blobs (insert: 21 ms -> 4 us).

3. (earlier) Skia final flush + Mailbox present mode (vendor, M3).

Instrumentation (all behind --features perf): OTHR/INVK/INVK_TAG/FLCK
lines for cross-thread closure timing and push-site tagging.

Warm measured swipe, 8580-file grid: cadence median 8.33 ms (exact
120 Hz), p99 15.5 ms, 5/552 gaps >16.7 ms (0.9%, max 28.3 ms, zero
>33 ms); PRES phase median ~994 us, zero late frames. Docs updated
(docs/BUGS/scroll-jank-120hz.md, full scorecard + line reference).

Also: vendored i-slint-core + wgpu-hal (vkshim interposition patch),
vkshim profiling tooling, backend made a non-optional dep, debuggable
flag for simpleperf (REMOVE before shipping).
2026-10-30 02:49:23 -04:00

532 lines
22 KiB
C

// vkshim.c — Vulkan loader interposer for jank-hunt (see
// docs/BUGS/scroll-jank-120hz.md).
//
// Packaged INSIDE the app APK as lib/<abi>/libvulkan.so. The app's linker
// namespace searches the APK lib dir before /system/lib64, so when wgpu-hal
// dlopens "libvulkan.so" it gets THIS library instead of the system loader.
// We forward every call to the real /system/lib64/libvulkan.so (dlopened by
// absolute path in the constructor) and time a curated set of device-level
// calls. Slow ones are logged (with a backtrace) to
// /sdcard/Download/.gallery_vk.log.
//
// This answers, for each slow frame: WHICH vk call blocked, with what
// arguments (e.g. AcquireNextImage timeout), and from which code path
// (stack: libgallery.so = Skia/wgpu/Slint; libvulkan.so/loader = ICD dispatch;
// libmali = driver; libc futex = fence/poll wait).
//
// Threshold: 2 ms default, override with env VKSHIM_MS (milliseconds).
#define _GNU_SOURCE
#include <dlfcn.h>
#include <unwind.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <fcntl.h>
#include <unistd.h>
// We intentionally do NOT include <vulkan/vulkan.h> (the NDK sysroot has it,
// but a lean hand-rolled subset keeps this file self-contained and avoids any
// ABI drift with the loader's actual structs — we only read fixed offsets).
#ifndef VKAPI_PTR
#define VKAPI_PTR
#endif
#ifndef VKAPI_ATTR
#define VKAPI_ATTR
#endif
#ifndef VKAPI_CALL
#define VKAPI_CALL
#endif
typedef void *VkInstance;
typedef void *VkDevice;
typedef void *VkQueue;
typedef void *VkDeviceMemory;
typedef void *VkSemaphore;
typedef void *VkFence;
typedef void *VkCommandBuffer;
typedef void *VkSurfaceKHR;
typedef void *VkSwapchainKHR;
typedef uint32_t VkResult;
typedef uint64_t VkDeviceSize;
typedef uint32_t VkPipelineStageFlags;
typedef void (VKAPI_PTR *PFN_vkVoidFunction)(void);
// GetInstanceProcAddr / GetDeviceProcAddr shape: handle + name -> function ptr.
typedef PFN_vkVoidFunction (*GetProcAddrFn)(void *handle, const char *name);
// ---------------------------------------------------------------------------
// Globals
// ---------------------------------------------------------------------------
static void *g_real = NULL;
static PFN_vkVoidFunction g_real_CreateInstance;
static PFN_vkVoidFunction g_real_CreateDevice;
static GetProcAddrFn g_real_GetInstanceProcAddr;
static GetProcAddrFn g_real_GetDeviceProcAddr;
static int g_logfd = -1;
static uint64_t g_threshold_us = 2000; // 2 ms
// Per-frame aggregate (reset at each present). Answers "how much of a slow
// frame was actually inside vk calls vs between them (CPU work / desched /
// sub-threshold polling loops)". Single-threaded in this app (UI thread
// drives all Vulkan calls), so plain statics suffice.
static uint64_t now_ns(void);
static uint32_t f_sub_n; static uint64_t f_sub_ns;
static uint32_t f_acq_n; static uint64_t f_acq_ns;
static uint32_t f_o_n; static uint64_t f_o_ns;
static void f_count(int which, uint64_t ns) {
switch (which) {
case 0: f_sub_n++; f_sub_ns += ns; break;
case 1: f_acq_n++; f_acq_ns += ns; break;
default: f_o_n++; f_o_ns += ns; break;
}
}
static void f_report(uint32_t pre_rc) {
char b[160];
int n = snprintf(b, sizeof(b),
"VKF fsub=%u/%llu facq=%u/%llu fo=%u/%llu ts=%llu pre_rc=%u\n",
f_sub_n, (unsigned long long)f_sub_ns,
f_acq_n, (unsigned long long)f_acq_ns,
f_o_n, (unsigned long long)f_o_ns,
(unsigned long long)now_ns(), pre_rc);
if (g_logfd >= 0) { ssize_t r = write(g_logfd, b, (size_t)n); (void)r; }
f_sub_n = f_sub_ns = f_acq_n = f_acq_ns = f_o_n = f_o_ns = 0;
}
static uint64_t now_ns(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
}
// ---------------------------------------------------------------------------
// Backtrace capture (libunwind) + module labeling via dladdr
// ---------------------------------------------------------------------------
struct bt_ctx {
uintptr_t addrs[20];
int n;
};
static _Unwind_Reason_Code bt_cb(struct _Unwind_Context *ctx, void *arg) {
struct bt_ctx *c = (struct bt_ctx *)arg;
uintptr_t ip = (uintptr_t)_Unwind_GetIP(ctx);
if (ip) c->addrs[c->n++] = ip;
if (c->n >= 20) return _URC_END_OF_STACK;
return _URC_NO_REASON;
}
static void log_line(const char *name, uint64_t dt_us, uint32_t rc,
const char *args, uint64_t calls, int with_stack) {
char buf[2048];
int off = snprintf(buf, sizeof(buf),
"VKC %-28s dt=%6llu rc=%u %s calls=%llu ts=%llu",
name, (unsigned long long)dt_us, rc,
args ? args : "", (unsigned long long)calls,
(unsigned long long)now_ns());
if (with_stack) {
struct bt_ctx c;
memset(&c, 0, sizeof(c));
_Unwind_Backtrace(bt_cb, &c);
for (int i = 0; i < c.n && off < (int)sizeof(buf) - 160; i++) {
Dl_info info;
const char *mod = "?";
uintptr_t base = 0;
if (dladdr((void *)c.addrs[i], &info)) {
if (info.dli_fname) {
const char *b = strrchr(info.dli_fname, '/');
mod = b ? b + 1 : info.dli_fname;
}
base = (uintptr_t)info.dli_fbase;
}
off += snprintf(buf + off, sizeof(buf) - off, " %s+0x%llx", mod,
(unsigned long long)(c.addrs[i] - base));
}
}
buf[off++] = '\n';
if (g_logfd >= 0) {
ssize_t r = write(g_logfd, buf, (size_t)off);
(void)r;
}
}
// ---------------------------------------------------------------------------
// Wrapped device-level functions.
// All Vulkan functions we intercept return VkResult (uint32).
// Argument structs: only FIXED OFFSETS are read (64-bit ABI):
// VkSubmitInfo: [4]=waitCount [10]=commandBufferCount (size 64)
// VkPresentInfoKHR: [4]=waitCount [8]=swapchainCount
// ---------------------------------------------------------------------------
#define INTERCEPT_TABLE \
X(vkQueueSubmit) \
X(vkQueuePresentKHR) \
X(vkAcquireNextImageKHR) \
X(vkQueueWaitIdle) \
X(vkDeviceWaitIdle) \
X(vkWaitForFences) \
X(vkAllocateMemory) \
X(vkMapMemory) \
X(vkCreateShaderModule) \
X(vkCreateGraphicsPipelines) \
X(vkCreateComputePipelines) \
X(vkCreateSwapchainKHR) \
X(vkDestroySwapchainKHR) \
X(vkResetCommandPool)
struct intercept_entry {
const char *name;
PFN_vkVoidFunction real;
uint64_t calls;
void *wrap;
};
// Forward declaration (the table itself is defined below the wrappers, which
// need the per-entry call counters).
static struct intercept_entry g_entries[14];
static PFN_vkVoidFunction g_real_vkQueueSubmit;
static PFN_vkVoidFunction g_real_vkQueuePresentKHR;
static PFN_vkVoidFunction g_real_vkAcquireNextImageKHR;
static PFN_vkVoidFunction g_real_vkQueueWaitIdle;
static PFN_vkVoidFunction g_real_vkDeviceWaitIdle;
static PFN_vkVoidFunction g_real_vkWaitForFences;
static PFN_vkVoidFunction g_real_vkAllocateMemory;
static PFN_vkVoidFunction g_real_vkMapMemory;
static PFN_vkVoidFunction g_real_vkCreateShaderModule;
static PFN_vkVoidFunction g_real_vkCreateGraphicsPipelines;
static PFN_vkVoidFunction g_real_vkCreateComputePipelines;
static PFN_vkVoidFunction g_real_vkCreateSwapchainKHR;
static PFN_vkVoidFunction g_real_vkDestroySwapchainKHR;
static PFN_vkVoidFunction g_real_vkResetCommandPool;
static uint32_t w_vkQueueSubmit(VkQueue q, uint32_t n, const void *pSubmits, void *fence) {
uint32_t cmds = 0, waits = 0;
if (pSubmits)
for (uint32_t i = 0; i < n; i++) {
const uint32_t *s = (const uint32_t *)((const char *)pSubmits + i * 64);
cmds += s[10];
waits += s[4];
}
char a[64];
snprintf(a, sizeof(a), "nSubmits=%u cmds=%u waits=%u", n, cmds, waits);
uint64_t t0 = now_ns();
uint32_t rc = ((VkResult (*)(VkQueue, uint32_t, const void *, void *))g_real_vkQueueSubmit)(
q, n, pSubmits, fence);
uint64_t dt = (now_ns() - t0) / 1000;
f_count(0, dt * 1000);
if (dt >= g_threshold_us)
log_line("vkQueueSubmit", dt, rc, a, ++g_entries[0].calls, 1);
return rc;
}
static uint32_t w_vkQueuePresentKHR(VkQueue q, const void *pPresent) {
uint32_t swaps = 0;
if (pPresent) swaps = ((const uint32_t *)pPresent)[8];
char a[32];
snprintf(a, sizeof(a), "swaps=%u", swaps);
uint64_t t0 = now_ns();
uint32_t rc =
((VkResult (*)(VkQueue, const void *))g_real_vkQueuePresentKHR)(q, pPresent);
uint64_t dt = (now_ns() - t0) / 1000;
if (dt >= g_threshold_us)
log_line("vkQueuePresentKHR", dt, rc, a, ++g_entries[1].calls, 1);
f_report(rc);
return rc;
}
static uint32_t w_vkAcquireNextImageKHR(VkDevice d, void *swap, uint64_t timeout,
void *sem, void *fence, uint32_t *pIdx) {
char a[64];
snprintf(a, sizeof(a), "timeout=%llu", (unsigned long long)timeout);
uint64_t t0 = now_ns();
uint32_t rc = ((VkResult (*)(VkDevice, void *, uint64_t, void *, void *, uint32_t *))
g_real_vkAcquireNextImageKHR)(d, swap, timeout, sem, fence, pIdx);
uint64_t dt = (now_ns() - t0) / 1000;
f_count(1, dt * 1000);
if (dt >= g_threshold_us)
log_line("vkAcquireNextImageKHR", dt, rc, a, ++g_entries[2].calls, 1);
return rc;
}
static uint32_t w_vkQueueWaitIdle(VkQueue q) {
uint64_t t0 = now_ns();
uint32_t rc = ((VkResult (*)(VkQueue))g_real_vkQueueWaitIdle)(q);
uint64_t dt = (now_ns() - t0) / 1000;
if (dt >= g_threshold_us)
log_line("vkQueueWaitIdle", dt, rc, "", ++g_entries[3].calls, 1);
return rc;
}
static uint32_t w_vkDeviceWaitIdle(VkDevice d) {
uint64_t t0 = now_ns();
uint32_t rc = ((VkResult (*)(VkDevice))g_real_vkDeviceWaitIdle)(d);
uint64_t dt = (now_ns() - t0) / 1000;
if (dt >= g_threshold_us)
log_line("vkDeviceWaitIdle", dt, rc, "", ++g_entries[4].calls, 1);
return rc;
}
static uint32_t w_vkWaitForFences(VkDevice d, uint32_t n, const void *fences,
uint32_t waitAll, uint64_t timeout) {
char a[64];
snprintf(a, sizeof(a), "n=%u all=%u timeout=%llu", n, waitAll,
(unsigned long long)timeout);
uint64_t t0 = now_ns();
uint32_t rc =
((VkResult (*)(VkDevice, uint32_t, const void *, uint32_t, uint64_t))
g_real_vkWaitForFences)(d, n, fences, waitAll, timeout);
uint64_t dt = (now_ns() - t0) / 1000;
if (dt >= g_threshold_us)
log_line("vkWaitForFences", dt, rc, a, ++g_entries[5].calls, 1);
return rc;
}
static uint32_t w_vkAllocateMemory(VkDevice d, const void *pInfo, const void *pAlloc,
void *pMem) {
char a[64];
// VkMemoryAllocateInfo: [0]=sType [8]=pNext [16]=allocationSize [24]=memoryTypeIndex
if (pInfo) {
const uint32_t *s = (const uint32_t *)pInfo;
snprintf(a, sizeof(a), "typeIdx=%u size=%llu", s[6],
(unsigned long long)(((const uint64_t *)pInfo)[2]));
} else {
a[0] = 0;
}
uint64_t t0 = now_ns();
uint32_t rc =
((VkResult (*)(VkDevice, const void *, const void *, void *))
g_real_vkAllocateMemory)(d, pInfo, pAlloc, pMem);
uint64_t dt = (now_ns() - t0) / 1000;
if (dt >= g_threshold_us)
log_line("vkAllocateMemory", dt, rc, a, ++g_entries[6].calls, 1);
return rc;
}
static uint32_t w_vkMapMemory(VkDevice d, void *mem, VkDeviceSize off, VkDeviceSize size,
uint32_t flags, void *ppData) {
char a[64];
snprintf(a, sizeof(a), "off=%llu size=%llu", (unsigned long long)off,
(unsigned long long)size);
uint64_t t0 = now_ns();
uint32_t rc = ((VkResult (*)(VkDevice, void *, VkDeviceSize, VkDeviceSize, uint32_t, void *))
g_real_vkMapMemory)(d, mem, off, size, flags, ppData);
uint64_t dt = (now_ns() - t0) / 1000;
if (dt >= g_threshold_us)
log_line("vkMapMemory", dt, rc, a, ++g_entries[7].calls, 1);
return rc;
}
static uint32_t w_vkCreateShaderModule(VkDevice d, const void *pInfo, const void *pAlloc,
void *pModule) {
char a[48];
if (pInfo) {
const uint64_t *s = (const uint64_t *)pInfo;
snprintf(a, sizeof(a), "codeKB=%llu", (unsigned long long)(s[1] / 1024));
} else {
a[0] = 0;
}
uint64_t t0 = now_ns();
uint32_t rc =
((VkResult (*)(VkDevice, const void *, const void *, void *))
g_real_vkCreateShaderModule)(d, pInfo, pAlloc, pModule);
uint64_t dt = (now_ns() - t0) / 1000;
if (dt >= g_threshold_us)
log_line("vkCreateShaderModule", dt, rc, a, ++g_entries[8].calls, 1);
return rc;
}
static uint32_t w_vkCreateGraphicsPipelines(VkDevice d, void *cache, uint32_t n,
const void *pInfos, const void *pAlloc,
void *pPipelines) {
char a[32];
snprintf(a, sizeof(a), "n=%u", n);
uint64_t t0 = now_ns();
uint32_t rc = ((VkResult (*)(VkDevice, void *, uint32_t, const void *, const void *, void *))
g_real_vkCreateGraphicsPipelines)(d, cache, n, pInfos, pAlloc, pPipelines);
uint64_t dt = (now_ns() - t0) / 1000;
if (dt >= g_threshold_us)
log_line("vkCreateGraphicsPipelines", dt, rc, a, ++g_entries[9].calls, 1);
return rc;
}
static uint32_t w_vkCreateComputePipelines(VkDevice d, void *cache, uint32_t n,
const void *pInfos, const void *pAlloc,
void *pPipelines) {
char a[32];
snprintf(a, sizeof(a), "n=%u", n);
uint64_t t0 = now_ns();
uint32_t rc = ((VkResult (*)(VkDevice, void *, uint32_t, const void *, const void *, void *))
g_real_vkCreateComputePipelines)(d, cache, n, pInfos, pAlloc, pPipelines);
uint64_t dt = (now_ns() - t0) / 1000;
if (dt >= g_threshold_us)
log_line("vkCreateComputePipelines", dt, rc, a, ++g_entries[10].calls, 1);
return rc;
}
static uint32_t w_vkCreateSwapchainKHR(VkDevice d, const void *pInfo, const void *pAlloc,
void *pSwap) {
char a[32];
if (pInfo)
snprintf(a, sizeof(a), "images=%u", ((const uint32_t *)pInfo)[8]);
else
a[0] = 0;
uint64_t t0 = now_ns();
uint32_t rc = ((VkResult (*)(VkDevice, const void *, const void *, void *))
g_real_vkCreateSwapchainKHR)(d, pInfo, pAlloc, pSwap);
uint64_t dt = (now_ns() - t0) / 1000;
if (dt >= g_threshold_us)
log_line("vkCreateSwapchainKHR", dt, rc, a, ++g_entries[11].calls, 1);
return rc;
}
static uint32_t w_vkDestroySwapchainKHR(VkDevice d, void *swap, const void *pAlloc) {
uint64_t t0 = now_ns();
uint32_t rc =
((VkResult (*)(VkDevice, void *, const void *))g_real_vkDestroySwapchainKHR)(
d, swap, pAlloc);
uint64_t dt = (now_ns() - t0) / 1000;
if (dt >= g_threshold_us)
log_line("vkDestroySwapchainKHR", dt, rc, "", ++g_entries[12].calls, 1);
return rc;
}
static uint32_t w_vkResetCommandPool(VkDevice d, void *pool, uint32_t flags) {
uint64_t t0 = now_ns();
uint32_t rc =
((VkResult (*)(VkDevice, void *, uint32_t))g_real_vkResetCommandPool)(d, pool, flags);
uint64_t dt = (now_ns() - t0) / 1000;
if (dt >= g_threshold_us)
log_line("vkResetCommandPool", dt, rc, "", ++g_entries[13].calls, 1);
return rc;
}
// Table index order must match the g_entries[N] used above.
static struct intercept_entry g_entries[14] = {
{"vkQueueSubmit", NULL, 0, (void *)w_vkQueueSubmit},
{"vkQueuePresentKHR", NULL, 0, (void *)w_vkQueuePresentKHR},
{"vkAcquireNextImageKHR", NULL, 0, (void *)w_vkAcquireNextImageKHR},
{"vkQueueWaitIdle", NULL, 0, (void *)w_vkQueueWaitIdle},
{"vkDeviceWaitIdle", NULL, 0, (void *)w_vkDeviceWaitIdle},
{"vkWaitForFences", NULL, 0, (void *)w_vkWaitForFences},
{"vkAllocateMemory", NULL, 0, (void *)w_vkAllocateMemory},
{"vkMapMemory", NULL, 0, (void *)w_vkMapMemory},
{"vkCreateShaderModule", NULL, 0, (void *)w_vkCreateShaderModule},
{"vkCreateGraphicsPipelines", NULL, 0, (void *)w_vkCreateGraphicsPipelines},
{"vkCreateComputePipelines", NULL, 0, (void *)w_vkCreateComputePipelines},
{"vkCreateSwapchainKHR", NULL, 0, (void *)w_vkCreateSwapchainKHR},
{"vkDestroySwapchainKHR", NULL, 0, (void *)w_vkDestroySwapchainKHR},
{"vkResetCommandPool", NULL, 0, (void *)w_vkResetCommandPool},
};
// Pointers to the real-func slots, so shim_GetDeviceProcAddr can publish the
// lazily-resolved real pointers into the globals the wrappers call.
static PFN_vkVoidFunction *g_entry_real[14] = {
&g_real_vkQueueSubmit,
&g_real_vkQueuePresentKHR,
&g_real_vkAcquireNextImageKHR,
&g_real_vkQueueWaitIdle,
&g_real_vkDeviceWaitIdle,
&g_real_vkWaitForFences,
&g_real_vkAllocateMemory,
&g_real_vkMapMemory,
&g_real_vkCreateShaderModule,
&g_real_vkCreateGraphicsPipelines,
&g_real_vkCreateComputePipelines,
&g_real_vkCreateSwapchainKHR,
&g_real_vkDestroySwapchainKHR,
&g_real_vkResetCommandPool,
};
// ---------------------------------------------------------------------------
// Proc-addr dispatch
// ---------------------------------------------------------------------------
static PFN_vkVoidFunction shim_GetDeviceProcAddr(VkDevice dev, const char *name) {
for (int i = 0; i < 14; i++) {
if (strcmp(name, g_entries[i].name) == 0) {
if (!g_entries[i].real) {
g_entries[i].real = g_real_GetDeviceProcAddr(dev, name);
*g_entry_real[i] = g_entries[i].real;
}
return (PFN_vkVoidFunction)g_entries[i].wrap;
}
}
return g_real_GetDeviceProcAddr(dev, name);
}
static VkResult shim_CreateDevice(VkDevice d, const void *pInfo, const void *pAlloc,
void *pDev) {
uint64_t t0 = now_ns();
VkResult rc =
((VkResult (*)(VkDevice, const void *, const void *, void *))g_real_CreateDevice)(
d, pInfo, pAlloc, pDev);
uint64_t dt = (now_ns() - t0) / 1000;
log_line("vkCreateDevice", dt, rc, "one-shot", 1, dt >= g_threshold_us);
return rc;
}
static PFN_vkVoidFunction shim_GetInstanceProcAddr(VkInstance inst, const char *name) {
if (!strcmp(name, "vkGetDeviceProcAddr")) return (PFN_vkVoidFunction)shim_GetDeviceProcAddr;
if (!strcmp(name, "vkCreateDevice")) return (PFN_vkVoidFunction)shim_CreateDevice;
return g_real_GetInstanceProcAddr(inst, name);
}
static VkResult shim_CreateInstance(const void *pInfo, const void *pAlloc, void *pInst) {
uint64_t t0 = now_ns();
VkResult rc =
((VkResult (*)(const void *, const void *, void *))g_real_CreateInstance)(
pInfo, pAlloc, pInst);
uint64_t dt = (now_ns() - t0) / 1000;
log_line("vkCreateInstance", dt, rc, "one-shot", 1, dt >= g_threshold_us);
return rc;
}
// ---------------------------------------------------------------------------
// Entry points exported to the app (ash dlsyms vkGetInstanceProcAddr; other
// symbols may be dlsym'd directly by other tooling, so export the common ones).
// ---------------------------------------------------------------------------
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(VkInstance inst,
const char *name) {
return shim_GetInstanceProcAddr(inst, name);
}
VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(const void *pCreateInfo,
const void *pAllocator, void *pInstance) {
return shim_CreateInstance(pCreateInfo, pAllocator, pInstance);
}
VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(VkInstance instance, const void *pCreateInfo,
const void *pAllocator, void *pDevice) {
return shim_CreateDevice(instance, pCreateInfo, pAllocator, pDevice);
}
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkDevice device,
const char *pName) {
return shim_GetDeviceProcAddr(device, pName);
}
// ---------------------------------------------------------------------------
// Init
// ---------------------------------------------------------------------------
__attribute__((constructor)) static void vkshim_init(void) {
g_real = dlopen("/system/lib64/libvulkan.so", RTLD_NOW | RTLD_LOCAL);
if (!g_real) {
g_logfd = open("/sdcard/Download/.gallery_vk.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
const char m[] = "VKSHIM FAIL: could not dlopen /system/lib64/libvulkan.so\n";
if (g_logfd >= 0) write(g_logfd, m, sizeof(m) - 1);
return;
}
g_real_CreateInstance = (PFN_vkVoidFunction)dlsym(g_real, "vkCreateInstance");
g_real_CreateDevice = (PFN_vkVoidFunction)dlsym(g_real, "vkCreateDevice");
g_real_GetInstanceProcAddr = (GetProcAddrFn)dlsym(g_real, "vkGetInstanceProcAddr");
g_real_GetDeviceProcAddr = (GetProcAddrFn)dlsym(g_real, "vkGetDeviceProcAddr");
const char *ms = getenv("VKSHIM_MS");
if (ms) g_threshold_us = (uint64_t)atoll(ms) * 1000;
g_logfd = open("/sdcard/Download/.gallery_vk.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
char b[256];
int n = snprintf(b, sizeof(b),
"VKSHIM init ok pid=%d real=%p thr_us=%llu logfd=%d\n",
(int)getpid(), g_real, (unsigned long long)g_threshold_us, g_logfd);
if (g_logfd >= 0) write(g_logfd, b, (size_t)n);
}