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).
532 lines
22 KiB
C
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);
|
|
}
|