mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 02:51:10 +00:00
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.
This commit is contained in:
1 parent
cf06f89c11
commit
ee0fb08268
125 files changed
+119982
-12
No files matched your search
@@ -186,7 +186,7 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: app/src/main/assets/linuxfs
|
||||
key: preloads-${{ env.PRELOAD_CACHE_VERSION }}-${{ hashFiles('app/src/main/cpp/fakeinput_steam.cpp', 'app/src/main/cpp/adrenotools/src/hook/kgsl.h', 'tools/quest/*', 'tools/linuxfs/preload/*.c', 'tools/linuxfs/build-x86-preloads.sh', 'tools/linuxfs/fex/*', 'tools/linuxfs/clipboard/*.c', 'tools/linuxfs/ssbs/*.c', 'tools/linuxfs/xclipse-layer/*', 'tools/proot/fastpath/*.c', 'tools/linuxfs/overlay/usr/local/bin/*', 'tools/linuxfs/overlay/usr/bin/**', 'tools/linuxfs/desktop/*', 'tools/gamescope/release.env', 'tools/wlroots/release.env', 'tools/linuxfs/uruntime.env', 'tools/linuxfs/licenses/*', 'tools/mangoapp/*', 'tools/msitools/**', 'tools/comet/**', 'tools/linuxfs/debian-image.env') }}
|
||||
key: preloads-${{ env.PRELOAD_CACHE_VERSION }}-${{ hashFiles('app/src/main/cpp/fakeinput_steam.cpp', 'app/src/main/cpp/adrenotools/src/hook/kgsl.h', 'tools/quest/*', 'tools/linuxfs/preload/*.c', 'tools/linuxfs/build-x86-preloads.sh', 'tools/linuxfs/fex/*', 'tools/linuxfs/clipboard/*.c', 'tools/linuxfs/ssbs/*.c', 'tools/linuxfs/xclipse-layer/*', 'tools/proot/fastpath/*.c', 'tools/linuxfs/overlay/usr/local/bin/*', 'tools/linuxfs/overlay/usr/bin/**', 'tools/linuxfs/desktop/*', 'tools/gamescope/release.env', 'tools/wlroots/release.env', 'tools/linuxfs/uruntime.env', 'tools/linuxfs/licenses/*', 'tools/mangoapp/*', 'tools/msitools/**', 'tools/comet/**', 'tools/vkbridge/client/**', 'tools/vkbridge/common/**', 'tools/vkbridge/tests/**', 'tools/vkbridge/build-client-linux.sh', 'tools/linuxfs/debian-image.env') }}
|
||||
|
||||
- name: Build libfakeinput.so and libblsession.so (glibc aarch64)
|
||||
if: steps.preloads.outputs.cache-hit != 'true'
|
||||
@@ -222,6 +222,8 @@ jobs:
|
||||
aarch64-linux-gnu-strip --strip-unneeded "$D/usr/local/lib/droiddeck-xclipse/libVkLayer_droiddeck_xclipse.so"
|
||||
install -m644 tools/linuxfs/xclipse-layer/VkLayer_droiddeck_xclipse.json "$D/usr/local/lib/droiddeck-xclipse/"
|
||||
mkdir -p "$D/usr/local/bin"
|
||||
# The Mali bridge's Linux side: ICD, manifest and self-test (tools/vkbridge).
|
||||
tools/vkbridge/build-client-linux.sh "$D" aarch64-linux-gnu-gcc
|
||||
aarch64-linux-gnu-gcc -O2 -Wall -Wextra -o "$D/usr/local/bin/droiddeck-clipboard" tools/linuxfs/clipboard/clipboard.c -ldl
|
||||
aarch64-linux-gnu-strip --strip-unneeded "$D/usr/local/bin/droiddeck-clipboard"
|
||||
for script in tools/linuxfs/overlay/usr/local/bin/droiddeck-* tools/linuxfs/overlay/usr/local/bin/steam-compatibility; do
|
||||
@@ -412,6 +414,14 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
bash tools/linuxfs/check-preloads.sh app/src/main/assets/linuxfs
|
||||
D=app/src/main/assets/linuxfs
|
||||
syms() { readelf -Ws "$1" | awk '$4 == "FUNC" && $5 == "GLOBAL" {sub(/@.*/, "", $8); print $8}'; }
|
||||
# The bridge ICD: libc only, and nothing exported but the loader's three entry points.
|
||||
VKB="$D/usr/local/lib/vkbridge/libvulkan_droidbridge.so"
|
||||
test -f "$VKB" && test -f "$D/usr/local/share/vkbridge/droidbridge_icd.json" && test -f "$D/usr/local/bin/droiddeck-vkbridge-smoke"
|
||||
VKB_NEED=$(readelf -d "$VKB" | sed -n 's/.*NEEDED.*\[\(.*\)\]/\1/p' | grep -vx 'libc.so.6' | grep -vx 'ld-linux-aarch64.so.1' || true)
|
||||
[ -z "$VKB_NEED" ] || { echo "::error::the bridge ICD needs $VKB_NEED"; exit 1; }
|
||||
[ "$(syms "$VKB" | grep -c '^vk_icd')" = 3 ] || { echo "::error::the bridge ICD does not export the loader interface"; exit 1; }
|
||||
|
||||
# The store download engines (app/src/main/rust/stores) are Rust, built by Gradle's
|
||||
# buildRustStores task through cargo-ndk. rustup and cargo are on the runner image; the Android
|
||||
|
||||
@@ -149,6 +149,9 @@ android {
|
||||
release {
|
||||
minifyEnabled false
|
||||
signingConfig signingConfigs.testkey
|
||||
// Mali bridge development fork: debuggable so `adb shell run-as` reaches the session
|
||||
// logs and VkBridgeNative's development override (-Pdroiddeck.notDebuggable to drop).
|
||||
debuggable !project.hasProperty('droiddeck.notDebuggable')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,6 +279,24 @@ def buildRustStores = tasks.register('buildRustStores', Exec) {
|
||||
if (!skipRust) android.sourceSets.main.jniLibs.srcDir(rustJniLibsDir)
|
||||
tasks.named('preBuild').configure { dependsOn(buildRustStores) }
|
||||
|
||||
// The Mali bridge's server (tools/vkbridge, app/src/main/cpp/vkbridge) is a program, not a library:
|
||||
// it runs beside the session as the app's child, so a driver crash ends that process and not the
|
||||
// app. AGP's CMake integration packages only shared libraries, so the NDK's clang builds it here
|
||||
// into one more jniLibs source, named libvkbridge_server.so so Android extracts it (the native
|
||||
// library directory is the one place the app may exec from).
|
||||
def vkbridgeJniLibsDir = layout.buildDirectory.dir('vkbridgeJniLibs')
|
||||
def buildVkBridgeServer = tasks.register('buildVkBridgeServer', Exec) {
|
||||
description = 'Builds the Vulkan bridge server (libvkbridge_server.so) with the NDK.'
|
||||
inputs.dir(rootProject.file('tools/vkbridge/common'))
|
||||
inputs.dir(layout.projectDirectory.dir('src/main/cpp/vkbridge'))
|
||||
inputs.file(rootProject.file('tools/vkbridge/build-server-android.sh'))
|
||||
outputs.dir(vkbridgeJniLibsDir)
|
||||
commandLine 'bash', rootProject.file('tools/vkbridge/build-server-android.sh').absolutePath,
|
||||
android.ndkDirectory.absolutePath, vkbridgeJniLibsDir.get().asFile.absolutePath
|
||||
}
|
||||
android.sourceSets.main.jniLibs.srcDir(vkbridgeJniLibsDir)
|
||||
tasks.named('preBuild').configure { dependsOn(buildVkBridgeServer) }
|
||||
|
||||
dependencies {
|
||||
// The runtime tarball is a zstd-compressed tar.
|
||||
implementation 'com.github.luben:zstd-jni:1.5.2-3@aar'
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,535 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Connections: one thread per client thread, requests executed in order, replies sent back.
|
||||
* A command buffer's recorded commands arrive in one message at vkEndCommandBuffer and are
|
||||
* replayed here.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "vkb_server.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static pthread_mutex_t tables_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static vkb_srv_table *tables[VKB_MAX_TABLES];
|
||||
static uint32_t next_table_id = 1;
|
||||
|
||||
static pthread_mutex_t procs_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static vkb_srv_proc *procs;
|
||||
|
||||
static __thread vkb_srv_table *tls_table;
|
||||
__thread const char *vkb_srv_current_cmd;
|
||||
|
||||
vkb_srv_table *vkb_srv_current_table(void) { return tls_table; }
|
||||
void vkb_srv_set_current_table(vkb_srv_table *t) { tls_table = t; }
|
||||
|
||||
/* Server side of the generated encoders' helpers: handles are already the server's values. */
|
||||
uint64_t vkb_remote(const void *h) { return (uint64_t)(uintptr_t)h; }
|
||||
int vkb_cb_begin_is_secondary(const VkCommandBufferBeginInfo *s) { (void)s; return 1; }
|
||||
|
||||
uint32_t vkb_table_register(vkb_srv_table *t)
|
||||
{
|
||||
pthread_mutex_lock(&tables_lock);
|
||||
uint32_t id = 0;
|
||||
for (uint32_t tries = 0; tries < VKB_MAX_TABLES; tries++) {
|
||||
uint32_t cand = next_table_id++;
|
||||
if (next_table_id >= VKB_MAX_TABLES) next_table_id = 1;
|
||||
if (!tables[cand]) {
|
||||
id = cand;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (id) {
|
||||
t->id = id;
|
||||
tables[id] = t;
|
||||
}
|
||||
pthread_mutex_unlock(&tables_lock);
|
||||
return id;
|
||||
}
|
||||
|
||||
void vkb_table_unregister(vkb_srv_table *t)
|
||||
{
|
||||
pthread_mutex_lock(&tables_lock);
|
||||
if (t->id && t->id < VKB_MAX_TABLES && tables[t->id] == t) tables[t->id] = NULL;
|
||||
pthread_mutex_unlock(&tables_lock);
|
||||
}
|
||||
|
||||
vkb_srv_table *vkb_table_get(uint32_t id)
|
||||
{
|
||||
if (!id || id >= VKB_MAX_TABLES) return NULL;
|
||||
pthread_mutex_lock(&tables_lock);
|
||||
vkb_srv_table *t = tables[id];
|
||||
pthread_mutex_unlock(&tables_lock);
|
||||
return t;
|
||||
}
|
||||
|
||||
void vkb_srv_reply_status(vkb_srv_call *c, uint32_t status)
|
||||
{
|
||||
c->status = status;
|
||||
}
|
||||
|
||||
void vkb_srv_bad_message(vkb_srv_call *c)
|
||||
{
|
||||
VKB_ERR("malformed request");
|
||||
c->status = VKB_ST_BAD_MESSAGE;
|
||||
}
|
||||
|
||||
void vkb_srv_missing(vkb_srv_call *c, const char *name)
|
||||
{
|
||||
VKB_ERR("the driver has no %s", name);
|
||||
c->status = VKB_ST_MISSING;
|
||||
}
|
||||
|
||||
void vkb_srv_close_after_send(vkb_srv_call *c, int fd)
|
||||
{
|
||||
if (c->nclose < VKB_MAX_FDS) c->close_after[c->nclose++] = fd;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ process cleanup */
|
||||
|
||||
void vkb_proc_cleanup(vkb_srv_proc *p)
|
||||
{
|
||||
/* Devices first, then their instances. */
|
||||
for (int pass = 0; pass < 2; pass++) {
|
||||
for (uint32_t i = 1; i < VKB_MAX_TABLES; i++) {
|
||||
pthread_mutex_lock(&tables_lock);
|
||||
vkb_srv_table *t = tables[i];
|
||||
int mine = t && t->proc == p && t->is_device == (pass == 0);
|
||||
if (mine) tables[i] = NULL;
|
||||
pthread_mutex_unlock(&tables_lock);
|
||||
if (!mine) continue;
|
||||
if (t->is_device) {
|
||||
VKB_INFO("process %d exited without destroying device %u; cleaning up", p->pid, t->id);
|
||||
if (t->real.vkDeviceWaitIdle) t->real.vkDeviceWaitIdle(t->device);
|
||||
vkb_spvfix_device_destroy(t);
|
||||
vkb_dsmap_device_destroy(t);
|
||||
vkb_pcache_device_destroy(t);
|
||||
vkb_emu_device_destroy(t);
|
||||
vkb_mem_free_all(t);
|
||||
t->real.vkDestroyDevice(t->device, NULL);
|
||||
} else {
|
||||
VKB_INFO("process %d exited without destroying instance %u; cleaning up", p->pid, t->id);
|
||||
t->real.vkDestroyInstance(t->instance, NULL);
|
||||
}
|
||||
pthread_mutex_destroy(&t->lock);
|
||||
free(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static vkb_srv_proc *proc_join(uint64_t token, int pid)
|
||||
{
|
||||
pthread_mutex_lock(&procs_lock);
|
||||
vkb_srv_proc *p = procs;
|
||||
while (p && p->token != token) p = p->next;
|
||||
if (!p) {
|
||||
p = calloc(1, sizeof(*p));
|
||||
p->token = token;
|
||||
p->pid = pid;
|
||||
pthread_mutex_init(&p->olock, NULL);
|
||||
pthread_cond_init(&p->ocond, NULL);
|
||||
p->next = procs;
|
||||
procs = p;
|
||||
VKB_INFO("client process %d connected", pid);
|
||||
}
|
||||
p->conns++;
|
||||
pthread_mutex_unlock(&procs_lock);
|
||||
return p;
|
||||
}
|
||||
|
||||
static void proc_leave(vkb_srv_proc *p)
|
||||
{
|
||||
pthread_mutex_lock(&procs_lock);
|
||||
int last = --p->conns == 0;
|
||||
if (last) {
|
||||
vkb_srv_proc **pp = &procs;
|
||||
while (*pp && *pp != p) pp = &(*pp)->next;
|
||||
if (*pp) *pp = p->next;
|
||||
}
|
||||
pthread_mutex_unlock(&procs_lock);
|
||||
if (last) {
|
||||
vkb_proc_cleanup(p);
|
||||
VKB_INFO("client process %d gone", p->pid);
|
||||
pthread_mutex_destroy(&p->olock);
|
||||
pthread_cond_destroy(&p->ocond);
|
||||
free(p);
|
||||
} else {
|
||||
pthread_mutex_lock(&p->olock);
|
||||
p->conn_closed = 1;
|
||||
pthread_cond_broadcast(&p->ocond);
|
||||
pthread_mutex_unlock(&p->olock);
|
||||
}
|
||||
}
|
||||
|
||||
/* Waits until every async request of the process up to `barrier` has run. */
|
||||
static void order_wait(vkb_srv_proc *p, uint64_t barrier)
|
||||
{
|
||||
if (!p || !barrier) return;
|
||||
pthread_mutex_lock(&p->olock);
|
||||
int waited = 0;
|
||||
while (p->watermark < barrier) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
ts.tv_sec += 1;
|
||||
if (pthread_cond_timedwait(&p->ocond, &p->olock, &ts) != 0 && ++waited >= 3 && p->conn_closed) {
|
||||
/* A connection of this process is gone with requests it never sent: go on. */
|
||||
VKB_WARN("process %d: async request %llu never arrived; continuing", p->pid, (unsigned long long)(p->watermark + 1));
|
||||
p->watermark = barrier;
|
||||
memset(p->done, 0, sizeof(p->done));
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&p->olock);
|
||||
}
|
||||
|
||||
static void order_done(vkb_srv_proc *p, uint64_t seq)
|
||||
{
|
||||
if (!p || !seq) return;
|
||||
pthread_mutex_lock(&p->olock);
|
||||
if (seq > p->watermark) {
|
||||
p->done[seq % VKB_SEQ_RING] = 1;
|
||||
while (p->done[(p->watermark + 1) % VKB_SEQ_RING]) {
|
||||
p->done[(p->watermark + 1) % VKB_SEQ_RING] = 0;
|
||||
p->watermark++;
|
||||
}
|
||||
pthread_cond_broadcast(&p->ocond);
|
||||
}
|
||||
pthread_mutex_unlock(&p->olock);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ command buffer replay */
|
||||
|
||||
static int is_stream_cmd(uint32_t id)
|
||||
{
|
||||
const char *n = vkb_cmd_names[id];
|
||||
return !strncmp(n, "vkCmd", 5) || id == VKB_CMD_vkBeginCommandBuffer || id == VKB_CMD_vkbPushDescRaw;
|
||||
}
|
||||
|
||||
void vkb_sv_vkEndCommandBuffer(vkb_srv_call *c)
|
||||
{
|
||||
VkCommandBuffer cb = (VkCommandBuffer)(uintptr_t)vkb_dec_u64(&c->d);
|
||||
size_t len = 0;
|
||||
const uint8_t *s = vkb_dec_blob(&c->d, &len);
|
||||
if (c->d.err) {
|
||||
vkb_srv_bad_message(c);
|
||||
return;
|
||||
}
|
||||
VkResult begin_r = VK_SUCCESS;
|
||||
size_t pos = 0;
|
||||
vkb_enc scratch;
|
||||
vkb_enc_init(&scratch);
|
||||
uint32_t ncmds = 0;
|
||||
while (s && pos + 8 <= len) {
|
||||
uint32_t id, n;
|
||||
memcpy(&id, s + pos, 4);
|
||||
memcpy(&n, s + pos + 4, 4);
|
||||
pos += 8;
|
||||
if (id >= VKB_CMD_COUNT || !is_stream_cmd(id) || n > len - pos) {
|
||||
VKB_ERR("command stream: bad entry (id %u, %u bytes) after %u commands", id, n, ncmds);
|
||||
c->status = VKB_ST_BAD_MESSAGE;
|
||||
vkb_enc_free(&scratch);
|
||||
return;
|
||||
}
|
||||
vkb_srv_call sub;
|
||||
memset(&sub, 0, sizeof(sub));
|
||||
vkb_dec_init(&sub.d, s + pos, n, c->d.arena, NULL, 0);
|
||||
sub.r = scratch;
|
||||
vkb_enc_reset(&sub.r);
|
||||
sub.dt = c->dt;
|
||||
sub.table = c->table;
|
||||
sub.conn = c->conn;
|
||||
sub.in_stream = 1;
|
||||
vkb_srv_current_cmd = vkb_cmd_names[id];
|
||||
vkb_srv_handlers[id](&sub);
|
||||
vkb_srv_current_cmd = vkb_cmd_names[VKB_CMD_vkEndCommandBuffer];
|
||||
scratch = sub.r;
|
||||
if (sub.status != VKB_ST_OK) {
|
||||
VKB_ERR("command stream: %s failed (status %u)", vkb_cmd_names[id], sub.status);
|
||||
}
|
||||
if (id == VKB_CMD_vkBeginCommandBuffer && scratch.len >= 4) memcpy(&begin_r, scratch.buf, 4);
|
||||
pos += (n + 7) & ~(size_t)7;
|
||||
ncmds++;
|
||||
}
|
||||
vkb_enc_free(&scratch);
|
||||
VkResult r = c->dt->vkEndCommandBuffer(cb);
|
||||
if (begin_r != VK_SUCCESS) r = begin_r;
|
||||
if (vkb_verbose) VKB_DBG("command buffer %p: %u commands, %zu bytes -> %d", (void *)cb, ncmds, len, r);
|
||||
vkb_enc_u32(&c->r, (uint32_t)r);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ hello */
|
||||
|
||||
void vkb_sv_vkbHello(vkb_srv_call *c)
|
||||
{
|
||||
uint64_t hash = vkb_dec_u64(&c->d);
|
||||
uint64_t token = vkb_dec_u64(&c->d);
|
||||
uint32_t pid = vkb_dec_u32(&c->d);
|
||||
const char *comm = vkb_dec_str(&c->d);
|
||||
if (c->d.err) {
|
||||
vkb_srv_bad_message(c);
|
||||
return;
|
||||
}
|
||||
vkb_enc_u64(&c->r, VKB_PROTOCOL_HASH);
|
||||
if (hash != VKB_PROTOCOL_HASH) {
|
||||
VKB_ERR("client %d (%s) speaks protocol %016llx, this server %016llx: refusing", pid,
|
||||
comm ? comm : "?", (unsigned long long)hash, (unsigned long long)VKB_PROTOCOL_HASH);
|
||||
vkb_enc_u32(&c->r, 0);
|
||||
return;
|
||||
}
|
||||
if (!c->conn->proc) {
|
||||
c->conn->proc = proc_join(token, (int)pid);
|
||||
if (c->conn->proc->conns == 1) VKB_INFO("process %u is %s", pid, comm ? comm : "?");
|
||||
}
|
||||
vkb_enc_u32(&c->r, 1);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ statistics */
|
||||
|
||||
/* VKBRIDGE_STATS=<seconds> on the server: time spent per command, waiting for order, idle. */
|
||||
static uint64_t st_ns[VKB_CMD_COUNT], st_n[VKB_CMD_COUNT], st_wait_ns, st_last;
|
||||
static int st_period = -1;
|
||||
|
||||
static uint64_t mono_ns(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
|
||||
}
|
||||
|
||||
static void stats_add(uint32_t cmd, uint64_t ns, uint64_t wait_ns)
|
||||
{
|
||||
if (st_period < 0) {
|
||||
const char *v = getenv("VKBRIDGE_STATS");
|
||||
st_period = v ? atoi(v) : 0;
|
||||
}
|
||||
if (!st_period) return;
|
||||
__atomic_add_fetch(&st_ns[cmd], ns, __ATOMIC_RELAXED);
|
||||
__atomic_add_fetch(&st_n[cmd], 1, __ATOMIC_RELAXED);
|
||||
__atomic_add_fetch(&st_wait_ns, wait_ns, __ATOMIC_RELAXED);
|
||||
uint64_t now = mono_ns(), last = __atomic_load_n(&st_last, __ATOMIC_RELAXED);
|
||||
if (!last) {
|
||||
__atomic_store_n(&st_last, now, __ATOMIC_RELAXED);
|
||||
return;
|
||||
}
|
||||
if (now - last < (uint64_t)st_period * 1000000000ull || !__atomic_compare_exchange_n(&st_last, &last, now, 0, __ATOMIC_RELAXED, __ATOMIC_RELAXED))
|
||||
return;
|
||||
uint64_t total = 0;
|
||||
for (uint32_t i = 0; i < VKB_CMD_COUNT; i++) total += st_ns[i];
|
||||
VKB_INFO("stats over %ds: %.1f ms in handlers, %.1f ms waiting for order", st_period, total / 1e6, st_wait_ns / 1e6);
|
||||
for (int k = 0; k < 8; k++) {
|
||||
uint64_t best = 0;
|
||||
uint32_t bi = 0;
|
||||
for (uint32_t i = 0; i < VKB_CMD_COUNT; i++)
|
||||
if (st_ns[i] > best) {
|
||||
best = st_ns[i];
|
||||
bi = i;
|
||||
}
|
||||
if (!best) break;
|
||||
VKB_INFO(" %-40s %8llu calls %9.2f ms (%.2f us each)", vkb_cmd_names[bi], (unsigned long long)st_n[bi], best / 1e6,
|
||||
best / 1e3 / (double)st_n[bi]);
|
||||
st_ns[bi] = 0;
|
||||
}
|
||||
memset(st_ns, 0, sizeof(st_ns));
|
||||
memset(st_n, 0, sizeof(st_n));
|
||||
st_wait_ns = 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ connection loop */
|
||||
|
||||
/* Reads the next request. Buffered: a burst of small asynchronous requests costs one recvmsg, not
|
||||
* two reads each. Descriptors arrive with the first bytes of their message, so they queue up in
|
||||
* order and each header takes its nfds from the front. The payload points into the buffer and
|
||||
* stays valid until the next call. */
|
||||
static int srv_recv(vkb_srv_conn *conn, vkb_msg_hdr *h, uint8_t **payload, int *fds, int *nfds)
|
||||
{
|
||||
*nfds = 0;
|
||||
if (conn->rpos == conn->rlen && conn->rcap > (16u << 20)) {
|
||||
free(conn->rb);
|
||||
conn->rb = NULL;
|
||||
conn->rcap = conn->rpos = conn->rlen = 0;
|
||||
}
|
||||
for (;;) {
|
||||
size_t avail = conn->rlen - conn->rpos;
|
||||
size_t need = sizeof(*h);
|
||||
if (avail >= sizeof(*h)) {
|
||||
memcpy(h, conn->rb + conn->rpos, sizeof(*h));
|
||||
if (h->magic != VKB_MAGIC) {
|
||||
VKB_ERR("transport: bad message magic 0x%08x", h->magic);
|
||||
return -1;
|
||||
}
|
||||
if (h->size > (1u << 30) || h->nfds > VKB_MAX_FDS) {
|
||||
VKB_ERR("transport: bad message (%u bytes, %u descriptors)", h->size, h->nfds);
|
||||
return -1;
|
||||
}
|
||||
need += h->size;
|
||||
if (avail >= need) {
|
||||
if ((int)h->nfds > conn->fdn) {
|
||||
VKB_ERR("transport: message arrived without its %u descriptors", h->nfds);
|
||||
return -1;
|
||||
}
|
||||
*nfds = (int)h->nfds;
|
||||
memcpy(fds, conn->fdq, sizeof(int) * h->nfds);
|
||||
conn->fdn -= (int)h->nfds;
|
||||
memmove(conn->fdq, conn->fdq + h->nfds, sizeof(int) * conn->fdn);
|
||||
*payload = conn->rb + conn->rpos + sizeof(*h);
|
||||
conn->rpos += need;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (conn->rpos) {
|
||||
memmove(conn->rb, conn->rb + conn->rpos, avail);
|
||||
conn->rpos = 0;
|
||||
conn->rlen = avail;
|
||||
}
|
||||
if (need < (256u << 10)) need = 256u << 10;
|
||||
if (need > conn->rcap) {
|
||||
size_t nc = conn->rcap ? conn->rcap : (256u << 10);
|
||||
while (nc < need) nc *= 2;
|
||||
uint8_t *nb = realloc(conn->rb, nc);
|
||||
if (!nb) return -1;
|
||||
conn->rb = nb;
|
||||
conn->rcap = nc;
|
||||
}
|
||||
union {
|
||||
char buf[CMSG_SPACE(sizeof(int) * VKB_MAX_FDS)];
|
||||
struct cmsghdr align;
|
||||
} cm;
|
||||
struct iovec iov = {conn->rb + conn->rlen, conn->rcap - conn->rlen};
|
||||
struct msghdr msg;
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
msg.msg_control = cm.buf;
|
||||
msg.msg_controllen = sizeof(cm.buf);
|
||||
ssize_t r;
|
||||
do {
|
||||
r = recvmsg(conn->sock, &msg, MSG_CMSG_CLOEXEC);
|
||||
} while (r < 0 && errno == EINTR);
|
||||
if (r <= 0) return -1;
|
||||
for (struct cmsghdr *cmh = CMSG_FIRSTHDR(&msg); cmh; cmh = CMSG_NXTHDR(&msg, cmh)) {
|
||||
if (cmh->cmsg_level != SOL_SOCKET || cmh->cmsg_type != SCM_RIGHTS) continue;
|
||||
int n = (int)((cmh->cmsg_len - CMSG_LEN(0)) / sizeof(int));
|
||||
for (int i = 0; i < n; i++) {
|
||||
int fd;
|
||||
memcpy(&fd, CMSG_DATA(cmh) + i * sizeof(int), sizeof(int));
|
||||
if (conn->fdn < (int)(sizeof(conn->fdq) / sizeof(conn->fdq[0]))) conn->fdq[conn->fdn++] = fd;
|
||||
else close(fd);
|
||||
}
|
||||
}
|
||||
if (msg.msg_flags & MSG_CTRUNC) VKB_WARN("transport: descriptors truncated");
|
||||
conn->rlen += (size_t)r;
|
||||
}
|
||||
}
|
||||
|
||||
static void *conn_main(void *arg)
|
||||
{
|
||||
vkb_srv_conn *conn = arg;
|
||||
vkb_srv_call c;
|
||||
vkb_enc_init(&c.r);
|
||||
int fds[VKB_MAX_FDS];
|
||||
for (;;) {
|
||||
vkb_msg_hdr h;
|
||||
int nfds = 0;
|
||||
uint8_t *payload;
|
||||
if (srv_recv(conn, &h, &payload, fds, &nfds) < 0) break;
|
||||
vkb_enc_reset(&c.r);
|
||||
vkb_dec_init(&c.d, payload, h.size, &conn->arena, fds, nfds);
|
||||
c.conn = conn;
|
||||
c.status = VKB_ST_OK;
|
||||
c.nclose = 0;
|
||||
c.in_stream = 0;
|
||||
c.table = NULL;
|
||||
c.dt = &vkb_global_dt;
|
||||
if (h.cmd >= VKB_CMD_COUNT) {
|
||||
VKB_ERR("unknown command id %u", h.cmd);
|
||||
c.status = VKB_ST_BAD_MESSAGE;
|
||||
} else if (!conn->proc && h.cmd != VKB_CMD_vkbHello) {
|
||||
VKB_ERR("%s before hello", vkb_cmd_names[h.cmd]);
|
||||
c.status = VKB_ST_BAD_MESSAGE;
|
||||
} else {
|
||||
if (h.table) {
|
||||
c.table = vkb_table_get(h.table);
|
||||
if (!c.table || c.table->proc != conn->proc) {
|
||||
VKB_ERR("%s: unknown dispatch table %u", vkb_cmd_names[h.cmd], h.table);
|
||||
c.status = VKB_ST_BAD_TABLE;
|
||||
} else {
|
||||
c.dt = &c.table->dt;
|
||||
}
|
||||
}
|
||||
if (c.status == VKB_ST_OK) {
|
||||
uint64_t t0 = st_period ? mono_ns() : 0;
|
||||
order_wait(conn->proc, h.barrier);
|
||||
uint64_t t1 = st_period ? mono_ns() : 0;
|
||||
vkb_srv_set_current_table(c.table);
|
||||
if (vkb_verbose) VKB_DBG("-> %s", vkb_cmd_names[h.cmd]);
|
||||
vkb_srv_current_cmd = vkb_cmd_names[h.cmd];
|
||||
vkb_srv_handlers[h.cmd](&c);
|
||||
vkb_srv_current_cmd = NULL;
|
||||
vkb_srv_set_current_table(NULL);
|
||||
if (st_period) stats_add(h.cmd, mono_ns() - t1, t1 - t0);
|
||||
}
|
||||
}
|
||||
vkb_dec_close_untaken(&c.d);
|
||||
order_done(conn->proc, h.seq);
|
||||
if (!(h.flags & VKB_F_NOREPLY)) {
|
||||
vkb_msg_hdr rh = {VKB_MAGIC, c.status == VKB_ST_OK ? (uint32_t)c.r.len : 0, c.status, 0,
|
||||
c.status == VKB_ST_OK ? (uint32_t)c.r.nfds : 0, 0, 0, 0};
|
||||
if (c.r.oom) {
|
||||
rh.cmd = VKB_ST_BAD_MESSAGE;
|
||||
rh.size = 0;
|
||||
rh.nfds = 0;
|
||||
}
|
||||
if (vkb_send_msg(conn->sock, &rh, c.r.buf, c.r.fds, (int)rh.nfds) < 0) {
|
||||
for (int i = 0; i < c.nclose; i++) close(c.close_after[i]);
|
||||
break;
|
||||
}
|
||||
} else if (c.status != VKB_ST_OK) {
|
||||
VKB_ERR("asynchronous %s failed (status %u)", vkb_cmd_names[h.cmd], c.status);
|
||||
} else if (vkb_cmd_result[h.cmd] && c.r.len >= sizeof(int32_t)) {
|
||||
/* The client already told the app VK_SUCCESS; a failure can only be reported here. */
|
||||
int32_t vr;
|
||||
memcpy(&vr, c.r.buf, sizeof(vr));
|
||||
if (vr < 0) VKB_ERR("asynchronous %s returned %d", vkb_cmd_names[h.cmd], vr);
|
||||
}
|
||||
for (int i = 0; i < c.nclose; i++) close(c.close_after[i]);
|
||||
vkb_arena_reset(&conn->arena);
|
||||
}
|
||||
close(conn->sock);
|
||||
vkb_enc_free(&c.r);
|
||||
free(conn->rb);
|
||||
for (int i = 0; i < conn->fdn; i++) close(conn->fdq[i]);
|
||||
vkb_arena_free(&conn->arena);
|
||||
if (conn->proc) proc_leave(conn->proc);
|
||||
free(conn);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void vkb_srv_serve(int lfd)
|
||||
{
|
||||
stats_add(0, 0, 0);
|
||||
for (;;) {
|
||||
int s = accept4(lfd, NULL, NULL, SOCK_CLOEXEC);
|
||||
if (s < 0) {
|
||||
if (errno == EINTR || errno == ECONNABORTED) continue;
|
||||
VKB_ERR("accept: %s", strerror(errno));
|
||||
continue;
|
||||
}
|
||||
vkb_srv_conn *conn = calloc(1, sizeof(*conn));
|
||||
conn->sock = s;
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
/* Driver calls (pipeline compiles) can use a lot of stack. */
|
||||
pthread_attr_setstacksize(&attr, 4u << 20);
|
||||
if (pthread_create(&conn->thread, &attr, conn_main, conn) != 0) {
|
||||
VKB_ERR("cannot start a connection thread");
|
||||
close(s);
|
||||
free(conn);
|
||||
}
|
||||
pthread_attr_destroy(&attr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Descriptor sets named by the client. Where the client keeps a pool's books it knows an
|
||||
* allocation will succeed, so it names the sets itself (VKB_DS_TAG | n) and sends the allocation
|
||||
* without waiting (vkbAllocDescSets) - DXVK allocates a set or two per draw, and a round trip
|
||||
* each was most of the bridge's remaining cost per frame. Here those names map to the driver's
|
||||
* sets: wrappers in the device's dispatch table translate them wherever a set goes back to the
|
||||
* driver (updates, template updates, binds, frees), and a pool's reset or destruction forgets its
|
||||
* sets. Sets the server named itself (the synchronous path) pass through untouched.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "vkb_server.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define VKB_DS_TAG (1ull << 63)
|
||||
#define DS_BUCKETS 8192
|
||||
#define DS_LOCAL 64
|
||||
|
||||
typedef struct ds_ent {
|
||||
uint64_t id;
|
||||
VkDescriptorSet set;
|
||||
struct ds_ent *next; /* bucket chain */
|
||||
struct ds_ent *pool_next; /* the pool's sets */
|
||||
} ds_ent;
|
||||
|
||||
typedef struct ds_pool {
|
||||
VkDescriptorPool pool;
|
||||
ds_ent *sets;
|
||||
struct ds_pool *next;
|
||||
} ds_pool;
|
||||
|
||||
typedef struct vkb_dsmap {
|
||||
pthread_mutex_t lock;
|
||||
ds_ent *buckets[DS_BUCKETS];
|
||||
ds_pool *pools;
|
||||
int warned;
|
||||
PFN_vkAllocateDescriptorSets allocate;
|
||||
PFN_vkUpdateDescriptorSets update;
|
||||
PFN_vkUpdateDescriptorSetWithTemplate update_template;
|
||||
PFN_vkCmdBindDescriptorSets bind;
|
||||
PFN_vkCmdBindDescriptorSets2 bind2;
|
||||
PFN_vkFreeDescriptorSets free_sets;
|
||||
PFN_vkResetDescriptorPool reset_pool;
|
||||
PFN_vkDestroyDescriptorPool destroy_pool;
|
||||
} vkb_dsmap;
|
||||
|
||||
static unsigned bucket(uint64_t id)
|
||||
{
|
||||
id *= 0x9e3779b97f4a7c15ull;
|
||||
return (unsigned)(id >> 51) & (DS_BUCKETS - 1);
|
||||
}
|
||||
|
||||
static vkb_dsmap *cur(void)
|
||||
{
|
||||
vkb_srv_table *t = vkb_srv_current_table();
|
||||
return t ? t->dsmap : NULL;
|
||||
}
|
||||
|
||||
static int tagged(VkDescriptorSet s)
|
||||
{
|
||||
return ((uint64_t)s & VKB_DS_TAG) != 0;
|
||||
}
|
||||
|
||||
/* The driver's set for a client name; VK_NULL_HANDLE when its allocation failed. */
|
||||
static VkDescriptorSet lookup(vkb_dsmap *m, VkDescriptorSet s)
|
||||
{
|
||||
if (!tagged(s)) return s;
|
||||
uint64_t id = (uint64_t)s;
|
||||
pthread_mutex_lock(&m->lock);
|
||||
ds_ent *e = m->buckets[bucket(id)];
|
||||
while (e && e->id != id) e = e->next;
|
||||
VkDescriptorSet r = e ? e->set : VK_NULL_HANDLE;
|
||||
pthread_mutex_unlock(&m->lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
static void missing(vkb_dsmap *m, const char *where)
|
||||
{
|
||||
if (__atomic_fetch_add(&m->warned, 1, __ATOMIC_RELAXED) < 20)
|
||||
VKB_ERR("%s: a descriptor set the client allocated ahead does not exist (its allocation failed); skipped", where);
|
||||
}
|
||||
|
||||
static ds_pool *pool_get(vkb_dsmap *m, VkDescriptorPool pool, int create)
|
||||
{
|
||||
for (ds_pool *p = m->pools; p; p = p->next)
|
||||
if (p->pool == pool) return p;
|
||||
if (!create) return NULL;
|
||||
ds_pool *p = calloc(1, sizeof(*p));
|
||||
if (!p) return NULL;
|
||||
p->pool = pool;
|
||||
p->next = m->pools;
|
||||
m->pools = p;
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Forgets the pool's sets (a reset or destruction freed them); the record goes with destroy. */
|
||||
static void pool_forget(vkb_dsmap *m, VkDescriptorPool pool, int destroy)
|
||||
{
|
||||
pthread_mutex_lock(&m->lock);
|
||||
for (ds_pool **pp = &m->pools; *pp; pp = &(*pp)->next) {
|
||||
ds_pool *p = *pp;
|
||||
if (p->pool != pool) continue;
|
||||
while (p->sets) {
|
||||
ds_ent *e = p->sets;
|
||||
p->sets = e->pool_next;
|
||||
ds_ent **bp = &m->buckets[bucket(e->id)];
|
||||
while (*bp && *bp != e) bp = &(*bp)->next;
|
||||
if (*bp) *bp = e->next;
|
||||
free(e);
|
||||
}
|
||||
if (destroy) {
|
||||
*pp = p->next;
|
||||
free(p);
|
||||
}
|
||||
break;
|
||||
}
|
||||
pthread_mutex_unlock(&m->lock);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ the allocation request */
|
||||
|
||||
/* vkbAllocDescSets: device, pool, count, then per set its layout and the client's name. */
|
||||
void vkb_sv_vkbAllocDescSets(vkb_srv_call *c)
|
||||
{
|
||||
VkDevice dev = (VkDevice)(uintptr_t)vkb_dec_u64(&c->d);
|
||||
VkDescriptorPool pool;
|
||||
vkb_dec_bytes(&c->d, &pool, sizeof(pool));
|
||||
uint32_t n = vkb_dec_u32(&c->d);
|
||||
if (c->d.err || !c->table || !c->table->dsmap || n == 0 || n > 4096) {
|
||||
vkb_srv_bad_message(c);
|
||||
return;
|
||||
}
|
||||
vkb_dsmap *m = c->table->dsmap;
|
||||
VkDescriptorSetLayout layouts_local[DS_LOCAL], *layouts = n <= DS_LOCAL ? layouts_local : malloc(n * sizeof(*layouts));
|
||||
uint64_t ids_local[DS_LOCAL], *ids = n <= DS_LOCAL ? ids_local : malloc(n * sizeof(*ids));
|
||||
VkDescriptorSet sets_local[DS_LOCAL], *sets = n <= DS_LOCAL ? sets_local : malloc(n * sizeof(*sets));
|
||||
if (!layouts || !ids || !sets) goto out;
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
vkb_dec_bytes(&c->d, &layouts[i], sizeof(layouts[i]));
|
||||
ids[i] = vkb_dec_u64(&c->d);
|
||||
}
|
||||
if (c->d.err) {
|
||||
vkb_srv_bad_message(c);
|
||||
goto out;
|
||||
}
|
||||
VkDescriptorSetAllocateInfo ai = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, NULL, pool, n, layouts};
|
||||
VkResult r = m->allocate(dev, &ai, sets);
|
||||
if (r != VK_SUCCESS) {
|
||||
VKB_ERR("allocating %u descriptor sets the client named ahead failed: %d (the client's pool books disagree "
|
||||
"with the driver)", n, r);
|
||||
goto out;
|
||||
}
|
||||
pthread_mutex_lock(&m->lock);
|
||||
ds_pool *p = pool_get(m, pool, 1);
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
ds_ent *e = malloc(sizeof(*e));
|
||||
if (!e || !p) {
|
||||
free(e);
|
||||
break;
|
||||
}
|
||||
e->id = ids[i];
|
||||
e->set = sets[i];
|
||||
unsigned b = bucket(e->id);
|
||||
e->next = m->buckets[b];
|
||||
m->buckets[b] = e;
|
||||
e->pool_next = p->sets;
|
||||
p->sets = e;
|
||||
}
|
||||
pthread_mutex_unlock(&m->lock);
|
||||
out:
|
||||
if (layouts != layouts_local) free(layouts);
|
||||
if (ids != ids_local) free(ids);
|
||||
if (sets != sets_local) free(sets);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ wrappers */
|
||||
|
||||
static void VKAPI_CALL ds_UpdateDescriptorSets(VkDevice device, uint32_t nw, const VkWriteDescriptorSet *w, uint32_t nc,
|
||||
const VkCopyDescriptorSet *cp)
|
||||
{
|
||||
vkb_dsmap *m = cur();
|
||||
int any = 0;
|
||||
for (uint32_t i = 0; i < nw && !any; i++) any = tagged(w[i].dstSet);
|
||||
for (uint32_t i = 0; i < nc && !any; i++) any = tagged(cp[i].srcSet) || tagged(cp[i].dstSet);
|
||||
if (!any) {
|
||||
m->update(device, nw, w, nc, cp);
|
||||
return;
|
||||
}
|
||||
VkWriteDescriptorSet w_local[DS_LOCAL], *w2 = nw <= DS_LOCAL ? w_local : malloc(nw * sizeof(*w2));
|
||||
VkCopyDescriptorSet c_local[DS_LOCAL], *c2 = nc <= DS_LOCAL ? c_local : malloc(nc * sizeof(*c2));
|
||||
if ((nw && !w2) || (nc && !c2)) goto out;
|
||||
uint32_t kw = 0, kc = 0;
|
||||
for (uint32_t i = 0; i < nw; i++) {
|
||||
w2[kw] = w[i];
|
||||
w2[kw].dstSet = lookup(m, w[i].dstSet);
|
||||
if (w2[kw].dstSet) kw++;
|
||||
else missing(m, "vkUpdateDescriptorSets");
|
||||
}
|
||||
for (uint32_t i = 0; i < nc; i++) {
|
||||
c2[kc] = cp[i];
|
||||
c2[kc].srcSet = lookup(m, cp[i].srcSet);
|
||||
c2[kc].dstSet = lookup(m, cp[i].dstSet);
|
||||
if (c2[kc].srcSet && c2[kc].dstSet) kc++;
|
||||
else missing(m, "vkUpdateDescriptorSets (copy)");
|
||||
}
|
||||
if (kw || kc) m->update(device, kw, w2, kc, c2);
|
||||
out:
|
||||
if (w2 != w_local) free(w2);
|
||||
if (c2 != c_local) free(c2);
|
||||
}
|
||||
|
||||
static void VKAPI_CALL ds_UpdateDescriptorSetWithTemplate(VkDevice device, VkDescriptorSet set, VkDescriptorUpdateTemplate tmpl,
|
||||
const void *data)
|
||||
{
|
||||
vkb_dsmap *m = cur();
|
||||
VkDescriptorSet s = lookup(m, set);
|
||||
if (!s) {
|
||||
missing(m, "vkUpdateDescriptorSetWithTemplate");
|
||||
return;
|
||||
}
|
||||
m->update_template(device, s, tmpl, data);
|
||||
}
|
||||
|
||||
/* Translates n sets into out; 0 when one of them does not exist. */
|
||||
static int translate(vkb_dsmap *m, uint32_t n, const VkDescriptorSet *in, VkDescriptorSet *out)
|
||||
{
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
/* VK_NULL_HANDLE is allowed in binds (graphicsPipelineLibrary) and stays so. */
|
||||
out[i] = in[i] ? lookup(m, in[i]) : VK_NULL_HANDLE;
|
||||
if (in[i] && !out[i]) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void VKAPI_CALL ds_CmdBindDescriptorSets(VkCommandBuffer cb, VkPipelineBindPoint bp, VkPipelineLayout layout, uint32_t first,
|
||||
uint32_t n, const VkDescriptorSet *sets, uint32_t ndyn, const uint32_t *dyn)
|
||||
{
|
||||
vkb_dsmap *m = cur();
|
||||
VkDescriptorSet local[DS_LOCAL], *s = n <= DS_LOCAL ? local : malloc(n * sizeof(*s));
|
||||
if (!s) return;
|
||||
if (translate(m, n, sets, s)) m->bind(cb, bp, layout, first, n, s, ndyn, dyn);
|
||||
else missing(m, "vkCmdBindDescriptorSets");
|
||||
if (s != local) free(s);
|
||||
}
|
||||
|
||||
static void VKAPI_CALL ds_CmdBindDescriptorSets2(VkCommandBuffer cb, const VkBindDescriptorSetsInfo *info)
|
||||
{
|
||||
vkb_dsmap *m = cur();
|
||||
uint32_t n = info->descriptorSetCount;
|
||||
VkDescriptorSet local[DS_LOCAL], *s = n <= DS_LOCAL ? local : malloc(n * sizeof(*s));
|
||||
if (!s) return;
|
||||
VkBindDescriptorSetsInfo i2 = *info;
|
||||
i2.pDescriptorSets = s;
|
||||
if (translate(m, n, info->pDescriptorSets, s)) m->bind2(cb, &i2);
|
||||
else missing(m, "vkCmdBindDescriptorSets2");
|
||||
if (s != local) free(s);
|
||||
}
|
||||
|
||||
static VkResult VKAPI_CALL ds_FreeDescriptorSets(VkDevice device, VkDescriptorPool pool, uint32_t n, const VkDescriptorSet *sets)
|
||||
{
|
||||
vkb_dsmap *m = cur();
|
||||
VkDescriptorSet local[DS_LOCAL], *s = n <= DS_LOCAL ? local : malloc(n * sizeof(*s));
|
||||
if (!s) return VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
/* Named sets come only from pools without FREE_DESCRIPTOR_SET (the client keeps no books on
|
||||
* those), so this is an app error at worst: what does not exist is left out. */
|
||||
uint32_t k = 0;
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
VkDescriptorSet t = sets[i] ? lookup(m, sets[i]) : VK_NULL_HANDLE;
|
||||
if (t || !sets[i]) s[k++] = t;
|
||||
}
|
||||
VkResult r = k ? m->free_sets(device, pool, k, s) : VK_SUCCESS;
|
||||
if (s != local) free(s);
|
||||
return r;
|
||||
}
|
||||
|
||||
static VkResult VKAPI_CALL ds_ResetDescriptorPool(VkDevice device, VkDescriptorPool pool, VkDescriptorPoolResetFlags flags)
|
||||
{
|
||||
vkb_dsmap *m = cur();
|
||||
pool_forget(m, pool, 0);
|
||||
return m->reset_pool(device, pool, flags);
|
||||
}
|
||||
|
||||
static void VKAPI_CALL ds_DestroyDescriptorPool(VkDevice device, VkDescriptorPool pool, const VkAllocationCallbacks *a)
|
||||
{
|
||||
vkb_dsmap *m = cur();
|
||||
if (pool) pool_forget(m, pool, 1);
|
||||
m->destroy_pool(device, pool, a);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ device hooks */
|
||||
|
||||
void vkb_dsmap_device_init(vkb_srv_table *t)
|
||||
{
|
||||
vkb_dispatch *dt = &t->dt;
|
||||
if (!dt->vkAllocateDescriptorSets || !dt->vkUpdateDescriptorSets || !dt->vkCmdBindDescriptorSets ||
|
||||
!dt->vkResetDescriptorPool || !dt->vkDestroyDescriptorPool || !dt->vkFreeDescriptorSets)
|
||||
return;
|
||||
vkb_dsmap *m = calloc(1, sizeof(*m));
|
||||
if (!m) return;
|
||||
pthread_mutex_init(&m->lock, NULL);
|
||||
m->allocate = dt->vkAllocateDescriptorSets;
|
||||
m->update = dt->vkUpdateDescriptorSets;
|
||||
m->update_template = dt->vkUpdateDescriptorSetWithTemplate;
|
||||
m->bind = dt->vkCmdBindDescriptorSets;
|
||||
m->bind2 = dt->vkCmdBindDescriptorSets2;
|
||||
m->free_sets = dt->vkFreeDescriptorSets;
|
||||
m->reset_pool = dt->vkResetDescriptorPool;
|
||||
m->destroy_pool = dt->vkDestroyDescriptorPool;
|
||||
dt->vkUpdateDescriptorSets = ds_UpdateDescriptorSets;
|
||||
if (m->update_template) dt->vkUpdateDescriptorSetWithTemplate = ds_UpdateDescriptorSetWithTemplate;
|
||||
dt->vkCmdBindDescriptorSets = ds_CmdBindDescriptorSets;
|
||||
if (m->bind2) dt->vkCmdBindDescriptorSets2 = ds_CmdBindDescriptorSets2;
|
||||
dt->vkFreeDescriptorSets = ds_FreeDescriptorSets;
|
||||
dt->vkResetDescriptorPool = ds_ResetDescriptorPool;
|
||||
dt->vkDestroyDescriptorPool = ds_DestroyDescriptorPool;
|
||||
t->dsmap = m;
|
||||
}
|
||||
|
||||
void vkb_dsmap_device_destroy(vkb_srv_table *t)
|
||||
{
|
||||
vkb_dsmap *m = t->dsmap;
|
||||
if (!m) return;
|
||||
vkb_dispatch *dt = &t->dt;
|
||||
dt->vkUpdateDescriptorSets = m->update;
|
||||
if (m->update_template) dt->vkUpdateDescriptorSetWithTemplate = m->update_template;
|
||||
dt->vkCmdBindDescriptorSets = m->bind;
|
||||
if (m->bind2) dt->vkCmdBindDescriptorSets2 = m->bind2;
|
||||
dt->vkFreeDescriptorSets = m->free_sets;
|
||||
dt->vkResetDescriptorPool = m->reset_pool;
|
||||
dt->vkDestroyDescriptorPool = m->destroy_pool;
|
||||
while (m->pools) pool_forget(m, m->pools->pool, 1);
|
||||
pthread_mutex_destroy(&m->lock);
|
||||
free(m);
|
||||
t->dsmap = NULL;
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Feature emulation: entry point. What the GPU lacks and the bridge can make up for is decided
|
||||
* here per physical device; the client reports those features as present, the server strips
|
||||
* them from vkCreateDevice and installs wrappers in the device's dispatch table.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "vkb_server.h"
|
||||
#include "server_emu.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static int token(const char *list, const char *t)
|
||||
{
|
||||
size_t n = strlen(t);
|
||||
for (const char *p = list; p && (p = strstr(p, t)); p += n)
|
||||
if ((p == list || p[-1] == ',') && (p[n] == ',' || p[n] == 0)) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int env_off(const char *name)
|
||||
{
|
||||
const char *v = getenv(name);
|
||||
return v && !strcmp(v, "0");
|
||||
}
|
||||
|
||||
uint32_t vkb_emu_detect(const vkb_dispatch *idt, VkPhysicalDevice pd, uint32_t *missing)
|
||||
{
|
||||
VkPhysicalDeviceFeatures f;
|
||||
idt->vkGetPhysicalDeviceFeatures(pd, &f);
|
||||
uint32_t n = 0;
|
||||
idt->vkEnumerateDeviceExtensionProperties(pd, NULL, &n, NULL);
|
||||
VkExtensionProperties *e = calloc(n ? n : 1, sizeof(*e));
|
||||
idt->vkEnumerateDeviceExtensionProperties(pd, NULL, &n, e);
|
||||
int divisor = 0, depth_clip = 0, maint5 = 0, robust2 = 0;
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
if (!strcmp(e[i].extensionName, "VK_EXT_vertex_attribute_divisor") ||
|
||||
!strcmp(e[i].extensionName, "VK_KHR_vertex_attribute_divisor"))
|
||||
divisor = 1;
|
||||
if (!strcmp(e[i].extensionName, "VK_EXT_depth_clip_enable")) depth_clip = 1;
|
||||
if (!strcmp(e[i].extensionName, "VK_KHR_maintenance5")) maint5 = 1;
|
||||
if (!strcmp(e[i].extensionName, "VK_EXT_robustness2") || !strcmp(e[i].extensionName, "VK_KHR_robustness2")) robust2 = 1;
|
||||
}
|
||||
free(e);
|
||||
/* VKBRIDGE_FAKE_MISSING=bc,divisor,clip,cull,depthclip pretends the GPU lacks them (host tests). */
|
||||
const char *fake = getenv("VKBRIDGE_FAKE_MISSING");
|
||||
if (fake) {
|
||||
if (token(fake, "bc")) f.textureCompressionBC = VK_FALSE;
|
||||
if (token(fake, "divisor")) divisor = 0;
|
||||
if (token(fake, "clip")) f.shaderClipDistance = VK_FALSE;
|
||||
if (token(fake, "cull")) f.shaderCullDistance = VK_FALSE;
|
||||
if (token(fake, "depthclip")) depth_clip = 0;
|
||||
if (token(fake, "maint5")) maint5 = 0;
|
||||
if (token(fake, "robustness2")) robust2 = 0;
|
||||
}
|
||||
uint32_t emu = 0;
|
||||
VkPhysicalDeviceProperties props;
|
||||
idt->vkGetPhysicalDeviceProperties(pd, &props);
|
||||
/* Vulkan 1.4 has maintenance5 in core. */
|
||||
if (!maint5 && (props.apiVersion < VK_API_VERSION_1_4 || (fake && token(fake, "maint5"))) && !env_off("VKBRIDGE_EMU_MAINT5"))
|
||||
emu |= VKB_EMU_MAINT5;
|
||||
if (!f.textureCompressionBC && vkb_emu_bcn_supported(idt, pd) && !env_off("VKBRIDGE_EMU_BCN")) emu |= VKB_EMU_BCN;
|
||||
if (!divisor && !env_off("VKBRIDGE_EMU_DIVISOR")) emu |= VKB_EMU_DIVISOR;
|
||||
if (!f.shaderClipDistance && !env_off("VKBRIDGE_EMU_CLIP")) emu |= VKB_EMU_CLIP_DISTANCE;
|
||||
if (!f.shaderCullDistance && !env_off("VKBRIDGE_EMU_CULL")) emu |= VKB_EMU_CULL_DISTANCE;
|
||||
if (!depth_clip && f.depthClamp && !env_off("VKBRIDGE_EMU_DEPTHCLIP")) emu |= VKB_EMU_DEPTH_CLIP;
|
||||
/* DXVK will not start without robustness2. Mali r44 lacks it; GameNative's wrapper reports it
|
||||
* (robustBufferAccess2, robustImageAccess2, nullDescriptor) without enforcing it, and DXVK
|
||||
* games run - out-of-bounds reads and null descriptors are then up to the driver. */
|
||||
if (!robust2 && f.robustBufferAccess && !env_off("VKBRIDGE_EMU_ROBUSTNESS2")) emu |= VKB_EMU_ROBUSTNESS2;
|
||||
/* PointSize in geometry/tessellation stages: stripped when the feature is missing. */
|
||||
if (!f.shaderTessellationAndGeometryPointSize && (f.geometryShader || f.tessellationShader)) emu |= VKB_EMU_POINT_SIZE;
|
||||
uint32_t miss = 0;
|
||||
if (!f.textureCompressionBC) miss |= VKB_EMU_BCN;
|
||||
if (!divisor) miss |= VKB_EMU_DIVISOR;
|
||||
if (!f.shaderClipDistance) miss |= VKB_EMU_CLIP_DISTANCE;
|
||||
if (!f.shaderCullDistance) miss |= VKB_EMU_CULL_DISTANCE;
|
||||
if (!depth_clip) miss |= VKB_EMU_DEPTH_CLIP;
|
||||
if (emu & VKB_EMU_MAINT5) miss |= VKB_EMU_MAINT5;
|
||||
if (!robust2) miss |= VKB_EMU_ROBUSTNESS2;
|
||||
*missing = miss;
|
||||
return emu & VKB_EMU_IMPLEMENTED;
|
||||
}
|
||||
|
||||
/* Remove a structure from a pNext chain (the chain lives in the request's arena). */
|
||||
static void chain_remove(VkDeviceCreateInfo *ci, VkStructureType t)
|
||||
{
|
||||
VkBaseOutStructure *prev = (VkBaseOutStructure *)ci;
|
||||
while (prev->pNext) {
|
||||
if (prev->pNext->sType == t) prev->pNext = prev->pNext->pNext;
|
||||
else prev = prev->pNext;
|
||||
}
|
||||
}
|
||||
|
||||
static VkPhysicalDeviceFeatures *find_features(VkDeviceCreateInfo *ci)
|
||||
{
|
||||
if (ci->pEnabledFeatures) return (VkPhysicalDeviceFeatures *)ci->pEnabledFeatures;
|
||||
for (VkBaseOutStructure *b = (VkBaseOutStructure *)ci->pNext; b; b = b->pNext)
|
||||
if (b->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2) return &((VkPhysicalDeviceFeatures2 *)b)->features;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void vkb_emu_device_create_info(vkb_srv_table *inst, VkPhysicalDevice pd, uint32_t emu, VkDeviceCreateInfo *ci, vkb_arena *a)
|
||||
{
|
||||
VkPhysicalDeviceFeatures real;
|
||||
inst->real.vkGetPhysicalDeviceFeatures(pd, &real);
|
||||
VkPhysicalDeviceFeatures *f = find_features(ci);
|
||||
if ((emu & (VKB_EMU_BCN | VKB_EMU_DEPTH_CLIP)) && !f) {
|
||||
/* The BC decoder writes R8/RG8 storage images: it needs this feature on, so the request
|
||||
* gets a features structure if it had none. */
|
||||
f = vkb_arena_alloc(a, sizeof(*f));
|
||||
ci->pEnabledFeatures = f;
|
||||
}
|
||||
if (f) {
|
||||
/* pEnabledFeatures came from the request: it is ours to change. Anything the client asked
|
||||
* for that the GPU does not have is either emulated or would fail the call. */
|
||||
VkBool32 *want = (VkBool32 *)f;
|
||||
const VkBool32 *have = (const VkBool32 *)ℜ
|
||||
for (size_t i = 0; i < sizeof(*f) / sizeof(VkBool32); i++) {
|
||||
if (want[i] && !have[i]) want[i] = VK_FALSE;
|
||||
}
|
||||
if (emu & VKB_EMU_BCN) f->shaderStorageImageExtendedFormats = VK_TRUE;
|
||||
if (emu & VKB_EMU_DEPTH_CLIP) f->depthClamp = VK_TRUE;
|
||||
}
|
||||
if (emu & VKB_EMU_DIVISOR) chain_remove(ci, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_FEATURES_EXT);
|
||||
if (emu & VKB_EMU_DEPTH_CLIP) chain_remove(ci, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_ENABLE_FEATURES_EXT);
|
||||
if (emu & VKB_EMU_MAINT5) chain_remove(ci, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_FEATURES);
|
||||
if (emu & VKB_EMU_ROBUSTNESS2) {
|
||||
chain_remove(ci, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT);
|
||||
/* What robustness the GPU has stays on; robustBufferAccess2 asks for at least that. */
|
||||
VkPhysicalDeviceFeatures *rf = find_features(ci);
|
||||
if (rf) rf->robustBufferAccess = VK_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
void vkb_emu_device_init(vkb_srv_table *dev, uint32_t emu)
|
||||
{
|
||||
if (!emu) return;
|
||||
vkb_emu_device *e = calloc(1, sizeof(*e));
|
||||
e->flags = emu;
|
||||
e->dev = dev;
|
||||
pthread_mutex_init(&e->lock, NULL);
|
||||
dev->emu = e;
|
||||
vkb_emu_install(dev);
|
||||
}
|
||||
|
||||
void vkb_emu_device_destroy(vkb_srv_table *dev)
|
||||
{
|
||||
if (!dev->emu) return;
|
||||
vkb_emu_uninstall(dev);
|
||||
pthread_mutex_destroy(&dev->emu->lock);
|
||||
free(dev->emu);
|
||||
dev->emu = NULL;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef VKB_SERVER_EMU_H
|
||||
#define VKB_SERVER_EMU_H
|
||||
|
||||
#include "vkb_server.h"
|
||||
|
||||
/* Emulations that are implemented (the rest are never offered to the client). */
|
||||
#define VKB_EMU_IMPLEMENTED (VKB_EMU_MAINT5 | VKB_EMU_CLIP_DISTANCE | VKB_EMU_CULL_DISTANCE | VKB_EMU_POINT_SIZE | VKB_EMU_BCN | \
|
||||
VKB_EMU_DIVISOR | VKB_EMU_DEPTH_CLIP | VKB_EMU_ROBUSTNESS2)
|
||||
|
||||
#define VKB_EMU_BUCKETS 1024
|
||||
|
||||
typedef struct vkb_emu_image {
|
||||
VkImage image;
|
||||
VkImageType type;
|
||||
VkFormat format; /* what the driver holds */
|
||||
VkFormat client_format; /* what the app asked for (differs for emulated formats) */
|
||||
VkExtent3D extent;
|
||||
uint32_t mips, layers;
|
||||
void *emu; /* per-emulation state (BCn: decode resources) */
|
||||
struct vkb_emu_image *next;
|
||||
} vkb_emu_image;
|
||||
|
||||
typedef struct vkb_emu_buffer {
|
||||
VkBuffer buffer;
|
||||
VkDeviceSize size;
|
||||
struct vkb_emu_buffer *next;
|
||||
} vkb_emu_buffer;
|
||||
|
||||
typedef struct vkb_emu_device {
|
||||
uint32_t flags;
|
||||
vkb_srv_table *dev;
|
||||
pthread_mutex_t lock;
|
||||
vkb_emu_image *images[VKB_EMU_BUCKETS];
|
||||
vkb_emu_buffer *buffers[VKB_EMU_BUCKETS];
|
||||
void *bcn; /* server_emu_bcn.c */
|
||||
void *cmdstate; /* server_emu_cmdstate.c */
|
||||
void *divisor; /* server_emu_divisor.c */
|
||||
void *shaders; /* server_emu_shader.c */
|
||||
} vkb_emu_device;
|
||||
|
||||
/* command buffer state (server_emu_cmdstate.c) */
|
||||
void vkb_emu_cmdstate_install(vkb_srv_table *dev);
|
||||
void vkb_emu_cmdstate_uninstall(vkb_srv_table *dev);
|
||||
void vkb_emu_restore_compute(vkb_emu_device *e, VkCommandBuffer cb);
|
||||
void *vkb_emu_cb_gfx(vkb_emu_device *e, VkCommandBuffer cb, size_t size);
|
||||
VkDescriptorSet vkb_emu_cb_scratch_set(vkb_emu_device *e, VkCommandBuffer cb, VkDescriptorSetLayout dsl);
|
||||
/* vertex attribute divisor (server_emu_divisor.c) */
|
||||
typedef struct div_info div_info;
|
||||
size_t vkb_emu_divisor_info_size(void);
|
||||
void vkb_emu_divisor_pipeline(vkb_emu_device *e, VkDevice device, VkGraphicsPipelineCreateInfo *ci, div_info *out);
|
||||
void vkb_emu_divisor_created(vkb_emu_device *e, VkDevice device, VkPipelineCache cache, const VkGraphicsPipelineCreateInfo *ci,
|
||||
VkPipeline p, div_info *info);
|
||||
void vkb_emu_divisor_install(vkb_srv_table *dev);
|
||||
void vkb_emu_divisor_uninstall(vkb_srv_table *dev);
|
||||
void vkb_emu_gfx_bind_pipeline(vkb_emu_device *e, VkCommandBuffer cb, VkPipeline p);
|
||||
void vkb_emu_gfx_reset(void *gfx);
|
||||
void vkb_emu_gfx_free(void *gfx);
|
||||
/* BCn (server_emu_bcn.c) */
|
||||
void vkb_emu_bcn_install(vkb_srv_table *dev);
|
||||
void vkb_emu_bcn_uninstall(vkb_srv_table *dev);
|
||||
VkFormat vkb_emu_bcn_view_format(vkb_emu_device *e, VkImage image, VkFormat requested);
|
||||
void vkb_emu_bcn_buffer_usage(vkb_emu_device *e, VkBufferCreateInfo *ci);
|
||||
int vkb_emu_bcn_blocks_readback(vkb_emu_device *e, VkImage img);
|
||||
|
||||
/* layer (server_emu_layer.c) */
|
||||
void vkb_emu_depth_clip_pipeline(vkb_emu_device *e, VkGraphicsPipelineCreateInfo *ci);
|
||||
void vkb_emu_depth_clip_install(vkb_srv_table *dev);
|
||||
vkb_emu_device *vkb_emu_cur(void);
|
||||
const vkb_dispatch *vkb_emu_real(void);
|
||||
void *vkb_emu_chain_take(const void *head, VkStructureType t);
|
||||
vkb_emu_image *vkb_emu_find_image(vkb_emu_device *e, VkImage img);
|
||||
void vkb_emu_track_image(vkb_emu_device *e, VkImage img, const VkImageCreateInfo *ci, VkFormat client_format);
|
||||
|
||||
/* feature hooks (server_emu_features.c and friends) */
|
||||
int vkb_emu_bcn_supported(const vkb_dispatch *idt, VkPhysicalDevice pd);
|
||||
void vkb_emu_install(vkb_srv_table *dev);
|
||||
void vkb_emu_uninstall(vkb_srv_table *dev);
|
||||
void vkb_emu_install_features(vkb_srv_table *dev);
|
||||
void vkb_emu_uninstall_features(vkb_srv_table *dev);
|
||||
void vkb_emu_image_create_info(vkb_emu_device *e, VkImageCreateInfo *ci);
|
||||
void vkb_emu_image_release(vkb_emu_device *e, vkb_emu_image *img);
|
||||
/* Rewrites a graphics pipeline's shaders; temporary modules are appended to mods. */
|
||||
void vkb_emu_shader_pipeline(vkb_emu_device *e, VkDevice device, VkGraphicsPipelineCreateInfo *ci, VkShaderModule *mods,
|
||||
uint32_t *nmods, uint32_t cap);
|
||||
void vkb_emu_shader_install(vkb_srv_table *dev);
|
||||
VkShaderModule vkb_emu_shader_zero_base_instance(vkb_emu_device *e, VkDevice device, const VkPipelineShaderStageCreateInfo *vs);
|
||||
void vkb_emu_shader_uninstall(vkb_srv_table *dev);
|
||||
/* 1 = handled (the emulation recorded its own commands instead). */
|
||||
int vkb_emu_copy_image(vkb_emu_device *e, VkCommandBuffer cb, VkImage src, VkImageLayout sl, VkImage dst, VkImageLayout dl,
|
||||
uint32_t n, const VkImageCopy *regions);
|
||||
int vkb_emu_copy_image2(vkb_emu_device *e, VkCommandBuffer cb, const VkCopyImageInfo2 *info);
|
||||
int vkb_emu_copy_buffer_to_image(vkb_emu_device *e, VkCommandBuffer cb, VkBuffer src, VkImage dst, VkImageLayout dl, uint32_t n,
|
||||
const VkBufferImageCopy *regions);
|
||||
int vkb_emu_copy_buffer_to_image2(vkb_emu_device *e, VkCommandBuffer cb, const VkCopyBufferToImageInfo2 *info);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,432 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* BC1-BC7 textures on a GPU without them (textureCompressionBC missing; DXVK and vkd3d-proton
|
||||
* refuse such a device). The image the app creates in a BC format is created decoded instead
|
||||
* (RGBA8 / R8 / RG8 / RGBA16F, see vkb_emu_formats.h), mutable so sRGB/SNORM views work, and
|
||||
* every upload - vkCmdCopyBufferToImage(2), how all BC data arrives - becomes a compute dispatch
|
||||
* that decodes the blocks from the source buffer straight into it (shaders/bc_decode.comp). The
|
||||
* app's compute bindings are restored afterwards (server_emu_cmdstate.c).
|
||||
*
|
||||
* Not emulated (logged once): reading a BC image back to a buffer, and copies between BC and
|
||||
* uncompressed images (they would need an encoder).
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "server_emu.h"
|
||||
#include "vkb_emu_formats.h"
|
||||
#include "bc_spv.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
enum { FAM_BC1, FAM_BC2, FAM_BC3, FAM_BC4, FAM_BC5, FAM_BC6H, FAM_BC7, FAM_COUNT };
|
||||
|
||||
typedef struct bcn_state {
|
||||
VkDescriptorSetLayout dsl;
|
||||
VkPipelineLayout layout;
|
||||
VkPipeline pipes[FAM_COUNT];
|
||||
VkDeviceSize ssbo_align;
|
||||
int push; /* VK_KHR_push_descriptor; else per-command-buffer sets */
|
||||
pthread_mutex_t lock;
|
||||
} bcn_state;
|
||||
|
||||
typedef struct bcn_image {
|
||||
VkImageView views[16]; /* per mip: 2D array storage view of every layer */
|
||||
} bcn_image;
|
||||
|
||||
typedef struct pc_t {
|
||||
uint32_t src_word, row_blocks, layer_blocks, flags;
|
||||
int32_t dst_off[2];
|
||||
uint32_t extent[2];
|
||||
} pc_t;
|
||||
|
||||
static int family(VkFormat f)
|
||||
{
|
||||
switch (f) {
|
||||
case VK_FORMAT_BC1_RGB_UNORM_BLOCK: case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
|
||||
case VK_FORMAT_BC1_RGBA_UNORM_BLOCK: case VK_FORMAT_BC1_RGBA_SRGB_BLOCK: return FAM_BC1;
|
||||
case VK_FORMAT_BC2_UNORM_BLOCK: case VK_FORMAT_BC2_SRGB_BLOCK: return FAM_BC2;
|
||||
case VK_FORMAT_BC3_UNORM_BLOCK: case VK_FORMAT_BC3_SRGB_BLOCK: return FAM_BC3;
|
||||
case VK_FORMAT_BC4_UNORM_BLOCK: case VK_FORMAT_BC4_SNORM_BLOCK: return FAM_BC4;
|
||||
case VK_FORMAT_BC5_UNORM_BLOCK: case VK_FORMAT_BC5_SNORM_BLOCK: return FAM_BC5;
|
||||
case VK_FORMAT_BC6H_UFLOAT_BLOCK: case VK_FORMAT_BC6H_SFLOAT_BLOCK: return FAM_BC6H;
|
||||
default: return FAM_BC7;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t family_flags(VkFormat f)
|
||||
{
|
||||
uint32_t fl = 0;
|
||||
if (f == VK_FORMAT_BC1_RGB_UNORM_BLOCK || f == VK_FORMAT_BC1_RGB_SRGB_BLOCK) fl |= 1;
|
||||
if (f == VK_FORMAT_BC4_SNORM_BLOCK || f == VK_FORMAT_BC5_SNORM_BLOCK || f == VK_FORMAT_BC6H_SFLOAT_BLOCK) fl |= 2;
|
||||
return fl;
|
||||
}
|
||||
|
||||
int vkb_emu_bcn_supported(const vkb_dispatch *idt, VkPhysicalDevice pd)
|
||||
{
|
||||
VkPhysicalDeviceFeatures f;
|
||||
idt->vkGetPhysicalDeviceFeatures(pd, &f);
|
||||
if (!f.shaderStorageImageExtendedFormats) {
|
||||
VKB_WARN("BC emulation: the GPU lacks shaderStorageImageExtendedFormats");
|
||||
return 0;
|
||||
}
|
||||
static const VkFormat need[] = {VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8_UNORM, VK_FORMAT_R8G8_UNORM, VK_FORMAT_R16G16B16A16_SFLOAT};
|
||||
for (size_t i = 0; i < sizeof(need) / sizeof(need[0]); i++) {
|
||||
VkFormatProperties fp;
|
||||
idt->vkGetPhysicalDeviceFormatProperties(pd, need[i], &fp);
|
||||
if (!(fp.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT)) {
|
||||
VKB_WARN("BC emulation: format %d cannot be a storage image", need[i]);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
VkPhysicalDeviceProperties p;
|
||||
idt->vkGetPhysicalDeviceProperties(pd, &p);
|
||||
if (p.apiVersion >= VK_API_VERSION_1_4) return 1;
|
||||
uint32_t n = 0;
|
||||
idt->vkEnumerateDeviceExtensionProperties(pd, NULL, &n, NULL);
|
||||
VkExtensionProperties *e = calloc(n ? n : 1, sizeof(*e));
|
||||
idt->vkEnumerateDeviceExtensionProperties(pd, NULL, &n, e);
|
||||
free(e);
|
||||
/* Without VK_KHR_push_descriptor (Mali-G720 on driver 44.1) the decoder binds ordinary sets. */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Push descriptors for the decoder; without them, sets from the command buffer's scratch pools.
|
||||
* VKBRIDGE_FAKE_MISSING=...,push takes the second way on a GPU that has them (host tests). */
|
||||
static int has_push(vkb_srv_table *dev)
|
||||
{
|
||||
const char *fake = getenv("VKBRIDGE_FAKE_MISSING");
|
||||
if (fake && strstr(fake, "push")) return 0;
|
||||
VkPhysicalDeviceProperties p;
|
||||
dev->real.vkGetPhysicalDeviceProperties(dev->physical, &p);
|
||||
if (p.apiVersion >= VK_API_VERSION_1_4 && dev->api_version >= VK_API_VERSION_1_4) return dev->real.vkCmdPushDescriptorSet != NULL;
|
||||
uint32_t n = 0;
|
||||
dev->real.vkEnumerateDeviceExtensionProperties(dev->physical, NULL, &n, NULL);
|
||||
VkExtensionProperties *e = calloc(n ? n : 1, sizeof(*e));
|
||||
dev->real.vkEnumerateDeviceExtensionProperties(dev->physical, NULL, &n, e);
|
||||
int ok = 0;
|
||||
for (uint32_t i = 0; i < n; i++) ok |= !strcmp(e[i].extensionName, "VK_KHR_push_descriptor");
|
||||
free(e);
|
||||
return ok && dev->real.vkCmdPushDescriptorSet != NULL;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ images */
|
||||
|
||||
void vkb_emu_image_create_info(vkb_emu_device *e, VkImageCreateInfo *ci)
|
||||
{
|
||||
if (!(e->flags & VKB_EMU_BCN) || !vkb_is_bc(ci->format)) return;
|
||||
/* The image keeps the meaning of the app's format (sRGB, SNORM) so blits and copies convert as
|
||||
* they would; the decoder writes it through a view in the storage format (EXTENDED_USAGE lets
|
||||
* the image carry STORAGE although sRGB/SNORM formats cannot be stored to). */
|
||||
static __thread VkFormat list[2];
|
||||
VkFormat view = vkb_bc_view_format(ci->format), st = vkb_bc_storage_format(ci->format);
|
||||
list[0] = view;
|
||||
list[1] = st;
|
||||
ci->format = view;
|
||||
ci->flags &= ~(VkImageCreateFlags)VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT;
|
||||
ci->flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | VK_IMAGE_CREATE_EXTENDED_USAGE_BIT;
|
||||
ci->usage |= VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
for (VkBaseOutStructure *b = (VkBaseOutStructure *)ci->pNext; b; b = b->pNext) {
|
||||
if (b->sType == VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO) {
|
||||
VkImageFormatListCreateInfo *fl = (VkImageFormatListCreateInfo *)b;
|
||||
fl->viewFormatCount = view == st ? 1 : 2;
|
||||
fl->pViewFormats = list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void vkb_emu_image_release(vkb_emu_device *e, vkb_emu_image *img)
|
||||
{
|
||||
bcn_image *bi = img->emu;
|
||||
if (!bi) return;
|
||||
for (int i = 0; i < 16; i++)
|
||||
if (bi->views[i]) e->dev->real.vkDestroyImageView(e->dev->device, bi->views[i], NULL);
|
||||
free(bi);
|
||||
img->emu = NULL;
|
||||
}
|
||||
|
||||
VkFormat vkb_emu_bcn_view_format(vkb_emu_device *e, VkImage image, VkFormat requested)
|
||||
{
|
||||
if (!(e->flags & VKB_EMU_BCN)) return requested;
|
||||
if (vkb_is_bc(requested)) return vkb_bc_view_format(requested);
|
||||
vkb_emu_image *r = vkb_emu_find_image(e, image);
|
||||
if (r && vkb_is_bc(r->client_format)) {
|
||||
VKB_ONCE(VKB_LOG_WARN, "BC emulation: an uncompressed view (format %d) of a BC image shows decoded texels (logged once)", requested);
|
||||
return vkb_bc_view_format(r->client_format);
|
||||
}
|
||||
return requested;
|
||||
}
|
||||
|
||||
static VKAPI_ATTR VkResult VKAPI_CALL emu_CreateImageView(VkDevice device, const VkImageViewCreateInfo *ci,
|
||||
const VkAllocationCallbacks *pAllocator, VkImageView *pView)
|
||||
{
|
||||
VkImageViewCreateInfo *v = (VkImageViewCreateInfo *)ci;
|
||||
v->format = vkb_emu_bcn_view_format(vkb_emu_cur(), ci->image, ci->format);
|
||||
return vkb_emu_real()->vkCreateImageView(device, v, pAllocator, pView);
|
||||
}
|
||||
|
||||
void vkb_emu_bcn_buffer_usage(vkb_emu_device *e, VkBufferCreateInfo *ci)
|
||||
{
|
||||
/* The decoder reads uploads as a storage buffer. */
|
||||
if ((e->flags & VKB_EMU_BCN) && (ci->usage & VK_BUFFER_USAGE_TRANSFER_SRC_BIT)) ci->usage |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ decoder */
|
||||
|
||||
static VkPipeline pipeline(vkb_emu_device *e, int fam)
|
||||
{
|
||||
bcn_state *s = e->bcn;
|
||||
if (s->pipes[fam]) return s->pipes[fam];
|
||||
pthread_mutex_lock(&s->lock);
|
||||
if (!s->pipes[fam]) {
|
||||
static const uint32_t *const code[FAM_COUNT] = {spv_bc1, spv_bc2, spv_bc3, spv_bc4, spv_bc5, spv_bc6h, spv_bc7};
|
||||
static const size_t size[FAM_COUNT] = {sizeof(spv_bc1), sizeof(spv_bc2), sizeof(spv_bc3), sizeof(spv_bc4),
|
||||
sizeof(spv_bc5), sizeof(spv_bc6h), sizeof(spv_bc7)};
|
||||
const vkb_dispatch *r = &e->dev->real;
|
||||
VkDevice d = e->dev->device;
|
||||
VkShaderModuleCreateInfo mci = {VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, NULL, 0, size[fam], code[fam]};
|
||||
VkShaderModule m;
|
||||
if (r->vkCreateShaderModule(d, &mci, NULL, &m) == VK_SUCCESS) {
|
||||
VkComputePipelineCreateInfo pci = {VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
|
||||
pci.stage = (VkPipelineShaderStageCreateInfo){VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, NULL, 0,
|
||||
VK_SHADER_STAGE_COMPUTE_BIT, m, "main", NULL};
|
||||
pci.layout = s->layout;
|
||||
if (r->vkCreateComputePipelines(d, VK_NULL_HANDLE, 1, &pci, NULL, &s->pipes[fam]) != VK_SUCCESS) {
|
||||
VKB_ERR("BC emulation: decoder pipeline %d failed to build", fam);
|
||||
s->pipes[fam] = VK_NULL_HANDLE;
|
||||
}
|
||||
r->vkDestroyShaderModule(d, m, NULL);
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
return s->pipes[fam];
|
||||
}
|
||||
|
||||
static VkImageView storage_view(vkb_emu_device *e, vkb_emu_image *img, uint32_t mip)
|
||||
{
|
||||
if (mip >= 16) return VK_NULL_HANDLE;
|
||||
pthread_mutex_lock(&e->lock);
|
||||
if (!img->emu) img->emu = calloc(1, sizeof(bcn_image));
|
||||
bcn_image *bi = img->emu;
|
||||
VkImageView v = bi->views[mip];
|
||||
pthread_mutex_unlock(&e->lock);
|
||||
if (v) return v;
|
||||
VkImageViewUsageCreateInfo usage = {VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO, NULL, VK_IMAGE_USAGE_STORAGE_BIT};
|
||||
VkImageViewCreateInfo ci = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, &usage, 0, img->image, VK_IMAGE_VIEW_TYPE_2D_ARRAY,
|
||||
vkb_bc_storage_format(img->client_format),
|
||||
{0}, {VK_IMAGE_ASPECT_COLOR_BIT, mip, 1, 0, img->layers}};
|
||||
if (e->dev->real.vkCreateImageView(e->dev->device, &ci, NULL, &v) != VK_SUCCESS) return VK_NULL_HANDLE;
|
||||
pthread_mutex_lock(&e->lock);
|
||||
if (bi->views[mip]) {
|
||||
e->dev->real.vkDestroyImageView(e->dev->device, v, NULL);
|
||||
v = bi->views[mip];
|
||||
} else {
|
||||
bi->views[mip] = v;
|
||||
}
|
||||
pthread_mutex_unlock(&e->lock);
|
||||
return v;
|
||||
}
|
||||
|
||||
typedef struct region {
|
||||
VkDeviceSize offset;
|
||||
uint32_t row_length, image_height;
|
||||
VkImageSubresourceLayers sub;
|
||||
VkOffset3D ioff;
|
||||
VkExtent3D iext;
|
||||
} region;
|
||||
|
||||
static void decode(vkb_emu_device *e, VkCommandBuffer cb, VkBuffer src, vkb_emu_image *img, VkImageLayout layout,
|
||||
uint32_t n, const region *regs)
|
||||
{
|
||||
bcn_state *s = e->bcn;
|
||||
const vkb_dispatch *r = &e->dev->real;
|
||||
if (img->type != VK_IMAGE_TYPE_2D) {
|
||||
VKB_ONCE(VKB_LOG_WARN, "BC emulation: uploads to 1D/3D BC images are not decoded (logged once)");
|
||||
return;
|
||||
}
|
||||
VkPipeline pipe = pipeline(e, family(img->client_format));
|
||||
if (!pipe) return;
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
const region *g = ®s[i];
|
||||
VkImageView view = storage_view(e, img, g->sub.mipLevel);
|
||||
if (!view) continue;
|
||||
VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, g->sub.mipLevel, 1, g->sub.baseArrayLayer, g->sub.layerCount};
|
||||
VkMemoryBarrier mb = {VK_STRUCTURE_TYPE_MEMORY_BARRIER, NULL, VK_ACCESS_MEMORY_WRITE_BIT,
|
||||
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT};
|
||||
VkImageMemoryBarrier ib = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, NULL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_WRITE_BIT,
|
||||
layout, VK_IMAGE_LAYOUT_GENERAL, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, img->image, range};
|
||||
r->vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
0, 1, &mb, 0, NULL, 1, &ib);
|
||||
VkDeviceSize aligned = g->offset & ~(s->ssbo_align - 1);
|
||||
VkDescriptorBufferInfo bi = {src, aligned, VK_WHOLE_SIZE};
|
||||
VkDescriptorImageInfo ii = {VK_NULL_HANDLE, view, VK_IMAGE_LAYOUT_GENERAL};
|
||||
VkWriteDescriptorSet w[2] = {
|
||||
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, VK_NULL_HANDLE, 0, 0, 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, NULL, &bi, NULL},
|
||||
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, VK_NULL_HANDLE, 1, 0, 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, &ii, NULL, NULL}};
|
||||
r->vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_COMPUTE, pipe);
|
||||
if (s->push) {
|
||||
r->vkCmdPushDescriptorSet(cb, VK_PIPELINE_BIND_POINT_COMPUTE, s->layout, 0, 2, w);
|
||||
} else {
|
||||
VkDescriptorSet set = vkb_emu_cb_scratch_set(e, cb, s->dsl);
|
||||
if (!set) {
|
||||
VKB_ONCE(VKB_LOG_ERROR, "BC emulation: no descriptor set for the decoder (logged once)");
|
||||
continue;
|
||||
}
|
||||
w[0].dstSet = w[1].dstSet = set;
|
||||
r->vkUpdateDescriptorSets(e->dev->device, 2, w, 0, NULL);
|
||||
r->vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_COMPUTE, s->layout, 0, 1, &set, 0, NULL);
|
||||
}
|
||||
uint32_t w_tex = g->row_length ? g->row_length : g->iext.width;
|
||||
uint32_t h_tex = g->image_height ? g->image_height : g->iext.height;
|
||||
pc_t pc;
|
||||
pc.src_word = (uint32_t)((g->offset - aligned) / 4);
|
||||
pc.row_blocks = (w_tex + 3) / 4;
|
||||
pc.layer_blocks = pc.row_blocks * ((h_tex + 3) / 4);
|
||||
pc.flags = family_flags(img->client_format) | (g->sub.baseArrayLayer << 8);
|
||||
pc.dst_off[0] = g->ioff.x;
|
||||
pc.dst_off[1] = g->ioff.y;
|
||||
pc.extent[0] = g->iext.width;
|
||||
pc.extent[1] = g->iext.height;
|
||||
r->vkCmdPushConstants(cb, s->layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(pc), &pc);
|
||||
uint32_t bw = (g->iext.width + 3) / 4, bh = (g->iext.height + 3) / 4;
|
||||
r->vkCmdDispatch(cb, (bw + 7) / 8, (bh + 7) / 8, g->sub.layerCount);
|
||||
ib.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
|
||||
ib.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT;
|
||||
ib.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
ib.newLayout = layout;
|
||||
r->vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &ib);
|
||||
}
|
||||
vkb_emu_restore_compute(e, cb);
|
||||
}
|
||||
|
||||
static vkb_emu_image *bc_image(vkb_emu_device *e, VkImage img)
|
||||
{
|
||||
if (!(e->flags & VKB_EMU_BCN)) return NULL;
|
||||
vkb_emu_image *r = vkb_emu_find_image(e, img);
|
||||
return r && vkb_is_bc(r->client_format) ? r : NULL;
|
||||
}
|
||||
|
||||
int vkb_emu_copy_buffer_to_image(vkb_emu_device *e, VkCommandBuffer cb, VkBuffer src, VkImage dst, VkImageLayout dl, uint32_t n,
|
||||
const VkBufferImageCopy *regions)
|
||||
{
|
||||
vkb_emu_image *img = bc_image(e, dst);
|
||||
if (!img) return 0;
|
||||
region *g = malloc((n ? n : 1) * sizeof(*g));
|
||||
for (uint32_t i = 0; i < n; i++)
|
||||
g[i] = (region){regions[i].bufferOffset, regions[i].bufferRowLength, regions[i].bufferImageHeight, regions[i].imageSubresource,
|
||||
regions[i].imageOffset, regions[i].imageExtent};
|
||||
decode(e, cb, src, img, dl, n, g);
|
||||
free(g);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int vkb_emu_copy_buffer_to_image2(vkb_emu_device *e, VkCommandBuffer cb, const VkCopyBufferToImageInfo2 *info)
|
||||
{
|
||||
vkb_emu_image *img = bc_image(e, info->dstImage);
|
||||
if (!img) return 0;
|
||||
region *g = malloc((info->regionCount ? info->regionCount : 1) * sizeof(*g));
|
||||
for (uint32_t i = 0; i < info->regionCount; i++) {
|
||||
const VkBufferImageCopy2 *r = &info->pRegions[i];
|
||||
g[i] = (region){r->bufferOffset, r->bufferRowLength, r->bufferImageHeight, r->imageSubresource, r->imageOffset, r->imageExtent};
|
||||
}
|
||||
decode(e, cb, info->srcBuffer, img, info->dstImageLayout, info->regionCount, g);
|
||||
free(g);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Copies between BC images copy decoded texels; a region may run past a small mip's edge (whole
|
||||
* blocks), which the decoded image does not have. */
|
||||
static void clamp_region(const vkb_emu_image *s, const vkb_emu_image *d, const VkImageSubresourceLayers *ss,
|
||||
const VkImageSubresourceLayers *ds, const VkOffset3D *so, const VkOffset3D *dof, VkExtent3D *ext)
|
||||
{
|
||||
uint32_t sw = s->extent.width >> ss->mipLevel, sh = s->extent.height >> ss->mipLevel;
|
||||
uint32_t dw = d->extent.width >> ds->mipLevel, dh = d->extent.height >> ds->mipLevel;
|
||||
if (!sw) sw = 1;
|
||||
if (!sh) sh = 1;
|
||||
if (!dw) dw = 1;
|
||||
if (!dh) dh = 1;
|
||||
if ((uint32_t)so->x + ext->width > sw) ext->width = sw > (uint32_t)so->x ? sw - (uint32_t)so->x : 0;
|
||||
if ((uint32_t)so->y + ext->height > sh) ext->height = sh > (uint32_t)so->y ? sh - (uint32_t)so->y : 0;
|
||||
if ((uint32_t)dof->x + ext->width > dw) ext->width = dw > (uint32_t)dof->x ? dw - (uint32_t)dof->x : 0;
|
||||
if ((uint32_t)dof->y + ext->height > dh) ext->height = dh > (uint32_t)dof->y ? dh - (uint32_t)dof->y : 0;
|
||||
}
|
||||
|
||||
int vkb_emu_copy_image(vkb_emu_device *e, VkCommandBuffer cb, VkImage src, VkImageLayout sl, VkImage dst, VkImageLayout dl, uint32_t n,
|
||||
const VkImageCopy *regions)
|
||||
{
|
||||
vkb_emu_image *s = bc_image(e, src), *d = bc_image(e, dst);
|
||||
if (!s && !d) return 0;
|
||||
if (!s || !d || vkb_bc_storage_format(s->client_format) != vkb_bc_storage_format(d->client_format)) {
|
||||
VKB_ONCE(VKB_LOG_WARN, "BC emulation: a copy between a BC image and another format is skipped (logged once)");
|
||||
return 1;
|
||||
}
|
||||
VkImageCopy *c = (VkImageCopy *)regions;
|
||||
for (uint32_t i = 0; i < n; i++)
|
||||
clamp_region(s, d, &c[i].srcSubresource, &c[i].dstSubresource, &c[i].srcOffset, &c[i].dstOffset, &c[i].extent);
|
||||
e->dev->real.vkCmdCopyImage(cb, src, sl, dst, dl, n, regions);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int vkb_emu_copy_image2(vkb_emu_device *e, VkCommandBuffer cb, const VkCopyImageInfo2 *info)
|
||||
{
|
||||
vkb_emu_image *s = bc_image(e, info->srcImage), *d = bc_image(e, info->dstImage);
|
||||
if (!s && !d) return 0;
|
||||
if (!s || !d || vkb_bc_storage_format(s->client_format) != vkb_bc_storage_format(d->client_format)) {
|
||||
VKB_ONCE(VKB_LOG_WARN, "BC emulation: a copy between a BC image and another format is skipped (logged once)");
|
||||
return 1;
|
||||
}
|
||||
VkImageCopy2 *c = (VkImageCopy2 *)info->pRegions;
|
||||
for (uint32_t i = 0; i < info->regionCount; i++)
|
||||
clamp_region(s, d, &c[i].srcSubresource, &c[i].dstSubresource, &c[i].srcOffset, &c[i].dstOffset, &c[i].extent);
|
||||
e->dev->real.vkCmdCopyImage2(cb, info);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int vkb_emu_bcn_blocks_readback(vkb_emu_device *e, VkImage img)
|
||||
{
|
||||
if (!bc_image(e, img)) return 0;
|
||||
VKB_ONCE(VKB_LOG_WARN, "BC emulation: reading a BC image back to a buffer is not supported (logged once)");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ install */
|
||||
|
||||
void vkb_emu_bcn_install(vkb_srv_table *dev)
|
||||
{
|
||||
vkb_emu_device *e = dev->emu;
|
||||
if (!(e->flags & VKB_EMU_BCN)) return;
|
||||
bcn_state *s = calloc(1, sizeof(*s));
|
||||
pthread_mutex_init(&s->lock, NULL);
|
||||
VkPhysicalDeviceProperties p;
|
||||
dev->real.vkGetPhysicalDeviceProperties(dev->physical, &p);
|
||||
s->ssbo_align = p.limits.minStorageBufferOffsetAlignment ? p.limits.minStorageBufferOffsetAlignment : 4;
|
||||
VkDescriptorSetLayoutBinding b[2] = {{0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, NULL},
|
||||
{1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT, NULL}};
|
||||
s->push = has_push(dev);
|
||||
VKB_INFO("BC emulation: decoder binds with %s", s->push ? "push descriptors" : "descriptor sets (no VK_KHR_push_descriptor)");
|
||||
VkDescriptorSetLayoutCreateInfo dci = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, NULL,
|
||||
s->push ? VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT : 0, 2, b};
|
||||
VkPushConstantRange pr = {VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(pc_t)};
|
||||
if (dev->real.vkCreateDescriptorSetLayout(dev->device, &dci, NULL, &s->dsl) != VK_SUCCESS) {
|
||||
VKB_ERR("BC emulation: cannot create the decoder's descriptor layout");
|
||||
free(s);
|
||||
e->flags &= ~VKB_EMU_BCN;
|
||||
return;
|
||||
}
|
||||
VkPipelineLayoutCreateInfo plci = {VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, NULL, 0, 1, &s->dsl, 1, &pr};
|
||||
dev->real.vkCreatePipelineLayout(dev->device, &plci, NULL, &s->layout);
|
||||
e->bcn = s;
|
||||
dev->dt.vkCreateImageView = emu_CreateImageView;
|
||||
}
|
||||
|
||||
void vkb_emu_bcn_uninstall(vkb_srv_table *dev)
|
||||
{
|
||||
bcn_state *s = dev->emu->bcn;
|
||||
if (!s) return;
|
||||
for (int i = 0; i < FAM_COUNT; i++)
|
||||
if (s->pipes[i]) dev->real.vkDestroyPipeline(dev->device, s->pipes[i], NULL);
|
||||
dev->real.vkDestroyPipelineLayout(dev->device, s->layout, NULL);
|
||||
dev->real.vkDestroyDescriptorSetLayout(dev->device, s->dsl, NULL);
|
||||
pthread_mutex_destroy(&s->lock);
|
||||
free(s);
|
||||
dev->emu->bcn = NULL;
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* What an app has bound in a command buffer, for emulations that record commands of their own
|
||||
* into it (the BCn decoder's dispatches) and must leave the app's state as it was: the compute
|
||||
* pipeline, compute descriptor sets, push constants and push descriptors. Bind calls are kept as
|
||||
* they were made (a later call that covers an earlier one replaces it) and replayed in order.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "server_emu.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct bind_sets {
|
||||
VkPipelineLayout layout;
|
||||
uint32_t first, count, ndyn;
|
||||
VkDescriptorSet sets[32];
|
||||
uint32_t dyn[64];
|
||||
} bind_sets;
|
||||
|
||||
typedef struct push_consts {
|
||||
VkPipelineLayout layout;
|
||||
VkShaderStageFlags stages;
|
||||
uint32_t offset, size;
|
||||
uint8_t data[256];
|
||||
} push_consts;
|
||||
|
||||
typedef struct push_tmpl {
|
||||
VkDescriptorUpdateTemplate tmpl;
|
||||
VkPipelineLayout layout;
|
||||
uint32_t set;
|
||||
void *data;
|
||||
size_t size;
|
||||
} push_tmpl;
|
||||
|
||||
typedef struct cb_state {
|
||||
VkCommandBuffer cb;
|
||||
VkPipeline compute_pipeline;
|
||||
bind_sets *sets;
|
||||
uint32_t nsets, capsets;
|
||||
push_consts *pcs;
|
||||
uint32_t npcs, cappcs;
|
||||
push_tmpl *pts;
|
||||
uint32_t npts, cappts;
|
||||
void *gfx; /* graphics-side state (divisor emulation) */
|
||||
/* Descriptor sets emulations record with (no push descriptors), alive until the next begin. */
|
||||
VkDescriptorPool *scratch;
|
||||
uint32_t nscratch, cur_scratch, used_scratch;
|
||||
struct cb_state *next;
|
||||
} cb_state;
|
||||
|
||||
#define CB_BUCKETS 512
|
||||
#define SCRATCH_SETS 64
|
||||
typedef struct cmdstate {
|
||||
pthread_mutex_t lock;
|
||||
cb_state *b[CB_BUCKETS];
|
||||
} cmdstate;
|
||||
|
||||
/* Packed size of the template data being pushed (set by the raw push handler, server_misc.c). */
|
||||
__thread size_t vkb_emu_push_size;
|
||||
|
||||
static cmdstate *g_states_for(vkb_emu_device *e) { return (cmdstate *)e->cmdstate; }
|
||||
|
||||
static unsigned ch(VkCommandBuffer cb)
|
||||
{
|
||||
uint64_t k = (uint64_t)(uintptr_t)cb;
|
||||
k ^= k >> 30;
|
||||
k *= 0xbf58476d1ce4e5b9ull;
|
||||
return (unsigned)(k >> 55) & (CB_BUCKETS - 1);
|
||||
}
|
||||
|
||||
static cb_state *get(vkb_emu_device *e, VkCommandBuffer cb)
|
||||
{
|
||||
cmdstate *s = g_states_for(e);
|
||||
pthread_mutex_lock(&s->lock);
|
||||
cb_state *c = s->b[ch(cb)];
|
||||
while (c && c->cb != cb) c = c->next;
|
||||
if (!c) {
|
||||
c = calloc(1, sizeof(*c));
|
||||
c->cb = cb;
|
||||
c->next = s->b[ch(cb)];
|
||||
s->b[ch(cb)] = c;
|
||||
}
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
return c;
|
||||
}
|
||||
|
||||
/* A descriptor set of layout `dsl` (storage buffers and images only) for a command the emulation
|
||||
* records into cb; it lives until cb is begun again, when the app has seen the work finish. */
|
||||
VkDescriptorSet vkb_emu_cb_scratch_set(vkb_emu_device *e, VkCommandBuffer cb, VkDescriptorSetLayout dsl)
|
||||
{
|
||||
cb_state *c = get(e, cb);
|
||||
const vkb_dispatch *r = &e->dev->real;
|
||||
for (;;) {
|
||||
if (c->cur_scratch >= c->nscratch) {
|
||||
VkDescriptorPoolSize sizes[2] = {{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, SCRATCH_SETS * 2},
|
||||
{VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, SCRATCH_SETS * 2}};
|
||||
VkDescriptorPoolCreateInfo pci = {VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, NULL, 0, SCRATCH_SETS, 2, sizes};
|
||||
VkDescriptorPool pool;
|
||||
VkDescriptorPool *np = realloc(c->scratch, (c->nscratch + 1) * sizeof(*np));
|
||||
if (!np) return VK_NULL_HANDLE;
|
||||
c->scratch = np;
|
||||
if (r->vkCreateDescriptorPool(e->dev->device, &pci, NULL, &pool) != VK_SUCCESS) return VK_NULL_HANDLE;
|
||||
c->scratch[c->nscratch++] = pool;
|
||||
}
|
||||
if (c->used_scratch < SCRATCH_SETS) {
|
||||
VkDescriptorSetAllocateInfo ai = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, NULL, c->scratch[c->cur_scratch], 1, &dsl};
|
||||
VkDescriptorSet set;
|
||||
if (r->vkAllocateDescriptorSets(e->dev->device, &ai, &set) == VK_SUCCESS) {
|
||||
c->used_scratch++;
|
||||
return set;
|
||||
}
|
||||
}
|
||||
c->cur_scratch++;
|
||||
c->used_scratch = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void *vkb_emu_cb_gfx(vkb_emu_device *e, VkCommandBuffer cb, size_t size)
|
||||
{
|
||||
cb_state *c = get(e, cb);
|
||||
if (!c->gfx) c->gfx = calloc(1, size);
|
||||
return c->gfx;
|
||||
}
|
||||
|
||||
static void reset(vkb_emu_device *e, cb_state *c)
|
||||
{
|
||||
for (uint32_t i = 0; i < c->nscratch && i <= c->cur_scratch; i++)
|
||||
e->dev->real.vkResetDescriptorPool(e->dev->device, c->scratch[i], 0);
|
||||
c->cur_scratch = 0;
|
||||
c->used_scratch = 0;
|
||||
c->compute_pipeline = VK_NULL_HANDLE;
|
||||
c->nsets = 0;
|
||||
c->npcs = 0;
|
||||
for (uint32_t i = 0; i < c->npts; i++) free(c->pts[i].data);
|
||||
c->npts = 0;
|
||||
vkb_emu_gfx_reset(c->gfx);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ wrappers */
|
||||
|
||||
static VKAPI_ATTR VkResult VKAPI_CALL emu_BeginCommandBuffer(VkCommandBuffer cb, const VkCommandBufferBeginInfo *info)
|
||||
{
|
||||
reset(vkb_emu_cur(), get(vkb_emu_cur(), cb));
|
||||
return vkb_emu_real()->vkBeginCommandBuffer(cb, info);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdBindPipeline(VkCommandBuffer cb, VkPipelineBindPoint bp, VkPipeline p)
|
||||
{
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
if (bp == VK_PIPELINE_BIND_POINT_COMPUTE) get(e, cb)->compute_pipeline = p;
|
||||
if (bp == VK_PIPELINE_BIND_POINT_GRAPHICS) vkb_emu_gfx_bind_pipeline(e, cb, p);
|
||||
vkb_emu_real()->vkCmdBindPipeline(cb, bp, p);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdBindDescriptorSets(VkCommandBuffer cb, VkPipelineBindPoint bp, VkPipelineLayout layout,
|
||||
uint32_t first, uint32_t count, const VkDescriptorSet *sets,
|
||||
uint32_t ndyn, const uint32_t *dyn)
|
||||
{
|
||||
if (bp == VK_PIPELINE_BIND_POINT_COMPUTE && count <= 32 && ndyn <= 64) {
|
||||
cb_state *c = get(vkb_emu_cur(), cb);
|
||||
/* Drop earlier calls this one covers completely. */
|
||||
uint32_t k = 0;
|
||||
for (uint32_t i = 0; i < c->nsets; i++) {
|
||||
bind_sets *b = &c->sets[i];
|
||||
if (b->first >= first && b->first + b->count <= first + count) continue;
|
||||
c->sets[k++] = *b;
|
||||
}
|
||||
c->nsets = k;
|
||||
if (c->nsets == c->capsets) {
|
||||
c->capsets = c->capsets ? c->capsets * 2 : 8;
|
||||
c->sets = realloc(c->sets, c->capsets * sizeof(*c->sets));
|
||||
}
|
||||
bind_sets *b = &c->sets[c->nsets++];
|
||||
b->layout = layout;
|
||||
b->first = first;
|
||||
b->count = count;
|
||||
b->ndyn = ndyn;
|
||||
memcpy(b->sets, sets, count * sizeof(*sets));
|
||||
if (ndyn) memcpy(b->dyn, dyn, ndyn * sizeof(*dyn));
|
||||
}
|
||||
vkb_emu_real()->vkCmdBindDescriptorSets(cb, bp, layout, first, count, sets, ndyn, dyn);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdPushConstants(VkCommandBuffer cb, VkPipelineLayout layout, VkShaderStageFlags stages,
|
||||
uint32_t offset, uint32_t size, const void *data)
|
||||
{
|
||||
if ((stages & VK_SHADER_STAGE_COMPUTE_BIT) && offset + size <= 256) {
|
||||
cb_state *c = get(vkb_emu_cur(), cb);
|
||||
uint32_t k = 0;
|
||||
for (uint32_t i = 0; i < c->npcs; i++) {
|
||||
push_consts *p = &c->pcs[i];
|
||||
if (p->offset >= offset && p->offset + p->size <= offset + size && p->stages == stages) continue;
|
||||
c->pcs[k++] = *p;
|
||||
}
|
||||
c->npcs = k;
|
||||
if (c->npcs == c->cappcs) {
|
||||
c->cappcs = c->cappcs ? c->cappcs * 2 : 8;
|
||||
c->pcs = realloc(c->pcs, c->cappcs * sizeof(*c->pcs));
|
||||
}
|
||||
push_consts *p = &c->pcs[c->npcs++];
|
||||
p->layout = layout;
|
||||
p->stages = stages;
|
||||
p->offset = offset;
|
||||
p->size = size;
|
||||
memcpy(p->data + offset, data, size);
|
||||
}
|
||||
vkb_emu_real()->vkCmdPushConstants(cb, layout, stages, offset, size, data);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdPushDescriptorSetWithTemplate(VkCommandBuffer cb, VkDescriptorUpdateTemplate tmpl,
|
||||
VkPipelineLayout layout, uint32_t set, const void *data)
|
||||
{
|
||||
/* Only the template's bind point decides; recording it for both costs nothing when replayed
|
||||
* into the same bind point it was made for. Sizes are unknown here: the bridge's raw template
|
||||
* path hands over the packed size (vkb_emu_push_size). */
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
cb_state *c = get(e, cb);
|
||||
size_t size = vkb_emu_push_size;
|
||||
if (size && size < (1u << 20)) {
|
||||
if (c->npts == c->cappts) {
|
||||
c->cappts = c->cappts ? c->cappts * 2 : 4;
|
||||
c->pts = realloc(c->pts, c->cappts * sizeof(*c->pts));
|
||||
}
|
||||
push_tmpl *p = &c->pts[c->npts++];
|
||||
p->tmpl = tmpl;
|
||||
p->layout = layout;
|
||||
p->set = set;
|
||||
p->size = size;
|
||||
p->data = malloc(size);
|
||||
memcpy(p->data, data, size);
|
||||
}
|
||||
vkb_emu_real()->vkCmdPushDescriptorSetWithTemplate(cb, tmpl, layout, set, data);
|
||||
}
|
||||
|
||||
/* Puts back what the app had bound for compute after an emulation recorded its own dispatch. */
|
||||
void vkb_emu_restore_compute(vkb_emu_device *e, VkCommandBuffer cb)
|
||||
{
|
||||
cb_state *c = get(e, cb);
|
||||
const vkb_dispatch *r = vkb_emu_real();
|
||||
if (c->compute_pipeline) r->vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_COMPUTE, c->compute_pipeline);
|
||||
for (uint32_t i = 0; i < c->nsets; i++) {
|
||||
bind_sets *b = &c->sets[i];
|
||||
r->vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_COMPUTE, b->layout, b->first, b->count, b->sets, b->ndyn, b->dyn);
|
||||
}
|
||||
for (uint32_t i = 0; i < c->npts; i++) {
|
||||
push_tmpl *p = &c->pts[i];
|
||||
r->vkCmdPushDescriptorSetWithTemplate(cb, p->tmpl, p->layout, p->set, p->data);
|
||||
}
|
||||
for (uint32_t i = 0; i < c->npcs; i++) {
|
||||
push_consts *p = &c->pcs[i];
|
||||
r->vkCmdPushConstants(cb, p->layout, p->stages, p->offset, p->size, p->data + p->offset);
|
||||
}
|
||||
}
|
||||
|
||||
void vkb_emu_cmdstate_install(vkb_srv_table *dev)
|
||||
{
|
||||
vkb_emu_device *e = dev->emu;
|
||||
cmdstate *s = calloc(1, sizeof(*s));
|
||||
pthread_mutex_init(&s->lock, NULL);
|
||||
e->cmdstate = s;
|
||||
dev->dt.vkBeginCommandBuffer = emu_BeginCommandBuffer;
|
||||
dev->dt.vkCmdBindPipeline = emu_CmdBindPipeline;
|
||||
dev->dt.vkCmdBindDescriptorSets = emu_CmdBindDescriptorSets;
|
||||
dev->dt.vkCmdPushConstants = emu_CmdPushConstants;
|
||||
if (dev->real.vkCmdPushDescriptorSetWithTemplate) dev->dt.vkCmdPushDescriptorSetWithTemplate = emu_CmdPushDescriptorSetWithTemplate;
|
||||
}
|
||||
|
||||
void vkb_emu_cmdstate_uninstall(vkb_srv_table *dev)
|
||||
{
|
||||
cmdstate *s = dev->emu->cmdstate;
|
||||
if (!s) return;
|
||||
for (int i = 0; i < CB_BUCKETS; i++) {
|
||||
while (s->b[i]) {
|
||||
cb_state *c = s->b[i];
|
||||
s->b[i] = c->next;
|
||||
reset(dev->emu, c);
|
||||
for (uint32_t j = 0; j < c->nscratch; j++) dev->real.vkDestroyDescriptorPool(dev->device, c->scratch[j], NULL);
|
||||
free(c->scratch);
|
||||
free(c->sets);
|
||||
free(c->pcs);
|
||||
free(c->pts);
|
||||
vkb_emu_gfx_free(c->gfx);
|
||||
free(c);
|
||||
}
|
||||
}
|
||||
pthread_mutex_destroy(&s->lock);
|
||||
free(s);
|
||||
dev->emu->cmdstate = NULL;
|
||||
}
|
||||
@@ -0,0 +1,454 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Vertex attribute divisor (VK_EXT/KHR_vertex_attribute_divisor) on a GPU without it.
|
||||
*
|
||||
* A binding with divisor d != 1 fetches element firstInstance + i / d for instance i (d = 0: always
|
||||
* firstInstance). Emulated by giving such bindings a stride of 0 - every instance reads the same
|
||||
* element - and rebinding the buffer at that element before each draw. With d > 1 the draw is
|
||||
* split into runs of instances that share elements; each run keeps its own firstInstance, so
|
||||
* gl_InstanceIndex and every ordinary instance-rate attribute stay exactly as they were.
|
||||
* Indirect draws cannot be split on the CPU: divisor-0 bindings use element 0 there, d > 1 is
|
||||
* approximated the same way (logged once).
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "server_emu.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MAX_BIND 32
|
||||
|
||||
typedef struct div_info {
|
||||
uint32_t mask; /* bindings emulated */
|
||||
uint32_t divisor[MAX_BIND];
|
||||
uint32_t stride[MAX_BIND]; /* the app's stride (baked pipelines) */
|
||||
int dynamic_stride; /* the pipeline takes strides at bind time */
|
||||
int dynamic_input; /* the pipeline takes vertex input at record time */
|
||||
int split; /* some divisor > 1: draws are split */
|
||||
VkPipeline zero_base; /* variant whose vertex shader reads BaseInstance as 0 */
|
||||
VkShaderModule zero_vs; /* its vertex shader, between creation steps */
|
||||
uint32_t vs_stage;
|
||||
} div_info;
|
||||
|
||||
typedef struct pipe_rec {
|
||||
VkPipeline p;
|
||||
div_info info;
|
||||
struct pipe_rec *next;
|
||||
} pipe_rec;
|
||||
|
||||
typedef struct gfx_state {
|
||||
VkPipeline pipeline; /* bound graphics pipeline */
|
||||
div_info pipe; /* the bound pipeline's */
|
||||
div_info dyn; /* from vkCmdSetVertexInputEXT */
|
||||
int use_dyn;
|
||||
VkBuffer buf[MAX_BIND];
|
||||
VkDeviceSize off[MAX_BIND], size[MAX_BIND], stride[MAX_BIND];
|
||||
uint32_t bound; /* slots the app bound */
|
||||
uint32_t tampered; /* slots we rebound */
|
||||
} gfx_state;
|
||||
|
||||
typedef struct div_state {
|
||||
pthread_mutex_t lock;
|
||||
pipe_rec *b[256];
|
||||
} div_state;
|
||||
|
||||
static div_state *dstate(vkb_emu_device *e) { return e->divisor; }
|
||||
|
||||
static unsigned ph(VkPipeline p)
|
||||
{
|
||||
uint64_t k = (uint64_t)p;
|
||||
k ^= k >> 33;
|
||||
k *= 0xff51afd7ed558ccdull;
|
||||
return (unsigned)(k >> 56);
|
||||
}
|
||||
|
||||
static const div_info *find_pipe(vkb_emu_device *e, VkPipeline p)
|
||||
{
|
||||
div_state *s = dstate(e);
|
||||
pthread_mutex_lock(&s->lock);
|
||||
pipe_rec *r = s->b[ph(p)];
|
||||
while (r && r->p != p) r = r->next;
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
return r ? &r->info : NULL;
|
||||
}
|
||||
|
||||
static void store_pipe(vkb_emu_device *e, VkPipeline p, const div_info *info)
|
||||
{
|
||||
div_state *s = dstate(e);
|
||||
pipe_rec *r = calloc(1, sizeof(*r));
|
||||
r->p = p;
|
||||
r->info = *info;
|
||||
pthread_mutex_lock(&s->lock);
|
||||
r->next = s->b[ph(p)];
|
||||
s->b[ph(p)] = r;
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
}
|
||||
|
||||
static void drop_pipe(vkb_emu_device *e, VkPipeline p)
|
||||
{
|
||||
div_state *s = dstate(e);
|
||||
pthread_mutex_lock(&s->lock);
|
||||
pipe_rec **pp = &s->b[ph(p)];
|
||||
while (*pp && (*pp)->p != p) pp = &(*pp)->next;
|
||||
pipe_rec *r = *pp;
|
||||
if (r) *pp = r->next;
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
if (r && r->info.zero_base) e->dev->real.vkDestroyPipeline(e->dev->device, r->info.zero_base, NULL);
|
||||
free(r);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ pipelines */
|
||||
|
||||
static int has_dynamic(const VkGraphicsPipelineCreateInfo *ci, VkDynamicState d)
|
||||
{
|
||||
if (!ci->pDynamicState) return 0;
|
||||
for (uint32_t i = 0; i < ci->pDynamicState->dynamicStateCount; i++)
|
||||
if (ci->pDynamicState->pDynamicStates[i] == d) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Strips the divisor state of one create info; returns what the pipeline needs at draw time. */
|
||||
void vkb_emu_divisor_pipeline(vkb_emu_device *e, VkDevice device, VkGraphicsPipelineCreateInfo *ci, div_info *out)
|
||||
{
|
||||
memset(out, 0, sizeof(*out));
|
||||
if (!(e->flags & VKB_EMU_DIVISOR)) return;
|
||||
out->dynamic_stride = has_dynamic(ci, VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE);
|
||||
out->dynamic_input = has_dynamic(ci, VK_DYNAMIC_STATE_VERTEX_INPUT_EXT);
|
||||
/* Pieces linked from libraries bring their own records. */
|
||||
for (const VkBaseInStructure *b = ci->pNext; b; b = b->pNext) {
|
||||
if (b->sType != VK_STRUCTURE_TYPE_PIPELINE_LIBRARY_CREATE_INFO_KHR) continue;
|
||||
const VkPipelineLibraryCreateInfoKHR *l = (const void *)b;
|
||||
for (uint32_t i = 0; i < l->libraryCount; i++) {
|
||||
const div_info *li = find_pipe(e, l->pLibraries[i]);
|
||||
if (!li || !li->mask) continue;
|
||||
for (uint32_t k = 0; k < MAX_BIND; k++)
|
||||
if (li->mask & (1u << k)) {
|
||||
out->mask |= 1u << k;
|
||||
out->divisor[k] = li->divisor[k];
|
||||
out->stride[k] = li->stride[k];
|
||||
if (li->divisor[k] > 1) out->split = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
VkPipelineVertexInputStateCreateInfo *vi = (VkPipelineVertexInputStateCreateInfo *)ci->pVertexInputState;
|
||||
if (!vi || out->dynamic_input) return;
|
||||
VkPipelineVertexInputDivisorStateCreateInfo *d =
|
||||
vkb_emu_chain_take(vi, VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO);
|
||||
if (!d) return;
|
||||
VkVertexInputBindingDescription *bd = (VkVertexInputBindingDescription *)vi->pVertexBindingDescriptions;
|
||||
for (uint32_t i = 0; i < d->vertexBindingDivisorCount; i++) {
|
||||
uint32_t b = d->pVertexBindingDivisors[i].binding, div = d->pVertexBindingDivisors[i].divisor;
|
||||
if (div == 1 || b >= MAX_BIND) continue;
|
||||
for (uint32_t k = 0; k < vi->vertexBindingDescriptionCount; k++) {
|
||||
if (bd[k].binding != b) continue;
|
||||
out->mask |= 1u << b;
|
||||
out->divisor[b] = div;
|
||||
out->stride[b] = bd[k].stride;
|
||||
if (div > 1) out->split = 1;
|
||||
bd[k].stride = 0;
|
||||
}
|
||||
}
|
||||
/* The vertex shader's SPIR-V is still at hand here (it may be inline). */
|
||||
if (out->split && ci->pStages) {
|
||||
for (uint32_t st = 0; st < ci->stageCount; st++) {
|
||||
if (ci->pStages[st].stage != VK_SHADER_STAGE_VERTEX_BIT) continue;
|
||||
out->zero_vs = vkb_emu_shader_zero_base_instance(e, device, &ci->pStages[st]);
|
||||
out->vs_stage = st;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void vkb_emu_divisor_created(vkb_emu_device *e, VkDevice device, VkPipelineCache cache, const VkGraphicsPipelineCreateInfo *ci,
|
||||
VkPipeline p, div_info *info)
|
||||
{
|
||||
if (!(e->flags & VKB_EMU_DIVISOR)) return;
|
||||
if (!p) {
|
||||
if (info->zero_vs) vkb_emu_real()->vkDestroyShaderModule(device, info->zero_vs, NULL);
|
||||
return;
|
||||
}
|
||||
if (info->zero_vs) {
|
||||
/* Split draws move BaseInstance: a variant that reads it as 0 serves the usual
|
||||
* firstInstance = 0 draws exactly. */
|
||||
VkPipelineShaderStageCreateInfo *st = malloc(ci->stageCount * sizeof(*st));
|
||||
memcpy(st, ci->pStages, ci->stageCount * sizeof(*st));
|
||||
st[info->vs_stage].module = info->zero_vs;
|
||||
st[info->vs_stage].pNext = NULL;
|
||||
VkGraphicsPipelineCreateInfo v = *ci;
|
||||
v.pStages = st;
|
||||
if (vkb_emu_real()->vkCreateGraphicsPipelines(device, cache, 1, &v, NULL, &info->zero_base) != VK_SUCCESS)
|
||||
info->zero_base = VK_NULL_HANDLE;
|
||||
vkb_emu_real()->vkDestroyShaderModule(device, info->zero_vs, NULL);
|
||||
info->zero_vs = VK_NULL_HANDLE;
|
||||
free(st);
|
||||
}
|
||||
if (info->mask || info->dynamic_input || info->dynamic_stride) store_pipe(e, p, info);
|
||||
if (info->mask)
|
||||
VKB_DBG("divisor: pipeline %p emulates bindings 0x%x (dynamic stride %d, dynamic input %d, split %d, variant %p)", (void *)p,
|
||||
info->mask, info->dynamic_stride, info->dynamic_input, info->split, (void *)info->zero_base);
|
||||
}
|
||||
|
||||
size_t vkb_emu_divisor_info_size(void) { return sizeof(div_info); }
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_DestroyPipeline(VkDevice device, VkPipeline p, const VkAllocationCallbacks *pAllocator)
|
||||
{
|
||||
if (p) drop_pipe(vkb_emu_cur(), p);
|
||||
vkb_emu_real()->vkDestroyPipeline(device, p, pAllocator);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ command buffers */
|
||||
|
||||
static gfx_state *gfx(vkb_emu_device *e, VkCommandBuffer cb) { return vkb_emu_cb_gfx(e, cb, sizeof(gfx_state)); }
|
||||
|
||||
void vkb_emu_gfx_reset(void *g)
|
||||
{
|
||||
if (g) memset(g, 0, sizeof(gfx_state));
|
||||
}
|
||||
|
||||
void vkb_emu_gfx_free(void *g) { free(g); }
|
||||
|
||||
void vkb_emu_gfx_bind_pipeline(vkb_emu_device *e, VkCommandBuffer cb, VkPipeline p)
|
||||
{
|
||||
if (!(e->flags & VKB_EMU_DIVISOR)) return;
|
||||
gfx_state *g = gfx(e, cb);
|
||||
g->pipeline = p;
|
||||
const div_info *i = find_pipe(e, p);
|
||||
if (i) g->pipe = *i;
|
||||
else memset(&g->pipe, 0, sizeof(g->pipe));
|
||||
g->use_dyn = g->pipe.dynamic_input;
|
||||
}
|
||||
|
||||
static void record_bind(gfx_state *g, uint32_t first, uint32_t n, const VkBuffer *b, const VkDeviceSize *o, const VkDeviceSize *s,
|
||||
const VkDeviceSize *st)
|
||||
{
|
||||
for (uint32_t i = 0; i < n && first + i < MAX_BIND; i++) {
|
||||
uint32_t k = first + i;
|
||||
g->buf[k] = b[i];
|
||||
g->off[k] = o[i];
|
||||
g->size[k] = s ? s[i] : VK_WHOLE_SIZE;
|
||||
if (st) g->stride[k] = st[i];
|
||||
g->bound |= 1u << k;
|
||||
g->tampered &= ~(1u << k);
|
||||
}
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdBindVertexBuffers(VkCommandBuffer cb, uint32_t first, uint32_t n, const VkBuffer *b,
|
||||
const VkDeviceSize *o)
|
||||
{
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
record_bind(gfx(e, cb), first, n, b, o, NULL, NULL);
|
||||
vkb_emu_real()->vkCmdBindVertexBuffers(cb, first, n, b, o);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdBindVertexBuffers2(VkCommandBuffer cb, uint32_t first, uint32_t n, const VkBuffer *b,
|
||||
const VkDeviceSize *o, const VkDeviceSize *s, const VkDeviceSize *st)
|
||||
{
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
record_bind(gfx(e, cb), first, n, b, o, s, st);
|
||||
vkb_emu_real()->vkCmdBindVertexBuffers2(cb, first, n, b, o, s, st);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdSetVertexInputEXT(VkCommandBuffer cb, uint32_t nb, const VkVertexInputBindingDescription2EXT *bd,
|
||||
uint32_t na, const VkVertexInputAttributeDescription2EXT *ad)
|
||||
{
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
gfx_state *g = gfx(e, cb);
|
||||
memset(&g->dyn, 0, sizeof(g->dyn));
|
||||
g->dyn.dynamic_stride = 1;
|
||||
g->dyn.dynamic_input = 1;
|
||||
VkVertexInputBindingDescription2EXT *w = (VkVertexInputBindingDescription2EXT *)bd;
|
||||
for (uint32_t i = 0; i < nb; i++) {
|
||||
uint32_t b = w[i].binding;
|
||||
if (w[i].inputRate != VK_VERTEX_INPUT_RATE_INSTANCE || w[i].divisor == 1 || b >= MAX_BIND) continue;
|
||||
g->dyn.mask |= 1u << b;
|
||||
g->dyn.divisor[b] = w[i].divisor;
|
||||
g->dyn.stride[b] = w[i].stride;
|
||||
g->stride[b] = w[i].stride;
|
||||
w[i].stride = 0;
|
||||
w[i].divisor = 1;
|
||||
}
|
||||
vkb_emu_real()->vkCmdSetVertexInputEXT(cb, nb, bd, na, ad);
|
||||
}
|
||||
|
||||
/* Rebinds the emulated bindings for instances starting at `inst` (relative to the draw), and puts
|
||||
* back any slot we changed that the current pipeline does not emulate. */
|
||||
static void prepare(vkb_emu_device *e, VkCommandBuffer cb, gfx_state *g, const div_info *di, uint32_t first_instance, uint32_t inst)
|
||||
{
|
||||
const vkb_dispatch *r = vkb_emu_real();
|
||||
for (uint32_t k = 0; k < MAX_BIND; k++) {
|
||||
uint32_t bit = 1u << k;
|
||||
if (!(g->bound & bit)) continue;
|
||||
if (di->mask & bit) {
|
||||
uint32_t d = di->divisor[k];
|
||||
VkDeviceSize stride = di->dynamic_input ? g->dyn.stride[k] : (di->dynamic_stride ? g->stride[k] : di->stride[k]);
|
||||
VkDeviceSize elem = (VkDeviceSize)first_instance + (d ? inst / d : 0);
|
||||
VkDeviceSize off = g->off[k] + elem * stride;
|
||||
if (di->dynamic_stride || di->dynamic_input) {
|
||||
VkDeviceSize size = g->size[k] == VK_WHOLE_SIZE ? VK_WHOLE_SIZE : (g->size[k] > elem * stride ? g->size[k] - elem * stride : 0);
|
||||
VkDeviceSize zero = 0;
|
||||
r->vkCmdBindVertexBuffers2(cb, k, 1, &g->buf[k], &off, size == VK_WHOLE_SIZE ? NULL : &size, &zero);
|
||||
} else {
|
||||
r->vkCmdBindVertexBuffers(cb, k, 1, &g->buf[k], &off);
|
||||
}
|
||||
g->tampered |= bit;
|
||||
} else if (g->tampered & bit) {
|
||||
if (di->dynamic_stride) {
|
||||
VkDeviceSize size = g->size[k];
|
||||
r->vkCmdBindVertexBuffers2(cb, k, 1, &g->buf[k], &g->off[k], size == VK_WHOLE_SIZE ? NULL : &size, &g->stride[k]);
|
||||
} else {
|
||||
r->vkCmdBindVertexBuffers(cb, k, 1, &g->buf[k], &g->off[k]);
|
||||
}
|
||||
g->tampered &= ~bit;
|
||||
}
|
||||
}
|
||||
(void)e;
|
||||
}
|
||||
|
||||
static const div_info *active(gfx_state *g)
|
||||
{
|
||||
return g->use_dyn ? &g->dyn : &g->pipe;
|
||||
}
|
||||
|
||||
/* The next instance where some emulated binding moves to its next element. */
|
||||
static uint32_t next_boundary(const div_info *di, uint32_t i, uint32_t count)
|
||||
{
|
||||
uint32_t next = count;
|
||||
for (uint32_t k = 0; k < MAX_BIND; k++) {
|
||||
if (!(di->mask & (1u << k)) || di->divisor[k] <= 1) continue;
|
||||
uint32_t d = di->divisor[k], nb = (i / d + 1) * d;
|
||||
if (nb < next) next = nb;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
/* A split draw binds the BaseInstance-as-0 variant when that keeps it exact. */
|
||||
static int split_begin(vkb_emu_device *e, VkCommandBuffer cb, gfx_state *g, const div_info *di, uint32_t fi, uint32_t ic)
|
||||
{
|
||||
(void)e;
|
||||
if (!di->split || next_boundary(di, 0, ic) >= ic) return 0;
|
||||
if (fi == 0 && di->zero_base) {
|
||||
vkb_emu_real()->vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, di->zero_base);
|
||||
return 1;
|
||||
}
|
||||
if (fi != 0) VKB_ONCE(VKB_LOG_WARN, "divisor emulation: a split draw with firstInstance > 0 may see SV_InstanceID offset (logged once)");
|
||||
(void)g;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void split_end(VkCommandBuffer cb, gfx_state *g, int variant)
|
||||
{
|
||||
if (variant) vkb_emu_real()->vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, g->pipeline);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdDraw(VkCommandBuffer cb, uint32_t vc, uint32_t ic, uint32_t fv, uint32_t fi)
|
||||
{
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
gfx_state *g = gfx(e, cb);
|
||||
const div_info *di = active(g);
|
||||
if (!di->mask && !g->tampered) {
|
||||
vkb_emu_real()->vkCmdDraw(cb, vc, ic, fv, fi);
|
||||
return;
|
||||
}
|
||||
VKB_DBG("divisor: draw %u instances from %u, bindings 0x%x (dyn %d), bound 0x%x", ic, fi, di->mask, g->use_dyn, g->bound);
|
||||
int variant = split_begin(e, cb, g, di, fi, ic);
|
||||
for (uint32_t i = 0; i < ic || (ic == 0 && i == 0);) {
|
||||
uint32_t end = next_boundary(di, i, ic);
|
||||
prepare(e, cb, g, di, fi, i);
|
||||
if (ic) vkb_emu_real()->vkCmdDraw(cb, vc, end - i, fv, fi + i);
|
||||
if (!ic) break;
|
||||
i = end;
|
||||
}
|
||||
split_end(cb, g, variant);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdDrawIndexed(VkCommandBuffer cb, uint32_t xc, uint32_t ic, uint32_t fx, int32_t vo, uint32_t fi)
|
||||
{
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
gfx_state *g = gfx(e, cb);
|
||||
const div_info *di = active(g);
|
||||
if (!di->mask && !g->tampered) {
|
||||
vkb_emu_real()->vkCmdDrawIndexed(cb, xc, ic, fx, vo, fi);
|
||||
return;
|
||||
}
|
||||
int variant = split_begin(e, cb, g, di, fi, ic);
|
||||
for (uint32_t i = 0; i < ic || (ic == 0 && i == 0);) {
|
||||
uint32_t end = next_boundary(di, i, ic);
|
||||
prepare(e, cb, g, di, fi, i);
|
||||
if (ic) vkb_emu_real()->vkCmdDrawIndexed(cb, xc, end - i, fx, vo, fi + i);
|
||||
if (!ic) break;
|
||||
i = end;
|
||||
}
|
||||
split_end(cb, g, variant);
|
||||
}
|
||||
|
||||
static void indirect_prepare(vkb_emu_device *e, VkCommandBuffer cb)
|
||||
{
|
||||
gfx_state *g = gfx(e, cb);
|
||||
const div_info *di = active(g);
|
||||
if (!di->mask && !g->tampered) return;
|
||||
for (uint32_t k = 0; k < MAX_BIND; k++)
|
||||
if ((di->mask & (1u << k)) && di->divisor[k] > 1)
|
||||
VKB_ONCE(VKB_LOG_WARN, "divisor emulation: an indirect draw with an instance divisor > 1 is approximated (logged once)");
|
||||
prepare(e, cb, g, di, 0, 0);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdDrawIndirect(VkCommandBuffer cb, VkBuffer b, VkDeviceSize o, uint32_t n, uint32_t s)
|
||||
{
|
||||
indirect_prepare(vkb_emu_cur(), cb);
|
||||
vkb_emu_real()->vkCmdDrawIndirect(cb, b, o, n, s);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdDrawIndexedIndirect(VkCommandBuffer cb, VkBuffer b, VkDeviceSize o, uint32_t n, uint32_t s)
|
||||
{
|
||||
indirect_prepare(vkb_emu_cur(), cb);
|
||||
vkb_emu_real()->vkCmdDrawIndexedIndirect(cb, b, o, n, s);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdDrawIndirectCount(VkCommandBuffer cb, VkBuffer b, VkDeviceSize o, VkBuffer cbuf, VkDeviceSize co,
|
||||
uint32_t max, uint32_t s)
|
||||
{
|
||||
indirect_prepare(vkb_emu_cur(), cb);
|
||||
vkb_emu_real()->vkCmdDrawIndirectCount(cb, b, o, cbuf, co, max, s);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdDrawIndexedIndirectCount(VkCommandBuffer cb, VkBuffer b, VkDeviceSize o, VkBuffer cbuf,
|
||||
VkDeviceSize co, uint32_t max, uint32_t s)
|
||||
{
|
||||
indirect_prepare(vkb_emu_cur(), cb);
|
||||
vkb_emu_real()->vkCmdDrawIndexedIndirectCount(cb, b, o, cbuf, co, max, s);
|
||||
}
|
||||
|
||||
void vkb_emu_divisor_install(vkb_srv_table *dev)
|
||||
{
|
||||
vkb_emu_device *e = dev->emu;
|
||||
if (!(e->flags & VKB_EMU_DIVISOR)) return;
|
||||
div_state *s = calloc(1, sizeof(*s));
|
||||
pthread_mutex_init(&s->lock, NULL);
|
||||
e->divisor = s;
|
||||
vkb_dispatch *dt = &dev->dt;
|
||||
dt->vkDestroyPipeline = emu_DestroyPipeline;
|
||||
dt->vkCmdBindVertexBuffers = emu_CmdBindVertexBuffers;
|
||||
if (dev->real.vkCmdBindVertexBuffers2) dt->vkCmdBindVertexBuffers2 = emu_CmdBindVertexBuffers2;
|
||||
if (dev->real.vkCmdSetVertexInputEXT) dt->vkCmdSetVertexInputEXT = emu_CmdSetVertexInputEXT;
|
||||
dt->vkCmdDraw = emu_CmdDraw;
|
||||
dt->vkCmdDrawIndexed = emu_CmdDrawIndexed;
|
||||
dt->vkCmdDrawIndirect = emu_CmdDrawIndirect;
|
||||
dt->vkCmdDrawIndexedIndirect = emu_CmdDrawIndexedIndirect;
|
||||
if (dev->real.vkCmdDrawIndirectCount) dt->vkCmdDrawIndirectCount = emu_CmdDrawIndirectCount;
|
||||
if (dev->real.vkCmdDrawIndexedIndirectCount) dt->vkCmdDrawIndexedIndirectCount = emu_CmdDrawIndexedIndirectCount;
|
||||
}
|
||||
|
||||
void vkb_emu_divisor_uninstall(vkb_srv_table *dev)
|
||||
{
|
||||
div_state *s = dev->emu->divisor;
|
||||
if (!s) return;
|
||||
for (int i = 0; i < 256; i++) {
|
||||
while (s->b[i]) {
|
||||
pipe_rec *r = s->b[i];
|
||||
s->b[i] = r->next;
|
||||
free(r);
|
||||
}
|
||||
}
|
||||
pthread_mutex_destroy(&s->lock);
|
||||
free(s);
|
||||
dev->emu->divisor = NULL;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Installs the feature emulations into a device.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "server_emu.h"
|
||||
|
||||
void vkb_emu_install_features(vkb_srv_table *dev)
|
||||
{
|
||||
vkb_emu_cmdstate_install(dev);
|
||||
vkb_emu_shader_install(dev);
|
||||
vkb_emu_bcn_install(dev);
|
||||
vkb_emu_divisor_install(dev);
|
||||
}
|
||||
|
||||
void vkb_emu_uninstall_features(vkb_srv_table *dev)
|
||||
{
|
||||
vkb_emu_divisor_uninstall(dev);
|
||||
vkb_emu_bcn_uninstall(dev);
|
||||
vkb_emu_shader_uninstall(dev);
|
||||
vkb_emu_cmdstate_uninstall(dev);
|
||||
}
|
||||
@@ -0,0 +1,478 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Emulation wrappers installed into a device's dispatch table, like a Vulkan layer: the generated
|
||||
* handlers call c->dt, which for an emulating device points here first. The device is found
|
||||
* through the per-call table the connection loop sets (vkb_srv_current_table).
|
||||
*
|
||||
* VK_KHR_maintenance5 (Zink and DXVK 2.7 refuse a Vulkan 1.3 device without it):
|
||||
* - 64-bit flags structures (pipeline create flags 2, buffer usage 2) folded into the 32-bit
|
||||
* fields, which hold every bit a 1.3 driver knows;
|
||||
* - shader modules given inline (VkShaderModuleCreateInfo chained to a stage) created and
|
||||
* destroyed around the pipeline;
|
||||
* - vkCmdBindIndexBuffer2, vkGetRenderingAreaGranularity, vkGetImageSubresourceLayout2 and
|
||||
* vkGetDeviceImageSubresourceLayout on their 1.0 equivalents;
|
||||
* - VK_REMAINING_ARRAY_LAYERS in copy regions and VK_WHOLE_SIZE in vertex buffer sizes, resolved
|
||||
* from the image/buffer the server keeps a record of.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "server_emu.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ------------------------------------------------------------------ resource records */
|
||||
|
||||
static unsigned rh(uint64_t k)
|
||||
{
|
||||
k ^= k >> 29;
|
||||
k *= 0xbf58476d1ce4e5b9ull;
|
||||
return (unsigned)(k >> 52) & (VKB_EMU_BUCKETS - 1);
|
||||
}
|
||||
|
||||
void vkb_emu_track_image(vkb_emu_device *e, VkImage img, const VkImageCreateInfo *ci, VkFormat client_format)
|
||||
{
|
||||
vkb_emu_image *r = calloc(1, sizeof(*r));
|
||||
r->image = img;
|
||||
r->type = ci->imageType;
|
||||
r->format = ci->format;
|
||||
r->client_format = client_format;
|
||||
r->extent = ci->extent;
|
||||
r->mips = ci->mipLevels;
|
||||
r->layers = ci->arrayLayers;
|
||||
pthread_mutex_lock(&e->lock);
|
||||
unsigned h = rh((uint64_t)img);
|
||||
r->next = e->images[h];
|
||||
e->images[h] = r;
|
||||
pthread_mutex_unlock(&e->lock);
|
||||
}
|
||||
|
||||
vkb_emu_image *vkb_emu_find_image(vkb_emu_device *e, VkImage img)
|
||||
{
|
||||
pthread_mutex_lock(&e->lock);
|
||||
vkb_emu_image *r = e->images[rh((uint64_t)img)];
|
||||
while (r && r->image != img) r = r->next;
|
||||
pthread_mutex_unlock(&e->lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
static void untrack_image(vkb_emu_device *e, VkImage img)
|
||||
{
|
||||
pthread_mutex_lock(&e->lock);
|
||||
vkb_emu_image **pp = &e->images[rh((uint64_t)img)];
|
||||
while (*pp && (*pp)->image != img) pp = &(*pp)->next;
|
||||
vkb_emu_image *r = *pp;
|
||||
if (r) *pp = r->next;
|
||||
pthread_mutex_unlock(&e->lock);
|
||||
if (r) {
|
||||
vkb_emu_image_release(e, r);
|
||||
free(r);
|
||||
}
|
||||
}
|
||||
|
||||
static void track_buffer(vkb_emu_device *e, VkBuffer b, VkDeviceSize size)
|
||||
{
|
||||
vkb_emu_buffer *r = calloc(1, sizeof(*r));
|
||||
r->buffer = b;
|
||||
r->size = size;
|
||||
pthread_mutex_lock(&e->lock);
|
||||
unsigned h = rh((uint64_t)b);
|
||||
r->next = e->buffers[h];
|
||||
e->buffers[h] = r;
|
||||
pthread_mutex_unlock(&e->lock);
|
||||
}
|
||||
|
||||
static VkDeviceSize buffer_size(vkb_emu_device *e, VkBuffer b)
|
||||
{
|
||||
pthread_mutex_lock(&e->lock);
|
||||
vkb_emu_buffer *r = e->buffers[rh((uint64_t)b)];
|
||||
while (r && r->buffer != b) r = r->next;
|
||||
VkDeviceSize s = r ? r->size : 0;
|
||||
pthread_mutex_unlock(&e->lock);
|
||||
return s;
|
||||
}
|
||||
|
||||
static void untrack_buffer(vkb_emu_device *e, VkBuffer b)
|
||||
{
|
||||
pthread_mutex_lock(&e->lock);
|
||||
vkb_emu_buffer **pp = &e->buffers[rh((uint64_t)b)];
|
||||
while (*pp && (*pp)->buffer != b) pp = &(*pp)->next;
|
||||
vkb_emu_buffer *r = *pp;
|
||||
if (r) *pp = r->next;
|
||||
pthread_mutex_unlock(&e->lock);
|
||||
free(r);
|
||||
}
|
||||
|
||||
vkb_emu_device *vkb_emu_cur(void)
|
||||
{
|
||||
vkb_srv_table *t = vkb_srv_current_table();
|
||||
return t ? t->emu : NULL;
|
||||
}
|
||||
|
||||
const vkb_dispatch *vkb_emu_real(void)
|
||||
{
|
||||
return &vkb_srv_current_table()->real;
|
||||
}
|
||||
|
||||
/* Removes the first structure of type t from a chain we own (decoded into the call's arena). */
|
||||
void *vkb_emu_chain_take(const void *head_const, VkStructureType t)
|
||||
{
|
||||
VkBaseOutStructure *prev = (VkBaseOutStructure *)head_const;
|
||||
while (prev->pNext) {
|
||||
if (prev->pNext->sType == t) {
|
||||
VkBaseOutStructure *found = prev->pNext;
|
||||
prev->pNext = found->pNext;
|
||||
return found;
|
||||
}
|
||||
prev = prev->pNext;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ resources */
|
||||
|
||||
static VKAPI_ATTR VkResult VKAPI_CALL emu_CreateBuffer(VkDevice device, const VkBufferCreateInfo *pCreateInfo,
|
||||
const VkAllocationCallbacks *pAllocator, VkBuffer *pBuffer)
|
||||
{
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
VkBufferCreateInfo *ci = (VkBufferCreateInfo *)pCreateInfo;
|
||||
if (e->flags & VKB_EMU_MAINT5) {
|
||||
VkBufferUsageFlags2CreateInfo *u = vkb_emu_chain_take(ci, VK_STRUCTURE_TYPE_BUFFER_USAGE_FLAGS_2_CREATE_INFO);
|
||||
if (u) ci->usage = (VkBufferUsageFlags)u->usage;
|
||||
}
|
||||
vkb_emu_bcn_buffer_usage(e, ci);
|
||||
VkResult r = vkb_emu_real()->vkCreateBuffer(device, ci, pAllocator, pBuffer);
|
||||
if (r == VK_SUCCESS) track_buffer(e, *pBuffer, ci->size);
|
||||
return r;
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_DestroyBuffer(VkDevice device, VkBuffer buffer, const VkAllocationCallbacks *pAllocator)
|
||||
{
|
||||
if (buffer) untrack_buffer(vkb_emu_cur(), buffer);
|
||||
vkb_emu_real()->vkDestroyBuffer(device, buffer, pAllocator);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR VkResult VKAPI_CALL emu_CreateBufferView(VkDevice device, const VkBufferViewCreateInfo *pCreateInfo,
|
||||
const VkAllocationCallbacks *pAllocator, VkBufferView *pView)
|
||||
{
|
||||
if (vkb_emu_cur()->flags & VKB_EMU_MAINT5) vkb_emu_chain_take(pCreateInfo, VK_STRUCTURE_TYPE_BUFFER_USAGE_FLAGS_2_CREATE_INFO);
|
||||
return vkb_emu_real()->vkCreateBufferView(device, pCreateInfo, pAllocator, pView);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR VkResult VKAPI_CALL emu_CreateImage(VkDevice device, const VkImageCreateInfo *pCreateInfo,
|
||||
const VkAllocationCallbacks *pAllocator, VkImage *pImage)
|
||||
{
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
VkImageCreateInfo *ci = (VkImageCreateInfo *)pCreateInfo;
|
||||
VkFormat client_format = ci->format;
|
||||
vkb_emu_image_create_info(e, ci);
|
||||
VkResult r = vkb_emu_real()->vkCreateImage(device, ci, pAllocator, pImage);
|
||||
if (r == VK_SUCCESS) vkb_emu_track_image(e, *pImage, ci, client_format);
|
||||
return r;
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_DestroyImage(VkDevice device, VkImage image, const VkAllocationCallbacks *pAllocator)
|
||||
{
|
||||
if (image) untrack_image(vkb_emu_cur(), image);
|
||||
vkb_emu_real()->vkDestroyImage(device, image, pAllocator);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ pipelines */
|
||||
|
||||
/* Fold flags2 and create inline shader modules; temporaries are recorded in mods[]. */
|
||||
static void prep_stage(VkDevice device, VkPipelineShaderStageCreateInfo *st, VkShaderModule *mods, uint32_t *nmods, uint32_t cap)
|
||||
{
|
||||
if (st->module) return;
|
||||
VkShaderModuleCreateInfo *smci = vkb_emu_chain_take(st, VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO);
|
||||
if (!smci) return;
|
||||
VkShaderModule m = VK_NULL_HANDLE;
|
||||
smci->pNext = NULL;
|
||||
if (vkb_emu_real()->vkCreateShaderModule(device, smci, NULL, &m) == VK_SUCCESS) {
|
||||
st->module = m;
|
||||
if (*nmods < cap) mods[(*nmods)++] = m;
|
||||
}
|
||||
}
|
||||
|
||||
static VKAPI_ATTR VkResult VKAPI_CALL emu_CreateGraphicsPipelines(VkDevice device, VkPipelineCache cache, uint32_t n,
|
||||
const VkGraphicsPipelineCreateInfo *infos,
|
||||
const VkAllocationCallbacks *pAllocator, VkPipeline *pPipelines)
|
||||
{
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
VkShaderModule mods[64];
|
||||
uint32_t nmods = 0;
|
||||
size_t dsz = vkb_emu_divisor_info_size();
|
||||
uint8_t *divs = calloc(n ? n : 1, dsz);
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
VkGraphicsPipelineCreateInfo *ci = (VkGraphicsPipelineCreateInfo *)&infos[i];
|
||||
vkb_emu_divisor_pipeline(e, device, ci, (div_info *)(divs + i * dsz));
|
||||
vkb_emu_depth_clip_pipeline(e, ci);
|
||||
/* Shader rewrites first: they read inline SPIR-V before it becomes a module. */
|
||||
vkb_emu_shader_pipeline(e, device, ci, mods, &nmods, 64);
|
||||
if (e->flags & VKB_EMU_MAINT5) {
|
||||
VkPipelineCreateFlags2CreateInfo *f2 = vkb_emu_chain_take(ci, VK_STRUCTURE_TYPE_PIPELINE_CREATE_FLAGS_2_CREATE_INFO);
|
||||
if (f2) ci->flags = (VkPipelineCreateFlags)f2->flags;
|
||||
for (uint32_t s = 0; s < ci->stageCount; s++)
|
||||
prep_stage(device, (VkPipelineShaderStageCreateInfo *)&ci->pStages[s], mods, &nmods, 64);
|
||||
}
|
||||
}
|
||||
VkResult r = vkb_emu_real()->vkCreateGraphicsPipelines(device, cache, n, infos, pAllocator, pPipelines);
|
||||
/* Before the temporary modules go: a divisor variant is built from the same stages. */
|
||||
for (uint32_t i = 0; i < n; i++)
|
||||
vkb_emu_divisor_created(e, device, cache, &infos[i], r >= 0 ? pPipelines[i] : VK_NULL_HANDLE, (div_info *)(divs + i * dsz));
|
||||
for (uint32_t i = 0; i < nmods; i++) vkb_emu_real()->vkDestroyShaderModule(device, mods[i], NULL);
|
||||
free(divs);
|
||||
return r;
|
||||
}
|
||||
|
||||
static VKAPI_ATTR VkResult VKAPI_CALL emu_CreateComputePipelines(VkDevice device, VkPipelineCache cache, uint32_t n,
|
||||
const VkComputePipelineCreateInfo *infos,
|
||||
const VkAllocationCallbacks *pAllocator, VkPipeline *pPipelines)
|
||||
{
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
VkShaderModule mods[64];
|
||||
uint32_t nmods = 0;
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
VkComputePipelineCreateInfo *ci = (VkComputePipelineCreateInfo *)&infos[i];
|
||||
if (e->flags & VKB_EMU_MAINT5) {
|
||||
VkPipelineCreateFlags2CreateInfo *f2 = vkb_emu_chain_take(ci, VK_STRUCTURE_TYPE_PIPELINE_CREATE_FLAGS_2_CREATE_INFO);
|
||||
if (f2) ci->flags = (VkPipelineCreateFlags)f2->flags;
|
||||
prep_stage(device, &ci->stage, mods, &nmods, 64);
|
||||
}
|
||||
}
|
||||
VkResult r = vkb_emu_real()->vkCreateComputePipelines(device, cache, n, infos, pAllocator, pPipelines);
|
||||
for (uint32_t i = 0; i < nmods; i++) vkb_emu_real()->vkDestroyShaderModule(device, mods[i], NULL);
|
||||
return r;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ maintenance5 entry points */
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdBindIndexBuffer2(VkCommandBuffer cb, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size,
|
||||
VkIndexType type)
|
||||
{
|
||||
(void)size;
|
||||
vkb_emu_real()->vkCmdBindIndexBuffer(cb, buffer, offset, type);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_GetRenderingAreaGranularity(VkDevice device, const VkRenderingAreaInfo *info, VkExtent2D *g)
|
||||
{
|
||||
(void)device;
|
||||
(void)info;
|
||||
g->width = 1;
|
||||
g->height = 1;
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_GetImageSubresourceLayout2(VkDevice device, VkImage image, const VkImageSubresource2 *sub,
|
||||
VkSubresourceLayout2 *layout)
|
||||
{
|
||||
vkb_emu_real()->vkGetImageSubresourceLayout(device, image, &sub->imageSubresource, &layout->subresourceLayout);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_GetDeviceImageSubresourceLayout(VkDevice device, const VkDeviceImageSubresourceInfo *info,
|
||||
VkSubresourceLayout2 *layout)
|
||||
{
|
||||
VkImage img;
|
||||
memset(&layout->subresourceLayout, 0, sizeof(layout->subresourceLayout));
|
||||
if (vkb_emu_real()->vkCreateImage(device, info->pCreateInfo, NULL, &img) != VK_SUCCESS) return;
|
||||
vkb_emu_real()->vkGetImageSubresourceLayout(device, img, &info->pSubresource->imageSubresource, &layout->subresourceLayout);
|
||||
vkb_emu_real()->vkDestroyImage(device, img, NULL);
|
||||
}
|
||||
|
||||
/* VK_REMAINING_ARRAY_LAYERS in a copy region, resolved against the image. */
|
||||
static void fix_layers(vkb_emu_device *e, VkImage img, VkImageSubresourceLayers *l)
|
||||
{
|
||||
if (l->layerCount != VK_REMAINING_ARRAY_LAYERS) return;
|
||||
vkb_emu_image *r = vkb_emu_find_image(e, img);
|
||||
l->layerCount = r && r->layers > l->baseArrayLayer ? r->layers - l->baseArrayLayer : 1;
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdCopyImage(VkCommandBuffer cb, VkImage src, VkImageLayout sl, VkImage dst, VkImageLayout dl,
|
||||
uint32_t n, const VkImageCopy *regions)
|
||||
{
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
fix_layers(e, src, (VkImageSubresourceLayers *)®ions[i].srcSubresource);
|
||||
fix_layers(e, dst, (VkImageSubresourceLayers *)®ions[i].dstSubresource);
|
||||
}
|
||||
if (vkb_emu_copy_image(e, cb, src, sl, dst, dl, n, regions)) return;
|
||||
vkb_emu_real()->vkCmdCopyImage(cb, src, sl, dst, dl, n, regions);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdCopyImage2(VkCommandBuffer cb, const VkCopyImageInfo2 *info)
|
||||
{
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
for (uint32_t i = 0; i < info->regionCount; i++) {
|
||||
fix_layers(e, info->srcImage, (VkImageSubresourceLayers *)&info->pRegions[i].srcSubresource);
|
||||
fix_layers(e, info->dstImage, (VkImageSubresourceLayers *)&info->pRegions[i].dstSubresource);
|
||||
}
|
||||
if (vkb_emu_copy_image2(e, cb, info)) return;
|
||||
vkb_emu_real()->vkCmdCopyImage2(cb, info);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdBlitImage(VkCommandBuffer cb, VkImage src, VkImageLayout sl, VkImage dst, VkImageLayout dl,
|
||||
uint32_t n, const VkImageBlit *regions, VkFilter filter)
|
||||
{
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
fix_layers(e, src, (VkImageSubresourceLayers *)®ions[i].srcSubresource);
|
||||
fix_layers(e, dst, (VkImageSubresourceLayers *)®ions[i].dstSubresource);
|
||||
}
|
||||
vkb_emu_real()->vkCmdBlitImage(cb, src, sl, dst, dl, n, regions, filter);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdBlitImage2(VkCommandBuffer cb, const VkBlitImageInfo2 *info)
|
||||
{
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
for (uint32_t i = 0; i < info->regionCount; i++) {
|
||||
fix_layers(e, info->srcImage, (VkImageSubresourceLayers *)&info->pRegions[i].srcSubresource);
|
||||
fix_layers(e, info->dstImage, (VkImageSubresourceLayers *)&info->pRegions[i].dstSubresource);
|
||||
}
|
||||
vkb_emu_real()->vkCmdBlitImage2(cb, info);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdResolveImage(VkCommandBuffer cb, VkImage src, VkImageLayout sl, VkImage dst, VkImageLayout dl,
|
||||
uint32_t n, const VkImageResolve *regions)
|
||||
{
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
fix_layers(e, src, (VkImageSubresourceLayers *)®ions[i].srcSubresource);
|
||||
fix_layers(e, dst, (VkImageSubresourceLayers *)®ions[i].dstSubresource);
|
||||
}
|
||||
vkb_emu_real()->vkCmdResolveImage(cb, src, sl, dst, dl, n, regions);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdResolveImage2(VkCommandBuffer cb, const VkResolveImageInfo2 *info)
|
||||
{
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
for (uint32_t i = 0; i < info->regionCount; i++) {
|
||||
fix_layers(e, info->srcImage, (VkImageSubresourceLayers *)&info->pRegions[i].srcSubresource);
|
||||
fix_layers(e, info->dstImage, (VkImageSubresourceLayers *)&info->pRegions[i].dstSubresource);
|
||||
}
|
||||
vkb_emu_real()->vkCmdResolveImage2(cb, info);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdCopyBufferToImage(VkCommandBuffer cb, VkBuffer src, VkImage dst, VkImageLayout dl, uint32_t n,
|
||||
const VkBufferImageCopy *regions)
|
||||
{
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
for (uint32_t i = 0; i < n; i++) fix_layers(e, dst, (VkImageSubresourceLayers *)®ions[i].imageSubresource);
|
||||
if (vkb_emu_copy_buffer_to_image(e, cb, src, dst, dl, n, regions)) return;
|
||||
vkb_emu_real()->vkCmdCopyBufferToImage(cb, src, dst, dl, n, regions);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdCopyBufferToImage2(VkCommandBuffer cb, const VkCopyBufferToImageInfo2 *info)
|
||||
{
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
for (uint32_t i = 0; i < info->regionCount; i++)
|
||||
fix_layers(e, info->dstImage, (VkImageSubresourceLayers *)&info->pRegions[i].imageSubresource);
|
||||
if (vkb_emu_copy_buffer_to_image2(e, cb, info)) return;
|
||||
vkb_emu_real()->vkCmdCopyBufferToImage2(cb, info);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdCopyImageToBuffer(VkCommandBuffer cb, VkImage src, VkImageLayout sl, VkBuffer dst, uint32_t n,
|
||||
const VkBufferImageCopy *regions)
|
||||
{
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
for (uint32_t i = 0; i < n; i++) fix_layers(e, src, (VkImageSubresourceLayers *)®ions[i].imageSubresource);
|
||||
if (vkb_emu_bcn_blocks_readback(e, src)) return;
|
||||
vkb_emu_real()->vkCmdCopyImageToBuffer(cb, src, sl, dst, n, regions);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdCopyImageToBuffer2(VkCommandBuffer cb, const VkCopyImageToBufferInfo2 *info)
|
||||
{
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
for (uint32_t i = 0; i < info->regionCount; i++)
|
||||
fix_layers(e, info->srcImage, (VkImageSubresourceLayers *)&info->pRegions[i].imageSubresource);
|
||||
if (vkb_emu_bcn_blocks_readback(e, info->srcImage)) return;
|
||||
vkb_emu_real()->vkCmdCopyImageToBuffer2(cb, info);
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_CmdBindVertexBuffers2(VkCommandBuffer cb, uint32_t first, uint32_t n, const VkBuffer *buffers,
|
||||
const VkDeviceSize *offsets, const VkDeviceSize *sizes,
|
||||
const VkDeviceSize *strides)
|
||||
{
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
if (sizes) {
|
||||
VkDeviceSize *s = (VkDeviceSize *)sizes;
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
if (s[i] != VK_WHOLE_SIZE || !buffers[i]) continue;
|
||||
VkDeviceSize total = buffer_size(e, buffers[i]);
|
||||
s[i] = total > offsets[i] ? total - offsets[i] : 0;
|
||||
}
|
||||
}
|
||||
vkb_emu_real()->vkCmdBindVertexBuffers2(cb, first, n, buffers, offsets, sizes, strides);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ install */
|
||||
|
||||
void vkb_emu_install(vkb_srv_table *dev)
|
||||
{
|
||||
vkb_emu_device *e = dev->emu;
|
||||
vkb_dispatch *dt = &dev->dt;
|
||||
/* Resource records are needed by every emulation. */
|
||||
dt->vkCreateBuffer = emu_CreateBuffer;
|
||||
dt->vkDestroyBuffer = emu_DestroyBuffer;
|
||||
dt->vkCreateImage = emu_CreateImage;
|
||||
dt->vkDestroyImage = emu_DestroyImage;
|
||||
dt->vkCreateGraphicsPipelines = emu_CreateGraphicsPipelines;
|
||||
dt->vkCreateComputePipelines = emu_CreateComputePipelines;
|
||||
dt->vkCmdCopyImage = emu_CmdCopyImage;
|
||||
dt->vkCmdCopyBufferToImage = emu_CmdCopyBufferToImage;
|
||||
if (dev->real.vkCmdCopyImage2) dt->vkCmdCopyImage2 = emu_CmdCopyImage2;
|
||||
if (dev->real.vkCmdCopyBufferToImage2) dt->vkCmdCopyBufferToImage2 = emu_CmdCopyBufferToImage2;
|
||||
dt->vkCmdCopyImageToBuffer = emu_CmdCopyImageToBuffer;
|
||||
if (dev->real.vkCmdCopyImageToBuffer2) dt->vkCmdCopyImageToBuffer2 = emu_CmdCopyImageToBuffer2;
|
||||
if (e->flags & VKB_EMU_MAINT5) {
|
||||
dt->vkCreateBufferView = emu_CreateBufferView;
|
||||
dt->vkCmdBindIndexBuffer2 = emu_CmdBindIndexBuffer2;
|
||||
dt->vkGetRenderingAreaGranularity = emu_GetRenderingAreaGranularity;
|
||||
dt->vkGetImageSubresourceLayout2 = emu_GetImageSubresourceLayout2;
|
||||
dt->vkGetDeviceImageSubresourceLayout = emu_GetDeviceImageSubresourceLayout;
|
||||
dt->vkCmdBlitImage = emu_CmdBlitImage;
|
||||
dt->vkCmdResolveImage = emu_CmdResolveImage;
|
||||
if (dev->real.vkCmdBlitImage2) dt->vkCmdBlitImage2 = emu_CmdBlitImage2;
|
||||
if (dev->real.vkCmdResolveImage2) dt->vkCmdResolveImage2 = emu_CmdResolveImage2;
|
||||
if (dev->real.vkCmdBindVertexBuffers2) dt->vkCmdBindVertexBuffers2 = emu_CmdBindVertexBuffers2;
|
||||
}
|
||||
vkb_emu_install_features(dev);
|
||||
vkb_emu_depth_clip_install(dev);
|
||||
VKB_INFO("device %u: emulation 0x%x installed", dev->id, e->flags);
|
||||
}
|
||||
|
||||
void vkb_emu_uninstall(vkb_srv_table *dev)
|
||||
{
|
||||
vkb_emu_device *e = dev->emu;
|
||||
vkb_emu_uninstall_features(dev);
|
||||
for (int i = 0; i < VKB_EMU_BUCKETS; i++) {
|
||||
while (e->images[i]) {
|
||||
vkb_emu_image *r = e->images[i];
|
||||
e->images[i] = r->next;
|
||||
vkb_emu_image_release(e, r);
|
||||
free(r);
|
||||
}
|
||||
while (e->buffers[i]) {
|
||||
vkb_emu_buffer *r = e->buffers[i];
|
||||
e->buffers[i] = r->next;
|
||||
free(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ depth clip enable */
|
||||
|
||||
/* VK_EXT_depth_clip_enable on a GPU without it. Without the extension Vulkan clips exactly when
|
||||
* depth clamp is off, so a pipeline's clamp becomes "not clip" (D3D's DepthClipEnable = FALSE is
|
||||
* clamping; clip + clamp only differs for fragments the clip removes anyway). The client hides
|
||||
* dynamic depth clip/clamp while this is emulated, so both are static pipeline state here. */
|
||||
void vkb_emu_depth_clip_pipeline(vkb_emu_device *e, VkGraphicsPipelineCreateInfo *ci)
|
||||
{
|
||||
if (!(e->flags & VKB_EMU_DEPTH_CLIP)) return;
|
||||
VkPipelineRasterizationStateCreateInfo *rs = (VkPipelineRasterizationStateCreateInfo *)ci->pRasterizationState;
|
||||
if (!rs) return;
|
||||
VkPipelineRasterizationDepthClipStateCreateInfoEXT *dc =
|
||||
vkb_emu_chain_take(rs, VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_DEPTH_CLIP_STATE_CREATE_INFO_EXT);
|
||||
if (dc) rs->depthClampEnable = dc->depthClipEnable ? VK_FALSE : VK_TRUE;
|
||||
}
|
||||
|
||||
void vkb_emu_depth_clip_install(vkb_srv_table *dev)
|
||||
{
|
||||
(void)dev;
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Shader-side emulations, done when a graphics pipeline is created (both ends of the vertex ->
|
||||
* fragment interface are known then):
|
||||
*
|
||||
* clip distance (shaderClipDistance missing): the last pre-rasterization stage's ClipDistance
|
||||
* output becomes an ordinary varying at a free location; the fragment shader reads it and
|
||||
* discards the fragment when any distance is negative. For planar clip distances this is the
|
||||
* same set of pixels the hardware clipper would keep.
|
||||
* cull distance (shaderCullDistance missing): the output becomes an unread varying - nothing is
|
||||
* culled, which costs fragments but never drops geometry that should be visible.
|
||||
* point size in geometry/tessellation (shaderTessellationAndGeometryPointSize missing): same.
|
||||
*
|
||||
* Shader modules are kept as SPIR-V so a pipeline can build rewritten variants; the driver's own
|
||||
* module is created from a variant that needs none of the missing features.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "server_emu.h"
|
||||
#include "spirv_edit.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef struct shader_rec {
|
||||
VkShaderModule module;
|
||||
uint32_t *code;
|
||||
size_t size;
|
||||
struct shader_rec *next;
|
||||
} shader_rec;
|
||||
|
||||
typedef struct shader_state {
|
||||
pthread_mutex_t lock;
|
||||
shader_rec *mods[1024];
|
||||
} shader_state;
|
||||
|
||||
#define SHADER_EMU (VKB_EMU_CLIP_DISTANCE | VKB_EMU_CULL_DISTANCE | VKB_EMU_POINT_SIZE)
|
||||
/* Emulations that need the shader modules' SPIR-V kept. */
|
||||
#define SHADER_STORE (SHADER_EMU | VKB_EMU_DIVISOR)
|
||||
|
||||
static unsigned mh(VkShaderModule m)
|
||||
{
|
||||
uint64_t k = (uint64_t)m;
|
||||
k ^= k >> 31;
|
||||
k *= 0x94d049bb133111ebull;
|
||||
return (unsigned)(k >> 54);
|
||||
}
|
||||
|
||||
static shader_state *state(vkb_emu_device *e) { return e->shaders; }
|
||||
|
||||
static const shader_rec *find_module(vkb_emu_device *e, VkShaderModule m)
|
||||
{
|
||||
shader_state *s = state(e);
|
||||
pthread_mutex_lock(&s->lock);
|
||||
shader_rec *r = s->mods[mh(m)];
|
||||
while (r && r->module != m) r = r->next;
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ rewrites */
|
||||
|
||||
/* Finds a standalone Output/Input variable decorated with a builtin; 0 if none. 2 = in a block. */
|
||||
static uint32_t builtin_var(const spv_mod *m, uint32_t builtin, uint32_t storage, int *in_block)
|
||||
{
|
||||
*in_block = 0;
|
||||
for (uint32_t i = 0; i < m->count; i++) {
|
||||
const spv_inst *in = &m->ins[i];
|
||||
if (in->op == SpvOpDecorate && in->w[1] == SpvDecorationBuiltIn && in->w[2] == builtin) {
|
||||
int d = spv_find_def(m, in->w[0]);
|
||||
if (d >= 0 && m->ins[d].op == SpvOpVariable && m->ins[d].w[2] == storage) return in->w[0];
|
||||
}
|
||||
if (in->op == SpvOpMemberDecorate && in->w[2] == SpvDecorationBuiltIn && in->w[3] == builtin) *in_block = 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t var_pointee(const spv_mod *m, uint32_t var)
|
||||
{
|
||||
int d = spv_find_def(m, var);
|
||||
if (d < 0) return 0;
|
||||
int p = spv_find_def(m, m->ins[d].w[0]);
|
||||
return p < 0 ? 0 : m->ins[p].w[2];
|
||||
}
|
||||
|
||||
static void add_decoration(spv_mod *m, uint32_t id, uint32_t dec, uint32_t value)
|
||||
{
|
||||
uint32_t ops[3] = {id, dec, value};
|
||||
spv_insert(m, spv_annotations_end(m), SpvOpDecorate, ops, 3);
|
||||
}
|
||||
|
||||
/* Builtin output -> plain varying at *loc (advanced). Returns the element count, 0 if absent. */
|
||||
static uint32_t demote_builtin(spv_mod *m, uint32_t builtin, uint32_t *loc, int *unsupported)
|
||||
{
|
||||
int in_block;
|
||||
uint32_t var = builtin_var(m, builtin, SpvStorageClassOutput, &in_block);
|
||||
if (!var) {
|
||||
if (in_block) *unsupported = 1;
|
||||
return 0;
|
||||
}
|
||||
uint32_t pointee = var_pointee(m, var);
|
||||
uint32_t n = spv_array_length(m, pointee);
|
||||
if (!n) n = 1;
|
||||
spv_remove_decoration(m, var, SpvDecorationBuiltIn);
|
||||
add_decoration(m, var, SpvDecorationLocation, *loc);
|
||||
*loc += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
/* The pre-rasterization side. Returns the clip distance count (0: none), -1 if it cannot. */
|
||||
static int rewrite_pre_raster(spv_mod *m, uint32_t emu, uint32_t clip_loc, int last_stage)
|
||||
{
|
||||
int unsupported = 0;
|
||||
uint32_t loc = clip_loc;
|
||||
int clip = 0;
|
||||
if ((emu & VKB_EMU_CLIP_DISTANCE) && spv_has_capability(m, SpvCapabilityClipDistance)) {
|
||||
clip = (int)demote_builtin(m, SpvBuiltInClipDistance, &loc, &unsupported);
|
||||
spv_remove_capability(m, SpvCapabilityClipDistance);
|
||||
}
|
||||
if ((emu & VKB_EMU_CULL_DISTANCE) && spv_has_capability(m, SpvCapabilityCullDistance)) {
|
||||
demote_builtin(m, SpvBuiltInCullDistance, &loc, &unsupported);
|
||||
spv_remove_capability(m, SpvCapabilityCullDistance);
|
||||
}
|
||||
int model = spv_execution_model(m);
|
||||
if ((emu & VKB_EMU_POINT_SIZE) && (model == SpvExecutionModelGeometry || model == SpvExecutionModelTessellationEvaluation) &&
|
||||
(spv_has_capability(m, SpvCapabilityGeometryPointSize) || spv_has_capability(m, SpvCapabilityTessellationPointSize))) {
|
||||
demote_builtin(m, SpvBuiltInPointSize, &loc, &unsupported);
|
||||
spv_remove_capability(m, SpvCapabilityGeometryPointSize);
|
||||
spv_remove_capability(m, SpvCapabilityTessellationPointSize);
|
||||
}
|
||||
(void)last_stage;
|
||||
if (unsupported) VKB_ONCE(VKB_LOG_WARN, "emulation: clip/cull distance inside an output block is not handled (logged once)");
|
||||
return unsupported && !clip ? -1 : clip;
|
||||
}
|
||||
|
||||
/* The fragment side: read the distances at `loc` and discard when any is negative. */
|
||||
static int rewrite_fragment(spv_mod *m, uint32_t loc, uint32_t n)
|
||||
{
|
||||
uint32_t f32 = spv_type_float(m, 32);
|
||||
uint32_t len = spv_const_u32(m, n);
|
||||
uint32_t arr = spv_type_array(m, f32, len);
|
||||
uint32_t parr = spv_type_pointer(m, SpvStorageClassInput, arr);
|
||||
uint32_t pf = spv_type_pointer(m, SpvStorageClassInput, f32);
|
||||
uint32_t tbool = spv_type_bool(m);
|
||||
uint32_t zero = spv_const_f32(m, 0.0f);
|
||||
uint32_t idx[16];
|
||||
for (uint32_t i = 0; i < n && i < 16; i++) idx[i] = spv_const_u32(m, i);
|
||||
uint32_t var = spv_new_id(m);
|
||||
uint32_t vops[3] = {parr, var, SpvStorageClassInput};
|
||||
spv_insert(m, spv_types_end(m), SpvOpVariable, vops, 3);
|
||||
add_decoration(m, var, SpvDecorationLocation, loc);
|
||||
spv_add_interface(m, var);
|
||||
|
||||
int ep = spv_find_op(m, SpvOpEntryPoint, 0);
|
||||
if (ep < 0) return -1;
|
||||
uint32_t fn = m->ins[ep].w[1];
|
||||
int fi = -1;
|
||||
for (uint32_t i = 0; i < m->count; i++)
|
||||
if (m->ins[i].op == SpvOpFunction && m->ins[i].w[1] == fn) fi = (int)i;
|
||||
if (fi < 0) return -1;
|
||||
int li = spv_find_op(m, SpvOpLabel, (uint32_t)fi);
|
||||
if (li < 0) return -1;
|
||||
uint32_t first_label = m->ins[li].w[0];
|
||||
uint32_t at = (uint32_t)li + 1;
|
||||
while (at < m->count && (m->ins[at].op == SpvOpVariable || m->ins[at].op == SpvOpLine || m->ins[at].op == SpvOpNoLine)) at++;
|
||||
|
||||
uint32_t any = 0;
|
||||
for (uint32_t i = 0; i < n && i < 16; i++) {
|
||||
uint32_t p = spv_new_id(m), v = spv_new_id(m), lt = spv_new_id(m);
|
||||
uint32_t ac[4] = {pf, p, var, idx[i]};
|
||||
at = spv_insert(m, at, SpvOpAccessChain, ac, 4) + 1;
|
||||
uint32_t ld[3] = {f32, v, p};
|
||||
at = spv_insert(m, at, SpvOpLoad, ld, 3) + 1;
|
||||
uint32_t cmp[4] = {tbool, lt, v, zero};
|
||||
at = spv_insert(m, at, SpvOpFOrdLessThan, cmp, 4) + 1;
|
||||
if (any) {
|
||||
uint32_t o = spv_new_id(m), orr[4] = {tbool, o, any, lt};
|
||||
at = spv_insert(m, at, SpvOpLogicalOr, orr, 4) + 1;
|
||||
any = o;
|
||||
} else {
|
||||
any = lt;
|
||||
}
|
||||
}
|
||||
uint32_t kill = spv_new_id(m), merge = spv_new_id(m);
|
||||
uint32_t sm[2] = {merge, 0};
|
||||
at = spv_insert(m, at, SpvOpSelectionMerge, sm, 2) + 1;
|
||||
uint32_t bc[3] = {any, kill, merge};
|
||||
at = spv_insert(m, at, SpvOpBranchConditional, bc, 3) + 1;
|
||||
at = spv_insert(m, at, SpvOpLabel, &kill, 1) + 1;
|
||||
at = spv_insert(m, at, spv_version(m) >= 0x00010600 ? SpvOpTerminateInvocation : SpvOpKill, NULL, 0) + 1;
|
||||
at = spv_insert(m, at, SpvOpLabel, &merge, 1) + 1;
|
||||
/* Phis that named the old entry block as a predecessor now come from the merge block. */
|
||||
for (uint32_t i = at; i < m->count; i++) {
|
||||
spv_inst *in = &m->ins[i];
|
||||
if (in->op != 245 /* OpPhi */) continue;
|
||||
for (uint32_t k = 3; k < (uint32_t)in->n - 1; k += 2)
|
||||
if (in->w[k] == first_label) in->w[k] = merge;
|
||||
}
|
||||
return m->err ? -1 : 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ module store */
|
||||
|
||||
static VkResult make_module(VkDevice device, const spv_mod *m, VkShaderModule *out)
|
||||
{
|
||||
uint32_t *code;
|
||||
size_t size = spv_write(m, &code);
|
||||
if (!size) return VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
const char *dump = getenv("VKBRIDGE_DUMP_SPIRV");
|
||||
if (dump && *dump) {
|
||||
static int seq;
|
||||
char path[512];
|
||||
snprintf(path, sizeof(path), "%s/vkbridge-%d-%d.spv", dump, (int)getpid(), __atomic_fetch_add(&seq, 1, __ATOMIC_RELAXED));
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (f) {
|
||||
fwrite(code, 1, size, f);
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
VkShaderModuleCreateInfo ci = {VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, NULL, 0, size, code};
|
||||
VkResult r = vkb_emu_real()->vkCreateShaderModule(device, &ci, NULL, out);
|
||||
free(code);
|
||||
return r;
|
||||
}
|
||||
|
||||
static VKAPI_ATTR VkResult VKAPI_CALL emu_CreateShaderModule(VkDevice device, const VkShaderModuleCreateInfo *ci,
|
||||
const VkAllocationCallbacks *pAllocator, VkShaderModule *pModule)
|
||||
{
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
spv_mod m;
|
||||
VkResult r;
|
||||
int needs = 0;
|
||||
if (spv_parse(&m, ci->pCode, ci->codeSize) == 0) {
|
||||
needs = spv_has_capability(&m, SpvCapabilityClipDistance) || spv_has_capability(&m, SpvCapabilityCullDistance) ||
|
||||
spv_has_capability(&m, SpvCapabilityGeometryPointSize) || spv_has_capability(&m, SpvCapabilityTessellationPointSize);
|
||||
}
|
||||
if (needs) {
|
||||
/* The driver's module must not ask for what it lacks; pipelines use rewritten variants. */
|
||||
int maxo = spv_max_location(&m, SpvStorageClassOutput);
|
||||
rewrite_pre_raster(&m, e->flags, (uint32_t)(maxo + 1), 1);
|
||||
r = make_module(device, &m, pModule);
|
||||
} else {
|
||||
r = vkb_emu_real()->vkCreateShaderModule(device, ci, pAllocator, pModule);
|
||||
}
|
||||
spv_free(&m);
|
||||
if (r != VK_SUCCESS) return r;
|
||||
shader_rec *rec = calloc(1, sizeof(*rec));
|
||||
rec->module = *pModule;
|
||||
rec->size = ci->codeSize;
|
||||
rec->code = malloc(ci->codeSize);
|
||||
memcpy(rec->code, ci->pCode, ci->codeSize);
|
||||
shader_state *s = state(e);
|
||||
pthread_mutex_lock(&s->lock);
|
||||
unsigned h = mh(*pModule);
|
||||
rec->next = s->mods[h];
|
||||
s->mods[h] = rec;
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
static VKAPI_ATTR void VKAPI_CALL emu_DestroyShaderModule(VkDevice device, VkShaderModule module, const VkAllocationCallbacks *pAllocator)
|
||||
{
|
||||
vkb_emu_device *e = vkb_emu_cur();
|
||||
if (module) {
|
||||
shader_state *s = state(e);
|
||||
pthread_mutex_lock(&s->lock);
|
||||
shader_rec **pp = &s->mods[mh(module)];
|
||||
while (*pp && (*pp)->module != module) pp = &(*pp)->next;
|
||||
shader_rec *r = *pp;
|
||||
if (r) *pp = r->next;
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
if (r) {
|
||||
free(r->code);
|
||||
free(r);
|
||||
}
|
||||
}
|
||||
vkb_emu_real()->vkDestroyShaderModule(device, module, pAllocator);
|
||||
}
|
||||
|
||||
/* The SPIR-V of a stage: its module's (kept here) or the inline VkShaderModuleCreateInfo's. */
|
||||
static int stage_code(vkb_emu_device *e, const VkPipelineShaderStageCreateInfo *st, const uint32_t **code, size_t *size)
|
||||
{
|
||||
if (st->module) {
|
||||
const shader_rec *r = find_module(e, st->module);
|
||||
if (!r) return 0;
|
||||
*code = r->code;
|
||||
*size = r->size;
|
||||
return 1;
|
||||
}
|
||||
for (const VkBaseInStructure *b = st->pNext; b; b = b->pNext) {
|
||||
if (b->sType == VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO) {
|
||||
*code = ((const VkShaderModuleCreateInfo *)b)->pCode;
|
||||
*size = ((const VkShaderModuleCreateInfo *)b)->codeSize;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Replace a stage's shader by a rewritten module (recorded for destruction after the call). */
|
||||
static void set_stage_module(VkDevice device, VkPipelineShaderStageCreateInfo *st, const spv_mod *m, VkShaderModule *mods,
|
||||
uint32_t *nmods, uint32_t cap)
|
||||
{
|
||||
VkShaderModule mod;
|
||||
if (*nmods >= cap || make_module(device, m, &mod) != VK_SUCCESS) return;
|
||||
mods[(*nmods)++] = mod;
|
||||
st->module = mod;
|
||||
vkb_emu_chain_take(st, VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO);
|
||||
}
|
||||
|
||||
void vkb_emu_shader_pipeline(vkb_emu_device *e, VkDevice device, VkGraphicsPipelineCreateInfo *ci, VkShaderModule *mods,
|
||||
uint32_t *nmods, uint32_t cap)
|
||||
{
|
||||
if (!(e->flags & SHADER_EMU) || !ci->pStages) return;
|
||||
VkPipelineShaderStageCreateInfo *pre = NULL, *frag = NULL;
|
||||
int pre_rank = -1;
|
||||
for (uint32_t s = 0; s < ci->stageCount; s++) {
|
||||
VkPipelineShaderStageCreateInfo *st = (VkPipelineShaderStageCreateInfo *)&ci->pStages[s];
|
||||
int rank = st->stage == VK_SHADER_STAGE_VERTEX_BIT ? 0 : st->stage == VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT ? 1
|
||||
: st->stage == VK_SHADER_STAGE_GEOMETRY_BIT ? 2 : -1;
|
||||
if (rank > pre_rank) {
|
||||
pre_rank = rank;
|
||||
pre = st;
|
||||
}
|
||||
if (st->stage == VK_SHADER_STAGE_FRAGMENT_BIT) frag = st;
|
||||
}
|
||||
if (!pre) return;
|
||||
const uint32_t *code;
|
||||
size_t size;
|
||||
if (!stage_code(e, pre, &code, &size)) return;
|
||||
spv_mod pm;
|
||||
if (spv_parse(&pm, code, size) != 0) return;
|
||||
if (!spv_has_capability(&pm, SpvCapabilityClipDistance) && !spv_has_capability(&pm, SpvCapabilityCullDistance) &&
|
||||
!spv_has_capability(&pm, SpvCapabilityGeometryPointSize) && !spv_has_capability(&pm, SpvCapabilityTessellationPointSize)) {
|
||||
spv_free(&pm);
|
||||
return;
|
||||
}
|
||||
spv_mod fm;
|
||||
int have_frag = 0;
|
||||
const uint32_t *fcode;
|
||||
size_t fsize;
|
||||
if (frag && stage_code(e, frag, &fcode, &fsize) && spv_parse(&fm, fcode, fsize) == 0) have_frag = 1;
|
||||
int loc = spv_max_location(&pm, SpvStorageClassOutput);
|
||||
if (have_frag) {
|
||||
int fl = spv_max_location(&fm, SpvStorageClassInput);
|
||||
if (fl > loc) loc = fl;
|
||||
}
|
||||
int clip = rewrite_pre_raster(&pm, e->flags, (uint32_t)(loc + 1), 1);
|
||||
if (clip >= 0) set_stage_module(device, pre, &pm, mods, nmods, cap);
|
||||
if (clip > 0) {
|
||||
if (have_frag) {
|
||||
if (rewrite_fragment(&fm, (uint32_t)(loc + 1), (uint32_t)clip) == 0) set_stage_module(device, frag, &fm, mods, nmods, cap);
|
||||
} else {
|
||||
VKB_ONCE(VKB_LOG_WARN, "emulation: clip distances in a pipeline without a fragment shader are not applied (logged once)");
|
||||
}
|
||||
}
|
||||
VKB_DBG("emulation: pipeline with %d clip distance(s) rewritten at location %d", clip, loc + 1);
|
||||
spv_free(&pm);
|
||||
if (have_frag) spv_free(&fm);
|
||||
}
|
||||
|
||||
/* A vertex shader that reads BaseInstance as 0 (divisor emulation splits draws, which moves
|
||||
* BaseInstance; DXVK derives SV_InstanceID from it). VK_NULL_HANDLE when the shader never reads it. */
|
||||
VkShaderModule vkb_emu_shader_zero_base_instance(vkb_emu_device *e, VkDevice device, const VkPipelineShaderStageCreateInfo *vs)
|
||||
{
|
||||
const uint32_t *code;
|
||||
size_t size;
|
||||
if (!e->shaders || !stage_code(e, vs, &code, &size)) return VK_NULL_HANDLE;
|
||||
spv_mod m;
|
||||
if (spv_parse(&m, code, size) != 0) return VK_NULL_HANDLE;
|
||||
int in_block;
|
||||
uint32_t var = builtin_var(&m, SpvBuiltInBaseInstance, SpvStorageClassInput, &in_block);
|
||||
VkShaderModule out = VK_NULL_HANDLE;
|
||||
if (var) {
|
||||
int d = spv_find_def(&m, var);
|
||||
int p = d >= 0 ? spv_find_def(&m, m.ins[d].w[0]) : -1;
|
||||
uint32_t type = p >= 0 ? m.ins[p].w[2] : 0;
|
||||
int td = spv_find_def(&m, type);
|
||||
uint32_t zero = 0;
|
||||
if (td >= 0 && m.ins[td].op == SpvOpTypeInt) {
|
||||
zero = spv_new_id(&m);
|
||||
uint32_t ops[3] = {type, zero, 0};
|
||||
spv_insert(&m, spv_types_end(&m), SpvOpConstant, ops, 3);
|
||||
}
|
||||
int changed = 0;
|
||||
for (uint32_t i = 0; zero && i < m.count; i++) {
|
||||
spv_inst *in = &m.ins[i];
|
||||
if (in->op == SpvOpLoad && in->w[2] == var) {
|
||||
/* %r = OpLoad %int %BaseInstance -> %r = OpCopyObject %int %zero */
|
||||
in->op = 83; /* OpCopyObject */
|
||||
in->w[2] = zero;
|
||||
in->n = 4;
|
||||
changed = 1;
|
||||
}
|
||||
}
|
||||
if (changed) make_module(device, &m, &out);
|
||||
}
|
||||
spv_free(&m);
|
||||
return out;
|
||||
}
|
||||
|
||||
void vkb_emu_shader_install(vkb_srv_table *dev)
|
||||
{
|
||||
vkb_emu_device *e = dev->emu;
|
||||
if (!(e->flags & SHADER_STORE)) return;
|
||||
shader_state *s = calloc(1, sizeof(*s));
|
||||
pthread_mutex_init(&s->lock, NULL);
|
||||
e->shaders = s;
|
||||
dev->dt.vkCreateShaderModule = emu_CreateShaderModule;
|
||||
dev->dt.vkDestroyShaderModule = emu_DestroyShaderModule;
|
||||
}
|
||||
|
||||
void vkb_emu_shader_uninstall(vkb_srv_table *dev)
|
||||
{
|
||||
shader_state *s = dev->emu->shaders;
|
||||
if (!s) return;
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
while (s->mods[i]) {
|
||||
shader_rec *r = s->mods[i];
|
||||
s->mods[i] = r->next;
|
||||
free(r->code);
|
||||
free(r);
|
||||
}
|
||||
}
|
||||
pthread_mutex_destroy(&s->lock);
|
||||
free(s);
|
||||
dev->emu->shaders = NULL;
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Instances, devices and the startup self-test.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "vkb_server.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
vkb_pd_knowledge vkb_pds[VKB_MAX_PDS];
|
||||
int vkb_npds;
|
||||
|
||||
static int has_ext(const VkExtensionProperties *e, uint32_t n, const char *name)
|
||||
{
|
||||
for (uint32_t i = 0; i < n; i++)
|
||||
if (!strcmp(e[i].extensionName, name)) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const vkb_pd_knowledge *vkb_pd_lookup(const vkb_dispatch *dt, VkPhysicalDevice pd)
|
||||
{
|
||||
VkPhysicalDeviceIDProperties id = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES};
|
||||
VkPhysicalDeviceProperties2 p2 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &id};
|
||||
if (!dt->vkGetPhysicalDeviceProperties2) return NULL;
|
||||
dt->vkGetPhysicalDeviceProperties2(pd, &p2);
|
||||
for (int i = 0; i < vkb_npds; i++)
|
||||
if (vkb_pds[i].valid && !memcmp(vkb_pds[i].uuid, id.deviceUUID, VK_UUID_SIZE)) return &vkb_pds[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* What the self-test found, for a later session's log (the in-app server tests once). */
|
||||
void vkb_log_summary(void)
|
||||
{
|
||||
for (int i = 0; i < vkb_npds; i++) {
|
||||
const vkb_pd_knowledge *k = &vkb_pds[i];
|
||||
if (k->valid)
|
||||
VKB_INFO("%s: memory sharing = %s, missing 0x%x, emulating 0x%x (from the first session's self-test)", k->name,
|
||||
vkb_mem_strategy_name(k->info.strategy), k->info.missing, k->info.emu);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ startup self-test */
|
||||
|
||||
static void log_device(const vkb_dispatch *dt, VkPhysicalDevice pd, const VkPhysicalDeviceProperties *p)
|
||||
{
|
||||
VKB_INFO("GPU: %s (vendor 0x%04x device 0x%04x, Vulkan %u.%u.%u, driver 0x%08x)", p->deviceName,
|
||||
p->vendorID, p->deviceID, VK_API_VERSION_MAJOR(p->apiVersion), VK_API_VERSION_MINOR(p->apiVersion),
|
||||
VK_API_VERSION_PATCH(p->apiVersion), p->driverVersion);
|
||||
VkPhysicalDeviceFeatures f;
|
||||
dt->vkGetPhysicalDeviceFeatures(pd, &f);
|
||||
#define F(x) #x, f.x
|
||||
VKB_INFO("features: %s=%u %s=%u %s=%u %s=%u %s=%u %s=%u %s=%u %s=%u", F(textureCompressionBC), F(shaderClipDistance),
|
||||
F(shaderCullDistance), F(geometryShader), F(tessellationShader), F(depthClamp), F(shaderFloat64), F(shaderInt64));
|
||||
VKB_INFO("features: %s=%u %s=%u %s=%u %s=%u %s=%u %s=%u %s=%u %s=%u", F(multiViewport), F(independentBlend), F(logicOp),
|
||||
F(fillModeNonSolid), F(dualSrcBlend), F(sampleRateShading), F(depthBiasClamp), F(wideLines));
|
||||
VKB_INFO("features: %s=%u %s=%u %s=%u %s=%u %s=%u %s=%u %s=%u %s=%u", F(drawIndirectFirstInstance), F(multiDrawIndirect),
|
||||
F(occlusionQueryPrecise), F(pipelineStatisticsQuery), F(vertexPipelineStoresAndAtomics),
|
||||
F(fragmentStoresAndAtomics), F(shaderStorageImageWriteWithoutFormat), F(imageCubeArray));
|
||||
VKB_INFO("features: %s=%u %s=%u %s=%u %s=%u %s=%u %s=%u", F(robustBufferAccess), F(fullDrawIndexUint32), F(samplerAnisotropy),
|
||||
F(depthBounds), F(largePoints), F(shaderStorageImageExtendedFormats));
|
||||
#undef F
|
||||
uint32_t n = 0;
|
||||
dt->vkEnumerateDeviceExtensionProperties(pd, NULL, &n, NULL);
|
||||
VkExtensionProperties *e = calloc(n ? n : 1, sizeof(*e));
|
||||
dt->vkEnumerateDeviceExtensionProperties(pd, NULL, &n, e);
|
||||
static const char *const watch[] = {
|
||||
"VK_EXT_depth_clip_enable", "VK_EXT_vertex_attribute_divisor", "VK_KHR_vertex_attribute_divisor",
|
||||
"VK_EXT_external_memory_dma_buf", "VK_EXT_image_drm_format_modifier", "VK_KHR_external_memory_fd",
|
||||
"VK_EXT_external_memory_host", "VK_ANDROID_external_memory_android_hardware_buffer",
|
||||
"VK_KHR_external_semaphore_fd", "VK_KHR_external_fence_fd", "VK_EXT_queue_family_foreign",
|
||||
"VK_EXT_robustness2", "VK_EXT_transform_feedback", "VK_EXT_custom_border_color",
|
||||
"VK_EXT_mutable_descriptor_type", "VK_VALVE_mutable_descriptor_type", "VK_EXT_extended_dynamic_state3",
|
||||
"VK_EXT_graphics_pipeline_library", "VK_EXT_physical_device_drm", "VK_KHR_push_descriptor",
|
||||
"VK_EXT_descriptor_indexing", "VK_KHR_timeline_semaphore", "VK_EXT_memory_budget",
|
||||
"VK_KHR_maintenance5", "VK_EXT_line_rasterization", "VK_EXT_provoking_vertex",
|
||||
};
|
||||
char line[1024];
|
||||
size_t len = 0;
|
||||
line[0] = 0;
|
||||
for (size_t i = 0; i < sizeof(watch) / sizeof(watch[0]); i++) {
|
||||
int ok = has_ext(e, n, watch[i]);
|
||||
len += (size_t)snprintf(line + len, sizeof(line) - len, "%s%s%s", len ? " " : "", ok ? "+" : "-", watch[i] + 3);
|
||||
if (len > 700 || i + 1 == sizeof(watch) / sizeof(watch[0])) {
|
||||
VKB_INFO("extensions: %s", line);
|
||||
len = 0;
|
||||
line[0] = 0;
|
||||
}
|
||||
}
|
||||
VKB_INFO("%u device extensions in total", n);
|
||||
/* All of them, until the device runs show which ones the server's process sees (it is not an
|
||||
* app process, and saw fewer than the capability viewer on the same tablet). */
|
||||
len = 0;
|
||||
line[0] = 0;
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
len += (size_t)snprintf(line + len, sizeof(line) - len, "%s%s:%u", len ? " " : "", e[i].extensionName + 3, e[i].specVersion);
|
||||
if (len > 700 || i + 1 == n) {
|
||||
VKB_INFO(" all: %s", line);
|
||||
len = 0;
|
||||
line[0] = 0;
|
||||
}
|
||||
}
|
||||
free(e);
|
||||
VkPhysicalDeviceMemoryProperties mp;
|
||||
dt->vkGetPhysicalDeviceMemoryProperties(pd, &mp);
|
||||
for (uint32_t i = 0; i < mp.memoryHeapCount; i++)
|
||||
VKB_INFO("memory heap %u: %llu MB flags 0x%x", i, (unsigned long long)(mp.memoryHeaps[i].size >> 20), mp.memoryHeaps[i].flags);
|
||||
for (uint32_t i = 0; i < mp.memoryTypeCount; i++)
|
||||
VKB_INFO("memory type %u: heap %u flags 0x%x", i, mp.memoryTypes[i].heapIndex, mp.memoryTypes[i].propertyFlags);
|
||||
}
|
||||
|
||||
void vkb_selftest_all(void)
|
||||
{
|
||||
uint32_t api = VK_API_VERSION_1_0;
|
||||
if (vkb_global_dt.vkEnumerateInstanceVersion) vkb_global_dt.vkEnumerateInstanceVersion(&api);
|
||||
if (api > VK_API_VERSION_1_3) api = VK_API_VERSION_1_3;
|
||||
VkApplicationInfo app = {VK_STRUCTURE_TYPE_APPLICATION_INFO, NULL, "vkbridge-selftest", 1, "vkbridge", 1, api};
|
||||
VkInstanceCreateInfo ci = {VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, NULL, 0, &app, 0, NULL, 0, NULL};
|
||||
VkInstance inst;
|
||||
VkResult r = vkb_global_dt.vkCreateInstance(&ci, NULL, &inst);
|
||||
if (r != VK_SUCCESS) {
|
||||
VKB_ERR("self-test: vkCreateInstance failed (%d); the driver is unusable", r);
|
||||
return;
|
||||
}
|
||||
vkb_dispatch dt;
|
||||
vkb_dispatch_load_instance(&dt, vkb_gipa, inst);
|
||||
uint32_t n = 0;
|
||||
dt.vkEnumeratePhysicalDevices(inst, &n, NULL);
|
||||
if (n > VKB_MAX_PDS) n = VKB_MAX_PDS;
|
||||
VkPhysicalDevice pds[VKB_MAX_PDS];
|
||||
dt.vkEnumeratePhysicalDevices(inst, &n, pds);
|
||||
VKB_INFO("self-test: loader Vulkan %u.%u, %u physical device(s)", VK_API_VERSION_MAJOR(api), VK_API_VERSION_MINOR(api), n);
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
VkPhysicalDeviceIDProperties id = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES};
|
||||
VkPhysicalDeviceProperties2 p2 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &id};
|
||||
dt.vkGetPhysicalDeviceProperties2(pds[i], &p2);
|
||||
log_device(&dt, pds[i], &p2.properties);
|
||||
vkb_pd_knowledge *k = &vkb_pds[vkb_npds++];
|
||||
memcpy(k->uuid, id.deviceUUID, VK_UUID_SIZE);
|
||||
snprintf(k->name, sizeof(k->name), "%s", p2.properties.deviceName);
|
||||
k->info.real_api_version = p2.properties.apiVersion;
|
||||
vkb_mem_selftest(k, inst, &dt, pds[i]);
|
||||
k->info.emu_available = vkb_emu_detect(&dt, pds[i], &k->info.missing);
|
||||
k->info.emu = k->info.emu_available;
|
||||
const char *emu_env = getenv("VKBRIDGE_EMULATE");
|
||||
if (emu_env && !strcmp(emu_env, "0")) k->info.emu = 0;
|
||||
k->valid = 1;
|
||||
VKB_INFO("%s: memory sharing = %s, missing 0x%x, emulating 0x%x", k->name, vkb_mem_strategy_name(k->info.strategy),
|
||||
k->info.missing, k->info.emu);
|
||||
}
|
||||
dt.vkDestroyInstance(inst, NULL);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ instance */
|
||||
|
||||
void vkb_sv_vkCreateInstance(vkb_srv_call *c)
|
||||
{
|
||||
VkInstanceCreateInfo *ci = NULL;
|
||||
if (vkb_dec_u8(&c->d)) {
|
||||
ci = vkb_dec_alloc(&c->d, sizeof(*ci));
|
||||
if (ci) vkb_dec_VkInstanceCreateInfo(&c->d, ci);
|
||||
}
|
||||
uint8_t want_out = vkb_dec_u8(&c->d);
|
||||
if (c->d.err || !ci || !want_out) {
|
||||
vkb_srv_bad_message(c);
|
||||
return;
|
||||
}
|
||||
VkInstance inst = VK_NULL_HANDLE;
|
||||
VkResult r = vkb_global_dt.vkCreateInstance(ci, NULL, &inst);
|
||||
uint32_t id = 0;
|
||||
if (r == VK_SUCCESS) {
|
||||
vkb_srv_table *t = calloc(1, sizeof(*t));
|
||||
pthread_mutex_init(&t->lock, NULL);
|
||||
t->instance = inst;
|
||||
t->proc = c->conn->proc;
|
||||
t->api_version = ci->pApplicationInfo ? ci->pApplicationInfo->apiVersion : VK_API_VERSION_1_0;
|
||||
vkb_dispatch_load_instance(&t->real, vkb_gipa, inst);
|
||||
t->dt = t->real;
|
||||
id = vkb_table_register(t);
|
||||
if (!id) {
|
||||
t->real.vkDestroyInstance(inst, NULL);
|
||||
free(t);
|
||||
r = VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
inst = VK_NULL_HANDLE;
|
||||
} else {
|
||||
VKB_INFO("instance %u created (app \"%s\", engine \"%s\", Vulkan %u.%u)", id,
|
||||
ci->pApplicationInfo && ci->pApplicationInfo->pApplicationName ? ci->pApplicationInfo->pApplicationName : "",
|
||||
ci->pApplicationInfo && ci->pApplicationInfo->pEngineName ? ci->pApplicationInfo->pEngineName : "",
|
||||
VK_API_VERSION_MAJOR(t->api_version), VK_API_VERSION_MINOR(t->api_version));
|
||||
}
|
||||
} else {
|
||||
VKB_ERR("vkCreateInstance failed: %d", r);
|
||||
}
|
||||
vkb_enc_u32(&c->r, (uint32_t)r);
|
||||
vkb_enc_u64(&c->r, (uint64_t)(uintptr_t)inst);
|
||||
vkb_enc_u32(&c->r, id);
|
||||
}
|
||||
|
||||
void vkb_sv_vkDestroyInstance(vkb_srv_call *c)
|
||||
{
|
||||
VkInstance inst = (VkInstance)(uintptr_t)vkb_dec_u64(&c->d);
|
||||
vkb_srv_table *t = c->table;
|
||||
if (!t || t->is_device || t->instance != inst) {
|
||||
vkb_srv_bad_message(c);
|
||||
return;
|
||||
}
|
||||
vkb_table_unregister(t);
|
||||
t->real.vkDestroyInstance(inst, NULL);
|
||||
pthread_mutex_destroy(&t->lock);
|
||||
free(t);
|
||||
c->table = NULL;
|
||||
}
|
||||
|
||||
void vkb_sv_vkbQueryServer(vkb_srv_call *c)
|
||||
{
|
||||
VkPhysicalDevice pd = (VkPhysicalDevice)(uintptr_t)vkb_dec_u64(&c->d);
|
||||
if (c->d.err || !c->table) {
|
||||
vkb_srv_bad_message(c);
|
||||
return;
|
||||
}
|
||||
const vkb_pd_knowledge *k = vkb_pd_lookup(c->dt, pd);
|
||||
vkb_server_pd_info info;
|
||||
memset(&info, 0, sizeof(info));
|
||||
if (k) info = k->info;
|
||||
else VKB_WARN("query for a physical device the self-test did not see");
|
||||
vkb_enc_raw(&c->r, &info, sizeof(info));
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ device */
|
||||
|
||||
static int list_has(const char *const *l, uint32_t n, const char *s)
|
||||
{
|
||||
for (uint32_t i = 0; i < n; i++)
|
||||
if (l[i] && !strcmp(l[i], s)) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void vkb_sv_vkCreateDevice(vkb_srv_call *c)
|
||||
{
|
||||
VkPhysicalDevice pd = (VkPhysicalDevice)(uintptr_t)vkb_dec_u64(&c->d);
|
||||
VkDeviceCreateInfo *ci = NULL;
|
||||
if (vkb_dec_u8(&c->d)) {
|
||||
ci = vkb_dec_alloc(&c->d, sizeof(*ci));
|
||||
if (ci) vkb_dec_VkDeviceCreateInfo(&c->d, ci);
|
||||
}
|
||||
uint8_t want_out = vkb_dec_u8(&c->d);
|
||||
vkb_srv_table *inst = c->table;
|
||||
if (c->d.err || !ci || !want_out || !inst || inst->is_device) {
|
||||
vkb_srv_bad_message(c);
|
||||
return;
|
||||
}
|
||||
const vkb_pd_knowledge *k = vkb_pd_lookup(&inst->real, pd);
|
||||
uint32_t strategy = k ? k->info.strategy : VKB_MEM_NONE;
|
||||
uint32_t emu = k ? k->info.emu : 0;
|
||||
|
||||
/* Extensions the server itself needs (memory sharing), when the driver has them. */
|
||||
uint32_t avail_n = 0;
|
||||
inst->real.vkEnumerateDeviceExtensionProperties(pd, NULL, &avail_n, NULL);
|
||||
VkExtensionProperties *avail = vkb_dec_alloc(&c->d, (avail_n ? avail_n : 1) * sizeof(*avail));
|
||||
inst->real.vkEnumerateDeviceExtensionProperties(pd, NULL, &avail_n, avail);
|
||||
uint32_t extra_n = 0;
|
||||
const char *const *extra = vkb_mem_required_extensions(strategy, &extra_n);
|
||||
const char **exts = vkb_dec_alloc(&c->d, (ci->enabledExtensionCount + extra_n + 10) * sizeof(char *));
|
||||
uint32_t n = 0;
|
||||
for (uint32_t i = 0; i < ci->enabledExtensionCount; i++) {
|
||||
const char *e = ci->ppEnabledExtensionNames[i];
|
||||
if (!has_ext(avail, avail_n, e)) {
|
||||
VKB_DBG("device extension %s is emulated or client-side; not passed to the driver", e);
|
||||
continue;
|
||||
}
|
||||
exts[n++] = e;
|
||||
}
|
||||
for (uint32_t i = 0; i < extra_n; i++)
|
||||
if (has_ext(avail, avail_n, extra[i]) && !list_has(exts, n, extra[i])) exts[n++] = extra[i];
|
||||
/* What the client's presentation code uses on every device (dma-buf swapchains, sync fds). */
|
||||
static const char *const wsi_exts[] = {
|
||||
"VK_KHR_external_memory_fd", "VK_EXT_external_memory_dma_buf", "VK_KHR_image_format_list",
|
||||
"VK_EXT_image_drm_format_modifier", "VK_KHR_external_semaphore_fd", "VK_KHR_external_fence_fd",
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(wsi_exts) / sizeof(wsi_exts[0]); i++)
|
||||
if (has_ext(avail, avail_n, wsi_exts[i]) && !list_has(exts, n, wsi_exts[i])) exts[n++] = wsi_exts[i];
|
||||
/* The BC decoder binds its buffer and image with push descriptors. */
|
||||
if ((emu & VKB_EMU_BCN) && has_ext(avail, avail_n, "VK_KHR_push_descriptor") && !list_has(exts, n, "VK_KHR_push_descriptor"))
|
||||
exts[n++] = "VK_KHR_push_descriptor";
|
||||
ci->enabledExtensionCount = n;
|
||||
ci->ppEnabledExtensionNames = exts;
|
||||
ci->enabledLayerCount = 0;
|
||||
ci->ppEnabledLayerNames = NULL;
|
||||
|
||||
vkb_emu_device_create_info(inst, pd, emu, ci, c->d.arena);
|
||||
|
||||
VkDevice dev = VK_NULL_HANDLE;
|
||||
VkResult r = inst->real.vkCreateDevice(pd, ci, NULL, &dev);
|
||||
uint32_t id = 0;
|
||||
if (r == VK_SUCCESS) {
|
||||
vkb_srv_table *t = calloc(1, sizeof(*t));
|
||||
pthread_mutex_init(&t->lock, NULL);
|
||||
t->is_device = 1;
|
||||
t->proc = c->conn->proc;
|
||||
t->instance = inst->instance;
|
||||
t->device = dev;
|
||||
t->physical = pd;
|
||||
t->parent = inst;
|
||||
t->pd = k;
|
||||
t->api_version = inst->api_version;
|
||||
t->real = inst->real;
|
||||
PFN_vkGetDeviceProcAddr gdpa = (PFN_vkGetDeviceProcAddr)vkb_gipa(inst->instance, "vkGetDeviceProcAddr");
|
||||
vkb_dispatch_load_device(&t->real, gdpa, dev);
|
||||
t->dt = t->real;
|
||||
id = vkb_table_register(t);
|
||||
if (!id) {
|
||||
t->real.vkDestroyDevice(dev, NULL);
|
||||
free(t);
|
||||
dev = VK_NULL_HANDLE;
|
||||
r = VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
} else {
|
||||
vkb_emu_device_init(t, emu);
|
||||
vkb_pcache_device_init(t);
|
||||
vkb_dsmap_device_init(t);
|
||||
vkb_spvfix_device_init(t);
|
||||
VKB_INFO("device %u created on %s (%u extensions, memory sharing %s)", id, k ? k->name : "?", n,
|
||||
vkb_mem_strategy_name(strategy));
|
||||
for (uint32_t i = 0; i < n; i++) VKB_DBG(" device extension %s", exts[i]);
|
||||
}
|
||||
} else {
|
||||
VKB_ERR("vkCreateDevice failed: %d (%u extensions)", r, n);
|
||||
for (uint32_t i = 0; i < n; i++) VKB_ERR(" requested %s", exts[i]);
|
||||
}
|
||||
vkb_enc_u32(&c->r, (uint32_t)r);
|
||||
vkb_enc_u64(&c->r, (uint64_t)(uintptr_t)dev);
|
||||
vkb_enc_u32(&c->r, id);
|
||||
}
|
||||
|
||||
void vkb_sv_vkDestroyDevice(vkb_srv_call *c)
|
||||
{
|
||||
VkDevice dev = (VkDevice)(uintptr_t)vkb_dec_u64(&c->d);
|
||||
vkb_srv_table *t = c->table;
|
||||
if (!t || !t->is_device || t->device != dev) {
|
||||
vkb_srv_bad_message(c);
|
||||
return;
|
||||
}
|
||||
vkb_table_unregister(t);
|
||||
vkb_spvfix_device_destroy(t);
|
||||
vkb_dsmap_device_destroy(t);
|
||||
vkb_pcache_device_destroy(t);
|
||||
vkb_emu_device_destroy(t);
|
||||
vkb_mem_free_all(t);
|
||||
t->real.vkDestroyDevice(dev, NULL);
|
||||
VKB_INFO("device %u destroyed", t->id);
|
||||
pthread_mutex_destroy(&t->lock);
|
||||
free(t);
|
||||
c->table = NULL;
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* vkbridge server entry point.
|
||||
*
|
||||
* libvkbridge_server.so --socket <path> [--log <file>] [--vulkan <libvulkan>] [--cache-dir <dir>] [--selftest]
|
||||
*
|
||||
* Packaged as a "library" so Android extracts it into the native library directory, from where
|
||||
* the app executes it like proot. It loads the system Vulkan loader (on the device: the vendor's
|
||||
* Mali driver behind /system/lib64/libvulkan.so), runs a self-test that decides how mapped memory
|
||||
* is shared with the Linux side, then serves the session's Vulkan calls until it is killed or its
|
||||
* parent dies.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "vkb_server.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <android/log.h>
|
||||
#elif defined(__GLIBC__)
|
||||
#include <execinfo.h>
|
||||
#endif
|
||||
|
||||
PFN_vkGetInstanceProcAddr vkb_gipa;
|
||||
vkb_dispatch vkb_global_dt;
|
||||
int vkb_verbose;
|
||||
|
||||
static FILE *log_file;
|
||||
static int log_level = VKB_LOG_INFO;
|
||||
static pthread_mutex_t log_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
int vkb_log_level(void) { return log_level; }
|
||||
|
||||
void vkb_log(int level, const char *fmt, ...)
|
||||
{
|
||||
if (level > log_level) return;
|
||||
char msg[2048];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(msg, sizeof(msg), fmt, ap);
|
||||
va_end(ap);
|
||||
static const char *const tags[] = {"E", "W", "I", "D"};
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
struct tm tm;
|
||||
localtime_r(&ts.tv_sec, &tm);
|
||||
pthread_mutex_lock(&log_lock);
|
||||
FILE *f = log_file ? log_file : stderr;
|
||||
fprintf(f, "%02d:%02d:%02d.%03ld vkbridge-server %s %s\n", tm.tm_hour, tm.tm_min, tm.tm_sec,
|
||||
ts.tv_nsec / 1000000, tags[level & 3], msg);
|
||||
fflush(f);
|
||||
pthread_mutex_unlock(&log_lock);
|
||||
#ifdef __ANDROID__
|
||||
static const int prio[] = {ANDROID_LOG_ERROR, ANDROID_LOG_WARN, ANDROID_LOG_INFO, ANDROID_LOG_DEBUG};
|
||||
__android_log_write(prio[level & 3], "vkbridge", msg);
|
||||
#endif
|
||||
}
|
||||
|
||||
extern __thread const char *vkb_srv_current_cmd;
|
||||
|
||||
/* A crash is almost always the driver's: say in which call, then die as we would have. */
|
||||
__attribute__((unused)) static void on_crash(int sig)
|
||||
{
|
||||
char buf[256];
|
||||
const char *cmd = vkb_srv_current_cmd ? vkb_srv_current_cmd : "(no Vulkan call)";
|
||||
int n = snprintf(buf, sizeof(buf), "vkbridge-server E crashed with signal %d in %s\n", sig, cmd);
|
||||
if (log_file) {
|
||||
if (write(fileno(log_file), buf, (size_t)n) < 0) {}
|
||||
}
|
||||
if (write(2, buf, (size_t)n) < 0) {}
|
||||
#ifdef __ANDROID__
|
||||
__android_log_write(ANDROID_LOG_FATAL, "vkbridge", buf);
|
||||
#elif defined(__GLIBC__)
|
||||
void *frames[32];
|
||||
int nf = backtrace(frames, 32);
|
||||
backtrace_symbols_fd(frames, nf, 2);
|
||||
if (log_file) backtrace_symbols_fd(frames, nf, fileno(log_file));
|
||||
#endif
|
||||
signal(sig, SIG_DFL);
|
||||
raise(sig);
|
||||
}
|
||||
|
||||
static void *load_vulkan(const char *override)
|
||||
{
|
||||
const char *names[] = {
|
||||
override,
|
||||
#ifdef __ANDROID__
|
||||
"libvulkan.so",
|
||||
#else
|
||||
"libvulkan.so.1",
|
||||
"libvulkan.so",
|
||||
#endif
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i++) {
|
||||
if (!names[i]) continue;
|
||||
void *h = dlopen(names[i], RTLD_NOW | RTLD_LOCAL);
|
||||
if (h) {
|
||||
VKB_INFO("Vulkan loader: %s", names[i]);
|
||||
return h;
|
||||
}
|
||||
VKB_WARN("cannot open %s: %s", names[i], dlerror());
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int listen_on(const char *path)
|
||||
{
|
||||
struct sockaddr_un a;
|
||||
if (strlen(path) >= sizeof(a.sun_path)) {
|
||||
VKB_ERR("socket path too long (%zu bytes): %s", strlen(path), path);
|
||||
return -1;
|
||||
}
|
||||
int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
if (fd < 0) {
|
||||
VKB_ERR("socket: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
memset(&a, 0, sizeof(a));
|
||||
a.sun_family = AF_UNIX;
|
||||
strcpy(a.sun_path, path);
|
||||
unlink(path);
|
||||
if (bind(fd, (struct sockaddr *)&a, sizeof(a)) < 0) {
|
||||
VKB_ERR("bind %s: %s", path, strerror(errno));
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
chmod(path, 0600);
|
||||
if (listen(fd, 128) < 0) {
|
||||
VKB_ERR("listen: %s", strerror(errno));
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
/* Opens (or switches to) the log file; NULL keeps logging to stderr/logcat only. */
|
||||
static void open_log(const char *path)
|
||||
{
|
||||
FILE *f = path ? fopen(path, "ae") : NULL;
|
||||
if (path && !f) fprintf(stderr, "vkbridge-server: cannot open log %s: %s\n", path, strerror(errno));
|
||||
pthread_mutex_lock(&log_lock);
|
||||
FILE *old = log_file;
|
||||
log_file = f;
|
||||
pthread_mutex_unlock(&log_lock);
|
||||
if (old) fclose(old);
|
||||
}
|
||||
|
||||
/* Loads the Vulkan loader and runs the self-test; 0 when there is nothing to serve. */
|
||||
static int server_init(const char *vk_lib, const char *cache_dir)
|
||||
{
|
||||
VKB_INFO("starting (pid %d, protocol %016llx)", getpid(), (unsigned long long)VKB_PROTOCOL_HASH);
|
||||
void *lib = load_vulkan(vk_lib);
|
||||
if (!lib) {
|
||||
VKB_ERR("no Vulkan loader; nothing to serve");
|
||||
return 0;
|
||||
}
|
||||
vkb_gipa = (PFN_vkGetInstanceProcAddr)dlsym(lib, "vkGetInstanceProcAddr");
|
||||
if (!vkb_gipa) {
|
||||
VKB_ERR("the Vulkan loader exports no vkGetInstanceProcAddr");
|
||||
return 0;
|
||||
}
|
||||
vkb_dispatch_load_instance(&vkb_global_dt, vkb_gipa, VK_NULL_HANDLE);
|
||||
vkb_pcache_set_dir(cache_dir);
|
||||
vkb_selftest_all();
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef VKB_JNI
|
||||
/*
|
||||
* In the app's own process (the default on the device): Android chooses the GPU driver, Samsung's
|
||||
* updatable one included, while the app starts up, and only for the app's process - a separate
|
||||
* executable gets the plain /vendor driver, which on the Tab S10+ lacked VK_EXT_robustness2 and
|
||||
* more that the app itself sees. Winlator's Vortek runs its server in-process for the same reason.
|
||||
* Started once and kept for the app's life; each session only points the log at its own folder.
|
||||
*/
|
||||
#include <jni.h>
|
||||
|
||||
static pthread_mutex_t jni_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static char jni_socket[256];
|
||||
static int jni_state; /* 0 not started, 1 serving, -1 failed */
|
||||
|
||||
static void *serve_thread(void *arg)
|
||||
{
|
||||
vkb_srv_serve((int)(intptr_t)arg);
|
||||
VKB_ERR("in-app server stopped serving");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *jstr(JNIEnv *env, jstring s)
|
||||
{
|
||||
if (!s) return NULL;
|
||||
const char *c = (*env)->GetStringUTFChars(env, s, NULL);
|
||||
char *r = c ? strdup(c) : NULL;
|
||||
if (c) (*env)->ReleaseStringUTFChars(env, s, c);
|
||||
return r;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL Java_com_droiddeck_launcher_gpu_VkBridgeNative_nativeStart(JNIEnv *env, jclass cls, jstring jsocket,
|
||||
jstring jlog, jstring jcache)
|
||||
{
|
||||
(void)cls;
|
||||
char *sock = jstr(env, jsocket), *logp = jstr(env, jlog), *cache = jstr(env, jcache);
|
||||
pthread_mutex_lock(&jni_lock);
|
||||
open_log(logp);
|
||||
if (getenv("VKBRIDGE_DEBUG")) {
|
||||
vkb_verbose = 1;
|
||||
log_level = VKB_LOG_DEBUG;
|
||||
}
|
||||
int ok = 0;
|
||||
if (jni_state == 1 && sock && !strcmp(sock, jni_socket) && access(sock, F_OK) == 0) {
|
||||
VKB_INFO("in-app server already serving at %s (pid %d); this session's log continues here", sock, getpid());
|
||||
vkb_log_summary();
|
||||
ok = 1;
|
||||
} else if (jni_state == 1) {
|
||||
VKB_ERR("in-app server serves %s, asked for %s (or its socket is gone)", jni_socket, sock ? sock : "(none)");
|
||||
} else if (jni_state == 0 && sock) {
|
||||
jni_state = -1;
|
||||
if (server_init(NULL, cache)) {
|
||||
int lfd = listen_on(sock);
|
||||
pthread_t t;
|
||||
if (lfd >= 0 && pthread_create(&t, NULL, serve_thread, (void *)(intptr_t)lfd) == 0) {
|
||||
pthread_detach(t);
|
||||
snprintf(jni_socket, sizeof(jni_socket), "%s", sock);
|
||||
jni_state = 1;
|
||||
ok = 1;
|
||||
VKB_INFO("listening on %s (in the app's process)", sock);
|
||||
}
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&jni_lock);
|
||||
free(sock);
|
||||
free(logp);
|
||||
free(cache);
|
||||
return ok;
|
||||
}
|
||||
#else
|
||||
static void usage(void)
|
||||
{
|
||||
fprintf(stderr, "usage: vkbridge-server --socket PATH [--log FILE] [--vulkan LIB] [--cache-dir DIR] [--selftest] [--verbose]\n");
|
||||
}
|
||||
|
||||
static pid_t parent_pid;
|
||||
|
||||
static void *parent_watch(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
for (;;) {
|
||||
sleep(1);
|
||||
if (getppid() != parent_pid) {
|
||||
VKB_INFO("the app (pid %d) is gone; exiting", (int)parent_pid);
|
||||
_exit(0);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void start_parent_watch(void)
|
||||
{
|
||||
parent_pid = getppid();
|
||||
if (parent_pid <= 1) return; /* started by init or a shell that already left: nothing to watch */
|
||||
pthread_t t;
|
||||
pthread_attr_t a;
|
||||
pthread_attr_init(&a);
|
||||
pthread_attr_setdetachstate(&a, PTHREAD_CREATE_DETACHED);
|
||||
pthread_create(&t, &a, parent_watch, NULL);
|
||||
pthread_attr_destroy(&a);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *sock_path = getenv("VKBRIDGE_SOCKET");
|
||||
const char *log_path = getenv("VKBRIDGE_SERVER_LOG");
|
||||
const char *vk_lib = getenv("VKBRIDGE_SERVER_VULKAN");
|
||||
const char *cache_dir = getenv("VKBRIDGE_CACHE_DIR");
|
||||
int selftest_only = 0;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (!strcmp(argv[i], "--socket") && i + 1 < argc) sock_path = argv[++i];
|
||||
else if (!strcmp(argv[i], "--log") && i + 1 < argc) log_path = argv[++i];
|
||||
else if (!strcmp(argv[i], "--vulkan") && i + 1 < argc) vk_lib = argv[++i];
|
||||
else if (!strcmp(argv[i], "--cache-dir") && i + 1 < argc) cache_dir = argv[++i];
|
||||
else if (!strcmp(argv[i], "--selftest")) selftest_only = 1;
|
||||
else if (!strcmp(argv[i], "--verbose")) vkb_verbose = 1;
|
||||
else {
|
||||
usage();
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
if (getenv("VKBRIDGE_DEBUG")) vkb_verbose = 1;
|
||||
if (vkb_verbose) log_level = VKB_LOG_DEBUG;
|
||||
open_log(log_path);
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
signal(SIGSEGV, on_crash);
|
||||
signal(SIGBUS, on_crash);
|
||||
signal(SIGABRT, on_crash);
|
||||
signal(SIGILL, on_crash);
|
||||
signal(SIGFPE, on_crash);
|
||||
/* The server is the app's child: when the app dies, so does the session's GPU. Not with
|
||||
* PR_SET_PDEATHSIG: that fires when the parent THREAD exits, and Android's ProcessBuilder forks
|
||||
* from the session's start-up thread, which ends right after starting the guest (the server
|
||||
* was killed 6 ms into the first device run). The parent process is watched instead. */
|
||||
start_parent_watch();
|
||||
|
||||
if (!server_init(vk_lib, cache_dir)) return 1;
|
||||
if (selftest_only) return vkb_npds > 0 ? 0 : 1;
|
||||
|
||||
if (!sock_path) {
|
||||
usage();
|
||||
return 2;
|
||||
}
|
||||
int lfd = listen_on(sock_path);
|
||||
if (lfd < 0) return 1;
|
||||
VKB_INFO("listening on %s", sock_path);
|
||||
vkb_srv_serve(lfd);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,834 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Host-visible memory shared with the client.
|
||||
*
|
||||
* A Linux program maps Vulkan memory and writes to it directly; those writes must land in the
|
||||
* pages the GPU reads. The server makes such memory shareable in one of three ways, chosen per
|
||||
* GPU by a self-test at startup that checks the GPU and both CPU mappings really see the same
|
||||
* bytes:
|
||||
* hostptr the client creates a memfd, the server maps it and imports it
|
||||
* (VK_EXT_external_memory_host);
|
||||
* dmabuf the server allocates exportable memory and hands the client its dma-buf
|
||||
* (VK_EXT_external_memory_dma_buf), which the client maps;
|
||||
* ahb the server allocates a BLOB AHardwareBuffer, imports it, and hands the client the
|
||||
* dma-buf behind it (Android only);
|
||||
* dmaheap the server allocates from /dev/dma_heap (system-uncached, else system), imports that
|
||||
* dma-buf into the driver and hands it to the client - what GameNative's wrapper does on
|
||||
* Mali when the driver will not export one (Android; root-only on desktops).
|
||||
* VKBRIDGE_MEMORY=hostptr|dmabuf|dmaheap|ahb forces one.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#ifdef __ANDROID__
|
||||
#define VK_USE_PLATFORM_ANDROID_KHR 1
|
||||
#endif
|
||||
#include "vkb_server.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <linux/dma-heap.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <vulkan/vulkan_android.h>
|
||||
/* libnativewindow, opened at run time (getNativeHandle is not in the NDK headers). */
|
||||
typedef struct {
|
||||
uint32_t width, height, layers, format;
|
||||
uint64_t usage;
|
||||
uint32_t stride, rfu0;
|
||||
uint64_t rfu1;
|
||||
} vkb_ahb_desc;
|
||||
typedef struct {
|
||||
int version, numFds, numInts;
|
||||
int data[0];
|
||||
} vkb_native_handle;
|
||||
static int (*p_AHardwareBuffer_allocate)(const vkb_ahb_desc *, struct AHardwareBuffer **);
|
||||
static void (*p_AHardwareBuffer_release)(struct AHardwareBuffer *);
|
||||
static const vkb_native_handle *(*p_AHardwareBuffer_getNativeHandle)(const struct AHardwareBuffer *);
|
||||
#define AHB_FORMAT_BLOB 0x21
|
||||
#define AHB_USAGE_CPU_READ_OFTEN 3ull
|
||||
#define AHB_USAGE_CPU_WRITE_OFTEN (3ull << 4)
|
||||
#define AHB_USAGE_GPU_DATA_BUFFER (1ull << 24)
|
||||
|
||||
static int ahb_load(void)
|
||||
{
|
||||
static int state;
|
||||
if (state) return state > 0;
|
||||
void *h = dlopen("libnativewindow.so", RTLD_NOW);
|
||||
if (!h) h = dlopen("libandroid.so", RTLD_NOW);
|
||||
if (h) {
|
||||
p_AHardwareBuffer_allocate = dlsym(h, "AHardwareBuffer_allocate");
|
||||
p_AHardwareBuffer_release = dlsym(h, "AHardwareBuffer_release");
|
||||
p_AHardwareBuffer_getNativeHandle = dlsym(h, "AHardwareBuffer_getNativeHandle");
|
||||
}
|
||||
state = (p_AHardwareBuffer_allocate && p_AHardwareBuffer_release && p_AHardwareBuffer_getNativeHandle) ? 1 : -1;
|
||||
if (state < 0) VKB_WARN("AHardwareBuffer functions unavailable");
|
||||
return state > 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const char *const ext_hostptr[] = {"VK_KHR_external_memory", "VK_EXT_external_memory_host"};
|
||||
static const char *const ext_dmabuf[] = {"VK_KHR_external_memory", "VK_KHR_external_memory_fd", "VK_EXT_external_memory_dma_buf"};
|
||||
#ifdef __ANDROID__
|
||||
static const char *const ext_ahb[] = {"VK_KHR_external_memory", "VK_ANDROID_external_memory_android_hardware_buffer",
|
||||
"VK_EXT_queue_family_foreign", "VK_KHR_sampler_ycbcr_conversion",
|
||||
"VK_KHR_dedicated_allocation", "VK_KHR_get_memory_requirements2"};
|
||||
#endif
|
||||
|
||||
const char *const *vkb_mem_required_extensions(uint32_t strategy, uint32_t *count)
|
||||
{
|
||||
switch (strategy) {
|
||||
case VKB_MEM_HOSTPTR: *count = 2; return ext_hostptr;
|
||||
case VKB_MEM_DMABUF: *count = 3; return ext_dmabuf;
|
||||
case VKB_MEM_DMAHEAP: *count = 3; return ext_dmabuf;
|
||||
#ifdef __ANDROID__
|
||||
case VKB_MEM_AHB: *count = 6; return ext_ahb;
|
||||
#endif
|
||||
default: *count = 0; return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t strategy_handle_type(uint32_t s)
|
||||
{
|
||||
switch (s) {
|
||||
case VKB_MEM_HOSTPTR: return VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT;
|
||||
case VKB_MEM_DMABUF: return VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT;
|
||||
case VKB_MEM_DMAHEAP: return VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT;
|
||||
#ifdef __ANDROID__
|
||||
case VKB_MEM_AHB: return VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID;
|
||||
#endif
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int vkb_memfd(const char *name, size_t size)
|
||||
{
|
||||
int fd = (int)syscall(SYS_memfd_create, name, 1u /* MFD_CLOEXEC */);
|
||||
if (fd < 0) return -1;
|
||||
if (ftruncate(fd, (off_t)size) < 0) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ tracking */
|
||||
|
||||
static unsigned mem_hash(VkDeviceMemory m)
|
||||
{
|
||||
uint64_t v = (uint64_t)(uintptr_t)m;
|
||||
v ^= v >> 17;
|
||||
v *= 0x9E3779B97F4A7C15ull;
|
||||
return (unsigned)(v >> 56);
|
||||
}
|
||||
|
||||
void vkb_mem_track(vkb_srv_table *dev, vkb_srv_mem *m)
|
||||
{
|
||||
pthread_mutex_lock(&dev->lock);
|
||||
unsigned h = mem_hash(m->mem);
|
||||
m->next = dev->mems[h];
|
||||
dev->mems[h] = m;
|
||||
pthread_mutex_unlock(&dev->lock);
|
||||
}
|
||||
|
||||
vkb_srv_mem *vkb_mem_find(vkb_srv_table *dev, VkDeviceMemory mem, int remove)
|
||||
{
|
||||
pthread_mutex_lock(&dev->lock);
|
||||
vkb_srv_mem **pp = &dev->mems[mem_hash(mem)];
|
||||
while (*pp && (*pp)->mem != mem) pp = &(*pp)->next;
|
||||
vkb_srv_mem *m = *pp;
|
||||
if (m && remove) *pp = m->next;
|
||||
pthread_mutex_unlock(&dev->lock);
|
||||
return m;
|
||||
}
|
||||
|
||||
/* Frees the server-side resources of an allocation (the VkDeviceMemory must already be freed). */
|
||||
static void mem_release_backing(vkb_srv_mem *m)
|
||||
{
|
||||
if (m->host_ptr) munmap(m->host_ptr, m->host_len);
|
||||
#ifdef __ANDROID__
|
||||
if (m->ahb) p_AHardwareBuffer_release(m->ahb);
|
||||
#endif
|
||||
free(m);
|
||||
}
|
||||
|
||||
void vkb_mem_release(vkb_srv_table *dev, vkb_srv_mem *m)
|
||||
{
|
||||
if (m->mapped) dev->real.vkUnmapMemory(dev->device, m->mem);
|
||||
dev->real.vkFreeMemory(dev->device, m->mem, NULL);
|
||||
mem_release_backing(m);
|
||||
}
|
||||
|
||||
void vkb_mem_free_all(vkb_srv_table *dev)
|
||||
{
|
||||
for (int i = 0; i < 256; i++) {
|
||||
vkb_srv_mem *m = dev->mems[i];
|
||||
dev->mems[i] = NULL;
|
||||
while (m) {
|
||||
vkb_srv_mem *n = m->next;
|
||||
vkb_mem_release(dev, m);
|
||||
m = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The dma-heap the dmaheap strategy allocates from, opened once: system-uncached first (the GPU
|
||||
* and the client both write through it without cache maintenance), else the cached system heap. */
|
||||
static int dmaheap_fd(void)
|
||||
{
|
||||
static int fd = -2;
|
||||
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
pthread_mutex_lock(&lock);
|
||||
if (fd == -2) {
|
||||
const char *names[] = {"/dev/dma_heap/system-uncached", "/dev/dma_heap/system"};
|
||||
if (getenv("VKBRIDGE_DMAHEAP_CACHED")) names[0] = "/dev/dma_heap/system";
|
||||
fd = -1;
|
||||
for (int i = 0; i < 2 && fd < 0; i++) {
|
||||
fd = open(names[i], O_RDONLY | O_CLOEXEC);
|
||||
if (fd >= 0) VKB_INFO("dmaheap: allocating from %s", names[i]);
|
||||
else VKB_INFO("dmaheap: %s: %s", names[i], strerror(errno));
|
||||
}
|
||||
}
|
||||
int r = fd;
|
||||
pthread_mutex_unlock(&lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Unlinks every structure of type t from the chain starting at *head. */
|
||||
static void chain_drop(const void **head, VkStructureType t)
|
||||
{
|
||||
const VkBaseInStructure **pp = (const VkBaseInStructure **)head;
|
||||
while (*pp) {
|
||||
if ((*pp)->sType == t) *pp = (*pp)->pNext;
|
||||
else pp = (const VkBaseInStructure **)&((VkBaseInStructure *)*pp)->pNext;
|
||||
}
|
||||
}
|
||||
|
||||
/* The driver's vkGetDeviceProcAddr (for entry points the dispatch table does not carry). */
|
||||
PFN_vkGetDeviceProcAddr vkb_mem_gdpa;
|
||||
|
||||
/* ------------------------------------------------------------------ allocation */
|
||||
|
||||
/*
|
||||
* Allocates shared memory with strategy `s`. `ai` is the app's (or the self-test's) allocate
|
||||
* info; for hostptr, `client_fd` is the client's memfd (consumed). On success *out_fd is the fd
|
||||
* to send to the client (-1 for hostptr, where the client already has its pages).
|
||||
*/
|
||||
static VkResult alloc_shared(const vkb_dispatch *dt, VkDevice dev, uint32_t s, const VkMemoryAllocateInfo *ai,
|
||||
int client_fd, vkb_srv_mem **out, int *out_fd)
|
||||
{
|
||||
*out_fd = -1;
|
||||
*out = NULL;
|
||||
vkb_srv_mem *m = calloc(1, sizeof(*m));
|
||||
if (!m) {
|
||||
if (client_fd >= 0) close(client_fd);
|
||||
return VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
}
|
||||
m->size = ai->allocationSize;
|
||||
m->type = ai->memoryTypeIndex;
|
||||
m->strategy = s;
|
||||
VkMemoryAllocateInfo info = *ai;
|
||||
VkResult r = VK_ERROR_FEATURE_NOT_PRESENT;
|
||||
switch (s) {
|
||||
case VKB_MEM_HOSTPTR: {
|
||||
if (client_fd < 0) break;
|
||||
void *p = mmap(NULL, (size_t)ai->allocationSize, PROT_READ | PROT_WRITE, MAP_SHARED, client_fd, 0);
|
||||
close(client_fd);
|
||||
client_fd = -1;
|
||||
if (p == MAP_FAILED) {
|
||||
VKB_ERR("hostptr: mmap of the client's memory failed: %s", strerror(errno));
|
||||
r = VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
break;
|
||||
}
|
||||
m->host_ptr = p;
|
||||
m->host_len = (size_t)ai->allocationSize;
|
||||
VkMemoryHostPointerPropertiesEXT hp = {VK_STRUCTURE_TYPE_MEMORY_HOST_POINTER_PROPERTIES_EXT};
|
||||
if (!dt->vkGetMemoryHostPointerPropertiesEXT ||
|
||||
dt->vkGetMemoryHostPointerPropertiesEXT(dev, VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, p, &hp) != VK_SUCCESS ||
|
||||
!(hp.memoryTypeBits & (1u << ai->memoryTypeIndex))) {
|
||||
r = VK_ERROR_INVALID_EXTERNAL_HANDLE;
|
||||
break;
|
||||
}
|
||||
VkImportMemoryHostPointerInfoEXT imp = {VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT, info.pNext,
|
||||
VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, p};
|
||||
info.pNext = &imp;
|
||||
r = dt->vkAllocateMemory(dev, &info, NULL, &m->mem);
|
||||
break;
|
||||
}
|
||||
case VKB_MEM_DMABUF: {
|
||||
VkExportMemoryAllocateInfo exp = {VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO, info.pNext,
|
||||
VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT};
|
||||
/* An export the app asked for itself is merged rather than duplicated. */
|
||||
for (VkBaseOutStructure *b = (VkBaseOutStructure *)info.pNext; b; b = b->pNext) {
|
||||
if (b->sType == VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO) {
|
||||
((VkExportMemoryAllocateInfo *)b)->handleTypes |= VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT;
|
||||
exp.sType = VK_STRUCTURE_TYPE_MAX_ENUM;
|
||||
}
|
||||
}
|
||||
if (exp.sType != VK_STRUCTURE_TYPE_MAX_ENUM) info.pNext = &exp;
|
||||
r = dt->vkAllocateMemory(dev, &info, NULL, &m->mem);
|
||||
if (r != VK_SUCCESS) {
|
||||
VKB_INFO("dmabuf: exportable allocation of %llu bytes (type %u) failed: %d", (unsigned long long)info.allocationSize,
|
||||
info.memoryTypeIndex, r);
|
||||
break;
|
||||
}
|
||||
VkMemoryGetFdInfoKHR gi = {VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR, NULL, m->mem, VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT};
|
||||
int fd = -1;
|
||||
VkResult fr = dt->vkGetMemoryFdKHR ? dt->vkGetMemoryFdKHR(dev, &gi, &fd) : VK_ERROR_EXTENSION_NOT_PRESENT;
|
||||
if (fr != VK_SUCCESS || fd < 0) {
|
||||
VKB_INFO("dmabuf: vkGetMemoryFdKHR %s: %d (fd %d)", dt->vkGetMemoryFdKHR ? "failed" : "not loaded", fr, fd);
|
||||
dt->vkFreeMemory(dev, m->mem, NULL);
|
||||
m->mem = VK_NULL_HANDLE;
|
||||
r = VK_ERROR_INVALID_EXTERNAL_HANDLE;
|
||||
break;
|
||||
}
|
||||
*out_fd = fd;
|
||||
break;
|
||||
}
|
||||
case VKB_MEM_DMAHEAP: {
|
||||
int hfd = dmaheap_fd();
|
||||
if (hfd < 0) break;
|
||||
uint64_t len = (ai->allocationSize + 4095) & ~4095ull;
|
||||
struct dma_heap_allocation_data hd = {len, 0, O_RDWR | O_CLOEXEC, 0};
|
||||
if (ioctl(hfd, DMA_HEAP_IOCTL_ALLOC, &hd) < 0) {
|
||||
VKB_INFO("dmaheap: allocating %llu bytes failed: %s", (unsigned long long)len, strerror(errno));
|
||||
r = VK_ERROR_OUT_OF_DEVICE_MEMORY;
|
||||
break;
|
||||
}
|
||||
int fd = (int)hd.fd;
|
||||
VkMemoryFdPropertiesKHR fp = {VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR};
|
||||
VkResult pr = dt->vkGetMemoryFdPropertiesKHR
|
||||
? dt->vkGetMemoryFdPropertiesKHR(dev, VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, fd, &fp)
|
||||
: VK_ERROR_EXTENSION_NOT_PRESENT;
|
||||
if (pr != VK_SUCCESS || !(fp.memoryTypeBits & (1u << ai->memoryTypeIndex))) {
|
||||
VKB_INFO("dmaheap: fd properties %d, memory types 0x%x (want type %u)", pr, fp.memoryTypeBits, ai->memoryTypeIndex);
|
||||
close(fd);
|
||||
r = VK_ERROR_INVALID_EXTERNAL_HANDLE;
|
||||
break;
|
||||
}
|
||||
/* The import takes its own fd; ours goes to the client. A dedicated-allocation struct
|
||||
* with an imported dma-buf crashes some Mali drivers (GameNative mesa PR #8): dropped. */
|
||||
int imp_fd = fcntl(fd, F_DUPFD_CLOEXEC, 0);
|
||||
VkImportMemoryFdInfoKHR imp = {VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR, info.pNext,
|
||||
VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, imp_fd};
|
||||
chain_drop(&imp.pNext, VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO);
|
||||
chain_drop(&imp.pNext, VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO);
|
||||
info.pNext = &imp;
|
||||
info.allocationSize = len;
|
||||
r = imp_fd >= 0 ? dt->vkAllocateMemory(dev, &info, NULL, &m->mem) : VK_ERROR_TOO_MANY_OBJECTS;
|
||||
if (r != VK_SUCCESS) {
|
||||
VKB_INFO("dmaheap: import of %llu bytes failed: %d", (unsigned long long)len, r);
|
||||
if (imp_fd >= 0) close(imp_fd); /* a failed import leaves the fd with us */
|
||||
close(fd);
|
||||
break;
|
||||
}
|
||||
*out_fd = fd;
|
||||
break;
|
||||
}
|
||||
#ifdef __ANDROID__
|
||||
case VKB_MEM_AHB: {
|
||||
if (!ahb_load()) break;
|
||||
/* Through the device's own vkGetDeviceProcAddr: Android's loader answers NULL for it from
|
||||
* vkGetInstanceProcAddr(NULL, ...), which desktop loaders allow (the first device run). */
|
||||
PFN_vkGetAndroidHardwareBufferPropertiesANDROID get_props = NULL;
|
||||
if (vkb_mem_gdpa)
|
||||
get_props = (PFN_vkGetAndroidHardwareBufferPropertiesANDROID)vkb_mem_gdpa(dev, "vkGetAndroidHardwareBufferPropertiesANDROID");
|
||||
if (!get_props) {
|
||||
VKB_INFO("ahb: vkGetAndroidHardwareBufferPropertiesANDROID not found");
|
||||
break;
|
||||
}
|
||||
uint64_t sz = ai->allocationSize;
|
||||
if (sz > 0xFFFFFFFFull) {
|
||||
r = VK_ERROR_OUT_OF_DEVICE_MEMORY;
|
||||
break;
|
||||
}
|
||||
/* CPU usage decides how gralloc caches the pages; VKBRIDGE_AHB_RARELY=1 asks for uncached. */
|
||||
uint64_t cpu = getenv("VKBRIDGE_AHB_RARELY") ? (2ull | (2ull << 4)) : (AHB_USAGE_CPU_READ_OFTEN | AHB_USAGE_CPU_WRITE_OFTEN);
|
||||
vkb_ahb_desc d = {(uint32_t)sz, 1, 1, AHB_FORMAT_BLOB, cpu | AHB_USAGE_GPU_DATA_BUFFER, 0, 0, 0};
|
||||
struct AHardwareBuffer *ahb = NULL;
|
||||
int ar = p_AHardwareBuffer_allocate(&d, &ahb);
|
||||
if (ar != 0 || !ahb) {
|
||||
VKB_INFO("ahb: AHardwareBuffer_allocate(%u bytes) failed: %d", (unsigned)sz, ar);
|
||||
r = VK_ERROR_OUT_OF_DEVICE_MEMORY;
|
||||
break;
|
||||
}
|
||||
m->ahb = ahb;
|
||||
VkAndroidHardwareBufferPropertiesANDROID props = {VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID};
|
||||
VkResult pr = get_props(dev, ahb, &props);
|
||||
if (pr != VK_SUCCESS || !(props.memoryTypeBits & (1u << ai->memoryTypeIndex))) {
|
||||
VKB_INFO("ahb: properties %d, memory types 0x%x (want type %u)", pr, props.memoryTypeBits, ai->memoryTypeIndex);
|
||||
r = VK_ERROR_INVALID_EXTERNAL_HANDLE;
|
||||
break;
|
||||
}
|
||||
VkImportAndroidHardwareBufferInfoANDROID imp = {VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID, info.pNext, ahb};
|
||||
info.pNext = &imp;
|
||||
info.allocationSize = props.allocationSize;
|
||||
r = dt->vkAllocateMemory(dev, &info, NULL, &m->mem);
|
||||
if (r != VK_SUCCESS) {
|
||||
VKB_INFO("ahb: import of %llu bytes failed: %d", (unsigned long long)info.allocationSize, r);
|
||||
break;
|
||||
}
|
||||
const vkb_native_handle *nh = p_AHardwareBuffer_getNativeHandle(ahb);
|
||||
if (!nh || nh->numFds < 1) {
|
||||
VKB_INFO("ahb: no fd in the native handle (%d)", nh ? nh->numFds : -1);
|
||||
dt->vkFreeMemory(dev, m->mem, NULL);
|
||||
m->mem = VK_NULL_HANDLE;
|
||||
r = VK_ERROR_INVALID_EXTERNAL_HANDLE;
|
||||
break;
|
||||
}
|
||||
/* Mali's gralloc can put more than one fd in the handle (metadata beside the pages): the
|
||||
* memory is the one at least as large as the allocation. */
|
||||
int pick = -1;
|
||||
for (int i = 0; i < nh->numFds && pick < 0; i++) {
|
||||
off_t end = lseek(nh->data[i], 0, SEEK_END);
|
||||
if (end >= (off_t)sz) pick = nh->data[i];
|
||||
if (nh->numFds > 1) VKB_INFO("ahb: handle fd %d of %d is %lld bytes", i + 1, nh->numFds, (long long)end);
|
||||
}
|
||||
if (pick < 0) {
|
||||
VKB_INFO("ahb: no fd in the handle covers %llu bytes", (unsigned long long)sz);
|
||||
dt->vkFreeMemory(dev, m->mem, NULL);
|
||||
m->mem = VK_NULL_HANDLE;
|
||||
r = VK_ERROR_INVALID_EXTERNAL_HANDLE;
|
||||
break;
|
||||
}
|
||||
*out_fd = fcntl(pick, F_DUPFD_CLOEXEC, 0);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (client_fd >= 0) close(client_fd);
|
||||
if (r != VK_SUCCESS) {
|
||||
mem_release_backing(m);
|
||||
return r;
|
||||
}
|
||||
*out = m;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ handlers */
|
||||
|
||||
static void close_import_fds(const void *pNext)
|
||||
{
|
||||
for (const VkBaseInStructure *b = pNext; b; b = b->pNext)
|
||||
if (b->sType == VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR) vkb_close_fd(((const VkImportMemoryFdInfoKHR *)b)->fd);
|
||||
}
|
||||
|
||||
void vkb_sv_vkAllocateMemory(vkb_srv_call *c)
|
||||
{
|
||||
VkDevice dev = (VkDevice)(uintptr_t)vkb_dec_u64(&c->d);
|
||||
VkMemoryAllocateInfo ai;
|
||||
int have = vkb_dec_u8(&c->d);
|
||||
if (have) vkb_dec_VkMemoryAllocateInfo(&c->d, &ai);
|
||||
uint32_t mode = vkb_dec_u32(&c->d);
|
||||
int client_fd = vkb_dec_fd(&c->d);
|
||||
vkb_srv_table *t = c->table;
|
||||
if (c->d.err || !have || !t || !t->is_device || t->device != dev) {
|
||||
if (client_fd >= 0) close(client_fd);
|
||||
vkb_srv_bad_message(c);
|
||||
return;
|
||||
}
|
||||
VkResult r;
|
||||
vkb_srv_mem *m = NULL;
|
||||
int out_fd = -1;
|
||||
if (mode == VKB_ALLOC_SHARED) {
|
||||
uint32_t s = t->pd ? t->pd->info.strategy : VKB_MEM_NONE;
|
||||
r = alloc_shared(c->dt, dev, s, &ai, client_fd, &m, &out_fd);
|
||||
if (r != VK_SUCCESS) VKB_WARN("shared allocation of %llu bytes (type %u, %s) failed: %d",
|
||||
(unsigned long long)ai.allocationSize, ai.memoryTypeIndex, vkb_mem_strategy_name(s), r);
|
||||
} else {
|
||||
if (client_fd >= 0) close(client_fd);
|
||||
m = calloc(1, sizeof(*m));
|
||||
m->size = ai.allocationSize;
|
||||
m->type = ai.memoryTypeIndex;
|
||||
r = c->dt->vkAllocateMemory(dev, &ai, NULL, &m->mem);
|
||||
if (r != VK_SUCCESS) {
|
||||
close_import_fds(ai.pNext);
|
||||
free(m);
|
||||
m = NULL;
|
||||
}
|
||||
}
|
||||
if (m) vkb_mem_track(t, m);
|
||||
vkb_enc_u32(&c->r, (uint32_t)r);
|
||||
vkb_enc_u64(&c->r, m ? (uint64_t)(uintptr_t)m->mem : 0);
|
||||
vkb_enc_fd(&c->r, out_fd);
|
||||
if (out_fd >= 0) vkb_srv_close_after_send(c, out_fd);
|
||||
vkb_enc_u64(&c->r, m ? (uint64_t)m->size : 0);
|
||||
}
|
||||
|
||||
void vkb_sv_vkFreeMemory(vkb_srv_call *c)
|
||||
{
|
||||
VkDevice dev = (VkDevice)(uintptr_t)vkb_dec_u64(&c->d);
|
||||
VkDeviceMemory mem;
|
||||
vkb_dec_bytes(&c->d, &mem, sizeof(mem));
|
||||
vkb_srv_table *t = c->table;
|
||||
if (c->d.err || !t || !t->is_device || t->device != dev) {
|
||||
vkb_srv_bad_message(c);
|
||||
return;
|
||||
}
|
||||
if (!mem) return;
|
||||
vkb_srv_mem *m = vkb_mem_find(t, mem, 1);
|
||||
if (m) {
|
||||
vkb_mem_release(t, m);
|
||||
} else {
|
||||
VKB_WARN("vkFreeMemory of untracked memory");
|
||||
c->dt->vkFreeMemory(dev, mem, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static VkResult ensure_server_map(vkb_srv_table *t, vkb_srv_mem *m)
|
||||
{
|
||||
if (m->mapped) return VK_SUCCESS;
|
||||
pthread_mutex_lock(&t->lock);
|
||||
VkResult r = VK_SUCCESS;
|
||||
if (!m->mapped) r = t->real.vkMapMemory(t->device, m->mem, 0, VK_WHOLE_SIZE, 0, &m->mapped);
|
||||
pthread_mutex_unlock(&t->lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
static void flush_or_invalidate(vkb_srv_call *c, int flush)
|
||||
{
|
||||
VkDevice dev = (VkDevice)(uintptr_t)vkb_dec_u64(&c->d);
|
||||
uint32_t n = vkb_dec_u32(&c->d);
|
||||
vkb_srv_table *t = c->table;
|
||||
if (c->d.err || !t || !t->is_device || t->device != dev || n > 65536) {
|
||||
vkb_srv_bad_message(c);
|
||||
return;
|
||||
}
|
||||
VkMappedMemoryRange *ranges = vkb_dec_alloc(&c->d, (n ? n : 1) * sizeof(*ranges));
|
||||
for (uint32_t i = 0; ranges && i < n; i++) vkb_dec_VkMappedMemoryRange(&c->d, &ranges[i]);
|
||||
if (c->d.err) {
|
||||
vkb_srv_bad_message(c);
|
||||
return;
|
||||
}
|
||||
VkResult r = VK_SUCCESS;
|
||||
for (uint32_t i = 0; i < n && r == VK_SUCCESS; i++) {
|
||||
vkb_srv_mem *m = vkb_mem_find(t, ranges[i].memory, 0);
|
||||
if (m) r = ensure_server_map(t, m);
|
||||
}
|
||||
if (r == VK_SUCCESS && n) {
|
||||
r = flush ? c->dt->vkFlushMappedMemoryRanges(dev, n, ranges) : c->dt->vkInvalidateMappedMemoryRanges(dev, n, ranges);
|
||||
}
|
||||
vkb_enc_u32(&c->r, (uint32_t)r);
|
||||
}
|
||||
|
||||
void vkb_sv_vkFlushMappedMemoryRanges(vkb_srv_call *c) { flush_or_invalidate(c, 1); }
|
||||
void vkb_sv_vkInvalidateMappedMemoryRanges(vkb_srv_call *c) { flush_or_invalidate(c, 0); }
|
||||
|
||||
/* ------------------------------------------------------------------ self-test */
|
||||
|
||||
typedef struct {
|
||||
const vkb_dispatch *idt;
|
||||
vkb_dispatch dt;
|
||||
VkPhysicalDevice pd;
|
||||
VkDevice dev;
|
||||
VkQueue queue;
|
||||
VkCommandPool pool;
|
||||
uint32_t qf;
|
||||
VkPhysicalDeviceMemoryProperties mp;
|
||||
VkDeviceSize import_align;
|
||||
} selftest;
|
||||
|
||||
static int submit_and_wait(selftest *st, VkCommandBuffer cb)
|
||||
{
|
||||
VkFenceCreateInfo fci = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
|
||||
VkFence f;
|
||||
if (st->dt.vkCreateFence(st->dev, &fci, NULL, &f) != VK_SUCCESS) return 0;
|
||||
VkSubmitInfo si = {VK_STRUCTURE_TYPE_SUBMIT_INFO, NULL, 0, NULL, NULL, 1, &cb, 0, NULL};
|
||||
int ok = st->dt.vkQueueSubmit(st->queue, 1, &si, f) == VK_SUCCESS &&
|
||||
st->dt.vkWaitForFences(st->dev, 1, &f, VK_TRUE, 5000000000ull) == VK_SUCCESS;
|
||||
st->dt.vkDestroyFence(st->dev, f, NULL);
|
||||
return ok;
|
||||
}
|
||||
|
||||
#define TEST_SIZE (256u * 1024u)
|
||||
|
||||
/* One allocation of `type` with strategy `s`, checked from both sides. 1 = shared and coherent
|
||||
* (no flush needed), 2 = shared when flushed, 0 = not shared. */
|
||||
static int test_type_mode(selftest *st, uint32_t s, uint32_t type, int no_flush);
|
||||
|
||||
/* Without flushes first: imported pages from an uncached heap are coherent in practice even where
|
||||
* the driver's type does not say HOST_COHERENT (Mali-G720: dma-heap imports land in type 1,
|
||||
* HOST_CACHED without COHERENT, and pass with no flush). Only if that fails, with flushes. */
|
||||
static int test_type(selftest *st, uint32_t s, uint32_t type)
|
||||
{
|
||||
int coherent_flag = (st->mp.memoryTypes[type].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
|
||||
if (!coherent_flag && !getenv("VKBRIDGE_SELFTEST_NOFLUSH")) {
|
||||
int r = test_type_mode(st, s, type, 1);
|
||||
if (r) {
|
||||
VKB_INFO(" %s, type %u: coherent in practice (no flushes needed)", vkb_mem_strategy_name(s), type);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return test_type_mode(st, s, type, 0);
|
||||
}
|
||||
|
||||
static int test_type_mode(selftest *st, uint32_t s, uint32_t type, int no_flush)
|
||||
{
|
||||
const vkb_dispatch *dt = &st->dt;
|
||||
int result = 0;
|
||||
VkExternalMemoryBufferCreateInfo ext = {VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO, NULL, strategy_handle_type(s)};
|
||||
VkBufferCreateInfo bci = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, &ext, 0, TEST_SIZE,
|
||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
|
||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
|
||||
VK_SHARING_MODE_EXCLUSIVE, 0, NULL};
|
||||
VkBuffer buf = VK_NULL_HANDLE;
|
||||
if (dt->vkCreateBuffer(st->dev, &bci, NULL, &buf) != VK_SUCCESS) return 0;
|
||||
VkMemoryRequirements req;
|
||||
dt->vkGetBufferMemoryRequirements(st->dev, buf, &req);
|
||||
if (!(req.memoryTypeBits & (1u << type))) {
|
||||
dt->vkDestroyBuffer(st->dev, buf, NULL);
|
||||
return 0;
|
||||
}
|
||||
VkDeviceSize size = req.size;
|
||||
if (s == VKB_MEM_HOSTPTR) size = (size + st->import_align - 1) & ~(st->import_align - 1);
|
||||
VkMemoryAllocateInfo ai = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, size, type};
|
||||
int cfd = -1;
|
||||
if (s == VKB_MEM_HOSTPTR) {
|
||||
cfd = vkb_memfd("vkbridge-selftest", (size_t)size);
|
||||
if (cfd < 0) {
|
||||
dt->vkDestroyBuffer(st->dev, buf, NULL);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
int keep_fd = s == VKB_MEM_HOSTPTR ? fcntl(cfd, F_DUPFD_CLOEXEC, 0) : -1;
|
||||
vkb_srv_mem *m = NULL;
|
||||
int out_fd = -1;
|
||||
VkResult r = alloc_shared(dt, st->dev, s, &ai, cfd, &m, &out_fd);
|
||||
if (r != VK_SUCCESS) {
|
||||
VKB_INFO(" %s, type %u: allocation failed (%d)", vkb_mem_strategy_name(s), type, r);
|
||||
if (keep_fd >= 0) close(keep_fd);
|
||||
dt->vkDestroyBuffer(st->dev, buf, NULL);
|
||||
return 0;
|
||||
}
|
||||
int map_fd = s == VKB_MEM_HOSTPTR ? keep_fd : out_fd;
|
||||
uint8_t *cmap = mmap(NULL, (size_t)m->size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
|
||||
if (map_fd >= 0) close(map_fd);
|
||||
void *smap = NULL;
|
||||
if (cmap == MAP_FAILED) {
|
||||
VKB_INFO(" %s, type %u: the client cannot map the memory (%s)", vkb_mem_strategy_name(s), type, strerror(errno));
|
||||
cmap = NULL;
|
||||
goto out;
|
||||
}
|
||||
if (dt->vkBindBufferMemory(st->dev, buf, m->mem, 0) != VK_SUCCESS ||
|
||||
dt->vkMapMemory(st->dev, m->mem, 0, VK_WHOLE_SIZE, 0, &smap) != VK_SUCCESS) {
|
||||
VKB_INFO(" %s, type %u: bind/map failed", vkb_mem_strategy_name(s), type);
|
||||
goto out;
|
||||
}
|
||||
int coherent = (st->mp.memoryTypes[type].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
|
||||
/* VKBRIDGE_SELFTEST_NOFLUSH=1 forces it for every type. */
|
||||
if (no_flush || getenv("VKBRIDGE_SELFTEST_NOFLUSH")) coherent = 1;
|
||||
VkMappedMemoryRange whole = {VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, NULL, m->mem, 0, VK_WHOLE_SIZE};
|
||||
|
||||
/* 1: the two CPU mappings are the same pages. */
|
||||
for (uint32_t i = 0; i < TEST_SIZE / 4; i += 997) ((volatile uint32_t *)cmap)[i] = 0x11220000u + i;
|
||||
__sync_synchronize();
|
||||
for (uint32_t i = 0; i < TEST_SIZE / 4; i += 997) {
|
||||
if (((volatile uint32_t *)smap)[i] != 0x11220000u + i) {
|
||||
VKB_INFO(" %s, type %u: CPU mappings differ at word %u", vkb_mem_strategy_name(s), type, i);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
/* 2: GPU writes, client reads. 3: client writes, GPU copies, client reads the copy. */
|
||||
for (uint32_t i = TEST_SIZE / 8; i < TEST_SIZE / 8 + TEST_SIZE / 32; i++) ((volatile uint32_t *)cmap)[i] = 0x5a000000u ^ (i * 2654435761u);
|
||||
if (!coherent) dt->vkFlushMappedMemoryRanges(st->dev, 1, &whole);
|
||||
VkCommandBufferAllocateInfo cai = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, NULL, st->pool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1};
|
||||
VkCommandBuffer cb;
|
||||
if (dt->vkAllocateCommandBuffers(st->dev, &cai, &cb) != VK_SUCCESS) goto out;
|
||||
VkCommandBufferBeginInfo bi = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, NULL};
|
||||
dt->vkBeginCommandBuffer(cb, &bi);
|
||||
dt->vkCmdFillBuffer(cb, buf, 0, TEST_SIZE / 8, 0xA5A5A5A5u);
|
||||
/* Layout (bytes): [0, N/8) GPU fill; [N/2, N/2 + N/8) written by the client and copied by
|
||||
* the GPU to [3N/4, 3N/4 + N/8). */
|
||||
VkBufferCopy region = {TEST_SIZE / 2, TEST_SIZE / 4 * 3, TEST_SIZE / 8};
|
||||
dt->vkCmdCopyBuffer(cb, buf, buf, 1, ®ion);
|
||||
VkMemoryBarrier mb = {VK_STRUCTURE_TYPE_MEMORY_BARRIER, NULL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT};
|
||||
dt->vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 1, &mb, 0, NULL, 0, NULL);
|
||||
dt->vkEndCommandBuffer(cb);
|
||||
int submitted = submit_and_wait(st, cb);
|
||||
dt->vkFreeCommandBuffers(st->dev, st->pool, 1, &cb);
|
||||
if (!submitted) {
|
||||
VKB_INFO(" %s, type %u: GPU submission failed", vkb_mem_strategy_name(s), type);
|
||||
goto out;
|
||||
}
|
||||
if (!coherent) dt->vkInvalidateMappedMemoryRanges(st->dev, 1, &whole);
|
||||
for (uint32_t i = 0; i < TEST_SIZE / 32; i++) {
|
||||
if (((volatile uint32_t *)cmap)[i] != 0xA5A5A5A5u) {
|
||||
VKB_INFO(" %s, type %u: the client does not see a GPU write (word %u = 0x%08x)", vkb_mem_strategy_name(s), type, i,
|
||||
((volatile uint32_t *)cmap)[i]);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < TEST_SIZE / 32; i++) {
|
||||
uint32_t want = 0x5a000000u ^ ((TEST_SIZE / 8 + i) * 2654435761u);
|
||||
uint32_t got = ((volatile uint32_t *)cmap)[TEST_SIZE / 16 * 3 + i];
|
||||
if (got != want) {
|
||||
VKB_INFO(" %s, type %u: the GPU does not see a client write (word %u = 0x%08x, want 0x%08x)", vkb_mem_strategy_name(s),
|
||||
type, i, got, want);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
result = coherent ? 1 : 2;
|
||||
VKB_INFO(" %s, type %u (flags 0x%x): shared OK%s", vkb_mem_strategy_name(s), type, st->mp.memoryTypes[type].propertyFlags,
|
||||
coherent ? ", coherent" : ", needs flushes");
|
||||
out:
|
||||
if (cmap) munmap(cmap, (size_t)m->size);
|
||||
if (smap) dt->vkUnmapMemory(st->dev, m->mem);
|
||||
dt->vkDestroyBuffer(st->dev, buf, NULL);
|
||||
dt->vkFreeMemory(st->dev, m->mem, NULL);
|
||||
mem_release_backing(m);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int has_ext(const VkExtensionProperties *e, uint32_t n, const char *name)
|
||||
{
|
||||
for (uint32_t i = 0; i < n; i++)
|
||||
if (!strcmp(e[i].extensionName, name)) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* What the driver says it can do with each kind of external buffer memory (the device runs). */
|
||||
static void log_external_props(const vkb_dispatch *idt, VkPhysicalDevice pd)
|
||||
{
|
||||
if (!idt->vkGetPhysicalDeviceExternalBufferProperties) return;
|
||||
static const struct { uint32_t bit; const char *name; } types[] = {
|
||||
{VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT, "opaque fd"},
|
||||
{VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, "dma-buf"},
|
||||
{VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, "host allocation"},
|
||||
#ifdef __ANDROID__
|
||||
{VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID, "AHB"},
|
||||
#endif
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(types) / sizeof(types[0]); i++) {
|
||||
VkPhysicalDeviceExternalBufferInfo bi = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_BUFFER_INFO, NULL, 0,
|
||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
|
||||
(VkExternalMemoryHandleTypeFlagBits)types[i].bit};
|
||||
VkExternalBufferProperties ep = {VK_STRUCTURE_TYPE_EXTERNAL_BUFFER_PROPERTIES};
|
||||
idt->vkGetPhysicalDeviceExternalBufferProperties(pd, &bi, &ep);
|
||||
const VkExternalMemoryProperties *p = &ep.externalMemoryProperties;
|
||||
VKB_INFO("external buffers, %s: features 0x%x (%s%s%s), export from 0x%x, compatible 0x%x", types[i].name,
|
||||
p->externalMemoryFeatures,
|
||||
p->externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_DEDICATED_ONLY_BIT ? "dedicated-only " : "",
|
||||
p->externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT ? "exportable " : "",
|
||||
p->externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT ? "importable" : "",
|
||||
p->exportFromImportedHandleTypes, p->compatibleHandleTypes);
|
||||
}
|
||||
}
|
||||
|
||||
void vkb_mem_selftest(vkb_pd_knowledge *k, VkInstance inst, const vkb_dispatch *idt, VkPhysicalDevice pd)
|
||||
{
|
||||
selftest st;
|
||||
memset(&st, 0, sizeof(st));
|
||||
st.idt = idt;
|
||||
st.pd = pd;
|
||||
idt->vkGetPhysicalDeviceMemoryProperties(pd, &st.mp);
|
||||
|
||||
uint32_t nq = 0;
|
||||
idt->vkGetPhysicalDeviceQueueFamilyProperties(pd, &nq, NULL);
|
||||
VkQueueFamilyProperties qp[16];
|
||||
if (nq > 16) nq = 16;
|
||||
idt->vkGetPhysicalDeviceQueueFamilyProperties(pd, &nq, qp);
|
||||
st.qf = 0;
|
||||
for (uint32_t i = 0; i < nq; i++)
|
||||
if (qp[i].queueFlags & (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT)) {
|
||||
st.qf = i;
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t ne = 0;
|
||||
idt->vkEnumerateDeviceExtensionProperties(pd, NULL, &ne, NULL);
|
||||
VkExtensionProperties *ex = calloc(ne ? ne : 1, sizeof(*ex));
|
||||
idt->vkEnumerateDeviceExtensionProperties(pd, NULL, &ne, ex);
|
||||
|
||||
uint32_t candidates[4];
|
||||
int nc = 0;
|
||||
const char *force = getenv("VKBRIDGE_MEMORY");
|
||||
/* GameNative's order on Mali: the driver's own export, a dma-heap import, then an AHB. */
|
||||
uint32_t order[] = {VKB_MEM_HOSTPTR, VKB_MEM_DMABUF, VKB_MEM_DMAHEAP, VKB_MEM_AHB};
|
||||
for (int i = 0; i < 4; i++) {
|
||||
uint32_t s = order[i];
|
||||
if (force && strcmp(force, vkb_mem_strategy_name(s))) continue;
|
||||
uint32_t n;
|
||||
const char *const *req = vkb_mem_required_extensions(s, &n);
|
||||
if (!req) continue;
|
||||
int ok = 1;
|
||||
for (uint32_t j = 0; j < n; j++)
|
||||
if (strcmp(req[j], "VK_KHR_external_memory") && strcmp(req[j], "VK_KHR_sampler_ycbcr_conversion") &&
|
||||
strcmp(req[j], "VK_KHR_dedicated_allocation") && strcmp(req[j], "VK_KHR_get_memory_requirements2") &&
|
||||
!has_ext(ex, ne, req[j]))
|
||||
ok = 0;
|
||||
if (ok) candidates[nc++] = s;
|
||||
else VKB_INFO("memory sharing: %s unavailable (driver lacks its extensions)", vkb_mem_strategy_name(s));
|
||||
}
|
||||
|
||||
/* One test device with every candidate's extensions. */
|
||||
const char *exts[16];
|
||||
uint32_t nx = 0;
|
||||
for (int i = 0; i < nc; i++) {
|
||||
uint32_t n;
|
||||
const char *const *req = vkb_mem_required_extensions(candidates[i], &n);
|
||||
for (uint32_t j = 0; j < n; j++) {
|
||||
if (!has_ext(ex, ne, req[j])) continue;
|
||||
int dup = 0;
|
||||
for (uint32_t q = 0; q < nx; q++) dup |= !strcmp(exts[q], req[j]);
|
||||
if (!dup && nx < 16) exts[nx++] = req[j];
|
||||
}
|
||||
}
|
||||
free(ex);
|
||||
float prio = 1.0f;
|
||||
VkDeviceQueueCreateInfo qci = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, NULL, 0, st.qf, 1, &prio};
|
||||
VkDeviceCreateInfo dci = {VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, NULL, 0, 1, &qci, 0, NULL, nx, exts, NULL};
|
||||
if (idt->vkCreateDevice(pd, &dci, NULL, &st.dev) != VK_SUCCESS) {
|
||||
VKB_ERR("memory self-test: cannot create a device");
|
||||
return;
|
||||
}
|
||||
st.dt = *idt;
|
||||
PFN_vkGetDeviceProcAddr gdpa = (PFN_vkGetDeviceProcAddr)vkb_gipa(inst, "vkGetDeviceProcAddr");
|
||||
vkb_mem_gdpa = gdpa;
|
||||
log_external_props(idt, pd);
|
||||
vkb_dispatch_load_device(&st.dt, gdpa, st.dev);
|
||||
st.dt.vkGetDeviceQueue(st.dev, st.qf, 0, &st.queue);
|
||||
VkCommandPoolCreateInfo pci = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, NULL, 0, st.qf};
|
||||
st.dt.vkCreateCommandPool(st.dev, &pci, NULL, &st.pool);
|
||||
|
||||
VkPhysicalDeviceExternalMemoryHostPropertiesEXT hostp = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_HOST_PROPERTIES_EXT};
|
||||
VkPhysicalDeviceProperties2 p2 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &hostp};
|
||||
idt->vkGetPhysicalDeviceProperties2(pd, &p2);
|
||||
st.import_align = hostp.minImportedHostPointerAlignment ? hostp.minImportedHostPointerAlignment : 4096;
|
||||
if (st.import_align < 4096) st.import_align = 4096;
|
||||
|
||||
k->info.strategy = VKB_MEM_NONE;
|
||||
for (int i = 0; i < nc; i++) {
|
||||
uint32_t s = candidates[i];
|
||||
uint32_t shareable = 0, coherent = 0;
|
||||
for (uint32_t t = 0; t < st.mp.memoryTypeCount; t++) {
|
||||
if (!(st.mp.memoryTypes[t].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) continue;
|
||||
if (st.mp.memoryTypes[t].propertyFlags & VK_MEMORY_PROPERTY_PROTECTED_BIT) continue;
|
||||
int res = test_type(&st, s, t);
|
||||
if (res) shareable |= 1u << t;
|
||||
if (res == 1) coherent |= 1u << t;
|
||||
}
|
||||
if (coherent) {
|
||||
k->info.strategy = s;
|
||||
k->info.buffer_handle_types = strategy_handle_type(s);
|
||||
k->info.shareable_types = shareable;
|
||||
k->info.coherent_types = coherent;
|
||||
k->info.import_alignment = st.import_align;
|
||||
break;
|
||||
}
|
||||
VKB_INFO("memory sharing: %s failed the self-test", vkb_mem_strategy_name(s));
|
||||
}
|
||||
if (k->info.strategy == VKB_MEM_NONE)
|
||||
VKB_ERR("memory sharing: no method works on %s; Linux programs will get no host-visible memory", k->name);
|
||||
|
||||
st.dt.vkDestroyCommandPool(st.dev, st.pool, NULL);
|
||||
st.dt.vkDestroyDevice(st.dev, NULL);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Descriptor update templates. The client repacks the app's data to the layout of the template
|
||||
* it created on the server (entries rewritten to packed offsets), so the blob is used as is.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "vkb_server.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
void vkb_sv_vkbDescUpdateRaw(vkb_srv_call *c)
|
||||
{
|
||||
VkDevice dev = (VkDevice)(uintptr_t)vkb_dec_u64(&c->d);
|
||||
VkDescriptorSet set;
|
||||
VkDescriptorUpdateTemplate tmpl;
|
||||
vkb_dec_bytes(&c->d, &set, sizeof(set));
|
||||
vkb_dec_bytes(&c->d, &tmpl, sizeof(tmpl));
|
||||
size_t n = 0;
|
||||
const void *data = vkb_dec_blob(&c->d, &n);
|
||||
if (c->d.err || !c->table) {
|
||||
vkb_srv_bad_message(c);
|
||||
return;
|
||||
}
|
||||
if (!c->dt->vkUpdateDescriptorSetWithTemplate) {
|
||||
vkb_srv_missing(c, "vkUpdateDescriptorSetWithTemplate");
|
||||
return;
|
||||
}
|
||||
c->dt->vkUpdateDescriptorSetWithTemplate(dev, set, tmpl, data);
|
||||
}
|
||||
|
||||
void vkb_sv_vkbPushDescRaw(vkb_srv_call *c)
|
||||
{
|
||||
VkCommandBuffer cb = (VkCommandBuffer)(uintptr_t)vkb_dec_u64(&c->d);
|
||||
VkDescriptorUpdateTemplate tmpl;
|
||||
VkPipelineLayout layout;
|
||||
vkb_dec_bytes(&c->d, &tmpl, sizeof(tmpl));
|
||||
vkb_dec_bytes(&c->d, &layout, sizeof(layout));
|
||||
uint32_t set = vkb_dec_u32(&c->d);
|
||||
size_t n = 0;
|
||||
const void *data = vkb_dec_blob(&c->d, &n);
|
||||
if (c->d.err || !c->table) {
|
||||
vkb_srv_bad_message(c);
|
||||
return;
|
||||
}
|
||||
if (!c->dt->vkCmdPushDescriptorSetWithTemplate) {
|
||||
vkb_srv_missing(c, "vkCmdPushDescriptorSetWithTemplate");
|
||||
return;
|
||||
}
|
||||
extern __thread size_t vkb_emu_push_size;
|
||||
vkb_emu_push_size = n;
|
||||
c->dt->vkCmdPushDescriptorSetWithTemplate(cb, tmpl, layout, set, data);
|
||||
vkb_emu_push_size = 0;
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Persistent pipeline cache. Mali compiles every pipeline from scratch in each new process unless
|
||||
* it is handed a VkPipelineCache; games through DXVK pass none. With a cache directory set
|
||||
* (--cache-dir), each device gets a cache loaded from <dir>/<vendor>-<device>-<cacheUUID>.vkpc:
|
||||
* - pipelines created without a cache use it;
|
||||
* - caches the app creates empty start from it, and are merged back into it when destroyed;
|
||||
* - it is written back every few seconds while new pipelines come in (the app stops the server
|
||||
* with SIGKILL, so there is no reliable last chance) and when the device goes away.
|
||||
* The file's header is checked against the GPU before the driver sees it.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "vkb_server.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define PC_MAX_FILE (256u << 20)
|
||||
#define PC_SAVE_PERIOD_S 10
|
||||
|
||||
typedef struct vkb_pcache {
|
||||
vkb_srv_table *t;
|
||||
VkPipelineCache cache;
|
||||
char path[512];
|
||||
int dirty;
|
||||
size_t saved_size;
|
||||
PFN_vkCreateGraphicsPipelines create_gfx;
|
||||
PFN_vkCreateComputePipelines create_compute;
|
||||
PFN_vkCreatePipelineCache create_cache;
|
||||
PFN_vkDestroyPipelineCache destroy_cache;
|
||||
struct vkb_pcache *next;
|
||||
} vkb_pcache;
|
||||
|
||||
static const char *pc_dir;
|
||||
static pthread_mutex_t pc_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static vkb_pcache *pc_list;
|
||||
static int pc_thread_started;
|
||||
|
||||
void vkb_pcache_set_dir(const char *dir)
|
||||
{
|
||||
if (!dir || !*dir) return;
|
||||
mkdir(dir, 0700);
|
||||
pc_dir = dir;
|
||||
VKB_INFO("pipeline cache directory %s", dir);
|
||||
}
|
||||
|
||||
static vkb_pcache *cur(void)
|
||||
{
|
||||
vkb_srv_table *t = vkb_srv_current_table();
|
||||
return t ? t->pcache : NULL;
|
||||
}
|
||||
|
||||
/* The header every pipeline cache starts with (VkPipelineCacheHeaderVersionOne). */
|
||||
static int header_ok(const vkb_srv_table *t, const uint8_t *data, size_t size)
|
||||
{
|
||||
VkPipelineCacheHeaderVersionOne h;
|
||||
if (size < sizeof(h)) return 0;
|
||||
memcpy(&h, data, sizeof(h));
|
||||
VkPhysicalDeviceProperties props;
|
||||
t->real.vkGetPhysicalDeviceProperties(t->physical, &props);
|
||||
return h.headerSize >= sizeof(h) && h.headerVersion == VK_PIPELINE_CACHE_HEADER_VERSION_ONE &&
|
||||
h.vendorID == props.vendorID && h.deviceID == props.deviceID &&
|
||||
!memcmp(h.pipelineCacheUUID, props.pipelineCacheUUID, VK_UUID_SIZE);
|
||||
}
|
||||
|
||||
static uint8_t *read_file(const char *path, size_t *size)
|
||||
{
|
||||
*size = 0;
|
||||
int fd = open(path, O_RDONLY | O_CLOEXEC);
|
||||
if (fd < 0) return NULL;
|
||||
struct stat st;
|
||||
uint8_t *buf = NULL;
|
||||
if (fstat(fd, &st) == 0 && st.st_size > 0 && (uint64_t)st.st_size <= PC_MAX_FILE && (buf = malloc((size_t)st.st_size))) {
|
||||
size_t got = 0;
|
||||
while (got < (size_t)st.st_size) {
|
||||
ssize_t r = read(fd, buf + got, (size_t)st.st_size - got);
|
||||
if (r <= 0) break;
|
||||
got += (size_t)r;
|
||||
}
|
||||
if (got == (size_t)st.st_size) {
|
||||
*size = got;
|
||||
} else {
|
||||
free(buf);
|
||||
buf = NULL;
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* Writes the cache next to its file and renames it over, so a kill mid-write loses nothing. */
|
||||
static void save(vkb_pcache *p)
|
||||
{
|
||||
vkb_srv_table *t = p->t;
|
||||
size_t size = 0;
|
||||
if (t->real.vkGetPipelineCacheData(t->device, p->cache, &size, NULL) != VK_SUCCESS || !size || size > PC_MAX_FILE) return;
|
||||
if (size == p->saved_size) {
|
||||
/* Nothing new was compiled (merges of what it already had leave the size alone). */
|
||||
p->dirty = 0;
|
||||
return;
|
||||
}
|
||||
uint8_t *buf = malloc(size);
|
||||
if (!buf) return;
|
||||
VkResult r = t->real.vkGetPipelineCacheData(t->device, p->cache, &size, buf);
|
||||
if (r == VK_SUCCESS) {
|
||||
char tmp[600];
|
||||
snprintf(tmp, sizeof(tmp), "%s.%d.tmp", p->path, (int)getpid());
|
||||
int fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600);
|
||||
int ok = fd >= 0;
|
||||
for (size_t done = 0; ok && done < size;) {
|
||||
ssize_t w = write(fd, buf + done, size - done);
|
||||
if (w < 0 && errno == EINTR) continue;
|
||||
if (w <= 0) ok = 0;
|
||||
else done += (size_t)w;
|
||||
}
|
||||
if (fd >= 0 && fsync(fd) != 0) ok = 0;
|
||||
if (fd >= 0) close(fd);
|
||||
if (ok && rename(tmp, p->path) == 0) {
|
||||
VKB_INFO("device %u: pipeline cache saved (%zu KB)", t->id, size >> 10);
|
||||
p->saved_size = size;
|
||||
p->dirty = 0;
|
||||
} else {
|
||||
VKB_WARN("device %u: pipeline cache not saved to %s: %s", t->id, p->path, strerror(errno));
|
||||
unlink(tmp);
|
||||
}
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void *saver(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
for (;;) {
|
||||
struct timespec ts = {PC_SAVE_PERIOD_S, 0};
|
||||
nanosleep(&ts, NULL);
|
||||
pthread_mutex_lock(&pc_lock);
|
||||
for (vkb_pcache *p = pc_list; p; p = p->next)
|
||||
if (__atomic_load_n(&p->dirty, __ATOMIC_RELAXED)) save(p);
|
||||
pthread_mutex_unlock(&pc_lock);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ wrappers */
|
||||
|
||||
static VkResult VKAPI_CALL pc_CreateGraphicsPipelines(VkDevice device, VkPipelineCache cache, uint32_t n,
|
||||
const VkGraphicsPipelineCreateInfo *ci, const VkAllocationCallbacks *a,
|
||||
VkPipeline *out)
|
||||
{
|
||||
vkb_pcache *p = cur();
|
||||
if (!cache) {
|
||||
cache = p->cache;
|
||||
__atomic_store_n(&p->dirty, 1, __ATOMIC_RELAXED);
|
||||
}
|
||||
return p->create_gfx(device, cache, n, ci, a, out);
|
||||
}
|
||||
|
||||
static VkResult VKAPI_CALL pc_CreateComputePipelines(VkDevice device, VkPipelineCache cache, uint32_t n,
|
||||
const VkComputePipelineCreateInfo *ci, const VkAllocationCallbacks *a,
|
||||
VkPipeline *out)
|
||||
{
|
||||
vkb_pcache *p = cur();
|
||||
if (!cache) {
|
||||
cache = p->cache;
|
||||
__atomic_store_n(&p->dirty, 1, __ATOMIC_RELAXED);
|
||||
}
|
||||
return p->create_compute(device, cache, n, ci, a, out);
|
||||
}
|
||||
|
||||
static VkResult VKAPI_CALL pc_CreatePipelineCache(VkDevice device, const VkPipelineCacheCreateInfo *ci,
|
||||
const VkAllocationCallbacks *a, VkPipelineCache *out)
|
||||
{
|
||||
vkb_pcache *p = cur();
|
||||
if (ci->initialDataSize) return p->create_cache(device, ci, a, out);
|
||||
size_t size = 0;
|
||||
uint8_t *buf = NULL;
|
||||
if (p->t->real.vkGetPipelineCacheData(device, p->cache, &size, NULL) == VK_SUCCESS && size && (buf = malloc(size)) &&
|
||||
p->t->real.vkGetPipelineCacheData(device, p->cache, &size, buf) != VK_SUCCESS)
|
||||
size = 0;
|
||||
VkPipelineCacheCreateInfo seeded = *ci;
|
||||
seeded.initialDataSize = buf ? size : 0;
|
||||
seeded.pInitialData = buf;
|
||||
VkResult r = p->create_cache(device, &seeded, a, out);
|
||||
free(buf);
|
||||
return r;
|
||||
}
|
||||
|
||||
static void VKAPI_CALL pc_DestroyPipelineCache(VkDevice device, VkPipelineCache cache, const VkAllocationCallbacks *a)
|
||||
{
|
||||
vkb_pcache *p = cur();
|
||||
if (cache && cache != p->cache && p->t->real.vkMergePipelineCaches(device, p->cache, 1, &cache) == VK_SUCCESS)
|
||||
__atomic_store_n(&p->dirty, 1, __ATOMIC_RELAXED);
|
||||
p->destroy_cache(device, cache, a);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ device hooks */
|
||||
|
||||
void vkb_pcache_device_init(vkb_srv_table *t)
|
||||
{
|
||||
if (!pc_dir || !t->real.vkCreatePipelineCache || !t->real.vkGetPipelineCacheData || !t->real.vkMergePipelineCaches) return;
|
||||
vkb_pcache *p = calloc(1, sizeof(*p));
|
||||
if (!p) return;
|
||||
p->t = t;
|
||||
VkPhysicalDeviceProperties props;
|
||||
t->real.vkGetPhysicalDeviceProperties(t->physical, &props);
|
||||
char uuid[2 * VK_UUID_SIZE + 1];
|
||||
for (int i = 0; i < VK_UUID_SIZE; i++) snprintf(uuid + 2 * i, 3, "%02x", props.pipelineCacheUUID[i]);
|
||||
snprintf(p->path, sizeof(p->path), "%s/%04x-%04x-%s.vkpc", pc_dir, props.vendorID, props.deviceID, uuid);
|
||||
|
||||
size_t size;
|
||||
uint8_t *data = read_file(p->path, &size);
|
||||
if (data && !header_ok(t, data, size)) {
|
||||
VKB_WARN("device %u: %s is not this GPU's pipeline cache; starting empty", t->id, p->path);
|
||||
free(data);
|
||||
data = NULL;
|
||||
size = 0;
|
||||
}
|
||||
VkPipelineCacheCreateInfo ci = {VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, NULL, 0, data ? size : 0, data};
|
||||
VkResult r = t->real.vkCreatePipelineCache(t->device, &ci, NULL, &p->cache);
|
||||
if (r != VK_SUCCESS && data) {
|
||||
VKB_WARN("device %u: driver refused the saved pipeline cache (%d); starting empty", t->id, r);
|
||||
ci.initialDataSize = 0;
|
||||
ci.pInitialData = NULL;
|
||||
r = t->real.vkCreatePipelineCache(t->device, &ci, NULL, &p->cache);
|
||||
}
|
||||
free(data);
|
||||
if (r != VK_SUCCESS) {
|
||||
VKB_WARN("device %u: no pipeline cache (%d)", t->id, r);
|
||||
free(p);
|
||||
return;
|
||||
}
|
||||
p->saved_size = size;
|
||||
VKB_INFO("device %u: pipeline cache %s (%zu KB loaded)", t->id, p->path, size >> 10);
|
||||
|
||||
vkb_dispatch *dt = &t->dt;
|
||||
p->create_gfx = dt->vkCreateGraphicsPipelines;
|
||||
p->create_compute = dt->vkCreateComputePipelines;
|
||||
p->create_cache = dt->vkCreatePipelineCache;
|
||||
p->destroy_cache = dt->vkDestroyPipelineCache;
|
||||
dt->vkCreateGraphicsPipelines = pc_CreateGraphicsPipelines;
|
||||
dt->vkCreateComputePipelines = pc_CreateComputePipelines;
|
||||
dt->vkCreatePipelineCache = pc_CreatePipelineCache;
|
||||
dt->vkDestroyPipelineCache = pc_DestroyPipelineCache;
|
||||
t->pcache = p;
|
||||
|
||||
pthread_mutex_lock(&pc_lock);
|
||||
p->next = pc_list;
|
||||
pc_list = p;
|
||||
if (!pc_thread_started) {
|
||||
pthread_t th;
|
||||
pthread_attr_t at;
|
||||
pthread_attr_init(&at);
|
||||
pthread_attr_setdetachstate(&at, PTHREAD_CREATE_DETACHED);
|
||||
pc_thread_started = pthread_create(&th, &at, saver, NULL) == 0;
|
||||
pthread_attr_destroy(&at);
|
||||
}
|
||||
pthread_mutex_unlock(&pc_lock);
|
||||
}
|
||||
|
||||
/* Before the device is destroyed: last save, then the cache goes. */
|
||||
void vkb_pcache_device_destroy(vkb_srv_table *t)
|
||||
{
|
||||
vkb_pcache *p = t->pcache;
|
||||
if (!p) return;
|
||||
pthread_mutex_lock(&pc_lock);
|
||||
for (vkb_pcache **pp = &pc_list; *pp; pp = &(*pp)->next) {
|
||||
if (*pp == p) {
|
||||
*pp = p->next;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (p->dirty) save(p);
|
||||
pthread_mutex_unlock(&pc_lock);
|
||||
t->dt.vkCreateGraphicsPipelines = p->create_gfx;
|
||||
t->dt.vkCreateComputePipelines = p->create_compute;
|
||||
t->dt.vkCreatePipelineCache = p->create_cache;
|
||||
t->dt.vkDestroyPipelineCache = p->destroy_cache;
|
||||
t->real.vkDestroyPipelineCache(t->device, p->cache, NULL);
|
||||
t->pcache = NULL;
|
||||
free(p);
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* SPIR-V fixes for Mali's shader compiler, applied to every module the app hands the driver.
|
||||
*
|
||||
* DXVK (1.7.3 and later) guards texture reads with OpSelect(cond, texel, vec4(0)) where cond is an
|
||||
* OpConstantComposite of OpSpecConstantTrue/False. Mali's compiler folds that composite as if it
|
||||
* were a plain constant - taking the spec constants at their default - and every texture reads
|
||||
* black. Declaring the composite OpSpecConstantComposite (same operands, same length) keeps it
|
||||
* specializable and the fold away. Found by leegao (bionic-vulkan-wrapper issue #93,
|
||||
* "OpCompositeConstant" write-up, 2025-08-10); Vortek patches the same pattern.
|
||||
*
|
||||
* The code is patched in place: it lives in the server's receive buffer (vkCreateShaderModule's
|
||||
* pCode, or a VkShaderModuleCreateInfo chained to a pipeline stage), so nothing is copied.
|
||||
* On for ARM GPUs; VKBRIDGE_SPIRV_FIX=1/0 forces it (host tests).
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "vkb_server.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define OP_SPEC_CONSTANT_TRUE 48
|
||||
#define OP_SPEC_CONSTANT_FALSE 49
|
||||
#define OP_SPEC_CONSTANT 50
|
||||
#define OP_SPEC_CONSTANT_COMPOSITE 51
|
||||
#define OP_SPEC_CONSTANT_OP 52
|
||||
#define OP_CONSTANT_COMPOSITE 44
|
||||
#define OP_FUNCTION 54
|
||||
|
||||
typedef struct vkb_spvfix {
|
||||
PFN_vkCreateShaderModule create_module;
|
||||
PFN_vkCreateGraphicsPipelines create_gfx;
|
||||
PFN_vkCreateComputePipelines create_compute;
|
||||
unsigned long patched; /* composites changed, for the log */
|
||||
} vkb_spvfix;
|
||||
|
||||
static vkb_spvfix *cur(void)
|
||||
{
|
||||
vkb_srv_table *t = vkb_srv_current_table();
|
||||
return t ? t->spvfix : NULL;
|
||||
}
|
||||
|
||||
/* Patches one module in place; returns the number of composites changed. */
|
||||
static unsigned fix_module(uint32_t *w, size_t bytes)
|
||||
{
|
||||
size_t n = bytes / 4;
|
||||
if (n < 5 || w[0] != 0x07230203u) return 0;
|
||||
uint32_t bound = w[3];
|
||||
if (!bound || bound > (1u << 24)) return 0;
|
||||
uint8_t *spec = calloc(bound, 1);
|
||||
if (!spec) return 0;
|
||||
unsigned changed = 0;
|
||||
for (size_t i = 5; i < n;) {
|
||||
uint32_t op = w[i] & 0xffff, len = w[i] >> 16;
|
||||
if (!len || i + len > n) break;
|
||||
if (op == OP_FUNCTION) break; /* constants all come before the first function */
|
||||
if ((op == OP_SPEC_CONSTANT_TRUE || op == OP_SPEC_CONSTANT_FALSE || op == OP_SPEC_CONSTANT ||
|
||||
op == OP_SPEC_CONSTANT_COMPOSITE || op == OP_SPEC_CONSTANT_OP) && len >= 3 && w[i + 2] < bound) {
|
||||
spec[w[i + 2]] = 1;
|
||||
} else if (op == OP_CONSTANT_COMPOSITE && len >= 3 && w[i + 2] < bound) {
|
||||
int any = 0;
|
||||
for (uint32_t k = 3; k < len; k++) any |= w[i + k] < bound && spec[w[i + k]];
|
||||
if (any) {
|
||||
/* A composite of spec constants is itself one: declared so, it is no longer folded. */
|
||||
w[i] = (len << 16) | OP_SPEC_CONSTANT_COMPOSITE;
|
||||
spec[w[i + 2]] = 1;
|
||||
changed++;
|
||||
}
|
||||
}
|
||||
i += len;
|
||||
}
|
||||
free(spec);
|
||||
return changed;
|
||||
}
|
||||
|
||||
static void note(vkb_spvfix *f, unsigned changed)
|
||||
{
|
||||
if (!changed) return;
|
||||
unsigned long before = __atomic_fetch_add(&f->patched, changed, __ATOMIC_RELAXED);
|
||||
if (!before) VKB_INFO("SPIR-V fix: spec-constant composites redeclared for Mali's compiler (first in this device)");
|
||||
}
|
||||
|
||||
static void fix_stages(vkb_spvfix *f, uint32_t n, const VkPipelineShaderStageCreateInfo *st)
|
||||
{
|
||||
for (uint32_t i = 0; i < n; i++)
|
||||
for (const VkBaseInStructure *b = st[i].pNext; b; b = b->pNext)
|
||||
if (b->sType == VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO) {
|
||||
const VkShaderModuleCreateInfo *mi = (const void *)b;
|
||||
note(f, fix_module((uint32_t *)mi->pCode, mi->codeSize));
|
||||
}
|
||||
}
|
||||
|
||||
static VkResult VKAPI_CALL fx_CreateShaderModule(VkDevice device, const VkShaderModuleCreateInfo *ci, const VkAllocationCallbacks *a,
|
||||
VkShaderModule *out)
|
||||
{
|
||||
vkb_spvfix *f = cur();
|
||||
note(f, fix_module((uint32_t *)ci->pCode, ci->codeSize));
|
||||
return f->create_module(device, ci, a, out);
|
||||
}
|
||||
|
||||
static VkResult VKAPI_CALL fx_CreateGraphicsPipelines(VkDevice device, VkPipelineCache cache, uint32_t n,
|
||||
const VkGraphicsPipelineCreateInfo *ci, const VkAllocationCallbacks *a,
|
||||
VkPipeline *out)
|
||||
{
|
||||
vkb_spvfix *f = cur();
|
||||
for (uint32_t i = 0; i < n; i++) fix_stages(f, ci[i].stageCount, ci[i].pStages);
|
||||
return f->create_gfx(device, cache, n, ci, a, out);
|
||||
}
|
||||
|
||||
static VkResult VKAPI_CALL fx_CreateComputePipelines(VkDevice device, VkPipelineCache cache, uint32_t n,
|
||||
const VkComputePipelineCreateInfo *ci, const VkAllocationCallbacks *a,
|
||||
VkPipeline *out)
|
||||
{
|
||||
vkb_spvfix *f = cur();
|
||||
for (uint32_t i = 0; i < n; i++) fix_stages(f, 1, &ci[i].stage);
|
||||
return f->create_compute(device, cache, n, ci, a, out);
|
||||
}
|
||||
|
||||
void vkb_spvfix_device_init(vkb_srv_table *t)
|
||||
{
|
||||
VkPhysicalDeviceProperties p;
|
||||
t->real.vkGetPhysicalDeviceProperties(t->physical, &p);
|
||||
const char *env = getenv("VKBRIDGE_SPIRV_FIX");
|
||||
int on = env ? strcmp(env, "0") != 0 : p.vendorID == 0x13B5; /* ARM */
|
||||
if (!on || !t->dt.vkCreateShaderModule) return;
|
||||
vkb_spvfix *f = calloc(1, sizeof(*f));
|
||||
if (!f) return;
|
||||
vkb_dispatch *dt = &t->dt;
|
||||
f->create_module = dt->vkCreateShaderModule;
|
||||
f->create_gfx = dt->vkCreateGraphicsPipelines;
|
||||
f->create_compute = dt->vkCreateComputePipelines;
|
||||
dt->vkCreateShaderModule = fx_CreateShaderModule;
|
||||
dt->vkCreateGraphicsPipelines = fx_CreateGraphicsPipelines;
|
||||
dt->vkCreateComputePipelines = fx_CreateComputePipelines;
|
||||
t->spvfix = f;
|
||||
VKB_INFO("device %u: Mali SPIR-V fixes on", t->id);
|
||||
}
|
||||
|
||||
void vkb_spvfix_device_destroy(vkb_srv_table *t)
|
||||
{
|
||||
vkb_spvfix *f = t->spvfix;
|
||||
if (!f) return;
|
||||
t->dt.vkCreateShaderModule = f->create_module;
|
||||
t->dt.vkCreateGraphicsPipelines = f->create_gfx;
|
||||
t->dt.vkCreateComputePipelines = f->create_compute;
|
||||
if (f->patched) VKB_INFO("device %u: SPIR-V fix changed %lu composites", t->id, f->patched);
|
||||
free(f);
|
||||
t->spvfix = NULL;
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "spirv_edit.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
int spv_parse(spv_mod *m, const uint32_t *code, size_t bytes)
|
||||
{
|
||||
memset(m, 0, sizeof(*m));
|
||||
if (bytes < 20 || (bytes & 3) || code[0] != 0x07230203u) return -1;
|
||||
memcpy(m->header, code, 20);
|
||||
size_t words = bytes / 4, pos = 5;
|
||||
while (pos < words) {
|
||||
uint32_t n = code[pos] >> 16, op = code[pos] & 0xffff;
|
||||
if (n == 0 || pos + n > words) return -1;
|
||||
spv_insert(m, m->count, (uint16_t)op, code + pos + 1, (uint16_t)(n - 1));
|
||||
if (m->err) return -1;
|
||||
pos += n;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t spv_write(const spv_mod *m, uint32_t **out)
|
||||
{
|
||||
size_t words = 5;
|
||||
for (uint32_t i = 0; i < m->count; i++) words += m->ins[i].n;
|
||||
uint32_t *buf = malloc(words * 4);
|
||||
if (!buf) return 0;
|
||||
memcpy(buf, m->header, 20);
|
||||
size_t pos = 5;
|
||||
for (uint32_t i = 0; i < m->count; i++) {
|
||||
const spv_inst *in = &m->ins[i];
|
||||
buf[pos++] = ((uint32_t)in->n << 16) | in->op;
|
||||
memcpy(buf + pos, in->w, (size_t)(in->n - 1) * 4);
|
||||
pos += in->n - 1;
|
||||
}
|
||||
*out = buf;
|
||||
return words * 4;
|
||||
}
|
||||
|
||||
void spv_free(spv_mod *m)
|
||||
{
|
||||
for (uint32_t i = 0; i < m->count; i++) free(m->ins[i].w);
|
||||
free(m->ins);
|
||||
memset(m, 0, sizeof(*m));
|
||||
}
|
||||
|
||||
uint32_t spv_insert(spv_mod *m, uint32_t at, uint16_t op, const uint32_t *ops, uint16_t nops)
|
||||
{
|
||||
if (m->count == m->cap) {
|
||||
uint32_t nc = m->cap ? m->cap * 2 : 1024;
|
||||
spv_inst *ni = realloc(m->ins, nc * sizeof(*ni));
|
||||
if (!ni) {
|
||||
m->err = 1;
|
||||
return 0;
|
||||
}
|
||||
m->ins = ni;
|
||||
m->cap = nc;
|
||||
}
|
||||
if (at > m->count) at = m->count;
|
||||
memmove(&m->ins[at + 1], &m->ins[at], (m->count - at) * sizeof(spv_inst));
|
||||
spv_inst *in = &m->ins[at];
|
||||
in->op = op;
|
||||
in->n = (uint16_t)(nops + 1);
|
||||
in->w = malloc((size_t)(nops ? nops : 1) * 4);
|
||||
if (!in->w) m->err = 1;
|
||||
else if (nops) memcpy(in->w, ops, (size_t)nops * 4);
|
||||
m->count++;
|
||||
return at;
|
||||
}
|
||||
|
||||
void spv_remove(spv_mod *m, uint32_t at)
|
||||
{
|
||||
if (at >= m->count) return;
|
||||
free(m->ins[at].w);
|
||||
memmove(&m->ins[at], &m->ins[at + 1], (m->count - at - 1) * sizeof(spv_inst));
|
||||
m->count--;
|
||||
}
|
||||
|
||||
int spv_find_op(const spv_mod *m, uint16_t op, uint32_t from)
|
||||
{
|
||||
for (uint32_t i = from; i < m->count; i++)
|
||||
if (m->ins[i].op == op) return (int)i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int is_type(uint16_t op) { return op >= 19 && op <= 38; }
|
||||
static int is_const(uint16_t op) { return (op >= 41 && op <= 46) || (op >= 48 && op <= 52); }
|
||||
|
||||
uint32_t spv_result_id(const spv_inst *in)
|
||||
{
|
||||
if (in->n < 2) return 0;
|
||||
if (is_type(in->op) || in->op == SpvOpLabel || in->op == SpvOpString || in->op == SpvOpExtInstImport || in->op == 73)
|
||||
return in->w[0];
|
||||
switch (in->op) {
|
||||
case SpvOpName: case SpvOpMemberName: case SpvOpDecorate: case SpvOpMemberDecorate: case SpvOpEntryPoint:
|
||||
case SpvOpExecutionMode: case SpvOpCapability: case SpvOpExtension: case SpvOpMemoryModel: case SpvOpStore:
|
||||
case SpvOpBranch: case SpvOpBranchConditional: case SpvOpSelectionMerge: case SpvOpReturn: case SpvOpKill:
|
||||
case SpvOpFunctionEnd: case SpvOpLine: case SpvOpNoLine: case SpvOpSource: case SpvOpSourceExtension:
|
||||
case SpvOpDecorateId: case SpvOpDecorateString: case SpvOpMemberDecorateString: case SpvOpModuleProcessed:
|
||||
case SpvOpExecutionModeId: case 246 /* LoopMerge */: case 251 /* Switch */: case 254 /* ReturnValue */:
|
||||
case 255 /* Unreachable */: case 74: case 75: case 224 /* ControlBarrier */: case 225 /* MemoryBarrier */:
|
||||
case SpvOpTerminateInvocation:
|
||||
return 0;
|
||||
default:
|
||||
return in->n >= 3 ? in->w[1] : 0;
|
||||
}
|
||||
}
|
||||
|
||||
int spv_find_def(const spv_mod *m, uint32_t id)
|
||||
{
|
||||
for (uint32_t i = 0; i < m->count; i++)
|
||||
if (spv_result_id(&m->ins[i]) == id) return (int)i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t spv_types_end(const spv_mod *m)
|
||||
{
|
||||
int f = spv_find_op(m, SpvOpFunction, 0);
|
||||
return f < 0 ? m->count : (uint32_t)f;
|
||||
}
|
||||
|
||||
uint32_t spv_annotations_end(const spv_mod *m)
|
||||
{
|
||||
uint32_t end = spv_types_end(m);
|
||||
for (uint32_t i = 0; i < end; i++) {
|
||||
uint16_t op = m->ins[i].op;
|
||||
if (is_type(op) || is_const(op) || op == SpvOpVariable || op == SpvOpUndef) return i;
|
||||
}
|
||||
return end;
|
||||
}
|
||||
|
||||
static uint32_t add_global(spv_mod *m, uint16_t op, const uint32_t *ops, uint16_t n)
|
||||
{
|
||||
uint32_t at = spv_types_end(m);
|
||||
spv_insert(m, at, op, ops, n);
|
||||
return at;
|
||||
}
|
||||
|
||||
uint32_t spv_type_float(spv_mod *m, uint32_t width)
|
||||
{
|
||||
for (uint32_t i = 0; i < m->count; i++)
|
||||
if (m->ins[i].op == SpvOpTypeFloat && m->ins[i].w[1] == width && m->ins[i].n == 3) return m->ins[i].w[0];
|
||||
uint32_t id = spv_new_id(m), ops[2] = {id, width};
|
||||
add_global(m, SpvOpTypeFloat, ops, 2);
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32_t spv_type_int(spv_mod *m, uint32_t width, uint32_t sign)
|
||||
{
|
||||
for (uint32_t i = 0; i < m->count; i++)
|
||||
if (m->ins[i].op == SpvOpTypeInt && m->ins[i].w[1] == width && m->ins[i].w[2] == sign) return m->ins[i].w[0];
|
||||
uint32_t id = spv_new_id(m), ops[3] = {id, width, sign};
|
||||
add_global(m, SpvOpTypeInt, ops, 3);
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32_t spv_type_bool(spv_mod *m)
|
||||
{
|
||||
for (uint32_t i = 0; i < m->count; i++)
|
||||
if (m->ins[i].op == SpvOpTypeBool) return m->ins[i].w[0];
|
||||
uint32_t id = spv_new_id(m);
|
||||
add_global(m, SpvOpTypeBool, &id, 1);
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32_t spv_type_pointer(spv_mod *m, uint32_t storage, uint32_t pointee)
|
||||
{
|
||||
for (uint32_t i = 0; i < m->count; i++)
|
||||
if (m->ins[i].op == SpvOpTypePointer && m->ins[i].w[1] == storage && m->ins[i].w[2] == pointee) return m->ins[i].w[0];
|
||||
uint32_t id = spv_new_id(m), ops[3] = {id, storage, pointee};
|
||||
add_global(m, SpvOpTypePointer, ops, 3);
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32_t spv_type_array(spv_mod *m, uint32_t elem, uint32_t len_const)
|
||||
{
|
||||
for (uint32_t i = 0; i < m->count; i++) {
|
||||
if (m->ins[i].op == SpvOpTypeArray && m->ins[i].w[1] == elem && m->ins[i].w[2] == len_const &&
|
||||
spv_get_decoration(m, m->ins[i].w[0], 6 /* ArrayStride */) < 0)
|
||||
return m->ins[i].w[0];
|
||||
}
|
||||
uint32_t id = spv_new_id(m), ops[3] = {id, elem, len_const};
|
||||
add_global(m, SpvOpTypeArray, ops, 3);
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32_t spv_const_u32(spv_mod *m, uint32_t v)
|
||||
{
|
||||
uint32_t t = spv_type_int(m, 32, 0);
|
||||
for (uint32_t i = 0; i < m->count; i++)
|
||||
if (m->ins[i].op == SpvOpConstant && m->ins[i].w[0] == t && m->ins[i].n == 4 && m->ins[i].w[2] == v) return m->ins[i].w[1];
|
||||
uint32_t id = spv_new_id(m), ops[3] = {t, id, v};
|
||||
add_global(m, SpvOpConstant, ops, 3);
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32_t spv_const_f32(spv_mod *m, float v)
|
||||
{
|
||||
uint32_t t = spv_type_float(m, 32), bits;
|
||||
memcpy(&bits, &v, 4);
|
||||
for (uint32_t i = 0; i < m->count; i++)
|
||||
if (m->ins[i].op == SpvOpConstant && m->ins[i].w[0] == t && m->ins[i].n == 4 && m->ins[i].w[2] == bits) return m->ins[i].w[1];
|
||||
uint32_t id = spv_new_id(m), ops[3] = {t, id, bits};
|
||||
add_global(m, SpvOpConstant, ops, 3);
|
||||
return id;
|
||||
}
|
||||
|
||||
int64_t spv_get_decoration(const spv_mod *m, uint32_t id, uint32_t decoration)
|
||||
{
|
||||
for (uint32_t i = 0; i < m->count; i++) {
|
||||
const spv_inst *in = &m->ins[i];
|
||||
if (in->op == SpvOpDecorate && in->w[0] == id && in->w[1] == decoration) return in->n > 3 ? (int64_t)in->w[2] : 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void spv_remove_decoration(spv_mod *m, uint32_t id, uint32_t decoration)
|
||||
{
|
||||
for (uint32_t i = 0; i < m->count;) {
|
||||
const spv_inst *in = &m->ins[i];
|
||||
if (in->op == SpvOpDecorate && in->w[0] == id && in->w[1] == decoration) spv_remove(m, i);
|
||||
else i++;
|
||||
}
|
||||
}
|
||||
|
||||
int spv_has_capability(const spv_mod *m, uint32_t cap)
|
||||
{
|
||||
for (uint32_t i = 0; i < m->count; i++)
|
||||
if (m->ins[i].op == SpvOpCapability && m->ins[i].w[0] == cap) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void spv_remove_capability(spv_mod *m, uint32_t cap)
|
||||
{
|
||||
for (uint32_t i = 0; i < m->count;) {
|
||||
if (m->ins[i].op == SpvOpCapability && m->ins[i].w[0] == cap) spv_remove(m, i);
|
||||
else i++;
|
||||
}
|
||||
}
|
||||
|
||||
void spv_add_capability(spv_mod *m, uint32_t cap)
|
||||
{
|
||||
if (!spv_has_capability(m, cap)) spv_insert(m, 0, SpvOpCapability, &cap, 1);
|
||||
}
|
||||
|
||||
int spv_execution_model(const spv_mod *m)
|
||||
{
|
||||
int e = spv_find_op(m, SpvOpEntryPoint, 0);
|
||||
return e < 0 ? -1 : (int)m->ins[e].w[0];
|
||||
}
|
||||
|
||||
void spv_add_interface(spv_mod *m, uint32_t var)
|
||||
{
|
||||
for (uint32_t i = 0; i < m->count; i++) {
|
||||
spv_inst *in = &m->ins[i];
|
||||
if (in->op != SpvOpEntryPoint) continue;
|
||||
uint32_t *nw = realloc(in->w, (size_t)in->n * 4);
|
||||
if (!nw) {
|
||||
m->err = 1;
|
||||
return;
|
||||
}
|
||||
nw[in->n - 1] = var;
|
||||
in->w = nw;
|
||||
in->n++;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t spv_array_length(const spv_mod *m, uint32_t array_type)
|
||||
{
|
||||
int d = spv_find_def(m, array_type);
|
||||
if (d < 0 || m->ins[d].op != SpvOpTypeArray) return 0;
|
||||
int c = spv_find_def(m, m->ins[d].w[2]);
|
||||
if (c < 0 || m->ins[c].op != SpvOpConstant) return 0;
|
||||
return m->ins[c].w[2];
|
||||
}
|
||||
|
||||
/* Number of locations a type occupies. */
|
||||
static uint32_t loc_count(const spv_mod *m, uint32_t type, int depth)
|
||||
{
|
||||
int d = spv_find_def(m, type);
|
||||
if (d < 0 || depth > 16) return 1;
|
||||
const spv_inst *in = &m->ins[d];
|
||||
switch (in->op) {
|
||||
case SpvOpTypeArray: return spv_array_length(m, type) * loc_count(m, in->w[1], depth + 1);
|
||||
case 24 /* Matrix */: return in->w[2] * loc_count(m, in->w[1], depth + 1);
|
||||
case SpvOpTypeStruct: {
|
||||
uint32_t s = 0;
|
||||
for (uint32_t k = 1; k < (uint32_t)in->n - 1; k++) s += loc_count(m, in->w[k], depth + 1);
|
||||
return s;
|
||||
}
|
||||
case SpvOpTypeVector: {
|
||||
int e = spv_find_def(m, in->w[1]);
|
||||
uint32_t width = e >= 0 ? m->ins[e].w[1] : 32;
|
||||
return (width == 64 && in->w[2] > 2) ? 2 : 1;
|
||||
}
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
int spv_max_location(const spv_mod *m, uint32_t storage)
|
||||
{
|
||||
int max = -1;
|
||||
for (uint32_t i = 0; i < m->count; i++) {
|
||||
const spv_inst *in = &m->ins[i];
|
||||
if (in->op != SpvOpVariable || in->w[2] != storage) continue;
|
||||
int p = spv_find_def(m, in->w[0]);
|
||||
if (p < 0) continue;
|
||||
uint32_t pointee = m->ins[p].w[2];
|
||||
int64_t loc = spv_get_decoration(m, in->w[1], SpvDecorationLocation);
|
||||
if (loc >= 0) {
|
||||
int top = (int)loc + (int)loc_count(m, pointee, 0) - 1;
|
||||
if (top > max) max = top;
|
||||
continue;
|
||||
}
|
||||
/* A block with member locations. */
|
||||
for (uint32_t k = 0; k < m->count; k++) {
|
||||
const spv_inst *md = &m->ins[k];
|
||||
if (md->op == SpvOpMemberDecorate && md->w[0] == pointee && md->w[2] == SpvDecorationLocation) {
|
||||
int st = spv_find_def(m, pointee);
|
||||
uint32_t mt = st >= 0 && md->w[1] + 1 < m->ins[st].n ? m->ins[st].w[1 + md->w[1]] : 0;
|
||||
int top = (int)md->w[3] + (int)loc_count(m, mt, 0) - 1;
|
||||
if (top > max) max = top;
|
||||
}
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* A small SPIR-V editor for the bridge's shader emulations: parse a module into instructions,
|
||||
* look things up, append/insert/remove instructions, and write it back.
|
||||
*/
|
||||
#ifndef VKB_SPIRV_EDIT_H
|
||||
#define VKB_SPIRV_EDIT_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* Opcodes and enums used (from the SPIR-V spec). */
|
||||
enum {
|
||||
SpvOpName = 5, SpvOpMemberName = 6, SpvOpExtension = 10, SpvOpExtInstImport = 11, SpvOpMemoryModel = 14,
|
||||
SpvOpEntryPoint = 15, SpvOpExecutionMode = 16, SpvOpCapability = 17, SpvOpTypeVoid = 19, SpvOpTypeBool = 20,
|
||||
SpvOpTypeInt = 21, SpvOpTypeFloat = 22, SpvOpTypeVector = 23, SpvOpTypeArray = 28, SpvOpTypeRuntimeArray = 29,
|
||||
SpvOpTypeStruct = 30, SpvOpTypePointer = 32, SpvOpTypeFunction = 33, SpvOpConstantTrue = 41,
|
||||
SpvOpConstantFalse = 42, SpvOpConstant = 43, SpvOpConstantComposite = 44, SpvOpFunction = 54,
|
||||
SpvOpFunctionParameter = 55, SpvOpFunctionEnd = 56, SpvOpVariable = 59, SpvOpLoad = 61, SpvOpStore = 62,
|
||||
SpvOpAccessChain = 65, SpvOpInBoundsAccessChain = 66, SpvOpDecorate = 71, SpvOpMemberDecorate = 72,
|
||||
SpvOpCompositeExtract = 81, SpvOpFOrdLessThan = 184, SpvOpLogicalOr = 166, SpvOpSelectionMerge = 247,
|
||||
SpvOpLabel = 248, SpvOpBranch = 249, SpvOpBranchConditional = 250, SpvOpReturn = 253, SpvOpKill = 252,
|
||||
SpvOpTerminateInvocation = 4416, SpvOpLine = 8, SpvOpNoLine = 317, SpvOpModuleProcessed = 330,
|
||||
SpvOpString = 7, SpvOpSource = 3, SpvOpSourceExtension = 4, SpvOpSourceContinued = 2, SpvOpExecutionModeId = 331,
|
||||
SpvOpDecorateId = 332, SpvOpDecorateString = 5632, SpvOpMemberDecorateString = 5633, SpvOpUndef = 1,
|
||||
SpvOpExtInst = 12, SpvOpIAdd = 128, SpvOpUDiv = 134, SpvOpISub = 130,
|
||||
};
|
||||
enum { SpvDecorationBuiltIn = 11, SpvDecorationLocation = 30, SpvDecorationComponent = 31, SpvDecorationBlock = 2,
|
||||
SpvDecorationFlat = 14, SpvDecorationPatch = 15 };
|
||||
enum { SpvBuiltInPosition = 0, SpvBuiltInPointSize = 1, SpvBuiltInClipDistance = 3, SpvBuiltInCullDistance = 4,
|
||||
SpvBuiltInInstanceIndex = 43, SpvBuiltInBaseInstance = 4425 };
|
||||
enum { SpvStorageClassInput = 1, SpvStorageClassOutput = 3, SpvStorageClassPrivate = 6, SpvStorageClassFunction = 7 };
|
||||
enum { SpvCapabilityClipDistance = 32, SpvCapabilityCullDistance = 33, SpvCapabilityTessellationPointSize = 23,
|
||||
SpvCapabilityGeometryPointSize = 24, SpvCapabilityDrawParameters = 4427 };
|
||||
enum { SpvExecutionModelVertex = 0, SpvExecutionModelTessellationControl = 1, SpvExecutionModelTessellationEvaluation = 2,
|
||||
SpvExecutionModelGeometry = 3, SpvExecutionModelFragment = 4, SpvExecutionModelGLCompute = 5 };
|
||||
|
||||
typedef struct spv_inst {
|
||||
uint16_t op;
|
||||
uint16_t n; /* word count including the first word */
|
||||
uint32_t *w; /* operand words (w[0] = first operand), n - 1 of them */
|
||||
} spv_inst;
|
||||
|
||||
typedef struct spv_mod {
|
||||
uint32_t header[5];
|
||||
spv_inst *ins;
|
||||
uint32_t count, cap;
|
||||
int err;
|
||||
} spv_mod;
|
||||
|
||||
int spv_parse(spv_mod *m, const uint32_t *code, size_t bytes);
|
||||
/* Writes the module into a malloc'd buffer; returns its size in bytes (0 on failure). */
|
||||
size_t spv_write(const spv_mod *m, uint32_t **out);
|
||||
void spv_free(spv_mod *m);
|
||||
|
||||
static inline uint32_t spv_version(const spv_mod *m) { return m->header[1]; }
|
||||
static inline uint32_t spv_new_id(spv_mod *m) { return m->header[3]++; }
|
||||
|
||||
/* Insert an instruction before index `at` (at = count appends). Operands copied. Returns index. */
|
||||
uint32_t spv_insert(spv_mod *m, uint32_t at, uint16_t op, const uint32_t *ops, uint16_t nops);
|
||||
void spv_remove(spv_mod *m, uint32_t at);
|
||||
|
||||
/* Index of the first instruction with this opcode (or of the defining instruction of `id`), or -1. */
|
||||
int spv_find_op(const spv_mod *m, uint16_t op, uint32_t from);
|
||||
int spv_find_def(const spv_mod *m, uint32_t id);
|
||||
/* The id an instruction defines (result id), or 0. */
|
||||
uint32_t spv_result_id(const spv_inst *in);
|
||||
|
||||
/* Section boundaries: index where new decorations / types / function code may be inserted. */
|
||||
uint32_t spv_types_end(const spv_mod *m); /* end of the types/constants/globals section */
|
||||
uint32_t spv_annotations_end(const spv_mod *m); /* end of the decoration section */
|
||||
|
||||
/* Types and constants: found when they already exist (non-aggregate types must be unique). */
|
||||
uint32_t spv_type_float(spv_mod *m, uint32_t width);
|
||||
uint32_t spv_type_int(spv_mod *m, uint32_t width, uint32_t sign);
|
||||
uint32_t spv_type_bool(spv_mod *m);
|
||||
uint32_t spv_type_pointer(spv_mod *m, uint32_t storage, uint32_t pointee);
|
||||
uint32_t spv_type_array(spv_mod *m, uint32_t elem, uint32_t len_const);
|
||||
uint32_t spv_const_u32(spv_mod *m, uint32_t v);
|
||||
uint32_t spv_const_f32(spv_mod *m, float v);
|
||||
|
||||
/* Decorations of an id: returns the operand (after the decoration) or -1. */
|
||||
int64_t spv_get_decoration(const spv_mod *m, uint32_t id, uint32_t decoration);
|
||||
void spv_remove_decoration(spv_mod *m, uint32_t id, uint32_t decoration);
|
||||
int spv_has_capability(const spv_mod *m, uint32_t cap);
|
||||
void spv_remove_capability(spv_mod *m, uint32_t cap);
|
||||
void spv_add_capability(spv_mod *m, uint32_t cap);
|
||||
/* The execution model of the (first) entry point, or -1. */
|
||||
int spv_execution_model(const spv_mod *m);
|
||||
/* Adds an interface variable to every entry point. */
|
||||
void spv_add_interface(spv_mod *m, uint32_t var);
|
||||
/* Highest Location used by variables of a storage class (members included), or -1. */
|
||||
int spv_max_location(const spv_mod *m, uint32_t storage);
|
||||
/* Length of an OpTypeArray (via its constant), or 0. */
|
||||
uint32_t spv_array_length(const spv_mod *m, uint32_t array_type);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,146 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* DroidDeck Vulkan bridge - server side (bionic on the device, glibc for host tests).
|
||||
*
|
||||
* The server owns the real Vulkan objects. Linux programs in the session talk to it through the
|
||||
* client ICD (tools/vkbridge/client) over a Unix socket; every connection is one client thread.
|
||||
*/
|
||||
#ifndef VKB_SERVER_H
|
||||
#define VKB_SERVER_H
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "vkb_server_iface.h"
|
||||
#include "vkb_proto.h"
|
||||
#include "vkb_log.h"
|
||||
#include "gen/vkb_server_gen.h"
|
||||
|
||||
#define VKB_MAX_TABLES 65536
|
||||
#define VKB_MAX_PDS 16
|
||||
|
||||
/* A client process (all connections that said hello with the same token). */
|
||||
#define VKB_SEQ_RING 65536
|
||||
|
||||
typedef struct vkb_srv_proc {
|
||||
uint64_t token;
|
||||
int pid;
|
||||
int conns;
|
||||
struct vkb_srv_proc *next;
|
||||
/* Ordering of the process's asynchronous requests (vkb_msg_hdr.seq/barrier). */
|
||||
pthread_mutex_t olock;
|
||||
pthread_cond_t ocond;
|
||||
uint64_t watermark; /* every async seq <= this has run */
|
||||
uint8_t done[VKB_SEQ_RING]; /* seqs above the watermark that have run */
|
||||
int conn_closed; /* a connection ended (a seq may never arrive) */
|
||||
} vkb_srv_proc;
|
||||
|
||||
typedef struct vkb_srv_conn {
|
||||
int sock;
|
||||
vkb_srv_proc *proc;
|
||||
/* Receive buffer: messages are parsed out of it in place (srv_recv). */
|
||||
uint8_t *rb;
|
||||
size_t rcap, rpos, rlen;
|
||||
int fdq[2 * VKB_MAX_FDS]; /* descriptors received ahead of their message's turn */
|
||||
int fdn;
|
||||
vkb_arena arena;
|
||||
pthread_t thread;
|
||||
} vkb_srv_conn;
|
||||
|
||||
/* Startup knowledge about each physical device (keyed by deviceUUID). */
|
||||
typedef struct vkb_pd_knowledge {
|
||||
uint8_t uuid[VK_UUID_SIZE];
|
||||
char name[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE];
|
||||
vkb_server_pd_info info;
|
||||
int valid;
|
||||
} vkb_pd_knowledge;
|
||||
|
||||
/* A tracked device memory allocation. */
|
||||
typedef struct vkb_srv_mem {
|
||||
VkDeviceMemory mem;
|
||||
VkDeviceSize size;
|
||||
uint32_t type;
|
||||
uint32_t strategy;
|
||||
void *host_ptr; /* hostptr: server's mapping of the client's memfd */
|
||||
size_t host_len;
|
||||
void *mapped; /* server-side vkMapMemory pointer (for flush/invalidate) */
|
||||
void *ahb; /* AHardwareBuffer* (Android) */
|
||||
struct vkb_srv_mem *next;
|
||||
} vkb_srv_mem;
|
||||
|
||||
struct vkb_emu_device;
|
||||
|
||||
typedef struct vkb_srv_table {
|
||||
uint32_t id;
|
||||
int is_device;
|
||||
vkb_srv_proc *proc;
|
||||
VkInstance instance;
|
||||
VkDevice device;
|
||||
VkPhysicalDevice physical; /* device tables */
|
||||
struct vkb_srv_table *parent; /* device -> instance */
|
||||
vkb_dispatch dt; /* what handlers call (may hold emulation wrappers) */
|
||||
vkb_dispatch real; /* the driver's own entry points */
|
||||
uint32_t api_version;
|
||||
const vkb_pd_knowledge *pd;
|
||||
pthread_mutex_t lock;
|
||||
vkb_srv_mem *mems[256]; /* hash by handle */
|
||||
struct vkb_emu_device *emu;
|
||||
struct vkb_pcache *pcache; /* server_pcache.c */
|
||||
struct vkb_dsmap *dsmap; /* server_dsmap.c */
|
||||
struct vkb_spvfix *spvfix; /* server_spvfix.c */
|
||||
} vkb_srv_table;
|
||||
|
||||
/* server_main.c */
|
||||
extern PFN_vkGetInstanceProcAddr vkb_gipa;
|
||||
extern vkb_dispatch vkb_global_dt;
|
||||
extern int vkb_verbose;
|
||||
|
||||
void vkb_log_summary(void);
|
||||
|
||||
/* server_spvfix.c */
|
||||
void vkb_spvfix_device_init(vkb_srv_table *t);
|
||||
void vkb_spvfix_device_destroy(vkb_srv_table *t);
|
||||
|
||||
/* server_dsmap.c */
|
||||
void vkb_dsmap_device_init(vkb_srv_table *t);
|
||||
void vkb_dsmap_device_destroy(vkb_srv_table *t);
|
||||
|
||||
/* server_pcache.c */
|
||||
void vkb_pcache_set_dir(const char *dir);
|
||||
void vkb_pcache_device_init(vkb_srv_table *t);
|
||||
void vkb_pcache_device_destroy(vkb_srv_table *t);
|
||||
|
||||
/* server_conn.c */
|
||||
void vkb_srv_serve(int listen_fd);
|
||||
vkb_srv_table *vkb_table_get(uint32_t id);
|
||||
uint32_t vkb_table_register(vkb_srv_table *t);
|
||||
void vkb_table_unregister(vkb_srv_table *t);
|
||||
void vkb_srv_reply_status(vkb_srv_call *c, uint32_t status);
|
||||
/* The table of the call being executed on this thread (emulation wrappers use it). */
|
||||
vkb_srv_table *vkb_srv_current_table(void);
|
||||
void vkb_srv_set_current_table(vkb_srv_table *t);
|
||||
void vkb_proc_cleanup(vkb_srv_proc *p);
|
||||
|
||||
/* server_instance.c */
|
||||
void vkb_selftest_all(void);
|
||||
const vkb_pd_knowledge *vkb_pd_lookup(const vkb_dispatch *dt, VkPhysicalDevice pd);
|
||||
extern vkb_pd_knowledge vkb_pds[VKB_MAX_PDS];
|
||||
extern int vkb_npds;
|
||||
|
||||
/* server_memory.c */
|
||||
extern PFN_vkGetDeviceProcAddr vkb_mem_gdpa;
|
||||
void vkb_mem_selftest(vkb_pd_knowledge *k, VkInstance inst, const vkb_dispatch *idt, VkPhysicalDevice pd);
|
||||
const char *const *vkb_mem_required_extensions(uint32_t strategy, uint32_t *count);
|
||||
void vkb_mem_track(vkb_srv_table *dev, vkb_srv_mem *m);
|
||||
vkb_srv_mem *vkb_mem_find(vkb_srv_table *dev, VkDeviceMemory mem, int remove);
|
||||
void vkb_mem_release(vkb_srv_table *dev, vkb_srv_mem *m);
|
||||
void vkb_mem_free_all(vkb_srv_table *dev);
|
||||
|
||||
/* emulation (server_emu*.c) */
|
||||
uint32_t vkb_emu_detect(const vkb_dispatch *idt, VkPhysicalDevice pd, uint32_t *missing);
|
||||
void vkb_emu_device_create_info(vkb_srv_table *inst, VkPhysicalDevice pd, uint32_t emu,
|
||||
VkDeviceCreateInfo *ci, vkb_arena *a);
|
||||
void vkb_emu_device_init(vkb_srv_table *dev, uint32_t emu);
|
||||
void vkb_emu_device_destroy(vkb_srv_table *dev);
|
||||
|
||||
#endif
|
||||
@@ -146,6 +146,8 @@ class MainActivity : ComponentActivity() {
|
||||
private var gameCores by mutableStateOf<Set<Int>>(emptySet())
|
||||
private var tuSysmem by mutableStateOf(false)
|
||||
private var zinkLazy by mutableStateOf(false)
|
||||
private var vkBridge by mutableStateOf(false)
|
||||
private val adrenoGpu by lazy { com.droiddeck.launcher.core.DeviceSupport.adreno() }
|
||||
private var noXalia by mutableStateOf(true)
|
||||
private var fastSync by mutableStateOf(false)
|
||||
private var syncFallback by mutableStateOf(true)
|
||||
@@ -554,6 +556,7 @@ class MainActivity : ComponentActivity() {
|
||||
FrontEndScreen(
|
||||
FrontEndState(
|
||||
installed = installed, ready = ready, available = available?.version, removalPending = removalPending, runtimeActionsBlocked = runtimeChangesBlocked(),
|
||||
vkBridge = vkBridge, adreno = adrenoGpu,
|
||||
shortcutPicker = shortcutPicker,
|
||||
shortcutLibraryScanning = shortcutLibraryScanning,
|
||||
gameSyncFolder = gameSyncFolder,
|
||||
@@ -681,6 +684,7 @@ class MainActivity : ComponentActivity() {
|
||||
onInstallPackage = { id -> installPackage(id) },
|
||||
onRemovePackage = { id -> removePackage(id) },
|
||||
onRuntime = { onRuntimeButton() },
|
||||
onVkBridge = { on -> SessionPrefs.setVkBridge(this, on); vkBridge = on },
|
||||
onFrameGenPick = { mode ->
|
||||
FrameGen.set(this, mode)
|
||||
frameGenLabel = FrameGen.label(this)
|
||||
@@ -1575,6 +1579,7 @@ class MainActivity : ComponentActivity() {
|
||||
gameCores = CpuCores.parse(SessionPrefs.gameCpus(this)).ifEmpty { CpuCores.all.toSet() }
|
||||
tuSysmem = SessionPrefs.tuSysmem(this)
|
||||
zinkLazy = SessionPrefs.zinkLazy(this)
|
||||
vkBridge = SessionPrefs.vkBridge(this)
|
||||
noXalia = SessionPrefs.noXalia(this)
|
||||
fastSync = SessionPrefs.fastSync(this)
|
||||
syncFallback = SessionPrefs.syncFallback(this)
|
||||
@@ -1677,7 +1682,7 @@ class MainActivity : ComponentActivity() {
|
||||
// The runtime draws with Turnip, an Adreno driver: on Mali, Xclipse or PowerVR the
|
||||
// compositor gets no usable Vulkan device and a session is sound over a black screen.
|
||||
// Said before the download, not after it; the user may still go ahead.
|
||||
if (installed == null && !com.droiddeck.launcher.core.DeviceSupport.graphicsAvailable()) { showNonAdreno = release; return }
|
||||
if (installed == null && !com.droiddeck.launcher.core.DeviceSupport.canDraw(this)) { showNonAdreno = release; return }
|
||||
install(release)
|
||||
}
|
||||
|
||||
@@ -1689,7 +1694,7 @@ class MainActivity : ComponentActivity() {
|
||||
showPhantomGate = true
|
||||
return false
|
||||
}
|
||||
val warn = installed == null && !com.droiddeck.launcher.core.DeviceSupport.graphicsAvailable()
|
||||
val warn = installed == null && !com.droiddeck.launcher.core.DeviceSupport.canDraw(this)
|
||||
if (warn && available != null) { showNonAdreno = available; return false }
|
||||
startActivity(intent)
|
||||
return true
|
||||
|
||||
@@ -17,6 +17,17 @@ object DeviceSupport {
|
||||
|
||||
internal fun graphicsAvailable(adreno: Boolean, powerVrReady: Boolean): Boolean = adreno || powerVrReady
|
||||
|
||||
/**
|
||||
* Whether the Linux side draws through the Vulkan bridge (tools/vkbridge, PR #517): asked for in
|
||||
* Setup, and only on a GPU that is neither an Adreno nor one the system-Vulkan path (PowerVR,
|
||||
* Xclipse) already handles. An Adreno keeps Turnip whatever the setting says.
|
||||
*/
|
||||
fun vkBridge(context: Context): Boolean =
|
||||
!adreno() && !SystemVulkanDriver.isAvailable() && com.droiddeck.launcher.session.SessionPrefs.vkBridge(context)
|
||||
|
||||
/** A session can draw: Adreno with Turnip, PowerVR/Xclipse on the system driver, or the bridge. */
|
||||
fun canDraw(context: Context): Boolean = graphicsAvailable() || vkBridge(context)
|
||||
|
||||
/** The chip as the device names it, for the card that explains the refusal. */
|
||||
fun gpuName(context: Context): String {
|
||||
val soc = if (Build.VERSION.SDK_INT >= 31) Build.SOC_MODEL.takeIf { it.isNotBlank() && it != Build.UNKNOWN } else null
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.droiddeck.launcher.gpu
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import android.util.Log
|
||||
import com.droiddeck.launcher.core.SessionPart
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Clocks and thermal state through a Mali bridge session, into the session folder
|
||||
* (vkbridge-clocks.log), for telling the bridge's overhead apart from the phone holding the chip
|
||||
* back: Samsung's SSRM lowers the CPU policies' scaling_max_freq when the device warms, so a
|
||||
* policy whose max is below its cpuinfo_max_freq is being clamped (upstream issue #64).
|
||||
*
|
||||
* Every [PERIOD_MS]: each CPU policy as cur/max(hardware max) MHz, the GPU clock from the first
|
||||
* Mali/MediaTek source the app may read, and Android's thermal status and headroom. Which GPU
|
||||
* sources were readable is logged once at the start - SELinux refuses apps most of them, each
|
||||
* vendor differently, and this is how the next run finds out which.
|
||||
*/
|
||||
class ClockLogComponent(private val logFile: File) : SessionPart() {
|
||||
@Volatile private var running = false
|
||||
private var thread: Thread? = null
|
||||
|
||||
override fun start() {
|
||||
running = true
|
||||
thread = Thread({ run() }, "clock-log").apply { isDaemon = true; start() }
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
running = false
|
||||
thread?.interrupt()
|
||||
thread = null
|
||||
}
|
||||
|
||||
private fun run() {
|
||||
val policies = File("/sys/devices/system/cpu/cpufreq").listFiles { f -> f.name.startsWith("policy") }
|
||||
?.sortedBy { it.name.removePrefix("policy").toIntOrNull() ?: 0 } ?: emptyList()
|
||||
val gpuCandidates = gpuSources()
|
||||
// The /proc status files hold several numbers: listed for whether they read, not taken as the clock.
|
||||
val gpu = gpuCandidates.firstOrNull { !it.startsWith("/proc/") && firstNumber(it) != null }
|
||||
val power = app().getSystemService(Context.POWER_SERVICE) as? PowerManager
|
||||
val time = SimpleDateFormat("HH:mm:ss", Locale.US)
|
||||
try {
|
||||
FileWriter(logFile, true).use { w ->
|
||||
w.write("== clocks: ${Build.MANUFACTURER} ${Build.MODEL} (${Build.HARDWARE}), every ${PERIOD_MS / 1000}s\n")
|
||||
w.write("cpu policies: ${policies.joinToString { p -> p.name + " " + (File(p, "related_cpus").readTextOrNull()?.trim() ?: "?") }}\n")
|
||||
for (source in gpuCandidates) {
|
||||
val f = File(source)
|
||||
val state = when {
|
||||
!f.exists() -> continue
|
||||
firstNumber(source) != null -> "readable"
|
||||
else -> "refused"
|
||||
}
|
||||
w.write("gpu source $source: $state\n")
|
||||
}
|
||||
w.write("gpu clock from: ${gpu ?: "nothing readable"}\n")
|
||||
w.flush()
|
||||
while (running && logFile.length() < MAX_BYTES) {
|
||||
val line = StringBuilder(time.format(Date()))
|
||||
for (p in policies) {
|
||||
val cur = firstNumber(File(p, "scaling_cur_freq").path)
|
||||
val max = firstNumber(File(p, "scaling_max_freq").path)
|
||||
val hw = firstNumber(File(p, "cpuinfo_max_freq").path)
|
||||
line.append(" ${p.name.removePrefix("policy").let { "p$it" }} ${mhz(cur)}/${mhz(max)}")
|
||||
if (hw != null && max != null && max < hw) line.append("(of ${mhz(hw)})")
|
||||
}
|
||||
gpu?.let { line.append(" | gpu ${mhz(firstNumber(it))} MHz") }
|
||||
if (power != null && Build.VERSION.SDK_INT >= 29) {
|
||||
line.append(" | thermal ${power.currentThermalStatus}")
|
||||
if (Build.VERSION.SDK_INT >= 30) {
|
||||
val h = power.getThermalHeadroom(10)
|
||||
if (!h.isNaN()) line.append(String.format(Locale.US, " headroom %.2f", h))
|
||||
}
|
||||
}
|
||||
w.write(line.append('\n').toString())
|
||||
w.flush()
|
||||
try { Thread.sleep(PERIOD_MS) } catch (e: InterruptedException) { break }
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "clock log stopped: $e")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "SessionService"
|
||||
private const val PERIOD_MS = 2000L
|
||||
private const val MAX_BYTES = 4L shl 20
|
||||
|
||||
/** Mali (Exynos, Tensor, MediaTek) and MediaTek GED clock files, plus any GPU devfreq node. */
|
||||
private fun gpuSources(): List<String> {
|
||||
val list = mutableListOf(
|
||||
"/sys/kernel/ged/hal/current_freqency", "/sys/kernel/ged/hal/current_frequency",
|
||||
"/proc/gpufreqv2/gpufreq_status", "/proc/gpufreq/gpufreq_var_dump",
|
||||
"/sys/kernel/gpu/gpu_clock", "/sys/class/misc/mali0/device/cur_freq",
|
||||
"/sys/class/misc/mali0/device/clock",
|
||||
)
|
||||
File("/sys/class/devfreq").list()?.sorted()?.forEach { node ->
|
||||
if (node.contains("mali", true) || node.contains("gpu", true)) list.add("/sys/class/devfreq/$node/cur_freq")
|
||||
}
|
||||
return list.distinct()
|
||||
}
|
||||
|
||||
private fun File.readTextOrNull(): String? = runCatching { readText() }.getOrNull()
|
||||
|
||||
private fun firstNumber(path: String): Long? =
|
||||
runCatching { Regex("\\d+").find(File(path).readText())?.value?.toLong() }.getOrNull()
|
||||
|
||||
/** Hz, kHz or MHz to MHz; "?" when unread. */
|
||||
private fun mhz(raw: Long?): String = when {
|
||||
raw == null -> "?"
|
||||
raw > 10_000_000 -> (raw / 1_000_000).toString()
|
||||
raw > 10_000 -> (raw / 1_000).toString()
|
||||
else -> raw.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -342,6 +342,13 @@ public final class TurnipDriver {
|
||||
if (forced.startsWith("a7")) return DRIVER_A7XX;
|
||||
}
|
||||
if (SystemVulkanDriver.isPowerVr()) return null;
|
||||
// Not an Adreno (Mali, Xclipse...): Turnip cannot open a GPU without KGSL. The compositor
|
||||
// runs on the phone's own driver, which has the dma-buf import it needs (the Mali bridge
|
||||
// depends on it too).
|
||||
if (!com.droiddeck.launcher.core.DeviceSupport.INSTANCE.adreno()) {
|
||||
Log.i(TAG, "no KGSL: the compositor uses the system Vulkan driver");
|
||||
return null;
|
||||
}
|
||||
String model = gpuModel();
|
||||
Log.i(TAG, "gpu model: " + (model == null ? "unknown" : model));
|
||||
if (model != null) {
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.droiddeck.launcher.gpu
|
||||
|
||||
import android.os.Process
|
||||
import android.util.Log
|
||||
import com.droiddeck.launcher.core.HostProcess
|
||||
import com.droiddeck.launcher.core.SessionPart
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* The Vulkan bridge server (tools/vkbridge, app/src/main/cpp/vkbridge) for one session.
|
||||
*
|
||||
* On a GPU without Adreno's KGSL nothing inside the Linux runtime can reach the GPU: Turnip needs
|
||||
* KGSL, and the phone's own driver is a bionic library a glibc program cannot load. The server is
|
||||
* a bionic process the app starts here - executed from the native library directory like the
|
||||
* DirectAudio relay - which loads the system Vulkan loader and runs the Vulkan calls the
|
||||
* session's programs send it through the bridge ICD over a Unix socket. It is the app's child, so
|
||||
* it dies with the app (it also asks for that with PR_SET_PDEATHSIG).
|
||||
*
|
||||
* Started before the guest and waited for: the server runs a self-test first (which memory
|
||||
* sharing works on this GPU), and only then listens; a program that starts before the socket
|
||||
* exists would find no Vulkan driver.
|
||||
*/
|
||||
class VkBridgeComponent(private val socket: File, private val logFile: File) : SessionPart() {
|
||||
@Volatile private var pid = -1
|
||||
@Volatile private var exited = false
|
||||
|
||||
override fun start() {
|
||||
stop()
|
||||
val binary = File(app().applicationInfo.nativeLibraryDir, BINARY)
|
||||
if (!binary.isFile) {
|
||||
Log.e(TAG, "server missing at $binary; the session will have no Vulkan")
|
||||
return
|
||||
}
|
||||
socket.parentFile?.mkdirs()
|
||||
// Pipeline cache kept across sessions (Mali recompiles everything in a new process otherwise).
|
||||
val cacheDir = File(app().cacheDir, "vkbridge-pipelines").apply { mkdirs() }
|
||||
if (VkBridgeNative.start(app(), socket, logFile, cacheDir)) {
|
||||
Log.i(TAG, "server ready in the app's process at $socket")
|
||||
return
|
||||
}
|
||||
socket.delete()
|
||||
exited = false
|
||||
val cmd = listOf(binary.absolutePath, "--socket", socket.absolutePath, "--log", logFile.absolutePath,
|
||||
"--cache-dir", cacheDir.absolutePath)
|
||||
.joinToString(" ") { it.replace("\\", "\\\\").replace(" ", "\\ ") }
|
||||
val env = arrayOf("HOME=" + app().filesDir)
|
||||
pid = HostProcess.start(cmd, env, app().filesDir, { status ->
|
||||
exited = true
|
||||
Log.w(TAG, "server exited with status $status")
|
||||
runCatching { logFile.appendText("== vkbridge server exited with status $status\n") }
|
||||
}, { line -> Log.i(TAG, line) })
|
||||
// The self-test takes about a second; a cold driver can take longer.
|
||||
val deadline = System.currentTimeMillis() + 15_000L
|
||||
while (!socket.exists() && !exited && System.currentTimeMillis() < deadline) {
|
||||
try { Thread.sleep(50L) } catch (e: InterruptedException) { Thread.currentThread().interrupt(); break }
|
||||
}
|
||||
Log.i(TAG, if (socket.exists()) "server ready (pid $pid) at $socket" else "server NOT ready (pid $pid, exited=$exited); see $logFile")
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
// The in-app server outlives sessions: its socket stays.
|
||||
if (pid > 0) {
|
||||
Process.killProcess(pid)
|
||||
pid = -1
|
||||
}
|
||||
if (!VkBridgeNative.serving) socket.delete()
|
||||
}
|
||||
|
||||
override fun suspendPid(): Int = pid
|
||||
|
||||
companion object {
|
||||
private const val TAG = "VkBridge"
|
||||
/** The server, named as a library so Android extracts it and lets the app run it. */
|
||||
const val BINARY = "libvkbridge_server.so"
|
||||
/** Inside the runtime (staged by SessionFiles). */
|
||||
const val GUEST_ICD = "/usr/local/share/vkbridge/droidbridge_icd.json"
|
||||
const val GUEST_LIB = "/usr/local/lib/vkbridge/libvulkan_droidbridge.so"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.droiddeck.launcher.gpu
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.util.Log
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* The bridge server inside the app's own process (libvkbridge_jni.so, server_main.c).
|
||||
*
|
||||
* Android chooses the GPU driver - Samsung's updatable one included - while the app starts up, and
|
||||
* only for the app's process: the server run as a separate executable got the plain /vendor driver,
|
||||
* which on the Tab S10+ lacked VK_EXT_robustness2 and more that the app itself sees. Started by the
|
||||
* first bridge session and kept for the app's life; later sessions only point its log at their
|
||||
* own folder. VkBridgeComponent falls back to the executable when this cannot start.
|
||||
*/
|
||||
object VkBridgeNative {
|
||||
private const val TAG = "VkBridge"
|
||||
|
||||
@Volatile private var loaded: Boolean? = null
|
||||
|
||||
/**
|
||||
* The packaged library, or - in a debuggable build only - files/vkbridge/dev/libvkbridge_jni.so
|
||||
* when present, so a rebuilt server can be tried with `adb push` + `run-as cp` and an app
|
||||
* restart instead of a new APK (the app targets SDK 28 and may load code from its files).
|
||||
*/
|
||||
private fun load(context: Context): Boolean {
|
||||
loaded?.let { return it }
|
||||
val ok = try {
|
||||
val dev = File(context.filesDir, "vkbridge/dev/libvkbridge_jni.so")
|
||||
val debuggable = context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0
|
||||
if (debuggable && dev.isFile) {
|
||||
Log.w(TAG, "in-app server from the development override $dev")
|
||||
System.load(dev.absolutePath)
|
||||
} else {
|
||||
System.loadLibrary("vkbridge_jni")
|
||||
}
|
||||
true
|
||||
} catch (e: Throwable) {
|
||||
Log.w(TAG, "in-app server unavailable: $e")
|
||||
false
|
||||
}
|
||||
loaded = ok
|
||||
return ok
|
||||
}
|
||||
|
||||
/** Whether the in-app server is serving (its socket must then be left alone). */
|
||||
@Volatile var serving = false
|
||||
private set
|
||||
|
||||
/** Starts the server, or points its log at [log] when it already runs; false when it cannot serve. */
|
||||
fun start(context: Context, socket: File, log: File, cacheDir: File): Boolean {
|
||||
if (!load(context)) return false
|
||||
val ok = try {
|
||||
nativeStart(socket.absolutePath, log.absolutePath, cacheDir.absolutePath) == 1
|
||||
} catch (e: Throwable) {
|
||||
Log.w(TAG, "in-app server failed to start: $e")
|
||||
false
|
||||
}
|
||||
if (ok) serving = true
|
||||
return ok
|
||||
}
|
||||
|
||||
@JvmStatic private external fun nativeStart(socket: String, log: String, cacheDir: String): Int
|
||||
}
|
||||
@@ -229,6 +229,7 @@ object BwrapSpawner {
|
||||
* own choice of driver (VK_ICD_FILENAMES, MESA_LOADER_DRIVER_OVERRIDE) is left alone.
|
||||
*/
|
||||
private fun gpu(rootfs: String, root: File, env: MutableMap<String, String>): List<Bind>? {
|
||||
env["VKBRIDGE_SOCKET"]?.let { sock -> if (File(sock).exists()) return bridgeGpu(rootfs, root, env, sock) }
|
||||
val lib = File(rootfs, "usr/lib")
|
||||
val driver = File(lib, "libvulkan_freedreno.so")
|
||||
if (!driver.isFile || !File("/dev/kgsl-3d0").exists()) return null
|
||||
@@ -258,6 +259,32 @@ object BwrapSpawner {
|
||||
return binds
|
||||
}
|
||||
|
||||
/**
|
||||
* The GPU through the Mali bridge (tools/vkbridge), for a sandbox started from a bridge
|
||||
* session: the bridge ICD (it needs nothing but libc) and the server's socket, at the paths
|
||||
* the session uses. Unlike Turnip on KGSL it can also present to KWin's shared-memory
|
||||
* windows, so desktop apps get it too. The session's own VK_DRIVER_FILES points at the
|
||||
* runtime's copy of the manifest, which the sandbox cannot see; it is replaced here.
|
||||
*/
|
||||
private fun bridgeGpu(rootfs: String, root: File, env: MutableMap<String, String>, socket: String): List<Bind>? {
|
||||
val driver = File(rootfs, "usr/local/lib/vkbridge/libvulkan_droidbridge.so")
|
||||
if (!driver.isFile) { Log.w(TAG, "gpu: vkbridge session but no bridge ICD in the runtime"); return null }
|
||||
val binds = ArrayList<Bind>()
|
||||
binds.add(Bind(driver.canonicalPath, "$GPU_DIR/libvulkan_droidbridge.so"))
|
||||
val sockDir = File(socket).parent ?: return null
|
||||
binds.add(Bind(sockDir, sockDir))
|
||||
val icd = File(root.parentFile, "vkbridge_icd.json")
|
||||
icd.writeText("{\"file_format_version\": \"1.0.0\", \"ICD\": {\"library_path\": \"$GPU_DIR/libvulkan_droidbridge.so\", \"api_version\": \"1.3.247\"}}\n")
|
||||
binds.add(Bind(icd.path, "$GPU_DIR/vkbridge_icd.json"))
|
||||
env["VK_DRIVER_FILES"] = "$GPU_DIR/vkbridge_icd.json"
|
||||
env["VK_ICD_FILENAMES"] = "$GPU_DIR/vkbridge_icd.json"
|
||||
if (!env.containsKey("MESA_LOADER_DRIVER_OVERRIDE") && !env.containsKey("GALLIUM_DRIVER")) {
|
||||
env["MESA_LOADER_DRIVER_OVERRIDE"] = "zink"
|
||||
env["GALLIUM_DRIVER"] = "zink"
|
||||
}
|
||||
return binds
|
||||
}
|
||||
|
||||
/**
|
||||
* What every program in the session preloads (SessionFiles writes /etc/ld.so.preload): the
|
||||
* session shim - SysV shared memory for X11, the network calls Android refuses - and the
|
||||
|
||||
@@ -332,10 +332,40 @@ public final class LinuxRuntime {
|
||||
* may open) stands in: it appears as a render node with the sysfs entries libdrm reads, and our
|
||||
* Turnip build reports the same device numbers for it.
|
||||
*/
|
||||
/** Mali's kbase device, the stand-in render node on a Mali GPU (the Mali bridge's gamescope). */
|
||||
private static final String MALI_DEVICE = "/dev/mali0";
|
||||
|
||||
/**
|
||||
* The device that stands in for the GPU's render node: KGSL on an Adreno; with the Mali bridge
|
||||
* on, Mali's own device - nothing inside opens it for rendering (the bridge does that on the
|
||||
* Android side), but gamescope and libdrm need a render node to name before they offer
|
||||
* linux-dmabuf, and the bridge ICD reports this one's numbers. Null when there is none.
|
||||
*/
|
||||
public static String gpuStandIn(Context context) {
|
||||
if (new File(KGSL_DEVICE).exists()) return KGSL_DEVICE;
|
||||
if (com.droiddeck.launcher.core.DeviceSupport.INSTANCE.vkBridge(context) && new File(MALI_DEVICE).exists()) return MALI_DEVICE;
|
||||
return null;
|
||||
}
|
||||
|
||||
/** "major:minor" of a device node, or null. */
|
||||
public static String deviceNumbers(String path) {
|
||||
try {
|
||||
long dev = Os.stat(path).st_rdev;
|
||||
long major = ((dev >> 8) & 0xfff) | ((dev >> 32) & ~0xfffL);
|
||||
long minor = (dev & 0xff) | ((dev >> 12) & ~0xffL);
|
||||
return major + ":" + minor;
|
||||
} catch (ErrnoException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void bindGpuNode(Context context, List<String> cmd) {
|
||||
String standIn = gpuStandIn(context);
|
||||
if (standIn == null) return;
|
||||
boolean kgsl = standIn.equals(KGSL_DEVICE);
|
||||
StructStat st;
|
||||
try {
|
||||
st = Os.stat(KGSL_DEVICE);
|
||||
st = Os.stat(standIn);
|
||||
} catch (ErrnoException e) {
|
||||
return;
|
||||
}
|
||||
@@ -355,7 +385,8 @@ public final class LinuxRuntime {
|
||||
Files.write(new File(drm, "dev").toPath(),
|
||||
(major + ":" + minor + "\n").getBytes(StandardCharsets.UTF_8));
|
||||
Files.write(new File(device, "uevent").toPath(),
|
||||
"DRIVER=kgsl-3d0\nMODALIAS=platform:kgsl-3d0\n".getBytes(StandardCharsets.UTF_8));
|
||||
(kgsl ? "DRIVER=kgsl-3d0\nMODALIAS=platform:kgsl-3d0\n" : "DRIVER=mali\nMODALIAS=platform:mali\n")
|
||||
.getBytes(StandardCharsets.UTF_8));
|
||||
File subsystem = new File(device, "subsystem");
|
||||
if (!Files.isSymbolicLink(subsystem.toPath())) {
|
||||
Os.symlink("/sys/bus/platform", subsystem.getPath());
|
||||
@@ -363,7 +394,7 @@ public final class LinuxRuntime {
|
||||
// What MangoHud names a GPU's driver by; msm_drm is how an Adreno's render node reads
|
||||
// on a mainline kernel, and the driver it reads the load of (bindAdrenoStats).
|
||||
File driver = new File(device, "driver");
|
||||
if (!Files.isSymbolicLink(driver.toPath())) {
|
||||
if (kgsl && !Files.isSymbolicLink(driver.toPath())) {
|
||||
Os.symlink("/sys/bus/platform/drivers/msm_drm", driver.getPath());
|
||||
}
|
||||
} catch (IOException | ErrnoException e) {
|
||||
@@ -371,7 +402,7 @@ public final class LinuxRuntime {
|
||||
}
|
||||
bind(cmd, new File(base, "sys").getPath() + ":/sys/dev/char");
|
||||
bind(cmd, dri.getPath() + ":/dev/dri");
|
||||
bind(cmd, KGSL_DEVICE + ":/dev/dri/" + node);
|
||||
bind(cmd, standIn + ":/dev/dri/" + node);
|
||||
bindDrmClass(base, cmd, node, major + ":" + minor);
|
||||
}
|
||||
|
||||
|
||||
@@ -143,6 +143,10 @@ object SessionFiles {
|
||||
} + listOf("libthunkaudit.so", "libvulkan-thunk.so").map { "x86_64/$it" to "usr/local/lib/droiddeck-fex/x86_64/$it" }
|
||||
val optional = (arrayOf(
|
||||
"usr/local/bin/gamescope" to "usr/local/bin/gamescope",
|
||||
// The Mali bridge's Linux side (tools/vkbridge): the ICD, its manifest and the self-test.
|
||||
"usr/local/lib/vkbridge/libvulkan_droidbridge.so" to "usr/local/lib/vkbridge/libvulkan_droidbridge.so",
|
||||
"usr/local/share/vkbridge/droidbridge_icd.json" to "usr/local/share/vkbridge/droidbridge_icd.json",
|
||||
"usr/local/bin/droiddeck-vkbridge-smoke" to "usr/local/bin/droiddeck-vkbridge-smoke",
|
||||
"usr/local/lib/droiddeck/uruntime" to "usr/local/lib/droiddeck/uruntime",
|
||||
"usr/local/share/licenses/uruntime/LICENSE" to "usr/local/share/licenses/uruntime/LICENSE",
|
||||
) + mangoapp + xclipse.map { it to it } + listOf("comet", "GalaxyCommunication.exe", "update-permissions.exe", "LICENSE", "version").map { "usr/local/lib/droiddeck-comet/$it" to "usr/local/lib/droiddeck-comet/$it" } + msitools.map { it to it } + fexPreloads).filter { (asset, _) ->
|
||||
|
||||
@@ -497,6 +497,18 @@ object SessionPrefs {
|
||||
*/
|
||||
fun zinkLazy(context: Context): Boolean = prefs(context).getBoolean("zinkLazy", true)
|
||||
|
||||
/**
|
||||
* The Mali bridge (experimental): the session's Vulkan goes through the app's bridge server to
|
||||
* the phone's own driver instead of Turnip (tools/vkbridge). Only offered, and only used, on a
|
||||
* GPU without Adreno's KGSL; off by default everywhere.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun vkBridge(context: Context): Boolean = prefs(context).getBoolean("vkBridge", false)
|
||||
|
||||
fun setVkBridge(context: Context, on: Boolean) {
|
||||
prefs(context).edit().putBoolean("vkBridge", on).apply()
|
||||
}
|
||||
|
||||
fun setZinkLazy(context: Context, on: Boolean) {
|
||||
prefs(context).edit().putBoolean("zinkLazy", on).apply()
|
||||
}
|
||||
|
||||
@@ -372,6 +372,7 @@ class SessionService : Service() {
|
||||
val fastPathAt = guest.size
|
||||
|
||||
val pulse = startAudio(guest, sessionDir)
|
||||
startVkBridge(guest, sessionDir)
|
||||
|
||||
guest.add("BL_WIDTH=" + size.first)
|
||||
guest.add("BL_HEIGHT=" + size.second)
|
||||
@@ -629,10 +630,13 @@ class SessionService : Service() {
|
||||
// import falls back to. One driver for every session: the driver's shader cache is keyed on
|
||||
// its build, and with one per mode every emulator compiled its shaders twice.
|
||||
val linuxDriverId = SessionPrefs.linuxDriver(this)
|
||||
if (SystemVulkanDriver.usesDefault(linuxDriverId)) {
|
||||
// Mali bridge (PR #517) on: the bridge ICD is the driver (startVkBridge). Otherwise the system
|
||||
// PowerVR/Xclipse path (PR #570) or the imported/runtime Turnip, as before.
|
||||
val bridge = com.droiddeck.launcher.core.DeviceSupport.vkBridge(this)
|
||||
if (!bridge && SystemVulkanDriver.usesDefault(linuxDriverId)) {
|
||||
SystemVulkanDriver.addEnvironment(this, guest)
|
||||
}
|
||||
LinuxVulkanDriver.resolveIcdPath(this, linuxDriverId)
|
||||
(if (bridge) null else LinuxVulkanDriver.resolveIcdPath(this, linuxDriverId))
|
||||
?.let { guest.add(LinuxVulkanDriver.ENV + "=" + it) }
|
||||
// Turnip's own debug switches, for the runtime's driver and everything on it. The file in
|
||||
// Downloads holds the value verbatim ("sysmem", "sysmem,deck_emu"); with nothing there, an
|
||||
@@ -696,6 +700,35 @@ class SessionService : Service() {
|
||||
.takeIf { it.isNotEmpty() }?.let { guest.add("BL_GAME_CPUS=$it") }
|
||||
}
|
||||
|
||||
/**
|
||||
* The Mali bridge (Setup's experimental switch, non-Adreno GPUs only): the server that runs the
|
||||
* session's Vulkan calls on the phone's own driver, started before the guest. The session
|
||||
* script points the Vulkan loader at the bridge ICD when it sees BL_VKBRIDGE and runs the
|
||||
* bridge's self-test into the session log first; both sides log into the session folder.
|
||||
*/
|
||||
private fun startVkBridge(guest: MutableList<String>, sessionDir: File) {
|
||||
if (!com.droiddeck.launcher.core.DeviceSupport.vkBridge(this)) return
|
||||
// Under the files dir, which the session binds at its own path: one string for both sides.
|
||||
val socket = File(filesDir, "vkbridge/server.sock")
|
||||
val bridge = com.droiddeck.launcher.gpu.VkBridgeComponent(socket, File(sessionDir, "vkbridge-server.log"))
|
||||
bridge.attach(this)
|
||||
components.add(bridge)
|
||||
com.droiddeck.launcher.gpu.ClockLogComponent(File(sessionDir, "vkbridge-clocks.log")).let {
|
||||
it.attach(this)
|
||||
components.add(it)
|
||||
}
|
||||
guest.add("BL_VKBRIDGE=1")
|
||||
guest.add("VKBRIDGE_SOCKET=" + socket.path)
|
||||
guest.add("VKBRIDGE_CLIENT_LOG=" + File(sessionDir, "vkbridge-client.log").path)
|
||||
// The render node gamescope sees is Mali's device standing in (LinuxRuntime.bindGpuNode):
|
||||
// the ICD reports its numbers, and the session shim answers PRIME calls on it as on KGSL.
|
||||
LinuxRuntime.gpuStandIn(this)?.let { node ->
|
||||
guest.add("BL_DRM_STANDIN=$node")
|
||||
LinuxRuntime.deviceNumbers(node)?.let { guest.add("VKBRIDGE_DRM_RENDER=$it") }
|
||||
}
|
||||
Log.i(TAG, "vkbridge: on (${com.droiddeck.launcher.core.DeviceSupport.gpuName(this)}), socket $socket")
|
||||
}
|
||||
|
||||
/** PulseAudio, and the DirectAudio relay when a path needs it; returns the daemon for suspend and resume. */
|
||||
private fun startAudio(guest: MutableList<String>, sessionDir: File): PulseAudioComponent {
|
||||
// PulseAudio always: the client is a native Linux program and has no other way to make a
|
||||
|
||||
@@ -130,6 +130,10 @@ class FrontEndState(
|
||||
val oscMode: String = SessionPrefs.OSC_AUTO,
|
||||
val controller: com.droiddeck.launcher.input.ControllerPrefs.Settings? = null,
|
||||
val phantomProcessStatus: PhantomProcessStatus = PhantomProcessStatus.NOT_APPLICABLE,
|
||||
/** The GPU is an Adreno (Turnip); otherwise Setup offers the experimental Mali bridge. */
|
||||
val adreno: Boolean = true,
|
||||
/** Setup's "Mali bridge (experimental)" (SessionPrefs.vkBridge). */
|
||||
val vkBridge: Boolean = false,
|
||||
val showPhantomGate: Boolean = false,
|
||||
val orientation: Int = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
|
||||
val launcherFullscreen: Boolean = true,
|
||||
@@ -187,6 +191,7 @@ class FrontEndActions(
|
||||
val onInstallPackage: (String) -> Unit,
|
||||
val onRemovePackage: (String) -> Unit,
|
||||
val onRuntime: () -> Unit,
|
||||
val onVkBridge: (Boolean) -> Unit = {},
|
||||
val onFrameGenPick: (FrameGen.Mode) -> Unit,
|
||||
val onImportLossless: () -> Unit,
|
||||
val onProtons: () -> Unit,
|
||||
|
||||
@@ -212,9 +212,17 @@ internal fun SetupPanel(
|
||||
when (gpu.support) {
|
||||
com.droiddeck.launcher.gpu.GpuInfo.Support.TESTED -> stringResource(R.string.setup_gpu_detail, gpu.displayName(ctx), gpuName)
|
||||
com.droiddeck.launcher.gpu.GpuInfo.Support.UNTESTED -> stringResource(R.string.setup_gpu_untested_detail, gpu.displayName(ctx), gpu.supportText(ctx).replaceFirstChar { it.lowercase() })
|
||||
else -> stringResource(R.string.setup_gpu_unsupported_detail, gpuName)
|
||||
else -> if (s.vkBridge) stringResource(R.string.setup_gpu_bridge_detail, gpuName)
|
||||
else stringResource(R.string.setup_gpu_unsupported_detail, gpuName)
|
||||
},
|
||||
divider = divider,
|
||||
// Not an Adreno: the experimental Vulkan bridge is the one way to draw.
|
||||
action = if (s.adreno) null else { {
|
||||
SecondaryButton(
|
||||
stringResource(if (s.vkBridge) R.string.setup_gpu_bridge_on else R.string.setup_gpu_bridge_off),
|
||||
enabled = !s.sessionRunning, compact = true, onClick = { a.onVkBridge(!s.vkBridge) },
|
||||
)
|
||||
} },
|
||||
)
|
||||
}
|
||||
val runtimeCheck: @Composable (Boolean) -> Unit = { divider ->
|
||||
|
||||
@@ -2322,4 +2322,7 @@
|
||||
<string name="gog_achievements_enable_store">Enable GOG achievements in Stores settings</string>
|
||||
<string name="gog_achievements_unavailable">Achievements unavailable; check session logs</string>
|
||||
<string name="achievement_unlocked">Achievement unlocked</string>
|
||||
<string name="setup_gpu_bridge_off">Mali bridge: off</string>
|
||||
<string name="setup_gpu_bridge_on">Mali bridge: on</string>
|
||||
<string name="setup_gpu_bridge_detail">%1$s is not an Adreno. The experimental Vulkan bridge is on: the session draws through the phone\'s own driver. Expect bugs; send the session logs.</string>
|
||||
</resources>
|
||||
@@ -108,6 +108,7 @@ docker run --rm --platform linux/amd64 \
|
||||
aarch64-linux-gnu-strip --strip-unneeded "$d/usr/local/lib/droiddeck-xclipse/libVkLayer_droiddeck_xclipse.so"
|
||||
install -m644 tools/linuxfs/xclipse-layer/VkLayer_droiddeck_xclipse.json "$d/usr/local/lib/droiddeck-xclipse/"
|
||||
mkdir -p "$d/usr/local/bin"
|
||||
tools/vkbridge/build-client-linux.sh "$d" aarch64-linux-gnu-gcc
|
||||
aarch64-linux-gnu-gcc -O2 -Wall -Wextra -o "$d/usr/local/bin/droiddeck-clipboard" tools/linuxfs/clipboard/clipboard.c -ldl
|
||||
aarch64-linux-gnu-strip --strip-unneeded "$d/usr/local/bin/droiddeck-clipboard"
|
||||
for script in tools/linuxfs/overlay/usr/local/bin/droiddeck-* tools/linuxfs/overlay/usr/local/bin/steam-compatibility; do
|
||||
|
||||
@@ -138,6 +138,31 @@ if [ -n "${BL_VK_DRIVER:-}" ]; then
|
||||
fi
|
||||
unset bl_icd_lib
|
||||
fi
|
||||
# The Mali bridge (Setup's experimental switch on a non-Adreno GPU; tools/vkbridge): the app runs a
|
||||
# server on the phone's own Vulkan driver and this session draws through its ICD, which replaces
|
||||
# every other driver here. Checked from inside like an imported driver, and on the first pass only
|
||||
# the bridge's self-test and the driver's summary go into the log, so a black screen comes with the
|
||||
# reason in the same file.
|
||||
if [ "${BL_VKBRIDGE:-0}" = 1 ]; then
|
||||
bl_vkb_icd=/usr/local/share/vkbridge/droidbridge_icd.json
|
||||
if [ -r "$bl_vkb_icd" ] && [ -r /usr/local/lib/vkbridge/libvulkan_droidbridge.so ] && [ -S "${VKBRIDGE_SOCKET:-}" ]; then
|
||||
export VK_DRIVER_FILES=$bl_vkb_icd VK_ICD_FILENAMES=$bl_vkb_icd
|
||||
export VKBRIDGE_SOCKET VKBRIDGE_CLIENT_LOG
|
||||
# Turnip's switches mean nothing to the bridge.
|
||||
unset TU_DEBUG
|
||||
if [ -z "${BL_INSIDE:-}" ]; then
|
||||
step "vkbridge self-test"
|
||||
timeout 60 /usr/local/bin/droiddeck-vkbridge-smoke 2>&1 | sed 's/^/== vkbridge: /'
|
||||
if command -v vulkaninfo >/dev/null 2>&1; then
|
||||
timeout 30 vulkaninfo --summary 2>&1 | sed -n '/^Devices:/,$p' | head -40 | sed 's/^/== vulkaninfo: /'
|
||||
fi
|
||||
fi
|
||||
echo "== vulkan driver: Mali bridge ($bl_vkb_icd, server at $VKBRIDGE_SOCKET)"
|
||||
else
|
||||
echo "== vulkan driver: Mali bridge requested but NOT usable (icd $bl_vkb_icd, socket ${VKBRIDGE_SOCKET:-unset}); this GPU will show a black screen"
|
||||
fi
|
||||
unset bl_vkb_icd
|
||||
fi
|
||||
# The app's gamescope build (tools/gamescope) asks for realtime-priority Vulkan queues on this
|
||||
# switch instead of on CAP_SYS_NICE, which proot never has; it falls back to normal priority
|
||||
# where the driver refuses, and the runtime's own build ignores it. On KGSL Turnip (Mesa 26.2) it
|
||||
|
||||
@@ -51,8 +51,10 @@ static int is_kgsl(int fd) {
|
||||
struct stat st;
|
||||
pthread_mutex_lock(&lock);
|
||||
if (kgsl_state == 0) {
|
||||
kgsl_state = stat("/dev/kgsl-3d0", &st) == 0 ? 1 : -1;
|
||||
if (kgsl_state > 0) kgsl_dev = st.st_rdev;
|
||||
/* KGSL, or the device the Mali bridge's session names as its render node (BL_DRM_STANDIN). */
|
||||
const char *standin = getenv("BL_DRM_STANDIN");
|
||||
kgsl_state = stat("/dev/kgsl-3d0", &st) == 0 || (standin && *standin && stat(standin, &st) == 0) ? 1 : -1;
|
||||
kgsl_dev = st.st_rdev;
|
||||
}
|
||||
pthread_mutex_unlock(&lock);
|
||||
return kgsl_state > 0 && fstat(fd, &st) == 0 && S_ISCHR(st.st_mode) && st.st_rdev == kgsl_dev;
|
||||
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
# Builds the Linux side of the bridge for the runtime (glibc, aarch64):
|
||||
# tools/vkbridge/build-client-linux.sh <linuxfs-asset-dir> [cc]
|
||||
# Produces, under the asset dir (staged into the runtime by SessionFiles):
|
||||
# usr/local/lib/vkbridge/libvulkan_droidbridge.so the Vulkan ICD
|
||||
# usr/local/share/vkbridge/droidbridge_icd.json its manifest
|
||||
# usr/local/bin/droiddeck-vkbridge-smoke the session-start self-test
|
||||
set -euo pipefail
|
||||
d=$1
|
||||
cc=${2:-aarch64-linux-gnu-gcc}
|
||||
strip=${STRIP:-${cc%gcc}strip}
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
inc="-I$here/third_party/Vulkan-Headers-1.4.341/include -I$here/common -I$here/common/gen"
|
||||
mkdir -p "$d/usr/local/lib/vkbridge" "$d/usr/local/share/vkbridge" "$d/usr/local/bin"
|
||||
"$cc" -O2 -fPIC -shared -fvisibility=hidden -DVKB_CLIENT -Wall -Wno-unused-parameter -Wno-missing-field-initializers \
|
||||
$inc -I"$here/client" -I"$here/client/compat" -pthread \
|
||||
-Wl,--no-undefined -Wl,-soname,libvulkan_droidbridge.so \
|
||||
-o "$d/usr/local/lib/vkbridge/libvulkan_droidbridge.so" \
|
||||
"$here"/client/*.c "$here/common/vkb_wire.c" "$here/common/gen/vkb_gen_structs.c" "$here/common/gen/vkb_gen_client.c" -ldl
|
||||
"$strip" --strip-unneeded "$d/usr/local/lib/vkbridge/libvulkan_droidbridge.so"
|
||||
"$cc" -O2 -Wall $inc -I"$here/tests" -o "$d/usr/local/bin/droiddeck-vkbridge-smoke" "$here/tests/smoke.c" -ldl
|
||||
"$strip" --strip-unneeded "$d/usr/local/bin/droiddeck-vkbridge-smoke"
|
||||
cat > "$d/usr/local/share/vkbridge/droidbridge_icd.json" <<JSON
|
||||
{"file_format_version": "1.0.0", "ICD": {"library_path": "/usr/local/lib/vkbridge/libvulkan_droidbridge.so", "api_version": "1.4.341"}}
|
||||
JSON
|
||||
echo "built the vkbridge client into $d"
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
# Builds the bridge server for the device (bionic, arm64-v8a, API 26) with the NDK's clang:
|
||||
# tools/vkbridge/build-server-android.sh <ndk-dir> <jniLibs-out-dir>
|
||||
# The output is an executable named like a library (libvkbridge_server.so) so that Android
|
||||
# extracts it into the native library directory, the one place an app may exec from.
|
||||
set -euo pipefail
|
||||
ndk=$1
|
||||
out=$2/arm64-v8a
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
srv=$(cd "$here/../../app/src/main/cpp/vkbridge" && pwd)
|
||||
cc=$(ls -d "$ndk"/toolchains/llvm/prebuilt/*/bin/aarch64-linux-android26-clang | head -1)
|
||||
[ -x "$cc" ] || { echo "no NDK clang under $ndk" >&2; exit 1; }
|
||||
mkdir -p "$out"
|
||||
"$cc" -O2 -g0 -fPIE -pie -Wall -Wno-unused-parameter -Wno-missing-field-initializers \
|
||||
-I"$here/third_party/Vulkan-Headers-1.4.341/include" -I"$here/common" -I"$here/common/gen" -I"$srv" \
|
||||
-Wl,-z,max-page-size=16384 -Wl,--build-id=none \
|
||||
-o "$out/libvkbridge_server.so" \
|
||||
"$srv"/*.c "$here/common/vkb_wire.c" "$here/common/gen/vkb_gen_structs.c" "$here/common/gen/vkb_gen_server.c" \
|
||||
-ldl -llog -lm
|
||||
"$(dirname "$cc")/llvm-strip" --strip-unneeded "$out/libvkbridge_server.so"
|
||||
# The same server as a JNI library the app loads into its own process (the default): only there
|
||||
# does Android hand it the driver the app gets (VkBridgeNative, server_main.c).
|
||||
"$cc" -O2 -g0 -fPIC -shared -DVKB_JNI -Wall -Wno-unused-parameter -Wno-missing-field-initializers \
|
||||
-I"$here/third_party/Vulkan-Headers-1.4.341/include" -I"$here/common" -I"$here/common/gen" -I"$srv" \
|
||||
-Wl,-z,max-page-size=16384 -Wl,--build-id=none -Wl,--no-undefined -Wl,-soname,libvkbridge_jni.so \
|
||||
-o "$out/libvkbridge_jni.so" \
|
||||
"$srv"/*.c "$here/common/vkb_wire.c" "$here/common/gen/vkb_gen_structs.c" "$here/common/gen/vkb_gen_server.c" \
|
||||
-ldl -llog -lm
|
||||
"$(dirname "$cc")/llvm-strip" --strip-unneeded "$out/libvkbridge_jni.so"
|
||||
echo "built $out/libvkbridge_server.so and libvkbridge_jni.so"
|
||||
@@ -0,0 +1,234 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Command buffers: vkCmd* calls are encoded into the command buffer's own stream (no round trip
|
||||
* each); vkEndCommandBuffer sends the stream in one message and the server replays it.
|
||||
*
|
||||
* Stream entry: u32 command id, u32 payload bytes, payload (8-aligned), padded to 8.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "vkb_client.h"
|
||||
#include "gen/vkb_client_gen.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static __thread vkb_cmdbuf *tls_encoding;
|
||||
|
||||
int vkb_cb_begin_is_secondary(const VkCommandBufferBeginInfo *s)
|
||||
{
|
||||
(void)s;
|
||||
return tls_encoding && tls_encoding->level == VK_COMMAND_BUFFER_LEVEL_SECONDARY;
|
||||
}
|
||||
|
||||
vkb_enc *vkb_cmd_record_begin(VkCommandBuffer commandBuffer, uint32_t cmd)
|
||||
{
|
||||
vkb_cmdbuf *cb = vkb_cb(commandBuffer);
|
||||
if (!cb->recording) {
|
||||
VKB_ONCE(VKB_LOG_WARN, "%s on a command buffer that is not recording (ignored; logged once)", vkb_cmd_names[cmd]);
|
||||
return NULL;
|
||||
}
|
||||
vkb_enc *e = &cb->stream;
|
||||
/* entries start 8-aligned */
|
||||
while (e->len & 7) vkb_enc_u8(e, 0);
|
||||
vkb_enc_u32(e, cmd);
|
||||
vkb_enc_u32(e, 0);
|
||||
cb->entry = e->len;
|
||||
return e;
|
||||
}
|
||||
|
||||
void vkb_cmd_record_end(VkCommandBuffer commandBuffer)
|
||||
{
|
||||
vkb_cmdbuf *cb = vkb_cb(commandBuffer);
|
||||
vkb_enc *e = &cb->stream;
|
||||
if (e->oom) {
|
||||
cb->broken = 1;
|
||||
return;
|
||||
}
|
||||
if (e->nfds) {
|
||||
VKB_ERR("internal: a recorded command carries a file descriptor");
|
||||
cb->broken = 1;
|
||||
e->nfds = 0;
|
||||
}
|
||||
uint32_t n = (uint32_t)(e->len - cb->entry);
|
||||
memcpy(e->buf + cb->entry - 4, &n, 4);
|
||||
}
|
||||
|
||||
static void stream_reset(vkb_cmdbuf *cb)
|
||||
{
|
||||
if (cb->stream.cap > (8u << 20)) {
|
||||
vkb_enc_free(&cb->stream);
|
||||
vkb_enc_init(&cb->stream);
|
||||
} else {
|
||||
vkb_enc_reset(&cb->stream);
|
||||
}
|
||||
cb->recording = 0;
|
||||
cb->broken = 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ pools */
|
||||
|
||||
static vkb_cmdpool *pool_get(vkb_device *dev, VkCommandPool pool, int create)
|
||||
{
|
||||
vkb_cmdpool *p = vkb_map_get(&dev->pools, (uint64_t)pool);
|
||||
if (!p && create) {
|
||||
p = calloc(1, sizeof(*p));
|
||||
vkb_map_put(&dev->pools, (uint64_t)pool, p);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static void cmdbuf_free(vkb_cmdbuf *cb)
|
||||
{
|
||||
vkb_enc_free(&cb->stream);
|
||||
free(cb);
|
||||
}
|
||||
|
||||
void vkb_cmdpool_free(vkb_cmdpool *p)
|
||||
{
|
||||
vkb_cmdbuf *cb = p->head;
|
||||
while (cb) {
|
||||
vkb_cmdbuf *n = cb->pool_next;
|
||||
cmdbuf_free(cb);
|
||||
cb = n;
|
||||
}
|
||||
free(p);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkAllocateCommandBuffers(VkDevice device, const VkCommandBufferAllocateInfo *pAllocateInfo,
|
||||
VkCommandBuffer *pCommandBuffers)
|
||||
{
|
||||
VkResult r = vkb_wire_vkAllocateCommandBuffers(device, pAllocateInfo, pCommandBuffers);
|
||||
if (r != VK_SUCCESS) {
|
||||
for (uint32_t i = 0; i < pAllocateInfo->commandBufferCount; i++) pCommandBuffers[i] = VK_NULL_HANDLE;
|
||||
return r;
|
||||
}
|
||||
vkb_device *dev = vkb_dev(device);
|
||||
pthread_mutex_lock(&dev->lock);
|
||||
vkb_cmdpool *p = pool_get(dev, pAllocateInfo->commandPool, 1);
|
||||
for (uint32_t i = 0; i < pAllocateInfo->commandBufferCount; i++) {
|
||||
vkb_cmdbuf *cb = vkb_cb(pCommandBuffers[i]);
|
||||
if (!cb) continue;
|
||||
cb->pool = pAllocateInfo->commandPool;
|
||||
cb->level = pAllocateInfo->level;
|
||||
cb->pool_next = p->head;
|
||||
cb->pool_prev = NULL;
|
||||
if (p->head) p->head->pool_prev = cb;
|
||||
p->head = cb;
|
||||
}
|
||||
pthread_mutex_unlock(&dev->lock);
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkFreeCommandBuffers(VkDevice device, VkCommandPool commandPool, uint32_t commandBufferCount,
|
||||
const VkCommandBuffer *pCommandBuffers)
|
||||
{
|
||||
vkb_wire_vkFreeCommandBuffers(device, commandPool, commandBufferCount, pCommandBuffers);
|
||||
vkb_device *dev = vkb_dev(device);
|
||||
pthread_mutex_lock(&dev->lock);
|
||||
vkb_cmdpool *p = pool_get(dev, commandPool, 0);
|
||||
for (uint32_t i = 0; i < commandBufferCount; i++) {
|
||||
vkb_cmdbuf *cb = vkb_cb(pCommandBuffers[i]);
|
||||
if (!cb) continue;
|
||||
if (p) {
|
||||
if (cb->pool_prev) cb->pool_prev->pool_next = cb->pool_next;
|
||||
else if (p->head == cb) p->head = cb->pool_next;
|
||||
if (cb->pool_next) cb->pool_next->pool_prev = cb->pool_prev;
|
||||
}
|
||||
cmdbuf_free(cb);
|
||||
}
|
||||
pthread_mutex_unlock(&dev->lock);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroyCommandPool(VkDevice device, VkCommandPool commandPool, const VkAllocationCallbacks *pAllocator)
|
||||
{
|
||||
if (!commandPool) return;
|
||||
vkb_wire_vkDestroyCommandPool(device, commandPool, pAllocator);
|
||||
vkb_device *dev = vkb_dev(device);
|
||||
pthread_mutex_lock(&dev->lock);
|
||||
vkb_cmdpool *p = vkb_map_remove(&dev->pools, (uint64_t)commandPool);
|
||||
pthread_mutex_unlock(&dev->lock);
|
||||
if (p) vkb_cmdpool_free(p);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkResetCommandPool(VkDevice device, VkCommandPool commandPool, VkCommandPoolResetFlags flags)
|
||||
{
|
||||
VkResult r = vkb_wire_vkResetCommandPool(device, commandPool, flags);
|
||||
vkb_device *dev = vkb_dev(device);
|
||||
pthread_mutex_lock(&dev->lock);
|
||||
vkb_cmdpool *p = pool_get(dev, commandPool, 0);
|
||||
for (vkb_cmdbuf *cb = p ? p->head : NULL; cb; cb = cb->pool_next) stream_reset(cb);
|
||||
pthread_mutex_unlock(&dev->lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ recording */
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkBeginCommandBuffer(VkCommandBuffer commandBuffer, const VkCommandBufferBeginInfo *pBeginInfo)
|
||||
{
|
||||
vkb_cmdbuf *cb = vkb_cb(commandBuffer);
|
||||
stream_reset(cb);
|
||||
cb->recording = 1;
|
||||
vkb_enc *e = vkb_cmd_record_begin(commandBuffer, VKB_CMD_vkBeginCommandBuffer);
|
||||
vkb_enc_u64(e, cb->obj.remote);
|
||||
tls_encoding = cb;
|
||||
vkb_enc_u8(e, pBeginInfo != NULL);
|
||||
if (pBeginInfo) vkb_enc_VkCommandBufferBeginInfo(e, pBeginInfo);
|
||||
tls_encoding = NULL;
|
||||
vkb_cmd_record_end(commandBuffer);
|
||||
return cb->broken ? VK_ERROR_OUT_OF_HOST_MEMORY : VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkEndCommandBuffer(VkCommandBuffer commandBuffer)
|
||||
{
|
||||
vkb_cmdbuf *cb = vkb_cb(commandBuffer);
|
||||
if (!cb->recording) return VK_SUCCESS;
|
||||
cb->recording = 0;
|
||||
if (cb->broken || cb->stream.oom) {
|
||||
VKB_ERR("command buffer could not be recorded (out of memory)");
|
||||
stream_reset(cb);
|
||||
return VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
}
|
||||
vkb_call c;
|
||||
vkb_call_begin(&c, VKB_CMD_vkEndCommandBuffer, cb->obj.table);
|
||||
vkb_enc_u64(&c.e, cb->obj.remote);
|
||||
vkb_enc_blob(&c.e, cb->stream.buf ? cb->stream.buf : (const void *)"", cb->stream.len);
|
||||
VkResult r = VK_ERROR_DEVICE_LOST;
|
||||
if (vkb_async_results()) {
|
||||
/* Replayed on the server while the app goes on; ordered before its submit. */
|
||||
r = vkb_call_exec_async(&c) ? VK_SUCCESS : VK_ERROR_DEVICE_LOST;
|
||||
} else if (vkb_call_exec(&c)) {
|
||||
r = (VkResult)vkb_dec_u32(&c.d);
|
||||
vkb_call_end(&c);
|
||||
}
|
||||
stream_reset(cb);
|
||||
return r;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkResetCommandBuffer(VkCommandBuffer commandBuffer, VkCommandBufferResetFlags flags)
|
||||
{
|
||||
stream_reset(vkb_cb(commandBuffer));
|
||||
return vkb_wire_vkResetCommandBuffer(commandBuffer, flags);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkCmdExecuteCommands(VkCommandBuffer commandBuffer, uint32_t commandBufferCount,
|
||||
const VkCommandBuffer *pCommandBuffers)
|
||||
{
|
||||
vkb_wire_vkCmdExecuteCommands(commandBuffer, commandBufferCount, pCommandBuffers);
|
||||
}
|
||||
|
||||
/* Debug labels never reach the driver. */
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkCmdBeginDebugUtilsLabelEXT(VkCommandBuffer commandBuffer, const VkDebugUtilsLabelEXT *pLabelInfo)
|
||||
{
|
||||
(void)commandBuffer;
|
||||
(void)pLabelInfo;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkCmdEndDebugUtilsLabelEXT(VkCommandBuffer commandBuffer)
|
||||
{
|
||||
(void)commandBuffer;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkCmdInsertDebugUtilsLabelEXT(VkCommandBuffer commandBuffer, const VkDebugUtilsLabelEXT *pLabelInfo)
|
||||
{
|
||||
(void)commandBuffer;
|
||||
(void)pLabelInfo;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Minimal Xlib declarations for the bridge ICD: it never links libX11, it only needs the types
|
||||
* vulkan_xlib.h names and looks up XGetXCBConnection at run time. */
|
||||
#ifndef VKB_COMPAT_XLIB_H
|
||||
#define VKB_COMPAT_XLIB_H
|
||||
typedef struct _XDisplay Display;
|
||||
typedef unsigned long XID;
|
||||
typedef XID Window;
|
||||
typedef unsigned long VisualID;
|
||||
#endif
|
||||
@@ -0,0 +1,37 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* Minimal xcb declarations for the bridge ICD: libxcb is opened at run time (dlopen), so only the
|
||||
* types and the few request/reply layouts the software present path uses are declared here. */
|
||||
#ifndef VKB_COMPAT_XCB_H
|
||||
#define VKB_COMPAT_XCB_H
|
||||
#include <stdint.h>
|
||||
typedef struct xcb_connection_t xcb_connection_t;
|
||||
typedef uint32_t xcb_window_t;
|
||||
typedef uint32_t xcb_visualid_t;
|
||||
typedef uint32_t xcb_drawable_t;
|
||||
typedef uint32_t xcb_gcontext_t;
|
||||
typedef uint32_t xcb_pixmap_t;
|
||||
typedef struct { unsigned int sequence; } xcb_void_cookie_t;
|
||||
typedef struct { unsigned int sequence; } xcb_get_geometry_cookie_t;
|
||||
typedef struct {
|
||||
uint8_t response_type;
|
||||
uint8_t depth;
|
||||
uint16_t sequence;
|
||||
uint32_t length;
|
||||
xcb_window_t root;
|
||||
int16_t x, y;
|
||||
uint16_t width, height, border_width;
|
||||
uint8_t pad0[2];
|
||||
} xcb_get_geometry_reply_t;
|
||||
typedef struct {
|
||||
uint8_t response_type;
|
||||
uint8_t error_code;
|
||||
uint16_t sequence;
|
||||
uint32_t resource_id;
|
||||
uint16_t minor_code;
|
||||
uint8_t major_code;
|
||||
uint8_t pad0;
|
||||
uint32_t pad[5];
|
||||
uint32_t full_sequence;
|
||||
} xcb_generic_error_t;
|
||||
#define XCB_IMAGE_FORMAT_Z_PIXMAP 2
|
||||
#endif
|
||||
@@ -0,0 +1,672 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Client transport: one Unix-socket connection per thread (the server mirrors it with one thread),
|
||||
* so calls from different threads run in parallel exactly as they would on a local driver.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "vkb_client.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/un.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* ------------------------------------------------------------------ logging */
|
||||
|
||||
static int log_level_v = -1;
|
||||
static FILE *log_file;
|
||||
static pthread_mutex_t log_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
int vkb_log_level(void)
|
||||
{
|
||||
if (log_level_v < 0) {
|
||||
const char *d = getenv("VKBRIDGE_DEBUG");
|
||||
log_level_v = d && *d && strcmp(d, "0") ? VKB_LOG_DEBUG : VKB_LOG_WARN;
|
||||
const char *v = getenv("VKBRIDGE_LOG_LEVEL");
|
||||
if (v) log_level_v = atoi(v);
|
||||
const char *f = getenv("VKBRIDGE_CLIENT_LOG");
|
||||
if (f && *f) log_file = fopen(f, "ae");
|
||||
}
|
||||
return log_level_v;
|
||||
}
|
||||
|
||||
void vkb_log(int level, const char *fmt, ...)
|
||||
{
|
||||
if (level > vkb_log_level()) return;
|
||||
char msg[2048];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(msg, sizeof(msg), fmt, ap);
|
||||
va_end(ap);
|
||||
static const char *const tags[] = {"error", "warning", "info", "debug"};
|
||||
char comm[32] = "?";
|
||||
FILE *cf = fopen("/proc/self/comm", "re");
|
||||
if (cf) {
|
||||
if (fgets(comm, sizeof(comm), cf)) comm[strcspn(comm, "\n")] = 0;
|
||||
fclose(cf);
|
||||
}
|
||||
pthread_mutex_lock(&log_lock);
|
||||
fprintf(stderr, "vkbridge %s [%s %d]: %s\n", tags[level & 3], comm, getpid(), msg);
|
||||
if (log_file) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
struct tm tm;
|
||||
localtime_r(&ts.tv_sec, &tm);
|
||||
fprintf(log_file, "%02d:%02d:%02d.%03ld vkbridge-client %s [%s %d]: %s\n", tm.tm_hour, tm.tm_min, tm.tm_sec,
|
||||
ts.tv_nsec / 1000000, tags[level & 3], comm, getpid(), msg);
|
||||
fflush(log_file);
|
||||
}
|
||||
pthread_mutex_unlock(&log_lock);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ connection */
|
||||
|
||||
typedef struct vkb_conn {
|
||||
int sock;
|
||||
int dead;
|
||||
uint32_t epoch;
|
||||
vkb_enc enc; /* reused request buffer */
|
||||
uint8_t *in;
|
||||
size_t incap;
|
||||
vkb_arena arena;
|
||||
int busy;
|
||||
} vkb_conn;
|
||||
|
||||
static uint64_t process_token;
|
||||
/* The last asynchronous request's sequence number in this process (see vkb_msg_hdr). */
|
||||
static uint64_t async_seq;
|
||||
static uint32_t fork_epoch;
|
||||
static pthread_key_t conn_key;
|
||||
static pthread_once_t conn_once = PTHREAD_ONCE_INIT;
|
||||
static __thread vkb_conn *tls_conn;
|
||||
|
||||
static void conn_free(vkb_conn *c)
|
||||
{
|
||||
if (!c) return;
|
||||
if (c->sock >= 0) close(c->sock);
|
||||
vkb_enc_free(&c->enc);
|
||||
free(c->in);
|
||||
vkb_arena_free(&c->arena);
|
||||
free(c);
|
||||
}
|
||||
|
||||
static void conn_thread_exit(void *p)
|
||||
{
|
||||
conn_free(p);
|
||||
}
|
||||
|
||||
/* Asynchronous requests of the whole process wait here and are written out in one go, ahead of
|
||||
* the next synchronous request any thread makes (or when the queue grows large). They carry their
|
||||
* sequence numbers, so whichever connection carries them, the server runs them in order. */
|
||||
static pthread_mutex_t aq_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static uint8_t *aq_buf;
|
||||
static size_t aq_len, aq_cap;
|
||||
#define AQ_FLUSH (256u << 10)
|
||||
|
||||
static int write_all(int sock, const void *p, size_t n)
|
||||
{
|
||||
const uint8_t *b = p;
|
||||
while (n) {
|
||||
ssize_t r = send(sock, b, n, MSG_NOSIGNAL);
|
||||
if (r < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
return -1;
|
||||
}
|
||||
b += r;
|
||||
n -= (size_t)r;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aq_flush_locked(vkb_conn *conn)
|
||||
{
|
||||
if (!aq_len) return 0;
|
||||
int r = write_all(conn->sock, aq_buf, aq_len);
|
||||
aq_len = 0;
|
||||
if (aq_cap > (4u << 20)) {
|
||||
free(aq_buf);
|
||||
aq_buf = NULL;
|
||||
aq_cap = 0;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/* The flusher: asynchronous requests do not wait for the next synchronous one forever - another
|
||||
* thread may be blocked in the server on work they would start (a submit that signals what it is
|
||||
* waiting for). It sends what is queued ~100 us after it was queued, batching what follows. */
|
||||
static pthread_cond_t aq_cond = PTHREAD_COND_INITIALIZER;
|
||||
static int aq_flusher_started;
|
||||
static uint64_t aq_first_ns;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static vkb_conn *get_conn(void);
|
||||
|
||||
static void *aq_flusher(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
vkb_conn *conn = get_conn();
|
||||
if (!conn) return NULL;
|
||||
pthread_mutex_lock(&aq_lock);
|
||||
for (;;) {
|
||||
while (!aq_len) pthread_cond_wait(&aq_cond, &aq_lock);
|
||||
uint64_t due = aq_first_ns + 100000, now = now_ns();
|
||||
if (now < due && aq_len < AQ_FLUSH / 4) {
|
||||
pthread_mutex_unlock(&aq_lock);
|
||||
struct timespec ts = {0, (long)(due - now)};
|
||||
nanosleep(&ts, NULL);
|
||||
pthread_mutex_lock(&aq_lock);
|
||||
continue;
|
||||
}
|
||||
if (aq_flush_locked(conn) < 0) {
|
||||
conn->dead = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&aq_lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void aq_exit_flush(void)
|
||||
{
|
||||
vkb_conn *c = tls_conn;
|
||||
if (!c || c->dead) return;
|
||||
pthread_mutex_lock(&aq_lock);
|
||||
aq_flush_locked(c);
|
||||
pthread_mutex_unlock(&aq_lock);
|
||||
}
|
||||
|
||||
static void atfork_child(void)
|
||||
{
|
||||
aq_len = 0;
|
||||
aq_flusher_started = 0;
|
||||
pthread_cond_init(&aq_cond, NULL);
|
||||
pthread_mutex_init(&aq_lock, NULL);
|
||||
/* The child must not talk on the parent's sockets: new token, new connections. */
|
||||
fork_epoch++;
|
||||
process_token = 0;
|
||||
async_seq = 0;
|
||||
tls_conn = NULL; /* the old one is leaked deliberately: its socket belongs to the parent too */
|
||||
}
|
||||
|
||||
static void conn_init_once(void)
|
||||
{
|
||||
pthread_key_create(&conn_key, conn_thread_exit);
|
||||
pthread_atfork(NULL, NULL, atfork_child);
|
||||
atexit(aq_exit_flush);
|
||||
}
|
||||
|
||||
void vkb_conn_after_fork(void) { atfork_child(); }
|
||||
|
||||
static uint64_t make_token(void)
|
||||
{
|
||||
uint64_t t = 0;
|
||||
int fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
|
||||
if (fd >= 0) {
|
||||
if (read(fd, &t, sizeof(t)) != sizeof(t)) t = 0;
|
||||
close(fd);
|
||||
}
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
t ^= ((uint64_t)getpid() << 32) ^ (uint64_t)ts.tv_nsec ^ (uint64_t)ts.tv_sec * 1000003u;
|
||||
return t ? t : 1;
|
||||
}
|
||||
|
||||
static const char *socket_path(void)
|
||||
{
|
||||
const char *p = getenv(VKB_DEFAULT_SOCKET_ENV);
|
||||
if (p && *p) return p;
|
||||
static char buf[256];
|
||||
const char *rd = getenv("XDG_RUNTIME_DIR");
|
||||
snprintf(buf, sizeof(buf), "%s/vkbridge.sock", rd && *rd ? rd : "/tmp");
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int do_connect(vkb_conn *c)
|
||||
{
|
||||
const char *path = socket_path();
|
||||
struct sockaddr_un a;
|
||||
memset(&a, 0, sizeof(a));
|
||||
a.sun_family = AF_UNIX;
|
||||
if (strlen(path) >= sizeof(a.sun_path)) {
|
||||
VKB_ERR("socket path too long: %s", path);
|
||||
return -1;
|
||||
}
|
||||
strcpy(a.sun_path, path);
|
||||
int s = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
if (s < 0) return -1;
|
||||
if (connect(s, (struct sockaddr *)&a, sizeof(a)) < 0) {
|
||||
VKB_ONCE(VKB_LOG_ERROR, "cannot reach the Vulkan bridge server at %s: %s", path, strerror(errno));
|
||||
close(s);
|
||||
return -1;
|
||||
}
|
||||
c->sock = s;
|
||||
/* Hello: protocol check and which process this thread belongs to. */
|
||||
if (!process_token) process_token = make_token();
|
||||
vkb_enc e;
|
||||
vkb_enc_init(&e);
|
||||
vkb_enc_u64(&e, VKB_PROTOCOL_HASH);
|
||||
vkb_enc_u64(&e, process_token);
|
||||
vkb_enc_u32(&e, (uint32_t)getpid());
|
||||
char comm[64] = "?";
|
||||
FILE *f = fopen("/proc/self/comm", "re");
|
||||
if (f) {
|
||||
if (fgets(comm, sizeof(comm), f)) comm[strcspn(comm, "\n")] = 0;
|
||||
fclose(f);
|
||||
}
|
||||
vkb_enc_str(&e, comm);
|
||||
vkb_msg_hdr h = {VKB_MAGIC, (uint32_t)e.len, VKB_CMD_vkbHello, 0, 0, 0, 0, 0};
|
||||
int ok = vkb_send_msg(s, &h, e.buf, NULL, 0) == 0;
|
||||
vkb_enc_free(&e);
|
||||
int fds[VKB_MAX_FDS], nfds = 0;
|
||||
vkb_msg_hdr rh;
|
||||
if (ok) ok = vkb_recv_msg(s, &rh, &c->in, &c->incap, fds, &nfds) == 0 && rh.cmd == VKB_ST_OK && rh.size >= 12;
|
||||
if (ok) {
|
||||
uint64_t sh;
|
||||
uint32_t accepted;
|
||||
memcpy(&sh, c->in, 8);
|
||||
memcpy(&accepted, c->in + 8, 4);
|
||||
if (!accepted) {
|
||||
VKB_ONCE(VKB_LOG_ERROR, "the bridge server speaks protocol %016llx, this driver %016llx; update DroidDeck's Linux side",
|
||||
(unsigned long long)sh, (unsigned long long)VKB_PROTOCOL_HASH);
|
||||
ok = 0;
|
||||
}
|
||||
}
|
||||
if (!ok) {
|
||||
close(s);
|
||||
c->sock = -1;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static vkb_conn *get_conn(void)
|
||||
{
|
||||
pthread_once(&conn_once, conn_init_once);
|
||||
vkb_conn *c = tls_conn;
|
||||
if (c && c->epoch != fork_epoch) c = tls_conn = NULL;
|
||||
if (c) return c->dead ? NULL : c;
|
||||
c = calloc(1, sizeof(*c));
|
||||
if (!c) return NULL;
|
||||
c->sock = -1;
|
||||
c->epoch = fork_epoch;
|
||||
vkb_enc_init(&c->enc);
|
||||
if (do_connect(c) < 0) {
|
||||
c->dead = 1;
|
||||
}
|
||||
tls_conn = c;
|
||||
pthread_setspecific(conn_key, c);
|
||||
return c->dead ? NULL : c;
|
||||
}
|
||||
|
||||
int vkb_connected(void)
|
||||
{
|
||||
return get_conn() != NULL;
|
||||
}
|
||||
|
||||
void vkb_call_begin(vkb_call *c, uint32_t cmd, uint32_t table)
|
||||
{
|
||||
memset(c, 0, sizeof(*c));
|
||||
c->cmd = cmd;
|
||||
c->table = table;
|
||||
c->conn = get_conn();
|
||||
if (c->conn && !c->conn->busy) {
|
||||
c->conn->busy = 1;
|
||||
c->e = c->conn->enc;
|
||||
vkb_enc_reset(&c->e);
|
||||
} else {
|
||||
/* No connection, or a nested call: encode into a private buffer. */
|
||||
if (c->conn && c->conn->busy) VKB_ERR("internal: nested bridge call (%s)", vkb_cmd_names[cmd]);
|
||||
vkb_enc_init(&c->e);
|
||||
c->flags |= 0x80000000u;
|
||||
}
|
||||
}
|
||||
|
||||
static void release_enc(vkb_call *c)
|
||||
{
|
||||
if (c->flags & 0x80000000u) {
|
||||
vkb_enc_free(&c->e);
|
||||
} else if (c->conn) {
|
||||
c->conn->enc = c->e;
|
||||
c->conn->busy = 0;
|
||||
/* Don't keep a huge buffer around after one big message. */
|
||||
if (c->conn->enc.cap > (16u << 20)) {
|
||||
vkb_enc_free(&c->conn->enc);
|
||||
vkb_enc_init(&c->conn->enc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int trace_on(void)
|
||||
{
|
||||
static int v = -1;
|
||||
if (v < 0) v = getenv("VKBRIDGE_TRACE") && strcmp(getenv("VKBRIDGE_TRACE"), "0");
|
||||
return v;
|
||||
}
|
||||
|
||||
int vkb_call_exec(vkb_call *c)
|
||||
{
|
||||
vkb_conn *conn = c->conn;
|
||||
if (trace_on()) {
|
||||
/* VkResult-returning calls put it first in the reply; peeked after the round trip. */
|
||||
vkb_log(VKB_LOG_ERROR, "trace: %s", vkb_cmd_names[c->cmd]);
|
||||
}
|
||||
if (!conn || conn->dead || (c->flags & 0x80000000u)) {
|
||||
release_enc(c);
|
||||
c->conn = NULL;
|
||||
return 0;
|
||||
}
|
||||
if (c->e.oom) {
|
||||
VKB_ERR("%s: request could not be encoded", vkb_cmd_names[c->cmd]);
|
||||
release_enc(c);
|
||||
c->conn = NULL;
|
||||
return 0;
|
||||
}
|
||||
vkb_msg_hdr h = {VKB_MAGIC, (uint32_t)c->e.len, c->cmd, c->table, (uint32_t)c->e.nfds, c->flags & VKB_F_NOREPLY, 0, 0};
|
||||
vkb_stats_count(c->cmd, (c->flags & VKB_F_NOREPLY) != 0, c->e.len);
|
||||
pthread_mutex_lock(&aq_lock);
|
||||
if ((c->flags & VKB_F_NOREPLY) && !c->e.nfds) {
|
||||
/* Queued: header and payload appended under the lock that also orders the seqs. */
|
||||
h.seq = ++async_seq;
|
||||
h.barrier = h.seq - 1;
|
||||
size_t need = aq_len + sizeof(h) + c->e.len;
|
||||
if (need > aq_cap) {
|
||||
size_t nc = aq_cap ? aq_cap : 65536;
|
||||
while (nc < need) nc *= 2;
|
||||
uint8_t *nb = realloc(aq_buf, nc);
|
||||
if (!nb) {
|
||||
pthread_mutex_unlock(&aq_lock);
|
||||
release_enc(c);
|
||||
c->conn = NULL;
|
||||
return 0;
|
||||
}
|
||||
aq_buf = nb;
|
||||
aq_cap = nc;
|
||||
}
|
||||
if (!aq_len) aq_first_ns = now_ns();
|
||||
memcpy(aq_buf + aq_len, &h, sizeof(h));
|
||||
memcpy(aq_buf + aq_len + sizeof(h), c->e.buf, c->e.len);
|
||||
aq_len = need;
|
||||
if (!aq_flusher_started) {
|
||||
aq_flusher_started = 1;
|
||||
pthread_t t;
|
||||
pthread_attr_t a;
|
||||
pthread_attr_init(&a);
|
||||
pthread_attr_setdetachstate(&a, PTHREAD_CREATE_DETACHED);
|
||||
if (pthread_create(&t, &a, aq_flusher, NULL) != 0) aq_flusher_started = 0;
|
||||
pthread_attr_destroy(&a);
|
||||
}
|
||||
pthread_cond_signal(&aq_cond);
|
||||
int r = aq_len >= AQ_FLUSH ? aq_flush_locked(conn) : 0;
|
||||
pthread_mutex_unlock(&aq_lock);
|
||||
release_enc(c);
|
||||
c->conn = NULL;
|
||||
if (r < 0) {
|
||||
VKB_ERR("lost the bridge server (%s)", strerror(errno));
|
||||
conn->dead = 1;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
/* Everything queued goes first, on this connection. */
|
||||
int qr = aq_flush_locked(conn);
|
||||
if (c->flags & VKB_F_NOREPLY) {
|
||||
h.seq = ++async_seq;
|
||||
h.barrier = h.seq - 1;
|
||||
} else {
|
||||
h.barrier = async_seq;
|
||||
}
|
||||
pthread_mutex_unlock(&aq_lock);
|
||||
if (qr < 0 || vkb_send_msg(conn->sock, &h, c->e.buf, c->e.fds, c->e.nfds) < 0) {
|
||||
VKB_ERR("%s: lost the bridge server (%s)", vkb_cmd_names[c->cmd], strerror(errno));
|
||||
conn->dead = 1;
|
||||
release_enc(c);
|
||||
c->conn = NULL;
|
||||
return 0;
|
||||
}
|
||||
release_enc(c);
|
||||
if (c->flags & VKB_F_NOREPLY) {
|
||||
c->conn = NULL;
|
||||
return 1;
|
||||
}
|
||||
int fds[VKB_MAX_FDS], nfds = 0;
|
||||
vkb_msg_hdr rh;
|
||||
if (vkb_recv_msg(conn->sock, &rh, &conn->in, &conn->incap, fds, &nfds) < 0) {
|
||||
VKB_ERR("%s: lost the bridge server while waiting for the reply", vkb_cmd_names[c->cmd]);
|
||||
conn->dead = 1;
|
||||
c->conn = NULL;
|
||||
return 0;
|
||||
}
|
||||
if (rh.cmd != VKB_ST_OK) {
|
||||
static const char *const why[] = {"ok", "malformed request", "the driver lacks this command", "unknown object"};
|
||||
VKB_ERR("%s: the server refused the call (%s)", vkb_cmd_names[c->cmd], rh.cmd < 4 ? why[rh.cmd] : "?");
|
||||
for (int i = 0; i < nfds; i++) close(fds[i]);
|
||||
c->conn = NULL;
|
||||
return 0;
|
||||
}
|
||||
/* The decoder keeps fds in a per-call array. */
|
||||
static __thread int call_fds[VKB_MAX_FDS];
|
||||
memcpy(call_fds, fds, sizeof(int) * (size_t)nfds);
|
||||
vkb_dec_init(&c->d, conn->in, rh.size, &conn->arena, call_fds, nfds);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void vkb_call_end(vkb_call *c)
|
||||
{
|
||||
if (!c->conn) return;
|
||||
if (c->d.err) VKB_ERR("%s: malformed reply", vkb_cmd_names[c->cmd]);
|
||||
vkb_dec_close_untaken(&c->d);
|
||||
vkb_arena_reset(&c->conn->arena);
|
||||
c->conn = NULL;
|
||||
}
|
||||
|
||||
/* VkResult commands treated as asynchronous (ASYNC_RESULT in the generator): on unless
|
||||
* VKBRIDGE_SYNC is set. */
|
||||
int vkb_async_results(void)
|
||||
{
|
||||
static int v = -1;
|
||||
if (v < 0) v = getenv("VKBRIDGE_SYNC") == NULL;
|
||||
return v;
|
||||
}
|
||||
|
||||
/* Asynchronous variant: no reply is awaited (void commands without outputs). */
|
||||
int vkb_call_exec_async(vkb_call *c)
|
||||
{
|
||||
if (getenv("VKBRIDGE_SYNC")) return vkb_call_exec(c) ? (vkb_call_end(c), 1) : 0;
|
||||
c->flags |= VKB_F_NOREPLY;
|
||||
return vkb_call_exec(c);
|
||||
}
|
||||
|
||||
/* VKBRIDGE_STATS=<seconds>: per-command counts of round trips and async sends, logged. */
|
||||
static uint32_t stat_sync[VKB_CMD_COUNT], stat_async[VKB_CMD_COUNT];
|
||||
static uint64_t stat_bytes;
|
||||
static uint64_t stat_last;
|
||||
void vkb_stats_count(uint32_t cmd, int async, size_t bytes)
|
||||
{
|
||||
static int period = -1;
|
||||
if (period < 0) {
|
||||
const char *v = getenv("VKBRIDGE_STATS");
|
||||
period = v ? atoi(v) : 0;
|
||||
}
|
||||
if (!period) return;
|
||||
__atomic_add_fetch(async ? &stat_async[cmd] : &stat_sync[cmd], 1, __ATOMIC_RELAXED);
|
||||
__atomic_add_fetch(&stat_bytes, bytes, __ATOMIC_RELAXED);
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
uint64_t now = (uint64_t)ts.tv_sec;
|
||||
uint64_t last = __atomic_load_n(&stat_last, __ATOMIC_RELAXED);
|
||||
if (!last) {
|
||||
__atomic_store_n(&stat_last, now, __ATOMIC_RELAXED);
|
||||
return;
|
||||
}
|
||||
if (now - last < (uint64_t)period || !__atomic_compare_exchange_n(&stat_last, &last, now, 0, __ATOMIC_RELAXED, __ATOMIC_RELAXED))
|
||||
return;
|
||||
uint32_t tot_s = 0, tot_a = 0;
|
||||
for (uint32_t i = 0; i < VKB_CMD_COUNT; i++) {
|
||||
tot_s += stat_sync[i];
|
||||
tot_a += stat_async[i];
|
||||
}
|
||||
vkb_log(VKB_LOG_ERROR, "stats over %us: %u round trips, %u async, %llu KB sent", period, tot_s, tot_a,
|
||||
(unsigned long long)(stat_bytes >> 10));
|
||||
for (int k = 0; k < 8; k++) {
|
||||
uint32_t best = 0, bi = 0;
|
||||
for (uint32_t i = 0; i < VKB_CMD_COUNT; i++)
|
||||
if (stat_sync[i] + stat_async[i] > best) {
|
||||
best = stat_sync[i] + stat_async[i];
|
||||
bi = i;
|
||||
}
|
||||
if (!best) break;
|
||||
vkb_log(VKB_LOG_ERROR, " %-40s %6u sync %6u async", vkb_cmd_names[bi], stat_sync[bi], stat_async[bi]);
|
||||
stat_sync[bi] = stat_async[bi] = 0;
|
||||
}
|
||||
memset(stat_sync, 0, sizeof(stat_sync));
|
||||
memset(stat_async, 0, sizeof(stat_async));
|
||||
stat_bytes = 0;
|
||||
}
|
||||
|
||||
uint32_t vkb_table_of(const void *h)
|
||||
{
|
||||
return h ? ((const vkb_object *)h)->table : 0;
|
||||
}
|
||||
|
||||
uint64_t vkb_remote(const void *h)
|
||||
{
|
||||
return h ? ((const vkb_object *)h)->remote : 0;
|
||||
}
|
||||
|
||||
int vkb_memfd(const char *name, size_t size)
|
||||
{
|
||||
int fd = (int)syscall(SYS_memfd_create, name, 1u /* MFD_CLOEXEC */);
|
||||
if (fd < 0) {
|
||||
/* No memfd (old kernel or a filter): an unlinked file in the runtime dir. */
|
||||
char path[256];
|
||||
const char *rd = getenv("XDG_RUNTIME_DIR");
|
||||
snprintf(path, sizeof(path), "%s/vkbridge-XXXXXX", rd && *rd ? rd : "/tmp");
|
||||
fd = mkostemp(path, O_CLOEXEC);
|
||||
if (fd < 0) return -1;
|
||||
unlink(path);
|
||||
}
|
||||
if (ftruncate(fd, (off_t)size) < 0) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ hash map */
|
||||
|
||||
void vkb_map_init(vkb_map *m)
|
||||
{
|
||||
memset(m, 0, sizeof(*m));
|
||||
pthread_mutex_init(&m->lock, NULL);
|
||||
}
|
||||
|
||||
void vkb_map_destroy(vkb_map *m)
|
||||
{
|
||||
free(m->keys);
|
||||
free(m->vals);
|
||||
pthread_mutex_destroy(&m->lock);
|
||||
memset(m, 0, sizeof(*m));
|
||||
}
|
||||
|
||||
static uint32_t hash64(uint64_t k)
|
||||
{
|
||||
k ^= k >> 33;
|
||||
k *= 0xff51afd7ed558ccdull;
|
||||
k ^= k >> 33;
|
||||
return (uint32_t)k;
|
||||
}
|
||||
|
||||
/* Keys are never 0 (VK_NULL_HANDLE); 0 marks an empty slot, ~0 a deleted one. */
|
||||
#define TOMB (~(uint64_t)0)
|
||||
|
||||
static void map_rehash(vkb_map *m, uint32_t ncap)
|
||||
{
|
||||
uint64_t *ok = m->keys;
|
||||
void **ov = m->vals;
|
||||
uint32_t oc = m->cap;
|
||||
m->keys = calloc(ncap, sizeof(uint64_t));
|
||||
m->vals = calloc(ncap, sizeof(void *));
|
||||
m->cap = ncap;
|
||||
m->count = 0;
|
||||
for (uint32_t i = 0; i < oc; i++) {
|
||||
if (ok[i] && ok[i] != TOMB) {
|
||||
uint32_t j = hash64(ok[i]) & (ncap - 1);
|
||||
while (m->keys[j]) j = (j + 1) & (ncap - 1);
|
||||
m->keys[j] = ok[i];
|
||||
m->vals[j] = ov[i];
|
||||
m->count++;
|
||||
}
|
||||
}
|
||||
free(ok);
|
||||
free(ov);
|
||||
}
|
||||
|
||||
void vkb_map_put(vkb_map *m, uint64_t k, void *v)
|
||||
{
|
||||
if (!k) return;
|
||||
pthread_mutex_lock(&m->lock);
|
||||
if (!m->cap || (m->count + 1) * 2 > m->cap) map_rehash(m, m->cap ? m->cap * 2 : 64);
|
||||
uint32_t j = hash64(k) & (m->cap - 1);
|
||||
int64_t tomb = -1;
|
||||
while (m->keys[j]) {
|
||||
if (m->keys[j] == k) {
|
||||
m->vals[j] = v;
|
||||
pthread_mutex_unlock(&m->lock);
|
||||
return;
|
||||
}
|
||||
if (m->keys[j] == TOMB && tomb < 0) tomb = j;
|
||||
j = (j + 1) & (m->cap - 1);
|
||||
}
|
||||
if (tomb >= 0) j = (uint32_t)tomb;
|
||||
m->keys[j] = k;
|
||||
m->vals[j] = v;
|
||||
m->count++;
|
||||
pthread_mutex_unlock(&m->lock);
|
||||
}
|
||||
|
||||
static int64_t map_find(vkb_map *m, uint64_t k)
|
||||
{
|
||||
if (!m->cap || !k) return -1;
|
||||
uint32_t j = hash64(k) & (m->cap - 1);
|
||||
for (uint32_t n = 0; n < m->cap && m->keys[j]; n++) {
|
||||
if (m->keys[j] == k) return j;
|
||||
j = (j + 1) & (m->cap - 1);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void *vkb_map_get(vkb_map *m, uint64_t k)
|
||||
{
|
||||
pthread_mutex_lock(&m->lock);
|
||||
int64_t j = map_find(m, k);
|
||||
void *v = j >= 0 ? m->vals[j] : NULL;
|
||||
pthread_mutex_unlock(&m->lock);
|
||||
return v;
|
||||
}
|
||||
|
||||
void *vkb_map_remove(vkb_map *m, uint64_t k)
|
||||
{
|
||||
pthread_mutex_lock(&m->lock);
|
||||
int64_t j = map_find(m, k);
|
||||
void *v = NULL;
|
||||
if (j >= 0) {
|
||||
v = m->vals[j];
|
||||
m->keys[j] = TOMB;
|
||||
m->vals[j] = NULL;
|
||||
/* count keeps tombstones, so the table rehashes before it fills with them */
|
||||
}
|
||||
pthread_mutex_unlock(&m->lock);
|
||||
return v;
|
||||
}
|
||||
@@ -0,0 +1,652 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Descriptor update templates and private data.
|
||||
*
|
||||
* A template's data is laid out by the app (offsets and strides into its own structures). The
|
||||
* server's copy of the template is created with packed entries instead, and every update packs
|
||||
* the app's data the same way before sending it, so the server passes the blob straight on.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "vkb_client.h"
|
||||
#include "gen/vkb_client_gen.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
typedef struct vkb_template {
|
||||
uint32_t count;
|
||||
size_t size; /* packed bytes */
|
||||
VkDescriptorUpdateTemplateEntry *app; /* the app's entries */
|
||||
size_t *packed; /* packed offset per entry */
|
||||
size_t *elem; /* element size per entry (inline: 1) */
|
||||
} vkb_template;
|
||||
|
||||
static size_t elem_size(VkDescriptorType t)
|
||||
{
|
||||
switch (t) {
|
||||
case VK_DESCRIPTOR_TYPE_SAMPLER:
|
||||
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
|
||||
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
|
||||
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
|
||||
case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
|
||||
return sizeof(VkDescriptorImageInfo);
|
||||
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
|
||||
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
|
||||
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
|
||||
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
|
||||
return sizeof(VkDescriptorBufferInfo);
|
||||
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
|
||||
case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
|
||||
return sizeof(VkBufferView);
|
||||
case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK:
|
||||
return 1;
|
||||
default:
|
||||
return 8;
|
||||
}
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateDescriptorUpdateTemplate(VkDevice device, const VkDescriptorUpdateTemplateCreateInfo *pCreateInfo,
|
||||
const VkAllocationCallbacks *pAllocator,
|
||||
VkDescriptorUpdateTemplate *pTemplate)
|
||||
{
|
||||
uint32_t n = pCreateInfo->descriptorUpdateEntryCount;
|
||||
vkb_template *t = calloc(1, sizeof(*t));
|
||||
t->count = n;
|
||||
t->app = calloc(n ? n : 1, sizeof(*t->app));
|
||||
t->packed = calloc(n ? n : 1, sizeof(size_t));
|
||||
t->elem = calloc(n ? n : 1, sizeof(size_t));
|
||||
VkDescriptorUpdateTemplateEntry *packed = calloc(n ? n : 1, sizeof(*packed));
|
||||
size_t off = 0;
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
const VkDescriptorUpdateTemplateEntry *e = &pCreateInfo->pDescriptorUpdateEntries[i];
|
||||
t->app[i] = *e;
|
||||
size_t es = elem_size(e->descriptorType);
|
||||
t->elem[i] = es;
|
||||
t->packed[i] = off;
|
||||
packed[i] = *e;
|
||||
packed[i].offset = off;
|
||||
packed[i].stride = es;
|
||||
off += (es * e->descriptorCount + 7) & ~(size_t)7;
|
||||
}
|
||||
t->size = off;
|
||||
VkDescriptorUpdateTemplateCreateInfo ci = *pCreateInfo;
|
||||
ci.pDescriptorUpdateEntries = packed;
|
||||
VkResult r = vkb_wire_vkCreateDescriptorUpdateTemplate(device, &ci, pAllocator, pTemplate);
|
||||
free(packed);
|
||||
if (r != VK_SUCCESS) {
|
||||
free(t->app);
|
||||
free(t->packed);
|
||||
free(t->elem);
|
||||
free(t);
|
||||
return r;
|
||||
}
|
||||
vkb_map_put(&vkb_dev(device)->templates, (uint64_t)*pTemplate, t);
|
||||
return r;
|
||||
}
|
||||
|
||||
static void template_free(vkb_template *t)
|
||||
{
|
||||
if (!t) return;
|
||||
free(t->app);
|
||||
free(t->packed);
|
||||
free(t->elem);
|
||||
free(t);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroyDescriptorUpdateTemplate(VkDevice device, VkDescriptorUpdateTemplate tmpl,
|
||||
const VkAllocationCallbacks *pAllocator)
|
||||
{
|
||||
if (!tmpl) return;
|
||||
template_free(vkb_map_remove(&vkb_dev(device)->templates, (uint64_t)tmpl));
|
||||
vkb_wire_vkDestroyDescriptorUpdateTemplate(device, tmpl, pAllocator);
|
||||
}
|
||||
|
||||
static void pack(const vkb_template *t, const void *pData, uint8_t *out)
|
||||
{
|
||||
const uint8_t *src = pData;
|
||||
for (uint32_t i = 0; i < t->count; i++) {
|
||||
const VkDescriptorUpdateTemplateEntry *e = &t->app[i];
|
||||
uint8_t *dst = out + t->packed[i];
|
||||
if (e->descriptorType == VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK) {
|
||||
memcpy(dst, src + e->offset, e->descriptorCount);
|
||||
continue;
|
||||
}
|
||||
size_t es = t->elem[i];
|
||||
for (uint32_t j = 0; j < e->descriptorCount; j++) memcpy(dst + j * es, src + e->offset + (size_t)j * e->stride, es);
|
||||
}
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkUpdateDescriptorSetWithTemplate(VkDevice device, VkDescriptorSet set, VkDescriptorUpdateTemplate tmpl,
|
||||
const void *pData)
|
||||
{
|
||||
vkb_device *dev = vkb_dev(device);
|
||||
vkb_template *t = vkb_map_get(&dev->templates, (uint64_t)tmpl);
|
||||
if (!t) {
|
||||
VKB_ERR("vkUpdateDescriptorSetWithTemplate with an unknown template");
|
||||
return;
|
||||
}
|
||||
vkb_call c;
|
||||
vkb_call_begin(&c, VKB_CMD_vkbDescUpdateRaw, dev->obj.table);
|
||||
vkb_enc_u64(&c.e, dev->obj.remote);
|
||||
vkb_enc_bytes(&c.e, &set, sizeof(set));
|
||||
vkb_enc_bytes(&c.e, &tmpl, sizeof(tmpl));
|
||||
/* Packed straight into the request. */
|
||||
vkb_enc_u8(&c.e, 1);
|
||||
vkb_enc_u64(&c.e, t->size);
|
||||
size_t at = vkb_enc_raw(&c.e, NULL, t->size);
|
||||
if (!c.e.oom) pack(t, pData, c.e.buf + at);
|
||||
vkb_call_exec_async(&c);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkCmdPushDescriptorSetWithTemplate(VkCommandBuffer commandBuffer, VkDescriptorUpdateTemplate tmpl,
|
||||
VkPipelineLayout layout, uint32_t set, const void *pData)
|
||||
{
|
||||
vkb_cmdbuf *cb = vkb_cb(commandBuffer);
|
||||
vkb_template *t = vkb_map_get(&cb->device->templates, (uint64_t)tmpl);
|
||||
if (!t) {
|
||||
VKB_ERR("vkCmdPushDescriptorSetWithTemplateKHR with an unknown template");
|
||||
return;
|
||||
}
|
||||
vkb_enc *e = vkb_cmd_record_begin(commandBuffer, VKB_CMD_vkbPushDescRaw);
|
||||
if (!e) return;
|
||||
vkb_enc_u64(e, cb->obj.remote);
|
||||
vkb_enc_bytes(e, &tmpl, sizeof(tmpl));
|
||||
vkb_enc_bytes(e, &layout, sizeof(layout));
|
||||
vkb_enc_u32(e, set);
|
||||
vkb_enc_u8(e, 1);
|
||||
vkb_enc_u64(e, t->size);
|
||||
size_t at = vkb_enc_raw(e, NULL, t->size);
|
||||
if (!e->oom) pack(t, pData, e->buf + at);
|
||||
vkb_cmd_record_end(commandBuffer);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ private data (client-local) */
|
||||
|
||||
typedef struct vkb_private_slot {
|
||||
vkb_map values;
|
||||
} vkb_private_slot;
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreatePrivateDataSlot(VkDevice device, const VkPrivateDataSlotCreateInfo *pCreateInfo,
|
||||
const VkAllocationCallbacks *pAllocator, VkPrivateDataSlot *pSlot)
|
||||
{
|
||||
(void)device;
|
||||
(void)pCreateInfo;
|
||||
(void)pAllocator;
|
||||
vkb_private_slot *s = calloc(1, sizeof(*s));
|
||||
if (!s) return VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
vkb_map_init(&s->values);
|
||||
*pSlot = (VkPrivateDataSlot)(uintptr_t)s;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroyPrivateDataSlot(VkDevice device, VkPrivateDataSlot slot, const VkAllocationCallbacks *pAllocator)
|
||||
{
|
||||
(void)device;
|
||||
(void)pAllocator;
|
||||
vkb_private_slot *s = (vkb_private_slot *)(uintptr_t)slot;
|
||||
if (!s) return;
|
||||
vkb_map_destroy(&s->values);
|
||||
free(s);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkSetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle,
|
||||
VkPrivateDataSlot slot, uint64_t data)
|
||||
{
|
||||
(void)device;
|
||||
(void)objectType;
|
||||
vkb_private_slot *s = (vkb_private_slot *)(uintptr_t)slot;
|
||||
vkb_map_put(&s->values, objectHandle, (void *)(uintptr_t)data);
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle,
|
||||
VkPrivateDataSlot slot, uint64_t *pData)
|
||||
{
|
||||
(void)device;
|
||||
(void)objectType;
|
||||
vkb_private_slot *s = (vkb_private_slot *)(uintptr_t)slot;
|
||||
*pData = (uint64_t)(uintptr_t)vkb_map_get(&s->values, objectHandle);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ debug utils (no-ops) */
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkSetDebugUtilsObjectNameEXT(VkDevice device, const VkDebugUtilsObjectNameInfoEXT *pNameInfo)
|
||||
{
|
||||
(void)device;
|
||||
(void)pNameInfo;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkSetDebugUtilsObjectTagEXT(VkDevice device, const VkDebugUtilsObjectTagInfoEXT *pTagInfo)
|
||||
{
|
||||
(void)device;
|
||||
(void)pTagInfo;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkQueueBeginDebugUtilsLabelEXT(VkQueue queue, const VkDebugUtilsLabelEXT *pLabelInfo)
|
||||
{
|
||||
(void)queue;
|
||||
(void)pLabelInfo;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkQueueEndDebugUtilsLabelEXT(VkQueue queue)
|
||||
{
|
||||
(void)queue;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkQueueInsertDebugUtilsLabelEXT(VkQueue queue, const VkDebugUtilsLabelEXT *pLabelInfo)
|
||||
{
|
||||
(void)queue;
|
||||
(void)pLabelInfo;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT *pCreateInfo,
|
||||
const VkAllocationCallbacks *pAllocator, VkDebugUtilsMessengerEXT *pMessenger)
|
||||
{
|
||||
(void)instance;
|
||||
(void)pCreateInfo;
|
||||
(void)pAllocator;
|
||||
static uint64_t next = 1;
|
||||
*pMessenger = (VkDebugUtilsMessengerEXT)(uintptr_t)__atomic_fetch_add(&next, 1, __ATOMIC_RELAXED);
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT messenger,
|
||||
const VkAllocationCallbacks *pAllocator)
|
||||
{
|
||||
(void)instance;
|
||||
(void)messenger;
|
||||
(void)pAllocator;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkSubmitDebugUtilsMessageEXT(VkInstance instance, VkDebugUtilsMessageSeverityFlagBitsEXT severity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT types,
|
||||
const VkDebugUtilsMessengerCallbackDataEXT *pData)
|
||||
{
|
||||
(void)instance;
|
||||
(void)severity;
|
||||
(void)types;
|
||||
(void)pData;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkCmdPushDescriptorSetWithTemplate2(VkCommandBuffer commandBuffer,
|
||||
const VkPushDescriptorSetWithTemplateInfo *pInfo)
|
||||
{
|
||||
vkb_ep_vkCmdPushDescriptorSetWithTemplate(commandBuffer, pInfo->descriptorUpdateTemplate, pInfo->layout, pInfo->set, pInfo->pData);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ host image copy (not offered) */
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCopyMemoryToImage(VkDevice device, const VkCopyMemoryToImageInfo *pInfo)
|
||||
{
|
||||
(void)device;
|
||||
(void)pInfo;
|
||||
return VK_ERROR_FEATURE_NOT_PRESENT;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCopyImageToMemory(VkDevice device, const VkCopyImageToMemoryInfo *pInfo)
|
||||
{
|
||||
(void)device;
|
||||
(void)pInfo;
|
||||
return VK_ERROR_FEATURE_NOT_PRESENT;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCopyImageToImage(VkDevice device, const VkCopyImageToImageInfo *pInfo)
|
||||
{
|
||||
(void)device;
|
||||
(void)pInfo;
|
||||
return VK_ERROR_FEATURE_NOT_PRESENT;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkTransitionImageLayout(VkDevice device, uint32_t transitionCount,
|
||||
const VkHostImageLayoutTransitionInfo *pTransitions)
|
||||
{
|
||||
(void)device;
|
||||
(void)transitionCount;
|
||||
(void)pTransitions;
|
||||
return VK_ERROR_FEATURE_NOT_PRESENT;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ descriptor set batching */
|
||||
|
||||
/*
|
||||
* DXVK allocates a descriptor set (or two) per draw; one round trip each was most of a frame's
|
||||
* time. Sets are allocated from the server in batches per (pool, layout) and handed out here. A
|
||||
* pool's reset or destruction drops what was cached for it (the reset freed those sets anyway).
|
||||
*
|
||||
* Where it can, the client keeps the pool's books: what each layout takes of every descriptor
|
||||
* type, and what the pool has left. A batch then asks for no more than fits, and a pool that is
|
||||
* full answers VK_ERROR_OUT_OF_POOL_MEMORY here, without a round trip (DXVK fills a pool every few
|
||||
* hundred draws). Pools and layouts the books cannot follow (sets freed one by one, inline uniform
|
||||
* blocks, mutable or variable-count descriptors) go by the driver's answers: a batch that does not
|
||||
* fit falls back to what the app asked for, so the app sees the out-of-pool error when it would.
|
||||
*/
|
||||
#define DS_BATCH 256
|
||||
#define DS_TYPES 12 /* VK_DESCRIPTOR_TYPE_SAMPLER..INPUT_ATTACHMENT, then ACCELERATION_STRUCTURE_KHR */
|
||||
|
||||
static int ds_type_index(VkDescriptorType t)
|
||||
{
|
||||
if ((uint32_t)t <= VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT) return (int)t;
|
||||
if (t == VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR) return 11;
|
||||
return -1;
|
||||
}
|
||||
|
||||
typedef struct ds_cache {
|
||||
VkDescriptorPool pool;
|
||||
VkDescriptorSetLayout layout;
|
||||
VkDescriptorSet sets[DS_BATCH];
|
||||
uint32_t n;
|
||||
uint32_t batch; /* grows from 4 to the pool's max_batch while the pool keeps up */
|
||||
struct ds_cache *next;
|
||||
} ds_cache;
|
||||
|
||||
static pthread_mutex_t ds_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static ds_cache *ds_head;
|
||||
|
||||
/* Pools the app made, with the batch they allow: a pool sized for exactly what the app will
|
||||
* allocate (DXVK's sampler pool) must not lose capacity to prefetching. */
|
||||
typedef struct ds_pool {
|
||||
VkDescriptorPool pool;
|
||||
uint32_t max_batch;
|
||||
int books; /* capacity below is kept */
|
||||
uint32_t max_sets, sets_left;
|
||||
uint32_t size[DS_TYPES], left[DS_TYPES];
|
||||
struct ds_pool *next;
|
||||
} ds_pool;
|
||||
static ds_pool *ds_pools;
|
||||
|
||||
/* Layouts with what one set of them takes; known = 0 when the books cannot follow it. */
|
||||
typedef struct ds_layout {
|
||||
VkDescriptorSetLayout layout;
|
||||
int known;
|
||||
uint32_t count[DS_TYPES];
|
||||
struct ds_layout *next;
|
||||
} ds_layout;
|
||||
static ds_layout *ds_layouts;
|
||||
|
||||
static ds_pool *pool_find(VkDescriptorPool pool)
|
||||
{
|
||||
for (ds_pool *p = ds_pools; p; p = p->next)
|
||||
if (p->pool == pool) return p;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ds_layout *layout_find(VkDescriptorSetLayout layout)
|
||||
{
|
||||
for (ds_layout *l = ds_layouts; l; l = l->next)
|
||||
if (l->layout == layout) return l;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateDescriptorSetLayout(VkDevice device, const VkDescriptorSetLayoutCreateInfo *ci,
|
||||
const VkAllocationCallbacks *pAllocator, VkDescriptorSetLayout *pLayout)
|
||||
{
|
||||
VkResult r = vkb_wire_vkCreateDescriptorSetLayout(device, ci, pAllocator, pLayout);
|
||||
if (r != VK_SUCCESS) return r;
|
||||
ds_layout *l = calloc(1, sizeof(*l));
|
||||
if (!l) return r;
|
||||
l->layout = *pLayout;
|
||||
/* Binding flags only matter for variable counts; anything else in the chain is left alone. */
|
||||
l->known = 1;
|
||||
for (const VkBaseInStructure *n = ci->pNext; n; n = n->pNext) {
|
||||
if (n->sType != VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO) {
|
||||
l->known = 0;
|
||||
continue;
|
||||
}
|
||||
const VkDescriptorSetLayoutBindingFlagsCreateInfo *bf = (const void *)n;
|
||||
for (uint32_t i = 0; i < bf->bindingCount; i++)
|
||||
if (bf->pBindingFlags[i] & VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT) l->known = 0;
|
||||
}
|
||||
for (uint32_t i = 0; i < ci->bindingCount && l->known; i++) {
|
||||
int t = ds_type_index(ci->pBindings[i].descriptorType);
|
||||
if (t < 0) l->known = 0;
|
||||
else l->count[t] += ci->pBindings[i].descriptorCount;
|
||||
}
|
||||
pthread_mutex_lock(&ds_lock);
|
||||
l->next = ds_layouts;
|
||||
ds_layouts = l;
|
||||
pthread_mutex_unlock(&ds_lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroyDescriptorSetLayout(VkDevice device, VkDescriptorSetLayout layout,
|
||||
const VkAllocationCallbacks *pAllocator)
|
||||
{
|
||||
if (!layout) return;
|
||||
pthread_mutex_lock(&ds_lock);
|
||||
for (ds_layout **pp = &ds_layouts; *pp; pp = &(*pp)->next) {
|
||||
if ((*pp)->layout == layout) {
|
||||
ds_layout *l = *pp;
|
||||
*pp = l->next;
|
||||
free(l);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Sets cached for it stay allocated in their pools, but a new layout may get its handle. */
|
||||
for (ds_cache **pp = &ds_head; *pp;) {
|
||||
if ((*pp)->layout == layout) {
|
||||
ds_cache *c = *pp;
|
||||
*pp = c->next;
|
||||
free(c);
|
||||
} else {
|
||||
pp = &(*pp)->next;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&ds_lock);
|
||||
vkb_wire_vkDestroyDescriptorSetLayout(device, layout, pAllocator);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateDescriptorPool(VkDevice device, const VkDescriptorPoolCreateInfo *ci,
|
||||
const VkAllocationCallbacks *pAllocator, VkDescriptorPool *pPool)
|
||||
{
|
||||
VkResult r = vkb_wire_vkCreateDescriptorPool(device, ci, pAllocator, pPool);
|
||||
if (r != VK_SUCCESS) return r;
|
||||
ds_pool *p = calloc(1, sizeof(*p));
|
||||
if (!p) return r;
|
||||
p->pool = *pPool;
|
||||
p->max_batch = ci->maxSets >= 256 ? ci->maxSets / 8 : 1;
|
||||
if (p->max_batch > DS_BATCH) p->max_batch = DS_BATCH;
|
||||
p->books = !ci->pNext && !(ci->flags & VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT);
|
||||
p->max_sets = p->sets_left = ci->maxSets;
|
||||
for (uint32_t i = 0; i < ci->poolSizeCount && p->books; i++) {
|
||||
int t = ds_type_index(ci->pPoolSizes[i].type);
|
||||
if (t < 0) p->books = 0;
|
||||
else p->size[t] += ci->pPoolSizes[i].descriptorCount;
|
||||
}
|
||||
memcpy(p->left, p->size, sizeof(p->left));
|
||||
VKB_DBG("descriptor pool: maxSets %u, %u pool sizes, prefetch up to %u%s", ci->maxSets, ci->poolSizeCount,
|
||||
p->max_batch, p->books ? ", capacity tracked" : "");
|
||||
pthread_mutex_lock(&ds_lock);
|
||||
p->next = ds_pools;
|
||||
ds_pools = p;
|
||||
pthread_mutex_unlock(&ds_lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
static ds_cache *ds_find(VkDescriptorPool pool, VkDescriptorSetLayout layout, int create)
|
||||
{
|
||||
for (ds_cache *c = ds_head; c; c = c->next)
|
||||
if (c->pool == pool && c->layout == layout) return c;
|
||||
if (!create) return NULL;
|
||||
ds_cache *c = calloc(1, sizeof(*c));
|
||||
if (!c) return NULL;
|
||||
c->pool = pool;
|
||||
c->layout = layout;
|
||||
c->batch = 4;
|
||||
c->next = ds_head;
|
||||
ds_head = c;
|
||||
return c;
|
||||
}
|
||||
|
||||
/* A reset frees the pool's sets, cached ones included; destruction also forgets the pool. The
|
||||
* learned batch size survives a reset (pools are reset every frame). */
|
||||
static void ds_drop_pool(VkDescriptorPool pool, int destroy)
|
||||
{
|
||||
pthread_mutex_lock(&ds_lock);
|
||||
ds_cache **pp = &ds_head;
|
||||
while (*pp) {
|
||||
if ((*pp)->pool == pool && destroy) {
|
||||
ds_cache *c = *pp;
|
||||
*pp = c->next;
|
||||
free(c);
|
||||
} else {
|
||||
if ((*pp)->pool == pool) (*pp)->n = 0;
|
||||
pp = &(*pp)->next;
|
||||
}
|
||||
}
|
||||
ds_pool *p = pool_find(pool);
|
||||
if (p && !destroy) {
|
||||
p->sets_left = p->max_sets;
|
||||
memcpy(p->left, p->size, sizeof(p->left));
|
||||
}
|
||||
pthread_mutex_unlock(&ds_lock);
|
||||
}
|
||||
|
||||
/* How many sets of layout l still fit in pool p by the books; UINT32_MAX when not kept. */
|
||||
static uint32_t ds_fits(const ds_pool *p, const ds_layout *l)
|
||||
{
|
||||
if (!p->books || !l || !l->known) return UINT32_MAX;
|
||||
uint32_t n = p->sets_left;
|
||||
for (int t = 0; t < DS_TYPES; t++)
|
||||
if (l->count[t] && p->left[t] / l->count[t] < n) n = p->left[t] / l->count[t];
|
||||
return n;
|
||||
}
|
||||
|
||||
/* Fills the cache for (pool, layout) with `want` sets; returns the driver's result. */
|
||||
static VkResult ds_refill(VkDevice device, ds_pool *p, const ds_layout *l, ds_cache *c, uint32_t want)
|
||||
{
|
||||
VkDescriptorSetLayout layouts[DS_BATCH];
|
||||
for (uint32_t i = 0; i < want; i++) layouts[i] = c->layout;
|
||||
VkDescriptorSetAllocateInfo ai = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, NULL, c->pool, want, layouts};
|
||||
VkResult r = vkb_wire_vkAllocateDescriptorSets(device, &ai, c->sets + c->n);
|
||||
if (r != VK_SUCCESS) return r;
|
||||
c->n += want;
|
||||
if (ds_fits(p, l) != UINT32_MAX) {
|
||||
p->sets_left -= want;
|
||||
for (int t = 0; t < DS_TYPES; t++) p->left[t] -= want * l->count[t];
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the client names itself: with the pool's books kept and every layout known, whether the
|
||||
* allocation fits is known here, so the sets get names (VKB_DS_TAG | n) at once and the server
|
||||
* allocates the driver's sets without a reply (server_dsmap.c). All or nothing, as the spec has
|
||||
* it. VK_ERROR_UNKNOWN: not for this path (a layout the books cannot follow).
|
||||
*/
|
||||
#define VKB_DS_TAG (1ull << 63)
|
||||
#define DS_NAMED_MAX 1024
|
||||
|
||||
static VkResult ds_alloc_named(VkDevice device, ds_pool *p, const VkDescriptorSetAllocateInfo *ai, VkDescriptorSet *out)
|
||||
{
|
||||
static uint64_t next_name;
|
||||
uint32_t n = ai->descriptorSetCount;
|
||||
if (!n || n > DS_NAMED_MAX) return VK_ERROR_UNKNOWN;
|
||||
uint64_t need[DS_TYPES] = {0};
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
const ds_layout *l = layout_find(ai->pSetLayouts[i]);
|
||||
if (!l || !l->known) return VK_ERROR_UNKNOWN;
|
||||
for (int t = 0; t < DS_TYPES; t++) need[t] += l->count[t];
|
||||
}
|
||||
if (n > p->sets_left) return VK_ERROR_OUT_OF_POOL_MEMORY;
|
||||
for (int t = 0; t < DS_TYPES; t++)
|
||||
if (need[t] > p->left[t]) return VK_ERROR_OUT_OF_POOL_MEMORY;
|
||||
vkb_device *dev = vkb_dev(device);
|
||||
vkb_call c;
|
||||
vkb_call_begin(&c, VKB_CMD_vkbAllocDescSets, dev->obj.table);
|
||||
vkb_enc_u64(&c.e, dev->obj.remote);
|
||||
vkb_enc_bytes(&c.e, &ai->descriptorPool, sizeof(ai->descriptorPool));
|
||||
vkb_enc_u32(&c.e, n);
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
out[i] = (VkDescriptorSet)(VKB_DS_TAG | __atomic_add_fetch(&next_name, 1, __ATOMIC_RELAXED));
|
||||
vkb_enc_bytes(&c.e, &ai->pSetLayouts[i], sizeof(ai->pSetLayouts[i]));
|
||||
vkb_enc_u64(&c.e, (uint64_t)out[i]);
|
||||
}
|
||||
if (!vkb_call_exec_async(&c)) {
|
||||
for (uint32_t i = 0; i < n; i++) out[i] = VK_NULL_HANDLE;
|
||||
return VK_ERROR_DEVICE_LOST;
|
||||
}
|
||||
p->sets_left -= n;
|
||||
for (int t = 0; t < DS_TYPES; t++) p->left[t] -= (uint32_t)need[t];
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkAllocateDescriptorSets(VkDevice device, const VkDescriptorSetAllocateInfo *ai, VkDescriptorSet *out)
|
||||
{
|
||||
/* Variable descriptor counts and the like are per allocation: those go straight through. */
|
||||
if (ai->pNext || getenv("VKBRIDGE_SYNC")) return vkb_wire_vkAllocateDescriptorSets(device, ai, out);
|
||||
pthread_mutex_lock(&ds_lock);
|
||||
ds_pool *p = pool_find(ai->descriptorPool);
|
||||
if (!p || p->max_batch <= 1) {
|
||||
pthread_mutex_unlock(&ds_lock);
|
||||
return vkb_wire_vkAllocateDescriptorSets(device, ai, out);
|
||||
}
|
||||
if (p->books) {
|
||||
VkResult nr = ds_alloc_named(device, p, ai, out);
|
||||
if (nr != VK_ERROR_UNKNOWN) {
|
||||
pthread_mutex_unlock(&ds_lock);
|
||||
return nr;
|
||||
}
|
||||
}
|
||||
VkResult r = VK_SUCCESS;
|
||||
uint32_t done = 0;
|
||||
for (; done < ai->descriptorSetCount; done++) {
|
||||
ds_cache *c = ds_find(ai->descriptorPool, ai->pSetLayouts[done], 1);
|
||||
if (!c) {
|
||||
r = VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
break;
|
||||
}
|
||||
if (c->batch > p->max_batch) c->batch = p->max_batch;
|
||||
if (!c->n) {
|
||||
const ds_layout *l = layout_find(c->layout);
|
||||
uint32_t fits = ds_fits(p, l), want = c->batch < fits ? c->batch : fits;
|
||||
if (!want) {
|
||||
r = VK_ERROR_OUT_OF_POOL_MEMORY;
|
||||
break;
|
||||
}
|
||||
r = ds_refill(device, p, l, c, want);
|
||||
if ((r == VK_ERROR_OUT_OF_POOL_MEMORY || r == VK_ERROR_FRAGMENTED_POOL) && want > 1) {
|
||||
/* The driver disagrees with the batch (or the books): just what was asked for. */
|
||||
c->batch = want / 2 > 4 ? want / 2 : 4;
|
||||
r = ds_refill(device, p, l, c, 1);
|
||||
} else if (r == VK_SUCCESS && c->batch < p->max_batch && want == c->batch) {
|
||||
c->batch = c->batch * 2 > p->max_batch ? p->max_batch : c->batch * 2;
|
||||
}
|
||||
if (r != VK_SUCCESS) break;
|
||||
}
|
||||
out[done] = c->sets[--c->n];
|
||||
}
|
||||
if (r != VK_SUCCESS) {
|
||||
/* None allocated, as the spec has it: what was taken goes back to the cache. */
|
||||
for (uint32_t i = 0; i < done; i++) {
|
||||
ds_cache *c = ds_find(ai->descriptorPool, ai->pSetLayouts[i], 0);
|
||||
if (c && c->n < DS_BATCH) c->sets[c->n++] = out[i];
|
||||
}
|
||||
for (uint32_t i = 0; i < ai->descriptorSetCount; i++) out[i] = VK_NULL_HANDLE;
|
||||
}
|
||||
pthread_mutex_unlock(&ds_lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkResetDescriptorPool(VkDevice device, VkDescriptorPool pool, VkDescriptorPoolResetFlags flags)
|
||||
{
|
||||
ds_drop_pool(pool, 0);
|
||||
return vkb_wire_vkResetDescriptorPool(device, pool, flags);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroyDescriptorPool(VkDevice device, VkDescriptorPool pool, const VkAllocationCallbacks *pAllocator)
|
||||
{
|
||||
if (!pool) return;
|
||||
ds_drop_pool(pool, 1);
|
||||
pthread_mutex_lock(&ds_lock);
|
||||
for (ds_pool **pp = &ds_pools; *pp; pp = &(*pp)->next) {
|
||||
if ((*pp)->pool == pool) {
|
||||
ds_pool *p = *pp;
|
||||
*pp = p->next;
|
||||
free(p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&ds_lock);
|
||||
vkb_wire_vkDestroyDescriptorPool(device, pool, pAllocator);
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Client side of feature emulation: the app sees a feature when the GPU has it or the server
|
||||
* emulates it (vkb_server_pd_info.emu), and does not see one the GPU lacks (.missing).
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "emu.h"
|
||||
#include "gen/vkb_client_gen.h"
|
||||
#include "vkb_emu_formats.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define EMU(pd, f) (((pd)->info.emu & (f)) != 0)
|
||||
#define MISSING(pd, f) (((pd)->info.missing & (f)) != 0 && !EMU(pd, f))
|
||||
|
||||
const VkExtensionProperties *vkb_emu_extensions(vkb_physdev *pd, uint32_t *n)
|
||||
{
|
||||
static __thread VkExtensionProperties list[8];
|
||||
uint32_t c = 0;
|
||||
if (EMU(pd, VKB_EMU_MAINT5)) list[c++] = (VkExtensionProperties){"VK_KHR_maintenance5", 1};
|
||||
if (EMU(pd, VKB_EMU_DIVISOR)) {
|
||||
list[c++] = (VkExtensionProperties){"VK_EXT_vertex_attribute_divisor", 3};
|
||||
list[c++] = (VkExtensionProperties){"VK_KHR_vertex_attribute_divisor", 1};
|
||||
}
|
||||
if (EMU(pd, VKB_EMU_DEPTH_CLIP)) list[c++] = (VkExtensionProperties){"VK_EXT_depth_clip_enable", 1};
|
||||
if (EMU(pd, VKB_EMU_ROBUSTNESS2)) list[c++] = (VkExtensionProperties){"VK_EXT_robustness2", 1};
|
||||
*n = c;
|
||||
return list;
|
||||
}
|
||||
|
||||
void vkb_emu_patch_limits(vkb_physdev *pd, VkPhysicalDeviceProperties *p)
|
||||
{
|
||||
(void)pd;
|
||||
(void)p;
|
||||
}
|
||||
|
||||
void vkb_emu_patch_properties_chain(vkb_physdev *pd, VkBaseOutStructure *b)
|
||||
{
|
||||
if (EMU(pd, VKB_EMU_ROBUSTNESS2) && b->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_PROPERTIES_EXT) {
|
||||
VkPhysicalDeviceRobustness2PropertiesEXT *r = (void *)b;
|
||||
r->robustStorageBufferAccessSizeAlignment = 4;
|
||||
r->robustUniformBufferAccessSizeAlignment = 256;
|
||||
}
|
||||
if (EMU(pd, VKB_EMU_DIVISOR)) {
|
||||
if (b->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_PROPERTIES_EXT)
|
||||
((VkPhysicalDeviceVertexAttributeDivisorPropertiesEXT *)b)->maxVertexAttribDivisor = UINT32_MAX;
|
||||
if (b->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_PROPERTIES) {
|
||||
VkPhysicalDeviceVertexAttributeDivisorProperties *p = (void *)b;
|
||||
p->maxVertexAttribDivisor = UINT32_MAX;
|
||||
p->supportsNonZeroFirstInstance = VK_TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void set_feature(vkb_physdev *pd, uint32_t flag, VkBool32 *f)
|
||||
{
|
||||
if (EMU(pd, flag)) *f = VK_TRUE;
|
||||
else if (MISSING(pd, flag)) *f = VK_FALSE;
|
||||
}
|
||||
|
||||
void vkb_emu_patch_features(vkb_physdev *pd, VkPhysicalDeviceFeatures *f)
|
||||
{
|
||||
set_feature(pd, VKB_EMU_BCN, &f->textureCompressionBC);
|
||||
set_feature(pd, VKB_EMU_CLIP_DISTANCE, &f->shaderClipDistance);
|
||||
set_feature(pd, VKB_EMU_CULL_DISTANCE, &f->shaderCullDistance);
|
||||
}
|
||||
|
||||
void vkb_emu_patch_features_chain(vkb_physdev *pd, VkBaseOutStructure *b)
|
||||
{
|
||||
switch ((int)b->sType) {
|
||||
case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_FEATURES:
|
||||
if (EMU(pd, VKB_EMU_MAINT5)) ((VkPhysicalDeviceMaintenance5Features *)b)->maintenance5 = VK_TRUE;
|
||||
break;
|
||||
case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_FEATURES: {
|
||||
VkPhysicalDeviceVertexAttributeDivisorFeatures *d = (void *)b;
|
||||
set_feature(pd, VKB_EMU_DIVISOR, &d->vertexAttributeInstanceRateDivisor);
|
||||
set_feature(pd, VKB_EMU_DIVISOR, &d->vertexAttributeInstanceRateZeroDivisor);
|
||||
break;
|
||||
}
|
||||
case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT:
|
||||
/* Emulated depth clip is pipeline state (clamp = not clip): neither may be dynamic. */
|
||||
if (MISSING(pd, VKB_EMU_DEPTH_CLIP) || EMU(pd, VKB_EMU_DEPTH_CLIP)) {
|
||||
VkPhysicalDeviceExtendedDynamicState3FeaturesEXT *d3 = (void *)b;
|
||||
d3->extendedDynamicState3DepthClipEnable = VK_FALSE;
|
||||
if (EMU(pd, VKB_EMU_DEPTH_CLIP)) d3->extendedDynamicState3DepthClampEnable = VK_FALSE;
|
||||
}
|
||||
break;
|
||||
case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT:
|
||||
if (EMU(pd, VKB_EMU_ROBUSTNESS2)) {
|
||||
VkPhysicalDeviceRobustness2FeaturesEXT *r = (void *)b;
|
||||
r->robustBufferAccess2 = r->robustImageAccess2 = r->nullDescriptor = VK_TRUE;
|
||||
}
|
||||
break;
|
||||
case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_ENABLE_FEATURES_EXT:
|
||||
set_feature(pd, VKB_EMU_DEPTH_CLIP, &((VkPhysicalDeviceDepthClipEnableFeaturesEXT *)b)->depthClipEnable);
|
||||
break;
|
||||
case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_FEATURES: {
|
||||
VkPhysicalDeviceVulkan14Features *v = (void *)b;
|
||||
v->hostImageCopy = VK_FALSE;
|
||||
set_feature(pd, VKB_EMU_DIVISOR, &v->vertexAttributeInstanceRateDivisor);
|
||||
set_feature(pd, VKB_EMU_DIVISOR, &v->vertexAttributeInstanceRateZeroDivisor);
|
||||
break;
|
||||
}
|
||||
case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_IMAGE_COPY_FEATURES:
|
||||
/* Host image copy hands the driver host pointers: never offered across the bridge. */
|
||||
((VkPhysicalDeviceHostImageCopyFeatures *)b)->hostImageCopy = VK_FALSE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int vkb_emu_is_emulated_format(vkb_physdev *pd, VkFormat format)
|
||||
{
|
||||
return vkb_is_bc(format) && EMU(pd, VKB_EMU_BCN);
|
||||
}
|
||||
|
||||
int vkb_emu_format_properties(vkb_physdev *pd, VkFormat format, VkFormatProperties *fp, VkFormatProperties2 *fp2)
|
||||
{
|
||||
if (!vkb_is_bc(format) || !(pd->info.missing & VKB_EMU_BCN)) return 0;
|
||||
memset(fp, 0, sizeof(*fp));
|
||||
if (EMU(pd, VKB_EMU_BCN)) {
|
||||
/* What the decoded image can do, limited to what a compressed texture is used for. */
|
||||
VkFormatProperties real;
|
||||
vkb_wire_vkGetPhysicalDeviceFormatProperties((VkPhysicalDevice)pd, vkb_bc_view_format(format), &real);
|
||||
fp->optimalTilingFeatures = real.optimalTilingFeatures &
|
||||
(VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT |
|
||||
VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT |
|
||||
VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_MINMAX_BIT);
|
||||
}
|
||||
if (fp2) {
|
||||
for (VkBaseOutStructure *b = (VkBaseOutStructure *)fp2->pNext; b; b = b->pNext) {
|
||||
if (b->sType == VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3) {
|
||||
VkFormatProperties3 *p3 = (void *)b;
|
||||
p3->linearTilingFeatures = 0;
|
||||
p3->optimalTilingFeatures = fp->optimalTilingFeatures;
|
||||
p3->bufferFeatures = 0;
|
||||
} else if (b->sType == VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT) {
|
||||
((VkDrmFormatModifierPropertiesListEXT *)b)->drmFormatModifierCount = 0;
|
||||
} else if (b->sType == VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_2_EXT) {
|
||||
((VkDrmFormatModifierPropertiesList2EXT *)b)->drmFormatModifierCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
VkFormat vkb_emu_real_format(vkb_physdev *pd, VkFormat format, VkImageUsageFlags *usage, VkImageCreateFlags *flags)
|
||||
{
|
||||
if (!vkb_emu_is_emulated_format(pd, format)) return format;
|
||||
/* As the server creates it (server_emu_bcn.c): decoded format, mutable, extended usage. */
|
||||
*flags &= ~(VkImageCreateFlags)VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT;
|
||||
*flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | VK_IMAGE_CREATE_EXTENDED_USAGE_BIT;
|
||||
*usage |= VK_IMAGE_USAGE_STORAGE_BIT;
|
||||
return vkb_bc_view_format(format);
|
||||
}
|
||||
|
||||
/* Image creation is rewritten on the server (it knows the decode); nothing to do here. */
|
||||
void vkb_emu_image_create_info(vkb_device *dev, VkImageCreateInfo *ci) { (void)dev; (void)ci; }
|
||||
void vkb_emu_image_created(vkb_device *dev, const VkImageCreateInfo *ci, VkImage image) { (void)dev; (void)ci; (void)image; }
|
||||
void vkb_emu_image_destroyed(vkb_device *dev, VkImage image) { (void)dev; (void)image; }
|
||||
@@ -0,0 +1,24 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Client side of feature emulation: reporting what the server emulates (see server_emu.c).
|
||||
*/
|
||||
#ifndef VKB_CLIENT_EMU_H
|
||||
#define VKB_CLIENT_EMU_H
|
||||
|
||||
#include "vkb_client.h"
|
||||
|
||||
const VkExtensionProperties *vkb_emu_extensions(vkb_physdev *pd, uint32_t *n);
|
||||
void vkb_emu_patch_limits(vkb_physdev *pd, VkPhysicalDeviceProperties *p);
|
||||
void vkb_emu_patch_properties_chain(vkb_physdev *pd, VkBaseOutStructure *b);
|
||||
void vkb_emu_patch_features(vkb_physdev *pd, VkPhysicalDeviceFeatures *f);
|
||||
void vkb_emu_patch_features_chain(vkb_physdev *pd, VkBaseOutStructure *b);
|
||||
/* 1 = handled (format is emulated). fp2 may be NULL. */
|
||||
int vkb_emu_format_properties(vkb_physdev *pd, VkFormat format, VkFormatProperties *fp, VkFormatProperties2 *fp2);
|
||||
/* The format the server really uses for an emulated one (usage/flags adjusted); else format. */
|
||||
VkFormat vkb_emu_real_format(vkb_physdev *pd, VkFormat format, VkImageUsageFlags *usage, VkImageCreateFlags *flags);
|
||||
int vkb_emu_is_emulated_format(vkb_physdev *pd, VkFormat format);
|
||||
void vkb_emu_image_create_info(vkb_device *dev, VkImageCreateInfo *ci);
|
||||
void vkb_emu_image_created(vkb_device *dev, const VkImageCreateInfo *ci, VkImage image);
|
||||
void vkb_emu_image_destroyed(vkb_device *dev, VkImage image);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,593 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Loader interface, instances, physical devices, devices and queues.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "vkb_client.h"
|
||||
#include "gen/vkb_client_gen.h"
|
||||
#include "wsi.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ------------------------------------------------------------------ proc lookup */
|
||||
|
||||
static int proc_cmp(const void *a, const void *b)
|
||||
{
|
||||
return strcmp(((const vkb_proc_desc *)a)->name, ((const vkb_proc_desc *)b)->name);
|
||||
}
|
||||
|
||||
static vkb_proc_desc *sorted_procs;
|
||||
static pthread_once_t procs_once = PTHREAD_ONCE_INIT;
|
||||
|
||||
static void procs_init(void)
|
||||
{
|
||||
sorted_procs = malloc(vkb_client_proc_count * sizeof(vkb_proc_desc));
|
||||
memcpy(sorted_procs, vkb_client_procs, vkb_client_proc_count * sizeof(vkb_proc_desc));
|
||||
qsort(sorted_procs, vkb_client_proc_count, sizeof(vkb_proc_desc), proc_cmp);
|
||||
}
|
||||
|
||||
static const vkb_proc_desc *find_proc(const char *name)
|
||||
{
|
||||
pthread_once(&procs_once, procs_init);
|
||||
vkb_proc_desc key = {name, NULL, 0, NULL};
|
||||
return bsearch(&key, sorted_procs, vkb_client_proc_count, sizeof(vkb_proc_desc), proc_cmp);
|
||||
}
|
||||
|
||||
int vkb_device_ext_enabled(const vkb_device *d, const char *name)
|
||||
{
|
||||
for (uint32_t i = 0; i < d->enabled_ext_count; i++)
|
||||
if (!strcmp(d->enabled_exts[i], name)) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
PFN_vkVoidFunction vkb_lookup_proc(const char *name, int level_max, vkb_device *dev, vkb_instance *inst)
|
||||
{
|
||||
(void)inst;
|
||||
if (!name) return NULL;
|
||||
const vkb_proc_desc *p = find_proc(name);
|
||||
if (!p) return NULL;
|
||||
if (level_max == VKB_LEVEL_DEVICE) {
|
||||
if (p->level != VKB_LEVEL_DEVICE) return NULL;
|
||||
if (dev && p->ext && !vkb_device_ext_enabled(dev, p->ext)) return NULL;
|
||||
/* Core commands newer than the device's Vulkan version do not exist for it. */
|
||||
if (dev && p->core && p->core > dev->api_version) return NULL;
|
||||
}
|
||||
return p->fn;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ loader entry points */
|
||||
|
||||
VKB_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vk_icdNegotiateLoaderICDInterfaceVersion(uint32_t *pVersion)
|
||||
{
|
||||
if (*pVersion > 5) *pVersion = 5;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKB_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vk_icdGetInstanceProcAddr(VkInstance instance, const char *pName)
|
||||
{
|
||||
if (!pName) return NULL;
|
||||
if (!instance) {
|
||||
const vkb_proc_desc *p = find_proc(pName);
|
||||
if (!p) return NULL;
|
||||
if (p->level == VKB_LEVEL_GLOBAL || !strcmp(pName, "vkGetInstanceProcAddr")) return p->fn;
|
||||
return NULL;
|
||||
}
|
||||
return vkb_lookup_proc(pName, VKB_LEVEL_DEVICE + 1, NULL, vkb_inst(instance));
|
||||
}
|
||||
|
||||
VKB_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vk_icdGetPhysicalDeviceProcAddr(VkInstance instance, const char *pName)
|
||||
{
|
||||
const vkb_proc_desc *p = find_proc(pName);
|
||||
if (!p || p->level != VKB_LEVEL_PHYSDEV) return NULL;
|
||||
(void)instance;
|
||||
return p->fn;
|
||||
}
|
||||
|
||||
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkb_ep_vkGetInstanceProcAddr(VkInstance instance, const char *pName)
|
||||
{
|
||||
return vk_icdGetInstanceProcAddr(instance, pName);
|
||||
}
|
||||
|
||||
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkb_ep_vkGetDeviceProcAddr(VkDevice device, const char *pName)
|
||||
{
|
||||
return vkb_lookup_proc(pName, VKB_LEVEL_DEVICE, vkb_dev(device), NULL);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ wrapping server objects */
|
||||
|
||||
void *vkb_wrap(VkObjectType type, const void *parent, uint64_t remote)
|
||||
{
|
||||
if (!remote) return NULL;
|
||||
switch (type) {
|
||||
case VK_OBJECT_TYPE_PHYSICAL_DEVICE: {
|
||||
vkb_instance *inst = (vkb_instance *)parent;
|
||||
pthread_mutex_lock(&inst->lock);
|
||||
vkb_physdev *pd = inst->pds;
|
||||
while (pd && pd->obj.remote != remote) pd = pd->list_next;
|
||||
if (!pd) {
|
||||
pd = calloc(1, sizeof(*pd));
|
||||
pd->obj.loader.loaderMagic = ICD_LOADER_MAGIC;
|
||||
pd->obj.type = type;
|
||||
pd->obj.remote = remote;
|
||||
pd->obj.table = inst->obj.table;
|
||||
pd->instance = inst;
|
||||
pthread_mutex_init(&pd->lock, NULL);
|
||||
pd->list_next = inst->pds;
|
||||
inst->pds = pd;
|
||||
}
|
||||
pthread_mutex_unlock(&inst->lock);
|
||||
return pd;
|
||||
}
|
||||
case VK_OBJECT_TYPE_QUEUE: {
|
||||
vkb_device *dev = (vkb_device *)parent;
|
||||
pthread_mutex_lock(&dev->lock);
|
||||
vkb_queue *q = dev->queues;
|
||||
while (q && q->obj.remote != remote) q = q->next;
|
||||
if (!q) {
|
||||
q = calloc(1, sizeof(*q));
|
||||
q->obj.loader.loaderMagic = ICD_LOADER_MAGIC;
|
||||
q->obj.type = type;
|
||||
q->obj.remote = remote;
|
||||
q->obj.table = dev->obj.table;
|
||||
q->device = dev;
|
||||
q->next = dev->queues;
|
||||
dev->queues = q;
|
||||
}
|
||||
pthread_mutex_unlock(&dev->lock);
|
||||
return q;
|
||||
}
|
||||
case VK_OBJECT_TYPE_COMMAND_BUFFER: {
|
||||
vkb_device *dev = (vkb_device *)parent;
|
||||
vkb_cmdbuf *cb = calloc(1, sizeof(*cb));
|
||||
cb->obj.loader.loaderMagic = ICD_LOADER_MAGIC;
|
||||
cb->obj.type = type;
|
||||
cb->obj.remote = remote;
|
||||
cb->obj.table = dev->obj.table;
|
||||
cb->device = dev;
|
||||
vkb_enc_init(&cb->stream);
|
||||
return cb;
|
||||
}
|
||||
default:
|
||||
VKB_ERR("internal: cannot wrap object type %d", type);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ instance */
|
||||
|
||||
static uint32_t server_instance_version(void)
|
||||
{
|
||||
static uint32_t v;
|
||||
if (!v) {
|
||||
uint32_t sv = VK_API_VERSION_1_0;
|
||||
if (vkb_wire_vkEnumerateInstanceVersion(&sv) != VK_SUCCESS) return VK_API_VERSION_1_0;
|
||||
extern uint32_t vkb_max_api(void);
|
||||
v = sv < vkb_max_api() ? sv : vkb_max_api();
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkEnumerateInstanceVersion(uint32_t *pApiVersion)
|
||||
{
|
||||
if (!vkb_connected()) {
|
||||
*pApiVersion = VK_API_VERSION_1_0;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
*pApiVersion = server_instance_version();
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
static const char *const client_instance_exts[] = {
|
||||
"VK_KHR_surface", "VK_KHR_wayland_surface", "VK_KHR_xcb_surface", "VK_KHR_xlib_surface",
|
||||
"VK_EXT_headless_surface", "VK_KHR_get_surface_capabilities2", "VK_EXT_swapchain_colorspace",
|
||||
"VK_EXT_debug_utils",
|
||||
};
|
||||
|
||||
static int bridged_ext(const char *name, int device)
|
||||
{
|
||||
for (int i = 0; i < VKB_EXT_COUNT; i++)
|
||||
if (vkb_exts[i].device == device && !vkb_exts[i].client && !strcmp(vkb_exts[i].name, name)) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static VkResult fill_props(const VkExtensionProperties *list, uint32_t n, uint32_t *pCount, VkExtensionProperties *out)
|
||||
{
|
||||
if (!out) {
|
||||
*pCount = n;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
uint32_t c = *pCount < n ? *pCount : n;
|
||||
memcpy(out, list, c * sizeof(*out));
|
||||
*pCount = c;
|
||||
return c < n ? VK_INCOMPLETE : VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkEnumerateInstanceExtensionProperties(const char *pLayerName, uint32_t *pPropertyCount,
|
||||
VkExtensionProperties *pProperties)
|
||||
{
|
||||
if (pLayerName) return VK_ERROR_LAYER_NOT_PRESENT;
|
||||
static VkExtensionProperties *list;
|
||||
static uint32_t count;
|
||||
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
pthread_mutex_lock(&lock);
|
||||
if (!list) {
|
||||
if (!vkb_connected()) {
|
||||
/* No server: offer nothing, and vkCreateInstance will say the driver is incompatible. */
|
||||
pthread_mutex_unlock(&lock);
|
||||
*pPropertyCount = 0;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
uint32_t n = 0;
|
||||
vkb_wire_vkEnumerateInstanceExtensionProperties(NULL, &n, NULL);
|
||||
VkExtensionProperties *srv = calloc(n + 1, sizeof(*srv));
|
||||
vkb_wire_vkEnumerateInstanceExtensionProperties(NULL, &n, srv);
|
||||
size_t nc = sizeof(client_instance_exts) / sizeof(client_instance_exts[0]);
|
||||
list = calloc(n + nc, sizeof(*list));
|
||||
for (uint32_t i = 0; i < n; i++)
|
||||
if (bridged_ext(srv[i].extensionName, 0)) list[count++] = srv[i];
|
||||
for (size_t i = 0; i < nc; i++) {
|
||||
snprintf(list[count].extensionName, sizeof(list[count].extensionName), "%s", client_instance_exts[i]);
|
||||
list[count].specVersion = 1;
|
||||
if (!strcmp(client_instance_exts[i], "VK_KHR_surface")) list[count].specVersion = 25;
|
||||
if (!strcmp(client_instance_exts[i], "VK_KHR_wayland_surface")) list[count].specVersion = 6;
|
||||
if (!strcmp(client_instance_exts[i], "VK_KHR_xcb_surface")) list[count].specVersion = 6;
|
||||
if (!strcmp(client_instance_exts[i], "VK_KHR_xlib_surface")) list[count].specVersion = 6;
|
||||
if (!strcmp(client_instance_exts[i], "VK_EXT_swapchain_colorspace")) list[count].specVersion = 4;
|
||||
if (!strcmp(client_instance_exts[i], "VK_EXT_debug_utils")) list[count].specVersion = 2;
|
||||
count++;
|
||||
}
|
||||
free(srv);
|
||||
}
|
||||
pthread_mutex_unlock(&lock);
|
||||
return fill_props(list, count, pPropertyCount, pProperties);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkEnumerateInstanceLayerProperties(uint32_t *pPropertyCount, VkLayerProperties *pProperties)
|
||||
{
|
||||
(void)pProperties;
|
||||
*pPropertyCount = 0;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
static int is_client_instance_ext(const char *n)
|
||||
{
|
||||
for (size_t i = 0; i < sizeof(client_instance_exts) / sizeof(client_instance_exts[0]); i++)
|
||||
if (!strcmp(client_instance_exts[i], n)) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateInstance(const VkInstanceCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator,
|
||||
VkInstance *pInstance)
|
||||
{
|
||||
(void)pAllocator;
|
||||
if (!vkb_connected()) return VK_ERROR_INCOMPATIBLE_DRIVER;
|
||||
vkb_instance *inst = calloc(1, sizeof(*inst));
|
||||
if (!inst) return VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
inst->obj.loader.loaderMagic = ICD_LOADER_MAGIC;
|
||||
inst->obj.type = VK_OBJECT_TYPE_INSTANCE;
|
||||
pthread_mutex_init(&inst->lock, NULL);
|
||||
|
||||
VkInstanceCreateInfo ci = *pCreateInfo;
|
||||
const char **exts = calloc(ci.enabledExtensionCount + 1, sizeof(char *));
|
||||
uint32_t n = 0;
|
||||
for (uint32_t i = 0; i < ci.enabledExtensionCount; i++) {
|
||||
const char *e = ci.ppEnabledExtensionNames[i];
|
||||
VKB_DBG("instance extension requested: %s", e);
|
||||
if (is_client_instance_ext(e)) {
|
||||
if (!strcmp(e, "VK_KHR_surface")) inst->ext_surface = 1;
|
||||
else if (!strcmp(e, "VK_KHR_wayland_surface")) inst->ext_wayland = 1;
|
||||
else if (!strcmp(e, "VK_KHR_xcb_surface")) inst->ext_xcb = 1;
|
||||
else if (!strcmp(e, "VK_KHR_xlib_surface")) inst->ext_xlib = 1;
|
||||
else if (!strcmp(e, "VK_EXT_headless_surface")) inst->ext_headless = 1;
|
||||
else if (!strcmp(e, "VK_KHR_get_surface_capabilities2")) inst->ext_surface_caps2 = 1;
|
||||
else if (!strcmp(e, "VK_EXT_debug_utils")) inst->ext_debug_utils = 1;
|
||||
continue;
|
||||
}
|
||||
exts[n++] = e;
|
||||
}
|
||||
ci.enabledExtensionCount = n;
|
||||
ci.ppEnabledExtensionNames = exts;
|
||||
ci.enabledLayerCount = 0;
|
||||
ci.ppEnabledLayerNames = NULL;
|
||||
/* The app's version goes through as asked: the device's effective version is the lower of it
|
||||
* and the GPU's, which is what a driver does (and what apps compute for themselves). */
|
||||
if (ci.pApplicationInfo) {
|
||||
inst->api_version = ci.pApplicationInfo->apiVersion ? ci.pApplicationInfo->apiVersion : VK_API_VERSION_1_0;
|
||||
} else {
|
||||
inst->api_version = VK_API_VERSION_1_0;
|
||||
}
|
||||
|
||||
vkb_call c;
|
||||
vkb_call_begin(&c, VKB_CMD_vkCreateInstance, 0);
|
||||
vkb_enc_u8(&c.e, 1);
|
||||
vkb_enc_VkInstanceCreateInfo(&c.e, &ci);
|
||||
vkb_enc_u8(&c.e, 1);
|
||||
free(exts);
|
||||
if (!vkb_call_exec(&c)) {
|
||||
free(inst);
|
||||
return VK_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
VkResult r = (VkResult)vkb_dec_u32(&c.d);
|
||||
inst->obj.remote = vkb_dec_u64(&c.d);
|
||||
inst->obj.table = vkb_dec_u32(&c.d);
|
||||
vkb_call_end(&c);
|
||||
if (r != VK_SUCCESS) {
|
||||
free(inst);
|
||||
return r;
|
||||
}
|
||||
VKB_INFO("instance created (Vulkan %u.%u)", VK_API_VERSION_MAJOR(inst->api_version), VK_API_VERSION_MINOR(inst->api_version));
|
||||
*pInstance = (VkInstance)inst;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroyInstance(VkInstance instance, const VkAllocationCallbacks *pAllocator)
|
||||
{
|
||||
(void)pAllocator;
|
||||
if (!instance) return;
|
||||
vkb_instance *inst = vkb_inst(instance);
|
||||
vkb_call c;
|
||||
vkb_call_begin(&c, VKB_CMD_vkDestroyInstance, inst->obj.table);
|
||||
vkb_enc_u64(&c.e, inst->obj.remote);
|
||||
if (vkb_call_exec(&c)) vkb_call_end(&c);
|
||||
vkb_physdev *pd = inst->pds;
|
||||
while (pd) {
|
||||
vkb_physdev *n = pd->list_next;
|
||||
free(pd->exts);
|
||||
pthread_mutex_destroy(&pd->lock);
|
||||
free(pd);
|
||||
pd = n;
|
||||
}
|
||||
pthread_mutex_destroy(&inst->lock);
|
||||
free(inst);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ physical devices */
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkEnumeratePhysicalDevices(VkInstance instance, uint32_t *pCount, VkPhysicalDevice *pDevices)
|
||||
{
|
||||
VkResult r = vkb_wire_vkEnumeratePhysicalDevices(instance, pCount, pDevices);
|
||||
if (pDevices && (r == VK_SUCCESS || r == VK_INCOMPLETE)) {
|
||||
for (uint32_t i = 0; i < *pCount; i++) vkb_physdev_ready(vkb_pd(pDevices[i]));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkEnumeratePhysicalDeviceGroups(VkInstance instance, uint32_t *pCount,
|
||||
VkPhysicalDeviceGroupProperties *pGroups)
|
||||
{
|
||||
/* One group per device: what a single-GPU driver reports. */
|
||||
uint32_t n = 0;
|
||||
VkResult r = vkb_ep_vkEnumeratePhysicalDevices(instance, &n, NULL);
|
||||
if (r != VK_SUCCESS) return r;
|
||||
if (!pGroups) {
|
||||
*pCount = n;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
VkPhysicalDevice *pds = calloc(n ? n : 1, sizeof(*pds));
|
||||
r = vkb_ep_vkEnumeratePhysicalDevices(instance, &n, pds);
|
||||
uint32_t c = *pCount < n ? *pCount : n;
|
||||
for (uint32_t i = 0; i < c; i++) {
|
||||
pGroups[i].physicalDeviceCount = 1;
|
||||
memset(pGroups[i].physicalDevices, 0, sizeof(pGroups[i].physicalDevices));
|
||||
pGroups[i].physicalDevices[0] = pds[i];
|
||||
pGroups[i].subsetAllocation = VK_FALSE;
|
||||
}
|
||||
free(pds);
|
||||
*pCount = c;
|
||||
return c < n ? VK_INCOMPLETE : r;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, const char *pLayerName,
|
||||
uint32_t *pPropertyCount, VkExtensionProperties *pProperties)
|
||||
{
|
||||
if (pLayerName) return VK_ERROR_LAYER_NOT_PRESENT;
|
||||
vkb_physdev *pd = vkb_pd(physicalDevice);
|
||||
if (!vkb_physdev_ready(pd)) {
|
||||
*pPropertyCount = 0;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
return fill_props(pd->exts, pd->next, pPropertyCount, pProperties);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkEnumerateDeviceLayerProperties(VkPhysicalDevice physicalDevice, uint32_t *pPropertyCount,
|
||||
VkLayerProperties *pProperties)
|
||||
{
|
||||
(void)physicalDevice;
|
||||
(void)pProperties;
|
||||
*pPropertyCount = 0;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ device */
|
||||
|
||||
static const char *const client_device_exts[] = {
|
||||
"VK_KHR_swapchain", "VK_KHR_incremental_present", "VK_KHR_swapchain_mutable_format",
|
||||
};
|
||||
|
||||
int vkb_is_client_device_ext(const char *n)
|
||||
{
|
||||
for (size_t i = 0; i < sizeof(client_device_exts) / sizeof(client_device_exts[0]); i++)
|
||||
if (!strcmp(client_device_exts[i], n)) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *const *vkb_client_device_exts(uint32_t *n)
|
||||
{
|
||||
*n = sizeof(client_device_exts) / sizeof(client_device_exts[0]);
|
||||
return client_device_exts;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo *pCreateInfo,
|
||||
const VkAllocationCallbacks *pAllocator, VkDevice *pDevice)
|
||||
{
|
||||
(void)pAllocator;
|
||||
vkb_physdev *pd = vkb_pd(physicalDevice);
|
||||
if (!vkb_physdev_ready(pd)) return VK_ERROR_INITIALIZATION_FAILED;
|
||||
vkb_device *dev = calloc(1, sizeof(*dev));
|
||||
if (!dev) return VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
dev->obj.loader.loaderMagic = ICD_LOADER_MAGIC;
|
||||
dev->obj.type = VK_OBJECT_TYPE_DEVICE;
|
||||
dev->pd = pd;
|
||||
dev->instance = pd->instance;
|
||||
pthread_mutex_init(&dev->lock, NULL);
|
||||
|
||||
VkDeviceCreateInfo ci = *pCreateInfo;
|
||||
const char **server_exts = calloc(ci.enabledExtensionCount + 1, sizeof(char *));
|
||||
dev->enabled_exts = calloc(ci.enabledExtensionCount + 1, sizeof(char *));
|
||||
uint32_t n = 0;
|
||||
for (uint32_t i = 0; i < ci.enabledExtensionCount; i++) {
|
||||
const char *e = ci.ppEnabledExtensionNames[i];
|
||||
VKB_DBG("device extension requested: %s", e);
|
||||
dev->enabled_exts[dev->enabled_ext_count++] = strdup(e);
|
||||
if (!strcmp(e, "VK_KHR_swapchain")) dev->ext_swapchain = 1;
|
||||
if (vkb_is_client_device_ext(e)) continue;
|
||||
server_exts[n++] = e;
|
||||
}
|
||||
ci.enabledExtensionCount = n;
|
||||
ci.ppEnabledExtensionNames = server_exts;
|
||||
ci.enabledLayerCount = 0;
|
||||
ci.ppEnabledLayerNames = NULL;
|
||||
|
||||
vkb_call c;
|
||||
vkb_call_begin(&c, VKB_CMD_vkCreateDevice, pd->obj.table);
|
||||
vkb_enc_u64(&c.e, pd->obj.remote);
|
||||
vkb_enc_u8(&c.e, 1);
|
||||
vkb_enc_VkDeviceCreateInfo(&c.e, &ci);
|
||||
vkb_enc_u8(&c.e, 1);
|
||||
free(server_exts);
|
||||
VkResult r;
|
||||
if (!vkb_call_exec(&c)) {
|
||||
r = VK_ERROR_INITIALIZATION_FAILED;
|
||||
} else {
|
||||
r = (VkResult)vkb_dec_u32(&c.d);
|
||||
dev->obj.remote = vkb_dec_u64(&c.d);
|
||||
dev->obj.table = vkb_dec_u32(&c.d);
|
||||
vkb_call_end(&c);
|
||||
}
|
||||
if (r != VK_SUCCESS) {
|
||||
for (uint32_t i = 0; i < dev->enabled_ext_count; i++) free((void *)dev->enabled_exts[i]);
|
||||
free(dev->enabled_exts);
|
||||
free(dev);
|
||||
return r;
|
||||
}
|
||||
{
|
||||
extern uint32_t vkb_max_api(void);
|
||||
uint32_t v = pd->props.apiVersion < vkb_max_api() ? pd->props.apiVersion : vkb_max_api();
|
||||
dev->api_version = v < pd->instance->api_version ? v : pd->instance->api_version;
|
||||
}
|
||||
vkb_map_init(&dev->memories);
|
||||
vkb_map_init(&dev->templates);
|
||||
vkb_map_init(&dev->pools);
|
||||
vkb_map_init(&dev->unshared);
|
||||
vkb_map_init(&dev->shared_images);
|
||||
vkb_memory_init_device(dev);
|
||||
VKB_INFO("device created on %s", pd->props.deviceName);
|
||||
*pDevice = (VkDevice)dev;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroyDevice(VkDevice device, const VkAllocationCallbacks *pAllocator)
|
||||
{
|
||||
(void)pAllocator;
|
||||
if (!device) return;
|
||||
vkb_device *dev = vkb_dev(device);
|
||||
vkb_call c;
|
||||
vkb_call_begin(&c, VKB_CMD_vkDestroyDevice, dev->obj.table);
|
||||
vkb_enc_u64(&c.e, dev->obj.remote);
|
||||
if (vkb_call_exec(&c)) vkb_call_end(&c);
|
||||
vkb_queue *q = dev->queues;
|
||||
while (q) {
|
||||
vkb_queue *n = q->next;
|
||||
free(q);
|
||||
q = n;
|
||||
}
|
||||
/* Mapped memory the app never freed. */
|
||||
for (uint32_t i = 0; i < dev->memories.cap; i++) {
|
||||
if (dev->memories.keys[i] && dev->memories.keys[i] != ~(uint64_t)0) {
|
||||
vkb_memory *m = dev->memories.vals[i];
|
||||
extern void vkb_memory_unmap_free(vkb_memory * m);
|
||||
vkb_memory_unmap_free(m);
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < dev->pools.cap; i++) {
|
||||
if (dev->pools.keys[i] && dev->pools.keys[i] != ~(uint64_t)0) {
|
||||
extern void vkb_cmdpool_free(vkb_cmdpool * p);
|
||||
vkb_cmdpool_free(dev->pools.vals[i]);
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < dev->templates.cap; i++)
|
||||
if (dev->templates.keys[i] && dev->templates.keys[i] != ~(uint64_t)0) free(dev->templates.vals[i]);
|
||||
vkb_map_destroy(&dev->memories);
|
||||
vkb_map_destroy(&dev->templates);
|
||||
vkb_map_destroy(&dev->pools);
|
||||
vkb_map_destroy(&dev->unshared);
|
||||
vkb_map_destroy(&dev->shared_images);
|
||||
for (uint32_t i = 0; i < dev->enabled_ext_count; i++) free((void *)dev->enabled_exts[i]);
|
||||
free(dev->enabled_exts);
|
||||
pthread_mutex_destroy(&dev->lock);
|
||||
free(dev);
|
||||
}
|
||||
|
||||
static void queue_set_info(VkQueue q, uint32_t family, uint32_t index)
|
||||
{
|
||||
if (!q) return;
|
||||
vkb_queue *vq = (vkb_queue *)q;
|
||||
vq->family = family;
|
||||
vq->index = index;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetDeviceQueue(VkDevice device, uint32_t queueFamilyIndex, uint32_t queueIndex, VkQueue *pQueue)
|
||||
{
|
||||
vkb_wire_vkGetDeviceQueue(device, queueFamilyIndex, queueIndex, pQueue);
|
||||
queue_set_info(*pQueue, queueFamilyIndex, queueIndex);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetDeviceQueue2(VkDevice device, const VkDeviceQueueInfo2 *pQueueInfo, VkQueue *pQueue)
|
||||
{
|
||||
vkb_wire_vkGetDeviceQueue2(device, pQueueInfo, pQueue);
|
||||
queue_set_info(*pQueue, pQueueInfo->queueFamilyIndex, pQueueInfo->queueIndex);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ queues and sync */
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkQueueSubmit(VkQueue queue, uint32_t submitCount, const VkSubmitInfo *pSubmits, VkFence fence)
|
||||
{
|
||||
return vkb_wire_vkQueueSubmit(queue, submitCount, pSubmits, fence);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkQueueSubmit2(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2 *pSubmits, VkFence fence)
|
||||
{
|
||||
return vkb_wire_vkQueueSubmit2(queue, submitCount, pSubmits, fence);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkQueueWaitIdle(VkQueue queue)
|
||||
{
|
||||
return vkb_wire_vkQueueWaitIdle(queue);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkDeviceWaitIdle(VkDevice device)
|
||||
{
|
||||
return vkb_wire_vkDeviceWaitIdle(device);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkWaitForFences(VkDevice device, uint32_t fenceCount, const VkFence *pFences, VkBool32 waitAll,
|
||||
uint64_t timeout)
|
||||
{
|
||||
return vkb_wire_vkWaitForFences(device, fenceCount, pFences, waitAll, timeout);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetFenceStatus(VkDevice device, VkFence fence)
|
||||
{
|
||||
return vkb_wire_vkGetFenceStatus(device, fence);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkWaitSemaphores(VkDevice device, const VkSemaphoreWaitInfo *pWaitInfo, uint64_t timeout)
|
||||
{
|
||||
return vkb_wire_vkWaitSemaphores(device, pWaitInfo, timeout);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetSemaphoreCounterValue(VkDevice device, VkSemaphore semaphore, uint64_t *pValue)
|
||||
{
|
||||
return vkb_wire_vkGetSemaphoreCounterValue(device, semaphore, pValue);
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Device memory on the client side: the memory-type translation of physdev.c applied to every
|
||||
* allocation and requirement, and mapping of shared allocations (server_memory.c explains how
|
||||
* they are shared).
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "vkb_client.h"
|
||||
#include "gen/vkb_client_gen.h"
|
||||
#include "emu.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void vkb_memory_init_device(vkb_device *d)
|
||||
{
|
||||
(void)d;
|
||||
}
|
||||
|
||||
void vkb_memory_unmap_free(vkb_memory *m)
|
||||
{
|
||||
if (m->ptr) munmap(m->ptr, (size_t)m->size);
|
||||
free(m);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ resources */
|
||||
|
||||
/* Shallow copy of a pNext chain (known structures only), in one malloc'd block (*mem). */
|
||||
void *vkb_chain_clone(const void *pNext, void **mem)
|
||||
{
|
||||
size_t total = 0;
|
||||
for (const VkBaseInStructure *b = pNext; b; b = b->pNext) total += (vkb_struct_size(b->sType) + 15) & ~(size_t)15;
|
||||
uint8_t *block = calloc(1, total ? total : 16);
|
||||
*mem = block;
|
||||
VkBaseOutStructure *head = NULL, *prev = NULL;
|
||||
size_t off = 0;
|
||||
for (const VkBaseInStructure *b = pNext; b; b = b->pNext) {
|
||||
size_t sz = vkb_struct_size(b->sType);
|
||||
if (!sz) continue;
|
||||
VkBaseOutStructure *n = (VkBaseOutStructure *)(block + off);
|
||||
memcpy(n, b, sz);
|
||||
n->pNext = NULL;
|
||||
off += (sz + 15) & ~(size_t)15;
|
||||
if (prev) prev->pNext = n;
|
||||
else head = n;
|
||||
prev = n;
|
||||
}
|
||||
return head;
|
||||
}
|
||||
|
||||
static int can_share(vkb_device *dev)
|
||||
{
|
||||
return dev->pd->info.strategy != VKB_MEM_NONE && dev->pd->info.buffer_handle_types;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateBuffer(VkDevice device, const VkBufferCreateInfo *pCreateInfo,
|
||||
const VkAllocationCallbacks *pAllocator, VkBuffer *pBuffer)
|
||||
{
|
||||
vkb_device *dev = vkb_dev(device);
|
||||
if (!can_share(dev) || (pCreateInfo->flags & VK_BUFFER_CREATE_SPARSE_BINDING_BIT)) {
|
||||
VkResult r = vkb_wire_vkCreateBuffer(device, pCreateInfo, pAllocator, pBuffer);
|
||||
if (r == VK_SUCCESS) vkb_map_put(&dev->unshared, (uint64_t)*pBuffer, (void *)1);
|
||||
return r;
|
||||
}
|
||||
/* Every buffer may be bound to shared memory: say so at creation, as the spec requires. */
|
||||
VkBufferCreateInfo ci = *pCreateInfo;
|
||||
VkExternalMemoryBufferCreateInfo ext = {VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO, ci.pNext,
|
||||
dev->pd->info.buffer_handle_types};
|
||||
int had = 0;
|
||||
for (VkBaseOutStructure *b = (VkBaseOutStructure *)ci.pNext; b; b = b->pNext) {
|
||||
if (b->sType == VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO) had = 1;
|
||||
}
|
||||
VkResult r;
|
||||
if (had) {
|
||||
/* The app has its own external handle types: ours are added to a copy of its chain. */
|
||||
void *mem = NULL;
|
||||
ci.pNext = vkb_chain_clone(ci.pNext, &mem);
|
||||
for (VkBaseOutStructure *b = (VkBaseOutStructure *)ci.pNext; b; b = b->pNext)
|
||||
if (b->sType == VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO)
|
||||
((VkExternalMemoryBufferCreateInfo *)b)->handleTypes |= dev->pd->info.buffer_handle_types;
|
||||
r = vkb_wire_vkCreateBuffer(device, &ci, pAllocator, pBuffer);
|
||||
free(mem);
|
||||
if (r != VK_SUCCESS && r != VK_ERROR_OUT_OF_HOST_MEMORY && r != VK_ERROR_OUT_OF_DEVICE_MEMORY) {
|
||||
r = vkb_wire_vkCreateBuffer(device, pCreateInfo, pAllocator, pBuffer);
|
||||
if (r == VK_SUCCESS) vkb_map_put(&dev->unshared, (uint64_t)*pBuffer, (void *)1);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
ci.pNext = &ext;
|
||||
r = vkb_wire_vkCreateBuffer(device, &ci, pAllocator, pBuffer);
|
||||
if (r != VK_SUCCESS && r != VK_ERROR_OUT_OF_HOST_MEMORY && r != VK_ERROR_OUT_OF_DEVICE_MEMORY) {
|
||||
VKB_ONCE(VKB_LOG_WARN, "a buffer cannot be created shareable (%d); it gets device-only memory (logged once)", r);
|
||||
r = vkb_wire_vkCreateBuffer(device, pCreateInfo, pAllocator, pBuffer);
|
||||
if (r == VK_SUCCESS) vkb_map_put(&dev->unshared, (uint64_t)*pBuffer, (void *)1);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroyBuffer(VkDevice device, VkBuffer buffer, const VkAllocationCallbacks *pAllocator)
|
||||
{
|
||||
if (!buffer) return;
|
||||
vkb_map_remove(&vkb_dev(device)->unshared, (uint64_t)buffer);
|
||||
vkb_wire_vkDestroyBuffer(device, buffer, pAllocator);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateImage(VkDevice device, const VkImageCreateInfo *pCreateInfo,
|
||||
const VkAllocationCallbacks *pAllocator, VkImage *pImage)
|
||||
{
|
||||
vkb_device *dev = vkb_dev(device);
|
||||
VkImageCreateInfo ci = *pCreateInfo;
|
||||
vkb_emu_image_create_info(dev, &ci);
|
||||
/* Linear images may be mapped by the app: those get the shared handle type when allowed. */
|
||||
if (can_share(dev) && ci.tiling == VK_IMAGE_TILING_LINEAR && !(ci.flags & VK_IMAGE_CREATE_SPARSE_BINDING_BIT)) {
|
||||
int had = 0;
|
||||
for (VkBaseOutStructure *b = (VkBaseOutStructure *)ci.pNext; b; b = b->pNext)
|
||||
had |= b->sType == VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO;
|
||||
if (!had) {
|
||||
VkExternalMemoryImageCreateInfo ext = {VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, ci.pNext,
|
||||
dev->pd->info.buffer_handle_types};
|
||||
VkImageCreateInfo c2 = ci;
|
||||
c2.pNext = &ext;
|
||||
VkResult r = vkb_wire_vkCreateImage(device, &c2, pAllocator, pImage);
|
||||
if (r == VK_SUCCESS) {
|
||||
vkb_map_put(&dev->shared_images, (uint64_t)*pImage, (void *)1);
|
||||
vkb_emu_image_created(dev, pCreateInfo, *pImage);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
VkResult r = vkb_wire_vkCreateImage(device, &ci, pAllocator, pImage);
|
||||
if (r == VK_SUCCESS) vkb_emu_image_created(dev, pCreateInfo, *pImage);
|
||||
return r;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroyImage(VkDevice device, VkImage image, const VkAllocationCallbacks *pAllocator)
|
||||
{
|
||||
if (!image) return;
|
||||
vkb_device *dev = vkb_dev(device);
|
||||
vkb_map_remove(&dev->shared_images, (uint64_t)image);
|
||||
vkb_emu_image_destroyed(dev, image);
|
||||
vkb_wire_vkDestroyImage(device, image, pAllocator);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ requirements */
|
||||
|
||||
static void fix_reqs(vkb_device *dev, VkMemoryRequirements *r, int allow_shared)
|
||||
{
|
||||
r->memoryTypeBits = vkb_mem_bits_to_client(dev->pd, r->memoryTypeBits, allow_shared);
|
||||
}
|
||||
|
||||
static int buffer_shareable(vkb_device *dev, VkBuffer b)
|
||||
{
|
||||
return can_share(dev) && !vkb_map_get(&dev->unshared, (uint64_t)b);
|
||||
}
|
||||
|
||||
static int image_shareable(vkb_device *dev, VkImage i)
|
||||
{
|
||||
return vkb_map_get(&dev->shared_images, (uint64_t)i) != NULL;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetBufferMemoryRequirements(VkDevice device, VkBuffer buffer, VkMemoryRequirements *pReq)
|
||||
{
|
||||
vkb_wire_vkGetBufferMemoryRequirements(device, buffer, pReq);
|
||||
fix_reqs(vkb_dev(device), pReq, buffer_shareable(vkb_dev(device), buffer));
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetBufferMemoryRequirements2(VkDevice device, const VkBufferMemoryRequirementsInfo2 *pInfo,
|
||||
VkMemoryRequirements2 *pReq)
|
||||
{
|
||||
vkb_wire_vkGetBufferMemoryRequirements2(device, pInfo, pReq);
|
||||
fix_reqs(vkb_dev(device), &pReq->memoryRequirements, buffer_shareable(vkb_dev(device), pInfo->buffer));
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetImageMemoryRequirements(VkDevice device, VkImage image, VkMemoryRequirements *pReq)
|
||||
{
|
||||
vkb_wire_vkGetImageMemoryRequirements(device, image, pReq);
|
||||
fix_reqs(vkb_dev(device), pReq, image_shareable(vkb_dev(device), image));
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetImageMemoryRequirements2(VkDevice device, const VkImageMemoryRequirementsInfo2 *pInfo,
|
||||
VkMemoryRequirements2 *pReq)
|
||||
{
|
||||
vkb_wire_vkGetImageMemoryRequirements2(device, pInfo, pReq);
|
||||
fix_reqs(vkb_dev(device), &pReq->memoryRequirements, image_shareable(vkb_dev(device), pInfo->image));
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetDeviceBufferMemoryRequirements(VkDevice device, const VkDeviceBufferMemoryRequirements *pInfo,
|
||||
VkMemoryRequirements2 *pReq)
|
||||
{
|
||||
vkb_device *dev = vkb_dev(device);
|
||||
if (!can_share(dev)) {
|
||||
vkb_wire_vkGetDeviceBufferMemoryRequirements(device, pInfo, pReq);
|
||||
fix_reqs(dev, &pReq->memoryRequirements, 0);
|
||||
return;
|
||||
}
|
||||
VkBufferCreateInfo ci = *pInfo->pCreateInfo;
|
||||
VkExternalMemoryBufferCreateInfo ext = {VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO, ci.pNext,
|
||||
dev->pd->info.buffer_handle_types};
|
||||
int had = 0;
|
||||
for (VkBaseOutStructure *b = (VkBaseOutStructure *)ci.pNext; b; b = b->pNext)
|
||||
had |= b->sType == VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO;
|
||||
if (!had) ci.pNext = &ext;
|
||||
VkDeviceBufferMemoryRequirements info = *pInfo;
|
||||
info.pCreateInfo = &ci;
|
||||
vkb_wire_vkGetDeviceBufferMemoryRequirements(device, &info, pReq);
|
||||
fix_reqs(dev, &pReq->memoryRequirements, 1);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetDeviceImageMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements *pInfo,
|
||||
VkMemoryRequirements2 *pReq)
|
||||
{
|
||||
vkb_device *dev = vkb_dev(device);
|
||||
VkImageCreateInfo ci = *pInfo->pCreateInfo;
|
||||
vkb_emu_image_create_info(dev, &ci);
|
||||
VkDeviceImageMemoryRequirements info = *pInfo;
|
||||
info.pCreateInfo = &ci;
|
||||
vkb_wire_vkGetDeviceImageMemoryRequirements(device, &info, pReq);
|
||||
fix_reqs(dev, &pReq->memoryRequirements, 0);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetImageSparseMemoryRequirements(VkDevice device, VkImage image, uint32_t *pCount,
|
||||
VkSparseImageMemoryRequirements *pReq)
|
||||
{
|
||||
vkb_wire_vkGetImageSparseMemoryRequirements(device, image, pCount, pReq);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetImageSparseMemoryRequirements2(VkDevice device, const VkImageSparseMemoryRequirementsInfo2 *pInfo,
|
||||
uint32_t *pCount, VkSparseImageMemoryRequirements2 *pReq)
|
||||
{
|
||||
vkb_wire_vkGetImageSparseMemoryRequirements2(device, pInfo, pCount, pReq);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetDeviceImageSparseMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements *pInfo,
|
||||
uint32_t *pCount, VkSparseImageMemoryRequirements2 *pReq)
|
||||
{
|
||||
vkb_wire_vkGetDeviceImageSparseMemoryRequirements(device, pInfo, pCount, pReq);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetMemoryFdPropertiesKHR(VkDevice device, VkExternalMemoryHandleTypeFlagBits handleType, int fd,
|
||||
VkMemoryFdPropertiesKHR *pProps)
|
||||
{
|
||||
VkResult r = vkb_wire_vkGetMemoryFdPropertiesKHR(device, handleType, fd, pProps);
|
||||
if (r == VK_SUCCESS) pProps->memoryTypeBits = vkb_mem_bits_to_client(vkb_dev(device)->pd, pProps->memoryTypeBits, 0);
|
||||
return r;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize offset)
|
||||
{
|
||||
return vkb_wire_vkBindBufferMemory(device, buffer, memory, offset);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkBindBufferMemory2(VkDevice device, uint32_t n, const VkBindBufferMemoryInfo *p)
|
||||
{
|
||||
return vkb_wire_vkBindBufferMemory2(device, n, p);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkBindImageMemory(VkDevice device, VkImage image, VkDeviceMemory memory, VkDeviceSize offset)
|
||||
{
|
||||
return vkb_wire_vkBindImageMemory(device, image, memory, offset);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkBindImageMemory2(VkDevice device, uint32_t n, const VkBindImageMemoryInfo *p)
|
||||
{
|
||||
return vkb_wire_vkBindImageMemory2(device, n, p);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetImageSubresourceLayout(VkDevice device, VkImage image, const VkImageSubresource *pSubresource,
|
||||
VkSubresourceLayout *pLayout)
|
||||
{
|
||||
vkb_wire_vkGetImageSubresourceLayout(device, image, pSubresource, pLayout);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ allocation and mapping */
|
||||
|
||||
static void close_import_fds(const void *pNext)
|
||||
{
|
||||
for (const VkBaseInStructure *b = pNext; b; b = b->pNext) {
|
||||
if (b->sType == VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR) {
|
||||
int fd = ((const VkImportMemoryFdInfoKHR *)b)->fd;
|
||||
if (fd >= 0) close(fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkAllocateMemory(VkDevice device, const VkMemoryAllocateInfo *pAllocateInfo,
|
||||
const VkAllocationCallbacks *pAllocator, VkDeviceMemory *pMemory)
|
||||
{
|
||||
(void)pAllocator;
|
||||
vkb_device *dev = vkb_dev(device);
|
||||
vkb_physdev *pd = dev->pd;
|
||||
uint32_t ct = pAllocateInfo->memoryTypeIndex;
|
||||
if (ct >= pd->client_mem.memoryTypeCount) return VK_ERROR_OUT_OF_DEVICE_MEMORY;
|
||||
VkMemoryAllocateInfo ai = *pAllocateInfo;
|
||||
ai.memoryTypeIndex = pd->type_map[ct].server_type;
|
||||
int shared = pd->type_map[ct].shared;
|
||||
/* An app-imported dma-buf/opaque fd keeps its own memory; it is not one of our shared pages. */
|
||||
for (const VkBaseInStructure *b = ai.pNext; b; b = b->pNext)
|
||||
if (b->sType == VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR) shared = 0;
|
||||
uint32_t strategy = pd->info.strategy;
|
||||
int memfd = -1;
|
||||
void *ptr = NULL;
|
||||
if (shared && strategy == VKB_MEM_HOSTPTR) {
|
||||
VkDeviceSize a = pd->info.import_alignment ? pd->info.import_alignment : 4096;
|
||||
ai.allocationSize = (ai.allocationSize + a - 1) & ~(a - 1);
|
||||
memfd = vkb_memfd("vkbridge-memory", (size_t)ai.allocationSize);
|
||||
if (memfd < 0) return VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
ptr = mmap(NULL, (size_t)ai.allocationSize, PROT_READ | PROT_WRITE, MAP_SHARED, memfd, 0);
|
||||
if (ptr == MAP_FAILED) {
|
||||
close(memfd);
|
||||
return VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
}
|
||||
}
|
||||
vkb_call c;
|
||||
vkb_call_begin(&c, VKB_CMD_vkAllocateMemory, dev->obj.table);
|
||||
vkb_enc_u64(&c.e, dev->obj.remote);
|
||||
vkb_enc_u8(&c.e, 1);
|
||||
vkb_enc_VkMemoryAllocateInfo(&c.e, &ai);
|
||||
vkb_enc_u32(&c.e, shared ? VKB_ALLOC_SHARED : VKB_ALLOC_PLAIN);
|
||||
vkb_enc_fd(&c.e, memfd);
|
||||
int ok = vkb_call_exec(&c);
|
||||
if (memfd >= 0) close(memfd);
|
||||
if (!ok) {
|
||||
if (ptr) munmap(ptr, (size_t)ai.allocationSize);
|
||||
return VK_ERROR_DEVICE_LOST;
|
||||
}
|
||||
VkResult r = (VkResult)vkb_dec_u32(&c.d);
|
||||
VkDeviceMemory mem = (VkDeviceMemory)vkb_dec_u64(&c.d);
|
||||
int fd = vkb_dec_fd(&c.d);
|
||||
uint64_t size = vkb_dec_u64(&c.d);
|
||||
vkb_call_end(&c);
|
||||
if (r != VK_SUCCESS) {
|
||||
if (ptr) munmap(ptr, (size_t)ai.allocationSize);
|
||||
if (fd >= 0) close(fd);
|
||||
return r;
|
||||
}
|
||||
/* The import succeeded: the fd the app handed over now belongs to the implementation, and the
|
||||
* server holds its own copy. */
|
||||
close_import_fds(pAllocateInfo->pNext);
|
||||
vkb_memory *m = calloc(1, sizeof(*m));
|
||||
m->client_type = ct;
|
||||
m->shared = shared;
|
||||
m->size = shared && strategy == VKB_MEM_HOSTPTR ? ai.allocationSize : size;
|
||||
if (shared && strategy != VKB_MEM_HOSTPTR) {
|
||||
if (fd < 0) {
|
||||
VKB_ERR("shared allocation came back without its dma-buf");
|
||||
} else {
|
||||
ptr = mmap(NULL, (size_t)m->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (ptr == MAP_FAILED) {
|
||||
VKB_ERR("cannot map shared memory (%llu bytes): %s", (unsigned long long)m->size, strerror(errno));
|
||||
ptr = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fd >= 0) close(fd);
|
||||
m->ptr = ptr;
|
||||
vkb_map_put(&dev->memories, (uint64_t)mem, m);
|
||||
*pMemory = mem;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkFreeMemory(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks *pAllocator)
|
||||
{
|
||||
(void)pAllocator;
|
||||
if (!memory) return;
|
||||
vkb_device *dev = vkb_dev(device);
|
||||
vkb_memory *m = vkb_map_remove(&dev->memories, (uint64_t)memory);
|
||||
vkb_call c;
|
||||
vkb_call_begin(&c, VKB_CMD_vkFreeMemory, dev->obj.table);
|
||||
vkb_enc_u64(&c.e, dev->obj.remote);
|
||||
vkb_enc_u64(&c.e, (uint64_t)memory);
|
||||
vkb_call_exec_async(&c);
|
||||
if (m) vkb_memory_unmap_free(m);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkMapMemory(VkDevice device, VkDeviceMemory memory, VkDeviceSize offset, VkDeviceSize size,
|
||||
VkMemoryMapFlags flags, void **ppData)
|
||||
{
|
||||
(void)size;
|
||||
(void)flags;
|
||||
vkb_memory *m = vkb_map_get(&vkb_dev(device)->memories, (uint64_t)memory);
|
||||
if (!m || !m->ptr) {
|
||||
VKB_ONCE(VKB_LOG_WARN, "vkMapMemory on memory that is not host-visible through the bridge (logged once)");
|
||||
*ppData = NULL;
|
||||
return VK_ERROR_MEMORY_MAP_FAILED;
|
||||
}
|
||||
*ppData = (uint8_t *)m->ptr + offset;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkUnmapMemory(VkDevice device, VkDeviceMemory memory)
|
||||
{
|
||||
/* The mapping stays until vkFreeMemory: mapping again is free, and nothing else reuses it. */
|
||||
(void)device;
|
||||
(void)memory;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkMapMemory2(VkDevice device, const VkMemoryMapInfo *pInfo, void **ppData)
|
||||
{
|
||||
return vkb_ep_vkMapMemory(device, pInfo->memory, pInfo->offset, pInfo->size, pInfo->flags, ppData);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkUnmapMemory2(VkDevice device, const VkMemoryUnmapInfo *pInfo)
|
||||
{
|
||||
(void)device;
|
||||
(void)pInfo;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
static VkResult ranges_call(VkDevice device, uint32_t cmd, uint32_t n, const VkMappedMemoryRange *r)
|
||||
{
|
||||
vkb_device *dev = vkb_dev(device);
|
||||
vkb_call c;
|
||||
vkb_call_begin(&c, cmd, dev->obj.table);
|
||||
vkb_enc_u64(&c.e, dev->obj.remote);
|
||||
vkb_enc_u32(&c.e, n);
|
||||
for (uint32_t i = 0; i < n; i++) vkb_enc_VkMappedMemoryRange(&c.e, &r[i]);
|
||||
if (!vkb_call_exec(&c)) return VK_ERROR_DEVICE_LOST;
|
||||
VkResult res = (VkResult)vkb_dec_u32(&c.d);
|
||||
vkb_call_end(&c);
|
||||
return res;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkFlushMappedMemoryRanges(VkDevice device, uint32_t n, const VkMappedMemoryRange *r)
|
||||
{
|
||||
return ranges_call(device, VKB_CMD_vkFlushMappedMemoryRanges, n, r);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkInvalidateMappedMemoryRanges(VkDevice device, uint32_t n, const VkMappedMemoryRange *r)
|
||||
{
|
||||
return ranges_call(device, VKB_CMD_vkInvalidateMappedMemoryRanges, n, r);
|
||||
}
|
||||
@@ -0,0 +1,435 @@
|
||||
/* 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);
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* DroidDeck Vulkan bridge - client ICD (libvulkan_droidbridge.so, glibc).
|
||||
*
|
||||
* A Vulkan driver for the Vulkan loader inside the Linux session. It owns no GPU: every call is
|
||||
* encoded and executed by the server (app/src/main/cpp/vkbridge) on the device's real driver.
|
||||
* Command buffers are recorded locally and shipped at vkEndCommandBuffer; mapped memory is
|
||||
* shared pages (see server_memory.c); presentation (WSI) is implemented here.
|
||||
*/
|
||||
#ifndef VKB_CLIENT_H
|
||||
#define VKB_CLIENT_H
|
||||
|
||||
#define VK_USE_PLATFORM_WAYLAND_KHR 1
|
||||
#define VK_USE_PLATFORM_XCB_KHR 1
|
||||
#define VK_USE_PLATFORM_XLIB_KHR 1
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "vkb_wire.h"
|
||||
#include "vkb_proto.h"
|
||||
#include "vkb_log.h"
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <vulkan/vk_icd.h>
|
||||
|
||||
#define VKB_EXPORT __attribute__((visibility("default")))
|
||||
#define VKB_API_VERSION VK_MAKE_API_VERSION(0, 1, 4, VK_HEADER_VERSION)
|
||||
|
||||
/* ------------------------------------------------------------------ small hash map (u64 -> ptr) */
|
||||
typedef struct vkb_map {
|
||||
uint64_t *keys;
|
||||
void **vals;
|
||||
uint32_t cap, count;
|
||||
pthread_mutex_t lock;
|
||||
} vkb_map;
|
||||
void vkb_map_init(vkb_map *m);
|
||||
void vkb_map_destroy(vkb_map *m);
|
||||
void vkb_map_put(vkb_map *m, uint64_t k, void *v);
|
||||
void *vkb_map_get(vkb_map *m, uint64_t k);
|
||||
void *vkb_map_remove(vkb_map *m, uint64_t k);
|
||||
|
||||
/* ------------------------------------------------------------------ dispatchable objects */
|
||||
typedef struct vkb_object {
|
||||
VK_LOADER_DATA loader; /* must be first: the loader keeps its dispatch pointer here */
|
||||
VkObjectType type;
|
||||
uint64_t remote; /* the server's handle */
|
||||
uint32_t table; /* the server's dispatch table for calls on this object */
|
||||
} vkb_object;
|
||||
|
||||
typedef struct vkb_mem_type_map {
|
||||
uint32_t server_type;
|
||||
uint8_t shared;
|
||||
} vkb_mem_type_map;
|
||||
|
||||
struct vkb_instance;
|
||||
|
||||
typedef struct vkb_physdev {
|
||||
vkb_object obj;
|
||||
struct vkb_instance *instance;
|
||||
vkb_server_pd_info info;
|
||||
VkPhysicalDeviceProperties props;
|
||||
VkPhysicalDeviceMemoryProperties server_mem;
|
||||
VkPhysicalDeviceMemoryProperties client_mem; /* what the app sees */
|
||||
vkb_mem_type_map type_map[VK_MAX_MEMORY_TYPES];
|
||||
uint32_t heap_map[VK_MAX_MEMORY_HEAPS]; /* client heap -> server heap */
|
||||
VkExtensionProperties *exts; /* filtered device extensions */
|
||||
uint32_t next;
|
||||
int ready;
|
||||
pthread_mutex_t lock;
|
||||
struct vkb_physdev *list_next;
|
||||
} vkb_physdev;
|
||||
|
||||
typedef struct vkb_instance {
|
||||
vkb_object obj;
|
||||
uint32_t api_version;
|
||||
vkb_physdev *pds;
|
||||
pthread_mutex_t lock;
|
||||
uint8_t ext_surface, ext_wayland, ext_xcb, ext_xlib, ext_headless, ext_surface_caps2, ext_debug_utils;
|
||||
} vkb_instance;
|
||||
|
||||
struct vkb_queue;
|
||||
|
||||
typedef struct vkb_device {
|
||||
vkb_object obj;
|
||||
vkb_physdev *pd;
|
||||
vkb_instance *instance;
|
||||
struct vkb_queue *queues;
|
||||
pthread_mutex_t lock;
|
||||
vkb_map memories; /* VkDeviceMemory -> vkb_memory* */
|
||||
vkb_map templates; /* VkDescriptorUpdateTemplate -> vkb_template* */
|
||||
vkb_map pools; /* VkCommandPool -> vkb_cmdpool* */
|
||||
vkb_map unshared; /* VkBuffer/VkImage that cannot live in shared memory -> (void*)1 */
|
||||
vkb_map shared_images; /* VkImage that may live in shared memory -> (void*)1 */
|
||||
const char **enabled_exts;
|
||||
uint32_t enabled_ext_count;
|
||||
uint8_t ext_swapchain;
|
||||
uint32_t api_version;
|
||||
} vkb_device;
|
||||
|
||||
typedef struct vkb_queue {
|
||||
vkb_object obj;
|
||||
vkb_device *device;
|
||||
uint32_t family, index, flags;
|
||||
struct vkb_queue *next;
|
||||
} vkb_queue;
|
||||
|
||||
typedef struct vkb_cmdbuf {
|
||||
vkb_object obj;
|
||||
vkb_device *device;
|
||||
VkCommandPool pool;
|
||||
VkCommandBufferLevel level;
|
||||
vkb_enc stream;
|
||||
size_t entry; /* offset of the open entry's payload */
|
||||
int recording;
|
||||
int broken;
|
||||
struct vkb_cmdbuf *pool_next, *pool_prev;
|
||||
} vkb_cmdbuf;
|
||||
|
||||
typedef struct vkb_cmdpool {
|
||||
vkb_cmdbuf *head;
|
||||
} vkb_cmdpool;
|
||||
|
||||
typedef struct vkb_memory {
|
||||
void *ptr; /* client mapping (shared allocations) */
|
||||
VkDeviceSize size;
|
||||
uint32_t client_type;
|
||||
int shared;
|
||||
} vkb_memory;
|
||||
|
||||
static inline vkb_device *vkb_dev(VkDevice d) { return (vkb_device *)d; }
|
||||
static inline vkb_physdev *vkb_pd(VkPhysicalDevice p) { return (vkb_physdev *)p; }
|
||||
static inline vkb_instance *vkb_inst(VkInstance i) { return (vkb_instance *)i; }
|
||||
static inline vkb_cmdbuf *vkb_cb(VkCommandBuffer c) { return (vkb_cmdbuf *)c; }
|
||||
|
||||
/* ------------------------------------------------------------------ calls */
|
||||
typedef struct vkb_call {
|
||||
vkb_enc e;
|
||||
vkb_dec d;
|
||||
uint32_t cmd, table, flags;
|
||||
struct vkb_conn *conn;
|
||||
} vkb_call;
|
||||
|
||||
void vkb_call_begin(vkb_call *c, uint32_t cmd, uint32_t table);
|
||||
/* Sends the request and waits for the reply. 0 = failed (the call is already released). */
|
||||
int vkb_call_exec(vkb_call *c);
|
||||
void vkb_call_end(vkb_call *c);
|
||||
/* Sends without waiting for a reply (void commands without outputs); ordered by vkb_msg_hdr.seq. */
|
||||
int vkb_call_exec_async(vkb_call *c);
|
||||
int vkb_async_results(void);
|
||||
void vkb_stats_count(uint32_t cmd, int async, size_t bytes);
|
||||
uint32_t vkb_table_of(const void *dispatchable);
|
||||
int vkb_connected(void);
|
||||
/* Per-thread transport cleanup for fork children. */
|
||||
void vkb_conn_after_fork(void);
|
||||
|
||||
/* Command streams (cmdbuf.c). */
|
||||
vkb_enc *vkb_cmd_record_begin(VkCommandBuffer cb, uint32_t cmd);
|
||||
void vkb_cmd_record_end(VkCommandBuffer cb);
|
||||
|
||||
/* Dispatchable objects created by the server (generated stubs call this). */
|
||||
void *vkb_wrap(VkObjectType type, const void *parent, uint64_t remote);
|
||||
|
||||
/* physdev.c */
|
||||
int vkb_physdev_ready(vkb_physdev *pd);
|
||||
uint32_t vkb_mem_bits_to_client(vkb_physdev *pd, uint32_t server_bits, int allow_shared);
|
||||
|
||||
/* memory.c */
|
||||
void vkb_memory_init_device(vkb_device *d);
|
||||
|
||||
/* icd.c */
|
||||
PFN_vkVoidFunction vkb_lookup_proc(const char *name, int level_max, vkb_device *dev, vkb_instance *inst);
|
||||
int vkb_device_ext_enabled(const vkb_device *d, const char *name);
|
||||
|
||||
/* memory.c */
|
||||
void *vkb_chain_clone(const void *pNext, void **mem);
|
||||
int vkb_is_client_device_ext(const char *n);
|
||||
|
||||
/* misc */
|
||||
int vkb_memfd(const char *name, size_t size);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,801 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Surfaces and swapchains (backend-independent part).
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "wsi.h"
|
||||
#include "gen/vkb_client_gen.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
const VkSurfaceFormatKHR vkb_wsi_formats[] = {
|
||||
{VK_FORMAT_B8G8R8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR},
|
||||
{VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR},
|
||||
{VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR},
|
||||
{VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR},
|
||||
};
|
||||
const uint32_t vkb_wsi_format_count = sizeof(vkb_wsi_formats) / sizeof(vkb_wsi_formats[0]);
|
||||
|
||||
#define FOURCC(a, b, c, d) ((uint32_t)(a) | ((uint32_t)(b) << 8) | ((uint32_t)(c) << 16) | ((uint32_t)(d) << 24))
|
||||
|
||||
uint32_t vkb_drm_format(VkFormat f)
|
||||
{
|
||||
switch (f) {
|
||||
case VK_FORMAT_B8G8R8A8_SRGB:
|
||||
case VK_FORMAT_B8G8R8A8_UNORM: return FOURCC('A', 'R', '2', '4');
|
||||
case VK_FORMAT_R8G8B8A8_SRGB:
|
||||
case VK_FORMAT_R8G8B8A8_UNORM: return FOURCC('A', 'B', '2', '4');
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static vkb_surface *surf(VkSurfaceKHR s) { return (vkb_surface *)(uintptr_t)s; }
|
||||
static vkb_swapchain *chain(VkSwapchainKHR s) { return (vkb_swapchain *)(uintptr_t)s; }
|
||||
|
||||
/* ------------------------------------------------------------------ surfaces */
|
||||
|
||||
static VkResult new_surface(enum vkb_surface_kind kind, VkSurfaceKHR *out, vkb_surface **ps)
|
||||
{
|
||||
vkb_surface *s = calloc(1, sizeof(*s));
|
||||
if (!s) return VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
s->kind = kind;
|
||||
*ps = s;
|
||||
*out = (VkSurfaceKHR)(uintptr_t)s;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateWaylandSurfaceKHR(VkInstance instance, const VkWaylandSurfaceCreateInfoKHR *pCreateInfo,
|
||||
const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface)
|
||||
{
|
||||
(void)instance;
|
||||
(void)pAllocator;
|
||||
if (!vkb_wl_load()) return VK_ERROR_INITIALIZATION_FAILED;
|
||||
vkb_surface *s;
|
||||
VkResult r = new_surface(VKB_SURF_WAYLAND, pSurface, &s);
|
||||
if (r == VK_SUCCESS) {
|
||||
s->wl_display = pCreateInfo->display;
|
||||
s->wl_surface = pCreateInfo->surface;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateXcbSurfaceKHR(VkInstance instance, const VkXcbSurfaceCreateInfoKHR *pCreateInfo,
|
||||
const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface)
|
||||
{
|
||||
(void)instance;
|
||||
(void)pAllocator;
|
||||
if (!vkb_xcb_load()) return VK_ERROR_INITIALIZATION_FAILED;
|
||||
vkb_surface *s;
|
||||
VkResult r = new_surface(VKB_SURF_XCB, pSurface, &s);
|
||||
if (r == VK_SUCCESS) {
|
||||
s->xcb_conn = pCreateInfo->connection;
|
||||
s->xcb_window = pCreateInfo->window;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateXlibSurfaceKHR(VkInstance instance, const VkXlibSurfaceCreateInfoKHR *pCreateInfo,
|
||||
const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface)
|
||||
{
|
||||
(void)instance;
|
||||
(void)pAllocator;
|
||||
if (!vkb_xcb_load()) return VK_ERROR_INITIALIZATION_FAILED;
|
||||
void *conn = vkb_xlib_to_xcb(pCreateInfo->dpy);
|
||||
if (!conn) return VK_ERROR_INITIALIZATION_FAILED;
|
||||
vkb_surface *s;
|
||||
VkResult r = new_surface(VKB_SURF_XCB, pSurface, &s);
|
||||
if (r == VK_SUCCESS) {
|
||||
s->xcb_conn = conn;
|
||||
s->xcb_window = (uint32_t)pCreateInfo->window;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateHeadlessSurfaceEXT(VkInstance instance, const VkHeadlessSurfaceCreateInfoEXT *pCreateInfo,
|
||||
const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface)
|
||||
{
|
||||
(void)instance;
|
||||
(void)pCreateInfo;
|
||||
(void)pAllocator;
|
||||
vkb_surface *s;
|
||||
return new_surface(VKB_SURF_HEADLESS, pSurface, &s);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroySurfaceKHR(VkInstance instance, VkSurfaceKHR surface, const VkAllocationCallbacks *pAllocator)
|
||||
{
|
||||
(void)instance;
|
||||
(void)pAllocator;
|
||||
free(surf(surface));
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkBool32 VKAPI_CALL vkb_ep_vkGetPhysicalDeviceWaylandPresentationSupportKHR(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex,
|
||||
struct wl_display *display)
|
||||
{
|
||||
(void)physicalDevice;
|
||||
(void)queueFamilyIndex;
|
||||
(void)display;
|
||||
return vkb_wl_load() ? VK_TRUE : VK_FALSE;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkBool32 VKAPI_CALL vkb_ep_vkGetPhysicalDeviceXcbPresentationSupportKHR(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex,
|
||||
xcb_connection_t *connection, xcb_visualid_t visual_id)
|
||||
{
|
||||
(void)physicalDevice;
|
||||
(void)queueFamilyIndex;
|
||||
(void)connection;
|
||||
(void)visual_id;
|
||||
return vkb_xcb_load() ? VK_TRUE : VK_FALSE;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkBool32 VKAPI_CALL vkb_ep_vkGetPhysicalDeviceXlibPresentationSupportKHR(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex,
|
||||
Display *dpy, VisualID visualID)
|
||||
{
|
||||
(void)physicalDevice;
|
||||
(void)queueFamilyIndex;
|
||||
(void)dpy;
|
||||
(void)visualID;
|
||||
return vkb_xcb_load() ? VK_TRUE : VK_FALSE;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetPhysicalDeviceSurfaceSupportKHR(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex,
|
||||
VkSurfaceKHR surface, VkBool32 *pSupported)
|
||||
{
|
||||
(void)surface;
|
||||
/* Presentation is a copy or a hand-over after the queue's work: any queue that can do
|
||||
* graphics or compute (or transfer) can present. */
|
||||
uint32_t n = 0;
|
||||
vkb_wire_vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &n, NULL);
|
||||
VkQueueFamilyProperties *qp = calloc(n ? n : 1, sizeof(*qp));
|
||||
vkb_wire_vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &n, qp);
|
||||
*pSupported = queueFamilyIndex < n &&
|
||||
(qp[queueFamilyIndex].queueFlags & (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT));
|
||||
free(qp);
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
static VkResult surface_extent(vkb_surface *s, VkExtent2D *e)
|
||||
{
|
||||
switch (s->kind) {
|
||||
case VKB_SURF_WAYLAND: return vkb_wl_surface_extent(s, e);
|
||||
case VKB_SURF_XCB: return vkb_xcb_surface_extent(s, e);
|
||||
default:
|
||||
e->width = e->height = 0xFFFFFFFFu;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
static VkImageUsageFlags supported_usage(VkPhysicalDevice pd)
|
||||
{
|
||||
VkImageUsageFlags u = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
||||
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
|
||||
VkFormatProperties fp;
|
||||
vkb_wire_vkGetPhysicalDeviceFormatProperties(pd, VK_FORMAT_B8G8R8A8_UNORM, &fp);
|
||||
if (fp.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) u |= VK_IMAGE_USAGE_STORAGE_BIT;
|
||||
return u;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetPhysicalDeviceSurfaceCapabilitiesKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface,
|
||||
VkSurfaceCapabilitiesKHR *c)
|
||||
{
|
||||
vkb_surface *s = surf(surface);
|
||||
memset(c, 0, sizeof(*c));
|
||||
VkResult r = surface_extent(s, &c->currentExtent);
|
||||
if (r != VK_SUCCESS) return r;
|
||||
c->minImageCount = s->kind == VKB_SURF_WAYLAND ? 3 : 2;
|
||||
c->maxImageCount = 8;
|
||||
c->minImageExtent = (VkExtent2D){1, 1};
|
||||
c->maxImageExtent = (VkExtent2D){16384, 16384};
|
||||
if (c->currentExtent.width != 0xFFFFFFFFu) {
|
||||
c->minImageExtent = c->maxImageExtent = c->currentExtent;
|
||||
if (!c->currentExtent.width || !c->currentExtent.height) c->minImageExtent = (VkExtent2D){0, 0};
|
||||
}
|
||||
c->maxImageArrayLayers = 1;
|
||||
c->supportedTransforms = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
||||
c->currentTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
||||
c->supportedCompositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR | VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
|
||||
if (s->kind == VKB_SURF_WAYLAND) c->supportedCompositeAlpha |= VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR;
|
||||
c->supportedUsageFlags = supported_usage(physicalDevice);
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetPhysicalDeviceSurfaceCapabilities2KHR(VkPhysicalDevice physicalDevice,
|
||||
const VkPhysicalDeviceSurfaceInfo2KHR *pInfo,
|
||||
VkSurfaceCapabilities2KHR *pCaps)
|
||||
{
|
||||
VkResult r = vkb_ep_vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, pInfo->surface, &pCaps->surfaceCapabilities);
|
||||
for (VkBaseOutStructure *b = (VkBaseOutStructure *)pCaps->pNext; b; b = b->pNext) {
|
||||
if (b->sType == VK_STRUCTURE_TYPE_SURFACE_PROTECTED_CAPABILITIES_KHR)
|
||||
((VkSurfaceProtectedCapabilitiesKHR *)b)->supportsProtected = VK_FALSE;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface,
|
||||
uint32_t *pCount, VkSurfaceFormatKHR *pFormats)
|
||||
{
|
||||
(void)physicalDevice;
|
||||
(void)surface;
|
||||
if (!pFormats) {
|
||||
*pCount = vkb_wsi_format_count;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
uint32_t n = *pCount < vkb_wsi_format_count ? *pCount : vkb_wsi_format_count;
|
||||
memcpy(pFormats, vkb_wsi_formats, n * sizeof(*pFormats));
|
||||
*pCount = n;
|
||||
return n < vkb_wsi_format_count ? VK_INCOMPLETE : VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetPhysicalDeviceSurfaceFormats2KHR(VkPhysicalDevice physicalDevice,
|
||||
const VkPhysicalDeviceSurfaceInfo2KHR *pInfo, uint32_t *pCount,
|
||||
VkSurfaceFormat2KHR *pFormats)
|
||||
{
|
||||
(void)physicalDevice;
|
||||
(void)pInfo;
|
||||
if (!pFormats) {
|
||||
*pCount = vkb_wsi_format_count;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
uint32_t n = *pCount < vkb_wsi_format_count ? *pCount : vkb_wsi_format_count;
|
||||
for (uint32_t i = 0; i < n; i++) pFormats[i].surfaceFormat = vkb_wsi_formats[i];
|
||||
*pCount = n;
|
||||
return n < vkb_wsi_format_count ? VK_INCOMPLETE : VK_SUCCESS;
|
||||
}
|
||||
|
||||
static const VkPresentModeKHR present_modes[] = {VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR};
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetPhysicalDeviceSurfacePresentModesKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface,
|
||||
uint32_t *pCount, VkPresentModeKHR *pModes)
|
||||
{
|
||||
(void)physicalDevice;
|
||||
(void)surface;
|
||||
uint32_t total = 3;
|
||||
if (!pModes) {
|
||||
*pCount = total;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
uint32_t n = *pCount < total ? *pCount : total;
|
||||
memcpy(pModes, present_modes, n * sizeof(*pModes));
|
||||
*pCount = n;
|
||||
return n < total ? VK_INCOMPLETE : VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetPhysicalDevicePresentRectanglesKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface,
|
||||
uint32_t *pRectCount, VkRect2D *pRects)
|
||||
{
|
||||
(void)physicalDevice;
|
||||
if (!pRects) {
|
||||
*pRectCount = 1;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
if (*pRectCount < 1) return VK_INCOMPLETE;
|
||||
VkExtent2D e;
|
||||
surface_extent(surf(surface), &e);
|
||||
if (e.width == 0xFFFFFFFFu) e = (VkExtent2D){16384, 16384};
|
||||
pRects[0] = (VkRect2D){{0, 0}, e};
|
||||
*pRectCount = 1;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetDeviceGroupPresentCapabilitiesKHR(VkDevice device, VkDeviceGroupPresentCapabilitiesKHR *pCaps)
|
||||
{
|
||||
(void)device;
|
||||
memset(pCaps->presentMask, 0, sizeof(pCaps->presentMask));
|
||||
pCaps->presentMask[0] = 1;
|
||||
pCaps->modes = VK_DEVICE_GROUP_PRESENT_MODE_LOCAL_BIT_KHR;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetDeviceGroupSurfacePresentModesKHR(VkDevice device, VkSurfaceKHR surface,
|
||||
VkDeviceGroupPresentModeFlagsKHR *pModes)
|
||||
{
|
||||
(void)device;
|
||||
(void)surface;
|
||||
*pModes = VK_DEVICE_GROUP_PRESENT_MODE_LOCAL_BIT_KHR;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ swapchain images */
|
||||
|
||||
static uint32_t pick_type(vkb_device *dev, uint32_t bits, VkMemoryPropertyFlags want)
|
||||
{
|
||||
const VkPhysicalDeviceMemoryProperties *mp = &dev->pd->client_mem;
|
||||
for (uint32_t i = 0; i < mp->memoryTypeCount; i++)
|
||||
if ((bits & (1u << i)) && (mp->memoryTypes[i].propertyFlags & want) == want) return i;
|
||||
for (uint32_t i = 0; i < mp->memoryTypeCount; i++)
|
||||
if (bits & (1u << i)) return i;
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
static int pd_has_ext(vkb_physdev *pd, const char *name)
|
||||
{
|
||||
for (uint32_t i = 0; i < pd->next; i++)
|
||||
if (!strcmp(pd->exts[i].extensionName, name)) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* One swapchain image: exported as a linear dma-buf when `dmabuf`, else an ordinary image. */
|
||||
static VkResult create_image(vkb_swapchain *sc, vkb_swap_image *img, int dmabuf)
|
||||
{
|
||||
vkb_device *dev = sc->dev;
|
||||
VkDevice device = (VkDevice)dev;
|
||||
const VkSwapchainCreateInfoKHR *ci = &sc->info;
|
||||
VkImageCreateInfo ici = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
||||
ici.imageType = VK_IMAGE_TYPE_2D;
|
||||
ici.format = ci->imageFormat;
|
||||
ici.extent = (VkExtent3D){ci->imageExtent.width, ci->imageExtent.height, 1};
|
||||
ici.mipLevels = 1;
|
||||
ici.arrayLayers = ci->imageArrayLayers;
|
||||
ici.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
ici.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
ici.usage = ci->imageUsage | (sc->copy_present || !dmabuf ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : 0);
|
||||
ici.sharingMode = ci->imageSharingMode;
|
||||
ici.queueFamilyIndexCount = ci->queueFamilyIndexCount;
|
||||
ici.pQueueFamilyIndices = ci->pQueueFamilyIndices;
|
||||
ici.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
if (ci->flags & VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR) ici.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
|
||||
VkImageFormatListCreateInfo fl = {VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO, NULL, sc->view_format_count, sc->view_formats};
|
||||
VkExternalMemoryImageCreateInfo ext = {VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, NULL,
|
||||
VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT};
|
||||
uint64_t linear = 0; /* DRM_FORMAT_MOD_LINEAR */
|
||||
VkImageDrmFormatModifierListCreateInfoEXT mods = {VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_LIST_CREATE_INFO_EXT, NULL, 1, &linear};
|
||||
const void *chain = NULL;
|
||||
if (sc->view_format_count) {
|
||||
fl.pNext = chain;
|
||||
chain = &fl;
|
||||
}
|
||||
int use_mods = 0;
|
||||
if (dmabuf) {
|
||||
ext.pNext = chain;
|
||||
chain = &ext;
|
||||
if (pd_has_ext(dev->pd, "VK_EXT_image_drm_format_modifier")) {
|
||||
mods.pNext = chain;
|
||||
chain = &mods;
|
||||
ici.tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT;
|
||||
use_mods = 1;
|
||||
} else {
|
||||
ici.tiling = VK_IMAGE_TILING_LINEAR;
|
||||
}
|
||||
}
|
||||
ici.pNext = chain;
|
||||
VkResult r = vkb_wire_vkCreateImage(device, &ici, NULL, &img->image);
|
||||
if (r != VK_SUCCESS) return r;
|
||||
VkMemoryRequirements req;
|
||||
vkb_ep_vkGetImageMemoryRequirements(device, img->image, &req);
|
||||
uint32_t type = pick_type(dev, req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
if (type == UINT32_MAX) return VK_ERROR_OUT_OF_DEVICE_MEMORY;
|
||||
VkMemoryDedicatedAllocateInfo ded = {VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, NULL, img->image, VK_NULL_HANDLE};
|
||||
VkExportMemoryAllocateInfo exp = {VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO, &ded, VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT};
|
||||
VkMemoryAllocateInfo ai = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, dmabuf ? (const void *)&exp : (const void *)&ded, req.size, type};
|
||||
r = vkb_ep_vkAllocateMemory(device, &ai, NULL, &img->memory);
|
||||
if (r != VK_SUCCESS) return r;
|
||||
r = vkb_wire_vkBindImageMemory(device, img->image, img->memory, 0);
|
||||
if (r != VK_SUCCESS) return r;
|
||||
img->dmabuf_fd = -1;
|
||||
if (dmabuf) {
|
||||
VkMemoryGetFdInfoKHR gi = {VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR, NULL, img->memory, VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT};
|
||||
r = vkb_wire_vkGetMemoryFdKHR(device, &gi, &img->dmabuf_fd);
|
||||
if (r != VK_SUCCESS || img->dmabuf_fd < 0) return r != VK_SUCCESS ? r : VK_ERROR_INITIALIZATION_FAILED;
|
||||
VkImageSubresource sub = {use_mods ? VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT : VK_IMAGE_ASPECT_COLOR_BIT, 0, 0};
|
||||
VkSubresourceLayout lay;
|
||||
vkb_wire_vkGetImageSubresourceLayout(device, img->image, &sub, &lay);
|
||||
img->stride = (uint32_t)lay.rowPitch;
|
||||
img->offset = (uint32_t)lay.offset;
|
||||
img->modifier = 0;
|
||||
if (use_mods) {
|
||||
VkImageDrmFormatModifierPropertiesEXT mp = {VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_PROPERTIES_EXT};
|
||||
if (vkb_wire_vkGetImageDrmFormatModifierPropertiesEXT(device, img->image, &mp) == VK_SUCCESS) img->modifier = mp.drmFormatModifier;
|
||||
}
|
||||
}
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
static void destroy_image(vkb_swapchain *sc, vkb_swap_image *img)
|
||||
{
|
||||
VkDevice device = (VkDevice)sc->dev;
|
||||
if (img->dmabuf_fd >= 0) close(img->dmabuf_fd);
|
||||
if (img->image) vkb_ep_vkDestroyImage(device, img->image, NULL);
|
||||
if (img->memory) vkb_ep_vkFreeMemory(device, img->memory, NULL);
|
||||
}
|
||||
|
||||
static VkResult create_staging(vkb_swapchain *sc)
|
||||
{
|
||||
vkb_device *dev = sc->dev;
|
||||
VkDevice device = (VkDevice)dev;
|
||||
VkDeviceSize size = (VkDeviceSize)sc->info.imageExtent.width * sc->info.imageExtent.height * 4;
|
||||
VkBufferCreateInfo bci = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, NULL, 0, size, VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
VK_SHARING_MODE_EXCLUSIVE, 0, NULL};
|
||||
VkResult r = vkb_ep_vkCreateBuffer(device, &bci, NULL, &sc->staging);
|
||||
if (r != VK_SUCCESS) return r;
|
||||
VkMemoryRequirements req;
|
||||
vkb_ep_vkGetBufferMemoryRequirements(device, sc->staging, &req);
|
||||
uint32_t type = pick_type(dev, req.memoryTypeBits,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT);
|
||||
if (type == UINT32_MAX || !(dev->pd->client_mem.memoryTypes[type].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT))
|
||||
type = pick_type(dev, req.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
|
||||
if (type == UINT32_MAX || !(dev->pd->client_mem.memoryTypes[type].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) {
|
||||
VKB_ERR("swapchain: no host-visible memory to read frames back into");
|
||||
return VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
}
|
||||
VkMemoryAllocateInfo ai = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, req.size, type};
|
||||
r = vkb_ep_vkAllocateMemory(device, &ai, NULL, &sc->staging_mem);
|
||||
if (r != VK_SUCCESS) return r;
|
||||
r = vkb_wire_vkBindBufferMemory(device, sc->staging, sc->staging_mem, 0);
|
||||
if (r != VK_SUCCESS) return r;
|
||||
void *p = NULL;
|
||||
r = vkb_ep_vkMapMemory(device, sc->staging_mem, 0, VK_WHOLE_SIZE, 0, &p);
|
||||
sc->staging_ptr = p;
|
||||
sc->staging_size = size;
|
||||
return r;
|
||||
}
|
||||
|
||||
static VkResult ensure_copy_cbs(vkb_swapchain *sc, uint32_t family)
|
||||
{
|
||||
VkDevice device = (VkDevice)sc->dev;
|
||||
if (sc->pool && sc->pool_family == family) return VK_SUCCESS;
|
||||
if (sc->pool) {
|
||||
vkb_ep_vkDestroyCommandPool(device, sc->pool, NULL);
|
||||
sc->pool = VK_NULL_HANDLE;
|
||||
}
|
||||
VkCommandPoolCreateInfo pci = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, NULL, 0, family};
|
||||
VkResult r = vkb_wire_vkCreateCommandPool(device, &pci, NULL, &sc->pool);
|
||||
if (r != VK_SUCCESS) return r;
|
||||
sc->pool_family = family;
|
||||
VkCommandBufferAllocateInfo cai = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, NULL, sc->pool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, sc->count};
|
||||
r = vkb_ep_vkAllocateCommandBuffers(device, &cai, sc->copy_cbs);
|
||||
if (r != VK_SUCCESS) return r;
|
||||
for (uint32_t i = 0; i < sc->count; i++) {
|
||||
VkCommandBuffer cb = sc->copy_cbs[i];
|
||||
VkCommandBufferBeginInfo bi = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL, 0, NULL};
|
||||
vkb_ep_vkBeginCommandBuffer(cb, &bi);
|
||||
VkImageMemoryBarrier b = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, NULL, VK_ACCESS_MEMORY_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT,
|
||||
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_QUEUE_FAMILY_IGNORED,
|
||||
VK_QUEUE_FAMILY_IGNORED, sc->images[i].image, {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
|
||||
vkb_wire_vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &b);
|
||||
VkBufferImageCopy region = {0, 0, 0, {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, {0, 0, 0},
|
||||
{sc->info.imageExtent.width, sc->info.imageExtent.height, 1}};
|
||||
vkb_wire_vkCmdCopyImageToBuffer(cb, sc->images[i].image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, sc->staging, 1, ®ion);
|
||||
b.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
b.dstAccessMask = 0;
|
||||
b.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
b.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
VkMemoryBarrier hb = {VK_STRUCTURE_TYPE_MEMORY_BARRIER, NULL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT};
|
||||
vkb_wire_vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 1,
|
||||
&hb, 0, NULL, 1, &b);
|
||||
r = vkb_ep_vkEndCommandBuffer(cb);
|
||||
if (r != VK_SUCCESS) return r;
|
||||
}
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
/* Waits (on the CPU) for the app's rendering into image `index`, reading it back when needed. */
|
||||
VkResult vkb_wsi_wait_rendering(vkb_swapchain *sc, VkQueue queue, uint32_t index, uint32_t nwait, const VkSemaphore *wait)
|
||||
{
|
||||
VkDevice device = (VkDevice)sc->dev;
|
||||
VkPipelineStageFlags *stages = alloca((nwait ? nwait : 1) * sizeof(VkPipelineStageFlags));
|
||||
for (uint32_t i = 0; i < nwait; i++) stages[i] = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
|
||||
VkSubmitInfo si = {VK_STRUCTURE_TYPE_SUBMIT_INFO, NULL, nwait, wait, stages, 0, NULL, 0, NULL};
|
||||
if (sc->copy_present) {
|
||||
VkResult r = ensure_copy_cbs(sc, ((vkb_queue *)queue)->family);
|
||||
if (r != VK_SUCCESS) return r;
|
||||
si.commandBufferCount = 1;
|
||||
si.pCommandBuffers = &sc->copy_cbs[index];
|
||||
}
|
||||
VkResult r = vkb_wire_vkQueueSubmit(queue, 1, &si, sc->fence);
|
||||
if (r != VK_SUCCESS) return r;
|
||||
r = vkb_wire_vkWaitForFences(device, 1, &sc->fence, VK_TRUE, 10ull * 1000 * 1000 * 1000);
|
||||
vkb_wire_vkResetFences(device, 1, &sc->fence);
|
||||
if (r == VK_TIMEOUT) {
|
||||
VKB_ERR("swapchain: a frame took more than 10 s to render");
|
||||
return VK_ERROR_DEVICE_LOST;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ swapchain */
|
||||
|
||||
static void swapchain_free(vkb_swapchain *sc)
|
||||
{
|
||||
VkDevice device = (VkDevice)sc->dev;
|
||||
if (sc->backend && sc->backend->destroy) sc->backend->destroy(sc);
|
||||
for (uint32_t i = 0; i < sc->count; i++) destroy_image(sc, &sc->images[i]);
|
||||
if (sc->pool) vkb_ep_vkDestroyCommandPool(device, sc->pool, NULL);
|
||||
if (sc->fence) vkb_wire_vkDestroyFence(device, sc->fence, NULL);
|
||||
if (sc->staging) vkb_ep_vkDestroyBuffer(device, sc->staging, NULL);
|
||||
if (sc->staging_mem) vkb_ep_vkFreeMemory(device, sc->staging_mem, NULL);
|
||||
free(sc->images);
|
||||
free(sc->copy_cbs);
|
||||
free(sc->view_formats);
|
||||
pthread_mutex_destroy(&sc->lock);
|
||||
free(sc);
|
||||
}
|
||||
|
||||
static VkResult swapchain_build(vkb_swapchain *sc, int dmabuf)
|
||||
{
|
||||
VkDevice device = (VkDevice)sc->dev;
|
||||
sc->dmabuf = dmabuf;
|
||||
for (uint32_t i = 0; i < sc->count; i++) {
|
||||
VkResult r = create_image(sc, &sc->images[i], dmabuf);
|
||||
if (r != VK_SUCCESS) return r;
|
||||
sc->images[i].chain = sc;
|
||||
}
|
||||
if (sc->copy_present) {
|
||||
VkResult r = create_staging(sc);
|
||||
if (r != VK_SUCCESS) return r;
|
||||
}
|
||||
VkFenceCreateInfo fci = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
|
||||
VkResult r = vkb_wire_vkCreateFence(device, &fci, NULL, &sc->fence);
|
||||
if (r != VK_SUCCESS) return r;
|
||||
return sc->backend->init(sc);
|
||||
}
|
||||
|
||||
static void swapchain_teardown(vkb_swapchain *sc)
|
||||
{
|
||||
VkDevice device = (VkDevice)sc->dev;
|
||||
if (sc->backend && sc->backend->destroy) sc->backend->destroy(sc);
|
||||
sc->bdata = NULL;
|
||||
for (uint32_t i = 0; i < sc->count; i++) {
|
||||
destroy_image(sc, &sc->images[i]);
|
||||
memset(&sc->images[i], 0, sizeof(sc->images[i]));
|
||||
sc->images[i].dmabuf_fd = -1;
|
||||
}
|
||||
if (sc->fence) vkb_wire_vkDestroyFence(device, sc->fence, NULL);
|
||||
sc->fence = VK_NULL_HANDLE;
|
||||
if (sc->staging) vkb_ep_vkDestroyBuffer(device, sc->staging, NULL);
|
||||
if (sc->staging_mem) vkb_ep_vkFreeMemory(device, sc->staging_mem, NULL);
|
||||
sc->staging = VK_NULL_HANDLE;
|
||||
sc->staging_mem = VK_NULL_HANDLE;
|
||||
sc->staging_ptr = NULL;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateSwapchainKHR(VkDevice device, const VkSwapchainCreateInfoKHR *pCreateInfo,
|
||||
const VkAllocationCallbacks *pAllocator, VkSwapchainKHR *pSwapchain)
|
||||
{
|
||||
(void)pAllocator;
|
||||
vkb_device *dev = vkb_dev(device);
|
||||
vkb_surface *s = surf(pCreateInfo->surface);
|
||||
if (pCreateInfo->oldSwapchain) chain(pCreateInfo->oldSwapchain)->retired = 1;
|
||||
vkb_swapchain *sc = calloc(1, sizeof(*sc));
|
||||
if (!sc) return VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
pthread_mutex_init(&sc->lock, NULL);
|
||||
sc->dev = dev;
|
||||
sc->surface = s;
|
||||
sc->info = *pCreateInfo;
|
||||
sc->info.pNext = NULL;
|
||||
sc->info.pQueueFamilyIndices = NULL;
|
||||
sc->info.queueFamilyIndexCount = 0;
|
||||
sc->info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
for (const VkBaseInStructure *b = pCreateInfo->pNext; b; b = b->pNext) {
|
||||
if (b->sType == VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO) {
|
||||
const VkImageFormatListCreateInfo *fl = (const void *)b;
|
||||
sc->view_formats = calloc(fl->viewFormatCount + 1, sizeof(VkFormat));
|
||||
memcpy(sc->view_formats, fl->pViewFormats, fl->viewFormatCount * sizeof(VkFormat));
|
||||
sc->view_format_count = fl->viewFormatCount;
|
||||
}
|
||||
}
|
||||
sc->count = pCreateInfo->minImageCount < 2 ? 2 : pCreateInfo->minImageCount;
|
||||
if (s->kind == VKB_SURF_WAYLAND && sc->count < 3) sc->count = 3;
|
||||
sc->images = calloc(sc->count, sizeof(vkb_swap_image));
|
||||
sc->copy_cbs = calloc(sc->count, sizeof(VkCommandBuffer));
|
||||
for (uint32_t i = 0; i < sc->count; i++) sc->images[i].dmabuf_fd = -1;
|
||||
VkResult r;
|
||||
switch (s->kind) {
|
||||
case VKB_SURF_WAYLAND:
|
||||
sc->backend = &vkb_wsi_wayland_backend;
|
||||
sc->copy_present = getenv("VKBRIDGE_WSI_SHM") != NULL;
|
||||
r = swapchain_build(sc, !sc->copy_present);
|
||||
if (r != VK_SUCCESS && !sc->copy_present) {
|
||||
VKB_WARN("Wayland: no dma-buf presentation (%d); falling back to copies through shared memory", r);
|
||||
swapchain_teardown(sc);
|
||||
sc->copy_present = 1;
|
||||
r = swapchain_build(sc, 0);
|
||||
}
|
||||
break;
|
||||
case VKB_SURF_XCB:
|
||||
sc->backend = &vkb_wsi_x11_backend;
|
||||
sc->copy_present = 1;
|
||||
r = swapchain_build(sc, 0);
|
||||
break;
|
||||
default:
|
||||
sc->backend = &vkb_wsi_headless_backend;
|
||||
r = swapchain_build(sc, 0);
|
||||
break;
|
||||
}
|
||||
if (r != VK_SUCCESS) {
|
||||
VKB_ERR("swapchain creation failed: %d", r);
|
||||
swapchain_free(sc);
|
||||
return r;
|
||||
}
|
||||
VKB_INFO("swapchain %ux%u format %d, %u images, %s", sc->info.imageExtent.width, sc->info.imageExtent.height, sc->info.imageFormat,
|
||||
sc->count, sc->copy_present ? "presented by copy" : sc->dmabuf ? "presented as dma-bufs" : "headless");
|
||||
*pSwapchain = (VkSwapchainKHR)(uintptr_t)sc;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroySwapchainKHR(VkDevice device, VkSwapchainKHR swapchain, const VkAllocationCallbacks *pAllocator)
|
||||
{
|
||||
(void)pAllocator;
|
||||
if (!swapchain) return;
|
||||
vkb_swapchain *sc = chain(swapchain);
|
||||
/* Images may still be in use by the GPU (present waits are CPU-side, so not by us). */
|
||||
vkb_wire_vkDeviceWaitIdle(device);
|
||||
swapchain_free(sc);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetSwapchainImagesKHR(VkDevice device, VkSwapchainKHR swapchain, uint32_t *pCount, VkImage *pImages)
|
||||
{
|
||||
(void)device;
|
||||
vkb_swapchain *sc = chain(swapchain);
|
||||
if (!pImages) {
|
||||
*pCount = sc->count;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
uint32_t n = *pCount < sc->count ? *pCount : sc->count;
|
||||
for (uint32_t i = 0; i < n; i++) pImages[i] = sc->images[i].image;
|
||||
*pCount = n;
|
||||
return n < sc->count ? VK_INCOMPLETE : VK_SUCCESS;
|
||||
}
|
||||
|
||||
/* Signals the app's semaphore/fence for an acquired image (no GPU work is involved). */
|
||||
static VkResult signal_acquire(vkb_device *dev, VkSemaphore sem, VkFence fence)
|
||||
{
|
||||
VkDevice device = (VkDevice)dev;
|
||||
if (sem) {
|
||||
VkImportSemaphoreFdInfoKHR si = {VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR, NULL, sem, VK_SEMAPHORE_IMPORT_TEMPORARY_BIT,
|
||||
VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, -1};
|
||||
VkResult r = vkb_wire_vkImportSemaphoreFdKHR(device, &si);
|
||||
if (r != VK_SUCCESS) {
|
||||
/* No sync-fd import: signal it with an empty submit. */
|
||||
if (!dev->queues) return VK_ERROR_SURFACE_LOST_KHR;
|
||||
VkSubmitInfo s = {VK_STRUCTURE_TYPE_SUBMIT_INFO, NULL, 0, NULL, NULL, 0, NULL, 1, &sem};
|
||||
r = vkb_wire_vkQueueSubmit((VkQueue)dev->queues, 1, &s, fence);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
if (fence) {
|
||||
VkImportFenceFdInfoKHR fi = {VK_STRUCTURE_TYPE_IMPORT_FENCE_FD_INFO_KHR, NULL, fence, VK_FENCE_IMPORT_TEMPORARY_BIT,
|
||||
VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT, -1};
|
||||
VkResult r = vkb_wire_vkImportFenceFdKHR(device, &fi);
|
||||
if (r != VK_SUCCESS) {
|
||||
if (!dev->queues) return VK_ERROR_SURFACE_LOST_KHR;
|
||||
r = vkb_wire_vkQueueSubmit((VkQueue)dev->queues, 0, NULL, fence);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkAcquireNextImageKHR(VkDevice device, VkSwapchainKHR swapchain, uint64_t timeout, VkSemaphore semaphore,
|
||||
VkFence fence, uint32_t *pImageIndex)
|
||||
{
|
||||
vkb_swapchain *sc = chain(swapchain);
|
||||
if (sc->retired) return VK_ERROR_OUT_OF_DATE_KHR;
|
||||
if (sc->status != VK_SUCCESS) return sc->status;
|
||||
uint64_t deadline = timeout == UINT64_MAX ? UINT64_MAX : now_ns() + timeout;
|
||||
for (;;) {
|
||||
pthread_mutex_lock(&sc->lock);
|
||||
for (uint32_t i = 0; i < sc->count; i++) {
|
||||
if (!sc->images[i].busy && !sc->images[i].acquired) {
|
||||
sc->images[i].acquired = 1;
|
||||
pthread_mutex_unlock(&sc->lock);
|
||||
VkResult r = signal_acquire(vkb_dev(device), semaphore, fence);
|
||||
if (r != VK_SUCCESS) return r;
|
||||
*pImageIndex = i;
|
||||
return sc->status;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&sc->lock);
|
||||
uint64_t now = now_ns();
|
||||
if (timeout == 0) return VK_NOT_READY;
|
||||
if (now >= deadline) return VK_TIMEOUT;
|
||||
VkResult r = sc->backend->wait_free(sc, deadline == UINT64_MAX ? UINT64_MAX : deadline - now);
|
||||
if (r < 0) {
|
||||
sc->status = r;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkAcquireNextImage2KHR(VkDevice device, const VkAcquireNextImageInfoKHR *pInfo, uint32_t *pImageIndex)
|
||||
{
|
||||
return vkb_ep_vkAcquireNextImageKHR(device, pInfo->swapchain, pInfo->timeout, pInfo->semaphore, pInfo->fence, pImageIndex);
|
||||
}
|
||||
|
||||
/* VKBRIDGE_WSI_DUMP=/path/prefix: writes presented frame 60 as a PPM (debugging aid). */
|
||||
static void dump_frame(vkb_swapchain *sc, uint32_t index)
|
||||
{
|
||||
/* The 60th present, or every VKBRIDGE_WSI_DUMP_EVERY-th (watching a game get somewhere). */
|
||||
static long every = -1;
|
||||
if (every < 0) every = getenv("VKBRIDGE_WSI_DUMP_EVERY") ? atol(getenv("VKBRIDGE_WSI_DUMP_EVERY")) : 0;
|
||||
if (every > 0 ? sc->presents % every != 0 : sc->presents != 60) return;
|
||||
uint32_t w = sc->info.imageExtent.width, h = sc->info.imageExtent.height, stride = w * 4;
|
||||
const uint8_t *px = NULL;
|
||||
void *map = NULL;
|
||||
size_t maplen = 0;
|
||||
if (sc->copy_present) {
|
||||
px = sc->staging_ptr;
|
||||
} else if (sc->images[index].dmabuf_fd >= 0) {
|
||||
stride = sc->images[index].stride;
|
||||
maplen = (size_t)sc->images[index].offset + (size_t)stride * h;
|
||||
map = mmap(NULL, maplen, PROT_READ, MAP_SHARED, sc->images[index].dmabuf_fd, 0);
|
||||
if (map == MAP_FAILED) return;
|
||||
px = (const uint8_t *)map + sc->images[index].offset;
|
||||
}
|
||||
if (!px) return;
|
||||
char path[512];
|
||||
if (every > 0)
|
||||
snprintf(path, sizeof(path), "%s-%d-%06u.ppm", getenv("VKBRIDGE_WSI_DUMP"), (int)getpid(), (unsigned)sc->presents);
|
||||
else
|
||||
snprintf(path, sizeof(path), "%s-%d.ppm", getenv("VKBRIDGE_WSI_DUMP"), (int)getpid());
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (f) {
|
||||
int bgr = sc->info.imageFormat == VK_FORMAT_B8G8R8A8_UNORM || sc->info.imageFormat == VK_FORMAT_B8G8R8A8_SRGB;
|
||||
fprintf(f, "P6 %u %u 255\n", w, h);
|
||||
for (uint32_t y = 0; y < h; y++)
|
||||
for (uint32_t x = 0; x < w; x++) {
|
||||
const uint8_t *p = px + (size_t)y * stride + x * 4;
|
||||
uint8_t rgb[3] = {p[bgr ? 2 : 0], p[1], p[bgr ? 0 : 2]};
|
||||
fwrite(rgb, 1, 3, f);
|
||||
}
|
||||
fclose(f);
|
||||
VKB_INFO("dumped frame to %s", path);
|
||||
}
|
||||
if (map) munmap(map, maplen);
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR *pPresentInfo)
|
||||
{
|
||||
const VkPresentRegionsKHR *regions = NULL;
|
||||
for (const VkBaseInStructure *b = pPresentInfo->pNext; b; b = b->pNext)
|
||||
if (b->sType == VK_STRUCTURE_TYPE_PRESENT_REGIONS_KHR) regions = (const void *)b;
|
||||
VkResult worst = VK_SUCCESS;
|
||||
for (uint32_t i = 0; i < pPresentInfo->swapchainCount; i++) {
|
||||
vkb_swapchain *sc = chain(pPresentInfo->pSwapchains[i]);
|
||||
uint32_t index = pPresentInfo->pImageIndices[i];
|
||||
VkResult r = sc->status;
|
||||
if (index >= sc->count) r = VK_ERROR_OUT_OF_DATE_KHR;
|
||||
if (r >= 0) {
|
||||
r = vkb_wsi_wait_rendering(sc, queue, index, i == 0 ? pPresentInfo->waitSemaphoreCount : 0, pPresentInfo->pWaitSemaphores);
|
||||
}
|
||||
pthread_mutex_lock(&sc->lock);
|
||||
if (index < sc->count) {
|
||||
sc->images[index].acquired = 0;
|
||||
sc->images[index].busy = sc->dmabuf && !sc->copy_present;
|
||||
}
|
||||
pthread_mutex_unlock(&sc->lock);
|
||||
if (r >= 0 && getenv("VKBRIDGE_WSI_DUMP")) dump_frame(sc, index);
|
||||
if (r >= 0) {
|
||||
r = sc->backend->present(sc, index, regions && i < regions->swapchainCount ? ®ions->pRegions[i] : NULL);
|
||||
sc->presents++;
|
||||
}
|
||||
if (r < 0) {
|
||||
pthread_mutex_lock(&sc->lock);
|
||||
if (index < sc->count) sc->images[index].busy = 0;
|
||||
pthread_mutex_unlock(&sc->lock);
|
||||
if (r == VK_ERROR_OUT_OF_DATE_KHR || r == VK_ERROR_SURFACE_LOST_KHR) sc->status = r;
|
||||
}
|
||||
if (r == VK_SUCCESS && sc->status == VK_SUBOPTIMAL_KHR) r = VK_SUBOPTIMAL_KHR;
|
||||
if (pPresentInfo->pResults) pPresentInfo->pResults[i] = r;
|
||||
if (r < 0 && (worst >= 0 || r == VK_ERROR_DEVICE_LOST)) worst = r;
|
||||
else if (worst == VK_SUCCESS && r == VK_SUBOPTIMAL_KHR) worst = r;
|
||||
}
|
||||
return worst;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ headless */
|
||||
|
||||
static VkResult hl_init(vkb_swapchain *sc) { (void)sc; return VK_SUCCESS; }
|
||||
static VkResult hl_present(vkb_swapchain *sc, uint32_t i, const VkPresentRegionKHR *r) { (void)sc; (void)i; (void)r; return VK_SUCCESS; }
|
||||
static VkResult hl_wait(vkb_swapchain *sc, uint64_t t) { (void)sc; (void)t; return VK_TIMEOUT; }
|
||||
static void hl_destroy(vkb_swapchain *sc) { (void)sc; }
|
||||
const vkb_wsi_backend vkb_wsi_headless_backend = {hl_init, hl_present, hl_wait, hl_destroy};
|
||||
@@ -0,0 +1,99 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Window-system integration implemented by the bridge client: the server's GPU has no Linux
|
||||
* window systems, so swapchains are built here from ordinary images and handed to the
|
||||
* compositor as dma-bufs (Wayland, zwp_linux_dmabuf_v1) or copied out through shared memory
|
||||
* (Wayland wl_shm, X11 PutImage), or not shown at all (VK_EXT_headless_surface).
|
||||
*/
|
||||
#ifndef VKB_WSI_H
|
||||
#define VKB_WSI_H
|
||||
|
||||
#include "vkb_client.h"
|
||||
|
||||
struct wl_display;
|
||||
struct wl_surface;
|
||||
|
||||
enum vkb_surface_kind { VKB_SURF_WAYLAND, VKB_SURF_XCB, VKB_SURF_HEADLESS };
|
||||
|
||||
typedef struct vkb_surface {
|
||||
enum vkb_surface_kind kind;
|
||||
struct wl_display *wl_display;
|
||||
struct wl_surface *wl_surface;
|
||||
void *xcb_conn; /* xcb_connection_t* */
|
||||
uint32_t xcb_window;
|
||||
struct vkb_wl_display_info *wl_info; /* cached formats/modifiers per wl_display */
|
||||
} vkb_surface;
|
||||
|
||||
typedef struct vkb_swapchain vkb_swapchain;
|
||||
|
||||
typedef struct vkb_swap_image {
|
||||
VkImage image;
|
||||
VkDeviceMemory memory;
|
||||
int busy; /* held by the compositor (or acquired by the app) */
|
||||
int acquired;
|
||||
void *backend; /* backend per-image data (wl_buffer...) */
|
||||
uint32_t stride, offset;
|
||||
uint64_t modifier;
|
||||
int dmabuf_fd;
|
||||
vkb_swapchain *chain;
|
||||
} vkb_swap_image;
|
||||
|
||||
typedef struct vkb_wsi_backend {
|
||||
/* Called after images exist; may fail (e.g. no dma-buf support) - the generic code then
|
||||
* retries with copy_present = 1. */
|
||||
VkResult (*init)(vkb_swapchain *sc);
|
||||
/* The image's rendering is complete (and, with copy_present, its pixels are in sc->staging). */
|
||||
VkResult (*present)(vkb_swapchain *sc, uint32_t index, const VkPresentRegionKHR *region);
|
||||
/* Wait until an image may be acquired (dispatching events); 0 timeout = poll. */
|
||||
VkResult (*wait_free)(vkb_swapchain *sc, uint64_t timeout_ns);
|
||||
void (*destroy)(vkb_swapchain *sc);
|
||||
} vkb_wsi_backend;
|
||||
|
||||
struct vkb_swapchain {
|
||||
vkb_device *dev;
|
||||
vkb_surface *surface;
|
||||
VkSwapchainCreateInfoKHR info;
|
||||
VkFormat *view_formats;
|
||||
uint32_t view_format_count;
|
||||
uint32_t count;
|
||||
vkb_swap_image *images;
|
||||
const vkb_wsi_backend *backend;
|
||||
void *bdata;
|
||||
int dmabuf; /* images are exported dma-bufs */
|
||||
int copy_present; /* pixels are read back into `staging` at present */
|
||||
/* Read-back resources (copy_present). */
|
||||
VkBuffer staging;
|
||||
VkDeviceMemory staging_mem;
|
||||
uint8_t *staging_ptr;
|
||||
VkDeviceSize staging_size;
|
||||
VkCommandPool pool;
|
||||
uint32_t pool_family;
|
||||
VkCommandBuffer *copy_cbs;
|
||||
VkFence fence;
|
||||
VkResult status; /* VK_ERROR_OUT_OF_DATE_KHR / SURFACE_LOST once broken */
|
||||
pthread_mutex_t lock;
|
||||
uint64_t presents;
|
||||
int retired;
|
||||
};
|
||||
|
||||
/* Formats every surface offers. */
|
||||
extern const VkSurfaceFormatKHR vkb_wsi_formats[];
|
||||
extern const uint32_t vkb_wsi_format_count;
|
||||
|
||||
/* Wayland */
|
||||
extern const vkb_wsi_backend vkb_wsi_wayland_backend;
|
||||
int vkb_wl_load(void);
|
||||
VkResult vkb_wl_surface_extent(vkb_surface *s, VkExtent2D *e);
|
||||
/* X11 */
|
||||
extern const vkb_wsi_backend vkb_wsi_x11_backend;
|
||||
int vkb_xcb_load(void);
|
||||
VkResult vkb_xcb_surface_extent(vkb_surface *s, VkExtent2D *e);
|
||||
void *vkb_xlib_to_xcb(void *dpy);
|
||||
/* Headless */
|
||||
extern const vkb_wsi_backend vkb_wsi_headless_backend;
|
||||
|
||||
/* Helpers in wsi.c */
|
||||
uint32_t vkb_drm_format(VkFormat f);
|
||||
VkResult vkb_wsi_wait_rendering(vkb_swapchain *sc, VkQueue queue, uint32_t index, uint32_t nwait, const VkSemaphore *wait);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,558 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Wayland presentation.
|
||||
*
|
||||
* - dma-buf: swapchain images are linear exportable images; each becomes a wl_buffer through
|
||||
* zwp_linux_dmabuf_v1 once, and a present is attach + commit (no copy). This is the path for
|
||||
* gamescope (and its WSI layer, which turns X11 games' swapchains into Wayland ones).
|
||||
* - wl_shm: for compositors without dma-buf (KWin compositing in software on the desktop): the
|
||||
* frame is read back and copied into a shared-memory buffer.
|
||||
*
|
||||
* libwayland-client is opened at run time, so the driver loads in programs that never touch
|
||||
* Wayland. Our objects live on a private event queue, like Mesa's WSI.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "wsi.h"
|
||||
#include "gen/vkb_client_gen.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* ------------------------------------------------------------------ libwayland-client */
|
||||
|
||||
struct wl_proxy;
|
||||
struct wl_event_queue;
|
||||
struct wl_message {
|
||||
const char *name;
|
||||
const char *signature;
|
||||
const struct wl_interface **types;
|
||||
};
|
||||
struct wl_interface {
|
||||
const char *name;
|
||||
int version;
|
||||
int method_count;
|
||||
const struct wl_message *methods;
|
||||
int event_count;
|
||||
const struct wl_message *events;
|
||||
};
|
||||
|
||||
static struct {
|
||||
void *lib;
|
||||
struct wl_proxy *(*proxy_marshal_constructor)(struct wl_proxy *, uint32_t, const struct wl_interface *, ...);
|
||||
struct wl_proxy *(*proxy_marshal_constructor_versioned)(struct wl_proxy *, uint32_t, const struct wl_interface *, uint32_t, ...);
|
||||
void (*proxy_marshal)(struct wl_proxy *, uint32_t, ...);
|
||||
int (*proxy_add_listener)(struct wl_proxy *, void (**)(void), void *);
|
||||
void (*proxy_destroy)(struct wl_proxy *);
|
||||
void *(*proxy_create_wrapper)(void *);
|
||||
void (*proxy_wrapper_destroy)(void *);
|
||||
void (*proxy_set_queue)(struct wl_proxy *, struct wl_event_queue *);
|
||||
uint32_t (*proxy_get_version)(struct wl_proxy *);
|
||||
struct wl_event_queue *(*display_create_queue)(struct wl_display *);
|
||||
void (*event_queue_destroy)(struct wl_event_queue *);
|
||||
int (*display_roundtrip_queue)(struct wl_display *, struct wl_event_queue *);
|
||||
int (*display_dispatch_queue_pending)(struct wl_display *, struct wl_event_queue *);
|
||||
int (*display_prepare_read_queue)(struct wl_display *, struct wl_event_queue *);
|
||||
int (*display_read_events)(struct wl_display *);
|
||||
void (*display_cancel_read)(struct wl_display *);
|
||||
int (*display_flush)(struct wl_display *);
|
||||
int (*display_get_fd)(struct wl_display *);
|
||||
int (*display_get_error)(struct wl_display *);
|
||||
const struct wl_interface *registry_iface, *callback_iface, *buffer_iface, *shm_iface, *shm_pool_iface;
|
||||
} wl;
|
||||
|
||||
int vkb_wl_load(void)
|
||||
{
|
||||
static int state;
|
||||
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
pthread_mutex_lock(&lock);
|
||||
if (!state) {
|
||||
wl.lib = dlopen("libwayland-client.so.0", RTLD_NOW | RTLD_LOCAL);
|
||||
state = -1;
|
||||
if (wl.lib) {
|
||||
#define L(f, n) (*(void **)&wl.f = dlsym(wl.lib, n))
|
||||
int ok = 1;
|
||||
ok &= !!L(proxy_marshal_constructor, "wl_proxy_marshal_constructor");
|
||||
ok &= !!L(proxy_marshal_constructor_versioned, "wl_proxy_marshal_constructor_versioned");
|
||||
ok &= !!L(proxy_marshal, "wl_proxy_marshal");
|
||||
ok &= !!L(proxy_add_listener, "wl_proxy_add_listener");
|
||||
ok &= !!L(proxy_destroy, "wl_proxy_destroy");
|
||||
ok &= !!L(proxy_create_wrapper, "wl_proxy_create_wrapper");
|
||||
ok &= !!L(proxy_wrapper_destroy, "wl_proxy_wrapper_destroy");
|
||||
ok &= !!L(proxy_set_queue, "wl_proxy_set_queue");
|
||||
ok &= !!L(proxy_get_version, "wl_proxy_get_version");
|
||||
ok &= !!L(display_create_queue, "wl_display_create_queue");
|
||||
ok &= !!L(event_queue_destroy, "wl_event_queue_destroy");
|
||||
ok &= !!L(display_roundtrip_queue, "wl_display_roundtrip_queue");
|
||||
ok &= !!L(display_dispatch_queue_pending, "wl_display_dispatch_queue_pending");
|
||||
ok &= !!L(display_prepare_read_queue, "wl_display_prepare_read_queue");
|
||||
ok &= !!L(display_read_events, "wl_display_read_events");
|
||||
ok &= !!L(display_cancel_read, "wl_display_cancel_read");
|
||||
ok &= !!L(display_flush, "wl_display_flush");
|
||||
ok &= !!L(display_get_fd, "wl_display_get_fd");
|
||||
ok &= !!L(display_get_error, "wl_display_get_error");
|
||||
ok &= !!L(registry_iface, "wl_registry_interface");
|
||||
ok &= !!L(callback_iface, "wl_callback_interface");
|
||||
ok &= !!L(buffer_iface, "wl_buffer_interface");
|
||||
ok &= !!L(shm_iface, "wl_shm_interface");
|
||||
ok &= !!L(shm_pool_iface, "wl_shm_pool_interface");
|
||||
#undef L
|
||||
if (ok) state = 1;
|
||||
else VKB_ERR("libwayland-client lacks functions this driver needs");
|
||||
} else {
|
||||
VKB_WARN("no libwayland-client: %s", dlerror());
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&lock);
|
||||
return state > 0;
|
||||
}
|
||||
|
||||
/* zwp_linux_dmabuf_v1 / zwp_linux_buffer_params_v1, as wayland-scanner would emit them. */
|
||||
static const struct wl_interface params_iface;
|
||||
static const struct wl_interface *dmabuf_types[8];
|
||||
static const struct wl_message dmabuf_requests[] = {
|
||||
{"destroy", "", dmabuf_types + 0},
|
||||
{"create_params", "n", dmabuf_types + 6},
|
||||
};
|
||||
static const struct wl_message dmabuf_events[] = {
|
||||
{"format", "u", dmabuf_types + 0},
|
||||
{"modifier", "3uuu", dmabuf_types + 0},
|
||||
};
|
||||
static const struct wl_interface dmabuf_iface = {"zwp_linux_dmabuf_v1", 3, 2, dmabuf_requests, 2, dmabuf_events};
|
||||
static const struct wl_message params_requests[] = {
|
||||
{"destroy", "", dmabuf_types + 0},
|
||||
{"add", "huuuuu", dmabuf_types + 0},
|
||||
{"create", "iiuu", dmabuf_types + 0},
|
||||
{"create_immed", "2niiuu", dmabuf_types + 7},
|
||||
};
|
||||
static const struct wl_message params_events[] = {
|
||||
{"created", "n", dmabuf_types + 7},
|
||||
{"failed", "", dmabuf_types + 0},
|
||||
};
|
||||
static const struct wl_interface params_iface = {"zwp_linux_buffer_params_v1", 3, 4, params_requests, 2, params_events};
|
||||
|
||||
static void init_types(void)
|
||||
{
|
||||
/* types[0..5] stay NULL (no object arguments); [6] = params, [7] = wl_buffer */
|
||||
dmabuf_types[6] = ¶ms_iface;
|
||||
dmabuf_types[7] = wl.buffer_iface;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ per-swapchain state */
|
||||
|
||||
#define WL_DISPLAY_GET_REGISTRY 1
|
||||
#define WL_REGISTRY_BIND 0
|
||||
#define WL_SURFACE_ATTACH 1
|
||||
#define WL_SURFACE_DAMAGE 2
|
||||
#define WL_SURFACE_FRAME 3
|
||||
#define WL_SURFACE_COMMIT 6
|
||||
#define WL_SURFACE_DAMAGE_BUFFER 9
|
||||
#define WL_BUFFER_DESTROY 0
|
||||
#define WL_SHM_CREATE_POOL 0
|
||||
#define WL_SHM_POOL_CREATE_BUFFER 0
|
||||
#define WL_SHM_POOL_DESTROY 1
|
||||
#define DMABUF_DESTROY 0
|
||||
#define DMABUF_CREATE_PARAMS 1
|
||||
#define PARAMS_DESTROY 0
|
||||
#define PARAMS_ADD 1
|
||||
#define PARAMS_CREATE_IMMED 3
|
||||
#define WL_SHM_FORMAT_ARGB8888 0u
|
||||
#define WL_SHM_FORMAT_XRGB8888 1u
|
||||
#define FOURCC(a, b, c, d) ((uint32_t)(a) | ((uint32_t)(b) << 8) | ((uint32_t)(c) << 16) | ((uint32_t)(d) << 24))
|
||||
#define DRM_XRGB8888 FOURCC('X', 'R', '2', '4')
|
||||
#define DRM_ARGB8888 FOURCC('A', 'R', '2', '4')
|
||||
#define DRM_XBGR8888 FOURCC('X', 'B', '2', '4')
|
||||
#define DRM_ABGR8888 FOURCC('A', 'B', '2', '4')
|
||||
#define MAX_FMTS 64
|
||||
|
||||
typedef struct shm_buf {
|
||||
struct wl_proxy *buffer;
|
||||
uint8_t *ptr;
|
||||
int busy;
|
||||
} shm_buf;
|
||||
|
||||
typedef struct wl_state {
|
||||
struct wl_display *display;
|
||||
struct wl_event_queue *queue;
|
||||
void *display_wrapper;
|
||||
void *surface_wrapper;
|
||||
struct wl_proxy *registry;
|
||||
struct wl_proxy *shm;
|
||||
struct wl_proxy *dmabuf;
|
||||
uint32_t dmabuf_version;
|
||||
uint32_t shm_formats[MAX_FMTS];
|
||||
int nshm;
|
||||
struct { uint32_t format; uint64_t modifier; } dmabuf_mods[256];
|
||||
int nmods;
|
||||
uint32_t dmabuf_formats[MAX_FMTS];
|
||||
int nformats;
|
||||
struct wl_proxy **buffers; /* dma-buf wl_buffers, one per image */
|
||||
shm_buf *shm_bufs;
|
||||
int nshm_bufs;
|
||||
struct wl_proxy *shm_pool;
|
||||
uint8_t *shm_map;
|
||||
size_t shm_size;
|
||||
uint32_t shm_stride, shm_format;
|
||||
int swizzle; /* RGBA frame into an ARGB/XRGB shm buffer */
|
||||
struct wl_proxy *frame; /* pending frame callback */
|
||||
vkb_swapchain *sc;
|
||||
} wl_state;
|
||||
|
||||
static void registry_global(void *data, struct wl_proxy *reg, uint32_t name, const char *iface, uint32_t version)
|
||||
{
|
||||
wl_state *s = data;
|
||||
if (!strcmp(iface, "wl_shm") && !s->shm) {
|
||||
s->shm = wl.proxy_marshal_constructor_versioned(reg, WL_REGISTRY_BIND, wl.shm_iface, 1, name, wl.shm_iface->name, 1, NULL);
|
||||
} else if (!strcmp(iface, "zwp_linux_dmabuf_v1") && !s->dmabuf && version >= 2) {
|
||||
uint32_t v = version < 3 ? version : 3;
|
||||
s->dmabuf = wl.proxy_marshal_constructor_versioned(reg, WL_REGISTRY_BIND, &dmabuf_iface, v, name, dmabuf_iface.name, v, NULL);
|
||||
s->dmabuf_version = v;
|
||||
}
|
||||
}
|
||||
|
||||
static void registry_global_remove(void *data, struct wl_proxy *reg, uint32_t name)
|
||||
{
|
||||
(void)data;
|
||||
(void)reg;
|
||||
(void)name;
|
||||
}
|
||||
|
||||
static const void *registry_listener[] = {(void *)registry_global, (void *)registry_global_remove};
|
||||
|
||||
static void shm_format(void *data, struct wl_proxy *shm, uint32_t format)
|
||||
{
|
||||
(void)shm;
|
||||
wl_state *s = data;
|
||||
if (s->nshm < MAX_FMTS) s->shm_formats[s->nshm++] = format;
|
||||
}
|
||||
|
||||
static const void *shm_listener[] = {(void *)shm_format};
|
||||
|
||||
static void dmabuf_format(void *data, struct wl_proxy *d, uint32_t format)
|
||||
{
|
||||
(void)d;
|
||||
wl_state *s = data;
|
||||
if (s->nformats < MAX_FMTS) s->dmabuf_formats[s->nformats++] = format;
|
||||
}
|
||||
|
||||
static void dmabuf_modifier(void *data, struct wl_proxy *d, uint32_t format, uint32_t hi, uint32_t lo)
|
||||
{
|
||||
(void)d;
|
||||
wl_state *s = data;
|
||||
if (s->nmods < 256) {
|
||||
s->dmabuf_mods[s->nmods].format = format;
|
||||
s->dmabuf_mods[s->nmods].modifier = ((uint64_t)hi << 32) | lo;
|
||||
s->nmods++;
|
||||
}
|
||||
}
|
||||
|
||||
static const void *dmabuf_listener[] = {(void *)dmabuf_format, (void *)dmabuf_modifier};
|
||||
|
||||
static void buffer_release(void *data, struct wl_proxy *buffer)
|
||||
{
|
||||
(void)buffer;
|
||||
vkb_swap_image *img = data;
|
||||
pthread_mutex_lock(&img->chain->lock);
|
||||
img->busy = 0;
|
||||
pthread_mutex_unlock(&img->chain->lock);
|
||||
}
|
||||
|
||||
static const void *buffer_listener[] = {(void *)buffer_release};
|
||||
|
||||
static void shm_buffer_release(void *data, struct wl_proxy *buffer)
|
||||
{
|
||||
(void)buffer;
|
||||
((shm_buf *)data)->busy = 0;
|
||||
}
|
||||
|
||||
static const void *shm_buffer_listener[] = {(void *)shm_buffer_release};
|
||||
|
||||
static void frame_done(void *data, struct wl_proxy *cb, uint32_t t)
|
||||
{
|
||||
(void)t;
|
||||
wl_state *s = data;
|
||||
if (s->frame == cb) s->frame = NULL;
|
||||
wl.proxy_destroy(cb);
|
||||
}
|
||||
|
||||
static const void *frame_listener[] = {(void *)frame_done};
|
||||
|
||||
/* Dispatch our queue, waiting up to timeout_ns for events. -1 on a dead connection. */
|
||||
static int dispatch_timeout(wl_state *s, uint64_t timeout_ns)
|
||||
{
|
||||
if (wl.display_dispatch_queue_pending(s->display, s->queue) < 0) return -1;
|
||||
while (wl.display_prepare_read_queue(s->display, s->queue) != 0) {
|
||||
if (wl.display_dispatch_queue_pending(s->display, s->queue) < 0) return -1;
|
||||
}
|
||||
if (wl.display_flush(s->display) < 0 && errno != EAGAIN) {
|
||||
wl.display_cancel_read(s->display);
|
||||
return -1;
|
||||
}
|
||||
struct pollfd p = {wl.display_get_fd(s->display), POLLIN, 0};
|
||||
int ms = timeout_ns == UINT64_MAX ? -1 : (int)((timeout_ns + 999999) / 1000000);
|
||||
int r = poll(&p, 1, ms);
|
||||
if (r <= 0) {
|
||||
wl.display_cancel_read(s->display);
|
||||
return r < 0 && errno != EINTR ? -1 : 0;
|
||||
}
|
||||
if (wl.display_read_events(s->display) < 0) return -1;
|
||||
return wl.display_dispatch_queue_pending(s->display, s->queue) < 0 ? -1 : 1;
|
||||
}
|
||||
|
||||
static int has_u32(const uint32_t *a, int n, uint32_t v)
|
||||
{
|
||||
for (int i = 0; i < n; i++)
|
||||
if (a[i] == v) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dmabuf_supports(wl_state *s, uint32_t fourcc, uint64_t modifier)
|
||||
{
|
||||
if (s->nmods) {
|
||||
for (int i = 0; i < s->nmods; i++) {
|
||||
/* DRM_FORMAT_MOD_INVALID means "implicit": treat as linear for our linear buffers. */
|
||||
if (s->dmabuf_mods[i].format == fourcc &&
|
||||
(s->dmabuf_mods[i].modifier == modifier || s->dmabuf_mods[i].modifier == 0x00ffffffffffffffull))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return has_u32(s->dmabuf_formats, s->nformats, fourcc);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ backend */
|
||||
|
||||
VkResult vkb_wl_surface_extent(vkb_surface *s, VkExtent2D *e)
|
||||
{
|
||||
(void)s;
|
||||
e->width = e->height = 0xFFFFFFFFu;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
static void wl_teardown(wl_state *s);
|
||||
|
||||
static VkResult wl_init(vkb_swapchain *sc)
|
||||
{
|
||||
vkb_surface *surface = sc->surface;
|
||||
wl_state *s = calloc(1, sizeof(*s));
|
||||
sc->bdata = s;
|
||||
s->sc = sc;
|
||||
s->display = surface->wl_display;
|
||||
init_types();
|
||||
s->queue = wl.display_create_queue(s->display);
|
||||
s->display_wrapper = wl.proxy_create_wrapper(s->display);
|
||||
wl.proxy_set_queue(s->display_wrapper, s->queue);
|
||||
s->surface_wrapper = wl.proxy_create_wrapper(surface->wl_surface);
|
||||
wl.proxy_set_queue(s->surface_wrapper, s->queue);
|
||||
s->registry = wl.proxy_marshal_constructor(s->display_wrapper, WL_DISPLAY_GET_REGISTRY, wl.registry_iface, NULL);
|
||||
wl.proxy_add_listener(s->registry, (void (**)(void))registry_listener, s);
|
||||
if (wl.display_roundtrip_queue(s->display, s->queue) < 0) return VK_ERROR_SURFACE_LOST_KHR;
|
||||
if (s->shm) wl.proxy_add_listener(s->shm, (void (**)(void))shm_listener, s);
|
||||
if (s->dmabuf) wl.proxy_add_listener(s->dmabuf, (void (**)(void))dmabuf_listener, s);
|
||||
if (wl.display_roundtrip_queue(s->display, s->queue) < 0) return VK_ERROR_SURFACE_LOST_KHR;
|
||||
|
||||
uint32_t w = sc->info.imageExtent.width, h = sc->info.imageExtent.height;
|
||||
int opaque = sc->info.compositeAlpha == VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
||||
int rgba = sc->info.imageFormat == VK_FORMAT_R8G8B8A8_UNORM || sc->info.imageFormat == VK_FORMAT_R8G8B8A8_SRGB;
|
||||
if (!sc->copy_present) {
|
||||
if (!s->dmabuf) {
|
||||
VKB_INFO("Wayland: the compositor has no zwp_linux_dmabuf_v1");
|
||||
return VK_ERROR_FEATURE_NOT_PRESENT;
|
||||
}
|
||||
uint32_t fourcc = rgba ? (opaque ? DRM_XBGR8888 : DRM_ABGR8888) : (opaque ? DRM_XRGB8888 : DRM_ARGB8888);
|
||||
uint64_t mod = sc->images[0].modifier;
|
||||
if (!dmabuf_supports(s, fourcc, mod)) {
|
||||
uint32_t alt = rgba ? DRM_ABGR8888 : DRM_ARGB8888;
|
||||
if (!dmabuf_supports(s, alt, mod)) {
|
||||
VKB_INFO("Wayland: the compositor takes no %.4s dma-buf with modifier 0x%llx", (char *)&fourcc, (unsigned long long)mod);
|
||||
return VK_ERROR_FORMAT_NOT_SUPPORTED;
|
||||
}
|
||||
fourcc = alt;
|
||||
}
|
||||
s->buffers = calloc(sc->count, sizeof(*s->buffers));
|
||||
for (uint32_t i = 0; i < sc->count; i++) {
|
||||
vkb_swap_image *img = &sc->images[i];
|
||||
struct wl_proxy *params = wl.proxy_marshal_constructor(s->dmabuf, DMABUF_CREATE_PARAMS, ¶ms_iface, NULL);
|
||||
wl.proxy_marshal(params, PARAMS_ADD, img->dmabuf_fd, 0u, img->offset, img->stride, (uint32_t)(img->modifier >> 32),
|
||||
(uint32_t)(img->modifier & 0xffffffffu));
|
||||
s->buffers[i] = wl.proxy_marshal_constructor(params, PARAMS_CREATE_IMMED, wl.buffer_iface, NULL, (int32_t)w, (int32_t)h, fourcc, 0u);
|
||||
wl.proxy_marshal(params, PARAMS_DESTROY);
|
||||
wl.proxy_destroy(params);
|
||||
wl.proxy_add_listener(s->buffers[i], (void (**)(void))buffer_listener, img);
|
||||
}
|
||||
if (wl.display_roundtrip_queue(s->display, s->queue) < 0 || wl.display_get_error(s->display)) {
|
||||
VKB_ERR("Wayland: the compositor refused the dma-bufs");
|
||||
return VK_ERROR_SURFACE_LOST_KHR;
|
||||
}
|
||||
VKB_INFO("Wayland: %u dma-buf buffers %ux%u %.4s, stride %u, modifier 0x%llx", sc->count, w, h, (char *)&fourcc,
|
||||
sc->images[0].stride, (unsigned long long)sc->images[0].modifier);
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
if (!s->shm) {
|
||||
VKB_ERR("Wayland: the compositor offers neither dma-buf nor wl_shm");
|
||||
return VK_ERROR_SURFACE_LOST_KHR;
|
||||
}
|
||||
uint32_t fmt;
|
||||
if (rgba && has_u32(s->shm_formats, s->nshm, opaque ? DRM_XBGR8888 : DRM_ABGR8888)) {
|
||||
fmt = opaque ? DRM_XBGR8888 : DRM_ABGR8888;
|
||||
} else {
|
||||
fmt = opaque ? WL_SHM_FORMAT_XRGB8888 : WL_SHM_FORMAT_ARGB8888;
|
||||
s->swizzle = rgba;
|
||||
}
|
||||
s->shm_format = fmt;
|
||||
s->shm_stride = w * 4;
|
||||
s->nshm_bufs = (int)sc->count + 1;
|
||||
size_t one = (size_t)s->shm_stride * h;
|
||||
s->shm_size = one * (size_t)s->nshm_bufs;
|
||||
int fd = vkb_memfd("vkbridge-wl-shm", s->shm_size);
|
||||
if (fd < 0) return VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
s->shm_map = mmap(NULL, s->shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (s->shm_map == MAP_FAILED) {
|
||||
s->shm_map = NULL;
|
||||
close(fd);
|
||||
return VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
}
|
||||
s->shm_pool = wl.proxy_marshal_constructor(s->shm, WL_SHM_CREATE_POOL, wl.shm_pool_iface, NULL, fd, (int32_t)s->shm_size);
|
||||
close(fd);
|
||||
s->shm_bufs = calloc((size_t)s->nshm_bufs, sizeof(shm_buf));
|
||||
for (int i = 0; i < s->nshm_bufs; i++) {
|
||||
s->shm_bufs[i].ptr = s->shm_map + one * (size_t)i;
|
||||
s->shm_bufs[i].buffer = wl.proxy_marshal_constructor(s->shm_pool, WL_SHM_POOL_CREATE_BUFFER, wl.buffer_iface, NULL,
|
||||
(int32_t)(one * (size_t)i), (int32_t)w, (int32_t)h, (int32_t)s->shm_stride, fmt);
|
||||
wl.proxy_add_listener(s->shm_bufs[i].buffer, (void (**)(void))shm_buffer_listener, &s->shm_bufs[i]);
|
||||
}
|
||||
VKB_INFO("Wayland: presenting %ux%u through wl_shm (format 0x%x%s)", w, h, fmt, s->swizzle ? ", swizzled" : "");
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
static void copy_frame(wl_state *s, const uint8_t *src, uint8_t *dst, uint32_t w, uint32_t h)
|
||||
{
|
||||
size_t row = (size_t)w * 4;
|
||||
if (!s->swizzle) {
|
||||
memcpy(dst, src, row * h);
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < (size_t)w * h; i++) {
|
||||
dst[i * 4 + 0] = src[i * 4 + 2];
|
||||
dst[i * 4 + 1] = src[i * 4 + 1];
|
||||
dst[i * 4 + 2] = src[i * 4 + 0];
|
||||
dst[i * 4 + 3] = src[i * 4 + 3];
|
||||
}
|
||||
}
|
||||
|
||||
static VkResult wl_present(vkb_swapchain *sc, uint32_t index, const VkPresentRegionKHR *region)
|
||||
{
|
||||
wl_state *s = sc->bdata;
|
||||
uint32_t w = sc->info.imageExtent.width, h = sc->info.imageExtent.height;
|
||||
if (wl.display_get_error(s->display)) return VK_ERROR_SURFACE_LOST_KHR;
|
||||
/* FIFO: one frame per compositor frame callback (bounded, so a hidden window cannot hang
|
||||
* the game forever). */
|
||||
if (sc->info.presentMode == VK_PRESENT_MODE_FIFO_KHR || sc->info.presentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR) {
|
||||
struct timespec t0;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t0);
|
||||
while (s->frame) {
|
||||
if (dispatch_timeout(s, 20000000ull) < 0) return VK_ERROR_SURFACE_LOST_KHR;
|
||||
struct timespec t1;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t1);
|
||||
if ((t1.tv_sec - t0.tv_sec) * 1000 + (t1.tv_nsec - t0.tv_nsec) / 1000000 > 250) break;
|
||||
}
|
||||
}
|
||||
struct wl_proxy *buffer;
|
||||
if (!sc->copy_present) {
|
||||
buffer = s->buffers[index];
|
||||
} else {
|
||||
shm_buf *b = NULL;
|
||||
for (int tries = 0; !b && tries < 50; tries++) {
|
||||
for (int i = 0; i < s->nshm_bufs; i++)
|
||||
if (!s->shm_bufs[i].busy) {
|
||||
b = &s->shm_bufs[i];
|
||||
break;
|
||||
}
|
||||
if (!b && dispatch_timeout(s, 10000000ull) < 0) return VK_ERROR_SURFACE_LOST_KHR;
|
||||
}
|
||||
if (!b) return VK_SUCCESS; /* compositor holds every buffer: drop this frame */
|
||||
copy_frame(s, sc->staging_ptr, b->ptr, w, h);
|
||||
b->busy = 1;
|
||||
buffer = b->buffer;
|
||||
}
|
||||
if (sc->info.presentMode == VK_PRESENT_MODE_FIFO_KHR || sc->info.presentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR) {
|
||||
if (s->frame) wl.proxy_destroy(s->frame);
|
||||
s->frame = wl.proxy_marshal_constructor(s->surface_wrapper, WL_SURFACE_FRAME, wl.callback_iface, NULL);
|
||||
wl.proxy_add_listener(s->frame, (void (**)(void))frame_listener, s);
|
||||
}
|
||||
wl.proxy_marshal(s->surface_wrapper, WL_SURFACE_ATTACH, buffer, 0, 0);
|
||||
int v4 = wl.proxy_get_version(s->surface_wrapper) >= 4;
|
||||
if (region && region->rectangleCount && region->pRectangles) {
|
||||
for (uint32_t i = 0; i < region->rectangleCount; i++) {
|
||||
const VkRectLayerKHR *r = ®ion->pRectangles[i];
|
||||
wl.proxy_marshal(s->surface_wrapper, v4 ? WL_SURFACE_DAMAGE_BUFFER : WL_SURFACE_DAMAGE, r->offset.x, r->offset.y,
|
||||
(int32_t)r->extent.width, (int32_t)r->extent.height);
|
||||
}
|
||||
} else {
|
||||
wl.proxy_marshal(s->surface_wrapper, v4 ? WL_SURFACE_DAMAGE_BUFFER : WL_SURFACE_DAMAGE, 0, 0, (int32_t)w, (int32_t)h);
|
||||
}
|
||||
wl.proxy_marshal(s->surface_wrapper, WL_SURFACE_COMMIT);
|
||||
wl.display_flush(s->display);
|
||||
if (wl.display_dispatch_queue_pending(s->display, s->queue) < 0) return VK_ERROR_SURFACE_LOST_KHR;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
static VkResult wl_wait_free(vkb_swapchain *sc, uint64_t timeout_ns)
|
||||
{
|
||||
wl_state *s = sc->bdata;
|
||||
int r = dispatch_timeout(s, timeout_ns);
|
||||
if (r < 0) return VK_ERROR_SURFACE_LOST_KHR;
|
||||
return r == 0 ? VK_TIMEOUT : VK_SUCCESS;
|
||||
}
|
||||
|
||||
static void wl_teardown(wl_state *s)
|
||||
{
|
||||
if (!s) return;
|
||||
vkb_swapchain *sc = s->sc;
|
||||
if (s->frame) wl.proxy_destroy(s->frame);
|
||||
if (s->buffers) {
|
||||
for (uint32_t i = 0; i < sc->count; i++)
|
||||
if (s->buffers[i]) {
|
||||
wl.proxy_marshal(s->buffers[i], WL_BUFFER_DESTROY);
|
||||
wl.proxy_destroy(s->buffers[i]);
|
||||
}
|
||||
free(s->buffers);
|
||||
}
|
||||
if (s->shm_bufs) {
|
||||
for (int i = 0; i < s->nshm_bufs; i++)
|
||||
if (s->shm_bufs[i].buffer) {
|
||||
wl.proxy_marshal(s->shm_bufs[i].buffer, WL_BUFFER_DESTROY);
|
||||
wl.proxy_destroy(s->shm_bufs[i].buffer);
|
||||
}
|
||||
free(s->shm_bufs);
|
||||
}
|
||||
if (s->shm_pool) {
|
||||
wl.proxy_marshal(s->shm_pool, WL_SHM_POOL_DESTROY);
|
||||
wl.proxy_destroy(s->shm_pool);
|
||||
}
|
||||
if (s->shm_map) munmap(s->shm_map, s->shm_size);
|
||||
if (s->dmabuf) {
|
||||
wl.proxy_marshal(s->dmabuf, DMABUF_DESTROY);
|
||||
wl.proxy_destroy(s->dmabuf);
|
||||
}
|
||||
if (s->shm) wl.proxy_destroy(s->shm);
|
||||
if (s->registry) wl.proxy_destroy(s->registry);
|
||||
if (s->surface_wrapper) wl.proxy_wrapper_destroy(s->surface_wrapper);
|
||||
if (s->display_wrapper) wl.proxy_wrapper_destroy(s->display_wrapper);
|
||||
if (s->display) wl.display_flush(s->display);
|
||||
if (s->queue) wl.event_queue_destroy(s->queue);
|
||||
free(s);
|
||||
}
|
||||
|
||||
static void wl_destroy(vkb_swapchain *sc)
|
||||
{
|
||||
wl_teardown(sc->bdata);
|
||||
sc->bdata = NULL;
|
||||
}
|
||||
|
||||
const vkb_wsi_backend vkb_wsi_wayland_backend = {wl_init, wl_present, wl_wait_free, wl_destroy};
|
||||
@@ -0,0 +1,162 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* X11 presentation, software path: the frame is read back and drawn with PutImage. Used only
|
||||
* when nothing better exists - under gamescope its WSI layer turns X11 swapchains into Wayland
|
||||
* ones before they reach this driver. libxcb (and libX11-xcb for Xlib surfaces) is opened at
|
||||
* run time.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "wsi.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <string.h>
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
static struct {
|
||||
void *lib, *x11xcb;
|
||||
xcb_get_geometry_cookie_t (*get_geometry)(xcb_connection_t *, xcb_drawable_t);
|
||||
xcb_get_geometry_reply_t *(*get_geometry_reply)(xcb_connection_t *, xcb_get_geometry_cookie_t, xcb_generic_error_t **);
|
||||
xcb_void_cookie_t (*put_image)(xcb_connection_t *, uint8_t, xcb_drawable_t, xcb_gcontext_t, uint16_t, uint16_t, int16_t, int16_t,
|
||||
uint8_t, uint8_t, uint32_t, const uint8_t *);
|
||||
xcb_void_cookie_t (*create_gc)(xcb_connection_t *, xcb_gcontext_t, xcb_drawable_t, uint32_t, const void *);
|
||||
xcb_void_cookie_t (*free_gc)(xcb_connection_t *, xcb_gcontext_t);
|
||||
uint32_t (*generate_id)(xcb_connection_t *);
|
||||
uint32_t (*get_maximum_request_length)(xcb_connection_t *);
|
||||
int (*flush)(xcb_connection_t *);
|
||||
int (*connection_has_error)(xcb_connection_t *);
|
||||
xcb_connection_t *(*xlib_get_xcb)(void *);
|
||||
} x;
|
||||
|
||||
int vkb_xcb_load(void)
|
||||
{
|
||||
static int state;
|
||||
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
pthread_mutex_lock(&lock);
|
||||
if (!state) {
|
||||
state = -1;
|
||||
x.lib = dlopen("libxcb.so.1", RTLD_NOW | RTLD_LOCAL);
|
||||
if (x.lib) {
|
||||
#define L(f, n) (*(void **)&x.f = dlsym(x.lib, n))
|
||||
int ok = 1;
|
||||
ok &= !!L(get_geometry, "xcb_get_geometry");
|
||||
ok &= !!L(get_geometry_reply, "xcb_get_geometry_reply");
|
||||
ok &= !!L(put_image, "xcb_put_image");
|
||||
ok &= !!L(create_gc, "xcb_create_gc");
|
||||
ok &= !!L(free_gc, "xcb_free_gc");
|
||||
ok &= !!L(generate_id, "xcb_generate_id");
|
||||
ok &= !!L(get_maximum_request_length, "xcb_get_maximum_request_length");
|
||||
ok &= !!L(flush, "xcb_flush");
|
||||
ok &= !!L(connection_has_error, "xcb_connection_has_error");
|
||||
#undef L
|
||||
if (ok) state = 1;
|
||||
}
|
||||
x.x11xcb = dlopen("libX11-xcb.so.1", RTLD_NOW | RTLD_LOCAL);
|
||||
if (x.x11xcb) *(void **)&x.xlib_get_xcb = dlsym(x.x11xcb, "XGetXCBConnection");
|
||||
if (state < 0) VKB_WARN("no usable libxcb: X11 presentation unavailable");
|
||||
}
|
||||
pthread_mutex_unlock(&lock);
|
||||
return state > 0;
|
||||
}
|
||||
|
||||
void *vkb_xlib_to_xcb(void *dpy)
|
||||
{
|
||||
if (!vkb_xcb_load() || !x.xlib_get_xcb) return NULL;
|
||||
return x.xlib_get_xcb(dpy);
|
||||
}
|
||||
|
||||
static int geometry(vkb_surface *s, uint32_t *w, uint32_t *h, uint8_t *depth)
|
||||
{
|
||||
xcb_get_geometry_reply_t *g = x.get_geometry_reply(s->xcb_conn, x.get_geometry(s->xcb_conn, s->xcb_window), NULL);
|
||||
if (!g) return 0;
|
||||
*w = g->width;
|
||||
*h = g->height;
|
||||
if (depth) *depth = g->depth;
|
||||
free(g);
|
||||
return 1;
|
||||
}
|
||||
|
||||
VkResult vkb_xcb_surface_extent(vkb_surface *s, VkExtent2D *e)
|
||||
{
|
||||
uint32_t w, h;
|
||||
if (!geometry(s, &w, &h, NULL)) return VK_ERROR_SURFACE_LOST_KHR;
|
||||
e->width = w;
|
||||
e->height = h;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
typedef struct x11_state {
|
||||
xcb_gcontext_t gc;
|
||||
uint8_t depth;
|
||||
uint8_t *swz;
|
||||
} x11_state;
|
||||
|
||||
static VkResult x11_init(vkb_swapchain *sc)
|
||||
{
|
||||
vkb_surface *s = sc->surface;
|
||||
x11_state *st = calloc(1, sizeof(*st));
|
||||
sc->bdata = st;
|
||||
uint32_t w, h;
|
||||
if (!geometry(s, &w, &h, &st->depth)) return VK_ERROR_SURFACE_LOST_KHR;
|
||||
if (st->depth != 24 && st->depth != 32) {
|
||||
VKB_ERR("X11: window depth %u is not supported by the software present path", st->depth);
|
||||
return VK_ERROR_SURFACE_LOST_KHR;
|
||||
}
|
||||
st->gc = x.generate_id(s->xcb_conn);
|
||||
x.create_gc(s->xcb_conn, st->gc, s->xcb_window, 0, NULL);
|
||||
VKB_INFO("X11: presenting %ux%u by PutImage (software path)", sc->info.imageExtent.width, sc->info.imageExtent.height);
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
static VkResult x11_present(vkb_swapchain *sc, uint32_t index, const VkPresentRegionKHR *region)
|
||||
{
|
||||
(void)index;
|
||||
(void)region;
|
||||
vkb_surface *s = sc->surface;
|
||||
x11_state *st = sc->bdata;
|
||||
if (x.connection_has_error(s->xcb_conn)) return VK_ERROR_SURFACE_LOST_KHR;
|
||||
uint32_t w = sc->info.imageExtent.width, h = sc->info.imageExtent.height;
|
||||
const uint8_t *src = sc->staging_ptr;
|
||||
int rgba = sc->info.imageFormat == VK_FORMAT_R8G8B8A8_UNORM || sc->info.imageFormat == VK_FORMAT_R8G8B8A8_SRGB;
|
||||
if (rgba) {
|
||||
if (!st->swz) st->swz = malloc((size_t)w * h * 4);
|
||||
for (size_t i = 0; i < (size_t)w * h; i++) {
|
||||
st->swz[i * 4 + 0] = src[i * 4 + 2];
|
||||
st->swz[i * 4 + 1] = src[i * 4 + 1];
|
||||
st->swz[i * 4 + 2] = src[i * 4 + 0];
|
||||
st->swz[i * 4 + 3] = src[i * 4 + 3];
|
||||
}
|
||||
src = st->swz;
|
||||
}
|
||||
/* Split into requests that fit the server's maximum request length (in 4-byte units). */
|
||||
size_t max = (size_t)x.get_maximum_request_length(s->xcb_conn) * 4;
|
||||
size_t row = (size_t)w * 4;
|
||||
uint32_t rows = max > 64 + row ? (uint32_t)((max - 64) / row) : 1;
|
||||
for (uint32_t y = 0; y < h; y += rows) {
|
||||
uint32_t n = h - y < rows ? h - y : rows;
|
||||
x.put_image(s->xcb_conn, XCB_IMAGE_FORMAT_Z_PIXMAP, s->xcb_window, st->gc, (uint16_t)w, (uint16_t)n, 0, (int16_t)y, 0, st->depth,
|
||||
(uint32_t)(row * n), src + row * y);
|
||||
}
|
||||
x.flush(s->xcb_conn);
|
||||
uint32_t cw, ch;
|
||||
if (geometry(s, &cw, &ch, NULL) && (cw != w || ch != h)) sc->status = VK_SUBOPTIMAL_KHR;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
static VkResult x11_wait_free(vkb_swapchain *sc, uint64_t timeout_ns)
|
||||
{
|
||||
(void)sc;
|
||||
(void)timeout_ns;
|
||||
return VK_TIMEOUT; /* images are free as soon as their present returns */
|
||||
}
|
||||
|
||||
static void x11_destroy(vkb_swapchain *sc)
|
||||
{
|
||||
x11_state *st = sc->bdata;
|
||||
if (!st) return;
|
||||
if (st->gc) x.free_gc(sc->surface->xcb_conn, st->gc);
|
||||
free(st->swz);
|
||||
free(st);
|
||||
sc->bdata = NULL;
|
||||
}
|
||||
|
||||
const vkb_wsi_backend vkb_wsi_x11_backend = {x11_init, x11_present, x11_wait_free, x11_destroy};
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,422 @@
|
||||
/* Generated by tools/vkbridge/codegen/gen_vkbridge.py from vk.xml 1.4.341 - do not edit. */
|
||||
|
||||
#ifndef VKB_CLIENT_GEN_H
|
||||
#define VKB_CLIENT_GEN_H
|
||||
#include "vkb_gen.h"
|
||||
VkResult vkb_wire_vkAllocateCommandBuffers(VkDevice device, const VkCommandBufferAllocateInfo* pAllocateInfo, VkCommandBuffer* pCommandBuffers);
|
||||
VkResult vkb_wire_vkAllocateDescriptorSets(VkDevice device, const VkDescriptorSetAllocateInfo* pAllocateInfo, VkDescriptorSet* pDescriptorSets);
|
||||
VkResult vkb_wire_vkAllocateMemory(VkDevice device, const VkMemoryAllocateInfo* pAllocateInfo, const VkAllocationCallbacks* pAllocator, VkDeviceMemory* pMemory);
|
||||
VkResult vkb_wire_vkBeginCommandBuffer(VkCommandBuffer commandBuffer, const VkCommandBufferBeginInfo* pBeginInfo);
|
||||
VkResult vkb_wire_vkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset);
|
||||
VkResult vkb_wire_vkBindBufferMemory2(VkDevice device, uint32_t bindInfoCount, const VkBindBufferMemoryInfo* pBindInfos);
|
||||
VkResult vkb_wire_vkBindImageMemory(VkDevice device, VkImage image, VkDeviceMemory memory, VkDeviceSize memoryOffset);
|
||||
VkResult vkb_wire_vkBindImageMemory2(VkDevice device, uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos);
|
||||
VkResult vkb_wire_vkBindTensorMemoryARM(VkDevice device, uint32_t bindInfoCount, const VkBindTensorMemoryInfoARM* pBindInfos);
|
||||
void vkb_wire_vkCmdBeginConditionalRenderingEXT(VkCommandBuffer commandBuffer, const VkConditionalRenderingBeginInfoEXT* pConditionalRenderingBegin);
|
||||
void vkb_wire_vkCmdBeginCustomResolveEXT(VkCommandBuffer commandBuffer, const VkBeginCustomResolveInfoEXT* pBeginCustomResolveInfo);
|
||||
void vkb_wire_vkCmdBeginQuery(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t query, VkQueryControlFlags flags);
|
||||
void vkb_wire_vkCmdBeginQueryIndexedEXT(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t query, VkQueryControlFlags flags, uint32_t index);
|
||||
void vkb_wire_vkCmdBeginRenderPass(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* pRenderPassBegin, VkSubpassContents contents);
|
||||
void vkb_wire_vkCmdBeginRenderPass2(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* pRenderPassBegin, const VkSubpassBeginInfo* pSubpassBeginInfo);
|
||||
void vkb_wire_vkCmdBeginRendering(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo);
|
||||
void vkb_wire_vkCmdBeginTransformFeedbackEXT(VkCommandBuffer commandBuffer, uint32_t firstCounterBuffer, uint32_t counterBufferCount, const VkBuffer* pCounterBuffers, const VkDeviceSize* pCounterBufferOffsets);
|
||||
void vkb_wire_vkCmdBindDescriptorSets(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipelineLayout layout, uint32_t firstSet, uint32_t descriptorSetCount, const VkDescriptorSet* pDescriptorSets, uint32_t dynamicOffsetCount, const uint32_t* pDynamicOffsets);
|
||||
void vkb_wire_vkCmdBindDescriptorSets2(VkCommandBuffer commandBuffer, const VkBindDescriptorSetsInfo* pBindDescriptorSetsInfo);
|
||||
void vkb_wire_vkCmdBindIndexBuffer(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkIndexType indexType);
|
||||
void vkb_wire_vkCmdBindIndexBuffer2(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size, VkIndexType indexType);
|
||||
void vkb_wire_vkCmdBindPipeline(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipeline pipeline);
|
||||
void vkb_wire_vkCmdBindTransformFeedbackBuffersEXT(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets, const VkDeviceSize* pSizes);
|
||||
void vkb_wire_vkCmdBindVertexBuffers(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets);
|
||||
void vkb_wire_vkCmdBindVertexBuffers2(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets, const VkDeviceSize* pSizes, const VkDeviceSize* pStrides);
|
||||
void vkb_wire_vkCmdBlitImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, const VkImageBlit* pRegions, VkFilter filter);
|
||||
void vkb_wire_vkCmdBlitImage2(VkCommandBuffer commandBuffer, const VkBlitImageInfo2* pBlitImageInfo);
|
||||
void vkb_wire_vkCmdClearAttachments(VkCommandBuffer commandBuffer, uint32_t attachmentCount, const VkClearAttachment* pAttachments, uint32_t rectCount, const VkClearRect* pRects);
|
||||
void vkb_wire_vkCmdClearColorImage(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout imageLayout, const VkClearColorValue* pColor, uint32_t rangeCount, const VkImageSubresourceRange* pRanges);
|
||||
void vkb_wire_vkCmdClearDepthStencilImage(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout imageLayout, const VkClearDepthStencilValue* pDepthStencil, uint32_t rangeCount, const VkImageSubresourceRange* pRanges);
|
||||
void vkb_wire_vkCmdCopyBuffer(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkBuffer dstBuffer, uint32_t regionCount, const VkBufferCopy* pRegions);
|
||||
void vkb_wire_vkCmdCopyBuffer2(VkCommandBuffer commandBuffer, const VkCopyBufferInfo2* pCopyBufferInfo);
|
||||
void vkb_wire_vkCmdCopyBufferToImage(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, const VkBufferImageCopy* pRegions);
|
||||
void vkb_wire_vkCmdCopyBufferToImage2(VkCommandBuffer commandBuffer, const VkCopyBufferToImageInfo2* pCopyBufferToImageInfo);
|
||||
void vkb_wire_vkCmdCopyImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, const VkImageCopy* pRegions);
|
||||
void vkb_wire_vkCmdCopyImage2(VkCommandBuffer commandBuffer, const VkCopyImageInfo2* pCopyImageInfo);
|
||||
void vkb_wire_vkCmdCopyImageToBuffer(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkBuffer dstBuffer, uint32_t regionCount, const VkBufferImageCopy* pRegions);
|
||||
void vkb_wire_vkCmdCopyImageToBuffer2(VkCommandBuffer commandBuffer, const VkCopyImageToBufferInfo2* pCopyImageToBufferInfo);
|
||||
void vkb_wire_vkCmdCopyQueryPoolResults(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount, VkBuffer dstBuffer, VkDeviceSize dstOffset, VkDeviceSize stride, VkQueryResultFlags flags);
|
||||
void vkb_wire_vkCmdCopyTensorARM(VkCommandBuffer commandBuffer, const VkCopyTensorInfoARM* pCopyTensorInfo);
|
||||
void vkb_wire_vkCmdDispatch(VkCommandBuffer commandBuffer, uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ);
|
||||
void vkb_wire_vkCmdDispatchBase(VkCommandBuffer commandBuffer, uint32_t baseGroupX, uint32_t baseGroupY, uint32_t baseGroupZ, uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ);
|
||||
void vkb_wire_vkCmdDispatchIndirect(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset);
|
||||
void vkb_wire_vkCmdDraw(VkCommandBuffer commandBuffer, uint32_t vertexCount, uint32_t instanceCount, uint32_t firstVertex, uint32_t firstInstance);
|
||||
void vkb_wire_vkCmdDrawIndexed(VkCommandBuffer commandBuffer, uint32_t indexCount, uint32_t instanceCount, uint32_t firstIndex, int32_t vertexOffset, uint32_t firstInstance);
|
||||
void vkb_wire_vkCmdDrawIndexedIndirect(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, uint32_t drawCount, uint32_t stride);
|
||||
void vkb_wire_vkCmdDrawIndexedIndirectCount(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkBuffer countBuffer, VkDeviceSize countBufferOffset, uint32_t maxDrawCount, uint32_t stride);
|
||||
void vkb_wire_vkCmdDrawIndirect(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, uint32_t drawCount, uint32_t stride);
|
||||
void vkb_wire_vkCmdDrawIndirectByteCountEXT(VkCommandBuffer commandBuffer, uint32_t instanceCount, uint32_t firstInstance, VkBuffer counterBuffer, VkDeviceSize counterBufferOffset, uint32_t counterOffset, uint32_t vertexStride);
|
||||
void vkb_wire_vkCmdDrawIndirectCount(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkBuffer countBuffer, VkDeviceSize countBufferOffset, uint32_t maxDrawCount, uint32_t stride);
|
||||
void vkb_wire_vkCmdDrawMultiEXT(VkCommandBuffer commandBuffer, uint32_t drawCount, const VkMultiDrawInfoEXT* pVertexInfo, uint32_t instanceCount, uint32_t firstInstance, uint32_t stride);
|
||||
void vkb_wire_vkCmdDrawMultiIndexedEXT(VkCommandBuffer commandBuffer, uint32_t drawCount, const VkMultiDrawIndexedInfoEXT* pIndexInfo, uint32_t instanceCount, uint32_t firstInstance, uint32_t stride, const int32_t* pVertexOffset);
|
||||
void vkb_wire_vkCmdEndConditionalRenderingEXT(VkCommandBuffer commandBuffer);
|
||||
void vkb_wire_vkCmdEndQuery(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t query);
|
||||
void vkb_wire_vkCmdEndQueryIndexedEXT(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t query, uint32_t index);
|
||||
void vkb_wire_vkCmdEndRenderPass(VkCommandBuffer commandBuffer);
|
||||
void vkb_wire_vkCmdEndRenderPass2(VkCommandBuffer commandBuffer, const VkSubpassEndInfo* pSubpassEndInfo);
|
||||
void vkb_wire_vkCmdEndRendering(VkCommandBuffer commandBuffer);
|
||||
void vkb_wire_vkCmdEndRendering2KHR(VkCommandBuffer commandBuffer, const VkRenderingEndInfoKHR* pRenderingEndInfo);
|
||||
void vkb_wire_vkCmdEndTransformFeedbackEXT(VkCommandBuffer commandBuffer, uint32_t firstCounterBuffer, uint32_t counterBufferCount, const VkBuffer* pCounterBuffers, const VkDeviceSize* pCounterBufferOffsets);
|
||||
void vkb_wire_vkCmdExecuteCommands(VkCommandBuffer commandBuffer, uint32_t commandBufferCount, const VkCommandBuffer* pCommandBuffers);
|
||||
void vkb_wire_vkCmdFillBuffer(VkCommandBuffer commandBuffer, VkBuffer dstBuffer, VkDeviceSize dstOffset, VkDeviceSize size, uint32_t data);
|
||||
void vkb_wire_vkCmdNextSubpass(VkCommandBuffer commandBuffer, VkSubpassContents contents);
|
||||
void vkb_wire_vkCmdNextSubpass2(VkCommandBuffer commandBuffer, const VkSubpassBeginInfo* pSubpassBeginInfo, const VkSubpassEndInfo* pSubpassEndInfo);
|
||||
void vkb_wire_vkCmdPipelineBarrier(VkCommandBuffer commandBuffer, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkDependencyFlags dependencyFlags, uint32_t memoryBarrierCount, const VkMemoryBarrier* pMemoryBarriers, uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier* pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier* pImageMemoryBarriers);
|
||||
void vkb_wire_vkCmdPipelineBarrier2(VkCommandBuffer commandBuffer, const VkDependencyInfo* pDependencyInfo);
|
||||
void vkb_wire_vkCmdPushConstants(VkCommandBuffer commandBuffer, VkPipelineLayout layout, VkShaderStageFlags stageFlags, uint32_t offset, uint32_t size, const void* pValues);
|
||||
void vkb_wire_vkCmdPushConstants2(VkCommandBuffer commandBuffer, const VkPushConstantsInfo* pPushConstantsInfo);
|
||||
void vkb_wire_vkCmdPushDescriptorSet(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipelineLayout layout, uint32_t set, uint32_t descriptorWriteCount, const VkWriteDescriptorSet* pDescriptorWrites);
|
||||
void vkb_wire_vkCmdPushDescriptorSet2(VkCommandBuffer commandBuffer, const VkPushDescriptorSetInfo* pPushDescriptorSetInfo);
|
||||
void vkb_wire_vkCmdResetEvent(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags stageMask);
|
||||
void vkb_wire_vkCmdResetEvent2(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags2 stageMask);
|
||||
void vkb_wire_vkCmdResetQueryPool(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount);
|
||||
void vkb_wire_vkCmdResolveImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, const VkImageResolve* pRegions);
|
||||
void vkb_wire_vkCmdResolveImage2(VkCommandBuffer commandBuffer, const VkResolveImageInfo2* pResolveImageInfo);
|
||||
void vkb_wire_vkCmdSetAlphaToCoverageEnableEXT(VkCommandBuffer commandBuffer, VkBool32 alphaToCoverageEnable);
|
||||
void vkb_wire_vkCmdSetAlphaToOneEnableEXT(VkCommandBuffer commandBuffer, VkBool32 alphaToOneEnable);
|
||||
void vkb_wire_vkCmdSetAttachmentFeedbackLoopEnableEXT(VkCommandBuffer commandBuffer, VkImageAspectFlags aspectMask);
|
||||
void vkb_wire_vkCmdSetBlendConstants(VkCommandBuffer commandBuffer, const float blendConstants[4]);
|
||||
void vkb_wire_vkCmdSetColorBlendAdvancedEXT(VkCommandBuffer commandBuffer, uint32_t firstAttachment, uint32_t attachmentCount, const VkColorBlendAdvancedEXT* pColorBlendAdvanced);
|
||||
void vkb_wire_vkCmdSetColorBlendEnableEXT(VkCommandBuffer commandBuffer, uint32_t firstAttachment, uint32_t attachmentCount, const VkBool32* pColorBlendEnables);
|
||||
void vkb_wire_vkCmdSetColorBlendEquationEXT(VkCommandBuffer commandBuffer, uint32_t firstAttachment, uint32_t attachmentCount, const VkColorBlendEquationEXT* pColorBlendEquations);
|
||||
void vkb_wire_vkCmdSetColorWriteEnableEXT(VkCommandBuffer commandBuffer, uint32_t attachmentCount, const VkBool32* pColorWriteEnables);
|
||||
void vkb_wire_vkCmdSetColorWriteMaskEXT(VkCommandBuffer commandBuffer, uint32_t firstAttachment, uint32_t attachmentCount, const VkColorComponentFlags* pColorWriteMasks);
|
||||
void vkb_wire_vkCmdSetConservativeRasterizationModeEXT(VkCommandBuffer commandBuffer, VkConservativeRasterizationModeEXT conservativeRasterizationMode);
|
||||
void vkb_wire_vkCmdSetCullMode(VkCommandBuffer commandBuffer, VkCullModeFlags cullMode);
|
||||
void vkb_wire_vkCmdSetDepthBias(VkCommandBuffer commandBuffer, float depthBiasConstantFactor, float depthBiasClamp, float depthBiasSlopeFactor);
|
||||
void vkb_wire_vkCmdSetDepthBias2EXT(VkCommandBuffer commandBuffer, const VkDepthBiasInfoEXT* pDepthBiasInfo);
|
||||
void vkb_wire_vkCmdSetDepthBiasEnable(VkCommandBuffer commandBuffer, VkBool32 depthBiasEnable);
|
||||
void vkb_wire_vkCmdSetDepthBounds(VkCommandBuffer commandBuffer, float minDepthBounds, float maxDepthBounds);
|
||||
void vkb_wire_vkCmdSetDepthBoundsTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthBoundsTestEnable);
|
||||
void vkb_wire_vkCmdSetDepthClampEnableEXT(VkCommandBuffer commandBuffer, VkBool32 depthClampEnable);
|
||||
void vkb_wire_vkCmdSetDepthClampRangeEXT(VkCommandBuffer commandBuffer, VkDepthClampModeEXT depthClampMode, const VkDepthClampRangeEXT* pDepthClampRange);
|
||||
void vkb_wire_vkCmdSetDepthClipEnableEXT(VkCommandBuffer commandBuffer, VkBool32 depthClipEnable);
|
||||
void vkb_wire_vkCmdSetDepthClipNegativeOneToOneEXT(VkCommandBuffer commandBuffer, VkBool32 negativeOneToOne);
|
||||
void vkb_wire_vkCmdSetDepthCompareOp(VkCommandBuffer commandBuffer, VkCompareOp depthCompareOp);
|
||||
void vkb_wire_vkCmdSetDepthTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthTestEnable);
|
||||
void vkb_wire_vkCmdSetDepthWriteEnable(VkCommandBuffer commandBuffer, VkBool32 depthWriteEnable);
|
||||
void vkb_wire_vkCmdSetDeviceMask(VkCommandBuffer commandBuffer, uint32_t deviceMask);
|
||||
void vkb_wire_vkCmdSetDiscardRectangleEXT(VkCommandBuffer commandBuffer, uint32_t firstDiscardRectangle, uint32_t discardRectangleCount, const VkRect2D* pDiscardRectangles);
|
||||
void vkb_wire_vkCmdSetDiscardRectangleEnableEXT(VkCommandBuffer commandBuffer, VkBool32 discardRectangleEnable);
|
||||
void vkb_wire_vkCmdSetDiscardRectangleModeEXT(VkCommandBuffer commandBuffer, VkDiscardRectangleModeEXT discardRectangleMode);
|
||||
void vkb_wire_vkCmdSetEvent(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags stageMask);
|
||||
void vkb_wire_vkCmdSetEvent2(VkCommandBuffer commandBuffer, VkEvent event, const VkDependencyInfo* pDependencyInfo);
|
||||
void vkb_wire_vkCmdSetExtraPrimitiveOverestimationSizeEXT(VkCommandBuffer commandBuffer, float extraPrimitiveOverestimationSize);
|
||||
void vkb_wire_vkCmdSetFragmentShadingRateKHR(VkCommandBuffer commandBuffer, const VkExtent2D* pFragmentSize, const VkFragmentShadingRateCombinerOpKHR combinerOps[2]);
|
||||
void vkb_wire_vkCmdSetFrontFace(VkCommandBuffer commandBuffer, VkFrontFace frontFace);
|
||||
void vkb_wire_vkCmdSetLineRasterizationModeEXT(VkCommandBuffer commandBuffer, VkLineRasterizationModeEXT lineRasterizationMode);
|
||||
void vkb_wire_vkCmdSetLineStipple(VkCommandBuffer commandBuffer, uint32_t lineStippleFactor, uint16_t lineStipplePattern);
|
||||
void vkb_wire_vkCmdSetLineStippleEnableEXT(VkCommandBuffer commandBuffer, VkBool32 stippledLineEnable);
|
||||
void vkb_wire_vkCmdSetLineWidth(VkCommandBuffer commandBuffer, float lineWidth);
|
||||
void vkb_wire_vkCmdSetLogicOpEXT(VkCommandBuffer commandBuffer, VkLogicOp logicOp);
|
||||
void vkb_wire_vkCmdSetLogicOpEnableEXT(VkCommandBuffer commandBuffer, VkBool32 logicOpEnable);
|
||||
void vkb_wire_vkCmdSetPatchControlPointsEXT(VkCommandBuffer commandBuffer, uint32_t patchControlPoints);
|
||||
void vkb_wire_vkCmdSetPolygonModeEXT(VkCommandBuffer commandBuffer, VkPolygonMode polygonMode);
|
||||
void vkb_wire_vkCmdSetPrimitiveRestartEnable(VkCommandBuffer commandBuffer, VkBool32 primitiveRestartEnable);
|
||||
void vkb_wire_vkCmdSetPrimitiveTopology(VkCommandBuffer commandBuffer, VkPrimitiveTopology primitiveTopology);
|
||||
void vkb_wire_vkCmdSetProvokingVertexModeEXT(VkCommandBuffer commandBuffer, VkProvokingVertexModeEXT provokingVertexMode);
|
||||
void vkb_wire_vkCmdSetRasterizationSamplesEXT(VkCommandBuffer commandBuffer, VkSampleCountFlagBits rasterizationSamples);
|
||||
void vkb_wire_vkCmdSetRasterizationStreamEXT(VkCommandBuffer commandBuffer, uint32_t rasterizationStream);
|
||||
void vkb_wire_vkCmdSetRasterizerDiscardEnable(VkCommandBuffer commandBuffer, VkBool32 rasterizerDiscardEnable);
|
||||
void vkb_wire_vkCmdSetRenderingAttachmentLocations(VkCommandBuffer commandBuffer, const VkRenderingAttachmentLocationInfo* pLocationInfo);
|
||||
void vkb_wire_vkCmdSetRenderingInputAttachmentIndices(VkCommandBuffer commandBuffer, const VkRenderingInputAttachmentIndexInfo* pInputAttachmentIndexInfo);
|
||||
void vkb_wire_vkCmdSetSampleLocationsEXT(VkCommandBuffer commandBuffer, const VkSampleLocationsInfoEXT* pSampleLocationsInfo);
|
||||
void vkb_wire_vkCmdSetSampleLocationsEnableEXT(VkCommandBuffer commandBuffer, VkBool32 sampleLocationsEnable);
|
||||
void vkb_wire_vkCmdSetSampleMaskEXT(VkCommandBuffer commandBuffer, VkSampleCountFlagBits samples, const VkSampleMask* pSampleMask);
|
||||
void vkb_wire_vkCmdSetScissor(VkCommandBuffer commandBuffer, uint32_t firstScissor, uint32_t scissorCount, const VkRect2D* pScissors);
|
||||
void vkb_wire_vkCmdSetScissorWithCount(VkCommandBuffer commandBuffer, uint32_t scissorCount, const VkRect2D* pScissors);
|
||||
void vkb_wire_vkCmdSetStencilCompareMask(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, uint32_t compareMask);
|
||||
void vkb_wire_vkCmdSetStencilOp(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, VkStencilOp failOp, VkStencilOp passOp, VkStencilOp depthFailOp, VkCompareOp compareOp);
|
||||
void vkb_wire_vkCmdSetStencilReference(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, uint32_t reference);
|
||||
void vkb_wire_vkCmdSetStencilTestEnable(VkCommandBuffer commandBuffer, VkBool32 stencilTestEnable);
|
||||
void vkb_wire_vkCmdSetStencilWriteMask(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, uint32_t writeMask);
|
||||
void vkb_wire_vkCmdSetTessellationDomainOriginEXT(VkCommandBuffer commandBuffer, VkTessellationDomainOrigin domainOrigin);
|
||||
void vkb_wire_vkCmdSetVertexInputEXT(VkCommandBuffer commandBuffer, uint32_t vertexBindingDescriptionCount, const VkVertexInputBindingDescription2EXT* pVertexBindingDescriptions, uint32_t vertexAttributeDescriptionCount, const VkVertexInputAttributeDescription2EXT* pVertexAttributeDescriptions);
|
||||
void vkb_wire_vkCmdSetViewport(VkCommandBuffer commandBuffer, uint32_t firstViewport, uint32_t viewportCount, const VkViewport* pViewports);
|
||||
void vkb_wire_vkCmdSetViewportWithCount(VkCommandBuffer commandBuffer, uint32_t viewportCount, const VkViewport* pViewports);
|
||||
void vkb_wire_vkCmdUpdateBuffer(VkCommandBuffer commandBuffer, VkBuffer dstBuffer, VkDeviceSize dstOffset, VkDeviceSize dataSize, const void* pData);
|
||||
void vkb_wire_vkCmdWaitEvents(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, uint32_t memoryBarrierCount, const VkMemoryBarrier* pMemoryBarriers, uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier* pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier* pImageMemoryBarriers);
|
||||
void vkb_wire_vkCmdWaitEvents2(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents, const VkDependencyInfo* pDependencyInfos);
|
||||
void vkb_wire_vkCmdWriteTimestamp(VkCommandBuffer commandBuffer, VkPipelineStageFlagBits pipelineStage, VkQueryPool queryPool, uint32_t query);
|
||||
void vkb_wire_vkCmdWriteTimestamp2(VkCommandBuffer commandBuffer, VkPipelineStageFlags2 stage, VkQueryPool queryPool, uint32_t query);
|
||||
VkResult vkb_wire_vkCreateBuffer(VkDevice device, const VkBufferCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkBuffer* pBuffer);
|
||||
VkResult vkb_wire_vkCreateBufferView(VkDevice device, const VkBufferViewCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkBufferView* pView);
|
||||
VkResult vkb_wire_vkCreateCommandPool(VkDevice device, const VkCommandPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkCommandPool* pCommandPool);
|
||||
VkResult vkb_wire_vkCreateComputePipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount, const VkComputePipelineCreateInfo* pCreateInfos, const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines);
|
||||
VkResult vkb_wire_vkCreateDescriptorPool(VkDevice device, const VkDescriptorPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDescriptorPool* pDescriptorPool);
|
||||
VkResult vkb_wire_vkCreateDescriptorSetLayout(VkDevice device, const VkDescriptorSetLayoutCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDescriptorSetLayout* pSetLayout);
|
||||
VkResult vkb_wire_vkCreateDescriptorUpdateTemplate(VkDevice device, const VkDescriptorUpdateTemplateCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDescriptorUpdateTemplate* pDescriptorUpdateTemplate);
|
||||
VkResult vkb_wire_vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDevice* pDevice);
|
||||
VkResult vkb_wire_vkCreateEvent(VkDevice device, const VkEventCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkEvent* pEvent);
|
||||
VkResult vkb_wire_vkCreateFence(VkDevice device, const VkFenceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkFence* pFence);
|
||||
VkResult vkb_wire_vkCreateFramebuffer(VkDevice device, const VkFramebufferCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkFramebuffer* pFramebuffer);
|
||||
VkResult vkb_wire_vkCreateGraphicsPipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount, const VkGraphicsPipelineCreateInfo* pCreateInfos, const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines);
|
||||
VkResult vkb_wire_vkCreateImage(VkDevice device, const VkImageCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkImage* pImage);
|
||||
VkResult vkb_wire_vkCreateImageView(VkDevice device, const VkImageViewCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkImageView* pView);
|
||||
VkResult vkb_wire_vkCreateInstance(const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkInstance* pInstance);
|
||||
VkResult vkb_wire_vkCreatePipelineCache(VkDevice device, const VkPipelineCacheCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkPipelineCache* pPipelineCache);
|
||||
VkResult vkb_wire_vkCreatePipelineLayout(VkDevice device, const VkPipelineLayoutCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkPipelineLayout* pPipelineLayout);
|
||||
VkResult vkb_wire_vkCreateQueryPool(VkDevice device, const VkQueryPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkQueryPool* pQueryPool);
|
||||
VkResult vkb_wire_vkCreateRenderPass(VkDevice device, const VkRenderPassCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkRenderPass* pRenderPass);
|
||||
VkResult vkb_wire_vkCreateRenderPass2(VkDevice device, const VkRenderPassCreateInfo2* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkRenderPass* pRenderPass);
|
||||
VkResult vkb_wire_vkCreateSampler(VkDevice device, const VkSamplerCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSampler* pSampler);
|
||||
VkResult vkb_wire_vkCreateSamplerYcbcrConversion(VkDevice device, const VkSamplerYcbcrConversionCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSamplerYcbcrConversion* pYcbcrConversion);
|
||||
VkResult vkb_wire_vkCreateSemaphore(VkDevice device, const VkSemaphoreCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSemaphore* pSemaphore);
|
||||
VkResult vkb_wire_vkCreateShaderModule(VkDevice device, const VkShaderModuleCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkShaderModule* pShaderModule);
|
||||
VkResult vkb_wire_vkCreateTensorARM(VkDevice device, const VkTensorCreateInfoARM* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkTensorARM* pTensor);
|
||||
VkResult vkb_wire_vkCreateTensorViewARM(VkDevice device, const VkTensorViewCreateInfoARM* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkTensorViewARM* pView);
|
||||
void vkb_wire_vkDestroyBuffer(VkDevice device, VkBuffer buffer, const VkAllocationCallbacks* pAllocator);
|
||||
void vkb_wire_vkDestroyBufferView(VkDevice device, VkBufferView bufferView, const VkAllocationCallbacks* pAllocator);
|
||||
void vkb_wire_vkDestroyCommandPool(VkDevice device, VkCommandPool commandPool, const VkAllocationCallbacks* pAllocator);
|
||||
void vkb_wire_vkDestroyDescriptorPool(VkDevice device, VkDescriptorPool descriptorPool, const VkAllocationCallbacks* pAllocator);
|
||||
void vkb_wire_vkDestroyDescriptorSetLayout(VkDevice device, VkDescriptorSetLayout descriptorSetLayout, const VkAllocationCallbacks* pAllocator);
|
||||
void vkb_wire_vkDestroyDescriptorUpdateTemplate(VkDevice device, VkDescriptorUpdateTemplate descriptorUpdateTemplate, const VkAllocationCallbacks* pAllocator);
|
||||
void vkb_wire_vkDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator);
|
||||
void vkb_wire_vkDestroyEvent(VkDevice device, VkEvent event, const VkAllocationCallbacks* pAllocator);
|
||||
void vkb_wire_vkDestroyFence(VkDevice device, VkFence fence, const VkAllocationCallbacks* pAllocator);
|
||||
void vkb_wire_vkDestroyFramebuffer(VkDevice device, VkFramebuffer framebuffer, const VkAllocationCallbacks* pAllocator);
|
||||
void vkb_wire_vkDestroyImage(VkDevice device, VkImage image, const VkAllocationCallbacks* pAllocator);
|
||||
void vkb_wire_vkDestroyImageView(VkDevice device, VkImageView imageView, const VkAllocationCallbacks* pAllocator);
|
||||
void vkb_wire_vkDestroyInstance(VkInstance instance, const VkAllocationCallbacks* pAllocator);
|
||||
void vkb_wire_vkDestroyPipeline(VkDevice device, VkPipeline pipeline, const VkAllocationCallbacks* pAllocator);
|
||||
void vkb_wire_vkDestroyPipelineCache(VkDevice device, VkPipelineCache pipelineCache, const VkAllocationCallbacks* pAllocator);
|
||||
void vkb_wire_vkDestroyPipelineLayout(VkDevice device, VkPipelineLayout pipelineLayout, const VkAllocationCallbacks* pAllocator);
|
||||
void vkb_wire_vkDestroyQueryPool(VkDevice device, VkQueryPool queryPool, const VkAllocationCallbacks* pAllocator);
|
||||
void vkb_wire_vkDestroyRenderPass(VkDevice device, VkRenderPass renderPass, const VkAllocationCallbacks* pAllocator);
|
||||
void vkb_wire_vkDestroySampler(VkDevice device, VkSampler sampler, const VkAllocationCallbacks* pAllocator);
|
||||
void vkb_wire_vkDestroySamplerYcbcrConversion(VkDevice device, VkSamplerYcbcrConversion ycbcrConversion, const VkAllocationCallbacks* pAllocator);
|
||||
void vkb_wire_vkDestroySemaphore(VkDevice device, VkSemaphore semaphore, const VkAllocationCallbacks* pAllocator);
|
||||
void vkb_wire_vkDestroyShaderModule(VkDevice device, VkShaderModule shaderModule, const VkAllocationCallbacks* pAllocator);
|
||||
void vkb_wire_vkDestroyTensorARM(VkDevice device, VkTensorARM tensor, const VkAllocationCallbacks* pAllocator);
|
||||
void vkb_wire_vkDestroyTensorViewARM(VkDevice device, VkTensorViewARM tensorView, const VkAllocationCallbacks* pAllocator);
|
||||
VkResult vkb_wire_vkDeviceWaitIdle(VkDevice device);
|
||||
VkResult vkb_wire_vkEndCommandBuffer(VkCommandBuffer commandBuffer);
|
||||
VkResult vkb_wire_vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, const char* pLayerName, uint32_t* pPropertyCount, VkExtensionProperties* pProperties);
|
||||
VkResult vkb_wire_vkEnumerateInstanceExtensionProperties(const char* pLayerName, uint32_t* pPropertyCount, VkExtensionProperties* pProperties);
|
||||
VkResult vkb_wire_vkEnumerateInstanceLayerProperties(uint32_t* pPropertyCount, VkLayerProperties* pProperties);
|
||||
VkResult vkb_wire_vkEnumerateInstanceVersion(uint32_t* pApiVersion);
|
||||
VkResult vkb_wire_vkEnumeratePhysicalDeviceGroups(VkInstance instance, uint32_t* pPhysicalDeviceGroupCount, VkPhysicalDeviceGroupProperties* pPhysicalDeviceGroupProperties);
|
||||
VkResult vkb_wire_vkEnumeratePhysicalDeviceQueueFamilyPerformanceCountersByRegionARM(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, uint32_t* pCounterCount, VkPerformanceCounterARM* pCounters, VkPerformanceCounterDescriptionARM* pCounterDescriptions);
|
||||
VkResult vkb_wire_vkEnumeratePhysicalDevices(VkInstance instance, uint32_t* pPhysicalDeviceCount, VkPhysicalDevice* pPhysicalDevices);
|
||||
VkResult vkb_wire_vkFlushMappedMemoryRanges(VkDevice device, uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges);
|
||||
void vkb_wire_vkFreeCommandBuffers(VkDevice device, VkCommandPool commandPool, uint32_t commandBufferCount, const VkCommandBuffer* pCommandBuffers);
|
||||
VkResult vkb_wire_vkFreeDescriptorSets(VkDevice device, VkDescriptorPool descriptorPool, uint32_t descriptorSetCount, const VkDescriptorSet* pDescriptorSets);
|
||||
void vkb_wire_vkFreeMemory(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks* pAllocator);
|
||||
VkDeviceAddress vkb_wire_vkGetBufferDeviceAddress(VkDevice device, const VkBufferDeviceAddressInfo* pInfo);
|
||||
void vkb_wire_vkGetBufferMemoryRequirements(VkDevice device, VkBuffer buffer, VkMemoryRequirements* pMemoryRequirements);
|
||||
void vkb_wire_vkGetBufferMemoryRequirements2(VkDevice device, const VkBufferMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements);
|
||||
uint64_t vkb_wire_vkGetBufferOpaqueCaptureAddress(VkDevice device, const VkBufferDeviceAddressInfo* pInfo);
|
||||
VkResult vkb_wire_vkGetCalibratedTimestampsKHR(VkDevice device, uint32_t timestampCount, const VkCalibratedTimestampInfoKHR* pTimestampInfos, uint64_t* pTimestamps, uint64_t* pMaxDeviation);
|
||||
void vkb_wire_vkGetDescriptorSetLayoutSupport(VkDevice device, const VkDescriptorSetLayoutCreateInfo* pCreateInfo, VkDescriptorSetLayoutSupport* pSupport);
|
||||
void vkb_wire_vkGetDeviceBufferMemoryRequirements(VkDevice device, const VkDeviceBufferMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements);
|
||||
void vkb_wire_vkGetDeviceGroupPeerMemoryFeatures(VkDevice device, uint32_t heapIndex, uint32_t localDeviceIndex, uint32_t remoteDeviceIndex, VkPeerMemoryFeatureFlags* pPeerMemoryFeatures);
|
||||
void vkb_wire_vkGetDeviceImageMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements);
|
||||
void vkb_wire_vkGetDeviceImageSparseMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements);
|
||||
void vkb_wire_vkGetDeviceImageSubresourceLayout(VkDevice device, const VkDeviceImageSubresourceInfo* pInfo, VkSubresourceLayout2* pLayout);
|
||||
void vkb_wire_vkGetDeviceMemoryCommitment(VkDevice device, VkDeviceMemory memory, VkDeviceSize* pCommittedMemoryInBytes);
|
||||
uint64_t vkb_wire_vkGetDeviceMemoryOpaqueCaptureAddress(VkDevice device, const VkDeviceMemoryOpaqueCaptureAddressInfo* pInfo);
|
||||
void vkb_wire_vkGetDeviceQueue(VkDevice device, uint32_t queueFamilyIndex, uint32_t queueIndex, VkQueue* pQueue);
|
||||
void vkb_wire_vkGetDeviceQueue2(VkDevice device, const VkDeviceQueueInfo2* pQueueInfo, VkQueue* pQueue);
|
||||
void vkb_wire_vkGetDeviceTensorMemoryRequirementsARM(VkDevice device, const VkDeviceTensorMemoryRequirementsARM* pInfo, VkMemoryRequirements2* pMemoryRequirements);
|
||||
VkResult vkb_wire_vkGetEventStatus(VkDevice device, VkEvent event);
|
||||
VkResult vkb_wire_vkGetFenceFdKHR(VkDevice device, const VkFenceGetFdInfoKHR* pGetFdInfo, int* pFd);
|
||||
VkResult vkb_wire_vkGetFenceStatus(VkDevice device, VkFence fence);
|
||||
VkResult vkb_wire_vkGetImageDrmFormatModifierPropertiesEXT(VkDevice device, VkImage image, VkImageDrmFormatModifierPropertiesEXT* pProperties);
|
||||
void vkb_wire_vkGetImageMemoryRequirements(VkDevice device, VkImage image, VkMemoryRequirements* pMemoryRequirements);
|
||||
void vkb_wire_vkGetImageMemoryRequirements2(VkDevice device, const VkImageMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements);
|
||||
void vkb_wire_vkGetImageSparseMemoryRequirements(VkDevice device, VkImage image, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements* pSparseMemoryRequirements);
|
||||
void vkb_wire_vkGetImageSparseMemoryRequirements2(VkDevice device, const VkImageSparseMemoryRequirementsInfo2* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements);
|
||||
void vkb_wire_vkGetImageSubresourceLayout(VkDevice device, VkImage image, const VkImageSubresource* pSubresource, VkSubresourceLayout* pLayout);
|
||||
void vkb_wire_vkGetImageSubresourceLayout2(VkDevice device, VkImage image, const VkImageSubresource2* pSubresource, VkSubresourceLayout2* pLayout);
|
||||
VkResult vkb_wire_vkGetMemoryFdKHR(VkDevice device, const VkMemoryGetFdInfoKHR* pGetFdInfo, int* pFd);
|
||||
VkResult vkb_wire_vkGetMemoryFdPropertiesKHR(VkDevice device, VkExternalMemoryHandleTypeFlagBits handleType, int fd, VkMemoryFdPropertiesKHR* pMemoryFdProperties);
|
||||
VkResult vkb_wire_vkGetPhysicalDeviceCalibrateableTimeDomainsKHR(VkPhysicalDevice physicalDevice, uint32_t* pTimeDomainCount, VkTimeDomainKHR* pTimeDomains);
|
||||
VkResult vkb_wire_vkGetPhysicalDeviceCooperativeMatrixPropertiesKHR(VkPhysicalDevice physicalDevice, uint32_t* pPropertyCount, VkCooperativeMatrixPropertiesKHR* pProperties);
|
||||
void vkb_wire_vkGetPhysicalDeviceExternalBufferProperties(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalBufferInfo* pExternalBufferInfo, VkExternalBufferProperties* pExternalBufferProperties);
|
||||
void vkb_wire_vkGetPhysicalDeviceExternalFenceProperties(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalFenceInfo* pExternalFenceInfo, VkExternalFenceProperties* pExternalFenceProperties);
|
||||
void vkb_wire_vkGetPhysicalDeviceExternalSemaphoreProperties(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalSemaphoreInfo* pExternalSemaphoreInfo, VkExternalSemaphoreProperties* pExternalSemaphoreProperties);
|
||||
void vkb_wire_vkGetPhysicalDeviceExternalTensorPropertiesARM(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalTensorInfoARM* pExternalTensorInfo, VkExternalTensorPropertiesARM* pExternalTensorProperties);
|
||||
void vkb_wire_vkGetPhysicalDeviceFeatures(VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures* pFeatures);
|
||||
void vkb_wire_vkGetPhysicalDeviceFeatures2(VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures2* pFeatures);
|
||||
void vkb_wire_vkGetPhysicalDeviceFormatProperties(VkPhysicalDevice physicalDevice, VkFormat format, VkFormatProperties* pFormatProperties);
|
||||
void vkb_wire_vkGetPhysicalDeviceFormatProperties2(VkPhysicalDevice physicalDevice, VkFormat format, VkFormatProperties2* pFormatProperties);
|
||||
VkResult vkb_wire_vkGetPhysicalDeviceFragmentShadingRatesKHR(VkPhysicalDevice physicalDevice, uint32_t* pFragmentShadingRateCount, VkPhysicalDeviceFragmentShadingRateKHR* pFragmentShadingRates);
|
||||
VkResult vkb_wire_vkGetPhysicalDeviceImageFormatProperties(VkPhysicalDevice physicalDevice, VkFormat format, VkImageType type, VkImageTiling tiling, VkImageUsageFlags usage, VkImageCreateFlags flags, VkImageFormatProperties* pImageFormatProperties);
|
||||
VkResult vkb_wire_vkGetPhysicalDeviceImageFormatProperties2(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceImageFormatInfo2* pImageFormatInfo, VkImageFormatProperties2* pImageFormatProperties);
|
||||
void vkb_wire_vkGetPhysicalDeviceMemoryProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties* pMemoryProperties);
|
||||
void vkb_wire_vkGetPhysicalDeviceMemoryProperties2(VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties2* pMemoryProperties);
|
||||
void vkb_wire_vkGetPhysicalDeviceMultisamplePropertiesEXT(VkPhysicalDevice physicalDevice, VkSampleCountFlagBits samples, VkMultisamplePropertiesEXT* pMultisampleProperties);
|
||||
void vkb_wire_vkGetPhysicalDeviceProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties* pProperties);
|
||||
void vkb_wire_vkGetPhysicalDeviceProperties2(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties2* pProperties);
|
||||
void vkb_wire_vkGetPhysicalDeviceQueueFamilyProperties(VkPhysicalDevice physicalDevice, uint32_t* pQueueFamilyPropertyCount, VkQueueFamilyProperties* pQueueFamilyProperties);
|
||||
void vkb_wire_vkGetPhysicalDeviceQueueFamilyProperties2(VkPhysicalDevice physicalDevice, uint32_t* pQueueFamilyPropertyCount, VkQueueFamilyProperties2* pQueueFamilyProperties);
|
||||
void vkb_wire_vkGetPhysicalDeviceSparseImageFormatProperties(VkPhysicalDevice physicalDevice, VkFormat format, VkImageType type, VkSampleCountFlagBits samples, VkImageUsageFlags usage, VkImageTiling tiling, uint32_t* pPropertyCount, VkSparseImageFormatProperties* pProperties);
|
||||
void vkb_wire_vkGetPhysicalDeviceSparseImageFormatProperties2(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSparseImageFormatInfo2* pFormatInfo, uint32_t* pPropertyCount, VkSparseImageFormatProperties2* pProperties);
|
||||
VkResult vkb_wire_vkGetPhysicalDeviceToolProperties(VkPhysicalDevice physicalDevice, uint32_t* pToolCount, VkPhysicalDeviceToolProperties* pToolProperties);
|
||||
VkResult vkb_wire_vkGetPipelineCacheData(VkDevice device, VkPipelineCache pipelineCache, size_t* pDataSize, void* pData);
|
||||
VkResult vkb_wire_vkGetQueryPoolResults(VkDevice device, VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount, size_t dataSize, void* pData, VkDeviceSize stride, VkQueryResultFlags flags);
|
||||
void vkb_wire_vkGetRenderAreaGranularity(VkDevice device, VkRenderPass renderPass, VkExtent2D* pGranularity);
|
||||
void vkb_wire_vkGetRenderingAreaGranularity(VkDevice device, const VkRenderingAreaInfo* pRenderingAreaInfo, VkExtent2D* pGranularity);
|
||||
VkResult vkb_wire_vkGetSemaphoreCounterValue(VkDevice device, VkSemaphore semaphore, uint64_t* pValue);
|
||||
VkResult vkb_wire_vkGetSemaphoreFdKHR(VkDevice device, const VkSemaphoreGetFdInfoKHR* pGetFdInfo, int* pFd);
|
||||
void vkb_wire_vkGetShaderModuleCreateInfoIdentifierEXT(VkDevice device, const VkShaderModuleCreateInfo* pCreateInfo, VkShaderModuleIdentifierEXT* pIdentifier);
|
||||
void vkb_wire_vkGetShaderModuleIdentifierEXT(VkDevice device, VkShaderModule shaderModule, VkShaderModuleIdentifierEXT* pIdentifier);
|
||||
void vkb_wire_vkGetTensorMemoryRequirementsARM(VkDevice device, const VkTensorMemoryRequirementsInfoARM* pInfo, VkMemoryRequirements2* pMemoryRequirements);
|
||||
VkResult vkb_wire_vkImportFenceFdKHR(VkDevice device, const VkImportFenceFdInfoKHR* pImportFenceFdInfo);
|
||||
VkResult vkb_wire_vkImportSemaphoreFdKHR(VkDevice device, const VkImportSemaphoreFdInfoKHR* pImportSemaphoreFdInfo);
|
||||
VkResult vkb_wire_vkInvalidateMappedMemoryRanges(VkDevice device, uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges);
|
||||
VkResult vkb_wire_vkMergePipelineCaches(VkDevice device, VkPipelineCache dstCache, uint32_t srcCacheCount, const VkPipelineCache* pSrcCaches);
|
||||
VkResult vkb_wire_vkQueueBindSparse(VkQueue queue, uint32_t bindInfoCount, const VkBindSparseInfo* pBindInfo, VkFence fence);
|
||||
VkResult vkb_wire_vkQueueSubmit(VkQueue queue, uint32_t submitCount, const VkSubmitInfo* pSubmits, VkFence fence);
|
||||
VkResult vkb_wire_vkQueueSubmit2(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2* pSubmits, VkFence fence);
|
||||
VkResult vkb_wire_vkQueueWaitIdle(VkQueue queue);
|
||||
VkResult vkb_wire_vkResetCommandBuffer(VkCommandBuffer commandBuffer, VkCommandBufferResetFlags flags);
|
||||
VkResult vkb_wire_vkResetCommandPool(VkDevice device, VkCommandPool commandPool, VkCommandPoolResetFlags flags);
|
||||
VkResult vkb_wire_vkResetDescriptorPool(VkDevice device, VkDescriptorPool descriptorPool, VkDescriptorPoolResetFlags flags);
|
||||
VkResult vkb_wire_vkResetEvent(VkDevice device, VkEvent event);
|
||||
VkResult vkb_wire_vkResetFences(VkDevice device, uint32_t fenceCount, const VkFence* pFences);
|
||||
void vkb_wire_vkResetQueryPool(VkDevice device, VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount);
|
||||
void vkb_wire_vkSetDeviceMemoryPriorityEXT(VkDevice device, VkDeviceMemory memory, float priority);
|
||||
VkResult vkb_wire_vkSetEvent(VkDevice device, VkEvent event);
|
||||
VkResult vkb_wire_vkSignalSemaphore(VkDevice device, const VkSemaphoreSignalInfo* pSignalInfo);
|
||||
void vkb_wire_vkTrimCommandPool(VkDevice device, VkCommandPool commandPool, VkCommandPoolTrimFlags flags);
|
||||
void vkb_wire_vkUpdateDescriptorSets(VkDevice device, uint32_t descriptorWriteCount, const VkWriteDescriptorSet* pDescriptorWrites, uint32_t descriptorCopyCount, const VkCopyDescriptorSet* pDescriptorCopies);
|
||||
VkResult vkb_wire_vkWaitForFences(VkDevice device, uint32_t fenceCount, const VkFence* pFences, VkBool32 waitAll, uint64_t timeout);
|
||||
VkResult vkb_wire_vkWaitSemaphores(VkDevice device, const VkSemaphoreWaitInfo* pWaitInfo, uint64_t timeout);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkAcquireNextImage2KHR(VkDevice device, const VkAcquireNextImageInfoKHR* pAcquireInfo, uint32_t* pImageIndex);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkAcquireNextImageKHR(VkDevice device, VkSwapchainKHR swapchain, uint64_t timeout, VkSemaphore semaphore, VkFence fence, uint32_t* pImageIndex);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkAllocateCommandBuffers(VkDevice device, const VkCommandBufferAllocateInfo* pAllocateInfo, VkCommandBuffer* pCommandBuffers);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkAllocateDescriptorSets(VkDevice device, const VkDescriptorSetAllocateInfo* pAllocateInfo, VkDescriptorSet* pDescriptorSets);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkAllocateMemory(VkDevice device, const VkMemoryAllocateInfo* pAllocateInfo, const VkAllocationCallbacks* pAllocator, VkDeviceMemory* pMemory);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkBeginCommandBuffer(VkCommandBuffer commandBuffer, const VkCommandBufferBeginInfo* pBeginInfo);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkBindBufferMemory2(VkDevice device, uint32_t bindInfoCount, const VkBindBufferMemoryInfo* pBindInfos);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkBindImageMemory(VkDevice device, VkImage image, VkDeviceMemory memory, VkDeviceSize memoryOffset);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkBindImageMemory2(VkDevice device, uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkCmdBeginDebugUtilsLabelEXT(VkCommandBuffer commandBuffer, const VkDebugUtilsLabelEXT* pLabelInfo);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkCmdEndDebugUtilsLabelEXT(VkCommandBuffer commandBuffer);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkCmdExecuteCommands(VkCommandBuffer commandBuffer, uint32_t commandBufferCount, const VkCommandBuffer* pCommandBuffers);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkCmdInsertDebugUtilsLabelEXT(VkCommandBuffer commandBuffer, const VkDebugUtilsLabelEXT* pLabelInfo);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkCmdPushDescriptorSetWithTemplate(VkCommandBuffer commandBuffer, VkDescriptorUpdateTemplate descriptorUpdateTemplate, VkPipelineLayout layout, uint32_t set, const void* pData);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkCmdPushDescriptorSetWithTemplate2(VkCommandBuffer commandBuffer, const VkPushDescriptorSetWithTemplateInfo* pPushDescriptorSetWithTemplateInfo);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCopyImageToImage(VkDevice device, const VkCopyImageToImageInfo* pCopyImageToImageInfo);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCopyImageToMemory(VkDevice device, const VkCopyImageToMemoryInfo* pCopyImageToMemoryInfo);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCopyMemoryToImage(VkDevice device, const VkCopyMemoryToImageInfo* pCopyMemoryToImageInfo);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateBuffer(VkDevice device, const VkBufferCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkBuffer* pBuffer);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pMessenger);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateDescriptorPool(VkDevice device, const VkDescriptorPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDescriptorPool* pDescriptorPool);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateDescriptorSetLayout(VkDevice device, const VkDescriptorSetLayoutCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDescriptorSetLayout* pSetLayout);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateDescriptorUpdateTemplate(VkDevice device, const VkDescriptorUpdateTemplateCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDescriptorUpdateTemplate* pDescriptorUpdateTemplate);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDevice* pDevice);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateHeadlessSurfaceEXT(VkInstance instance, const VkHeadlessSurfaceCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateImage(VkDevice device, const VkImageCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkImage* pImage);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateInstance(const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkInstance* pInstance);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreatePrivateDataSlot(VkDevice device, const VkPrivateDataSlotCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkPrivateDataSlot* pPrivateDataSlot);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateSwapchainKHR(VkDevice device, const VkSwapchainCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchain);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateWaylandSurfaceKHR(VkInstance instance, const VkWaylandSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateXcbSurfaceKHR(VkInstance instance, const VkXcbSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkCreateXlibSurfaceKHR(VkInstance instance, const VkXlibSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroyBuffer(VkDevice device, VkBuffer buffer, const VkAllocationCallbacks* pAllocator);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroyCommandPool(VkDevice device, VkCommandPool commandPool, const VkAllocationCallbacks* pAllocator);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT messenger, const VkAllocationCallbacks* pAllocator);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroyDescriptorPool(VkDevice device, VkDescriptorPool descriptorPool, const VkAllocationCallbacks* pAllocator);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroyDescriptorSetLayout(VkDevice device, VkDescriptorSetLayout descriptorSetLayout, const VkAllocationCallbacks* pAllocator);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroyDescriptorUpdateTemplate(VkDevice device, VkDescriptorUpdateTemplate descriptorUpdateTemplate, const VkAllocationCallbacks* pAllocator);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroyImage(VkDevice device, VkImage image, const VkAllocationCallbacks* pAllocator);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroyInstance(VkInstance instance, const VkAllocationCallbacks* pAllocator);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroyPrivateDataSlot(VkDevice device, VkPrivateDataSlot privateDataSlot, const VkAllocationCallbacks* pAllocator);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroySurfaceKHR(VkInstance instance, VkSurfaceKHR surface, const VkAllocationCallbacks* pAllocator);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkDestroySwapchainKHR(VkDevice device, VkSwapchainKHR swapchain, const VkAllocationCallbacks* pAllocator);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkDeviceWaitIdle(VkDevice device);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkEndCommandBuffer(VkCommandBuffer commandBuffer);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, const char* pLayerName, uint32_t* pPropertyCount, VkExtensionProperties* pProperties);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkEnumerateDeviceLayerProperties(VkPhysicalDevice physicalDevice, uint32_t* pPropertyCount, VkLayerProperties* pProperties);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkEnumerateInstanceExtensionProperties(const char* pLayerName, uint32_t* pPropertyCount, VkExtensionProperties* pProperties);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkEnumerateInstanceLayerProperties(uint32_t* pPropertyCount, VkLayerProperties* pProperties);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkEnumerateInstanceVersion(uint32_t* pApiVersion);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkEnumeratePhysicalDeviceGroups(VkInstance instance, uint32_t* pPhysicalDeviceGroupCount, VkPhysicalDeviceGroupProperties* pPhysicalDeviceGroupProperties);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkEnumeratePhysicalDevices(VkInstance instance, uint32_t* pPhysicalDeviceCount, VkPhysicalDevice* pPhysicalDevices);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkFlushMappedMemoryRanges(VkDevice device, uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkFreeCommandBuffers(VkDevice device, VkCommandPool commandPool, uint32_t commandBufferCount, const VkCommandBuffer* pCommandBuffers);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkFreeMemory(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks* pAllocator);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetBufferMemoryRequirements(VkDevice device, VkBuffer buffer, VkMemoryRequirements* pMemoryRequirements);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetBufferMemoryRequirements2(VkDevice device, const VkBufferMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetDeviceBufferMemoryRequirements(VkDevice device, const VkDeviceBufferMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetDeviceGroupPresentCapabilitiesKHR(VkDevice device, VkDeviceGroupPresentCapabilitiesKHR* pDeviceGroupPresentCapabilities);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetDeviceGroupSurfacePresentModesKHR(VkDevice device, VkSurfaceKHR surface, VkDeviceGroupPresentModeFlagsKHR* pModes);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetDeviceImageMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetDeviceImageSparseMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements);
|
||||
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkb_ep_vkGetDeviceProcAddr(VkDevice device, const char* pName);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetDeviceQueue(VkDevice device, uint32_t queueFamilyIndex, uint32_t queueIndex, VkQueue* pQueue);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetDeviceQueue2(VkDevice device, const VkDeviceQueueInfo2* pQueueInfo, VkQueue* pQueue);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetFenceStatus(VkDevice device, VkFence fence);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetImageMemoryRequirements(VkDevice device, VkImage image, VkMemoryRequirements* pMemoryRequirements);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetImageMemoryRequirements2(VkDevice device, const VkImageMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetImageSparseMemoryRequirements(VkDevice device, VkImage image, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements* pSparseMemoryRequirements);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetImageSparseMemoryRequirements2(VkDevice device, const VkImageSparseMemoryRequirementsInfo2* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetImageSubresourceLayout(VkDevice device, VkImage image, const VkImageSubresource* pSubresource, VkSubresourceLayout* pLayout);
|
||||
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkb_ep_vkGetInstanceProcAddr(VkInstance instance, const char* pName);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetMemoryFdPropertiesKHR(VkDevice device, VkExternalMemoryHandleTypeFlagBits handleType, int fd, VkMemoryFdPropertiesKHR* pMemoryFdProperties);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceExternalBufferProperties(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalBufferInfo* pExternalBufferInfo, VkExternalBufferProperties* pExternalBufferProperties);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceExternalFenceProperties(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalFenceInfo* pExternalFenceInfo, VkExternalFenceProperties* pExternalFenceProperties);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceExternalSemaphoreProperties(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalSemaphoreInfo* pExternalSemaphoreInfo, VkExternalSemaphoreProperties* pExternalSemaphoreProperties);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceFeatures(VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures* pFeatures);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceFeatures2(VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures2* pFeatures);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceFormatProperties(VkPhysicalDevice physicalDevice, VkFormat format, VkFormatProperties* pFormatProperties);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceFormatProperties2(VkPhysicalDevice physicalDevice, VkFormat format, VkFormatProperties2* pFormatProperties);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetPhysicalDeviceImageFormatProperties(VkPhysicalDevice physicalDevice, VkFormat format, VkImageType type, VkImageTiling tiling, VkImageUsageFlags usage, VkImageCreateFlags flags, VkImageFormatProperties* pImageFormatProperties);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetPhysicalDeviceImageFormatProperties2(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceImageFormatInfo2* pImageFormatInfo, VkImageFormatProperties2* pImageFormatProperties);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceMemoryProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties* pMemoryProperties);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceMemoryProperties2(VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties2* pMemoryProperties);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceMultisamplePropertiesEXT(VkPhysicalDevice physicalDevice, VkSampleCountFlagBits samples, VkMultisamplePropertiesEXT* pMultisampleProperties);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetPhysicalDevicePresentRectanglesKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t* pRectCount, VkRect2D* pRects);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties* pProperties);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceProperties2(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties2* pProperties);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceQueueFamilyProperties(VkPhysicalDevice physicalDevice, uint32_t* pQueueFamilyPropertyCount, VkQueueFamilyProperties* pQueueFamilyProperties);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceQueueFamilyProperties2(VkPhysicalDevice physicalDevice, uint32_t* pQueueFamilyPropertyCount, VkQueueFamilyProperties2* pQueueFamilyProperties);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceSparseImageFormatProperties(VkPhysicalDevice physicalDevice, VkFormat format, VkImageType type, VkSampleCountFlagBits samples, VkImageUsageFlags usage, VkImageTiling tiling, uint32_t* pPropertyCount, VkSparseImageFormatProperties* pProperties);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPhysicalDeviceSparseImageFormatProperties2(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSparseImageFormatInfo2* pFormatInfo, uint32_t* pPropertyCount, VkSparseImageFormatProperties2* pProperties);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetPhysicalDeviceSurfaceCapabilities2KHR(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, VkSurfaceCapabilities2KHR* pSurfaceCapabilities);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetPhysicalDeviceSurfaceCapabilitiesKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR* pSurfaceCapabilities);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetPhysicalDeviceSurfaceFormats2KHR(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, uint32_t* pSurfaceFormatCount, VkSurfaceFormat2KHR* pSurfaceFormats);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t* pSurfaceFormatCount, VkSurfaceFormatKHR* pSurfaceFormats);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetPhysicalDeviceSurfacePresentModesKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t* pPresentModeCount, VkPresentModeKHR* pPresentModes);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetPhysicalDeviceSurfaceSupportKHR(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, VkSurfaceKHR surface, VkBool32* pSupported);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetPhysicalDeviceToolProperties(VkPhysicalDevice physicalDevice, uint32_t* pToolCount, VkPhysicalDeviceToolProperties* pToolProperties);
|
||||
VKAPI_ATTR VkBool32 VKAPI_CALL vkb_ep_vkGetPhysicalDeviceWaylandPresentationSupportKHR(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, struct wl_display* display);
|
||||
VKAPI_ATTR VkBool32 VKAPI_CALL vkb_ep_vkGetPhysicalDeviceXcbPresentationSupportKHR(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, xcb_connection_t* connection, xcb_visualid_t visual_id);
|
||||
VKAPI_ATTR VkBool32 VKAPI_CALL vkb_ep_vkGetPhysicalDeviceXlibPresentationSupportKHR(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, Display* dpy, VisualID visualID);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkGetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlot privateDataSlot, uint64_t* pData);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetSemaphoreCounterValue(VkDevice device, VkSemaphore semaphore, uint64_t* pValue);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkGetSwapchainImagesKHR(VkDevice device, VkSwapchainKHR swapchain, uint32_t* pSwapchainImageCount, VkImage* pSwapchainImages);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkInvalidateMappedMemoryRanges(VkDevice device, uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkMapMemory(VkDevice device, VkDeviceMemory memory, VkDeviceSize offset, VkDeviceSize size, VkMemoryMapFlags flags, void** ppData);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkMapMemory2(VkDevice device, const VkMemoryMapInfo* pMemoryMapInfo, void** ppData);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkQueueBeginDebugUtilsLabelEXT(VkQueue queue, const VkDebugUtilsLabelEXT* pLabelInfo);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkQueueEndDebugUtilsLabelEXT(VkQueue queue);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkQueueInsertDebugUtilsLabelEXT(VkQueue queue, const VkDebugUtilsLabelEXT* pLabelInfo);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* pPresentInfo);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkQueueSubmit(VkQueue queue, uint32_t submitCount, const VkSubmitInfo* pSubmits, VkFence fence);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkQueueSubmit2(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2* pSubmits, VkFence fence);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkQueueWaitIdle(VkQueue queue);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkResetCommandBuffer(VkCommandBuffer commandBuffer, VkCommandBufferResetFlags flags);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkResetCommandPool(VkDevice device, VkCommandPool commandPool, VkCommandPoolResetFlags flags);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkResetDescriptorPool(VkDevice device, VkDescriptorPool descriptorPool, VkDescriptorPoolResetFlags flags);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkSetDebugUtilsObjectNameEXT(VkDevice device, const VkDebugUtilsObjectNameInfoEXT* pNameInfo);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkSetDebugUtilsObjectTagEXT(VkDevice device, const VkDebugUtilsObjectTagInfoEXT* pTagInfo);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkSetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlot privateDataSlot, uint64_t data);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkSubmitDebugUtilsMessageEXT(VkInstance instance, VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkTransitionImageLayout(VkDevice device, uint32_t transitionCount, const VkHostImageLayoutTransitionInfo* pTransitions);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkUnmapMemory(VkDevice device, VkDeviceMemory memory);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkUnmapMemory2(VkDevice device, const VkMemoryUnmapInfo* pMemoryUnmapInfo);
|
||||
VKAPI_ATTR void VKAPI_CALL vkb_ep_vkUpdateDescriptorSetWithTemplate(VkDevice device, VkDescriptorSet descriptorSet, VkDescriptorUpdateTemplate descriptorUpdateTemplate, const void* pData);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkWaitForFences(VkDevice device, uint32_t fenceCount, const VkFence* pFences, VkBool32 waitAll, uint64_t timeout);
|
||||
VKAPI_ATTR VkResult VKAPI_CALL vkb_ep_vkWaitSemaphores(VkDevice device, const VkSemaphoreWaitInfo* pWaitInfo, uint64_t timeout);
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,300 @@
|
||||
/* Generated by tools/vkbridge/codegen/gen_vkbridge.py from vk.xml 1.4.341 - do not edit. */
|
||||
|
||||
#ifndef VKB_SERVER_GEN_H
|
||||
#define VKB_SERVER_GEN_H
|
||||
#include "vkb_gen.h"
|
||||
typedef struct vkb_srv_call vkb_srv_call;
|
||||
typedef void (*vkb_srv_handler)(vkb_srv_call *c);
|
||||
extern const vkb_srv_handler vkb_srv_handlers[VKB_CMD_COUNT];
|
||||
void vkb_sv_vkAllocateCommandBuffers(vkb_srv_call *c);
|
||||
void vkb_sv_vkAllocateDescriptorSets(vkb_srv_call *c);
|
||||
void vkb_sv_vkAllocateMemory(vkb_srv_call *c);
|
||||
void vkb_sv_vkBeginCommandBuffer(vkb_srv_call *c);
|
||||
void vkb_sv_vkBindBufferMemory(vkb_srv_call *c);
|
||||
void vkb_sv_vkBindBufferMemory2(vkb_srv_call *c);
|
||||
void vkb_sv_vkBindImageMemory(vkb_srv_call *c);
|
||||
void vkb_sv_vkBindImageMemory2(vkb_srv_call *c);
|
||||
void vkb_sv_vkBindTensorMemoryARM(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdBeginConditionalRenderingEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdBeginCustomResolveEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdBeginQuery(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdBeginQueryIndexedEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdBeginRenderPass(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdBeginRenderPass2(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdBeginRendering(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdBeginTransformFeedbackEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdBindDescriptorSets(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdBindDescriptorSets2(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdBindIndexBuffer(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdBindIndexBuffer2(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdBindPipeline(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdBindTransformFeedbackBuffersEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdBindVertexBuffers(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdBindVertexBuffers2(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdBlitImage(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdBlitImage2(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdClearAttachments(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdClearColorImage(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdClearDepthStencilImage(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdCopyBuffer(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdCopyBuffer2(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdCopyBufferToImage(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdCopyBufferToImage2(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdCopyImage(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdCopyImage2(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdCopyImageToBuffer(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdCopyImageToBuffer2(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdCopyQueryPoolResults(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdCopyTensorARM(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdDispatch(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdDispatchBase(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdDispatchIndirect(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdDraw(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdDrawIndexed(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdDrawIndexedIndirect(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdDrawIndexedIndirectCount(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdDrawIndirect(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdDrawIndirectByteCountEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdDrawIndirectCount(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdDrawMultiEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdDrawMultiIndexedEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdEndConditionalRenderingEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdEndQuery(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdEndQueryIndexedEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdEndRenderPass(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdEndRenderPass2(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdEndRendering(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdEndRendering2KHR(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdEndTransformFeedbackEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdExecuteCommands(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdFillBuffer(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdNextSubpass(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdNextSubpass2(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdPipelineBarrier(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdPipelineBarrier2(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdPushConstants(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdPushConstants2(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdPushDescriptorSet(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdPushDescriptorSet2(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdResetEvent(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdResetEvent2(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdResetQueryPool(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdResolveImage(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdResolveImage2(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetAlphaToCoverageEnableEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetAlphaToOneEnableEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetAttachmentFeedbackLoopEnableEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetBlendConstants(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetColorBlendAdvancedEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetColorBlendEnableEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetColorBlendEquationEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetColorWriteEnableEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetColorWriteMaskEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetConservativeRasterizationModeEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetCullMode(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetDepthBias(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetDepthBias2EXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetDepthBiasEnable(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetDepthBounds(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetDepthBoundsTestEnable(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetDepthClampEnableEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetDepthClampRangeEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetDepthClipEnableEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetDepthClipNegativeOneToOneEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetDepthCompareOp(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetDepthTestEnable(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetDepthWriteEnable(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetDeviceMask(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetDiscardRectangleEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetDiscardRectangleEnableEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetDiscardRectangleModeEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetEvent(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetEvent2(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetExtraPrimitiveOverestimationSizeEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetFragmentShadingRateKHR(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetFrontFace(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetLineRasterizationModeEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetLineStipple(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetLineStippleEnableEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetLineWidth(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetLogicOpEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetLogicOpEnableEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetPatchControlPointsEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetPolygonModeEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetPrimitiveRestartEnable(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetPrimitiveTopology(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetProvokingVertexModeEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetRasterizationSamplesEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetRasterizationStreamEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetRasterizerDiscardEnable(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetRenderingAttachmentLocations(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetRenderingInputAttachmentIndices(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetSampleLocationsEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetSampleLocationsEnableEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetSampleMaskEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetScissor(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetScissorWithCount(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetStencilCompareMask(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetStencilOp(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetStencilReference(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetStencilTestEnable(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetStencilWriteMask(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetTessellationDomainOriginEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetVertexInputEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetViewport(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdSetViewportWithCount(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdUpdateBuffer(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdWaitEvents(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdWaitEvents2(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdWriteTimestamp(vkb_srv_call *c);
|
||||
void vkb_sv_vkCmdWriteTimestamp2(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateBuffer(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateBufferView(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateCommandPool(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateComputePipelines(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateDescriptorPool(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateDescriptorSetLayout(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateDescriptorUpdateTemplate(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateDevice(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateEvent(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateFence(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateFramebuffer(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateGraphicsPipelines(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateImage(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateImageView(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateInstance(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreatePipelineCache(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreatePipelineLayout(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateQueryPool(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateRenderPass(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateRenderPass2(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateSampler(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateSamplerYcbcrConversion(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateSemaphore(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateShaderModule(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateTensorARM(vkb_srv_call *c);
|
||||
void vkb_sv_vkCreateTensorViewARM(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroyBuffer(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroyBufferView(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroyCommandPool(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroyDescriptorPool(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroyDescriptorSetLayout(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroyDescriptorUpdateTemplate(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroyDevice(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroyEvent(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroyFence(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroyFramebuffer(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroyImage(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroyImageView(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroyInstance(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroyPipeline(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroyPipelineCache(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroyPipelineLayout(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroyQueryPool(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroyRenderPass(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroySampler(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroySamplerYcbcrConversion(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroySemaphore(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroyShaderModule(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroyTensorARM(vkb_srv_call *c);
|
||||
void vkb_sv_vkDestroyTensorViewARM(vkb_srv_call *c);
|
||||
void vkb_sv_vkDeviceWaitIdle(vkb_srv_call *c);
|
||||
void vkb_sv_vkEndCommandBuffer(vkb_srv_call *c);
|
||||
void vkb_sv_vkEnumerateDeviceExtensionProperties(vkb_srv_call *c);
|
||||
void vkb_sv_vkEnumerateInstanceExtensionProperties(vkb_srv_call *c);
|
||||
void vkb_sv_vkEnumerateInstanceLayerProperties(vkb_srv_call *c);
|
||||
void vkb_sv_vkEnumerateInstanceVersion(vkb_srv_call *c);
|
||||
void vkb_sv_vkEnumeratePhysicalDeviceGroups(vkb_srv_call *c);
|
||||
void vkb_sv_vkEnumeratePhysicalDeviceQueueFamilyPerformanceCountersByRegionARM(vkb_srv_call *c);
|
||||
void vkb_sv_vkEnumeratePhysicalDevices(vkb_srv_call *c);
|
||||
void vkb_sv_vkFlushMappedMemoryRanges(vkb_srv_call *c);
|
||||
void vkb_sv_vkFreeCommandBuffers(vkb_srv_call *c);
|
||||
void vkb_sv_vkFreeDescriptorSets(vkb_srv_call *c);
|
||||
void vkb_sv_vkFreeMemory(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetBufferDeviceAddress(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetBufferMemoryRequirements(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetBufferMemoryRequirements2(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetBufferOpaqueCaptureAddress(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetCalibratedTimestampsKHR(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetDescriptorSetLayoutSupport(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetDeviceBufferMemoryRequirements(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetDeviceGroupPeerMemoryFeatures(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetDeviceImageMemoryRequirements(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetDeviceImageSparseMemoryRequirements(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetDeviceImageSubresourceLayout(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetDeviceMemoryCommitment(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetDeviceMemoryOpaqueCaptureAddress(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetDeviceQueue(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetDeviceQueue2(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetDeviceTensorMemoryRequirementsARM(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetEventStatus(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetFenceFdKHR(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetFenceStatus(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetImageDrmFormatModifierPropertiesEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetImageMemoryRequirements(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetImageMemoryRequirements2(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetImageSparseMemoryRequirements(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetImageSparseMemoryRequirements2(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetImageSubresourceLayout(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetImageSubresourceLayout2(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetMemoryFdKHR(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetMemoryFdPropertiesKHR(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPhysicalDeviceCalibrateableTimeDomainsKHR(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPhysicalDeviceCooperativeMatrixPropertiesKHR(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPhysicalDeviceExternalBufferProperties(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPhysicalDeviceExternalFenceProperties(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPhysicalDeviceExternalSemaphoreProperties(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPhysicalDeviceExternalTensorPropertiesARM(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPhysicalDeviceFeatures(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPhysicalDeviceFeatures2(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPhysicalDeviceFormatProperties(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPhysicalDeviceFormatProperties2(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPhysicalDeviceFragmentShadingRatesKHR(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPhysicalDeviceImageFormatProperties(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPhysicalDeviceImageFormatProperties2(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPhysicalDeviceMemoryProperties(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPhysicalDeviceMemoryProperties2(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPhysicalDeviceMultisamplePropertiesEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPhysicalDeviceProperties(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPhysicalDeviceProperties2(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPhysicalDeviceQueueFamilyProperties(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPhysicalDeviceQueueFamilyProperties2(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPhysicalDeviceSparseImageFormatProperties(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPhysicalDeviceSparseImageFormatProperties2(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPhysicalDeviceToolProperties(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetPipelineCacheData(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetQueryPoolResults(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetRenderAreaGranularity(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetRenderingAreaGranularity(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetSemaphoreCounterValue(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetSemaphoreFdKHR(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetShaderModuleCreateInfoIdentifierEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetShaderModuleIdentifierEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkGetTensorMemoryRequirementsARM(vkb_srv_call *c);
|
||||
void vkb_sv_vkImportFenceFdKHR(vkb_srv_call *c);
|
||||
void vkb_sv_vkImportSemaphoreFdKHR(vkb_srv_call *c);
|
||||
void vkb_sv_vkInvalidateMappedMemoryRanges(vkb_srv_call *c);
|
||||
void vkb_sv_vkMergePipelineCaches(vkb_srv_call *c);
|
||||
void vkb_sv_vkQueueBindSparse(vkb_srv_call *c);
|
||||
void vkb_sv_vkQueueSubmit(vkb_srv_call *c);
|
||||
void vkb_sv_vkQueueSubmit2(vkb_srv_call *c);
|
||||
void vkb_sv_vkQueueWaitIdle(vkb_srv_call *c);
|
||||
void vkb_sv_vkResetCommandBuffer(vkb_srv_call *c);
|
||||
void vkb_sv_vkResetCommandPool(vkb_srv_call *c);
|
||||
void vkb_sv_vkResetDescriptorPool(vkb_srv_call *c);
|
||||
void vkb_sv_vkResetEvent(vkb_srv_call *c);
|
||||
void vkb_sv_vkResetFences(vkb_srv_call *c);
|
||||
void vkb_sv_vkResetQueryPool(vkb_srv_call *c);
|
||||
void vkb_sv_vkSetDeviceMemoryPriorityEXT(vkb_srv_call *c);
|
||||
void vkb_sv_vkSetEvent(vkb_srv_call *c);
|
||||
void vkb_sv_vkSignalSemaphore(vkb_srv_call *c);
|
||||
void vkb_sv_vkTrimCommandPool(vkb_srv_call *c);
|
||||
void vkb_sv_vkUpdateDescriptorSets(vkb_srv_call *c);
|
||||
void vkb_sv_vkWaitForFences(vkb_srv_call *c);
|
||||
void vkb_sv_vkWaitSemaphores(vkb_srv_call *c);
|
||||
void vkb_sv_vkbHello(vkb_srv_call *c);
|
||||
void vkb_sv_vkbQueryServer(vkb_srv_call *c);
|
||||
void vkb_sv_vkbDescUpdateRaw(vkb_srv_call *c);
|
||||
void vkb_sv_vkbPushDescRaw(vkb_srv_call *c);
|
||||
void vkb_sv_vkbAllocDescSets(vkb_srv_call *c);
|
||||
#endif
|
||||
@@ -0,0 +1,71 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* BCn emulation: what each block-compressed format is decoded into. Shared by the client (format
|
||||
* queries) and the server (image creation, decode). The decoded image is created with the UNORM
|
||||
* base format and MUTABLE_FORMAT so the decoder can write it through a storage view, and the
|
||||
* app's sRGB views still work.
|
||||
*/
|
||||
#ifndef VKB_EMU_FORMATS_H
|
||||
#define VKB_EMU_FORMATS_H
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
static inline int vkb_is_bc(VkFormat f)
|
||||
{
|
||||
return f >= VK_FORMAT_BC1_RGB_UNORM_BLOCK && f <= VK_FORMAT_BC7_SRGB_BLOCK;
|
||||
}
|
||||
|
||||
/* The format an app's view of a BC format becomes (keeps sRGB/SNORM meaning). */
|
||||
static inline VkFormat vkb_bc_view_format(VkFormat f)
|
||||
{
|
||||
switch (f) {
|
||||
case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC2_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC3_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC7_UNORM_BLOCK: return VK_FORMAT_R8G8B8A8_UNORM;
|
||||
case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
|
||||
case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
|
||||
case VK_FORMAT_BC2_SRGB_BLOCK:
|
||||
case VK_FORMAT_BC3_SRGB_BLOCK:
|
||||
case VK_FORMAT_BC7_SRGB_BLOCK: return VK_FORMAT_R8G8B8A8_SRGB;
|
||||
case VK_FORMAT_BC4_UNORM_BLOCK: return VK_FORMAT_R8_UNORM;
|
||||
case VK_FORMAT_BC4_SNORM_BLOCK: return VK_FORMAT_R8_SNORM;
|
||||
case VK_FORMAT_BC5_UNORM_BLOCK: return VK_FORMAT_R8G8_UNORM;
|
||||
case VK_FORMAT_BC5_SNORM_BLOCK: return VK_FORMAT_R8G8_SNORM;
|
||||
case VK_FORMAT_BC6H_UFLOAT_BLOCK:
|
||||
case VK_FORMAT_BC6H_SFLOAT_BLOCK: return VK_FORMAT_R16G16B16A16_SFLOAT;
|
||||
default: return f;
|
||||
}
|
||||
}
|
||||
|
||||
/* The format the decoded image is created with and written through (storage-capable). */
|
||||
static inline VkFormat vkb_bc_storage_format(VkFormat f)
|
||||
{
|
||||
switch (vkb_bc_view_format(f)) {
|
||||
case VK_FORMAT_R8G8B8A8_SRGB:
|
||||
case VK_FORMAT_R8G8B8A8_UNORM: return VK_FORMAT_R8G8B8A8_UNORM;
|
||||
case VK_FORMAT_R8_SNORM:
|
||||
case VK_FORMAT_R8_UNORM: return VK_FORMAT_R8_UNORM;
|
||||
case VK_FORMAT_R8G8_SNORM:
|
||||
case VK_FORMAT_R8G8_UNORM: return VK_FORMAT_R8G8_UNORM;
|
||||
case VK_FORMAT_R16G16B16A16_SFLOAT: return VK_FORMAT_R16G16B16A16_SFLOAT;
|
||||
default: return f;
|
||||
}
|
||||
}
|
||||
|
||||
/* Bytes per 4x4 block. */
|
||||
static inline uint32_t vkb_bc_block_bytes(VkFormat f)
|
||||
{
|
||||
switch (f) {
|
||||
case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
|
||||
case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
|
||||
case VK_FORMAT_BC4_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC4_SNORM_BLOCK: return 8;
|
||||
default: return 16;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,24 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef VKB_LOG_H
|
||||
#define VKB_LOG_H
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
#define VKB_LOG_ERROR 0
|
||||
#define VKB_LOG_WARN 1
|
||||
#define VKB_LOG_INFO 2
|
||||
#define VKB_LOG_DEBUG 3
|
||||
|
||||
/* Each side provides the sink (client: stderr + optional file; server: logcat + file). */
|
||||
void vkb_log(int level, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
|
||||
int vkb_log_level(void);
|
||||
|
||||
#define VKB_ERR(...) vkb_log(VKB_LOG_ERROR, __VA_ARGS__)
|
||||
#define VKB_WARN(...) vkb_log(VKB_LOG_WARN, __VA_ARGS__)
|
||||
#define VKB_INFO(...) vkb_log(VKB_LOG_INFO, __VA_ARGS__)
|
||||
#define VKB_DBG(...) do { if (vkb_log_level() >= VKB_LOG_DEBUG) vkb_log(VKB_LOG_DEBUG, __VA_ARGS__); } while (0)
|
||||
|
||||
/* Logs a message only the first time this call site is reached. */
|
||||
#define VKB_ONCE(level, ...) do { static int once_; if (!__atomic_exchange_n(&once_, 1, __ATOMIC_RELAXED)) vkb_log(level, __VA_ARGS__); } while (0)
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,63 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Bridge-specific (non-Vulkan) parts of the protocol: hello, server query, memory sharing.
|
||||
*/
|
||||
#ifndef VKB_PROTO_H
|
||||
#define VKB_PROTO_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define VKB_DEFAULT_SOCKET_ENV "VKBRIDGE_SOCKET"
|
||||
|
||||
/* How host-visible memory is shared between the client's address space and the GPU. */
|
||||
enum vkb_mem_strategy {
|
||||
VKB_MEM_NONE = 0, /* no sharing works: host-visible memory is not offered */
|
||||
VKB_MEM_HOSTPTR = 1, /* client memfd, server imports it (VK_EXT_external_memory_host) */
|
||||
VKB_MEM_DMABUF = 2, /* server exports a dma-buf (VK_EXT_external_memory_dma_buf), client mmaps */
|
||||
VKB_MEM_AHB = 3, /* server allocates an AHardwareBuffer (BLOB), client mmaps its dma-buf */
|
||||
VKB_MEM_DMAHEAP = 4, /* server allocates from /dev/dma_heap and imports it as a dma-buf, client mmaps */
|
||||
};
|
||||
|
||||
static inline const char *vkb_mem_strategy_name(uint32_t s)
|
||||
{
|
||||
switch (s) {
|
||||
case VKB_MEM_HOSTPTR: return "hostptr";
|
||||
case VKB_MEM_DMABUF: return "dmabuf";
|
||||
case VKB_MEM_AHB: return "ahb";
|
||||
case VKB_MEM_DMAHEAP: return "dmaheap";
|
||||
default: return "none";
|
||||
}
|
||||
}
|
||||
|
||||
/* Emulated features (server rewrites resources/shaders; client reports them as supported). */
|
||||
#define VKB_EMU_BCN (1u << 0) /* BC1-BC7 textures decoded on upload */
|
||||
#define VKB_EMU_DIVISOR (1u << 1) /* VK_EXT_vertex_attribute_divisor */
|
||||
#define VKB_EMU_CLIP_DISTANCE (1u << 2) /* shaderClipDistance */
|
||||
#define VKB_EMU_CULL_DISTANCE (1u << 3) /* shaderCullDistance (accepted, not applied) */
|
||||
#define VKB_EMU_POINT_SIZE (1u << 4) /* strip PointSize from geometry/tessellation stages */
|
||||
#define VKB_EMU_DEPTH_CLIP (1u << 5) /* VK_EXT_depth_clip_enable via depthClamp */
|
||||
#define VKB_EMU_MAINT5 (1u << 6) /* VK_KHR_maintenance5 on a driver without it */
|
||||
#define VKB_EMU_ROBUSTNESS2 (1u << 7) /* VK_EXT_robustness2 reported, not enforced (GameNative's r44 profile) */
|
||||
|
||||
/* vkbQueryServer reply (per physical device). */
|
||||
typedef struct vkb_server_pd_info {
|
||||
uint32_t strategy; /* enum vkb_mem_strategy */
|
||||
uint32_t buffer_handle_types; /* VkExternalMemoryHandleTypeFlags to put on buffers */
|
||||
uint32_t shareable_types; /* server memory type bits that can be shared */
|
||||
uint32_t coherent_types; /* of those, verified coherent without flushes */
|
||||
uint64_t import_alignment; /* hostptr: minImportedHostPointerAlignment */
|
||||
uint32_t emu; /* VKB_EMU_* the server will perform */
|
||||
uint32_t emu_available; /* VKB_EMU_* the server could perform */
|
||||
uint32_t real_api_version;
|
||||
uint32_t missing; /* VKB_EMU_* features the GPU lacks (emulated or not) */
|
||||
uint32_t reserved[6];
|
||||
} vkb_server_pd_info;
|
||||
|
||||
/* vkAllocateMemory wire extras (after VkMemoryAllocateInfo). */
|
||||
#define VKB_ALLOC_PLAIN 0u
|
||||
#define VKB_ALLOC_SHARED 1u
|
||||
|
||||
/* Extra structure the client hangs on create infos to tell the server which shared handle
|
||||
* types were injected (so the server can remove them if the driver refuses). Not on the wire. */
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,29 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* What the generated server handlers need from the server: the per-call context.
|
||||
*/
|
||||
#ifndef VKB_SERVER_IFACE_H
|
||||
#define VKB_SERVER_IFACE_H
|
||||
|
||||
#include "vkb_wire.h"
|
||||
|
||||
struct vkb_srv_table;
|
||||
struct vkb_srv_conn;
|
||||
|
||||
struct vkb_srv_call {
|
||||
vkb_dec d; /* request */
|
||||
vkb_enc r; /* reply payload */
|
||||
vkb_dispatch *dt; /* dispatch for the table named in the request */
|
||||
struct vkb_srv_table *table; /* NULL for global commands */
|
||||
struct vkb_srv_conn *conn;
|
||||
uint32_t status; /* reply status (VKB_ST_*) */
|
||||
int close_after[VKB_MAX_FDS]; /* server fds to close once the reply is sent */
|
||||
int nclose;
|
||||
int in_stream; /* replaying a command buffer stream */
|
||||
};
|
||||
|
||||
void vkb_srv_bad_message(struct vkb_srv_call *c);
|
||||
void vkb_srv_missing(struct vkb_srv_call *c, const char *name);
|
||||
void vkb_srv_close_after_send(struct vkb_srv_call *c, int fd);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,710 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "vkb_wire.h"
|
||||
#include "vkb_log.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/uio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* ------------------------------------------------------------------ arena */
|
||||
|
||||
struct vkb_arena_block {
|
||||
vkb_arena_block *next;
|
||||
size_t used, cap;
|
||||
/* data follows, 16-aligned */
|
||||
};
|
||||
|
||||
#define ARENA_HDR ((sizeof(vkb_arena_block) + 15) & ~(size_t)15)
|
||||
|
||||
void *vkb_arena_alloc(vkb_arena *a, size_t n)
|
||||
{
|
||||
n = (n + 15) & ~(size_t)15;
|
||||
if (n == 0) n = 16;
|
||||
vkb_arena_block *b = a->head;
|
||||
if (!b || b->cap - b->used < n) {
|
||||
size_t cap = n > 65536 ? n : 65536;
|
||||
b = malloc(ARENA_HDR + cap);
|
||||
if (!b) return NULL;
|
||||
b->next = a->head;
|
||||
b->used = 0;
|
||||
b->cap = cap;
|
||||
a->head = b;
|
||||
}
|
||||
void *p = (uint8_t *)b + ARENA_HDR + b->used;
|
||||
b->used += n;
|
||||
a->used_total += n;
|
||||
memset(p, 0, n);
|
||||
return p;
|
||||
}
|
||||
|
||||
void vkb_arena_reset(vkb_arena *a)
|
||||
{
|
||||
/* Keep the most recent block if it is the default size; free the rest. */
|
||||
vkb_arena_block *b = a->head, *keep = NULL;
|
||||
while (b) {
|
||||
vkb_arena_block *n = b->next;
|
||||
if (!keep && b->cap == 65536) {
|
||||
keep = b;
|
||||
keep->next = NULL;
|
||||
keep->used = 0;
|
||||
} else {
|
||||
free(b);
|
||||
}
|
||||
b = n;
|
||||
}
|
||||
a->head = keep;
|
||||
a->used_total = 0;
|
||||
}
|
||||
|
||||
void vkb_arena_free(vkb_arena *a)
|
||||
{
|
||||
vkb_arena_block *b = a->head;
|
||||
while (b) {
|
||||
vkb_arena_block *n = b->next;
|
||||
free(b);
|
||||
b = n;
|
||||
}
|
||||
a->head = NULL;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ encoder */
|
||||
|
||||
void vkb_enc_init(vkb_enc *e)
|
||||
{
|
||||
memset(e, 0, sizeof(*e));
|
||||
}
|
||||
|
||||
void vkb_enc_reset(vkb_enc *e)
|
||||
{
|
||||
e->len = 0;
|
||||
e->nfds = 0;
|
||||
e->oom = 0;
|
||||
}
|
||||
|
||||
void vkb_enc_free(vkb_enc *e)
|
||||
{
|
||||
free(e->buf);
|
||||
memset(e, 0, sizeof(*e));
|
||||
}
|
||||
|
||||
static int enc_grow(vkb_enc *e, size_t add)
|
||||
{
|
||||
if (e->oom) return 0;
|
||||
size_t need = e->len + add;
|
||||
if (need <= e->cap) return 1;
|
||||
size_t cap = e->cap ? e->cap : 4096;
|
||||
while (cap < need) cap *= 2;
|
||||
uint8_t *nb = realloc(e->buf, cap);
|
||||
if (!nb) {
|
||||
e->oom = 1;
|
||||
return 0;
|
||||
}
|
||||
e->buf = nb;
|
||||
e->cap = cap;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void vkb_enc_bytes(vkb_enc *e, const void *p, size_t n)
|
||||
{
|
||||
if (!n || !enc_grow(e, n)) return;
|
||||
if (p) memcpy(e->buf + e->len, p, n);
|
||||
else memset(e->buf + e->len, 0, n);
|
||||
e->len += n;
|
||||
}
|
||||
|
||||
void vkb_enc_u8(vkb_enc *e, uint8_t v) { vkb_enc_bytes(e, &v, 1); }
|
||||
void vkb_enc_u32(vkb_enc *e, uint32_t v) { vkb_enc_bytes(e, &v, 4); }
|
||||
void vkb_enc_u64(vkb_enc *e, uint64_t v) { vkb_enc_bytes(e, &v, 8); }
|
||||
|
||||
static void enc_align(vkb_enc *e)
|
||||
{
|
||||
size_t pad = (8 - (e->len & 7)) & 7;
|
||||
if (pad) vkb_enc_bytes(e, NULL, pad);
|
||||
}
|
||||
|
||||
size_t vkb_enc_raw(vkb_enc *e, const void *p, size_t n)
|
||||
{
|
||||
enc_align(e);
|
||||
size_t off = e->len;
|
||||
vkb_enc_bytes(e, p, n);
|
||||
return off;
|
||||
}
|
||||
|
||||
void vkb_enc_patch_u64(vkb_enc *e, size_t off, uint64_t v)
|
||||
{
|
||||
if (e->oom || off + 8 > e->len) return;
|
||||
memcpy(e->buf + off, &v, 8);
|
||||
}
|
||||
|
||||
void vkb_enc_str(vkb_enc *e, const char *s)
|
||||
{
|
||||
if (!s) {
|
||||
vkb_enc_u8(e, 0);
|
||||
return;
|
||||
}
|
||||
uint32_t n = (uint32_t)strlen(s);
|
||||
vkb_enc_u8(e, 1);
|
||||
vkb_enc_u32(e, n);
|
||||
vkb_enc_bytes(e, s, n);
|
||||
}
|
||||
|
||||
void vkb_enc_strarray(vkb_enc *e, const char *const *a, uint32_t n)
|
||||
{
|
||||
if (!a) {
|
||||
vkb_enc_u8(e, 0);
|
||||
return;
|
||||
}
|
||||
vkb_enc_u8(e, 1);
|
||||
vkb_enc_u32(e, n);
|
||||
for (uint32_t i = 0; i < n; i++) vkb_enc_str(e, a[i]);
|
||||
}
|
||||
|
||||
void vkb_enc_blob(vkb_enc *e, const void *p, size_t n)
|
||||
{
|
||||
if (!p) {
|
||||
vkb_enc_u8(e, 0);
|
||||
return;
|
||||
}
|
||||
vkb_enc_u8(e, 1);
|
||||
vkb_enc_u64(e, n);
|
||||
vkb_enc_raw(e, p, n);
|
||||
}
|
||||
|
||||
void vkb_enc_fd(vkb_enc *e, int fd)
|
||||
{
|
||||
if (fd < 0) {
|
||||
vkb_enc_u32(e, VKB_FD_NONE);
|
||||
return;
|
||||
}
|
||||
if (e->nfds >= VKB_MAX_FDS) {
|
||||
VKB_ERR("too many file descriptors in one message");
|
||||
e->oom = 1;
|
||||
return;
|
||||
}
|
||||
e->fds[e->nfds] = fd;
|
||||
vkb_enc_u32(e, (uint32_t)e->nfds);
|
||||
e->nfds++;
|
||||
}
|
||||
|
||||
void vkb_enc_append(vkb_enc *e, const vkb_enc *src)
|
||||
{
|
||||
enc_align(e);
|
||||
/* fd indices inside src are relative to src; a stream that carries fds is not appendable. */
|
||||
if (src->nfds) {
|
||||
VKB_ERR("internal: appending a message that carries file descriptors");
|
||||
e->oom = 1;
|
||||
return;
|
||||
}
|
||||
vkb_enc_bytes(e, src->buf, src->len);
|
||||
}
|
||||
|
||||
static const VkBaseInStructure *next_known(const void *p)
|
||||
{
|
||||
const VkBaseInStructure *b = p;
|
||||
while (b) {
|
||||
if (vkb_struct_size(b->sType)) return b;
|
||||
VKB_ONCE(VKB_LOG_WARN, "pNext: structure type %d is not bridged; dropped (logged once)", (int)b->sType);
|
||||
b = b->pNext;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void vkb_enc_chain(vkb_enc *e, const void *pNext)
|
||||
{
|
||||
const VkBaseInStructure *b = next_known(pNext);
|
||||
if (!b) {
|
||||
vkb_enc_u32(e, VKB_CHAIN_END);
|
||||
return;
|
||||
}
|
||||
vkb_enc_u32(e, (uint32_t)b->sType);
|
||||
vkb_enc_chain_elem(e, b);
|
||||
}
|
||||
|
||||
/* Out-structs that carry caller-provided arrays (the two-call idiom inside a struct). An
|
||||
* "empty" one is always answered with no elements (its elements carry chains of their own). */
|
||||
typedef struct out_arr {
|
||||
VkStructureType t;
|
||||
size_t count_off, ptr_off, elem;
|
||||
int empty;
|
||||
} out_arr;
|
||||
|
||||
#define OA(T, ST, CNT, PTR, ELEM, EMPTY) {ST, offsetof(T, CNT), offsetof(T, PTR), sizeof(ELEM), EMPTY}
|
||||
static const out_arr out_arrays[] = {
|
||||
OA(VkDrmFormatModifierPropertiesListEXT, VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT,
|
||||
drmFormatModifierCount, pDrmFormatModifierProperties, VkDrmFormatModifierPropertiesEXT, 0),
|
||||
OA(VkDrmFormatModifierPropertiesList2EXT, VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_2_EXT,
|
||||
drmFormatModifierCount, pDrmFormatModifierProperties, VkDrmFormatModifierProperties2EXT, 0),
|
||||
OA(VkPhysicalDeviceVulkan14Properties, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_PROPERTIES,
|
||||
copySrcLayoutCount, pCopySrcLayouts, VkImageLayout, 0),
|
||||
OA(VkPhysicalDeviceVulkan14Properties, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_PROPERTIES,
|
||||
copyDstLayoutCount, pCopyDstLayouts, VkImageLayout, 0),
|
||||
OA(VkPhysicalDeviceHostImageCopyProperties, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_IMAGE_COPY_PROPERTIES,
|
||||
copySrcLayoutCount, pCopySrcLayouts, VkImageLayout, 0),
|
||||
OA(VkPhysicalDeviceHostImageCopyProperties, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_IMAGE_COPY_PROPERTIES,
|
||||
copyDstLayoutCount, pCopyDstLayouts, VkImageLayout, 0),
|
||||
OA(VkPhysicalDeviceLayeredApiPropertiesListKHR, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LAYERED_API_PROPERTIES_LIST_KHR,
|
||||
layeredApiCount, pLayeredApis, VkPhysicalDeviceLayeredApiPropertiesKHR, 1),
|
||||
};
|
||||
#undef OA
|
||||
#define N_OUT_ARRAYS (sizeof(out_arrays) / sizeof(out_arrays[0]))
|
||||
|
||||
static uint32_t rd32(const void *base, size_t off) { uint32_t v; memcpy(&v, (const uint8_t *)base + off, 4); return v; }
|
||||
static void wr32(void *base, size_t off, uint32_t v) { memcpy((uint8_t *)base + off, &v, 4); }
|
||||
static void *rdp(const void *base, size_t off) { void *v; memcpy(&v, (const uint8_t *)base + off, sizeof(v)); return v; }
|
||||
static void wrp(void *base, size_t off, void *v) { memcpy((uint8_t *)base + off, &v, sizeof(v)); }
|
||||
|
||||
static const VkBaseInStructure *next_known_out(const void *p)
|
||||
{
|
||||
const VkBaseInStructure *b = p;
|
||||
while (b) {
|
||||
if (vkb_struct_size(b->sType)) return b;
|
||||
VKB_ONCE(VKB_LOG_WARN, "output pNext: structure type %d is not bridged; left unfilled (logged once)", (int)b->sType);
|
||||
b = b->pNext;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void vkb_enc_out_template(vkb_enc *e, const void *s, size_t size)
|
||||
{
|
||||
vkb_enc_raw(e, s, size);
|
||||
const VkBaseInStructure *b = next_known_out(((const VkBaseInStructure *)s)->pNext);
|
||||
while (b) {
|
||||
size_t sz = vkb_struct_size(b->sType);
|
||||
vkb_enc_u32(e, (uint32_t)b->sType);
|
||||
vkb_enc_raw(e, b, sz);
|
||||
for (size_t i = 0; i < N_OUT_ARRAYS; i++)
|
||||
if (out_arrays[i].t == b->sType) vkb_enc_u8(e, !out_arrays[i].empty && rdp(b, out_arrays[i].ptr_off) != NULL);
|
||||
b = next_known_out(b->pNext);
|
||||
}
|
||||
vkb_enc_u32(e, VKB_CHAIN_END);
|
||||
}
|
||||
|
||||
void vkb_enc_out_struct(vkb_enc *e, const void *s, size_t size)
|
||||
{
|
||||
vkb_enc_raw(e, s, size);
|
||||
const VkBaseInStructure *b = ((const VkBaseInStructure *)s)->pNext;
|
||||
for (; b; b = b->pNext) {
|
||||
size_t sz = vkb_struct_size(b->sType);
|
||||
if (!sz) continue;
|
||||
vkb_enc_u32(e, (uint32_t)b->sType);
|
||||
vkb_enc_raw(e, b, sz);
|
||||
for (size_t i = 0; i < N_OUT_ARRAYS; i++) {
|
||||
const out_arr *oa = &out_arrays[i];
|
||||
if (oa->t != b->sType) continue;
|
||||
const void *arr = rdp(b, oa->ptr_off);
|
||||
vkb_enc_u8(e, arr != NULL);
|
||||
if (arr) {
|
||||
uint32_t n = rd32(b, oa->count_off);
|
||||
vkb_enc_u32(e, n);
|
||||
vkb_enc_raw(e, arr, (size_t)n * oa->elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
vkb_enc_u32(e, VKB_CHAIN_END);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ decoder */
|
||||
|
||||
void vkb_dec_init(vkb_dec *d, const void *buf, size_t len, vkb_arena *a, int *fds, int nfds)
|
||||
{
|
||||
memset(d, 0, sizeof(*d));
|
||||
d->buf = buf;
|
||||
d->len = len;
|
||||
d->arena = a;
|
||||
d->fds = fds;
|
||||
d->nfds = nfds;
|
||||
}
|
||||
|
||||
void vkb_dec_bytes(vkb_dec *d, void *dst, size_t n)
|
||||
{
|
||||
if (d->err || d->len - d->pos < n) {
|
||||
d->err = 1;
|
||||
if (dst) memset(dst, 0, n);
|
||||
return;
|
||||
}
|
||||
if (dst) memcpy(dst, d->buf + d->pos, n);
|
||||
d->pos += n;
|
||||
}
|
||||
|
||||
uint8_t vkb_dec_u8(vkb_dec *d) { uint8_t v; vkb_dec_bytes(d, &v, 1); return v; }
|
||||
uint32_t vkb_dec_u32(vkb_dec *d) { uint32_t v; vkb_dec_bytes(d, &v, 4); return v; }
|
||||
uint64_t vkb_dec_u64(vkb_dec *d) { uint64_t v; vkb_dec_bytes(d, &v, 8); return v; }
|
||||
|
||||
static void dec_align(vkb_dec *d)
|
||||
{
|
||||
size_t pad = (8 - (d->pos & 7)) & 7;
|
||||
if (d->len - d->pos < pad) {
|
||||
d->err = 1;
|
||||
return;
|
||||
}
|
||||
d->pos += pad;
|
||||
}
|
||||
|
||||
void vkb_dec_raw_into(vkb_dec *d, void *dst, size_t n)
|
||||
{
|
||||
dec_align(d);
|
||||
vkb_dec_bytes(d, dst, n);
|
||||
}
|
||||
|
||||
const void *vkb_dec_raw_view(vkb_dec *d, size_t n)
|
||||
{
|
||||
dec_align(d);
|
||||
if (d->err || d->len - d->pos < n) {
|
||||
d->err = 1;
|
||||
return NULL;
|
||||
}
|
||||
const void *p = d->buf + d->pos;
|
||||
d->pos += n;
|
||||
return p;
|
||||
}
|
||||
|
||||
void *vkb_dec_alloc(vkb_dec *d, size_t n)
|
||||
{
|
||||
if (d->err) return NULL;
|
||||
if (n > ((size_t)1 << 32)) {
|
||||
d->err = 1;
|
||||
return NULL;
|
||||
}
|
||||
void *p = vkb_arena_alloc(d->arena, n);
|
||||
if (!p) d->err = 1;
|
||||
return p;
|
||||
}
|
||||
|
||||
void *vkb_dec_alloc_zero(vkb_dec *d, size_t n) { return vkb_dec_alloc(d, n); }
|
||||
|
||||
const char *vkb_dec_str(vkb_dec *d)
|
||||
{
|
||||
if (!vkb_dec_u8(d)) return NULL;
|
||||
uint32_t n = vkb_dec_u32(d);
|
||||
if (d->err || d->len - d->pos < n) {
|
||||
d->err = 1;
|
||||
return NULL;
|
||||
}
|
||||
char *s = vkb_dec_alloc(d, (size_t)n + 1);
|
||||
if (!s) return NULL;
|
||||
memcpy(s, d->buf + d->pos, n);
|
||||
s[n] = 0;
|
||||
d->pos += n;
|
||||
return s;
|
||||
}
|
||||
|
||||
const char *const *vkb_dec_strarray(vkb_dec *d)
|
||||
{
|
||||
if (!vkb_dec_u8(d)) return NULL;
|
||||
uint32_t n = vkb_dec_u32(d);
|
||||
if (d->err || n > (d->len - d->pos)) {
|
||||
d->err = 1;
|
||||
return NULL;
|
||||
}
|
||||
const char **a = vkb_dec_alloc(d, (size_t)(n ? n : 1) * sizeof(char *));
|
||||
for (uint32_t i = 0; a && i < n; i++) a[i] = vkb_dec_str(d);
|
||||
return a;
|
||||
}
|
||||
|
||||
const void *vkb_dec_blob(vkb_dec *d, size_t *n)
|
||||
{
|
||||
if (!vkb_dec_u8(d)) {
|
||||
if (n) *n = 0;
|
||||
return NULL;
|
||||
}
|
||||
uint64_t sz = vkb_dec_u64(d);
|
||||
if (n) *n = (size_t)sz;
|
||||
return vkb_dec_raw_view(d, (size_t)sz);
|
||||
}
|
||||
|
||||
void vkb_dec_blob_into(vkb_dec *d, void *dst, size_t cap)
|
||||
{
|
||||
size_t n = 0;
|
||||
const void *p = vkb_dec_blob(d, &n);
|
||||
if (p && dst) memcpy(dst, p, n < cap ? n : cap);
|
||||
}
|
||||
|
||||
int vkb_dec_fd(vkb_dec *d)
|
||||
{
|
||||
uint32_t i = vkb_dec_u32(d);
|
||||
if (i == VKB_FD_NONE || d->err) return -1;
|
||||
if ((int)i >= d->nfds || (d->fd_taken & (1u << i))) {
|
||||
d->err = 1;
|
||||
return -1;
|
||||
}
|
||||
d->fd_taken |= 1u << i;
|
||||
return d->fds[i];
|
||||
}
|
||||
|
||||
void vkb_dec_close_untaken(vkb_dec *d)
|
||||
{
|
||||
for (int i = 0; i < d->nfds; i++) {
|
||||
if (!(d->fd_taken & (1u << i)) && d->fds[i] >= 0) close(d->fds[i]);
|
||||
}
|
||||
d->fd_taken = 0xffffffffu;
|
||||
}
|
||||
|
||||
const void *vkb_dec_chain(vkb_dec *d)
|
||||
{
|
||||
uint32_t t = vkb_dec_u32(d);
|
||||
if (d->err || t == VKB_CHAIN_END) return NULL;
|
||||
size_t sz = vkb_struct_size((VkStructureType)t);
|
||||
if (!sz) {
|
||||
VKB_ERR("protocol: unknown structure type %u in a chain", t);
|
||||
d->err = 1;
|
||||
return NULL;
|
||||
}
|
||||
void *p = vkb_dec_alloc(d, sz);
|
||||
if (!p) return NULL;
|
||||
vkb_dec_chain_elem(d, (VkStructureType)t, p);
|
||||
return p;
|
||||
}
|
||||
|
||||
void vkb_dec_out_template(vkb_dec *d, void *dst, size_t size)
|
||||
{
|
||||
vkb_dec_raw_into(d, dst, size);
|
||||
VkBaseOutStructure *prev = dst;
|
||||
prev->pNext = NULL;
|
||||
for (;;) {
|
||||
uint32_t t = vkb_dec_u32(d);
|
||||
if (d->err || t == VKB_CHAIN_END) break;
|
||||
size_t sz = vkb_struct_size((VkStructureType)t);
|
||||
if (!sz) {
|
||||
d->err = 1;
|
||||
break;
|
||||
}
|
||||
VkBaseOutStructure *el = vkb_dec_alloc(d, sz);
|
||||
if (!el) break;
|
||||
vkb_dec_raw_into(d, el, sz);
|
||||
el->pNext = NULL;
|
||||
for (size_t i = 0; i < N_OUT_ARRAYS; i++) {
|
||||
const out_arr *oa = &out_arrays[i];
|
||||
if (oa->t != (VkStructureType)t) continue;
|
||||
void *arr = NULL;
|
||||
if (vkb_dec_u8(d)) {
|
||||
uint32_t n = rd32(el, oa->count_off);
|
||||
arr = vkb_dec_alloc(d, (size_t)(n ? n : 1) * oa->elem);
|
||||
} else {
|
||||
wr32(el, oa->count_off, 0);
|
||||
}
|
||||
wrp(el, oa->ptr_off, arr);
|
||||
}
|
||||
prev->pNext = el;
|
||||
prev = el;
|
||||
}
|
||||
}
|
||||
|
||||
void vkb_dec_out_struct(vkb_dec *d, void *dst, size_t size)
|
||||
{
|
||||
VkBaseOutStructure *top = dst;
|
||||
VkBaseOutStructure *keep = top->pNext;
|
||||
vkb_dec_raw_into(d, dst, size);
|
||||
top->pNext = keep;
|
||||
VkBaseOutStructure *app = (VkBaseOutStructure *)next_known_out(keep);
|
||||
for (;;) {
|
||||
uint32_t t = vkb_dec_u32(d);
|
||||
if (d->err || t == VKB_CHAIN_END) break;
|
||||
size_t sz = vkb_struct_size((VkStructureType)t);
|
||||
const uint8_t *src = vkb_dec_raw_view(d, sz);
|
||||
if (!src || !sz) {
|
||||
d->err = 1;
|
||||
break;
|
||||
}
|
||||
if (!app || (uint32_t)app->sType != t) {
|
||||
/* The server answered for a structure we did not send: protocol mismatch. */
|
||||
d->err = 1;
|
||||
break;
|
||||
}
|
||||
/* The caller's own pointers and array capacities survive the copy. */
|
||||
VkBaseOutStructure *keep_next = app->pNext;
|
||||
void *app_arr[4] = {0};
|
||||
uint32_t cap[4] = {0};
|
||||
int na = 0;
|
||||
for (size_t i = 0; i < N_OUT_ARRAYS && na < 4; i++) {
|
||||
if (out_arrays[i].t != (VkStructureType)t) continue;
|
||||
app_arr[na] = rdp(app, out_arrays[i].ptr_off);
|
||||
cap[na] = rd32(app, out_arrays[i].count_off);
|
||||
na++;
|
||||
}
|
||||
memcpy(app, src, sz);
|
||||
app->pNext = keep_next;
|
||||
na = 0;
|
||||
for (size_t i = 0; i < N_OUT_ARRAYS && na < 4; i++) {
|
||||
const out_arr *oa = &out_arrays[i];
|
||||
if (oa->t != (VkStructureType)t) continue;
|
||||
wrp(app, oa->ptr_off, app_arr[na]);
|
||||
uint8_t present = vkb_dec_u8(d);
|
||||
uint32_t n = 0;
|
||||
const uint8_t *arr_src = NULL;
|
||||
if (present) {
|
||||
n = vkb_dec_u32(d);
|
||||
arr_src = vkb_dec_raw_view(d, (size_t)n * oa->elem);
|
||||
}
|
||||
if (oa->empty) {
|
||||
wr32(app, oa->count_off, 0);
|
||||
} else if (app_arr[na] && arr_src) {
|
||||
uint32_t m = n < cap[na] ? n : cap[na];
|
||||
memcpy(app_arr[na], arr_src, (size_t)m * oa->elem);
|
||||
wr32(app, oa->count_off, m);
|
||||
}
|
||||
na++;
|
||||
}
|
||||
app = (VkBaseOutStructure *)next_known_out(keep_next);
|
||||
}
|
||||
}
|
||||
|
||||
void vkb_close_fd(int fd)
|
||||
{
|
||||
if (fd >= 0) close(fd);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ predicates */
|
||||
|
||||
int vkb_desc_has_image(VkDescriptorType t)
|
||||
{
|
||||
return t == VK_DESCRIPTOR_TYPE_SAMPLER || t == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER ||
|
||||
t == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE || t == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE ||
|
||||
t == VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT || t == VK_DESCRIPTOR_TYPE_SAMPLE_WEIGHT_IMAGE_QCOM ||
|
||||
t == VK_DESCRIPTOR_TYPE_BLOCK_MATCH_IMAGE_QCOM;
|
||||
}
|
||||
|
||||
int vkb_desc_has_buffer(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;
|
||||
}
|
||||
|
||||
int vkb_desc_has_texel(VkDescriptorType t)
|
||||
{
|
||||
return t == VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER || t == VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER;
|
||||
}
|
||||
|
||||
int vkb_gp_has_tess(const VkGraphicsPipelineCreateInfo *s)
|
||||
{
|
||||
if (!s->pStages) return 1; /* library pieces: trust the pointer */
|
||||
for (uint32_t i = 0; i < s->stageCount; i++) {
|
||||
if (s->pStages[i].stage & (VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT | VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ transport */
|
||||
|
||||
int vkb_send_msg(int sock, const vkb_msg_hdr *h, const void *payload, const int *fds, int nfds)
|
||||
{
|
||||
struct iovec iov[2];
|
||||
iov[0].iov_base = (void *)h;
|
||||
iov[0].iov_len = sizeof(*h);
|
||||
iov[1].iov_base = (void *)payload;
|
||||
iov[1].iov_len = h->size;
|
||||
union {
|
||||
char buf[CMSG_SPACE(sizeof(int) * VKB_MAX_FDS)];
|
||||
struct cmsghdr align;
|
||||
} cm;
|
||||
struct msghdr msg;
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.msg_iov = iov;
|
||||
msg.msg_iovlen = h->size ? 2 : 1;
|
||||
if (nfds > 0) {
|
||||
memset(&cm, 0, sizeof(cm));
|
||||
msg.msg_control = cm.buf;
|
||||
msg.msg_controllen = CMSG_SPACE(sizeof(int) * nfds);
|
||||
struct cmsghdr *c = CMSG_FIRSTHDR(&msg);
|
||||
c->cmsg_level = SOL_SOCKET;
|
||||
c->cmsg_type = SCM_RIGHTS;
|
||||
c->cmsg_len = CMSG_LEN(sizeof(int) * nfds);
|
||||
memcpy(CMSG_DATA(c), fds, sizeof(int) * nfds);
|
||||
}
|
||||
size_t total = sizeof(*h) + h->size;
|
||||
size_t sent = 0;
|
||||
while (sent < total) {
|
||||
ssize_t r = sendmsg(sock, &msg, MSG_NOSIGNAL);
|
||||
if (r < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
return -1;
|
||||
}
|
||||
sent += (size_t)r;
|
||||
if (sent >= total) break;
|
||||
/* Partial write: advance the iovecs; fds went with the first chunk. */
|
||||
msg.msg_control = NULL;
|
||||
msg.msg_controllen = 0;
|
||||
size_t skip = (size_t)r;
|
||||
while (msg.msg_iovlen && skip >= msg.msg_iov[0].iov_len) {
|
||||
skip -= msg.msg_iov[0].iov_len;
|
||||
msg.msg_iov++;
|
||||
msg.msg_iovlen--;
|
||||
}
|
||||
if (msg.msg_iovlen) {
|
||||
msg.msg_iov[0].iov_base = (uint8_t *)msg.msg_iov[0].iov_base + skip;
|
||||
msg.msg_iov[0].iov_len -= skip;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_full(int sock, void *buf, size_t n)
|
||||
{
|
||||
size_t got = 0;
|
||||
while (got < n) {
|
||||
ssize_t r = recv(sock, (uint8_t *)buf + got, n - got, 0);
|
||||
if (r == 0) return -1;
|
||||
if (r < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
return -1;
|
||||
}
|
||||
got += (size_t)r;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vkb_recv_msg(int sock, vkb_msg_hdr *h, uint8_t **payload, size_t *cap, int *fds, int *nfds)
|
||||
{
|
||||
union {
|
||||
char buf[CMSG_SPACE(sizeof(int) * VKB_MAX_FDS)];
|
||||
struct cmsghdr align;
|
||||
} cm;
|
||||
struct iovec iov = {h, sizeof(*h)};
|
||||
struct msghdr msg;
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
msg.msg_control = cm.buf;
|
||||
msg.msg_controllen = sizeof(cm.buf);
|
||||
*nfds = 0;
|
||||
ssize_t r;
|
||||
do {
|
||||
r = recvmsg(sock, &msg, MSG_CMSG_CLOEXEC);
|
||||
} while (r < 0 && errno == EINTR);
|
||||
if (r <= 0) return -1;
|
||||
for (struct cmsghdr *c = CMSG_FIRSTHDR(&msg); c; c = CMSG_NXTHDR(&msg, c)) {
|
||||
if (c->cmsg_level == SOL_SOCKET && c->cmsg_type == SCM_RIGHTS) {
|
||||
int n = (int)((c->cmsg_len - CMSG_LEN(0)) / sizeof(int));
|
||||
for (int i = 0; i < n; i++) {
|
||||
int fd;
|
||||
memcpy(&fd, CMSG_DATA(c) + i * sizeof(int), sizeof(int));
|
||||
if (*nfds < VKB_MAX_FDS) fds[(*nfds)++] = fd;
|
||||
else close(fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((size_t)r < sizeof(*h) && read_full(sock, (uint8_t *)h + r, sizeof(*h) - (size_t)r) < 0) goto fail;
|
||||
if (h->magic != VKB_MAGIC) {
|
||||
VKB_ERR("transport: bad message magic 0x%08x", h->magic);
|
||||
goto fail;
|
||||
}
|
||||
if (h->size) {
|
||||
if (h->size > *cap) {
|
||||
size_t nc = *cap ? *cap : 65536;
|
||||
while (nc < h->size) nc *= 2;
|
||||
uint8_t *nb = realloc(*payload, nc);
|
||||
if (!nb) goto fail;
|
||||
*payload = nb;
|
||||
*cap = nc;
|
||||
}
|
||||
if (read_full(sock, *payload, h->size) < 0) goto fail;
|
||||
}
|
||||
return 0;
|
||||
fail:
|
||||
for (int i = 0; i < *nfds; i++) close(fds[i]);
|
||||
*nfds = 0;
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Wire primitives shared by the bridge's client ICD (glibc) and server (bionic).
|
||||
* See tools/vkbridge/README.md for the protocol.
|
||||
*/
|
||||
#ifndef VKB_WIRE_H
|
||||
#define VKB_WIRE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "gen/vkb_gen.h"
|
||||
|
||||
#define VKB_MAX_FDS 16
|
||||
#define VKB_CHAIN_END 0x7FFFFFFFu
|
||||
#define VKB_FD_NONE 0xFFFFFFFFu
|
||||
|
||||
/* ---- arena: per-call scratch memory on the decoding side ---- */
|
||||
typedef struct vkb_arena_block vkb_arena_block;
|
||||
typedef struct vkb_arena {
|
||||
vkb_arena_block *head;
|
||||
size_t used_total;
|
||||
} vkb_arena;
|
||||
void *vkb_arena_alloc(vkb_arena *a, size_t n);
|
||||
void vkb_arena_reset(vkb_arena *a);
|
||||
void vkb_arena_free(vkb_arena *a);
|
||||
|
||||
/* ---- encoder ---- */
|
||||
struct vkb_enc {
|
||||
uint8_t *buf;
|
||||
size_t len, cap;
|
||||
int fds[VKB_MAX_FDS];
|
||||
int nfds;
|
||||
int oom;
|
||||
};
|
||||
void vkb_enc_init(vkb_enc *e);
|
||||
void vkb_enc_reset(vkb_enc *e); /* keeps the buffer */
|
||||
void vkb_enc_free(vkb_enc *e);
|
||||
void vkb_enc_bytes(vkb_enc *e, const void *p, size_t n);
|
||||
void vkb_enc_u8(vkb_enc *e, uint8_t v);
|
||||
void vkb_enc_u32(vkb_enc *e, uint32_t v);
|
||||
void vkb_enc_u64(vkb_enc *e, uint64_t v);
|
||||
size_t vkb_enc_raw(vkb_enc *e, const void *p, size_t n); /* 8-aligned; returns offset */
|
||||
void vkb_enc_patch_u64(vkb_enc *e, size_t off, uint64_t v);
|
||||
void vkb_enc_str(vkb_enc *e, const char *s);
|
||||
void vkb_enc_strarray(vkb_enc *e, const char *const *a, uint32_t n);
|
||||
void vkb_enc_blob(vkb_enc *e, const void *p, size_t n);
|
||||
void vkb_enc_fd(vkb_enc *e, int fd); /* sent with the message */
|
||||
void vkb_enc_chain(vkb_enc *e, const void *pNext);
|
||||
void vkb_enc_out_template(vkb_enc *e, const void *s, size_t size); /* client */
|
||||
void vkb_enc_out_struct(vkb_enc *e, const void *s, size_t size); /* server */
|
||||
void vkb_enc_append(vkb_enc *e, const vkb_enc *src); /* bytes + fds */
|
||||
|
||||
/* ---- decoder ---- */
|
||||
struct vkb_dec {
|
||||
const uint8_t *buf;
|
||||
size_t pos, len;
|
||||
int err;
|
||||
vkb_arena *arena;
|
||||
int *fds;
|
||||
int nfds;
|
||||
uint32_t fd_taken; /* bitmask of fds handed out */
|
||||
};
|
||||
void vkb_dec_init(vkb_dec *d, const void *buf, size_t len, vkb_arena *a, int *fds, int nfds);
|
||||
void vkb_dec_bytes(vkb_dec *d, void *dst, size_t n);
|
||||
uint8_t vkb_dec_u8(vkb_dec *d);
|
||||
uint32_t vkb_dec_u32(vkb_dec *d);
|
||||
uint64_t vkb_dec_u64(vkb_dec *d);
|
||||
void vkb_dec_raw_into(vkb_dec *d, void *dst, size_t n);
|
||||
const void *vkb_dec_raw_view(vkb_dec *d, size_t n);
|
||||
void *vkb_dec_alloc(vkb_dec *d, size_t n); /* zeroed */
|
||||
void *vkb_dec_alloc_zero(vkb_dec *d, size_t n);
|
||||
const char *vkb_dec_str(vkb_dec *d);
|
||||
const char *const *vkb_dec_strarray(vkb_dec *d);
|
||||
const void *vkb_dec_blob(vkb_dec *d, size_t *n);
|
||||
void vkb_dec_blob_into(vkb_dec *d, void *dst, size_t cap);
|
||||
int vkb_dec_fd(vkb_dec *d); /* caller owns the fd */
|
||||
const void *vkb_dec_chain(vkb_dec *d);
|
||||
void vkb_dec_out_template(vkb_dec *d, void *dst, size_t size); /* server */
|
||||
void vkb_dec_out_struct(vkb_dec *d, void *dst, size_t size); /* client */
|
||||
void vkb_dec_close_untaken(vkb_dec *d);
|
||||
|
||||
void vkb_close_fd(int fd);
|
||||
|
||||
/* Descriptor-type predicates used by generated encoders. */
|
||||
int vkb_desc_has_image(VkDescriptorType t);
|
||||
int vkb_desc_has_buffer(VkDescriptorType t);
|
||||
int vkb_desc_has_texel(VkDescriptorType t);
|
||||
int vkb_gp_has_tess(const VkGraphicsPipelineCreateInfo *s);
|
||||
/* Defined per side: on the client it consults the command buffer's level. */
|
||||
int vkb_cb_begin_is_secondary(const VkCommandBufferBeginInfo *s);
|
||||
|
||||
/* Defined per side: client maps a dispatchable handle to the server's value; server: identity. */
|
||||
uint64_t vkb_remote(const void *dispatchable);
|
||||
|
||||
/* ---- transport ---- */
|
||||
typedef struct vkb_msg_hdr {
|
||||
uint32_t magic; /* VKB_MAGIC */
|
||||
uint32_t size; /* payload bytes after the header */
|
||||
uint32_t cmd; /* request: command id; reply: status (0 = ok) */
|
||||
uint32_t table; /* request: server dispatch table id */
|
||||
uint32_t nfds;
|
||||
uint32_t flags;
|
||||
/* Ordering of asynchronous requests across a process's connections: an async request has
|
||||
* seq > 0; every request carries barrier = the highest async seq its process had issued
|
||||
* before it, and is not executed until all of those have been. */
|
||||
uint64_t seq;
|
||||
uint64_t barrier;
|
||||
} vkb_msg_hdr;
|
||||
#define VKB_MAGIC 0x32424b56u /* "VKB2" */
|
||||
#define VKB_F_NOREPLY 1u
|
||||
|
||||
/* Reply status codes (vkb_msg_hdr.cmd in replies). */
|
||||
#define VKB_ST_OK 0u
|
||||
#define VKB_ST_BAD_MESSAGE 1u
|
||||
#define VKB_ST_MISSING 2u
|
||||
#define VKB_ST_BAD_TABLE 3u
|
||||
|
||||
int vkb_send_msg(int sock, const vkb_msg_hdr *h, const void *payload, const int *fds, int nfds);
|
||||
/* Receives one message; *payload is malloc'd (or reused when *cap suffices). Returns 0 on
|
||||
* success, -1 on EOF/error. fds[] receives up to VKB_MAX_FDS descriptors. */
|
||||
int vkb_recv_msg(int sock, vkb_msg_hdr *h, uint8_t **payload, size_t *cap, int *fds, int *nfds);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1 @@
|
||||
out/
|
||||
@@ -0,0 +1,39 @@
|
||||
# Host (x86_64 Linux) build of both bridge halves, for testing against the desktop's own Vulkan
|
||||
# driver: the server loads libvulkan.so.1 (pick a driver with VK_DRIVER_FILES in ITS environment),
|
||||
# the client ICD is what test programs load (VK_DRIVER_FILES=$(OUT)/vkbridge_icd.json).
|
||||
# make -C tools/vkbridge/host build
|
||||
# make -C tools/vkbridge/host test vulkaninfo + vkcube-headless smoke tests
|
||||
ROOT := $(abspath ../../..)
|
||||
VKB := $(ROOT)/tools/vkbridge
|
||||
SRV := $(ROOT)/app/src/main/cpp/vkbridge
|
||||
OUT ?= $(VKB)/host/out
|
||||
CC ?= gcc
|
||||
CFLAGS ?= -O1 -g
|
||||
WARN := -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -Wno-sign-compare
|
||||
INC := -I$(VKB)/third_party/Vulkan-Headers-1.4.341/include -I$(VKB)/common -I$(VKB)/common/gen
|
||||
COMMON_SRC := $(VKB)/common/vkb_wire.c $(VKB)/common/gen/vkb_gen_structs.c
|
||||
SERVER_SRC := $(wildcard $(SRV)/*.c) $(VKB)/common/gen/vkb_gen_server.c $(COMMON_SRC)
|
||||
CLIENT_SRC := $(wildcard $(VKB)/client/*.c) $(VKB)/common/gen/vkb_gen_client.c $(COMMON_SRC)
|
||||
|
||||
all: $(OUT)/vkbridge-server $(OUT)/libvulkan_droidbridge.so $(OUT)/vkbridge_icd.json
|
||||
|
||||
$(OUT)/vkbridge-server: $(SERVER_SRC) $(wildcard $(SRV)/*.h $(VKB)/common/*.h $(VKB)/common/gen/*.h)
|
||||
@mkdir -p $(OUT)
|
||||
$(CC) $(CFLAGS) $(WARN) $(INC) -I$(SRV) -o $@ $(SERVER_SRC) -ldl -lpthread -lm -rdynamic
|
||||
|
||||
$(OUT)/libvulkan_droidbridge.so: $(CLIENT_SRC) $(wildcard $(VKB)/client/*.h $(VKB)/common/*.h $(VKB)/common/gen/*.h)
|
||||
@mkdir -p $(OUT)
|
||||
$(CC) $(CFLAGS) $(WARN) -fPIC -shared -fvisibility=hidden -DVKB_CLIENT $(INC) -I$(VKB)/client -I$(VKB)/client/compat \
|
||||
-o $@ $(CLIENT_SRC) -ldl -lpthread -Wl,--no-undefined
|
||||
|
||||
$(OUT)/vkbridge_icd.json:
|
||||
@mkdir -p $(OUT)
|
||||
printf '{"file_format_version":"1.0.0","ICD":{"library_path":"%s","api_version":"1.4.341"}}\n' "$(OUT)/libvulkan_droidbridge.so" > $@
|
||||
|
||||
test: all
|
||||
$(VKB)/host/run_tests.sh $(OUT)
|
||||
|
||||
clean:
|
||||
rm -rf $(OUT)
|
||||
|
||||
.PHONY: all test clean
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
# Source this: starts a host vkbridge server on a desktop driver (VKB_HOST_ICD, default RADV) and
|
||||
# points this shell's Vulkan programs at the bridge ICD.
|
||||
# [VKB_HOST_ICD=...] . tools/vkbridge/host/bridge_env.sh
|
||||
_vkb_host=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
_vkb_icd=${VKB_HOST_ICD:-/usr/share/vulkan/icd.d/radeon_icd.json}
|
||||
export VKBRIDGE_SOCKET=${VKBRIDGE_SOCKET:-${XDG_RUNTIME_DIR:-/tmp}/vkbridge-host.sock}
|
||||
pgrep -x vkbridge-server >/dev/null && kill $(pgrep -x vkbridge-server) 2>/dev/null; sleep 0.3
|
||||
rm -f "$VKBRIDGE_SOCKET"
|
||||
VK_DRIVER_FILES=$_vkb_icd VK_ICD_FILENAMES= nohup "$_vkb_host/out/vkbridge-server" --socket "$VKBRIDGE_SOCKET" \
|
||||
--log "$_vkb_host/out/server.log" >/dev/null 2>&1 &
|
||||
for _i in $(seq 50); do [ -S "$VKBRIDGE_SOCKET" ] && break; sleep 0.1; done
|
||||
export VK_DRIVER_FILES=$_vkb_host/out/vkbridge_icd.json
|
||||
echo "vkbridge server up ($_vkb_icd) at $VKBRIDGE_SOCKET"
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# Runs tests/d3d11_check.exe under Proton Experimental's wine + DXVK. Needs a prefix dir with the
|
||||
# exe, DXVK's d3d11/dxgi and vkd3d's DLLs (see README). VK_DRIVER_FILES picks native or bridge.
|
||||
P="$HOME/.steam/steam/steamapps/common/Proton - Experimental/files"
|
||||
W=${VKB_WINE_DIR:?set VKB_WINE_DIR}
|
||||
cd "$W/app" && WINEPREFIX=$W/pfx WINEDEBUG=-all WINEDLLOVERRIDES="d3d11,dxgi=n,b" DXVK_LOG_PATH=$W timeout 180 "$P/bin/wine" d3d11_check.exe 2>&1 \
|
||||
| grep -v "^info:\|^warn:\|fixme\|err:hid\|wineserver"
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
# Phase 3 host test: Arch's gamescope (the runtime's distribution) in a container WITHOUT any GPU
|
||||
# device, drawing only through the bridge, nested on the desktop's Wayland. Run after
|
||||
# bridge_env.sh. Usage: gamescope_test.sh [command inside gamescope...]
|
||||
host=$(cd "$(dirname "$0")" && pwd)
|
||||
R=${XDG_RUNTIME_DIR:-/run/user/$(id -u)}
|
||||
cmd=${*:-vkcube --c 600}
|
||||
exec docker run --rm ${VKB_DOCKER_EXTRA:-} --tmpfs /run/xdg:mode=0700 \
|
||||
-e XDG_RUNTIME_DIR=/run/xdg -e WAYLAND_DISPLAY=host-wayland -e HOME=/root \
|
||||
-v "$R/${WAYLAND_DISPLAY:-wayland-0}:/run/host-wayland" -v "$VKBRIDGE_SOCKET:/run/vkbridge.sock" \
|
||||
-v "$host/out:$host/out" -v "$host/out/shots:/shots" -e VK_DRIVER_FILES=$host/out/vkbridge_icd.json -e VKBRIDGE_SOCKET=/run/vkbridge.sock \
|
||||
-e VKBRIDGE_DRM_RENDER=${VKBRIDGE_DRM_RENDER:-} -e VKBRIDGE_HIDE_EXTS=${VKBRIDGE_HIDE_EXTS:-} -e VKBRIDGE_MAX_API=${VKBRIDGE_MAX_API:-} -e VKBRIDGE_LOG_LEVEL=${VKBRIDGE_LOG_LEVEL:-2} -e VKBRIDGE_WSI_DUMP=${VKBRIDGE_WSI_DUMP:-} \
|
||||
vkb-arch-gamescope bash -c "ln -s /run/host-wayland /run/xdg/host-wayland; gamescope --backend wayland --expose-wayland -W 960 -H 540 -w 960 -h 540 -- $cmd"
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
# Source this: a host bridge server that behaves like the Mali-G720 driver as far as Phase 0
|
||||
# found (no BCn, clip/cull distance, vertex attribute divisor, maintenance5, push descriptors, robustness2; Vulkan 1.3).
|
||||
_vkb_host=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
export VKBRIDGE_SOCKET=${XDG_RUNTIME_DIR:-/tmp}/vkbridge-host.sock
|
||||
pgrep -x vkbridge-server >/dev/null && kill $(pgrep -x vkbridge-server) 2>/dev/null; sleep 0.3
|
||||
rm -f "$VKBRIDGE_SOCKET"
|
||||
VKBRIDGE_FAKE_MISSING=${VKB_MISSING:-bc,clip,cull,divisor,maint5,depthclip,push,robustness2} VK_DRIVER_FILES=${VKB_HOST_ICD:-/usr/share/vulkan/icd.d/radeon_icd.json} \
|
||||
nohup "$_vkb_host/out/vkbridge-server" --socket "$VKBRIDGE_SOCKET" --log "$_vkb_host/out/server.log" >/dev/null 2>&1 &
|
||||
for _i in $(seq 50); do [ -S "$VKBRIDGE_SOCKET" ] && break; sleep 0.1; done
|
||||
chmod 666 "$VKBRIDGE_SOCKET"
|
||||
export VK_DRIVER_FILES=$_vkb_host/out/vkbridge_icd.json
|
||||
export VKBRIDGE_HIDE_EXTS=VK_KHR_maintenance5,VK_KHR_push_descriptor,VK_EXT_robustness2,VK_KHR_robustness2,VK_EXT_vertex_attribute_divisor,VK_KHR_vertex_attribute_divisor,VK_EXT_depth_clip_enable
|
||||
export VKBRIDGE_MAX_API=1.3
|
||||
echo "vkbridge server up, imitating Mali's gaps"
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
# Host regression suite for the bridge: every automated check, in two server modes -
|
||||
# plain the desktop driver as it is (RADV)
|
||||
# mali the desktop driver with Mali-G720's known gaps imitated (mali_env.sh)
|
||||
# Needs: make (out/), Proton Experimental for the DXVK check (VKB_WINE_DIR prepared, see README),
|
||||
# docker image vkb-arch-gamescope for the gamescope/Zink check (skipped if absent).
|
||||
cd "$(dirname "$0")"
|
||||
pass=0; fail=0
|
||||
result() { if [ "$1" = 0 ]; then pass=$((pass+1)); echo " ok $2"; else fail=$((fail+1)); echo " FAIL $2"; fi; }
|
||||
run_mode() {
|
||||
local mode=$1
|
||||
echo "== $mode"
|
||||
if [ "$mode" = mali ]; then . ./mali_env.sh >/dev/null; else unset VKBRIDGE_HIDE_EXTS VKBRIDGE_MAX_API; . ./bridge_env.sh >/dev/null; fi
|
||||
timeout 120 ./out/vkbridge-smoke >out/t-smoke.log 2>&1; result $? "smoke ($(tail -1 out/t-smoke.log))"
|
||||
timeout 180 ./out/bcn_check >out/t-bcn.log 2>&1; result $? "bcn_check ($(tail -1 out/t-bcn.log))"
|
||||
if [ -n "${VKB_WINE_DIR:-}" ] && [ -d "$VKB_WINE_DIR/app" ]; then
|
||||
./d3d11_test.sh >out/t-d3d11.log 2>&1; grep -q "^OK:" out/t-d3d11.log; result $? "d3d11_check via DXVK ($(grep -E '^(OK|FAILED)' out/t-d3d11.log))"
|
||||
fi
|
||||
timeout 60 vkcube-wayland --c 120 >out/t-vkcube.log 2>&1; result $? "vkcube-wayland (dma-buf)"
|
||||
VKBRIDGE_WSI_SHM=1 timeout 60 vkcube-wayland --c 120 >>out/t-vkcube.log 2>&1; result $? "vkcube-wayland (wl_shm)"
|
||||
timeout 60 vkcube --c 120 >>out/t-vkcube.log 2>&1; result $? "vkcube (X11)"
|
||||
if docker image inspect vkb-arch-gamescope >/dev/null 2>&1; then
|
||||
VKB_DOCKER_EXTRA="--device /dev/dri/renderD128" VKBRIDGE_DRM_RENDER=226:128 timeout 120 ./gamescope_test.sh \
|
||||
"bash -c 'export MESA_LOADER_DRIVER_OVERRIDE=zink GALLIUM_DRIVER=zink LIBGL_KOPPER_DRI2=true; timeout 20 glxgears 2>&1 | grep -m1 frames'" \
|
||||
>out/t-gamescope-$mode.log 2>&1
|
||||
grep -q "frames in" out/t-gamescope-$mode.log; result $? "gamescope + Zink glxgears ($(grep -m1 -o '[0-9.]* FPS' out/t-gamescope-$mode.log))"
|
||||
fi
|
||||
}
|
||||
run_mode plain
|
||||
run_mode mali
|
||||
echo "== $pass passed, $fail failed"
|
||||
[ "$fail" = 0 ]
|
||||
@@ -0,0 +1,364 @@
|
||||
#version 450
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* BCn block decoder for the Mali bridge's BC texture emulation. One invocation decodes one 4x4
|
||||
* block from the copy's source buffer and stores it into the decoded image (a 2D array view of one
|
||||
* mip level). The arithmetic follows bcdec (third_party/bcdec, MIT/Unlicense, Sergii Kudlai) so the
|
||||
* output can be checked bit-for-bit against it. Built once per format family (FMT_BC1 ... FMT_BC7).
|
||||
*/
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
|
||||
layout(std430, binding = 0) readonly buffer Src { uint w[]; } src;
|
||||
#if defined(FMT_BC6H)
|
||||
layout(rgba16f, binding = 1) uniform writeonly image2DArray dst;
|
||||
#elif defined(FMT_BC4)
|
||||
layout(r8, binding = 1) uniform writeonly image2DArray dst;
|
||||
#elif defined(FMT_BC5)
|
||||
layout(rg8, binding = 1) uniform writeonly image2DArray dst;
|
||||
#else
|
||||
layout(rgba8, binding = 1) uniform writeonly image2DArray dst;
|
||||
#endif
|
||||
|
||||
layout(push_constant) uniform PC {
|
||||
uint src_word; /* first block, in 32-bit words from the bound range's start */
|
||||
uint row_blocks; /* blocks per row in the buffer */
|
||||
uint layer_blocks; /* blocks per layer in the buffer */
|
||||
uint flags; /* 1: BC1 without alpha (alpha forced to 1); 2: signed (BC4/5/6H); first layer << 8 */
|
||||
ivec2 dst_off; /* texel offset of the region in the image */
|
||||
uvec2 extent; /* texels to write */
|
||||
} pc;
|
||||
|
||||
#extension GL_GOOGLE_include_directive : require
|
||||
#include "bc_tables.glsl"
|
||||
|
||||
uint bits(uvec4 b, uint pos, uint n)
|
||||
{
|
||||
if (n == 0u) return 0u;
|
||||
uint wi = pos >> 5, o = pos & 31u;
|
||||
uint v = b[wi] >> o;
|
||||
if (o + n > 32u && wi < 3u) v |= b[wi + 1u] << (32u - o);
|
||||
return n == 32u ? v : (v & ((1u << n) - 1u));
|
||||
}
|
||||
|
||||
int sext(int v, int n) { return (v << (32 - n)) >> (32 - n); }
|
||||
|
||||
void put(uint tx, uint ty, vec4 v)
|
||||
{
|
||||
uvec2 blk = gl_GlobalInvocationID.xy;
|
||||
uvec2 p = blk * 4u + uvec2(tx, ty);
|
||||
if (p.x < pc.extent.x && p.y < pc.extent.y)
|
||||
imageStore(dst, ivec3(pc.dst_off + ivec2(p), int(gl_GlobalInvocationID.z + (pc.flags >> 8))), v);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ BC1-3 */
|
||||
|
||||
uvec4 rgb565(uint c)
|
||||
{
|
||||
uint r = (c >> 11) & 31u, g = (c >> 5) & 63u, b = c & 31u;
|
||||
return uvec4((r * 527u + 23u) >> 6, (g * 259u + 33u) >> 6, (b * 527u + 23u) >> 6, 255u);
|
||||
}
|
||||
|
||||
void color_block(uint lo, uint idx, bool opaque, inout uvec4 texels[16])
|
||||
{
|
||||
uint c0 = lo & 0xffffu, c1 = lo >> 16;
|
||||
uint r0 = (c0 >> 11) & 31u, g0 = (c0 >> 5) & 63u, b0 = c0 & 31u;
|
||||
uint r1 = (c1 >> 11) & 31u, g1 = (c1 >> 5) & 63u, b1 = c1 & 31u;
|
||||
uvec4 ref[4];
|
||||
ref[0] = rgb565(c0);
|
||||
ref[1] = rgb565(c1);
|
||||
if (c0 > c1 || opaque) {
|
||||
ref[2] = uvec4(((2u * r0 + r1) * 351u + 61u) >> 7, ((2u * g0 + g1) * 2763u + 1039u) >> 11, ((2u * b0 + b1) * 351u + 61u) >> 7, 255u);
|
||||
ref[3] = uvec4(((r0 + r1 * 2u) * 351u + 61u) >> 7, ((g0 + g1 * 2u) * 2763u + 1039u) >> 11, ((b0 + b1 * 2u) * 351u + 61u) >> 7, 255u);
|
||||
} else {
|
||||
ref[2] = uvec4(((r0 + r1) * 1053u + 125u) >> 8, ((g0 + g1) * 4145u + 1019u) >> 11, ((b0 + b1) * 1053u + 125u) >> 8, 255u);
|
||||
ref[3] = uvec4(0u);
|
||||
}
|
||||
for (uint t = 0u; t < 16u; t++) texels[t] = ref[(idx >> (2u * t)) & 3u];
|
||||
}
|
||||
|
||||
/* BC3's alpha block as bcdec decodes it (integer division). */
|
||||
void smooth_alpha(uint lo, uint hi, out uint a[16])
|
||||
{
|
||||
uint a0 = lo & 0xffu, a1 = (lo >> 8) & 0xffu;
|
||||
uint pal[8];
|
||||
pal[0] = a0;
|
||||
pal[1] = a1;
|
||||
if (a0 > a1) {
|
||||
pal[2] = (6u * a0 + a1) / 7u; pal[3] = (5u * a0 + 2u * a1) / 7u; pal[4] = (4u * a0 + 3u * a1) / 7u;
|
||||
pal[5] = (3u * a0 + 4u * a1) / 7u; pal[6] = (2u * a0 + 5u * a1) / 7u; pal[7] = (a0 + 6u * a1) / 7u;
|
||||
} else {
|
||||
pal[2] = (4u * a0 + a1) / 5u; pal[3] = (3u * a0 + 2u * a1) / 5u; pal[4] = (2u * a0 + 3u * a1) / 5u;
|
||||
pal[5] = (a0 + 4u * a1) / 5u; pal[6] = 0u; pal[7] = 255u;
|
||||
}
|
||||
uvec4 b = uvec4(lo, hi, 0u, 0u);
|
||||
for (uint t = 0u; t < 16u; t++) a[t] = pal[bits(b, 16u + 3u * t, 3u)];
|
||||
}
|
||||
|
||||
/* BC4/BC5 channel (bcdec's precise variant), as the byte pattern of the unsigned/signed value. */
|
||||
void bc4_channel(uint lo, uint hi, bool sgn, out uint v[16])
|
||||
{
|
||||
int e0, e1;
|
||||
if (sgn) {
|
||||
e0 = max(sext(int(lo & 0xffu), 8), -127);
|
||||
e1 = max(sext(int((lo >> 8) & 0xffu), 8), -127);
|
||||
} else {
|
||||
e0 = int(lo & 0xffu);
|
||||
e1 = int((lo >> 8) & 0xffu);
|
||||
}
|
||||
const int w6[6] = int[6](9363, 18724, 28086, 37450, 46812, 56173);
|
||||
const int w4[4] = int[4](13107, 26215, 39321, 52429);
|
||||
int pal[8];
|
||||
pal[0] = e0;
|
||||
pal[1] = e1;
|
||||
if (e0 > e1) {
|
||||
for (int i = 0; i < 6; i++) pal[2 + i] = (w6[5 - i] * e0 + w6[i] * e1 + 32768) >> 16;
|
||||
} else {
|
||||
for (int i = 0; i < 4; i++) pal[2 + i] = (w4[3 - i] * e0 + w4[i] * e1 + 32768) >> 16;
|
||||
pal[6] = sgn ? -127 : 0;
|
||||
pal[7] = sgn ? 127 : 255;
|
||||
}
|
||||
uvec4 b = uvec4(lo, hi, 0u, 0u);
|
||||
for (uint t = 0u; t < 16u; t++) v[t] = uint(pal[bits(b, 16u + 3u * t, 3u)]) & 0xffu;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ BC6H */
|
||||
|
||||
int bc6_unquantize(int val, int nbits, bool sgn)
|
||||
{
|
||||
if (!sgn) {
|
||||
if (nbits >= 15) return val;
|
||||
if (val == 0) return 0;
|
||||
if (val == (1 << nbits) - 1) return 0xffff;
|
||||
return ((val << 16) + 0x8000) >> nbits;
|
||||
}
|
||||
if (nbits >= 16) return val;
|
||||
int s = 0;
|
||||
if (val < 0) { s = 1; val = -val; }
|
||||
int unq;
|
||||
if (val == 0) unq = 0;
|
||||
else if (val >= (1 << (nbits - 1)) - 1) unq = 0x7fff;
|
||||
else unq = ((val << 15) + 0x4000) >> (nbits - 1);
|
||||
return s != 0 ? -unq : unq;
|
||||
}
|
||||
|
||||
uint bc6_finish(int val, bool sgn)
|
||||
{
|
||||
if (!sgn) return uint((val * 31) >> 6);
|
||||
val = val < 0 ? -(((-val) * 31) >> 5) : (val * 31) >> 5;
|
||||
uint s = 0u;
|
||||
if (val < 0) { s = 0x8000u; val = -val; }
|
||||
return s | uint(val);
|
||||
}
|
||||
|
||||
void bc6h(uvec4 blk, bool sgn)
|
||||
{
|
||||
uint pos = 0u;
|
||||
uint code = bits(blk, 0u, 2u);
|
||||
pos = 2u;
|
||||
if (code > 1u) { code |= bits(blk, 2u, 3u) << 2; pos = 5u; }
|
||||
int mode = BC6_MODE_OF_CODE[code];
|
||||
if (mode < 0) {
|
||||
for (uint t = 0u; t < 16u; t++) put(t & 3u, t >> 2, vec4(0.0, 0.0, 0.0, 1.0));
|
||||
return;
|
||||
}
|
||||
int ep[12];
|
||||
for (int i = 0; i < 12; i++) ep[i] = 0;
|
||||
uint part_id = 0u;
|
||||
for (uint e = BC6_LAYOUT_START[mode]; e < BC6_LAYOUT_START[mode + 1]; e++) {
|
||||
uint d = BC6_LAYOUT[e];
|
||||
uint target = d & 15u, n = (d >> 4) & 31u, sh = (d >> 9) & 31u;
|
||||
uint v = bits(blk, pos, n);
|
||||
pos += n;
|
||||
if ((d & (1u << 14)) != 0u) v = bitfieldReverse(v) >> (32u - n);
|
||||
if (target == 12u) part_id = v;
|
||||
else ep[target] |= int(v << sh);
|
||||
}
|
||||
bool two = mode < 10;
|
||||
int npts = two ? 4 : 2;
|
||||
int w = BC6_BITS_W[mode];
|
||||
if (sgn) { ep[0] = sext(ep[0], w); ep[4] = sext(ep[4], w); ep[8] = sext(ep[8], w); }
|
||||
if ((mode != 9 && mode != 10) || sgn) {
|
||||
for (int i = 1; i < npts; i++) {
|
||||
ep[i] = sext(ep[i], BC6_BITS_DR[mode]);
|
||||
ep[4 + i] = sext(ep[4 + i], BC6_BITS_DG[mode]);
|
||||
ep[8 + i] = sext(ep[8 + i], BC6_BITS_DB[mode]);
|
||||
}
|
||||
}
|
||||
if (mode != 9 && mode != 10) {
|
||||
for (int i = 1; i < npts; i++) {
|
||||
for (int c = 0; c < 3; c++) {
|
||||
int v = (ep[c * 4 + i] + ep[c * 4]) & ((1 << w) - 1);
|
||||
ep[c * 4 + i] = sgn ? sext(v, w) : v;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < 12; i++) ep[i] = bc6_unquantize(ep[i], w, sgn);
|
||||
const int w3[8] = int[8](0, 9, 18, 27, 37, 46, 55, 64);
|
||||
const int w4[16] = int[16](0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64);
|
||||
uint part = BC7_PART2[part_id], anchor = BC7_PART2_ANCHOR[part_id] & 15u;
|
||||
for (uint t = 0u; t < 16u; t++) {
|
||||
uint subset = two ? ((part >> (2u * t)) & 3u) : 0u;
|
||||
uint nb = two ? 3u : 4u;
|
||||
if (t == 0u || (two && subset == 1u && t == anchor)) nb--;
|
||||
uint idx = bits(blk, pos, nb);
|
||||
pos += nb;
|
||||
int wt = two ? w3[idx] : w4[idx];
|
||||
int s2 = int(subset) * 2;
|
||||
uvec3 h;
|
||||
for (int c = 0; c < 3; c++) {
|
||||
int a = ep[c * 4 + s2], b = ep[c * 4 + s2 + 1];
|
||||
h[c] = bc6_finish((a * (64 - wt) + b * wt + 32) >> 6, sgn);
|
||||
}
|
||||
put(t & 3u, t >> 2, vec4(unpackHalf2x16(h.x).x, unpackHalf2x16(h.y).x, unpackHalf2x16(h.z).x, 1.0));
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ BC7 */
|
||||
|
||||
void bc7(uvec4 blk)
|
||||
{
|
||||
uint mode = 0u;
|
||||
while (mode < 8u && bits(blk, mode, 1u) == 0u) mode++;
|
||||
if (mode >= 8u) {
|
||||
for (uint t = 0u; t < 16u; t++) put(t & 3u, t >> 2, vec4(0.0));
|
||||
return;
|
||||
}
|
||||
uint pos = mode + 1u;
|
||||
uint nparts = 1u, part_id = 0u, rotation = 0u, isel = 0u;
|
||||
if (mode == 0u || mode == 1u || mode == 2u || mode == 3u || mode == 7u) {
|
||||
nparts = (mode == 0u || mode == 2u) ? 3u : 2u;
|
||||
uint pb = mode == 0u ? 4u : 6u;
|
||||
part_id = bits(blk, pos, pb);
|
||||
pos += pb;
|
||||
}
|
||||
uint neps = nparts * 2u;
|
||||
if (mode == 4u || mode == 5u) {
|
||||
rotation = bits(blk, pos, 2u);
|
||||
pos += 2u;
|
||||
if (mode == 4u) { isel = bits(blk, pos, 1u); pos += 1u; }
|
||||
}
|
||||
int cb = BC7_BITS_RGB[mode], ab = BC7_BITS_A[mode];
|
||||
uint ep[6][4];
|
||||
for (uint c = 0u; c < 3u; c++)
|
||||
for (uint e = 0u; e < neps; e++) { ep[e][c] = bits(blk, pos, uint(cb)); pos += uint(cb); }
|
||||
for (uint e = 0u; e < neps; e++) {
|
||||
if (ab > 0) { ep[e][3] = bits(blk, pos, uint(ab)); pos += uint(ab); }
|
||||
else ep[e][3] = 0u;
|
||||
}
|
||||
const uint has_pbits = 203u; /* 0b11001011 */
|
||||
bool pbits = ((has_pbits >> mode) & 1u) != 0u || mode == 1u;
|
||||
if (mode == 0u || mode == 1u || mode == 3u || mode == 6u || mode == 7u) {
|
||||
for (uint e = 0u; e < neps; e++)
|
||||
for (uint c = 0u; c < 4u; c++) ep[e][c] <<= 1;
|
||||
if (mode == 1u) {
|
||||
uint p0 = bits(blk, pos, 1u), p1 = bits(blk, pos + 1u, 1u);
|
||||
pos += 2u;
|
||||
for (uint c = 0u; c < 3u; c++) { ep[0][c] |= p0; ep[1][c] |= p0; ep[2][c] |= p1; ep[3][c] |= p1; }
|
||||
} else {
|
||||
for (uint e = 0u; e < neps; e++) {
|
||||
uint p = bits(blk, pos, 1u);
|
||||
pos += 1u;
|
||||
for (uint c = 0u; c < 4u; c++) ep[e][c] |= p;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (uint e = 0u; e < neps; e++) {
|
||||
uint j = uint(cb) + ((has_pbits >> mode) & 1u);
|
||||
for (uint c = 0u; c < 3u; c++) {
|
||||
ep[e][c] = ep[e][c] << (8u - j);
|
||||
ep[e][c] = (ep[e][c] | (ep[e][c] >> j)) & 0xffu;
|
||||
}
|
||||
j = uint(ab) + ((has_pbits >> mode) & 1u);
|
||||
ep[e][3] = ep[e][3] << (8u - j);
|
||||
ep[e][3] = (ep[e][3] | (ep[e][3] >> j)) & 0xffu;
|
||||
if (ab == 0) ep[e][3] = 255u;
|
||||
}
|
||||
const uint w2[4] = uint[4](0u, 21u, 43u, 64u);
|
||||
const uint w3[8] = uint[8](0u, 9u, 18u, 27u, 37u, 46u, 55u, 64u);
|
||||
const uint w4[16] = uint[16](0u, 4u, 9u, 13u, 17u, 21u, 26u, 30u, 34u, 38u, 43u, 47u, 51u, 55u, 60u, 64u);
|
||||
uint ib = (mode == 0u || mode == 1u) ? 3u : (mode == 6u ? 4u : 2u);
|
||||
uint ib2 = mode == 4u ? 3u : (mode == 5u ? 2u : 0u);
|
||||
uint part = nparts == 2u ? BC7_PART2[part_id] : (nparts == 3u ? BC7_PART3[part_id] : 0u);
|
||||
uint anchors = nparts == 2u ? BC7_PART2_ANCHOR[part_id] : (nparts == 3u ? BC7_PART3_ANCHOR[part_id] : 0u);
|
||||
uint idx1[16];
|
||||
for (uint t = 0u; t < 16u; t++) {
|
||||
uint subset = (part >> (2u * t)) & 3u;
|
||||
uint nb = ib;
|
||||
bool anchor = t == 0u || (subset == 1u && t == (anchors & 15u)) || (subset == 2u && t == (anchors >> 4));
|
||||
if (anchor) nb--;
|
||||
idx1[t] = bits(blk, pos, nb);
|
||||
pos += nb;
|
||||
}
|
||||
for (uint t = 0u; t < 16u; t++) {
|
||||
uint subset = (part >> (2u * t)) & 3u;
|
||||
uint e0 = subset * 2u, e1 = e0 + 1u;
|
||||
uint i1 = idx1[t];
|
||||
uvec4 c;
|
||||
if (ib2 == 0u) {
|
||||
uint wt = ib == 2u ? w2[i1] : (ib == 3u ? w3[i1] : w4[i1]);
|
||||
for (uint k = 0u; k < 4u; k++) c[k] = (ep[e0][k] * (64u - wt) + ep[e1][k] * wt + 32u) >> 6;
|
||||
} else {
|
||||
uint nb2 = t == 0u ? ib2 - 1u : ib2;
|
||||
uint i2 = bits(blk, pos, nb2);
|
||||
pos += nb2;
|
||||
uint wa = ib == 2u ? w2[i1] : w3[i1];
|
||||
uint wb = ib2 == 2u ? w2[i2] : w3[i2];
|
||||
uint wc = isel == 0u ? wa : wb, wal = isel == 0u ? wb : wa;
|
||||
for (uint k = 0u; k < 3u; k++) c[k] = (ep[e0][k] * (64u - wc) + ep[e1][k] * wc + 32u) >> 6;
|
||||
c.w = (ep[e0][3] * (64u - wal) + ep[e1][3] * wal + 32u) >> 6;
|
||||
}
|
||||
if (rotation == 1u) c = c.agbr;
|
||||
else if (rotation == 2u) c = c.rabg;
|
||||
else if (rotation == 3u) c = c.rgab;
|
||||
put(t & 3u, t >> 2, vec4(c) / 255.0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ main */
|
||||
|
||||
void main()
|
||||
{
|
||||
uvec2 blk = gl_GlobalInvocationID.xy;
|
||||
uvec2 nblk = (pc.extent + 3u) / 4u;
|
||||
if (blk.x >= nblk.x || blk.y >= nblk.y) return;
|
||||
#if defined(FMT_BC1) || defined(FMT_BC4)
|
||||
uint words = 2u;
|
||||
#else
|
||||
uint words = 4u;
|
||||
#endif
|
||||
uint base = pc.src_word + (gl_GlobalInvocationID.z * pc.layer_blocks + blk.y * pc.row_blocks + blk.x) * words;
|
||||
uvec4 b = uvec4(src.w[base], src.w[base + 1u], words > 2u ? src.w[base + 2u] : 0u, words > 2u ? src.w[base + 3u] : 0u);
|
||||
bool sgn = (pc.flags & 2u) != 0u;
|
||||
#if defined(FMT_BC1)
|
||||
uvec4 tex[16];
|
||||
color_block(b.x, b.y, false, tex);
|
||||
for (uint t = 0u; t < 16u; t++) {
|
||||
uvec4 c = tex[t];
|
||||
if ((pc.flags & 1u) != 0u) c.w = 255u;
|
||||
put(t & 3u, t >> 2, vec4(c) / 255.0);
|
||||
}
|
||||
#elif defined(FMT_BC2) || defined(FMT_BC3)
|
||||
uvec4 tex[16];
|
||||
color_block(b.z, b.w, true, tex);
|
||||
uint a[16];
|
||||
#if defined(FMT_BC2)
|
||||
for (uint t = 0u; t < 16u; t++) a[t] = bits(b, 4u * t, 4u) * 17u;
|
||||
#else
|
||||
smooth_alpha(b.x, b.y, a);
|
||||
#endif
|
||||
for (uint t = 0u; t < 16u; t++) put(t & 3u, t >> 2, vec4(tex[t].rgb, a[t]) / 255.0);
|
||||
#elif defined(FMT_BC4)
|
||||
uint v[16];
|
||||
bc4_channel(b.x, b.y, sgn, v);
|
||||
for (uint t = 0u; t < 16u; t++) put(t & 3u, t >> 2, vec4(float(v[t]) / 255.0, 0.0, 0.0, 1.0));
|
||||
#elif defined(FMT_BC5)
|
||||
uint r[16], g[16];
|
||||
bc4_channel(b.x, b.y, sgn, r);
|
||||
bc4_channel(b.z, b.w, sgn, g);
|
||||
for (uint t = 0u; t < 16u; t++) put(t & 3u, t >> 2, vec4(float(r[t]) / 255.0, float(g[t]) / 255.0, 0.0, 1.0));
|
||||
#elif defined(FMT_BC6H)
|
||||
bc6h(b, sgn);
|
||||
#elif defined(FMT_BC7)
|
||||
bc7(b);
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/* Generated by gen_bc_tables.py from bcdec.h (MIT/Unlicense, Sergii Kudlai) - do not edit. */
|
||||
const int BC6_MODE_OF_CODE[32] = int[32](0, 1, 2, 10, -1, -1, 3, 11, -1, -1, 4, 12, -1, -1, 5, 13, -1, -1, 6, -1, -1, -1, 7, -1, -1, -1, 8, -1, -1, -1, 9, -1);
|
||||
const uint BC6_LAYOUT_START[15] = uint[15](0u, 20u, 44u, 63u, 84u, 105u, 125u, 145u, 167u, 189u, 213u, 219u, 228u, 237u, 246u);
|
||||
const uint BC6_LAYOUT[246] = uint[246](2070u, 2074u, 2075u, 160u, 164u, 168u, 81u, 2071u, 70u, 85u, 27u, 71u, 89u, 539u, 74u, 82u, 1051u, 83u, 1563u, 92u, 2582u, 2071u, 2583u, 112u, 27u, 539u, 2074u, 116u, 2586u, 1051u, 2070u, 120u, 1563u, 2587u, 2075u, 97u, 70u, 101u, 71u, 105u, 74u, 98u, 99u, 92u, 160u, 164u, 168u, 81u, 5136u, 70u, 69u, 5140u, 27u, 71u, 73u, 5144u, 539u, 74u, 82u, 1051u, 83u, 1563u, 92u, 160u, 164u, 168u, 65u, 5136u, 2071u, 70u, 85u, 5140u, 71u, 73u, 5144u, 539u, 74u, 66u, 27u, 1051u, 67u, 2070u, 1563u, 92u, 160u, 164u, 168u, 65u, 5136u, 2074u, 70u, 69u, 5140u, 27u, 71u, 89u, 5144u, 74u, 66u, 539u, 1051u, 67u, 2075u, 1563u, 92u, 144u, 2074u, 148u, 2070u, 152u, 2075u, 81u, 2071u, 70u, 85u, 27u, 71u, 89u, 539u, 74u, 82u, 1051u, 83u, 1563u, 92u, 128u, 2071u, 2074u, 132u, 1051u, 2070u, 136u, 1563u, 2075u, 97u, 70u, 85u, 27u, 71u, 89u, 539u, 74u, 98u, 99u, 92u, 128u, 27u, 2074u, 132u, 2582u, 2070u, 136u, 2583u, 2075u, 81u, 2071u, 70u, 101u, 71u, 89u, 539u, 74u, 82u, 1051u, 83u, 1563u, 92u, 128u, 539u, 2074u, 132u, 2586u, 2070u, 136u, 2587u, 2075u, 81u, 2071u, 70u, 85u, 27u, 71u, 105u, 74u, 82u, 1051u, 83u, 1563u, 92u, 96u, 2071u, 27u, 539u, 2074u, 100u, 2582u, 2586u, 1051u, 2070u, 104u, 2583u, 1563u, 2587u, 2075u, 97u, 70u, 101u, 71u, 105u, 74u, 98u, 99u, 92u, 160u, 164u, 168u, 161u, 165u, 169u, 160u, 164u, 168u, 145u, 5136u, 149u, 5140u, 153u, 5144u, 160u, 164u, 168u, 129u, 21536u, 133u, 21540u, 137u, 21544u, 160u, 164u, 168u, 65u, 21600u, 69u, 21604u, 73u, 21608u);
|
||||
const int BC6_BITS_W[14] = int[14](10, 7, 11, 11, 11, 9, 8, 8, 8, 6, 10, 11, 12, 16);
|
||||
const int BC6_BITS_DR[14] = int[14](5, 6, 5, 4, 4, 5, 6, 5, 5, 6, 10, 9, 8, 4);
|
||||
const int BC6_BITS_DG[14] = int[14](5, 6, 4, 5, 4, 5, 5, 6, 5, 6, 10, 9, 8, 4);
|
||||
const int BC6_BITS_DB[14] = int[14](5, 6, 4, 4, 5, 5, 5, 5, 6, 6, 10, 9, 8, 4);
|
||||
const uint BC7_PART2[64] = uint[64](1347440720u, 1077952576u, 1414812756u, 1414549568u, 1346387968u, 1431589968u, 1431588928u, 1414545408u, 1346371584u, 1431655504u, 1431584768u, 1413480448u, 1431655488u, 1431633920u, 1431655680u, 1426063360u, 1427439872u, 16468u, 352387072u, 4214868u, 16464u, 352649472u, 83951616u, 1079005268u, 4210768u, 83951872u, 336860180u, 85202000u, 18175040u, 5592320u, 352403540u, 88162640u, 1145324612u, 1426085120u, 289673540u, 84234320u, 89130320u, 286344260u, 1091846420u, 1141969220u, 352669780u, 17125440u, 84152400u, 88428880u, 339820820u, 1342506320u, 1094784020u, 1315840u, 267524u, 1070096u, 273944576u, 68486144u, 1346438420u, 1090851920u, 88166420u, 335888720u, 1090848020u, 1095782420u, 1073812820u, 1410662720u, 1347441920u, 5591120u, 353701904u, 1414792196u);
|
||||
const uint BC7_PART2_ANCHOR[64] = uint[64](15u, 15u, 15u, 15u, 15u, 15u, 15u, 15u, 15u, 15u, 15u, 15u, 15u, 15u, 15u, 15u, 15u, 2u, 8u, 2u, 2u, 8u, 8u, 15u, 2u, 8u, 2u, 2u, 8u, 8u, 2u, 2u, 15u, 15u, 6u, 8u, 2u, 8u, 15u, 15u, 2u, 8u, 2u, 2u, 2u, 15u, 15u, 6u, 6u, 2u, 6u, 8u, 15u, 15u, 2u, 2u, 15u, 15u, 15u, 15u, 15u, 2u, 2u, 15u);
|
||||
const uint BC7_PART3[64] = uint[64](2858963024u, 1784303680u, 1515864576u, 1414570152u, 2779054080u, 2694860880u, 1431675040u, 1515868240u, 2857697280u, 2857719040u, 2863289600u, 2425393296u, 2492765332u, 2762253476u, 2846200912u, 705315408u, 2777960512u, 172118100u, 2779096320u, 1436590240u, 2829603924u, 1785348160u, 2762231808u, 437912832u, 5285028u, 2862977168u, 342452500u, 1768494080u, 2693105056u, 2860651540u, 1352967248u, 1784283648u, 2846195712u, 1351655592u, 2829094992u, 606348324u, 11162880u, 613566756u, 608801316u, 1352993360u, 1342874960u, 2863285316u, 1717960704u, 2778768800u, 1352683680u, 1764256040u, 1152035396u, 1717986816u, 2856600644u, 1420317864u, 2508232064u, 2526451200u, 2824098984u, 2157286784u, 2853442580u, 2526412800u, 2863272980u, 2689618080u, 2695210400u, 2516582400u, 1082146944u, 2846402984u, 2863311428u, 709513812u);
|
||||
const uint BC7_PART3_ANCHOR[64] = uint[64](243u, 131u, 143u, 63u, 248u, 243u, 63u, 143u, 248u, 248u, 246u, 246u, 246u, 245u, 243u, 131u, 243u, 131u, 248u, 63u, 243u, 131u, 246u, 138u, 53u, 248u, 104u, 166u, 248u, 245u, 175u, 143u, 248u, 63u, 243u, 165u, 166u, 138u, 152u, 175u, 111u, 243u, 143u, 245u, 63u, 111u, 111u, 143u, 243u, 63u, 245u, 245u, 245u, 248u, 245u, 250u, 245u, 250u, 248u, 253u, 63u, 252u, 243u, 131u);
|
||||
const int BC7_BITS_RGB[8] = int[8](4, 6, 5, 7, 5, 7, 7, 5);
|
||||
const int BC7_BITS_A[8] = int[8](0, 0, 0, 0, 6, 8, 7, 5);
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
# Compiles the BCn decoder variants into app/src/main/cpp/vkbridge/bc_spv.h (committed, so a build
|
||||
# needs no shader compiler). Needs glslangValidator; regenerates bc_tables.glsl first.
|
||||
set -eu
|
||||
cd "$(dirname "$0")"
|
||||
python3 gen_bc_tables.py
|
||||
out=../../../app/src/main/cpp/vkbridge/bc_spv.h
|
||||
tmp=$(mktemp -d)
|
||||
{
|
||||
echo "/* Generated by tools/vkbridge/shaders/build.sh from bc_decode.comp - do not edit. */"
|
||||
echo "#pragma once"
|
||||
echo "#include <stdint.h>"
|
||||
} > "$out"
|
||||
for f in BC1 BC2 BC3 BC4 BC5 BC6H BC7; do
|
||||
name=spv_$(echo $f | tr A-Z a-z)
|
||||
glslangValidator -V --target-env vulkan1.1 -DFMT_$f --vn "$name" -o "$tmp/$name.h" bc_decode.comp >/dev/null
|
||||
grep -v '^ *//\|#pragma once' "$tmp/$name.h" | sed 's/^const uint32_t/static const uint32_t/' >> "$out"
|
||||
done
|
||||
rm -rf "$tmp"
|
||||
echo "wrote $out"
|
||||
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""
|
||||
Generates bc_tables.glsl from bcdec.h (third_party/bcdec, MIT/Unlicense): the BC6H per-mode bit
|
||||
layouts (as a table instead of 14 hand-written cases) and the BC6H/BC7 partition and anchor tables.
|
||||
|
||||
BC6H layout entry (uint): target (r0..r3 = 0..3, g0..g3 = 4..7, b0..b3 = 8..11, partition = 12)
|
||||
| nbits << 4 | shift << 9 | reversed << 14.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
src = open(os.path.join(here, '..', 'third_party', 'bcdec', 'bcdec.h')).read()
|
||||
|
||||
# ---- BC6H modes
|
||||
bc6 = src[src.rindex('BCDECDEF void bcdec_bc6h_half('):src.rindex('BCDECDEF void bcdec_bc6h_float(')]
|
||||
cases = re.findall(r'case (0b[01]+): \{(.*?)\} break;', bc6, re.S)
|
||||
comp = {'r': 0, 'g': 4, 'b': 8}
|
||||
modes = {}
|
||||
for code, body in cases:
|
||||
entries = []
|
||||
mode_idx = None
|
||||
body = re.sub(r'/\*.*?\*/', '', body, flags=re.S)
|
||||
for line in body.split('\n'):
|
||||
line = line.split('/*')[0].strip()
|
||||
if not line:
|
||||
continue
|
||||
m = re.match(r'mode = (\d+);', line)
|
||||
if m:
|
||||
mode_idx = int(m.group(1))
|
||||
continue
|
||||
m = re.match(r'partition = bcdec__bitstream_read_bits\(&bstream, (\d+)\);', line)
|
||||
if m:
|
||||
entries.append((12, int(m.group(1)), 0, 0))
|
||||
continue
|
||||
m = re.match(r'([rgb])\[(\d)\] \|= bcdec__bitstream_read_bit(s_r|s)?\(&bstream(?:, (\d+))?\)(?: << (\d+))?;', line)
|
||||
if not m:
|
||||
raise SystemExit(f'unparsed BC6H line: {line}')
|
||||
c, i, kind, n, sh = m.groups()
|
||||
nbits = int(n) if n else 1
|
||||
entries.append((comp[c] + int(i), nbits, int(sh) if sh else 0, 1 if kind == 's_r' else 0))
|
||||
modes[int(code, 2)] = (mode_idx, entries)
|
||||
|
||||
out = []
|
||||
out.append('/* Generated by gen_bc_tables.py from bcdec.h (MIT/Unlicense, Sergii Kudlai) - do not edit. */')
|
||||
code_to_mode = [-1] * 32
|
||||
offsets, flat = [0] * 14, []
|
||||
for code in sorted(modes, key=lambda c: modes[c][0]):
|
||||
idx, entries = modes[code]
|
||||
code_to_mode[code] = idx
|
||||
offsets[idx] = len(flat)
|
||||
for t, n, sh, rv in entries:
|
||||
flat.append(t | (n << 4) | (sh << 9) | (rv << 14))
|
||||
offsets.append(len(flat))
|
||||
out.append(f'const int BC6_MODE_OF_CODE[32] = int[32]({", ".join(map(str, code_to_mode))});')
|
||||
out.append(f'const uint BC6_LAYOUT_START[15] = uint[15]({", ".join(str(o) + "u" for o in offsets)});')
|
||||
out.append(f'const uint BC6_LAYOUT[{len(flat)}] = uint[{len(flat)}]({", ".join(str(v) + "u" for v in flat)});')
|
||||
|
||||
def actual_bits(fn_src, rows):
|
||||
m = re.search(r'static char actual_bits_count\[\d\]\[\d+\] = \{(.*?)\};', fn_src, re.S)
|
||||
return [list(map(int, re.findall(r'-?\d+', r.split('/*')[0]))) for r in re.findall(r'\{([^{}]*)\}', m.group(1))][:rows]
|
||||
|
||||
ab6 = actual_bits(bc6, 4)
|
||||
for k, name in enumerate(['W', 'DR', 'DG', 'DB']):
|
||||
out.append(f'const int BC6_BITS_{name}[14] = int[14]({", ".join(map(str, ab6[k]))});')
|
||||
|
||||
# ---- partition tables (BC7's 2- and 3-subset; BC6H uses the first 32 of the 2-subset table)
|
||||
bc7 = src[src.rindex('BCDECDEF void bcdec_bc7('):]
|
||||
table = bc7[bc7.index('partition_sets[2][64][4][4] = {'):bc7.index('static int aWeight2')]
|
||||
rows = re.findall(r'\{ \{([^}]*)\}, \{([^}]*)\}, \{([^}]*)\}, \{([^}]*)\} \}', table)
|
||||
assert len(rows) == 128, len(rows)
|
||||
for name, part in (('BC7_PART2', rows[:64]), ('BC7_PART3', rows[64:])):
|
||||
packed, anchors = [], []
|
||||
for r in part:
|
||||
vals = [int(v) for row in r for v in row.split(',')]
|
||||
word, anc = 0, [0, 0, 0]
|
||||
for t, v in enumerate(vals):
|
||||
word |= (v & 3) << (2 * t)
|
||||
if v & 0x80:
|
||||
anc[v & 3] = t
|
||||
packed.append(word)
|
||||
anchors.append(anc[1] | (anc[2] << 4))
|
||||
out.append(f'const uint {name}[64] = uint[64]({", ".join(str(p) + "u" for p in packed)});')
|
||||
out.append(f'const uint {name}_ANCHOR[64] = uint[64]({", ".join(str(a) + "u" for a in anchors)});')
|
||||
ab7 = actual_bits(bc7, 2)
|
||||
out.append(f'const int BC7_BITS_RGB[8] = int[8]({", ".join(map(str, ab7[0]))});')
|
||||
out.append(f'const int BC7_BITS_A[8] = int[8]({", ".join(map(str, ab7[1]))});')
|
||||
|
||||
open(os.path.join(here, 'bc_tables.glsl'), 'w').write('\n'.join(out) + '\n')
|
||||
print(f'bc_tables.glsl: {len(flat)} BC6H layout entries, {len(modes)} modes')
|
||||
@@ -0,0 +1,319 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* bcn_check: uploads random blocks of every BC format (every BC6H and BC7 mode forced in turn),
|
||||
* blits the texture to RGBA32F and compares each texel with bcdec's CPU decode. Run it natively
|
||||
* (the GPU's own BC decoder) and through the bridge with BC emulated.
|
||||
*/
|
||||
#define VK_NO_PROTOTYPES
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define BCDEC_IMPLEMENTATION
|
||||
#define BCDEC_BC4BC5_PRECISE
|
||||
#include "../third_party/bcdec/bcdec.h"
|
||||
|
||||
#define TEX 64 /* 16x16 blocks */
|
||||
|
||||
static PFN_vkGetInstanceProcAddr gipa;
|
||||
static VkInstance inst;
|
||||
static VkPhysicalDevice pd;
|
||||
static VkDevice dev;
|
||||
static VkQueue queue;
|
||||
static VkCommandPool pool;
|
||||
static VkPhysicalDeviceMemoryProperties mp;
|
||||
#define F(n) static PFN_##n n
|
||||
F(vkCreateInstance); F(vkEnumeratePhysicalDevices); F(vkGetPhysicalDeviceMemoryProperties); F(vkCreateDevice);
|
||||
F(vkGetDeviceProcAddr); F(vkGetPhysicalDeviceFeatures); F(vkGetPhysicalDeviceFormatProperties); F(vkGetDeviceQueue);
|
||||
F(vkCreateCommandPool); F(vkAllocateCommandBuffers); F(vkBeginCommandBuffer); F(vkEndCommandBuffer); F(vkQueueSubmit);
|
||||
F(vkQueueWaitIdle); F(vkCreateBuffer); F(vkGetBufferMemoryRequirements); F(vkAllocateMemory); F(vkBindBufferMemory);
|
||||
F(vkMapMemory); F(vkCreateImage); F(vkGetImageMemoryRequirements); F(vkBindImageMemory); F(vkCmdPipelineBarrier);
|
||||
F(vkCmdCopyBufferToImage); F(vkCmdBlitImage); F(vkCmdCopyImageToBuffer); F(vkDestroyImage); F(vkDestroyBuffer);
|
||||
F(vkFreeMemory); F(vkGetPhysicalDeviceProperties);
|
||||
#undef F
|
||||
|
||||
static uint32_t find_type(uint32_t bits, VkMemoryPropertyFlags want)
|
||||
{
|
||||
for (uint32_t i = 0; i < mp.memoryTypeCount; i++)
|
||||
if ((bits & (1u << i)) && (mp.memoryTypes[i].propertyFlags & want) == want) return i;
|
||||
for (uint32_t i = 0; i < mp.memoryTypeCount; i++)
|
||||
if (bits & (1u << i)) return i;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static VkDeviceMemory bind_mem(VkMemoryRequirements r, VkMemoryPropertyFlags want)
|
||||
{
|
||||
VkMemoryAllocateInfo ai = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, r.size, find_type(r.memoryTypeBits, want)};
|
||||
VkDeviceMemory m;
|
||||
vkAllocateMemory(dev, &ai, NULL, &m);
|
||||
return m;
|
||||
}
|
||||
|
||||
static uint64_t rng = 0x9E3779B97F4A7C15ull;
|
||||
static uint32_t rnd(void)
|
||||
{
|
||||
rng ^= rng << 13;
|
||||
rng ^= rng >> 7;
|
||||
rng ^= rng << 17;
|
||||
return (uint32_t)(rng >> 16);
|
||||
}
|
||||
|
||||
/* Random block; for BC6H/BC7 the mode is chosen by `mode` (cycled per block). */
|
||||
static void make_block(VkFormat f, uint8_t *b, int bytes, int mode)
|
||||
{
|
||||
for (int i = 0; i < bytes; i++) b[i] = (uint8_t)rnd();
|
||||
if (f == VK_FORMAT_BC7_UNORM_BLOCK) {
|
||||
int m = mode % 8;
|
||||
b[0] = (uint8_t)((b[0] & ~((1u << (m + 1)) - 1)) | (1u << m));
|
||||
} else if (f == VK_FORMAT_BC6H_UFLOAT_BLOCK || f == VK_FORMAT_BC6H_SFLOAT_BLOCK) {
|
||||
static const uint8_t codes[14] = {0, 1, 2, 6, 10, 14, 18, 22, 26, 30, 3, 7, 11, 15};
|
||||
int c = codes[mode % 14];
|
||||
if (c < 2) b[0] = (uint8_t)((b[0] & ~3u) | c);
|
||||
else b[0] = (uint8_t)((b[0] & ~31u) | c);
|
||||
}
|
||||
}
|
||||
|
||||
static float h2f(uint16_t h)
|
||||
{
|
||||
uint32_t s = (h >> 15) & 1, e = (h >> 10) & 31, m = h & 1023;
|
||||
float v;
|
||||
if (e == 0) v = ldexpf((float)m, -24);
|
||||
else if (e == 31) v = m ? NAN : INFINITY;
|
||||
else v = ldexpf((float)(m | 1024), (int)e - 25);
|
||||
return s ? -v : v;
|
||||
}
|
||||
|
||||
/* Reference texel (RGBA float) at (x, y) of the texture made from `blocks`. */
|
||||
static void reference(VkFormat f, const uint8_t *blocks, int bytes, int x, int y, float out[4])
|
||||
{
|
||||
const uint8_t *b = blocks + ((y / 4) * (TEX / 4) + (x / 4)) * bytes;
|
||||
int tx = x & 3, ty = y & 3;
|
||||
uint8_t px8[16 * 4];
|
||||
int8_t spx[16 * 2];
|
||||
uint16_t px16[16 * 3];
|
||||
out[0] = out[1] = out[2] = 0;
|
||||
out[3] = 1;
|
||||
switch (f) {
|
||||
case VK_FORMAT_BC1_RGBA_UNORM_BLOCK: bcdec_bc1(b, px8, 16); break;
|
||||
case VK_FORMAT_BC2_UNORM_BLOCK: bcdec_bc2(b, px8, 16); break;
|
||||
case VK_FORMAT_BC3_UNORM_BLOCK: bcdec_bc3(b, px8, 16); break;
|
||||
case VK_FORMAT_BC7_UNORM_BLOCK: bcdec_bc7(b, px8, 16); break;
|
||||
case VK_FORMAT_BC4_UNORM_BLOCK: bcdec_bc4(b, px8, 4, 0); out[0] = px8[ty * 4 + tx] / 255.0f; return;
|
||||
case VK_FORMAT_BC4_SNORM_BLOCK: bcdec_bc4(b, spx, 4, 1); out[0] = fmaxf(spx[ty * 4 + tx] / 127.0f, -1.0f); return;
|
||||
case VK_FORMAT_BC5_UNORM_BLOCK:
|
||||
bcdec_bc5(b, px8, 8, 0);
|
||||
out[0] = px8[(ty * 4 + tx) * 2] / 255.0f;
|
||||
out[1] = px8[(ty * 4 + tx) * 2 + 1] / 255.0f;
|
||||
return;
|
||||
case VK_FORMAT_BC5_SNORM_BLOCK:
|
||||
bcdec_bc5(b, spx, 8, 1);
|
||||
out[0] = fmaxf(spx[(ty * 4 + tx) * 2] / 127.0f, -1.0f);
|
||||
out[1] = fmaxf(spx[(ty * 4 + tx) * 2 + 1] / 127.0f, -1.0f);
|
||||
return;
|
||||
case VK_FORMAT_BC6H_UFLOAT_BLOCK:
|
||||
case VK_FORMAT_BC6H_SFLOAT_BLOCK:
|
||||
bcdec_bc6h_half(b, px16, 12, f == VK_FORMAT_BC6H_SFLOAT_BLOCK);
|
||||
for (int c = 0; c < 3; c++) out[c] = h2f(px16[(ty * 4 + tx) * 3 + c]);
|
||||
return;
|
||||
default: return;
|
||||
}
|
||||
for (int c = 0; c < 4; c++) out[c] = px8[(ty * 4 + tx) * 4 + c] / 255.0f;
|
||||
}
|
||||
|
||||
static VkCommandBuffer begin(void)
|
||||
{
|
||||
VkCommandBufferAllocateInfo ai = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, NULL, pool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1};
|
||||
VkCommandBuffer cb;
|
||||
vkAllocateCommandBuffers(dev, &ai, &cb);
|
||||
VkCommandBufferBeginInfo bi = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, NULL};
|
||||
vkBeginCommandBuffer(cb, &bi);
|
||||
return cb;
|
||||
}
|
||||
|
||||
static void barrier(VkCommandBuffer cb, VkImage img, VkImageLayout from, VkImageLayout to)
|
||||
{
|
||||
VkImageMemoryBarrier b = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, NULL, VK_ACCESS_MEMORY_WRITE_BIT,
|
||||
VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, from, to, VK_QUEUE_FAMILY_IGNORED,
|
||||
VK_QUEUE_FAMILY_IGNORED, img, {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
|
||||
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, NULL, 0, NULL, 1, &b);
|
||||
}
|
||||
|
||||
static int failures;
|
||||
|
||||
static void test_format(VkFormat f, const char *name, float tol)
|
||||
{
|
||||
int bytes = (f == VK_FORMAT_BC1_RGBA_UNORM_BLOCK || f == VK_FORMAT_BC4_UNORM_BLOCK || f == VK_FORMAT_BC4_SNORM_BLOCK) ? 8 : 16;
|
||||
int nblocks = (TEX / 4) * (TEX / 4);
|
||||
uint8_t *blocks = malloc((size_t)nblocks * bytes);
|
||||
for (int i = 0; i < nblocks; i++) make_block(f, blocks + i * bytes, bytes, i);
|
||||
VkFormatProperties fp;
|
||||
vkGetPhysicalDeviceFormatProperties(pd, f, &fp);
|
||||
if (!(fp.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT)) {
|
||||
printf("SKIP %s: no blit from this format\n", name);
|
||||
free(blocks);
|
||||
return;
|
||||
}
|
||||
/* staging */
|
||||
VkBufferCreateInfo bci = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, NULL, 0, (VkDeviceSize)TEX * TEX * 16,
|
||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_SHARING_MODE_EXCLUSIVE, 0, NULL};
|
||||
VkBuffer up, down;
|
||||
vkCreateBuffer(dev, &bci, NULL, &up);
|
||||
vkCreateBuffer(dev, &bci, NULL, &down);
|
||||
VkMemoryRequirements r;
|
||||
vkGetBufferMemoryRequirements(dev, up, &r);
|
||||
VkDeviceMemory upm = bind_mem(r, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
vkBindBufferMemory(dev, up, upm, 0);
|
||||
vkGetBufferMemoryRequirements(dev, down, &r);
|
||||
VkDeviceMemory downm = bind_mem(r, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
vkBindBufferMemory(dev, down, downm, 0);
|
||||
void *upp, *downp;
|
||||
vkMapMemory(dev, upm, 0, VK_WHOLE_SIZE, 0, &upp);
|
||||
vkMapMemory(dev, downm, 0, VK_WHOLE_SIZE, 0, &downp);
|
||||
/* upload at an odd 16-byte offset with a padded row, to exercise the decoder's addressing */
|
||||
const VkDeviceSize off = 48;
|
||||
const uint32_t row_tex = TEX + 8;
|
||||
memset(upp, 0xcd, (size_t)TEX * TEX * 16);
|
||||
for (int by = 0; by < TEX / 4; by++)
|
||||
memcpy((uint8_t *)upp + off + (size_t)by * (row_tex / 4) * bytes, blocks + (size_t)by * (TEX / 4) * bytes, (size_t)(TEX / 4) * bytes);
|
||||
/* images */
|
||||
VkImageCreateInfo ici = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, NULL, 0, VK_IMAGE_TYPE_2D, f, {TEX, TEX, 1}, 1, 1, VK_SAMPLE_COUNT_1_BIT,
|
||||
VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||
VK_SHARING_MODE_EXCLUSIVE, 0, NULL, VK_IMAGE_LAYOUT_UNDEFINED};
|
||||
VkImage bc, rgba;
|
||||
if (vkCreateImage(dev, &ici, NULL, &bc) != VK_SUCCESS) {
|
||||
printf("FAIL %s: cannot create the image\n", name);
|
||||
failures++;
|
||||
return;
|
||||
}
|
||||
VkMemoryRequirements ir;
|
||||
vkGetImageMemoryRequirements(dev, bc, &ir);
|
||||
VkDeviceMemory bcm = bind_mem(ir, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
vkBindImageMemory(dev, bc, bcm, 0);
|
||||
ici.format = VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||
vkCreateImage(dev, &ici, NULL, &rgba);
|
||||
vkGetImageMemoryRequirements(dev, rgba, &ir);
|
||||
VkDeviceMemory rgbam = bind_mem(ir, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
vkBindImageMemory(dev, rgba, rgbam, 0);
|
||||
VkCommandBuffer cb = begin();
|
||||
barrier(cb, bc, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
VkBufferImageCopy c = {off, row_tex, 0, {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, {0, 0, 0}, {TEX, TEX, 1}};
|
||||
vkCmdCopyBufferToImage(cb, up, bc, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &c);
|
||||
barrier(cb, bc, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
barrier(cb, rgba, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
VkImageBlit bl = {{VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, {{0, 0, 0}, {TEX, TEX, 1}}, {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, {{0, 0, 0}, {TEX, TEX, 1}}};
|
||||
vkCmdBlitImage(cb, bc, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, rgba, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bl, VK_FILTER_NEAREST);
|
||||
barrier(cb, rgba, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
VkBufferImageCopy rb = {0, 0, 0, {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, {0, 0, 0}, {TEX, TEX, 1}};
|
||||
vkCmdCopyImageToBuffer(cb, rgba, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, down, 1, &rb);
|
||||
VkMemoryBarrier mb = {VK_STRUCTURE_TYPE_MEMORY_BARRIER, NULL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT};
|
||||
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 1, &mb, 0, NULL, 0, NULL);
|
||||
vkEndCommandBuffer(cb);
|
||||
VkSubmitInfo si = {VK_STRUCTURE_TYPE_SUBMIT_INFO, NULL, 0, NULL, NULL, 1, &cb, 0, NULL};
|
||||
vkQueueSubmit(queue, 1, &si, VK_NULL_HANDLE);
|
||||
vkQueueWaitIdle(queue);
|
||||
const float *got = downp;
|
||||
int bad = 0, nan_skip = 0;
|
||||
float worst = 0;
|
||||
int wx = 0, wy = 0, wc = 0;
|
||||
for (int y = 0; y < TEX; y++)
|
||||
for (int x = 0; x < TEX; x++) {
|
||||
float want[4];
|
||||
reference(f, blocks, bytes, x, y, want);
|
||||
for (int ch = 0; ch < 4; ch++) {
|
||||
float g = got[(y * TEX + x) * 4 + ch], w = want[ch];
|
||||
if (isnan(w) || isinf(w)) {
|
||||
nan_skip++;
|
||||
continue;
|
||||
}
|
||||
float d = fabsf(g - w);
|
||||
float lim = (f == VK_FORMAT_BC6H_UFLOAT_BLOCK || f == VK_FORMAT_BC6H_SFLOAT_BLOCK) ? tol * fmaxf(1.0f, fabsf(w)) : tol;
|
||||
if (!(d <= lim)) {
|
||||
bad++;
|
||||
if (!(d <= worst)) {
|
||||
worst = d;
|
||||
wx = x;
|
||||
wy = y;
|
||||
wc = ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bad) {
|
||||
float want[4];
|
||||
reference(f, blocks, bytes, wx, wy, want);
|
||||
printf("FAIL %s: %d of %d values differ from bcdec; worst %.4f at (%d,%d) ch %d: got %g want %g (block mode byte 0x%02x)\n", name, bad,
|
||||
TEX * TEX * 4, worst, wx, wy, wc, got[(wy * TEX + wx) * 4 + wc], want[wc],
|
||||
blocks[((wy / 4) * (TEX / 4) + (wx / 4)) * bytes]);
|
||||
failures++;
|
||||
} else {
|
||||
printf("PASS %s: %d texels match bcdec%s\n", name, TEX * TEX, nan_skip ? " (inf/nan references skipped)" : "");
|
||||
}
|
||||
vkDestroyImage(dev, bc, NULL);
|
||||
vkDestroyImage(dev, rgba, NULL);
|
||||
vkDestroyBuffer(dev, up, NULL);
|
||||
vkDestroyBuffer(dev, down, NULL);
|
||||
vkFreeMemory(dev, upm, NULL);
|
||||
vkFreeMemory(dev, downm, NULL);
|
||||
vkFreeMemory(dev, bcm, NULL);
|
||||
vkFreeMemory(dev, rgbam, NULL);
|
||||
free(blocks);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int gpu = argc > 1 ? atoi(argv[1]) : 0;
|
||||
void *lib = dlopen("libvulkan.so.1", RTLD_NOW);
|
||||
gipa = (PFN_vkGetInstanceProcAddr)dlsym(lib, "vkGetInstanceProcAddr");
|
||||
#define G(n) n = (PFN_##n)gipa(inst, #n)
|
||||
G(vkCreateInstance);
|
||||
VkApplicationInfo app = {VK_STRUCTURE_TYPE_APPLICATION_INFO, NULL, "bcn_check", 1, NULL, 0, VK_API_VERSION_1_1};
|
||||
VkInstanceCreateInfo ici = {VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, NULL, 0, &app, 0, NULL, 0, NULL};
|
||||
if (vkCreateInstance(&ici, NULL, &inst)) return 2;
|
||||
G(vkEnumeratePhysicalDevices); G(vkGetPhysicalDeviceMemoryProperties); G(vkCreateDevice); G(vkGetDeviceProcAddr);
|
||||
G(vkGetPhysicalDeviceFeatures); G(vkGetPhysicalDeviceFormatProperties); G(vkGetPhysicalDeviceProperties);
|
||||
uint32_t n = 8;
|
||||
VkPhysicalDevice pds[8];
|
||||
vkEnumeratePhysicalDevices(inst, &n, pds);
|
||||
pd = pds[gpu];
|
||||
VkPhysicalDeviceProperties props;
|
||||
vkGetPhysicalDeviceProperties(pd, &props);
|
||||
VkPhysicalDeviceFeatures feats;
|
||||
vkGetPhysicalDeviceFeatures(pd, &feats);
|
||||
printf("%s, textureCompressionBC=%u\n", props.deviceName, feats.textureCompressionBC);
|
||||
if (!feats.textureCompressionBC) return 1;
|
||||
vkGetPhysicalDeviceMemoryProperties(pd, &mp);
|
||||
float prio = 1;
|
||||
VkDeviceQueueCreateInfo qci = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, NULL, 0, 0, 1, &prio};
|
||||
VkPhysicalDeviceFeatures en = {0};
|
||||
en.textureCompressionBC = VK_TRUE;
|
||||
VkDeviceCreateInfo dci = {VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, NULL, 0, 1, &qci, 0, NULL, 0, NULL, &en};
|
||||
if (vkCreateDevice(pd, &dci, NULL, &dev)) return 2;
|
||||
#undef G
|
||||
#define G(n) n = (PFN_##n)vkGetDeviceProcAddr(dev, #n)
|
||||
G(vkGetDeviceQueue); G(vkCreateCommandPool); G(vkAllocateCommandBuffers); G(vkBeginCommandBuffer); G(vkEndCommandBuffer);
|
||||
G(vkQueueSubmit); G(vkQueueWaitIdle); G(vkCreateBuffer); G(vkGetBufferMemoryRequirements); G(vkAllocateMemory);
|
||||
G(vkBindBufferMemory); G(vkMapMemory); G(vkCreateImage); G(vkGetImageMemoryRequirements); G(vkBindImageMemory);
|
||||
G(vkCmdPipelineBarrier); G(vkCmdCopyBufferToImage); G(vkCmdBlitImage); G(vkCmdCopyImageToBuffer); G(vkDestroyImage);
|
||||
G(vkDestroyBuffer); G(vkFreeMemory);
|
||||
#undef G
|
||||
vkGetDeviceQueue(dev, 0, 0, &queue);
|
||||
VkCommandPoolCreateInfo pci = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, NULL, 0, 0};
|
||||
vkCreateCommandPool(dev, &pci, NULL, &pool);
|
||||
float u8 = 2.5f / 255.0f; /* hardware decoders round differently from bcdec by up to 2 */
|
||||
test_format(VK_FORMAT_BC1_RGBA_UNORM_BLOCK, "BC1", u8);
|
||||
test_format(VK_FORMAT_BC2_UNORM_BLOCK, "BC2", u8);
|
||||
test_format(VK_FORMAT_BC3_UNORM_BLOCK, "BC3", u8);
|
||||
test_format(VK_FORMAT_BC4_UNORM_BLOCK, "BC4 unorm", u8);
|
||||
test_format(VK_FORMAT_BC4_SNORM_BLOCK, "BC4 snorm", 2.5f / 127.0f);
|
||||
test_format(VK_FORMAT_BC5_UNORM_BLOCK, "BC5 unorm", u8);
|
||||
test_format(VK_FORMAT_BC5_SNORM_BLOCK, "BC5 snorm", 2.5f / 127.0f);
|
||||
test_format(VK_FORMAT_BC6H_UFLOAT_BLOCK, "BC6H ufloat (all 14 modes)", 0.002f);
|
||||
test_format(VK_FORMAT_BC6H_SFLOAT_BLOCK, "BC6H sfloat (all 14 modes)", 0.002f);
|
||||
test_format(VK_FORMAT_BC7_UNORM_BLOCK, "BC7 (all 8 modes)", u8);
|
||||
printf("%s\n", failures ? "FAILED" : "OK");
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* d3d11_bench.exe [draws] [frames]: a game-like D3D11 frame - many small draws, each with a
|
||||
* Map(WRITE_DISCARD) constant buffer update and its own texture/sampler binding - rendered
|
||||
* offscreen and flushed every frame (with a readback every 30th, so the GPU cannot fall behind).
|
||||
* Prints milliseconds per frame: the bridge's overhead shows as the difference to native.
|
||||
*/
|
||||
#define COBJMACROS
|
||||
#include <windows.h>
|
||||
#include <d3d11.h>
|
||||
#include <d3dcompiler.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int draws = argc > 1 ? atoi(argv[1]) : 1000;
|
||||
int frames = argc > 2 ? atoi(argv[2]) : 300;
|
||||
ID3D11Device *dev;
|
||||
ID3D11DeviceContext *ctx;
|
||||
D3D_FEATURE_LEVEL fl = D3D_FEATURE_LEVEL_11_0;
|
||||
if (FAILED(D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, &fl, 1, D3D11_SDK_VERSION, &dev, NULL, &ctx))) {
|
||||
printf("no device\n");
|
||||
return 1;
|
||||
}
|
||||
const char *vs = "cbuffer C : register(b0) { float4 pos; float4 col; };"
|
||||
"struct O { float4 p : SV_Position; float4 c : COLOR; float2 uv : TEXCOORD; };"
|
||||
"O main(uint id : SV_VertexID) { O o; float2 t = float2((id << 1) & 2, id & 2) * 0.05;"
|
||||
"o.p = float4(pos.xy + t, 0, 1); o.c = col; o.uv = t * 20; return o; }";
|
||||
const char *ps = "Texture2D t : register(t0); SamplerState s : register(s0);"
|
||||
"float4 main(float4 p : SV_Position, float4 c : COLOR, float2 uv : TEXCOORD) : SV_Target { return c * t.Sample(s, uv); }";
|
||||
ID3DBlob *vb, *pb;
|
||||
D3DCompile(vs, strlen(vs), NULL, NULL, NULL, "main", "vs_5_0", 0, 0, &vb, NULL);
|
||||
D3DCompile(ps, strlen(ps), NULL, NULL, NULL, "main", "ps_5_0", 0, 0, &pb, NULL);
|
||||
ID3D11VertexShader *vsh;
|
||||
ID3D11PixelShader *psh;
|
||||
ID3D11Device_CreateVertexShader(dev, ID3D10Blob_GetBufferPointer(vb), ID3D10Blob_GetBufferSize(vb), NULL, &vsh);
|
||||
ID3D11Device_CreatePixelShader(dev, ID3D10Blob_GetBufferPointer(pb), ID3D10Blob_GetBufferSize(pb), NULL, &psh);
|
||||
D3D11_BUFFER_DESC bd = {32, D3D11_USAGE_DYNAMIC, D3D11_BIND_CONSTANT_BUFFER, D3D11_CPU_ACCESS_WRITE, 0, 0};
|
||||
ID3D11Buffer *cb;
|
||||
ID3D11Device_CreateBuffer(dev, &bd, NULL, &cb);
|
||||
/* 8 small BC1 textures (block-compressed like a game's) */
|
||||
ID3D11ShaderResourceView *srv[8];
|
||||
uint8_t blocks[16 * 8];
|
||||
for (int i = 0; i < (int)sizeof(blocks); i++) blocks[i] = (uint8_t)(i * 37 + 11);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
D3D11_TEXTURE2D_DESC td = {16, 16, 1, 1, DXGI_FORMAT_BC1_UNORM, {1, 0}, D3D11_USAGE_DEFAULT, D3D11_BIND_SHADER_RESOURCE, 0, 0};
|
||||
D3D11_SUBRESOURCE_DATA init = {blocks, 32, 0};
|
||||
ID3D11Texture2D *t;
|
||||
ID3D11Device_CreateTexture2D(dev, &td, &init, &t);
|
||||
ID3D11Device_CreateShaderResourceView(dev, (ID3D11Resource *)t, NULL, &srv[i]);
|
||||
}
|
||||
D3D11_SAMPLER_DESC sd = {D3D11_FILTER_MIN_MAG_MIP_LINEAR, D3D11_TEXTURE_ADDRESS_WRAP, D3D11_TEXTURE_ADDRESS_WRAP, D3D11_TEXTURE_ADDRESS_WRAP,
|
||||
0, 1, D3D11_COMPARISON_NEVER, {0}, 0, D3D11_FLOAT32_MAX};
|
||||
ID3D11SamplerState *smp;
|
||||
ID3D11Device_CreateSamplerState(dev, &sd, &smp);
|
||||
D3D11_TEXTURE2D_DESC rd = {1280, 720, 1, 1, DXGI_FORMAT_R8G8B8A8_UNORM, {1, 0}, D3D11_USAGE_DEFAULT, D3D11_BIND_RENDER_TARGET, 0, 0};
|
||||
ID3D11Texture2D *rt, *st;
|
||||
ID3D11Device_CreateTexture2D(dev, &rd, NULL, &rt);
|
||||
rd.Usage = D3D11_USAGE_STAGING;
|
||||
rd.BindFlags = 0;
|
||||
rd.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||
rd.Width = rd.Height = 16;
|
||||
ID3D11Device_CreateTexture2D(dev, &rd, NULL, &st);
|
||||
ID3D11RenderTargetView *rtv;
|
||||
ID3D11Device_CreateRenderTargetView(dev, (ID3D11Resource *)rt, NULL, &rtv);
|
||||
D3D11_VIEWPORT vp = {0, 0, 1280, 720, 0, 1};
|
||||
LARGE_INTEGER f, t0, t1;
|
||||
QueryPerformanceFrequency(&f);
|
||||
double total = 0, worst = 0;
|
||||
for (int fr = 0; fr < frames + 10; fr++) {
|
||||
QueryPerformanceCounter(&t0);
|
||||
float clear[4] = {0.1f, 0.1f, 0.1f, 1};
|
||||
ID3D11DeviceContext_OMSetRenderTargets(ctx, 1, &rtv, NULL);
|
||||
ID3D11DeviceContext_RSSetViewports(ctx, 1, &vp);
|
||||
ID3D11DeviceContext_ClearRenderTargetView(ctx, rtv, clear);
|
||||
ID3D11DeviceContext_IASetPrimitiveTopology(ctx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||
ID3D11DeviceContext_VSSetShader(ctx, vsh, NULL, 0);
|
||||
ID3D11DeviceContext_PSSetShader(ctx, psh, NULL, 0);
|
||||
ID3D11DeviceContext_VSSetConstantBuffers(ctx, 0, 1, &cb);
|
||||
ID3D11DeviceContext_PSSetSamplers(ctx, 0, 1, &smp);
|
||||
for (int d = 0; d < draws; d++) {
|
||||
D3D11_MAPPED_SUBRESOURCE m;
|
||||
ID3D11DeviceContext_Map(ctx, (ID3D11Resource *)cb, 0, D3D11_MAP_WRITE_DISCARD, 0, &m);
|
||||
float *p = m.pData;
|
||||
p[0] = -1 + 2.0f * (d % 40) / 40;
|
||||
p[1] = -1 + 2.0f * ((d / 40) % 40) / 40;
|
||||
p[4] = (d & 1) ? 1.0f : 0.5f;
|
||||
p[5] = (d & 2) ? 1.0f : 0.5f;
|
||||
p[6] = (d & 4) ? 1.0f : 0.5f;
|
||||
p[7] = 1;
|
||||
ID3D11DeviceContext_Unmap(ctx, (ID3D11Resource *)cb, 0);
|
||||
ID3D11DeviceContext_PSSetShaderResources(ctx, 0, 1, &srv[d & 7]);
|
||||
ID3D11DeviceContext_Draw(ctx, 3, 0);
|
||||
}
|
||||
if (fr % 30 == 29) {
|
||||
D3D11_BOX box = {0, 0, 0, 16, 16, 1};
|
||||
ID3D11DeviceContext_CopySubresourceRegion(ctx, (ID3D11Resource *)st, 0, 0, 0, 0, (ID3D11Resource *)rt, 0, &box);
|
||||
D3D11_MAPPED_SUBRESOURCE m;
|
||||
ID3D11DeviceContext_Map(ctx, (ID3D11Resource *)st, 0, D3D11_MAP_READ, 0, &m);
|
||||
ID3D11DeviceContext_Unmap(ctx, (ID3D11Resource *)st, 0);
|
||||
} else {
|
||||
ID3D11DeviceContext_Flush(ctx);
|
||||
}
|
||||
QueryPerformanceCounter(&t1);
|
||||
double ms = (double)(t1.QuadPart - t0.QuadPart) * 1000.0 / (double)f.QuadPart;
|
||||
if (fr >= 10) {
|
||||
total += ms;
|
||||
if (ms > worst) worst = ms;
|
||||
}
|
||||
}
|
||||
printf("%d draws/frame: %.2f ms/frame average (%.0f fps), worst %.2f ms\n", draws, total / frames, 1000.0 * frames / total, worst);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* d3d11_check.exe - checks the D3D11 features the Mali bridge emulates, through DXVK, by reading
|
||||
* back pixels: BC1/BC3/BC4/BC5/BC7 sampling, SV_ClipDistance, instance data step rates
|
||||
* (vertex attribute divisor), plus a plain clear and draw. Prints PASS/FAIL lines; exit 0 = all.
|
||||
*
|
||||
* Built with mingw-w64: x86_64-w64-mingw32-gcc -O2 d3d11_check.c -o d3d11_check.exe -ld3d11 -ld3dcompiler_47 -ldxgi
|
||||
*/
|
||||
#define COBJMACROS
|
||||
#define INITGUID
|
||||
#include <windows.h>
|
||||
#include <d3d11.h>
|
||||
#include <d3dcompiler.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static ID3D11Device *dev;
|
||||
static ID3D11DeviceContext *ctx;
|
||||
static ID3D11Texture2D *rt, *staging;
|
||||
static ID3D11RenderTargetView *rtv;
|
||||
static int failures, checks;
|
||||
#define TW 64
|
||||
#define TH 64
|
||||
|
||||
static void check(int ok, const char *what, const char *detail)
|
||||
{
|
||||
checks++;
|
||||
if (!ok) failures++;
|
||||
printf("%s %s%s%s\n", ok ? "PASS" : "FAIL", what, detail && *detail ? ": " : "", detail ? detail : "");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static ID3DBlob *compile(const char *src, const char *entry, const char *target)
|
||||
{
|
||||
ID3DBlob *code = NULL, *err = NULL;
|
||||
HRESULT hr = D3DCompile(src, strlen(src), NULL, NULL, NULL, entry, target, 0, 0, &code, &err);
|
||||
if (FAILED(hr)) {
|
||||
printf("shader %s failed: %s\n", entry, err ? (const char *)ID3D10Blob_GetBufferPointer(err) : "?");
|
||||
return NULL;
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
/* Reads the render target back; returns a pointer valid until the next call. */
|
||||
static const uint32_t *readback(void)
|
||||
{
|
||||
static uint32_t px[TW * TH];
|
||||
ID3D11DeviceContext_CopyResource(ctx, (ID3D11Resource *)staging, (ID3D11Resource *)rt);
|
||||
D3D11_MAPPED_SUBRESOURCE m;
|
||||
if (FAILED(ID3D11DeviceContext_Map(ctx, (ID3D11Resource *)staging, 0, D3D11_MAP_READ, 0, &m))) return NULL;
|
||||
for (int y = 0; y < TH; y++) memcpy(px + y * TW, (const uint8_t *)m.pData + y * m.RowPitch, TW * 4);
|
||||
ID3D11DeviceContext_Unmap(ctx, (ID3D11Resource *)staging, 0);
|
||||
return px;
|
||||
}
|
||||
|
||||
static int close_to(uint32_t a, uint32_t b, int tol)
|
||||
{
|
||||
for (int i = 0; i < 32; i += 8) {
|
||||
int d = (int)((a >> i) & 0xff) - (int)((b >> i) & 0xff);
|
||||
if (d < -tol || d > tol) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void clear(float r, float g, float b)
|
||||
{
|
||||
float c[4] = {r, g, b, 1};
|
||||
ID3D11DeviceContext_ClearRenderTargetView(ctx, rtv, c);
|
||||
}
|
||||
|
||||
static void bind_target(void)
|
||||
{
|
||||
ID3D11DeviceContext_OMSetRenderTargets(ctx, 1, &rtv, NULL);
|
||||
D3D11_VIEWPORT vp = {0, 0, TW, TH, 0, 1};
|
||||
ID3D11DeviceContext_RSSetViewports(ctx, 1, &vp);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ textures */
|
||||
|
||||
static const char *tex_vs =
|
||||
"struct O { float4 p : SV_Position; float2 uv : TEXCOORD; };"
|
||||
"O main(uint id : SV_VertexID) { O o; float2 t = float2((id << 1) & 2, id & 2);"
|
||||
"o.p = float4(t * float2(2, -2) + float2(-1, 1), 0, 1); o.uv = t; return o; }";
|
||||
static const char *tex_ps =
|
||||
"Texture2D t : register(t0); SamplerState s : register(s0);"
|
||||
"float4 main(float4 p : SV_Position, float2 uv : TEXCOORD) : SV_Target { return t.Sample(s, uv); }";
|
||||
|
||||
/* One 8x8 texture of 2x2 blocks, each block a solid colour. Returns RGBA8 (A in top byte). */
|
||||
static void test_bc(DXGI_FORMAT fmt, const char *name, const void *blocks, size_t block_bytes, const uint32_t want[4], int tol)
|
||||
{
|
||||
D3D11_TEXTURE2D_DESC td = {8, 8, 1, 1, fmt, {1, 0}, D3D11_USAGE_DEFAULT, D3D11_BIND_SHADER_RESOURCE, 0, 0};
|
||||
D3D11_SUBRESOURCE_DATA init = {blocks, (UINT)(block_bytes * 2), 0};
|
||||
ID3D11Texture2D *tex = NULL;
|
||||
HRESULT hr = ID3D11Device_CreateTexture2D(dev, &td, &init, &tex);
|
||||
if (FAILED(hr)) {
|
||||
char d[64];
|
||||
snprintf(d, sizeof d, "CreateTexture2D 0x%08lx", (unsigned long)hr);
|
||||
check(0, name, d);
|
||||
return;
|
||||
}
|
||||
ID3D11ShaderResourceView *srv;
|
||||
ID3D11Device_CreateShaderResourceView(dev, (ID3D11Resource *)tex, NULL, &srv);
|
||||
D3D11_SAMPLER_DESC sd = {D3D11_FILTER_MIN_MAG_MIP_POINT, D3D11_TEXTURE_ADDRESS_CLAMP, D3D11_TEXTURE_ADDRESS_CLAMP,
|
||||
D3D11_TEXTURE_ADDRESS_CLAMP, 0, 1, D3D11_COMPARISON_NEVER, {0}, 0, D3D11_FLOAT32_MAX};
|
||||
ID3D11SamplerState *smp;
|
||||
ID3D11Device_CreateSamplerState(dev, &sd, &smp);
|
||||
static ID3D11VertexShader *vs;
|
||||
static ID3D11PixelShader *ps;
|
||||
if (!vs) {
|
||||
ID3DBlob *v = compile(tex_vs, "main", "vs_5_0"), *p = compile(tex_ps, "main", "ps_5_0");
|
||||
ID3D11Device_CreateVertexShader(dev, ID3D10Blob_GetBufferPointer(v), ID3D10Blob_GetBufferSize(v), NULL, &vs);
|
||||
ID3D11Device_CreatePixelShader(dev, ID3D10Blob_GetBufferPointer(p), ID3D10Blob_GetBufferSize(p), NULL, &ps);
|
||||
}
|
||||
bind_target();
|
||||
clear(0, 0, 0);
|
||||
ID3D11DeviceContext_IASetInputLayout(ctx, NULL);
|
||||
ID3D11DeviceContext_IASetPrimitiveTopology(ctx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||
ID3D11DeviceContext_VSSetShader(ctx, vs, NULL, 0);
|
||||
ID3D11DeviceContext_PSSetShader(ctx, ps, NULL, 0);
|
||||
ID3D11DeviceContext_PSSetShaderResources(ctx, 0, 1, &srv);
|
||||
ID3D11DeviceContext_PSSetSamplers(ctx, 0, 1, &smp);
|
||||
ID3D11DeviceContext_Draw(ctx, 3, 0);
|
||||
const uint32_t *px = readback();
|
||||
/* quadrant centres: TL, TR, BL, BR */
|
||||
int pts[4][2] = {{TW / 4, TH / 4}, {3 * TW / 4, TH / 4}, {TW / 4, 3 * TH / 4}, {3 * TW / 4, 3 * TH / 4}};
|
||||
char d[160] = "";
|
||||
int ok = px != NULL;
|
||||
for (int i = 0; ok && i < 4; i++) {
|
||||
uint32_t v = px[pts[i][1] * TW + pts[i][0]];
|
||||
if (!close_to(v, want[i], tol)) {
|
||||
snprintf(d, sizeof d, "block %d = 0x%08x, want 0x%08x", i, v, want[i]);
|
||||
ok = 0;
|
||||
}
|
||||
}
|
||||
check(ok, name, ok ? "4 blocks decode to the right colours" : d);
|
||||
}
|
||||
|
||||
static void tests_bc(void)
|
||||
{
|
||||
/* BC1: colour0 = colour1 = solid; indices 0. RGB565 values -> 8-bit. */
|
||||
uint16_t c[4] = {0xF800, 0x07E0, 0x001F, 0xFFFF}; /* red, green, blue, white */
|
||||
uint8_t bc1[4][8];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
memcpy(bc1[i], &c[i], 2);
|
||||
memcpy(bc1[i] + 2, &c[i], 2);
|
||||
memset(bc1[i] + 4, 0, 4);
|
||||
}
|
||||
/* layout: block row 0 = blocks 0,1; row 1 = 2,3 */
|
||||
uint32_t want1[4] = {0xFF0000FF, 0xFF00FF00, 0xFFFF0000, 0xFFFFFFFF};
|
||||
test_bc(DXGI_FORMAT_BC1_UNORM, "BC1 texture", bc1, 8, want1, 8);
|
||||
|
||||
/* BC3: alpha 255 (alpha endpoints 255,255) + BC1 colour block. */
|
||||
uint8_t bc3[4][16];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
memset(bc3[i], 0, 16);
|
||||
bc3[i][0] = bc3[i][1] = 0xFF;
|
||||
memcpy(bc3[i] + 8, bc1[i], 8);
|
||||
}
|
||||
test_bc(DXGI_FORMAT_BC3_UNORM, "BC3 texture", bc3, 16, want1, 8);
|
||||
|
||||
/* BC4: single channel; endpoints = value. Sampled as R, G=B=0, A=1. */
|
||||
uint8_t vals[4] = {255, 128, 64, 0};
|
||||
uint8_t bc4[4][8];
|
||||
uint32_t want4[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
memset(bc4[i], 0, 8);
|
||||
bc4[i][0] = bc4[i][1] = vals[i];
|
||||
want4[i] = 0xFF000000u | vals[i];
|
||||
}
|
||||
test_bc(DXGI_FORMAT_BC4_UNORM, "BC4 texture", bc4, 8, want4, 2);
|
||||
|
||||
/* BC5: two channels (R from first half, G from second). */
|
||||
uint8_t bc5[4][16];
|
||||
uint32_t want5[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
memset(bc5[i], 0, 16);
|
||||
bc5[i][0] = bc5[i][1] = vals[i];
|
||||
bc5[i][8] = bc5[i][9] = vals[3 - i];
|
||||
want5[i] = 0xFF000000u | vals[i] | ((uint32_t)vals[3 - i] << 8);
|
||||
}
|
||||
test_bc(DXGI_FORMAT_BC5_UNORM, "BC5 texture", bc5, 16, want5, 2);
|
||||
|
||||
/* BC7 mode 6: endpoints RGBA 7+1 bits; solid colour when both endpoints are equal. Encoded
|
||||
* below by bit packing: mode 6 = bit 6 set (bits 0-6 = 1000000b). */
|
||||
uint8_t bc7[4][16];
|
||||
uint32_t want7[4];
|
||||
uint8_t cols[4][3] = {{255, 0, 0}, {0, 255, 0}, {0, 0, 255}, {255, 255, 255}};
|
||||
for (int i = 0; i < 4; i++) {
|
||||
uint8_t blk[16] = {0};
|
||||
int bit = 0;
|
||||
#define PUT(v, n) do { for (int k_ = 0; k_ < (n); k_++, bit++) if (((v) >> k_) & 1) blk[bit >> 3] |= (uint8_t)(1u << (bit & 7)); } while (0)
|
||||
PUT(1u << 6, 7); /* mode 6 */
|
||||
for (int ch = 0; ch < 3; ch++) { PUT(cols[i][ch] >> 1, 7); PUT(cols[i][ch] >> 1, 7); }
|
||||
PUT(127, 7); PUT(127, 7); /* alpha endpoints */
|
||||
PUT(cols[i][0] & 1, 1); PUT(cols[i][0] & 1, 1); /* p-bits (same for all channels) */
|
||||
#undef PUT
|
||||
memcpy(bc7[i], blk, 16);
|
||||
uint8_t r = cols[i][0], g = cols[i][1], b = cols[i][2];
|
||||
/* 7-bit value + shared p-bit */
|
||||
uint8_t p = cols[i][0] & 1;
|
||||
r = (uint8_t)(((r >> 1) << 1) | p);
|
||||
g = (uint8_t)(((g >> 1) << 1) | p);
|
||||
b = (uint8_t)(((b >> 1) << 1) | p);
|
||||
want7[i] = 0xFF000000u | r | ((uint32_t)g << 8) | ((uint32_t)b << 16);
|
||||
}
|
||||
test_bc(DXGI_FORMAT_BC7_UNORM, "BC7 texture", bc7, 16, want7, 3);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ clip distance */
|
||||
|
||||
static void test_clip(void)
|
||||
{
|
||||
const char *vs =
|
||||
"struct O { float4 p : SV_Position; float c : SV_ClipDistance0; };"
|
||||
"O main(uint id : SV_VertexID) { O o; float2 t = float2((id << 1) & 2, id & 2);"
|
||||
"o.p = float4(t * float2(2, -2) + float2(-1, 1), 0, 1); o.c = o.p.x; return o; }";
|
||||
const char *ps = "float4 main() : SV_Target { return float4(0, 1, 0, 1); }";
|
||||
ID3DBlob *v = compile(vs, "main", "vs_5_0"), *p = compile(ps, "main", "ps_5_0");
|
||||
if (!v || !p) {
|
||||
check(0, "clip distance", "shader compile");
|
||||
return;
|
||||
}
|
||||
ID3D11VertexShader *vsh;
|
||||
ID3D11PixelShader *psh;
|
||||
ID3D11Device_CreateVertexShader(dev, ID3D10Blob_GetBufferPointer(v), ID3D10Blob_GetBufferSize(v), NULL, &vsh);
|
||||
ID3D11Device_CreatePixelShader(dev, ID3D10Blob_GetBufferPointer(p), ID3D10Blob_GetBufferSize(p), NULL, &psh);
|
||||
bind_target();
|
||||
clear(1, 0, 0);
|
||||
ID3D11DeviceContext_IASetInputLayout(ctx, NULL);
|
||||
ID3D11DeviceContext_IASetPrimitiveTopology(ctx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||
ID3D11DeviceContext_VSSetShader(ctx, vsh, NULL, 0);
|
||||
ID3D11DeviceContext_PSSetShader(ctx, psh, NULL, 0);
|
||||
ID3D11DeviceContext_Draw(ctx, 3, 0);
|
||||
const uint32_t *px = readback();
|
||||
int ok = px && close_to(px[TH / 2 * TW + TW / 4], 0xFF0000FF, 2) && close_to(px[TH / 2 * TW + 3 * TW / 4], 0xFF00FF00, 2);
|
||||
char d[96];
|
||||
snprintf(d, sizeof d, "left 0x%08x (want red), right 0x%08x (want green)", px ? px[TH / 2 * TW + TW / 4] : 0,
|
||||
px ? px[TH / 2 * TW + 3 * TW / 4] : 0);
|
||||
check(ok, "SV_ClipDistance", d);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ instance step rate */
|
||||
|
||||
static void test_step_rate(void)
|
||||
{
|
||||
/* Four instances of a quad column; per-instance colour with step rate 2: instances 0-1 get
|
||||
* colour 0 (blue), 2-3 colour 1 (yellow). Each instance covers a quarter of the width. */
|
||||
const char *vs =
|
||||
"struct I { uint id : SV_VertexID; uint inst : SV_InstanceID; float4 col : COLOR; };"
|
||||
"struct O { float4 p : SV_Position; float4 col : COLOR; };"
|
||||
"O main(I i) { O o; float2 t = float2((i.id << 1) & 2, i.id & 2) * 0.5; /* 0..1 tri covering a quad's lower-left half */"
|
||||
"float x0 = -1 + 0.5 * i.inst; float2 q = float2(i.id == 1 ? 1 : 0, i.id == 2 ? 1 : 0);"
|
||||
"float2 pos[6] = { float2(0,0), float2(1,0), float2(0,1), float2(1,0), float2(1,1), float2(0,1) };"
|
||||
"float2 v = pos[i.id]; o.p = float4(x0 + v.x * 0.5, -1 + v.y * 2, 0, 1); o.col = i.col; return o; }";
|
||||
const char *ps = "float4 main(float4 p : SV_Position, float4 c : COLOR) : SV_Target { return c; }";
|
||||
ID3DBlob *v = compile(vs, "main", "vs_5_0"), *p = compile(ps, "main", "ps_5_0");
|
||||
if (!v || !p) {
|
||||
check(0, "instance step rate", "shader compile");
|
||||
return;
|
||||
}
|
||||
ID3D11VertexShader *vsh;
|
||||
ID3D11PixelShader *psh;
|
||||
ID3D11Device_CreateVertexShader(dev, ID3D10Blob_GetBufferPointer(v), ID3D10Blob_GetBufferSize(v), NULL, &vsh);
|
||||
ID3D11Device_CreatePixelShader(dev, ID3D10Blob_GetBufferPointer(p), ID3D10Blob_GetBufferSize(p), NULL, &psh);
|
||||
D3D11_INPUT_ELEMENT_DESC el = {"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D11_INPUT_PER_INSTANCE_DATA, 2};
|
||||
ID3D11InputLayout *il;
|
||||
HRESULT hr = ID3D11Device_CreateInputLayout(dev, &el, 1, ID3D10Blob_GetBufferPointer(v), ID3D10Blob_GetBufferSize(v), &il);
|
||||
if (FAILED(hr)) {
|
||||
check(0, "instance step rate", "CreateInputLayout");
|
||||
return;
|
||||
}
|
||||
float cols[2][4] = {{0, 0, 1, 1}, {1, 1, 0, 1}};
|
||||
D3D11_BUFFER_DESC bd = {sizeof cols, D3D11_USAGE_DEFAULT, D3D11_BIND_VERTEX_BUFFER, 0, 0, 0};
|
||||
D3D11_SUBRESOURCE_DATA init = {cols, 0, 0};
|
||||
ID3D11Buffer *vb;
|
||||
ID3D11Device_CreateBuffer(dev, &bd, &init, &vb);
|
||||
UINT stride = 16, offset = 0;
|
||||
D3D11_RASTERIZER_DESC rd = {D3D11_FILL_SOLID, D3D11_CULL_NONE, FALSE, 0, 0, 0, TRUE, FALSE, FALSE, FALSE};
|
||||
ID3D11RasterizerState *rs;
|
||||
ID3D11Device_CreateRasterizerState(dev, &rd, &rs);
|
||||
ID3D11DeviceContext_RSSetState(ctx, rs);
|
||||
bind_target();
|
||||
clear(0, 0, 0);
|
||||
ID3D11DeviceContext_IASetInputLayout(ctx, il);
|
||||
ID3D11DeviceContext_IASetVertexBuffers(ctx, 0, 1, &vb, &stride, &offset);
|
||||
ID3D11DeviceContext_IASetPrimitiveTopology(ctx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||
ID3D11DeviceContext_VSSetShader(ctx, vsh, NULL, 0);
|
||||
ID3D11DeviceContext_PSSetShader(ctx, psh, NULL, 0);
|
||||
ID3D11DeviceContext_DrawInstanced(ctx, 6, 4, 0, 0);
|
||||
const uint32_t *px = readback();
|
||||
uint32_t want[4] = {0xFFFF0000, 0xFFFF0000, 0xFF00FFFF, 0xFF00FFFF};
|
||||
int ok = px != NULL;
|
||||
char d[128] = "instances 0-1 blue, 2-3 yellow";
|
||||
for (int i = 0; ok && i < 4; i++) {
|
||||
uint32_t got = px[TH / 2 * TW + i * TW / 4 + TW / 8];
|
||||
if (!close_to(got, want[i], 2)) {
|
||||
snprintf(d, sizeof d, "instance %d = 0x%08x, want 0x%08x", i, got, want[i]);
|
||||
ok = 0;
|
||||
}
|
||||
}
|
||||
check(ok, "instance step rate 2", d);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ depth clip */
|
||||
|
||||
static void test_depth_clip(void)
|
||||
{
|
||||
/* A full-screen triangle at z = 1.5 (beyond the far plane): clipped away with depth clip on,
|
||||
* visible (clamped to the far plane) with it off. */
|
||||
const char *vs = "float4 main(uint id : SV_VertexID) : SV_Position { float2 t = float2((id << 1) & 2, id & 2);"
|
||||
"return float4(t * float2(2, -2) + float2(-1, 1), 1.5, 1); }";
|
||||
const char *ps = "float4 main() : SV_Target { return float4(1, 1, 1, 1); }";
|
||||
ID3DBlob *v = compile(vs, "main", "vs_5_0"), *p = compile(ps, "main", "ps_5_0");
|
||||
ID3D11VertexShader *vsh;
|
||||
ID3D11PixelShader *psh;
|
||||
ID3D11Device_CreateVertexShader(dev, ID3D10Blob_GetBufferPointer(v), ID3D10Blob_GetBufferSize(v), NULL, &vsh);
|
||||
ID3D11Device_CreatePixelShader(dev, ID3D10Blob_GetBufferPointer(p), ID3D10Blob_GetBufferSize(p), NULL, &psh);
|
||||
uint32_t got[2];
|
||||
for (int on = 1; on >= 0; on--) {
|
||||
D3D11_RASTERIZER_DESC rd = {D3D11_FILL_SOLID, D3D11_CULL_NONE, FALSE, 0, 0, 0, on ? TRUE : FALSE, FALSE, FALSE, FALSE};
|
||||
ID3D11RasterizerState *rs;
|
||||
ID3D11Device_CreateRasterizerState(dev, &rd, &rs);
|
||||
ID3D11DeviceContext_RSSetState(ctx, rs);
|
||||
bind_target();
|
||||
clear(0, 0, 0);
|
||||
ID3D11DeviceContext_IASetInputLayout(ctx, NULL);
|
||||
ID3D11DeviceContext_IASetPrimitiveTopology(ctx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||
ID3D11DeviceContext_VSSetShader(ctx, vsh, NULL, 0);
|
||||
ID3D11DeviceContext_PSSetShader(ctx, psh, NULL, 0);
|
||||
ID3D11DeviceContext_Draw(ctx, 3, 0);
|
||||
const uint32_t *px = readback();
|
||||
got[on] = px ? px[TH / 2 * TW + TW / 2] : 0;
|
||||
}
|
||||
ID3D11DeviceContext_RSSetState(ctx, NULL);
|
||||
char d[96];
|
||||
snprintf(d, sizeof d, "clip on 0x%08x (want black), off 0x%08x (want white)", got[1], got[0]);
|
||||
check(close_to(got[1], 0xFF000000, 2) && close_to(got[0], 0xFFFFFFFF, 2), "DepthClipEnable", d);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
D3D_FEATURE_LEVEL fl = D3D_FEATURE_LEVEL_11_0, got;
|
||||
HRESULT hr = D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, &fl, 1, D3D11_SDK_VERSION, &dev, &got, &ctx);
|
||||
char d[64];
|
||||
snprintf(d, sizeof d, "hr 0x%08lx", (unsigned long)hr);
|
||||
check(SUCCEEDED(hr), "D3D11 device (feature level 11_0)", SUCCEEDED(hr) ? "" : d);
|
||||
if (FAILED(hr)) return 1;
|
||||
D3D11_TEXTURE2D_DESC td = {TW, TH, 1, 1, DXGI_FORMAT_R8G8B8A8_UNORM, {1, 0}, D3D11_USAGE_DEFAULT, D3D11_BIND_RENDER_TARGET, 0, 0};
|
||||
ID3D11Device_CreateTexture2D(dev, &td, NULL, &rt);
|
||||
ID3D11Device_CreateRenderTargetView(dev, (ID3D11Resource *)rt, NULL, &rtv);
|
||||
td.Usage = D3D11_USAGE_STAGING;
|
||||
td.BindFlags = 0;
|
||||
td.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||
ID3D11Device_CreateTexture2D(dev, &td, NULL, &staging);
|
||||
bind_target();
|
||||
clear(0.25f, 0.5f, 0.75f);
|
||||
const uint32_t *px = readback();
|
||||
check(px && close_to(px[0], 0xFFBF8040, 2), "clear + readback", "");
|
||||
tests_bc();
|
||||
test_clip();
|
||||
test_step_rate();
|
||||
test_depth_clip();
|
||||
printf("%s: %d of %d checks passed\n", failures ? "FAILED" : "OK", checks - failures, checks);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
# Compiles the test shaders into C arrays (tests/smoke_spv.h). Needs glslangValidator.
|
||||
set -eu
|
||||
cd "$(dirname "$0")"
|
||||
out=smoke_spv.h
|
||||
echo "/* Generated by gen_shaders.sh from shaders/ - do not edit. */" > $out
|
||||
for s in shaders/*.comp shaders/*.vert shaders/*.frag; do
|
||||
name=$(basename "$s" | tr . _)
|
||||
glslangValidator -V --vn "spv_$name" -o tmp.h "$s" >/dev/null
|
||||
grep -v '^ *//' tmp.h | sed 's/^const uint32_t/static const uint32_t/' >> $out
|
||||
rm -f tmp.h
|
||||
done
|
||||
@@ -0,0 +1,9 @@
|
||||
#version 450
|
||||
layout(local_size_x = 64) in;
|
||||
layout(std430, binding = 0) readonly buffer In { uint v[]; } src;
|
||||
layout(std430, binding = 1) writeonly buffer Out { uint v[]; } dst;
|
||||
layout(push_constant) uniform PC { uint mul; uint add; } pc;
|
||||
void main() {
|
||||
uint i = gl_GlobalInvocationID.x;
|
||||
dst.v[i] = src.v[i] * pc.mul + pc.add;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#version 450
|
||||
layout(push_constant) uniform PC { vec4 color; } pc;
|
||||
layout(location = 0) out vec4 o;
|
||||
void main() { o = pc.color; }
|
||||
@@ -0,0 +1,5 @@
|
||||
#version 450
|
||||
void main() {
|
||||
vec2 p = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
|
||||
gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,513 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* droiddeck-vkbridge-smoke: a self-checking Vulkan test, run at session start (Mali bridge on)
|
||||
* and on the host against the bridge. Every check compares real bytes; output goes to stdout
|
||||
* one line per check, exit status 0 only if all pass.
|
||||
*
|
||||
* droiddeck-vkbridge-smoke [--gpu N] [--quiet]
|
||||
*/
|
||||
#define VK_NO_PROTOTYPES
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "smoke_spv.h"
|
||||
|
||||
static PFN_vkGetInstanceProcAddr gipa;
|
||||
static VkInstance inst;
|
||||
static VkPhysicalDevice pd;
|
||||
static VkDevice dev;
|
||||
static VkQueue queue;
|
||||
static uint32_t qfam;
|
||||
static VkCommandPool pool;
|
||||
static VkPhysicalDeviceMemoryProperties mp;
|
||||
static int failures, checks;
|
||||
|
||||
#define I(name) PFN_##name name
|
||||
static I(vkCreateInstance); static I(vkEnumeratePhysicalDevices); static I(vkGetPhysicalDeviceProperties);
|
||||
static I(vkGetPhysicalDeviceQueueFamilyProperties); static I(vkGetPhysicalDeviceMemoryProperties); static I(vkCreateDevice);
|
||||
static I(vkGetDeviceProcAddr); static I(vkDestroyInstance); static I(vkGetPhysicalDeviceFeatures);
|
||||
static I(vkGetDeviceQueue); static I(vkCreateCommandPool); static I(vkAllocateCommandBuffers); static I(vkBeginCommandBuffer);
|
||||
static I(vkEndCommandBuffer); static I(vkQueueSubmit); static I(vkQueueWaitIdle); static I(vkCreateBuffer);
|
||||
static I(vkGetBufferMemoryRequirements); static I(vkAllocateMemory); static I(vkBindBufferMemory); static I(vkMapMemory);
|
||||
static I(vkCmdFillBuffer); static I(vkCmdCopyBuffer); static I(vkCmdUpdateBuffer); static I(vkCmdPipelineBarrier);
|
||||
static I(vkCreateShaderModule); static I(vkCreateDescriptorSetLayout); static I(vkCreatePipelineLayout);
|
||||
static I(vkCreateComputePipelines); static I(vkCreateDescriptorPool); static I(vkAllocateDescriptorSets);
|
||||
static I(vkUpdateDescriptorSets); static I(vkCmdBindPipeline); static I(vkCmdBindDescriptorSets); static I(vkCmdPushConstants);
|
||||
static I(vkCmdDispatch); static I(vkCreateImage); static I(vkGetImageMemoryRequirements); static I(vkBindImageMemory);
|
||||
static I(vkCmdClearColorImage); static I(vkCmdCopyImageToBuffer); static I(vkCreateImageView); static I(vkCreateRenderPass);
|
||||
static I(vkCreateFramebuffer); static I(vkCreateGraphicsPipelines); static I(vkCmdBeginRenderPass); static I(vkCmdEndRenderPass);
|
||||
static I(vkCmdDraw); static I(vkCreateSemaphore); static I(vkSignalSemaphore); static I(vkWaitSemaphores);
|
||||
static I(vkGetSemaphoreCounterValue); static I(vkDestroyDevice); static I(vkFlushMappedMemoryRanges);
|
||||
static I(vkInvalidateMappedMemoryRanges); static I(vkCmdSetViewport); static I(vkCmdSetScissor);
|
||||
static I(vkCreateFence); static I(vkWaitForFences); static I(vkCmdCopyBufferToImage);
|
||||
#undef I
|
||||
|
||||
static void check(int ok, const char *what, const char *detail)
|
||||
{
|
||||
checks++;
|
||||
if (!ok) failures++;
|
||||
printf("%s %s%s%s\n", ok ? "PASS" : "FAIL", what, detail && *detail ? ": " : "", detail ? detail : "");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static double now_ms(void)
|
||||
{
|
||||
struct timespec t;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||
return t.tv_sec * 1e3 + t.tv_nsec / 1e6;
|
||||
}
|
||||
|
||||
static uint32_t find_type(uint32_t bits, VkMemoryPropertyFlags want)
|
||||
{
|
||||
for (uint32_t i = 0; i < mp.memoryTypeCount; i++)
|
||||
if ((bits & (1u << i)) && (mp.memoryTypes[i].propertyFlags & want) == want) return i;
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
VkBuffer buf;
|
||||
VkDeviceMemory mem;
|
||||
void *ptr;
|
||||
VkMemoryPropertyFlags flags;
|
||||
} buffer;
|
||||
|
||||
static int make_buffer(buffer *b, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags want)
|
||||
{
|
||||
VkBufferCreateInfo ci = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, NULL, 0, size, usage, VK_SHARING_MODE_EXCLUSIVE, 0, NULL};
|
||||
if (vkCreateBuffer(dev, &ci, NULL, &b->buf)) return 0;
|
||||
VkMemoryRequirements r;
|
||||
vkGetBufferMemoryRequirements(dev, b->buf, &r);
|
||||
uint32_t t = find_type(r.memoryTypeBits, want);
|
||||
if (t == UINT32_MAX) return 0;
|
||||
b->flags = mp.memoryTypes[t].propertyFlags;
|
||||
VkMemoryAllocateInfo ai = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, r.size, t};
|
||||
if (vkAllocateMemory(dev, &ai, NULL, &b->mem) || vkBindBufferMemory(dev, b->buf, b->mem, 0)) return 0;
|
||||
b->ptr = NULL;
|
||||
if (want & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) {
|
||||
if (vkMapMemory(dev, b->mem, 0, VK_WHOLE_SIZE, 0, &b->ptr)) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void sync_to_gpu(buffer *b)
|
||||
{
|
||||
if (b->flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) return;
|
||||
VkMappedMemoryRange r = {VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, NULL, b->mem, 0, VK_WHOLE_SIZE};
|
||||
vkFlushMappedMemoryRanges(dev, 1, &r);
|
||||
}
|
||||
|
||||
static void sync_from_gpu(buffer *b)
|
||||
{
|
||||
if (b->flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) return;
|
||||
VkMappedMemoryRange r = {VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, NULL, b->mem, 0, VK_WHOLE_SIZE};
|
||||
vkInvalidateMappedMemoryRanges(dev, 1, &r);
|
||||
}
|
||||
|
||||
static VkCommandBuffer begin(void)
|
||||
{
|
||||
VkCommandBufferAllocateInfo ai = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, NULL, pool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1};
|
||||
VkCommandBuffer cb;
|
||||
vkAllocateCommandBuffers(dev, &ai, &cb);
|
||||
VkCommandBufferBeginInfo bi = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, NULL};
|
||||
vkBeginCommandBuffer(cb, &bi);
|
||||
return cb;
|
||||
}
|
||||
|
||||
static int submit(VkCommandBuffer cb)
|
||||
{
|
||||
VkMemoryBarrier mb = {VK_STRUCTURE_TYPE_MEMORY_BARRIER, NULL, VK_ACCESS_MEMORY_WRITE_BIT, VK_ACCESS_HOST_READ_BIT};
|
||||
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 1, &mb, 0, NULL, 0, NULL);
|
||||
if (vkEndCommandBuffer(cb)) return 0;
|
||||
VkFenceCreateInfo fci = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
|
||||
VkFence f;
|
||||
vkCreateFence(dev, &fci, NULL, &f);
|
||||
VkSubmitInfo si = {VK_STRUCTURE_TYPE_SUBMIT_INFO, NULL, 0, NULL, NULL, 1, &cb, 0, NULL};
|
||||
return !vkQueueSubmit(queue, 1, &si, f) && !vkWaitForFences(dev, 1, &f, VK_TRUE, 10000000000ull);
|
||||
}
|
||||
|
||||
static VkShaderModule shader(const uint32_t *code, size_t size)
|
||||
{
|
||||
VkShaderModuleCreateInfo ci = {VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, NULL, 0, size, code};
|
||||
VkShaderModule m = VK_NULL_HANDLE;
|
||||
vkCreateShaderModule(dev, &ci, NULL, &m);
|
||||
return m;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ tests */
|
||||
|
||||
static void test_memory(void)
|
||||
{
|
||||
char d[160];
|
||||
buffer b;
|
||||
const uint32_t n = 1 << 16;
|
||||
if (!make_buffer(&b, n * 4, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) {
|
||||
check(0, "host-visible buffer", "cannot create/map");
|
||||
return;
|
||||
}
|
||||
uint32_t *p = b.ptr;
|
||||
for (uint32_t i = 0; i < n; i++) p[i] = i ^ 0x5a5a0000u;
|
||||
sync_to_gpu(&b);
|
||||
VkCommandBuffer cb = begin();
|
||||
vkCmdFillBuffer(cb, b.buf, 0, n, 0xC0FFEE00u); /* words [0, n/4) */
|
||||
VkBufferCopy c = {n * 2, n * 3, n}; /* words [n/2, 3n/4) -> [3n/4, n) */
|
||||
vkCmdCopyBuffer(cb, b.buf, b.buf, 1, &c);
|
||||
uint32_t upd[16];
|
||||
for (int i = 0; i < 16; i++) upd[i] = 0xABC00000u + (uint32_t)i;
|
||||
vkCmdUpdateBuffer(cb, b.buf, n + 64, sizeof(upd), upd); /* words [n/4 + 16, +16) */
|
||||
int ok = submit(cb);
|
||||
sync_from_gpu(&b);
|
||||
int bad = -1;
|
||||
for (uint32_t i = 0; ok && i < n; i++) {
|
||||
uint32_t want;
|
||||
if (i < n / 4) want = 0xC0FFEE00u;
|
||||
else if (i >= n / 4 + 16 && i < n / 4 + 32) want = 0xABC00000u + (i - n / 4 - 16);
|
||||
else if (i >= 3 * n / 4) want = (i - n / 4) ^ 0x5a5a0000u;
|
||||
else want = i ^ 0x5a5a0000u;
|
||||
if (p[i] != want) {
|
||||
bad = (int)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
snprintf(d, sizeof(d), bad < 0 ? "fill/copy/update seen through the mapping (memory flags 0x%x)" : "word %d wrong", bad < 0 ? (int)b.flags : bad);
|
||||
check(ok && bad < 0, "mapped memory + transfer commands", d);
|
||||
}
|
||||
|
||||
static void test_compute(void)
|
||||
{
|
||||
char d[160];
|
||||
const uint32_t n = 4096;
|
||||
buffer in, out;
|
||||
if (!make_buffer(&in, n * 4, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) ||
|
||||
!make_buffer(&out, n * 4, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) {
|
||||
check(0, "compute", "buffers");
|
||||
return;
|
||||
}
|
||||
for (uint32_t i = 0; i < n; i++) ((uint32_t *)in.ptr)[i] = i;
|
||||
sync_to_gpu(&in);
|
||||
VkDescriptorSetLayoutBinding binds[2] = {{0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, NULL},
|
||||
{1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, NULL}};
|
||||
VkDescriptorSetLayoutCreateInfo lci = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, NULL, 0, 2, binds};
|
||||
VkDescriptorSetLayout dsl;
|
||||
vkCreateDescriptorSetLayout(dev, &lci, NULL, &dsl);
|
||||
VkPushConstantRange pcr = {VK_SHADER_STAGE_COMPUTE_BIT, 0, 8};
|
||||
VkPipelineLayoutCreateInfo plci = {VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, NULL, 0, 1, &dsl, 1, &pcr};
|
||||
VkPipelineLayout pl;
|
||||
vkCreatePipelineLayout(dev, &plci, NULL, &pl);
|
||||
VkComputePipelineCreateInfo cpci = {VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
|
||||
cpci.stage = (VkPipelineShaderStageCreateInfo){VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, NULL, 0, VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
shader(spv_smoke_comp, sizeof(spv_smoke_comp)), "main", NULL};
|
||||
cpci.layout = pl;
|
||||
VkPipeline pipe;
|
||||
double t0 = now_ms();
|
||||
VkResult r = vkCreateComputePipelines(dev, VK_NULL_HANDLE, 1, &cpci, NULL, &pipe);
|
||||
if (r) {
|
||||
check(0, "compute pipeline", "creation failed");
|
||||
return;
|
||||
}
|
||||
VkDescriptorPoolSize ps = {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2};
|
||||
VkDescriptorPoolCreateInfo dpci = {VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, NULL, 0, 1, 1, &ps};
|
||||
VkDescriptorPool dp;
|
||||
vkCreateDescriptorPool(dev, &dpci, NULL, &dp);
|
||||
VkDescriptorSetAllocateInfo dsai = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, NULL, dp, 1, &dsl};
|
||||
VkDescriptorSet set;
|
||||
vkAllocateDescriptorSets(dev, &dsai, &set);
|
||||
VkDescriptorBufferInfo bi[2] = {{in.buf, 0, VK_WHOLE_SIZE}, {out.buf, 0, VK_WHOLE_SIZE}};
|
||||
VkWriteDescriptorSet w[2] = {{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, set, 0, 0, 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, NULL, &bi[0], NULL},
|
||||
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, set, 1, 0, 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, NULL, &bi[1], NULL}};
|
||||
vkUpdateDescriptorSets(dev, 2, w, 0, NULL);
|
||||
VkCommandBuffer cb = begin();
|
||||
vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_COMPUTE, pipe);
|
||||
vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_COMPUTE, pl, 0, 1, &set, 0, NULL);
|
||||
uint32_t pc[2] = {3, 7};
|
||||
vkCmdPushConstants(cb, pl, VK_SHADER_STAGE_COMPUTE_BIT, 0, 8, pc);
|
||||
vkCmdDispatch(cb, n / 64, 1, 1);
|
||||
int ok = submit(cb);
|
||||
sync_from_gpu(&out);
|
||||
int bad = -1;
|
||||
for (uint32_t i = 0; ok && i < n; i++)
|
||||
if (((uint32_t *)out.ptr)[i] != i * 3 + 7) {
|
||||
bad = (int)i;
|
||||
break;
|
||||
}
|
||||
snprintf(d, sizeof(d), bad < 0 ? "%u invocations correct (%.1f ms incl. pipeline)" : "element %d wrong", bad < 0 ? n : (unsigned)bad,
|
||||
now_ms() - t0);
|
||||
check(ok && bad < 0, "compute shader", d);
|
||||
}
|
||||
|
||||
static VkImage make_image(VkFormat fmt, uint32_t w, uint32_t h, VkImageUsageFlags usage)
|
||||
{
|
||||
VkImageCreateInfo ci = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, NULL, 0, VK_IMAGE_TYPE_2D, fmt, {w, h, 1}, 1, 1, VK_SAMPLE_COUNT_1_BIT,
|
||||
VK_IMAGE_TILING_OPTIMAL, usage, VK_SHARING_MODE_EXCLUSIVE, 0, NULL, VK_IMAGE_LAYOUT_UNDEFINED};
|
||||
VkImage img;
|
||||
if (vkCreateImage(dev, &ci, NULL, &img)) return VK_NULL_HANDLE;
|
||||
VkMemoryRequirements r;
|
||||
vkGetImageMemoryRequirements(dev, img, &r);
|
||||
uint32_t t = find_type(r.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
if (t == UINT32_MAX) t = find_type(r.memoryTypeBits, 0);
|
||||
VkMemoryAllocateInfo ai = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, r.size, t};
|
||||
VkDeviceMemory m;
|
||||
if (vkAllocateMemory(dev, &ai, NULL, &m) || vkBindImageMemory(dev, img, m, 0)) return VK_NULL_HANDLE;
|
||||
return img;
|
||||
}
|
||||
|
||||
static void barrier(VkCommandBuffer cb, VkImage img, VkImageLayout from, VkImageLayout to)
|
||||
{
|
||||
VkImageMemoryBarrier b = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, NULL, VK_ACCESS_MEMORY_WRITE_BIT,
|
||||
VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, from, to, VK_QUEUE_FAMILY_IGNORED,
|
||||
VK_QUEUE_FAMILY_IGNORED, img, {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
|
||||
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, NULL, 0, NULL, 1, &b);
|
||||
}
|
||||
|
||||
static void test_image_clear(void)
|
||||
{
|
||||
const uint32_t w = 64, h = 64;
|
||||
VkImage img = make_image(VK_FORMAT_R8G8B8A8_UNORM, w, h, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
|
||||
buffer rb;
|
||||
if (!img || !make_buffer(&rb, w * h * 4, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) {
|
||||
check(0, "image clear + readback", "setup");
|
||||
return;
|
||||
}
|
||||
VkCommandBuffer cb = begin();
|
||||
barrier(cb, img, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
VkClearColorValue col = {.float32 = {1.0f, 0.5f, 0.0f, 1.0f}};
|
||||
VkImageSubresourceRange rng = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
vkCmdClearColorImage(cb, img, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &col, 1, &rng);
|
||||
/* upload a 8x8 block from the buffer over the corner, to test buffer->image too */
|
||||
for (int i = 0; i < 64; i++) ((uint32_t *)rb.ptr)[i] = 0xFF112233u;
|
||||
sync_to_gpu(&rb);
|
||||
VkBufferImageCopy up = {0, 8, 8, {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, {0, 0, 0}, {8, 8, 1}};
|
||||
vkCmdCopyBufferToImage(cb, rb.buf, img, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &up);
|
||||
barrier(cb, img, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
VkBufferImageCopy c = {0, 0, 0, {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, {0, 0, 0}, {w, h, 1}};
|
||||
VkMemoryBarrier mb = {VK_STRUCTURE_TYPE_MEMORY_BARRIER, NULL, VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT};
|
||||
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &mb, 0, NULL, 0, NULL);
|
||||
vkCmdCopyImageToBuffer(cb, img, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, rb.buf, 1, &c);
|
||||
int ok = submit(cb);
|
||||
sync_from_gpu(&rb);
|
||||
uint32_t *p = rb.ptr;
|
||||
uint32_t want_clear = 0xFF0080FFu; /* R=255 G=128 B=0 A=255, little endian; 0.5 -> 127 or 128 */
|
||||
int bad = -1;
|
||||
for (uint32_t y = 0; ok && y < h; y++)
|
||||
for (uint32_t x = 0; x < w; x++) {
|
||||
uint32_t v = p[y * w + x];
|
||||
int corner = x < 8 && y < 8;
|
||||
int good = corner ? v == 0xFF112233u : ((v & 0xFFFF00FFu) == (want_clear & 0xFFFF00FFu) && ((v >> 8) & 0xFF) >= 127 && ((v >> 8) & 0xFF) <= 128);
|
||||
if (!good && bad < 0) bad = (int)(y * w + x);
|
||||
}
|
||||
char d[128];
|
||||
snprintf(d, sizeof(d), bad < 0 ? "64x64 RGBA8 clear, upload and readback exact" : "pixel %d = 0x%08x", bad, bad < 0 ? 0 : p[bad]);
|
||||
check(ok && bad < 0, "image clear/upload/readback", d);
|
||||
}
|
||||
|
||||
static void test_draw(void)
|
||||
{
|
||||
const uint32_t w = 128, h = 96;
|
||||
VkFormat fmt = VK_FORMAT_B8G8R8A8_UNORM;
|
||||
VkImage img = make_image(fmt, w, h, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
|
||||
buffer rb;
|
||||
if (!img || !make_buffer(&rb, w * h * 4, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) {
|
||||
check(0, "draw", "setup");
|
||||
return;
|
||||
}
|
||||
VkImageViewCreateInfo vci = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, NULL, 0, img, VK_IMAGE_VIEW_TYPE_2D, fmt, {0},
|
||||
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
|
||||
VkImageView view;
|
||||
vkCreateImageView(dev, &vci, NULL, &view);
|
||||
VkAttachmentDescription att = {0, fmt, VK_SAMPLE_COUNT_1_BIT, VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE,
|
||||
VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL};
|
||||
VkAttachmentReference ref = {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
||||
VkSubpassDescription sub = {0, VK_PIPELINE_BIND_POINT_GRAPHICS, 0, NULL, 1, &ref, NULL, NULL, 0, NULL};
|
||||
VkRenderPassCreateInfo rpci = {VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, NULL, 0, 1, &att, 1, &sub, 0, NULL};
|
||||
VkRenderPass rp;
|
||||
vkCreateRenderPass(dev, &rpci, NULL, &rp);
|
||||
VkFramebufferCreateInfo fci = {VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, NULL, 0, rp, 1, &view, w, h, 1};
|
||||
VkFramebuffer fb;
|
||||
vkCreateFramebuffer(dev, &fci, NULL, &fb);
|
||||
VkPushConstantRange pcr = {VK_SHADER_STAGE_FRAGMENT_BIT, 0, 16};
|
||||
VkPipelineLayoutCreateInfo plci = {VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, NULL, 0, 0, NULL, 1, &pcr};
|
||||
VkPipelineLayout pl;
|
||||
vkCreatePipelineLayout(dev, &plci, NULL, &pl);
|
||||
VkPipelineShaderStageCreateInfo st[2] = {
|
||||
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, NULL, 0, VK_SHADER_STAGE_VERTEX_BIT, shader(spv_smoke_vert, sizeof(spv_smoke_vert)), "main", NULL},
|
||||
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, NULL, 0, VK_SHADER_STAGE_FRAGMENT_BIT, shader(spv_smoke_frag, sizeof(spv_smoke_frag)), "main", NULL}};
|
||||
VkPipelineVertexInputStateCreateInfo vi = {VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO};
|
||||
VkPipelineInputAssemblyStateCreateInfo ia = {VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, NULL, 0, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0};
|
||||
VkPipelineViewportStateCreateInfo vp = {VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, NULL, 0, 1, NULL, 1, NULL};
|
||||
VkPipelineRasterizationStateCreateInfo rs = {VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO};
|
||||
rs.polygonMode = VK_POLYGON_MODE_FILL;
|
||||
rs.cullMode = VK_CULL_MODE_NONE;
|
||||
rs.lineWidth = 1.0f;
|
||||
VkPipelineMultisampleStateCreateInfo ms = {VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, NULL, 0, VK_SAMPLE_COUNT_1_BIT};
|
||||
VkPipelineColorBlendAttachmentState cba = {0};
|
||||
cba.colorWriteMask = 0xF;
|
||||
VkPipelineColorBlendStateCreateInfo cb_ = {VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, NULL, 0, 0, 0, 1, &cba, {0}};
|
||||
VkDynamicState dyn[2] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
|
||||
VkPipelineDynamicStateCreateInfo ds = {VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, NULL, 0, 2, dyn};
|
||||
VkGraphicsPipelineCreateInfo gpci = {VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, NULL, 0, 2, st, &vi, &ia, NULL, &vp, &rs, &ms, NULL, &cb_, &ds,
|
||||
pl, rp, 0, VK_NULL_HANDLE, -1};
|
||||
VkPipeline pipe;
|
||||
if (vkCreateGraphicsPipelines(dev, VK_NULL_HANDLE, 1, &gpci, NULL, &pipe)) {
|
||||
check(0, "graphics pipeline", "creation failed");
|
||||
return;
|
||||
}
|
||||
VkCommandBuffer cb = begin();
|
||||
VkClearValue clear = {.color = {.float32 = {0, 0, 0, 1}}};
|
||||
VkRenderPassBeginInfo rbi = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, NULL, rp, fb, {{0, 0}, {w, h}}, 1, &clear};
|
||||
vkCmdBeginRenderPass(cb, &rbi, VK_SUBPASS_CONTENTS_INLINE);
|
||||
vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
|
||||
/* left half only: the right half keeps the clear colour */
|
||||
VkViewport v = {0, 0, (float)w, (float)h, 0, 1};
|
||||
VkRect2D sc = {{0, 0}, {w / 2, h}};
|
||||
vkCmdSetViewport(cb, 0, 1, &v);
|
||||
vkCmdSetScissor(cb, 0, 1, &sc);
|
||||
float color[4] = {0.0f, 1.0f, 0.0f, 1.0f};
|
||||
vkCmdPushConstants(cb, pl, VK_SHADER_STAGE_FRAGMENT_BIT, 0, 16, color);
|
||||
vkCmdDraw(cb, 3, 1, 0, 0);
|
||||
vkCmdEndRenderPass(cb);
|
||||
VkBufferImageCopy c = {0, 0, 0, {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, {0, 0, 0}, {w, h, 1}};
|
||||
vkCmdCopyImageToBuffer(cb, img, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, rb.buf, 1, &c);
|
||||
int ok = submit(cb);
|
||||
sync_from_gpu(&rb);
|
||||
uint32_t *p = rb.ptr;
|
||||
int bad = -1;
|
||||
for (uint32_t y = 0; ok && y < h; y++)
|
||||
for (uint32_t x = 0; x < w; x++) {
|
||||
uint32_t want = x < w / 2 ? 0xFF00FF00u : 0xFF000000u; /* BGRA: green / black */
|
||||
if (p[y * w + x] != want && bad < 0) bad = (int)(y * w + x);
|
||||
}
|
||||
char d[128];
|
||||
snprintf(d, sizeof(d), bad < 0 ? "render pass + pipeline + scissor exact (%ux%u)" : "pixel %d = 0x%08x", bad < 0 ? w : (unsigned)bad,
|
||||
bad < 0 ? h : p[bad]);
|
||||
check(ok && bad < 0, "draw", d);
|
||||
}
|
||||
|
||||
static void test_timeline(void)
|
||||
{
|
||||
VkSemaphoreTypeCreateInfo t = {VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO, NULL, VK_SEMAPHORE_TYPE_TIMELINE, 5};
|
||||
VkSemaphoreCreateInfo ci = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, &t, 0};
|
||||
VkSemaphore s;
|
||||
if (!vkCreateSemaphore || vkCreateSemaphore(dev, &ci, NULL, &s) || !vkSignalSemaphore) {
|
||||
check(1, "timeline semaphore", "skipped (Vulkan 1.2 not available)");
|
||||
return;
|
||||
}
|
||||
VkSemaphoreSignalInfo si = {VK_STRUCTURE_TYPE_SEMAPHORE_SIGNAL_INFO, NULL, s, 9};
|
||||
vkSignalSemaphore(dev, &si);
|
||||
uint64_t v = 0;
|
||||
vkGetSemaphoreCounterValue(dev, s, &v);
|
||||
uint64_t want = 9;
|
||||
VkSemaphoreWaitInfo wi = {VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO, NULL, 0, 1, &s, &want};
|
||||
VkResult r = vkWaitSemaphores(dev, &wi, 1000000000ull);
|
||||
char d[64];
|
||||
snprintf(d, sizeof(d), "value %llu", (unsigned long long)v);
|
||||
check(v == 9 && r == VK_SUCCESS, "timeline semaphore", d);
|
||||
}
|
||||
|
||||
static void test_roundtrip_rate(void)
|
||||
{
|
||||
/* Bridge overhead: how many trivial synchronous calls per second. */
|
||||
VkFenceCreateInfo fci = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, NULL, VK_FENCE_CREATE_SIGNALED_BIT};
|
||||
VkFence f;
|
||||
vkCreateFence(dev, &fci, NULL, &f);
|
||||
PFN_vkGetFenceStatus gfs = (PFN_vkGetFenceStatus)vkGetDeviceProcAddr(dev, "vkGetFenceStatus");
|
||||
double t0 = now_ms();
|
||||
int n = 2000;
|
||||
for (int i = 0; i < n; i++) gfs(dev, f);
|
||||
double us = (now_ms() - t0) * 1000.0 / n;
|
||||
char d[64];
|
||||
snprintf(d, sizeof(d), "%.1f us per synchronous call", us);
|
||||
check(1, "call latency", d);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int gpu = 0;
|
||||
for (int i = 1; i < argc; i++)
|
||||
if (!strcmp(argv[i], "--gpu") && i + 1 < argc) gpu = atoi(argv[++i]);
|
||||
void *lib = dlopen("libvulkan.so.1", RTLD_NOW);
|
||||
if (!lib) {
|
||||
printf("FAIL loader: %s\n", dlerror());
|
||||
return 2;
|
||||
}
|
||||
gipa = (PFN_vkGetInstanceProcAddr)dlsym(lib, "vkGetInstanceProcAddr");
|
||||
#define G(n) n = (PFN_##n)gipa(inst, #n)
|
||||
G(vkCreateInstance);
|
||||
VkApplicationInfo app = {VK_STRUCTURE_TYPE_APPLICATION_INFO, NULL, "vkbridge-smoke", 1, "vkbridge", 1, VK_API_VERSION_1_2};
|
||||
VkInstanceCreateInfo ici = {VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, NULL, 0, &app, 0, NULL, 0, NULL};
|
||||
VkResult r = vkCreateInstance(&ici, NULL, &inst);
|
||||
check(r == VK_SUCCESS, "instance", r ? "vkCreateInstance failed" : "");
|
||||
if (r) return 1;
|
||||
G(vkEnumeratePhysicalDevices); G(vkGetPhysicalDeviceProperties); G(vkGetPhysicalDeviceQueueFamilyProperties);
|
||||
G(vkGetPhysicalDeviceMemoryProperties); G(vkCreateDevice); G(vkGetDeviceProcAddr); G(vkDestroyInstance);
|
||||
G(vkGetPhysicalDeviceFeatures);
|
||||
uint32_t n = 0;
|
||||
vkEnumeratePhysicalDevices(inst, &n, NULL);
|
||||
VkPhysicalDevice pds[8];
|
||||
if (n > 8) n = 8;
|
||||
vkEnumeratePhysicalDevices(inst, &n, pds);
|
||||
if ((uint32_t)gpu >= n) {
|
||||
check(0, "physical device", "none");
|
||||
return 1;
|
||||
}
|
||||
pd = pds[gpu];
|
||||
VkPhysicalDeviceProperties props;
|
||||
vkGetPhysicalDeviceProperties(pd, &props);
|
||||
char d[300];
|
||||
snprintf(d, sizeof(d), "%s, Vulkan %u.%u.%u", props.deviceName, VK_API_VERSION_MAJOR(props.apiVersion),
|
||||
VK_API_VERSION_MINOR(props.apiVersion), VK_API_VERSION_PATCH(props.apiVersion));
|
||||
check(1, "physical device", d);
|
||||
vkGetPhysicalDeviceMemoryProperties(pd, &mp);
|
||||
for (uint32_t i = 0; i < mp.memoryTypeCount; i++)
|
||||
printf(" memory type %u: heap %u flags 0x%x\n", i, mp.memoryTypes[i].heapIndex, mp.memoryTypes[i].propertyFlags);
|
||||
uint32_t nq = 0;
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(pd, &nq, NULL);
|
||||
VkQueueFamilyProperties qp[16];
|
||||
if (nq > 16) nq = 16;
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(pd, &nq, qp);
|
||||
for (qfam = 0; qfam < nq; qfam++)
|
||||
if (qp[qfam].queueFlags & VK_QUEUE_GRAPHICS_BIT) break;
|
||||
float prio = 1.0f;
|
||||
VkDeviceQueueCreateInfo qci = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, NULL, 0, qfam, 1, &prio};
|
||||
VkPhysicalDeviceVulkan12Features f12 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES};
|
||||
f12.timelineSemaphore = VK_TRUE;
|
||||
VkDeviceCreateInfo dci = {VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, props.apiVersion >= VK_API_VERSION_1_2 ? &f12 : NULL, 0, 1, &qci, 0, NULL, 0, NULL, NULL};
|
||||
r = vkCreateDevice(pd, &dci, NULL, &dev);
|
||||
check(r == VK_SUCCESS, "device", r ? "vkCreateDevice failed" : "");
|
||||
if (r) return 1;
|
||||
#undef G
|
||||
#define G(n) n = (PFN_##n)vkGetDeviceProcAddr(dev, #n)
|
||||
G(vkGetDeviceQueue); G(vkCreateCommandPool); G(vkAllocateCommandBuffers); G(vkBeginCommandBuffer); G(vkEndCommandBuffer);
|
||||
G(vkQueueSubmit); G(vkQueueWaitIdle); G(vkCreateBuffer); G(vkGetBufferMemoryRequirements); G(vkAllocateMemory);
|
||||
G(vkBindBufferMemory); G(vkMapMemory); G(vkCmdFillBuffer); G(vkCmdCopyBuffer); G(vkCmdUpdateBuffer); G(vkCmdPipelineBarrier);
|
||||
G(vkCreateShaderModule); G(vkCreateDescriptorSetLayout); G(vkCreatePipelineLayout); G(vkCreateComputePipelines);
|
||||
G(vkCreateDescriptorPool); G(vkAllocateDescriptorSets); G(vkUpdateDescriptorSets); G(vkCmdBindPipeline);
|
||||
G(vkCmdBindDescriptorSets); G(vkCmdPushConstants); G(vkCmdDispatch); G(vkCreateImage); G(vkGetImageMemoryRequirements);
|
||||
G(vkBindImageMemory); G(vkCmdClearColorImage); G(vkCmdCopyImageToBuffer); G(vkCreateImageView); G(vkCreateRenderPass);
|
||||
G(vkCreateFramebuffer); G(vkCreateGraphicsPipelines); G(vkCmdBeginRenderPass); G(vkCmdEndRenderPass); G(vkCmdDraw);
|
||||
G(vkCreateSemaphore); G(vkSignalSemaphore); G(vkWaitSemaphores); G(vkGetSemaphoreCounterValue); G(vkDestroyDevice);
|
||||
G(vkFlushMappedMemoryRanges); G(vkInvalidateMappedMemoryRanges); G(vkCmdSetViewport); G(vkCmdSetScissor);
|
||||
G(vkCreateFence); G(vkWaitForFences); G(vkCmdCopyBufferToImage);
|
||||
#undef G
|
||||
vkGetDeviceQueue(dev, qfam, 0, &queue);
|
||||
VkCommandPoolCreateInfo pci = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, NULL, 0, qfam};
|
||||
vkCreateCommandPool(dev, &pci, NULL, &pool);
|
||||
|
||||
test_memory();
|
||||
test_compute();
|
||||
test_image_clear();
|
||||
test_draw();
|
||||
test_timeline();
|
||||
test_roundtrip_rate();
|
||||
|
||||
vkDestroyDevice(dev, NULL);
|
||||
vkDestroyInstance(inst, NULL);
|
||||
printf("%s: %d of %d checks passed\n", failures ? "FAILED" : "OK", checks - failures, checks);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/* Generated by gen_shaders.sh from shaders/ - do not edit. */
|
||||
// 1115.1.0
|
||||
#pragma once
|
||||
static const uint32_t spv_smoke_comp[] = {
|
||||
0x07230203,0x00010000,0x0008000b,0x0000002e,0x00000000,0x00020011,0x00000001,0x0006000b,
|
||||
0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001,
|
||||
0x0006000f,0x00000005,0x00000004,0x6e69616d,0x00000000,0x0000000b,0x00060010,0x00000004,
|
||||
0x00000011,0x00000040,0x00000001,0x00000001,0x00030003,0x00000002,0x000001c2,0x00040005,
|
||||
0x00000004,0x6e69616d,0x00000000,0x00030005,0x00000008,0x00000069,0x00080005,0x0000000b,
|
||||
0x475f6c67,0x61626f6c,0x766e496c,0x7461636f,0x496e6f69,0x00000044,0x00030005,0x00000011,
|
||||
0x0074754f,0x00040006,0x00000011,0x00000000,0x00000076,0x00030005,0x00000013,0x00747364,
|
||||
0x00030005,0x00000018,0x00006e49,0x00040006,0x00000018,0x00000000,0x00000076,0x00030005,
|
||||
0x0000001a,0x00637273,0x00030005,0x0000001f,0x00004350,0x00040006,0x0000001f,0x00000000,
|
||||
0x006c756d,0x00040006,0x0000001f,0x00000001,0x00646461,0x00030005,0x00000021,0x00006370,
|
||||
0x00040047,0x0000000b,0x0000000b,0x0000001c,0x00040047,0x00000010,0x00000006,0x00000004,
|
||||
0x00030047,0x00000011,0x00000003,0x00040048,0x00000011,0x00000000,0x00000019,0x00050048,
|
||||
0x00000011,0x00000000,0x00000023,0x00000000,0x00030047,0x00000013,0x00000019,0x00040047,
|
||||
0x00000013,0x00000021,0x00000001,0x00040047,0x00000013,0x00000022,0x00000000,0x00040047,
|
||||
0x00000017,0x00000006,0x00000004,0x00030047,0x00000018,0x00000003,0x00040048,0x00000018,
|
||||
0x00000000,0x00000018,0x00050048,0x00000018,0x00000000,0x00000023,0x00000000,0x00030047,
|
||||
0x0000001a,0x00000018,0x00040047,0x0000001a,0x00000021,0x00000000,0x00040047,0x0000001a,
|
||||
0x00000022,0x00000000,0x00030047,0x0000001f,0x00000002,0x00050048,0x0000001f,0x00000000,
|
||||
0x00000023,0x00000000,0x00050048,0x0000001f,0x00000001,0x00000023,0x00000004,0x00040047,
|
||||
0x0000002d,0x0000000b,0x00000019,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002,
|
||||
0x00040015,0x00000006,0x00000020,0x00000000,0x00040020,0x00000007,0x00000007,0x00000006,
|
||||
0x00040017,0x00000009,0x00000006,0x00000003,0x00040020,0x0000000a,0x00000001,0x00000009,
|
||||
0x0004003b,0x0000000a,0x0000000b,0x00000001,0x0004002b,0x00000006,0x0000000c,0x00000000,
|
||||
0x00040020,0x0000000d,0x00000001,0x00000006,0x0003001d,0x00000010,0x00000006,0x0003001e,
|
||||
0x00000011,0x00000010,0x00040020,0x00000012,0x00000002,0x00000011,0x0004003b,0x00000012,
|
||||
0x00000013,0x00000002,0x00040015,0x00000014,0x00000020,0x00000001,0x0004002b,0x00000014,
|
||||
0x00000015,0x00000000,0x0003001d,0x00000017,0x00000006,0x0003001e,0x00000018,0x00000017,
|
||||
0x00040020,0x00000019,0x00000002,0x00000018,0x0004003b,0x00000019,0x0000001a,0x00000002,
|
||||
0x00040020,0x0000001c,0x00000002,0x00000006,0x0004001e,0x0000001f,0x00000006,0x00000006,
|
||||
0x00040020,0x00000020,0x00000009,0x0000001f,0x0004003b,0x00000020,0x00000021,0x00000009,
|
||||
0x00040020,0x00000022,0x00000009,0x00000006,0x0004002b,0x00000014,0x00000026,0x00000001,
|
||||
0x0004002b,0x00000006,0x0000002b,0x00000040,0x0004002b,0x00000006,0x0000002c,0x00000001,
|
||||
0x0006002c,0x00000009,0x0000002d,0x0000002b,0x0000002c,0x0000002c,0x00050036,0x00000002,
|
||||
0x00000004,0x00000000,0x00000003,0x000200f8,0x00000005,0x0004003b,0x00000007,0x00000008,
|
||||
0x00000007,0x00050041,0x0000000d,0x0000000e,0x0000000b,0x0000000c,0x0004003d,0x00000006,
|
||||
0x0000000f,0x0000000e,0x0003003e,0x00000008,0x0000000f,0x0004003d,0x00000006,0x00000016,
|
||||
0x00000008,0x0004003d,0x00000006,0x0000001b,0x00000008,0x00060041,0x0000001c,0x0000001d,
|
||||
0x0000001a,0x00000015,0x0000001b,0x0004003d,0x00000006,0x0000001e,0x0000001d,0x00050041,
|
||||
0x00000022,0x00000023,0x00000021,0x00000015,0x0004003d,0x00000006,0x00000024,0x00000023,
|
||||
0x00050084,0x00000006,0x00000025,0x0000001e,0x00000024,0x00050041,0x00000022,0x00000027,
|
||||
0x00000021,0x00000026,0x0004003d,0x00000006,0x00000028,0x00000027,0x00050080,0x00000006,
|
||||
0x00000029,0x00000025,0x00000028,0x00060041,0x0000001c,0x0000002a,0x00000013,0x00000015,
|
||||
0x00000016,0x0003003e,0x0000002a,0x00000029,0x000100fd,0x00010038
|
||||
};
|
||||
// 1115.1.0
|
||||
#pragma once
|
||||
static const uint32_t spv_smoke_vert[] = {
|
||||
0x07230203,0x00010000,0x0008000b,0x0000002b,0x00000000,0x00020011,0x00000001,0x0006000b,
|
||||
0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001,
|
||||
0x0007000f,0x00000000,0x00000004,0x6e69616d,0x00000000,0x0000000c,0x0000001d,0x00030003,
|
||||
0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d,0x00000000,0x00030005,0x00000009,
|
||||
0x00000070,0x00060005,0x0000000c,0x565f6c67,0x65747265,0x646e4978,0x00007865,0x00060005,
|
||||
0x0000001b,0x505f6c67,0x65567265,0x78657472,0x00000000,0x00060006,0x0000001b,0x00000000,
|
||||
0x505f6c67,0x7469736f,0x006e6f69,0x00070006,0x0000001b,0x00000001,0x505f6c67,0x746e696f,
|
||||
0x657a6953,0x00000000,0x00070006,0x0000001b,0x00000002,0x435f6c67,0x4470696c,0x61747369,
|
||||
0x0065636e,0x00070006,0x0000001b,0x00000003,0x435f6c67,0x446c6c75,0x61747369,0x0065636e,
|
||||
0x00030005,0x0000001d,0x00000000,0x00040047,0x0000000c,0x0000000b,0x0000002a,0x00030047,
|
||||
0x0000001b,0x00000002,0x00050048,0x0000001b,0x00000000,0x0000000b,0x00000000,0x00050048,
|
||||
0x0000001b,0x00000001,0x0000000b,0x00000001,0x00050048,0x0000001b,0x00000002,0x0000000b,
|
||||
0x00000003,0x00050048,0x0000001b,0x00000003,0x0000000b,0x00000004,0x00020013,0x00000002,
|
||||
0x00030021,0x00000003,0x00000002,0x00030016,0x00000006,0x00000020,0x00040017,0x00000007,
|
||||
0x00000006,0x00000002,0x00040020,0x00000008,0x00000007,0x00000007,0x00040015,0x0000000a,
|
||||
0x00000020,0x00000001,0x00040020,0x0000000b,0x00000001,0x0000000a,0x0004003b,0x0000000b,
|
||||
0x0000000c,0x00000001,0x0004002b,0x0000000a,0x0000000e,0x00000001,0x0004002b,0x0000000a,
|
||||
0x00000010,0x00000002,0x00040017,0x00000017,0x00000006,0x00000004,0x00040015,0x00000018,
|
||||
0x00000020,0x00000000,0x0004002b,0x00000018,0x00000019,0x00000001,0x0004001c,0x0000001a,
|
||||
0x00000006,0x00000019,0x0006001e,0x0000001b,0x00000017,0x00000006,0x0000001a,0x0000001a,
|
||||
0x00040020,0x0000001c,0x00000003,0x0000001b,0x0004003b,0x0000001c,0x0000001d,0x00000003,
|
||||
0x0004002b,0x0000000a,0x0000001e,0x00000000,0x0004002b,0x00000006,0x00000020,0x40000000,
|
||||
0x0004002b,0x00000006,0x00000022,0x3f800000,0x0004002b,0x00000006,0x00000025,0x00000000,
|
||||
0x00040020,0x00000029,0x00000003,0x00000017,0x00050036,0x00000002,0x00000004,0x00000000,
|
||||
0x00000003,0x000200f8,0x00000005,0x0004003b,0x00000008,0x00000009,0x00000007,0x0004003d,
|
||||
0x0000000a,0x0000000d,0x0000000c,0x000500c4,0x0000000a,0x0000000f,0x0000000d,0x0000000e,
|
||||
0x000500c7,0x0000000a,0x00000011,0x0000000f,0x00000010,0x0004006f,0x00000006,0x00000012,
|
||||
0x00000011,0x0004003d,0x0000000a,0x00000013,0x0000000c,0x000500c7,0x0000000a,0x00000014,
|
||||
0x00000013,0x00000010,0x0004006f,0x00000006,0x00000015,0x00000014,0x00050050,0x00000007,
|
||||
0x00000016,0x00000012,0x00000015,0x0003003e,0x00000009,0x00000016,0x0004003d,0x00000007,
|
||||
0x0000001f,0x00000009,0x0005008e,0x00000007,0x00000021,0x0000001f,0x00000020,0x00050050,
|
||||
0x00000007,0x00000023,0x00000022,0x00000022,0x00050083,0x00000007,0x00000024,0x00000021,
|
||||
0x00000023,0x00050051,0x00000006,0x00000026,0x00000024,0x00000000,0x00050051,0x00000006,
|
||||
0x00000027,0x00000024,0x00000001,0x00070050,0x00000017,0x00000028,0x00000026,0x00000027,
|
||||
0x00000025,0x00000022,0x00050041,0x00000029,0x0000002a,0x0000001d,0x0000001e,0x0003003e,
|
||||
0x0000002a,0x00000028,0x000100fd,0x00010038
|
||||
};
|
||||
// 1115.1.0
|
||||
#pragma once
|
||||
static const uint32_t spv_smoke_frag[] = {
|
||||
0x07230203,0x00010000,0x0008000b,0x00000012,0x00000000,0x00020011,0x00000001,0x0006000b,
|
||||
0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001,
|
||||
0x0006000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x00000009,0x00030010,0x00000004,
|
||||
0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d,0x00000000,
|
||||
0x00030005,0x00000009,0x0000006f,0x00030005,0x0000000a,0x00004350,0x00050006,0x0000000a,
|
||||
0x00000000,0x6f6c6f63,0x00000072,0x00030005,0x0000000c,0x00006370,0x00040047,0x00000009,
|
||||
0x0000001e,0x00000000,0x00030047,0x0000000a,0x00000002,0x00050048,0x0000000a,0x00000000,
|
||||
0x00000023,0x00000000,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002,0x00030016,
|
||||
0x00000006,0x00000020,0x00040017,0x00000007,0x00000006,0x00000004,0x00040020,0x00000008,
|
||||
0x00000003,0x00000007,0x0004003b,0x00000008,0x00000009,0x00000003,0x0003001e,0x0000000a,
|
||||
0x00000007,0x00040020,0x0000000b,0x00000009,0x0000000a,0x0004003b,0x0000000b,0x0000000c,
|
||||
0x00000009,0x00040015,0x0000000d,0x00000020,0x00000001,0x0004002b,0x0000000d,0x0000000e,
|
||||
0x00000000,0x00040020,0x0000000f,0x00000009,0x00000007,0x00050036,0x00000002,0x00000004,
|
||||
0x00000000,0x00000003,0x000200f8,0x00000005,0x00050041,0x0000000f,0x00000010,0x0000000c,
|
||||
0x0000000e,0x0004003d,0x00000007,0x00000011,0x00000010,0x0003003e,0x00000009,0x00000011,
|
||||
0x000100fd,0x00010038
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
Copyright 2015-2023 The Khronos Group Inc.
|
||||
|
||||
Files in this repository fall under one of these licenses:
|
||||
|
||||
- `Apache-2.0`
|
||||
- `MIT`
|
||||
|
||||
Note: With the exception of `parse_dependency.py` the files using `MIT` license
|
||||
also fall under `Apache-2.0`. Example:
|
||||
|
||||
```
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
```
|
||||
|
||||
Full license text of these licenses is available at:
|
||||
|
||||
* Apache-2.0: https://opensource.org/licenses/Apache-2.0
|
||||
* MIT: https://opensource.org/licenses/MIT
|
||||
Vendored
+394
@@ -0,0 +1,394 @@
|
||||
#ifndef VULKAN_VIDEO_CODEC_AV1STD_H_
|
||||
#define VULKAN_VIDEO_CODEC_AV1STD_H_ 1
|
||||
|
||||
/*
|
||||
** Copyright 2015-2026 The Khronos Group Inc.
|
||||
**
|
||||
** SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
** This header is generated from the Khronos Vulkan XML API Registry.
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// vulkan_video_codec_av1std is a preprocessor guard. Do not pass it to API calls.
|
||||
#define vulkan_video_codec_av1std 1
|
||||
#include "vulkan_video_codecs_common.h"
|
||||
#define STD_VIDEO_AV1_NUM_REF_FRAMES 8U
|
||||
#define STD_VIDEO_AV1_REFS_PER_FRAME 7U
|
||||
#define STD_VIDEO_AV1_TOTAL_REFS_PER_FRAME 8U
|
||||
#define STD_VIDEO_AV1_MAX_TILE_COLS 64U
|
||||
#define STD_VIDEO_AV1_MAX_TILE_ROWS 64U
|
||||
#define STD_VIDEO_AV1_MAX_SEGMENTS 8U
|
||||
#define STD_VIDEO_AV1_SEG_LVL_MAX 8U
|
||||
#define STD_VIDEO_AV1_PRIMARY_REF_NONE 7U
|
||||
#define STD_VIDEO_AV1_SELECT_INTEGER_MV 2U
|
||||
#define STD_VIDEO_AV1_SELECT_SCREEN_CONTENT_TOOLS 2U
|
||||
#define STD_VIDEO_AV1_SKIP_MODE_FRAMES 2U
|
||||
#define STD_VIDEO_AV1_MAX_LOOP_FILTER_STRENGTHS 4U
|
||||
#define STD_VIDEO_AV1_LOOP_FILTER_ADJUSTMENTS 2U
|
||||
#define STD_VIDEO_AV1_MAX_CDEF_FILTER_STRENGTHS 8U
|
||||
#define STD_VIDEO_AV1_MAX_NUM_PLANES 3U
|
||||
#define STD_VIDEO_AV1_GLOBAL_MOTION_PARAMS 6U
|
||||
#define STD_VIDEO_AV1_MAX_NUM_Y_POINTS 14U
|
||||
#define STD_VIDEO_AV1_MAX_NUM_CB_POINTS 10U
|
||||
#define STD_VIDEO_AV1_MAX_NUM_CR_POINTS 10U
|
||||
#define STD_VIDEO_AV1_MAX_NUM_POS_LUMA 24U
|
||||
#define STD_VIDEO_AV1_MAX_NUM_POS_CHROMA 25U
|
||||
|
||||
typedef enum StdVideoAV1Profile {
|
||||
STD_VIDEO_AV1_PROFILE_MAIN = 0,
|
||||
STD_VIDEO_AV1_PROFILE_HIGH = 1,
|
||||
STD_VIDEO_AV1_PROFILE_PROFESSIONAL = 2,
|
||||
STD_VIDEO_AV1_PROFILE_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_AV1_PROFILE_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoAV1Profile;
|
||||
|
||||
typedef enum StdVideoAV1Level {
|
||||
STD_VIDEO_AV1_LEVEL_2_0 = 0,
|
||||
STD_VIDEO_AV1_LEVEL_2_1 = 1,
|
||||
STD_VIDEO_AV1_LEVEL_2_2 = 2,
|
||||
STD_VIDEO_AV1_LEVEL_2_3 = 3,
|
||||
STD_VIDEO_AV1_LEVEL_3_0 = 4,
|
||||
STD_VIDEO_AV1_LEVEL_3_1 = 5,
|
||||
STD_VIDEO_AV1_LEVEL_3_2 = 6,
|
||||
STD_VIDEO_AV1_LEVEL_3_3 = 7,
|
||||
STD_VIDEO_AV1_LEVEL_4_0 = 8,
|
||||
STD_VIDEO_AV1_LEVEL_4_1 = 9,
|
||||
STD_VIDEO_AV1_LEVEL_4_2 = 10,
|
||||
STD_VIDEO_AV1_LEVEL_4_3 = 11,
|
||||
STD_VIDEO_AV1_LEVEL_5_0 = 12,
|
||||
STD_VIDEO_AV1_LEVEL_5_1 = 13,
|
||||
STD_VIDEO_AV1_LEVEL_5_2 = 14,
|
||||
STD_VIDEO_AV1_LEVEL_5_3 = 15,
|
||||
STD_VIDEO_AV1_LEVEL_6_0 = 16,
|
||||
STD_VIDEO_AV1_LEVEL_6_1 = 17,
|
||||
STD_VIDEO_AV1_LEVEL_6_2 = 18,
|
||||
STD_VIDEO_AV1_LEVEL_6_3 = 19,
|
||||
STD_VIDEO_AV1_LEVEL_7_0 = 20,
|
||||
STD_VIDEO_AV1_LEVEL_7_1 = 21,
|
||||
STD_VIDEO_AV1_LEVEL_7_2 = 22,
|
||||
STD_VIDEO_AV1_LEVEL_7_3 = 23,
|
||||
STD_VIDEO_AV1_LEVEL_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_AV1_LEVEL_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoAV1Level;
|
||||
|
||||
typedef enum StdVideoAV1FrameType {
|
||||
STD_VIDEO_AV1_FRAME_TYPE_KEY = 0,
|
||||
STD_VIDEO_AV1_FRAME_TYPE_INTER = 1,
|
||||
STD_VIDEO_AV1_FRAME_TYPE_INTRA_ONLY = 2,
|
||||
STD_VIDEO_AV1_FRAME_TYPE_SWITCH = 3,
|
||||
STD_VIDEO_AV1_FRAME_TYPE_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_AV1_FRAME_TYPE_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoAV1FrameType;
|
||||
|
||||
typedef enum StdVideoAV1ReferenceName {
|
||||
STD_VIDEO_AV1_REFERENCE_NAME_INTRA_FRAME = 0,
|
||||
STD_VIDEO_AV1_REFERENCE_NAME_LAST_FRAME = 1,
|
||||
STD_VIDEO_AV1_REFERENCE_NAME_LAST2_FRAME = 2,
|
||||
STD_VIDEO_AV1_REFERENCE_NAME_LAST3_FRAME = 3,
|
||||
STD_VIDEO_AV1_REFERENCE_NAME_GOLDEN_FRAME = 4,
|
||||
STD_VIDEO_AV1_REFERENCE_NAME_BWDREF_FRAME = 5,
|
||||
STD_VIDEO_AV1_REFERENCE_NAME_ALTREF2_FRAME = 6,
|
||||
STD_VIDEO_AV1_REFERENCE_NAME_ALTREF_FRAME = 7,
|
||||
STD_VIDEO_AV1_REFERENCE_NAME_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_AV1_REFERENCE_NAME_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoAV1ReferenceName;
|
||||
|
||||
typedef enum StdVideoAV1InterpolationFilter {
|
||||
STD_VIDEO_AV1_INTERPOLATION_FILTER_EIGHTTAP = 0,
|
||||
STD_VIDEO_AV1_INTERPOLATION_FILTER_EIGHTTAP_SMOOTH = 1,
|
||||
STD_VIDEO_AV1_INTERPOLATION_FILTER_EIGHTTAP_SHARP = 2,
|
||||
STD_VIDEO_AV1_INTERPOLATION_FILTER_BILINEAR = 3,
|
||||
STD_VIDEO_AV1_INTERPOLATION_FILTER_SWITCHABLE = 4,
|
||||
STD_VIDEO_AV1_INTERPOLATION_FILTER_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_AV1_INTERPOLATION_FILTER_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoAV1InterpolationFilter;
|
||||
|
||||
typedef enum StdVideoAV1TxMode {
|
||||
STD_VIDEO_AV1_TX_MODE_ONLY_4X4 = 0,
|
||||
STD_VIDEO_AV1_TX_MODE_LARGEST = 1,
|
||||
STD_VIDEO_AV1_TX_MODE_SELECT = 2,
|
||||
STD_VIDEO_AV1_TX_MODE_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_AV1_TX_MODE_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoAV1TxMode;
|
||||
|
||||
typedef enum StdVideoAV1FrameRestorationType {
|
||||
STD_VIDEO_AV1_FRAME_RESTORATION_TYPE_NONE = 0,
|
||||
STD_VIDEO_AV1_FRAME_RESTORATION_TYPE_WIENER = 1,
|
||||
STD_VIDEO_AV1_FRAME_RESTORATION_TYPE_SGRPROJ = 2,
|
||||
STD_VIDEO_AV1_FRAME_RESTORATION_TYPE_SWITCHABLE = 3,
|
||||
STD_VIDEO_AV1_FRAME_RESTORATION_TYPE_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_AV1_FRAME_RESTORATION_TYPE_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoAV1FrameRestorationType;
|
||||
|
||||
typedef enum StdVideoAV1ColorPrimaries {
|
||||
STD_VIDEO_AV1_COLOR_PRIMARIES_BT_709 = 1,
|
||||
STD_VIDEO_AV1_COLOR_PRIMARIES_UNSPECIFIED = 2,
|
||||
STD_VIDEO_AV1_COLOR_PRIMARIES_BT_470_M = 4,
|
||||
STD_VIDEO_AV1_COLOR_PRIMARIES_BT_470_B_G = 5,
|
||||
STD_VIDEO_AV1_COLOR_PRIMARIES_BT_601 = 6,
|
||||
STD_VIDEO_AV1_COLOR_PRIMARIES_SMPTE_240 = 7,
|
||||
STD_VIDEO_AV1_COLOR_PRIMARIES_GENERIC_FILM = 8,
|
||||
STD_VIDEO_AV1_COLOR_PRIMARIES_BT_2020 = 9,
|
||||
STD_VIDEO_AV1_COLOR_PRIMARIES_XYZ = 10,
|
||||
STD_VIDEO_AV1_COLOR_PRIMARIES_SMPTE_431 = 11,
|
||||
STD_VIDEO_AV1_COLOR_PRIMARIES_SMPTE_432 = 12,
|
||||
STD_VIDEO_AV1_COLOR_PRIMARIES_EBU_3213 = 22,
|
||||
STD_VIDEO_AV1_COLOR_PRIMARIES_INVALID = 0x7FFFFFFF,
|
||||
// STD_VIDEO_AV1_COLOR_PRIMARIES_BT_UNSPECIFIED is a legacy alias
|
||||
STD_VIDEO_AV1_COLOR_PRIMARIES_BT_UNSPECIFIED = STD_VIDEO_AV1_COLOR_PRIMARIES_UNSPECIFIED,
|
||||
STD_VIDEO_AV1_COLOR_PRIMARIES_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoAV1ColorPrimaries;
|
||||
|
||||
typedef enum StdVideoAV1TransferCharacteristics {
|
||||
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_RESERVED_0 = 0,
|
||||
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_BT_709 = 1,
|
||||
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_UNSPECIFIED = 2,
|
||||
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_RESERVED_3 = 3,
|
||||
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_BT_470_M = 4,
|
||||
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_BT_470_B_G = 5,
|
||||
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_BT_601 = 6,
|
||||
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_SMPTE_240 = 7,
|
||||
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_LINEAR = 8,
|
||||
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_LOG_100 = 9,
|
||||
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_LOG_100_SQRT10 = 10,
|
||||
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_IEC_61966 = 11,
|
||||
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_BT_1361 = 12,
|
||||
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_SRGB = 13,
|
||||
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_BT_2020_10_BIT = 14,
|
||||
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_BT_2020_12_BIT = 15,
|
||||
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_SMPTE_2084 = 16,
|
||||
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_SMPTE_428 = 17,
|
||||
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_HLG = 18,
|
||||
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_AV1_TRANSFER_CHARACTERISTICS_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoAV1TransferCharacteristics;
|
||||
|
||||
typedef enum StdVideoAV1MatrixCoefficients {
|
||||
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_IDENTITY = 0,
|
||||
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_BT_709 = 1,
|
||||
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_UNSPECIFIED = 2,
|
||||
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_RESERVED_3 = 3,
|
||||
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_FCC = 4,
|
||||
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_BT_470_B_G = 5,
|
||||
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_BT_601 = 6,
|
||||
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_SMPTE_240 = 7,
|
||||
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_SMPTE_YCGCO = 8,
|
||||
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_BT_2020_NCL = 9,
|
||||
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_BT_2020_CL = 10,
|
||||
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_SMPTE_2085 = 11,
|
||||
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_CHROMAT_NCL = 12,
|
||||
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_CHROMAT_CL = 13,
|
||||
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_ICTCP = 14,
|
||||
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_AV1_MATRIX_COEFFICIENTS_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoAV1MatrixCoefficients;
|
||||
|
||||
typedef enum StdVideoAV1ChromaSamplePosition {
|
||||
STD_VIDEO_AV1_CHROMA_SAMPLE_POSITION_UNKNOWN = 0,
|
||||
STD_VIDEO_AV1_CHROMA_SAMPLE_POSITION_VERTICAL = 1,
|
||||
STD_VIDEO_AV1_CHROMA_SAMPLE_POSITION_COLOCATED = 2,
|
||||
STD_VIDEO_AV1_CHROMA_SAMPLE_POSITION_RESERVED = 3,
|
||||
STD_VIDEO_AV1_CHROMA_SAMPLE_POSITION_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_AV1_CHROMA_SAMPLE_POSITION_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoAV1ChromaSamplePosition;
|
||||
typedef struct StdVideoAV1ColorConfigFlags {
|
||||
uint32_t mono_chrome : 1;
|
||||
uint32_t color_range : 1;
|
||||
uint32_t separate_uv_delta_q : 1;
|
||||
uint32_t color_description_present_flag : 1;
|
||||
uint32_t reserved : 28;
|
||||
} StdVideoAV1ColorConfigFlags;
|
||||
|
||||
typedef struct StdVideoAV1ColorConfig {
|
||||
StdVideoAV1ColorConfigFlags flags;
|
||||
uint8_t BitDepth;
|
||||
uint8_t subsampling_x;
|
||||
uint8_t subsampling_y;
|
||||
uint8_t reserved1;
|
||||
StdVideoAV1ColorPrimaries color_primaries;
|
||||
StdVideoAV1TransferCharacteristics transfer_characteristics;
|
||||
StdVideoAV1MatrixCoefficients matrix_coefficients;
|
||||
StdVideoAV1ChromaSamplePosition chroma_sample_position;
|
||||
} StdVideoAV1ColorConfig;
|
||||
|
||||
typedef struct StdVideoAV1TimingInfoFlags {
|
||||
uint32_t equal_picture_interval : 1;
|
||||
uint32_t reserved : 31;
|
||||
} StdVideoAV1TimingInfoFlags;
|
||||
|
||||
typedef struct StdVideoAV1TimingInfo {
|
||||
StdVideoAV1TimingInfoFlags flags;
|
||||
uint32_t num_units_in_display_tick;
|
||||
uint32_t time_scale;
|
||||
uint32_t num_ticks_per_picture_minus_1;
|
||||
} StdVideoAV1TimingInfo;
|
||||
|
||||
typedef struct StdVideoAV1LoopFilterFlags {
|
||||
uint32_t loop_filter_delta_enabled : 1;
|
||||
uint32_t loop_filter_delta_update : 1;
|
||||
uint32_t reserved : 30;
|
||||
} StdVideoAV1LoopFilterFlags;
|
||||
|
||||
typedef struct StdVideoAV1LoopFilter {
|
||||
StdVideoAV1LoopFilterFlags flags;
|
||||
uint8_t loop_filter_level[STD_VIDEO_AV1_MAX_LOOP_FILTER_STRENGTHS];
|
||||
uint8_t loop_filter_sharpness;
|
||||
uint8_t update_ref_delta;
|
||||
int8_t loop_filter_ref_deltas[STD_VIDEO_AV1_TOTAL_REFS_PER_FRAME];
|
||||
uint8_t update_mode_delta;
|
||||
int8_t loop_filter_mode_deltas[STD_VIDEO_AV1_LOOP_FILTER_ADJUSTMENTS];
|
||||
} StdVideoAV1LoopFilter;
|
||||
|
||||
typedef struct StdVideoAV1QuantizationFlags {
|
||||
uint32_t using_qmatrix : 1;
|
||||
uint32_t diff_uv_delta : 1;
|
||||
uint32_t reserved : 30;
|
||||
} StdVideoAV1QuantizationFlags;
|
||||
|
||||
typedef struct StdVideoAV1Quantization {
|
||||
StdVideoAV1QuantizationFlags flags;
|
||||
uint8_t base_q_idx;
|
||||
int8_t DeltaQYDc;
|
||||
int8_t DeltaQUDc;
|
||||
int8_t DeltaQUAc;
|
||||
int8_t DeltaQVDc;
|
||||
int8_t DeltaQVAc;
|
||||
uint8_t qm_y;
|
||||
uint8_t qm_u;
|
||||
uint8_t qm_v;
|
||||
} StdVideoAV1Quantization;
|
||||
|
||||
typedef struct StdVideoAV1Segmentation {
|
||||
uint8_t FeatureEnabled[STD_VIDEO_AV1_MAX_SEGMENTS];
|
||||
int16_t FeatureData[STD_VIDEO_AV1_MAX_SEGMENTS][STD_VIDEO_AV1_SEG_LVL_MAX];
|
||||
} StdVideoAV1Segmentation;
|
||||
|
||||
typedef struct StdVideoAV1TileInfoFlags {
|
||||
uint32_t uniform_tile_spacing_flag : 1;
|
||||
uint32_t reserved : 31;
|
||||
} StdVideoAV1TileInfoFlags;
|
||||
|
||||
typedef struct StdVideoAV1TileInfo {
|
||||
StdVideoAV1TileInfoFlags flags;
|
||||
uint8_t TileCols;
|
||||
uint8_t TileRows;
|
||||
uint16_t context_update_tile_id;
|
||||
uint8_t tile_size_bytes_minus_1;
|
||||
uint8_t reserved1[7];
|
||||
const uint16_t* pMiColStarts;
|
||||
const uint16_t* pMiRowStarts;
|
||||
const uint16_t* pWidthInSbsMinus1;
|
||||
const uint16_t* pHeightInSbsMinus1;
|
||||
} StdVideoAV1TileInfo;
|
||||
|
||||
typedef struct StdVideoAV1CDEF {
|
||||
uint8_t cdef_damping_minus_3;
|
||||
uint8_t cdef_bits;
|
||||
uint8_t cdef_y_pri_strength[STD_VIDEO_AV1_MAX_CDEF_FILTER_STRENGTHS];
|
||||
uint8_t cdef_y_sec_strength[STD_VIDEO_AV1_MAX_CDEF_FILTER_STRENGTHS];
|
||||
uint8_t cdef_uv_pri_strength[STD_VIDEO_AV1_MAX_CDEF_FILTER_STRENGTHS];
|
||||
uint8_t cdef_uv_sec_strength[STD_VIDEO_AV1_MAX_CDEF_FILTER_STRENGTHS];
|
||||
} StdVideoAV1CDEF;
|
||||
|
||||
typedef struct StdVideoAV1LoopRestoration {
|
||||
StdVideoAV1FrameRestorationType FrameRestorationType[STD_VIDEO_AV1_MAX_NUM_PLANES];
|
||||
uint16_t LoopRestorationSize[STD_VIDEO_AV1_MAX_NUM_PLANES];
|
||||
} StdVideoAV1LoopRestoration;
|
||||
|
||||
typedef struct StdVideoAV1GlobalMotion {
|
||||
uint8_t GmType[STD_VIDEO_AV1_NUM_REF_FRAMES];
|
||||
int32_t gm_params[STD_VIDEO_AV1_NUM_REF_FRAMES][STD_VIDEO_AV1_GLOBAL_MOTION_PARAMS];
|
||||
} StdVideoAV1GlobalMotion;
|
||||
|
||||
typedef struct StdVideoAV1FilmGrainFlags {
|
||||
uint32_t chroma_scaling_from_luma : 1;
|
||||
uint32_t overlap_flag : 1;
|
||||
uint32_t clip_to_restricted_range : 1;
|
||||
uint32_t update_grain : 1;
|
||||
uint32_t reserved : 28;
|
||||
} StdVideoAV1FilmGrainFlags;
|
||||
|
||||
typedef struct StdVideoAV1FilmGrain {
|
||||
StdVideoAV1FilmGrainFlags flags;
|
||||
uint8_t grain_scaling_minus_8;
|
||||
uint8_t ar_coeff_lag;
|
||||
uint8_t ar_coeff_shift_minus_6;
|
||||
uint8_t grain_scale_shift;
|
||||
uint16_t grain_seed;
|
||||
uint8_t film_grain_params_ref_idx;
|
||||
uint8_t num_y_points;
|
||||
uint8_t point_y_value[STD_VIDEO_AV1_MAX_NUM_Y_POINTS];
|
||||
uint8_t point_y_scaling[STD_VIDEO_AV1_MAX_NUM_Y_POINTS];
|
||||
uint8_t num_cb_points;
|
||||
uint8_t point_cb_value[STD_VIDEO_AV1_MAX_NUM_CB_POINTS];
|
||||
uint8_t point_cb_scaling[STD_VIDEO_AV1_MAX_NUM_CB_POINTS];
|
||||
uint8_t num_cr_points;
|
||||
uint8_t point_cr_value[STD_VIDEO_AV1_MAX_NUM_CR_POINTS];
|
||||
uint8_t point_cr_scaling[STD_VIDEO_AV1_MAX_NUM_CR_POINTS];
|
||||
int8_t ar_coeffs_y_plus_128[STD_VIDEO_AV1_MAX_NUM_POS_LUMA];
|
||||
int8_t ar_coeffs_cb_plus_128[STD_VIDEO_AV1_MAX_NUM_POS_CHROMA];
|
||||
int8_t ar_coeffs_cr_plus_128[STD_VIDEO_AV1_MAX_NUM_POS_CHROMA];
|
||||
uint8_t cb_mult;
|
||||
uint8_t cb_luma_mult;
|
||||
uint16_t cb_offset;
|
||||
uint8_t cr_mult;
|
||||
uint8_t cr_luma_mult;
|
||||
uint16_t cr_offset;
|
||||
} StdVideoAV1FilmGrain;
|
||||
|
||||
typedef struct StdVideoAV1SequenceHeaderFlags {
|
||||
uint32_t still_picture : 1;
|
||||
uint32_t reduced_still_picture_header : 1;
|
||||
uint32_t use_128x128_superblock : 1;
|
||||
uint32_t enable_filter_intra : 1;
|
||||
uint32_t enable_intra_edge_filter : 1;
|
||||
uint32_t enable_interintra_compound : 1;
|
||||
uint32_t enable_masked_compound : 1;
|
||||
uint32_t enable_warped_motion : 1;
|
||||
uint32_t enable_dual_filter : 1;
|
||||
uint32_t enable_order_hint : 1;
|
||||
uint32_t enable_jnt_comp : 1;
|
||||
uint32_t enable_ref_frame_mvs : 1;
|
||||
uint32_t frame_id_numbers_present_flag : 1;
|
||||
uint32_t enable_superres : 1;
|
||||
uint32_t enable_cdef : 1;
|
||||
uint32_t enable_restoration : 1;
|
||||
uint32_t film_grain_params_present : 1;
|
||||
uint32_t timing_info_present_flag : 1;
|
||||
uint32_t initial_display_delay_present_flag : 1;
|
||||
uint32_t reserved : 13;
|
||||
} StdVideoAV1SequenceHeaderFlags;
|
||||
|
||||
typedef struct StdVideoAV1SequenceHeader {
|
||||
StdVideoAV1SequenceHeaderFlags flags;
|
||||
StdVideoAV1Profile seq_profile;
|
||||
uint8_t frame_width_bits_minus_1;
|
||||
uint8_t frame_height_bits_minus_1;
|
||||
uint16_t max_frame_width_minus_1;
|
||||
uint16_t max_frame_height_minus_1;
|
||||
uint8_t delta_frame_id_length_minus_2;
|
||||
uint8_t additional_frame_id_length_minus_1;
|
||||
uint8_t order_hint_bits_minus_1;
|
||||
uint8_t seq_force_integer_mv;
|
||||
uint8_t seq_force_screen_content_tools;
|
||||
uint8_t reserved1[5];
|
||||
const StdVideoAV1ColorConfig* pColorConfig;
|
||||
const StdVideoAV1TimingInfo* pTimingInfo;
|
||||
} StdVideoAV1SequenceHeader;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
#ifndef VULKAN_VIDEO_CODEC_AV1STD_DECODE_H_
|
||||
#define VULKAN_VIDEO_CODEC_AV1STD_DECODE_H_ 1
|
||||
|
||||
/*
|
||||
** Copyright 2015-2026 The Khronos Group Inc.
|
||||
**
|
||||
** SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
** This header is generated from the Khronos Vulkan XML API Registry.
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// vulkan_video_codec_av1std_decode is a preprocessor guard. Do not pass it to API calls.
|
||||
#define vulkan_video_codec_av1std_decode 1
|
||||
#include "vulkan_video_codec_av1std.h"
|
||||
|
||||
#define VK_STD_VULKAN_VIDEO_CODEC_AV1_DECODE_API_VERSION_1_0_0 VK_MAKE_VIDEO_STD_VERSION(1, 0, 0)
|
||||
|
||||
#define VK_STD_VULKAN_VIDEO_CODEC_AV1_DECODE_SPEC_VERSION VK_STD_VULKAN_VIDEO_CODEC_AV1_DECODE_API_VERSION_1_0_0
|
||||
#define VK_STD_VULKAN_VIDEO_CODEC_AV1_DECODE_EXTENSION_NAME "VK_STD_vulkan_video_codec_av1_decode"
|
||||
typedef struct StdVideoDecodeAV1PictureInfoFlags {
|
||||
uint32_t error_resilient_mode : 1;
|
||||
uint32_t disable_cdf_update : 1;
|
||||
uint32_t use_superres : 1;
|
||||
uint32_t render_and_frame_size_different : 1;
|
||||
uint32_t allow_screen_content_tools : 1;
|
||||
uint32_t is_filter_switchable : 1;
|
||||
uint32_t force_integer_mv : 1;
|
||||
uint32_t frame_size_override_flag : 1;
|
||||
uint32_t buffer_removal_time_present_flag : 1;
|
||||
uint32_t allow_intrabc : 1;
|
||||
uint32_t frame_refs_short_signaling : 1;
|
||||
uint32_t allow_high_precision_mv : 1;
|
||||
uint32_t is_motion_mode_switchable : 1;
|
||||
uint32_t use_ref_frame_mvs : 1;
|
||||
uint32_t disable_frame_end_update_cdf : 1;
|
||||
uint32_t allow_warped_motion : 1;
|
||||
uint32_t reduced_tx_set : 1;
|
||||
uint32_t reference_select : 1;
|
||||
uint32_t skip_mode_present : 1;
|
||||
uint32_t delta_q_present : 1;
|
||||
uint32_t delta_lf_present : 1;
|
||||
uint32_t delta_lf_multi : 1;
|
||||
uint32_t segmentation_enabled : 1;
|
||||
uint32_t segmentation_update_map : 1;
|
||||
uint32_t segmentation_temporal_update : 1;
|
||||
uint32_t segmentation_update_data : 1;
|
||||
uint32_t UsesLr : 1;
|
||||
uint32_t usesChromaLr : 1;
|
||||
uint32_t apply_grain : 1;
|
||||
uint32_t reserved : 3;
|
||||
} StdVideoDecodeAV1PictureInfoFlags;
|
||||
|
||||
typedef struct StdVideoDecodeAV1PictureInfo {
|
||||
StdVideoDecodeAV1PictureInfoFlags flags;
|
||||
StdVideoAV1FrameType frame_type;
|
||||
uint32_t current_frame_id;
|
||||
uint8_t OrderHint;
|
||||
uint8_t primary_ref_frame;
|
||||
uint8_t refresh_frame_flags;
|
||||
uint8_t reserved1;
|
||||
StdVideoAV1InterpolationFilter interpolation_filter;
|
||||
StdVideoAV1TxMode TxMode;
|
||||
uint8_t delta_q_res;
|
||||
uint8_t delta_lf_res;
|
||||
uint8_t SkipModeFrame[STD_VIDEO_AV1_SKIP_MODE_FRAMES];
|
||||
uint8_t coded_denom;
|
||||
uint8_t reserved2[3];
|
||||
uint8_t OrderHints[STD_VIDEO_AV1_NUM_REF_FRAMES];
|
||||
uint32_t expectedFrameId[STD_VIDEO_AV1_NUM_REF_FRAMES];
|
||||
const StdVideoAV1TileInfo* pTileInfo;
|
||||
const StdVideoAV1Quantization* pQuantization;
|
||||
const StdVideoAV1Segmentation* pSegmentation;
|
||||
const StdVideoAV1LoopFilter* pLoopFilter;
|
||||
const StdVideoAV1CDEF* pCDEF;
|
||||
const StdVideoAV1LoopRestoration* pLoopRestoration;
|
||||
const StdVideoAV1GlobalMotion* pGlobalMotion;
|
||||
const StdVideoAV1FilmGrain* pFilmGrain;
|
||||
} StdVideoDecodeAV1PictureInfo;
|
||||
|
||||
typedef struct StdVideoDecodeAV1ReferenceInfoFlags {
|
||||
uint32_t disable_frame_end_update_cdf : 1;
|
||||
uint32_t segmentation_enabled : 1;
|
||||
uint32_t reserved : 30;
|
||||
} StdVideoDecodeAV1ReferenceInfoFlags;
|
||||
|
||||
typedef struct StdVideoDecodeAV1ReferenceInfo {
|
||||
StdVideoDecodeAV1ReferenceInfoFlags flags;
|
||||
uint8_t frame_type;
|
||||
uint8_t RefFrameSignBias;
|
||||
uint8_t OrderHint;
|
||||
uint8_t SavedOrderHints[STD_VIDEO_AV1_NUM_REF_FRAMES];
|
||||
} StdVideoDecodeAV1ReferenceInfo;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
#ifndef VULKAN_VIDEO_CODEC_AV1STD_ENCODE_H_
|
||||
#define VULKAN_VIDEO_CODEC_AV1STD_ENCODE_H_ 1
|
||||
|
||||
/*
|
||||
** Copyright 2015-2026 The Khronos Group Inc.
|
||||
**
|
||||
** SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
** This header is generated from the Khronos Vulkan XML API Registry.
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// vulkan_video_codec_av1std_encode is a preprocessor guard. Do not pass it to API calls.
|
||||
#define vulkan_video_codec_av1std_encode 1
|
||||
#include "vulkan_video_codec_av1std.h"
|
||||
|
||||
#define VK_STD_VULKAN_VIDEO_CODEC_AV1_ENCODE_API_VERSION_1_0_0 VK_MAKE_VIDEO_STD_VERSION(1, 0, 0)
|
||||
|
||||
#define VK_STD_VULKAN_VIDEO_CODEC_AV1_ENCODE_SPEC_VERSION VK_STD_VULKAN_VIDEO_CODEC_AV1_ENCODE_API_VERSION_1_0_0
|
||||
#define VK_STD_VULKAN_VIDEO_CODEC_AV1_ENCODE_EXTENSION_NAME "VK_STD_vulkan_video_codec_av1_encode"
|
||||
typedef struct StdVideoEncodeAV1DecoderModelInfo {
|
||||
uint8_t buffer_delay_length_minus_1;
|
||||
uint8_t buffer_removal_time_length_minus_1;
|
||||
uint8_t frame_presentation_time_length_minus_1;
|
||||
uint8_t reserved1;
|
||||
uint32_t num_units_in_decoding_tick;
|
||||
} StdVideoEncodeAV1DecoderModelInfo;
|
||||
|
||||
typedef struct StdVideoEncodeAV1ExtensionHeader {
|
||||
uint8_t temporal_id;
|
||||
uint8_t spatial_id;
|
||||
} StdVideoEncodeAV1ExtensionHeader;
|
||||
|
||||
typedef struct StdVideoEncodeAV1OperatingPointInfoFlags {
|
||||
uint32_t decoder_model_present_for_this_op : 1;
|
||||
uint32_t low_delay_mode_flag : 1;
|
||||
uint32_t initial_display_delay_present_for_this_op : 1;
|
||||
uint32_t reserved : 29;
|
||||
} StdVideoEncodeAV1OperatingPointInfoFlags;
|
||||
|
||||
typedef struct StdVideoEncodeAV1OperatingPointInfo {
|
||||
StdVideoEncodeAV1OperatingPointInfoFlags flags;
|
||||
uint16_t operating_point_idc;
|
||||
uint8_t seq_level_idx;
|
||||
uint8_t seq_tier;
|
||||
uint32_t decoder_buffer_delay;
|
||||
uint32_t encoder_buffer_delay;
|
||||
uint8_t initial_display_delay_minus_1;
|
||||
} StdVideoEncodeAV1OperatingPointInfo;
|
||||
|
||||
typedef struct StdVideoEncodeAV1PictureInfoFlags {
|
||||
uint32_t error_resilient_mode : 1;
|
||||
uint32_t disable_cdf_update : 1;
|
||||
uint32_t use_superres : 1;
|
||||
uint32_t render_and_frame_size_different : 1;
|
||||
uint32_t allow_screen_content_tools : 1;
|
||||
uint32_t is_filter_switchable : 1;
|
||||
uint32_t force_integer_mv : 1;
|
||||
uint32_t frame_size_override_flag : 1;
|
||||
uint32_t buffer_removal_time_present_flag : 1;
|
||||
uint32_t allow_intrabc : 1;
|
||||
uint32_t frame_refs_short_signaling : 1;
|
||||
uint32_t allow_high_precision_mv : 1;
|
||||
uint32_t is_motion_mode_switchable : 1;
|
||||
uint32_t use_ref_frame_mvs : 1;
|
||||
uint32_t disable_frame_end_update_cdf : 1;
|
||||
uint32_t allow_warped_motion : 1;
|
||||
uint32_t reduced_tx_set : 1;
|
||||
uint32_t skip_mode_present : 1;
|
||||
uint32_t delta_q_present : 1;
|
||||
uint32_t delta_lf_present : 1;
|
||||
uint32_t delta_lf_multi : 1;
|
||||
uint32_t segmentation_enabled : 1;
|
||||
uint32_t segmentation_update_map : 1;
|
||||
uint32_t segmentation_temporal_update : 1;
|
||||
uint32_t segmentation_update_data : 1;
|
||||
uint32_t UsesLr : 1;
|
||||
uint32_t usesChromaLr : 1;
|
||||
uint32_t show_frame : 1;
|
||||
uint32_t showable_frame : 1;
|
||||
uint32_t reserved : 3;
|
||||
} StdVideoEncodeAV1PictureInfoFlags;
|
||||
|
||||
typedef struct StdVideoEncodeAV1PictureInfo {
|
||||
StdVideoEncodeAV1PictureInfoFlags flags;
|
||||
StdVideoAV1FrameType frame_type;
|
||||
uint32_t frame_presentation_time;
|
||||
uint32_t current_frame_id;
|
||||
uint8_t order_hint;
|
||||
uint8_t primary_ref_frame;
|
||||
uint8_t refresh_frame_flags;
|
||||
uint8_t coded_denom;
|
||||
uint16_t render_width_minus_1;
|
||||
uint16_t render_height_minus_1;
|
||||
StdVideoAV1InterpolationFilter interpolation_filter;
|
||||
StdVideoAV1TxMode TxMode;
|
||||
uint8_t delta_q_res;
|
||||
uint8_t delta_lf_res;
|
||||
uint8_t ref_order_hint[STD_VIDEO_AV1_NUM_REF_FRAMES];
|
||||
int8_t ref_frame_idx[STD_VIDEO_AV1_REFS_PER_FRAME];
|
||||
uint8_t reserved1[3];
|
||||
uint32_t delta_frame_id_minus_1[STD_VIDEO_AV1_REFS_PER_FRAME];
|
||||
const StdVideoAV1TileInfo* pTileInfo;
|
||||
const StdVideoAV1Quantization* pQuantization;
|
||||
const StdVideoAV1Segmentation* pSegmentation;
|
||||
const StdVideoAV1LoopFilter* pLoopFilter;
|
||||
const StdVideoAV1CDEF* pCDEF;
|
||||
const StdVideoAV1LoopRestoration* pLoopRestoration;
|
||||
const StdVideoAV1GlobalMotion* pGlobalMotion;
|
||||
const StdVideoEncodeAV1ExtensionHeader* pExtensionHeader;
|
||||
const uint32_t* pBufferRemovalTimes;
|
||||
} StdVideoEncodeAV1PictureInfo;
|
||||
|
||||
typedef struct StdVideoEncodeAV1ReferenceInfoFlags {
|
||||
uint32_t disable_frame_end_update_cdf : 1;
|
||||
uint32_t segmentation_enabled : 1;
|
||||
uint32_t reserved : 30;
|
||||
} StdVideoEncodeAV1ReferenceInfoFlags;
|
||||
|
||||
typedef struct StdVideoEncodeAV1ReferenceInfo {
|
||||
StdVideoEncodeAV1ReferenceInfoFlags flags;
|
||||
uint32_t RefFrameId;
|
||||
StdVideoAV1FrameType frame_type;
|
||||
uint8_t OrderHint;
|
||||
uint8_t reserved1[3];
|
||||
const StdVideoEncodeAV1ExtensionHeader* pExtensionHeader;
|
||||
} StdVideoEncodeAV1ReferenceInfo;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Vendored
+312
@@ -0,0 +1,312 @@
|
||||
#ifndef VULKAN_VIDEO_CODEC_H264STD_H_
|
||||
#define VULKAN_VIDEO_CODEC_H264STD_H_ 1
|
||||
|
||||
/*
|
||||
** Copyright 2015-2026 The Khronos Group Inc.
|
||||
**
|
||||
** SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
** This header is generated from the Khronos Vulkan XML API Registry.
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// vulkan_video_codec_h264std is a preprocessor guard. Do not pass it to API calls.
|
||||
#define vulkan_video_codec_h264std 1
|
||||
#include "vulkan_video_codecs_common.h"
|
||||
#define STD_VIDEO_H264_CPB_CNT_LIST_SIZE 32U
|
||||
#define STD_VIDEO_H264_SCALING_LIST_4X4_NUM_LISTS 6U
|
||||
#define STD_VIDEO_H264_SCALING_LIST_4X4_NUM_ELEMENTS 16U
|
||||
#define STD_VIDEO_H264_SCALING_LIST_8X8_NUM_LISTS 6U
|
||||
#define STD_VIDEO_H264_SCALING_LIST_8X8_NUM_ELEMENTS 64U
|
||||
#define STD_VIDEO_H264_MAX_NUM_LIST_REF 32U
|
||||
#define STD_VIDEO_H264_MAX_CHROMA_PLANES 2U
|
||||
#define STD_VIDEO_H264_NO_REFERENCE_PICTURE 0xFFU
|
||||
|
||||
typedef enum StdVideoH264ChromaFormatIdc {
|
||||
STD_VIDEO_H264_CHROMA_FORMAT_IDC_MONOCHROME = 0,
|
||||
STD_VIDEO_H264_CHROMA_FORMAT_IDC_420 = 1,
|
||||
STD_VIDEO_H264_CHROMA_FORMAT_IDC_422 = 2,
|
||||
STD_VIDEO_H264_CHROMA_FORMAT_IDC_444 = 3,
|
||||
STD_VIDEO_H264_CHROMA_FORMAT_IDC_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_H264_CHROMA_FORMAT_IDC_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoH264ChromaFormatIdc;
|
||||
|
||||
typedef enum StdVideoH264ProfileIdc {
|
||||
STD_VIDEO_H264_PROFILE_IDC_BASELINE = 66,
|
||||
STD_VIDEO_H264_PROFILE_IDC_MAIN = 77,
|
||||
STD_VIDEO_H264_PROFILE_IDC_HIGH = 100,
|
||||
STD_VIDEO_H264_PROFILE_IDC_HIGH_444_PREDICTIVE = 244,
|
||||
STD_VIDEO_H264_PROFILE_IDC_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_H264_PROFILE_IDC_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoH264ProfileIdc;
|
||||
|
||||
typedef enum StdVideoH264LevelIdc {
|
||||
STD_VIDEO_H264_LEVEL_IDC_1_0 = 0,
|
||||
STD_VIDEO_H264_LEVEL_IDC_1_1 = 1,
|
||||
STD_VIDEO_H264_LEVEL_IDC_1_2 = 2,
|
||||
STD_VIDEO_H264_LEVEL_IDC_1_3 = 3,
|
||||
STD_VIDEO_H264_LEVEL_IDC_2_0 = 4,
|
||||
STD_VIDEO_H264_LEVEL_IDC_2_1 = 5,
|
||||
STD_VIDEO_H264_LEVEL_IDC_2_2 = 6,
|
||||
STD_VIDEO_H264_LEVEL_IDC_3_0 = 7,
|
||||
STD_VIDEO_H264_LEVEL_IDC_3_1 = 8,
|
||||
STD_VIDEO_H264_LEVEL_IDC_3_2 = 9,
|
||||
STD_VIDEO_H264_LEVEL_IDC_4_0 = 10,
|
||||
STD_VIDEO_H264_LEVEL_IDC_4_1 = 11,
|
||||
STD_VIDEO_H264_LEVEL_IDC_4_2 = 12,
|
||||
STD_VIDEO_H264_LEVEL_IDC_5_0 = 13,
|
||||
STD_VIDEO_H264_LEVEL_IDC_5_1 = 14,
|
||||
STD_VIDEO_H264_LEVEL_IDC_5_2 = 15,
|
||||
STD_VIDEO_H264_LEVEL_IDC_6_0 = 16,
|
||||
STD_VIDEO_H264_LEVEL_IDC_6_1 = 17,
|
||||
STD_VIDEO_H264_LEVEL_IDC_6_2 = 18,
|
||||
STD_VIDEO_H264_LEVEL_IDC_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_H264_LEVEL_IDC_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoH264LevelIdc;
|
||||
|
||||
typedef enum StdVideoH264PocType {
|
||||
STD_VIDEO_H264_POC_TYPE_0 = 0,
|
||||
STD_VIDEO_H264_POC_TYPE_1 = 1,
|
||||
STD_VIDEO_H264_POC_TYPE_2 = 2,
|
||||
STD_VIDEO_H264_POC_TYPE_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_H264_POC_TYPE_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoH264PocType;
|
||||
|
||||
typedef enum StdVideoH264AspectRatioIdc {
|
||||
STD_VIDEO_H264_ASPECT_RATIO_IDC_UNSPECIFIED = 0,
|
||||
STD_VIDEO_H264_ASPECT_RATIO_IDC_SQUARE = 1,
|
||||
STD_VIDEO_H264_ASPECT_RATIO_IDC_12_11 = 2,
|
||||
STD_VIDEO_H264_ASPECT_RATIO_IDC_10_11 = 3,
|
||||
STD_VIDEO_H264_ASPECT_RATIO_IDC_16_11 = 4,
|
||||
STD_VIDEO_H264_ASPECT_RATIO_IDC_40_33 = 5,
|
||||
STD_VIDEO_H264_ASPECT_RATIO_IDC_24_11 = 6,
|
||||
STD_VIDEO_H264_ASPECT_RATIO_IDC_20_11 = 7,
|
||||
STD_VIDEO_H264_ASPECT_RATIO_IDC_32_11 = 8,
|
||||
STD_VIDEO_H264_ASPECT_RATIO_IDC_80_33 = 9,
|
||||
STD_VIDEO_H264_ASPECT_RATIO_IDC_18_11 = 10,
|
||||
STD_VIDEO_H264_ASPECT_RATIO_IDC_15_11 = 11,
|
||||
STD_VIDEO_H264_ASPECT_RATIO_IDC_64_33 = 12,
|
||||
STD_VIDEO_H264_ASPECT_RATIO_IDC_160_99 = 13,
|
||||
STD_VIDEO_H264_ASPECT_RATIO_IDC_4_3 = 14,
|
||||
STD_VIDEO_H264_ASPECT_RATIO_IDC_3_2 = 15,
|
||||
STD_VIDEO_H264_ASPECT_RATIO_IDC_2_1 = 16,
|
||||
STD_VIDEO_H264_ASPECT_RATIO_IDC_EXTENDED_SAR = 255,
|
||||
STD_VIDEO_H264_ASPECT_RATIO_IDC_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_H264_ASPECT_RATIO_IDC_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoH264AspectRatioIdc;
|
||||
|
||||
typedef enum StdVideoH264WeightedBipredIdc {
|
||||
STD_VIDEO_H264_WEIGHTED_BIPRED_IDC_DEFAULT = 0,
|
||||
STD_VIDEO_H264_WEIGHTED_BIPRED_IDC_EXPLICIT = 1,
|
||||
STD_VIDEO_H264_WEIGHTED_BIPRED_IDC_IMPLICIT = 2,
|
||||
STD_VIDEO_H264_WEIGHTED_BIPRED_IDC_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_H264_WEIGHTED_BIPRED_IDC_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoH264WeightedBipredIdc;
|
||||
|
||||
typedef enum StdVideoH264ModificationOfPicNumsIdc {
|
||||
STD_VIDEO_H264_MODIFICATION_OF_PIC_NUMS_IDC_SHORT_TERM_SUBTRACT = 0,
|
||||
STD_VIDEO_H264_MODIFICATION_OF_PIC_NUMS_IDC_SHORT_TERM_ADD = 1,
|
||||
STD_VIDEO_H264_MODIFICATION_OF_PIC_NUMS_IDC_LONG_TERM = 2,
|
||||
STD_VIDEO_H264_MODIFICATION_OF_PIC_NUMS_IDC_END = 3,
|
||||
STD_VIDEO_H264_MODIFICATION_OF_PIC_NUMS_IDC_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_H264_MODIFICATION_OF_PIC_NUMS_IDC_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoH264ModificationOfPicNumsIdc;
|
||||
|
||||
typedef enum StdVideoH264MemMgmtControlOp {
|
||||
STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_END = 0,
|
||||
STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_UNMARK_SHORT_TERM = 1,
|
||||
STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_UNMARK_LONG_TERM = 2,
|
||||
STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_MARK_LONG_TERM = 3,
|
||||
STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_SET_MAX_LONG_TERM_INDEX = 4,
|
||||
STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_UNMARK_ALL = 5,
|
||||
STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_MARK_CURRENT_AS_LONG_TERM = 6,
|
||||
STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoH264MemMgmtControlOp;
|
||||
|
||||
typedef enum StdVideoH264CabacInitIdc {
|
||||
STD_VIDEO_H264_CABAC_INIT_IDC_0 = 0,
|
||||
STD_VIDEO_H264_CABAC_INIT_IDC_1 = 1,
|
||||
STD_VIDEO_H264_CABAC_INIT_IDC_2 = 2,
|
||||
STD_VIDEO_H264_CABAC_INIT_IDC_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_H264_CABAC_INIT_IDC_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoH264CabacInitIdc;
|
||||
|
||||
typedef enum StdVideoH264DisableDeblockingFilterIdc {
|
||||
STD_VIDEO_H264_DISABLE_DEBLOCKING_FILTER_IDC_DISABLED = 0,
|
||||
STD_VIDEO_H264_DISABLE_DEBLOCKING_FILTER_IDC_ENABLED = 1,
|
||||
STD_VIDEO_H264_DISABLE_DEBLOCKING_FILTER_IDC_PARTIAL = 2,
|
||||
STD_VIDEO_H264_DISABLE_DEBLOCKING_FILTER_IDC_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_H264_DISABLE_DEBLOCKING_FILTER_IDC_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoH264DisableDeblockingFilterIdc;
|
||||
|
||||
typedef enum StdVideoH264SliceType {
|
||||
STD_VIDEO_H264_SLICE_TYPE_P = 0,
|
||||
STD_VIDEO_H264_SLICE_TYPE_B = 1,
|
||||
STD_VIDEO_H264_SLICE_TYPE_I = 2,
|
||||
STD_VIDEO_H264_SLICE_TYPE_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_H264_SLICE_TYPE_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoH264SliceType;
|
||||
|
||||
typedef enum StdVideoH264PictureType {
|
||||
STD_VIDEO_H264_PICTURE_TYPE_P = 0,
|
||||
STD_VIDEO_H264_PICTURE_TYPE_B = 1,
|
||||
STD_VIDEO_H264_PICTURE_TYPE_I = 2,
|
||||
STD_VIDEO_H264_PICTURE_TYPE_IDR = 5,
|
||||
STD_VIDEO_H264_PICTURE_TYPE_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_H264_PICTURE_TYPE_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoH264PictureType;
|
||||
|
||||
typedef enum StdVideoH264NonVclNaluType {
|
||||
STD_VIDEO_H264_NON_VCL_NALU_TYPE_SPS = 0,
|
||||
STD_VIDEO_H264_NON_VCL_NALU_TYPE_PPS = 1,
|
||||
STD_VIDEO_H264_NON_VCL_NALU_TYPE_AUD = 2,
|
||||
STD_VIDEO_H264_NON_VCL_NALU_TYPE_PREFIX = 3,
|
||||
STD_VIDEO_H264_NON_VCL_NALU_TYPE_END_OF_SEQUENCE = 4,
|
||||
STD_VIDEO_H264_NON_VCL_NALU_TYPE_END_OF_STREAM = 5,
|
||||
STD_VIDEO_H264_NON_VCL_NALU_TYPE_PRECODED = 6,
|
||||
STD_VIDEO_H264_NON_VCL_NALU_TYPE_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_H264_NON_VCL_NALU_TYPE_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoH264NonVclNaluType;
|
||||
typedef struct StdVideoH264SpsVuiFlags {
|
||||
uint32_t aspect_ratio_info_present_flag : 1;
|
||||
uint32_t overscan_info_present_flag : 1;
|
||||
uint32_t overscan_appropriate_flag : 1;
|
||||
uint32_t video_signal_type_present_flag : 1;
|
||||
uint32_t video_full_range_flag : 1;
|
||||
uint32_t color_description_present_flag : 1;
|
||||
uint32_t chroma_loc_info_present_flag : 1;
|
||||
uint32_t timing_info_present_flag : 1;
|
||||
uint32_t fixed_frame_rate_flag : 1;
|
||||
uint32_t bitstream_restriction_flag : 1;
|
||||
uint32_t nal_hrd_parameters_present_flag : 1;
|
||||
uint32_t vcl_hrd_parameters_present_flag : 1;
|
||||
} StdVideoH264SpsVuiFlags;
|
||||
|
||||
typedef struct StdVideoH264HrdParameters {
|
||||
uint8_t cpb_cnt_minus1;
|
||||
uint8_t bit_rate_scale;
|
||||
uint8_t cpb_size_scale;
|
||||
uint8_t reserved1;
|
||||
uint32_t bit_rate_value_minus1[STD_VIDEO_H264_CPB_CNT_LIST_SIZE];
|
||||
uint32_t cpb_size_value_minus1[STD_VIDEO_H264_CPB_CNT_LIST_SIZE];
|
||||
uint8_t cbr_flag[STD_VIDEO_H264_CPB_CNT_LIST_SIZE];
|
||||
uint32_t initial_cpb_removal_delay_length_minus1;
|
||||
uint32_t cpb_removal_delay_length_minus1;
|
||||
uint32_t dpb_output_delay_length_minus1;
|
||||
uint32_t time_offset_length;
|
||||
} StdVideoH264HrdParameters;
|
||||
|
||||
typedef struct StdVideoH264SequenceParameterSetVui {
|
||||
StdVideoH264SpsVuiFlags flags;
|
||||
StdVideoH264AspectRatioIdc aspect_ratio_idc;
|
||||
uint16_t sar_width;
|
||||
uint16_t sar_height;
|
||||
uint8_t video_format;
|
||||
uint8_t colour_primaries;
|
||||
uint8_t transfer_characteristics;
|
||||
uint8_t matrix_coefficients;
|
||||
uint32_t num_units_in_tick;
|
||||
uint32_t time_scale;
|
||||
uint8_t max_num_reorder_frames;
|
||||
uint8_t max_dec_frame_buffering;
|
||||
uint8_t chroma_sample_loc_type_top_field;
|
||||
uint8_t chroma_sample_loc_type_bottom_field;
|
||||
uint32_t reserved1;
|
||||
const StdVideoH264HrdParameters* pHrdParameters;
|
||||
} StdVideoH264SequenceParameterSetVui;
|
||||
|
||||
typedef struct StdVideoH264SpsFlags {
|
||||
uint32_t constraint_set0_flag : 1;
|
||||
uint32_t constraint_set1_flag : 1;
|
||||
uint32_t constraint_set2_flag : 1;
|
||||
uint32_t constraint_set3_flag : 1;
|
||||
uint32_t constraint_set4_flag : 1;
|
||||
uint32_t constraint_set5_flag : 1;
|
||||
uint32_t direct_8x8_inference_flag : 1;
|
||||
uint32_t mb_adaptive_frame_field_flag : 1;
|
||||
uint32_t frame_mbs_only_flag : 1;
|
||||
uint32_t delta_pic_order_always_zero_flag : 1;
|
||||
uint32_t separate_colour_plane_flag : 1;
|
||||
uint32_t gaps_in_frame_num_value_allowed_flag : 1;
|
||||
uint32_t qpprime_y_zero_transform_bypass_flag : 1;
|
||||
uint32_t frame_cropping_flag : 1;
|
||||
uint32_t seq_scaling_matrix_present_flag : 1;
|
||||
uint32_t vui_parameters_present_flag : 1;
|
||||
} StdVideoH264SpsFlags;
|
||||
|
||||
typedef struct StdVideoH264ScalingLists {
|
||||
uint16_t scaling_list_present_mask;
|
||||
uint16_t use_default_scaling_matrix_mask;
|
||||
uint8_t ScalingList4x4[STD_VIDEO_H264_SCALING_LIST_4X4_NUM_LISTS][STD_VIDEO_H264_SCALING_LIST_4X4_NUM_ELEMENTS];
|
||||
uint8_t ScalingList8x8[STD_VIDEO_H264_SCALING_LIST_8X8_NUM_LISTS][STD_VIDEO_H264_SCALING_LIST_8X8_NUM_ELEMENTS];
|
||||
} StdVideoH264ScalingLists;
|
||||
|
||||
typedef struct StdVideoH264SequenceParameterSet {
|
||||
StdVideoH264SpsFlags flags;
|
||||
StdVideoH264ProfileIdc profile_idc;
|
||||
StdVideoH264LevelIdc level_idc;
|
||||
StdVideoH264ChromaFormatIdc chroma_format_idc;
|
||||
uint8_t seq_parameter_set_id;
|
||||
uint8_t bit_depth_luma_minus8;
|
||||
uint8_t bit_depth_chroma_minus8;
|
||||
uint8_t log2_max_frame_num_minus4;
|
||||
StdVideoH264PocType pic_order_cnt_type;
|
||||
int32_t offset_for_non_ref_pic;
|
||||
int32_t offset_for_top_to_bottom_field;
|
||||
uint8_t log2_max_pic_order_cnt_lsb_minus4;
|
||||
uint8_t num_ref_frames_in_pic_order_cnt_cycle;
|
||||
uint8_t max_num_ref_frames;
|
||||
uint8_t reserved1;
|
||||
uint32_t pic_width_in_mbs_minus1;
|
||||
uint32_t pic_height_in_map_units_minus1;
|
||||
uint32_t frame_crop_left_offset;
|
||||
uint32_t frame_crop_right_offset;
|
||||
uint32_t frame_crop_top_offset;
|
||||
uint32_t frame_crop_bottom_offset;
|
||||
uint32_t reserved2;
|
||||
const int32_t* pOffsetForRefFrame;
|
||||
const StdVideoH264ScalingLists* pScalingLists;
|
||||
const StdVideoH264SequenceParameterSetVui* pSequenceParameterSetVui;
|
||||
} StdVideoH264SequenceParameterSet;
|
||||
|
||||
typedef struct StdVideoH264PpsFlags {
|
||||
uint32_t transform_8x8_mode_flag : 1;
|
||||
uint32_t redundant_pic_cnt_present_flag : 1;
|
||||
uint32_t constrained_intra_pred_flag : 1;
|
||||
uint32_t deblocking_filter_control_present_flag : 1;
|
||||
uint32_t weighted_pred_flag : 1;
|
||||
uint32_t bottom_field_pic_order_in_frame_present_flag : 1;
|
||||
uint32_t entropy_coding_mode_flag : 1;
|
||||
uint32_t pic_scaling_matrix_present_flag : 1;
|
||||
} StdVideoH264PpsFlags;
|
||||
|
||||
typedef struct StdVideoH264PictureParameterSet {
|
||||
StdVideoH264PpsFlags flags;
|
||||
uint8_t seq_parameter_set_id;
|
||||
uint8_t pic_parameter_set_id;
|
||||
uint8_t num_ref_idx_l0_default_active_minus1;
|
||||
uint8_t num_ref_idx_l1_default_active_minus1;
|
||||
StdVideoH264WeightedBipredIdc weighted_bipred_idc;
|
||||
int8_t pic_init_qp_minus26;
|
||||
int8_t pic_init_qs_minus26;
|
||||
int8_t chroma_qp_index_offset;
|
||||
int8_t second_chroma_qp_index_offset;
|
||||
const StdVideoH264ScalingLists* pScalingLists;
|
||||
} StdVideoH264PictureParameterSet;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
#ifndef VULKAN_VIDEO_CODEC_H264STD_DECODE_H_
|
||||
#define VULKAN_VIDEO_CODEC_H264STD_DECODE_H_ 1
|
||||
|
||||
/*
|
||||
** Copyright 2015-2026 The Khronos Group Inc.
|
||||
**
|
||||
** SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
** This header is generated from the Khronos Vulkan XML API Registry.
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// vulkan_video_codec_h264std_decode is a preprocessor guard. Do not pass it to API calls.
|
||||
#define vulkan_video_codec_h264std_decode 1
|
||||
#include "vulkan_video_codec_h264std.h"
|
||||
|
||||
#define VK_STD_VULKAN_VIDEO_CODEC_H264_DECODE_API_VERSION_1_0_0 VK_MAKE_VIDEO_STD_VERSION(1, 0, 0)
|
||||
|
||||
#define VK_STD_VULKAN_VIDEO_CODEC_H264_DECODE_SPEC_VERSION VK_STD_VULKAN_VIDEO_CODEC_H264_DECODE_API_VERSION_1_0_0
|
||||
#define VK_STD_VULKAN_VIDEO_CODEC_H264_DECODE_EXTENSION_NAME "VK_STD_vulkan_video_codec_h264_decode"
|
||||
#define STD_VIDEO_DECODE_H264_FIELD_ORDER_COUNT_LIST_SIZE 2U
|
||||
|
||||
typedef enum StdVideoDecodeH264FieldOrderCount {
|
||||
STD_VIDEO_DECODE_H264_FIELD_ORDER_COUNT_TOP = 0,
|
||||
STD_VIDEO_DECODE_H264_FIELD_ORDER_COUNT_BOTTOM = 1,
|
||||
STD_VIDEO_DECODE_H264_FIELD_ORDER_COUNT_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_DECODE_H264_FIELD_ORDER_COUNT_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoDecodeH264FieldOrderCount;
|
||||
typedef struct StdVideoDecodeH264PictureInfoFlags {
|
||||
uint32_t field_pic_flag : 1;
|
||||
uint32_t is_intra : 1;
|
||||
uint32_t IdrPicFlag : 1;
|
||||
uint32_t bottom_field_flag : 1;
|
||||
uint32_t is_reference : 1;
|
||||
uint32_t complementary_field_pair : 1;
|
||||
} StdVideoDecodeH264PictureInfoFlags;
|
||||
|
||||
typedef struct StdVideoDecodeH264PictureInfo {
|
||||
StdVideoDecodeH264PictureInfoFlags flags;
|
||||
uint8_t seq_parameter_set_id;
|
||||
uint8_t pic_parameter_set_id;
|
||||
uint8_t reserved1;
|
||||
uint8_t reserved2;
|
||||
uint16_t frame_num;
|
||||
uint16_t idr_pic_id;
|
||||
int32_t PicOrderCnt[STD_VIDEO_DECODE_H264_FIELD_ORDER_COUNT_LIST_SIZE];
|
||||
} StdVideoDecodeH264PictureInfo;
|
||||
|
||||
typedef struct StdVideoDecodeH264ReferenceInfoFlags {
|
||||
uint32_t top_field_flag : 1;
|
||||
uint32_t bottom_field_flag : 1;
|
||||
uint32_t used_for_long_term_reference : 1;
|
||||
uint32_t is_non_existing : 1;
|
||||
} StdVideoDecodeH264ReferenceInfoFlags;
|
||||
|
||||
typedef struct StdVideoDecodeH264ReferenceInfo {
|
||||
StdVideoDecodeH264ReferenceInfoFlags flags;
|
||||
uint16_t FrameNum;
|
||||
uint16_t reserved;
|
||||
int32_t PicOrderCnt[STD_VIDEO_DECODE_H264_FIELD_ORDER_COUNT_LIST_SIZE];
|
||||
} StdVideoDecodeH264ReferenceInfo;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
#ifndef VULKAN_VIDEO_CODEC_H264STD_ENCODE_H_
|
||||
#define VULKAN_VIDEO_CODEC_H264STD_ENCODE_H_ 1
|
||||
|
||||
/*
|
||||
** Copyright 2015-2026 The Khronos Group Inc.
|
||||
**
|
||||
** SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
** This header is generated from the Khronos Vulkan XML API Registry.
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// vulkan_video_codec_h264std_encode is a preprocessor guard. Do not pass it to API calls.
|
||||
#define vulkan_video_codec_h264std_encode 1
|
||||
#include "vulkan_video_codec_h264std.h"
|
||||
|
||||
#define VK_STD_VULKAN_VIDEO_CODEC_H264_ENCODE_API_VERSION_1_0_0 VK_MAKE_VIDEO_STD_VERSION(1, 0, 0)
|
||||
|
||||
#define VK_STD_VULKAN_VIDEO_CODEC_H264_ENCODE_SPEC_VERSION VK_STD_VULKAN_VIDEO_CODEC_H264_ENCODE_API_VERSION_1_0_0
|
||||
#define VK_STD_VULKAN_VIDEO_CODEC_H264_ENCODE_EXTENSION_NAME "VK_STD_vulkan_video_codec_h264_encode"
|
||||
typedef struct StdVideoEncodeH264WeightTableFlags {
|
||||
uint32_t luma_weight_l0_flag;
|
||||
uint32_t chroma_weight_l0_flag;
|
||||
uint32_t luma_weight_l1_flag;
|
||||
uint32_t chroma_weight_l1_flag;
|
||||
} StdVideoEncodeH264WeightTableFlags;
|
||||
|
||||
typedef struct StdVideoEncodeH264WeightTable {
|
||||
StdVideoEncodeH264WeightTableFlags flags;
|
||||
uint8_t luma_log2_weight_denom;
|
||||
uint8_t chroma_log2_weight_denom;
|
||||
int8_t luma_weight_l0[STD_VIDEO_H264_MAX_NUM_LIST_REF];
|
||||
int8_t luma_offset_l0[STD_VIDEO_H264_MAX_NUM_LIST_REF];
|
||||
int8_t chroma_weight_l0[STD_VIDEO_H264_MAX_NUM_LIST_REF][STD_VIDEO_H264_MAX_CHROMA_PLANES];
|
||||
int8_t chroma_offset_l0[STD_VIDEO_H264_MAX_NUM_LIST_REF][STD_VIDEO_H264_MAX_CHROMA_PLANES];
|
||||
int8_t luma_weight_l1[STD_VIDEO_H264_MAX_NUM_LIST_REF];
|
||||
int8_t luma_offset_l1[STD_VIDEO_H264_MAX_NUM_LIST_REF];
|
||||
int8_t chroma_weight_l1[STD_VIDEO_H264_MAX_NUM_LIST_REF][STD_VIDEO_H264_MAX_CHROMA_PLANES];
|
||||
int8_t chroma_offset_l1[STD_VIDEO_H264_MAX_NUM_LIST_REF][STD_VIDEO_H264_MAX_CHROMA_PLANES];
|
||||
} StdVideoEncodeH264WeightTable;
|
||||
|
||||
typedef struct StdVideoEncodeH264SliceHeaderFlags {
|
||||
uint32_t direct_spatial_mv_pred_flag : 1;
|
||||
uint32_t num_ref_idx_active_override_flag : 1;
|
||||
uint32_t reserved : 30;
|
||||
} StdVideoEncodeH264SliceHeaderFlags;
|
||||
|
||||
typedef struct StdVideoEncodeH264PictureInfoFlags {
|
||||
uint32_t IdrPicFlag : 1;
|
||||
uint32_t is_reference : 1;
|
||||
uint32_t no_output_of_prior_pics_flag : 1;
|
||||
uint32_t long_term_reference_flag : 1;
|
||||
uint32_t adaptive_ref_pic_marking_mode_flag : 1;
|
||||
uint32_t reserved : 27;
|
||||
} StdVideoEncodeH264PictureInfoFlags;
|
||||
|
||||
typedef struct StdVideoEncodeH264ReferenceInfoFlags {
|
||||
uint32_t used_for_long_term_reference : 1;
|
||||
uint32_t reserved : 31;
|
||||
} StdVideoEncodeH264ReferenceInfoFlags;
|
||||
|
||||
typedef struct StdVideoEncodeH264ReferenceListsInfoFlags {
|
||||
uint32_t ref_pic_list_modification_flag_l0 : 1;
|
||||
uint32_t ref_pic_list_modification_flag_l1 : 1;
|
||||
uint32_t reserved : 30;
|
||||
} StdVideoEncodeH264ReferenceListsInfoFlags;
|
||||
|
||||
typedef struct StdVideoEncodeH264RefListModEntry {
|
||||
StdVideoH264ModificationOfPicNumsIdc modification_of_pic_nums_idc;
|
||||
uint16_t abs_diff_pic_num_minus1;
|
||||
uint16_t long_term_pic_num;
|
||||
} StdVideoEncodeH264RefListModEntry;
|
||||
|
||||
typedef struct StdVideoEncodeH264RefPicMarkingEntry {
|
||||
StdVideoH264MemMgmtControlOp memory_management_control_operation;
|
||||
uint16_t difference_of_pic_nums_minus1;
|
||||
uint16_t long_term_pic_num;
|
||||
uint16_t long_term_frame_idx;
|
||||
uint16_t max_long_term_frame_idx_plus1;
|
||||
} StdVideoEncodeH264RefPicMarkingEntry;
|
||||
|
||||
typedef struct StdVideoEncodeH264ReferenceListsInfo {
|
||||
StdVideoEncodeH264ReferenceListsInfoFlags flags;
|
||||
uint8_t num_ref_idx_l0_active_minus1;
|
||||
uint8_t num_ref_idx_l1_active_minus1;
|
||||
uint8_t RefPicList0[STD_VIDEO_H264_MAX_NUM_LIST_REF];
|
||||
uint8_t RefPicList1[STD_VIDEO_H264_MAX_NUM_LIST_REF];
|
||||
uint8_t refList0ModOpCount;
|
||||
uint8_t refList1ModOpCount;
|
||||
uint8_t refPicMarkingOpCount;
|
||||
uint8_t reserved1[7];
|
||||
const StdVideoEncodeH264RefListModEntry* pRefList0ModOperations;
|
||||
const StdVideoEncodeH264RefListModEntry* pRefList1ModOperations;
|
||||
const StdVideoEncodeH264RefPicMarkingEntry* pRefPicMarkingOperations;
|
||||
} StdVideoEncodeH264ReferenceListsInfo;
|
||||
|
||||
typedef struct StdVideoEncodeH264PictureInfo {
|
||||
StdVideoEncodeH264PictureInfoFlags flags;
|
||||
uint8_t seq_parameter_set_id;
|
||||
uint8_t pic_parameter_set_id;
|
||||
uint16_t idr_pic_id;
|
||||
StdVideoH264PictureType primary_pic_type;
|
||||
uint32_t frame_num;
|
||||
int32_t PicOrderCnt;
|
||||
uint8_t temporal_id;
|
||||
uint8_t reserved1[3];
|
||||
const StdVideoEncodeH264ReferenceListsInfo* pRefLists;
|
||||
} StdVideoEncodeH264PictureInfo;
|
||||
|
||||
typedef struct StdVideoEncodeH264ReferenceInfo {
|
||||
StdVideoEncodeH264ReferenceInfoFlags flags;
|
||||
StdVideoH264PictureType primary_pic_type;
|
||||
uint32_t FrameNum;
|
||||
int32_t PicOrderCnt;
|
||||
uint16_t long_term_pic_num;
|
||||
uint16_t long_term_frame_idx;
|
||||
uint8_t temporal_id;
|
||||
} StdVideoEncodeH264ReferenceInfo;
|
||||
|
||||
typedef struct StdVideoEncodeH264SliceHeader {
|
||||
StdVideoEncodeH264SliceHeaderFlags flags;
|
||||
uint32_t first_mb_in_slice;
|
||||
StdVideoH264SliceType slice_type;
|
||||
int8_t slice_alpha_c0_offset_div2;
|
||||
int8_t slice_beta_offset_div2;
|
||||
int8_t slice_qp_delta;
|
||||
uint8_t reserved1;
|
||||
StdVideoH264CabacInitIdc cabac_init_idc;
|
||||
StdVideoH264DisableDeblockingFilterIdc disable_deblocking_filter_idc;
|
||||
const StdVideoEncodeH264WeightTable* pWeightTable;
|
||||
} StdVideoEncodeH264SliceHeader;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Vendored
+446
@@ -0,0 +1,446 @@
|
||||
#ifndef VULKAN_VIDEO_CODEC_H265STD_H_
|
||||
#define VULKAN_VIDEO_CODEC_H265STD_H_ 1
|
||||
|
||||
/*
|
||||
** Copyright 2015-2026 The Khronos Group Inc.
|
||||
**
|
||||
** SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
** This header is generated from the Khronos Vulkan XML API Registry.
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// vulkan_video_codec_h265std is a preprocessor guard. Do not pass it to API calls.
|
||||
#define vulkan_video_codec_h265std 1
|
||||
#include "vulkan_video_codecs_common.h"
|
||||
#define STD_VIDEO_H265_CPB_CNT_LIST_SIZE 32U
|
||||
#define STD_VIDEO_H265_SUBLAYERS_LIST_SIZE 7U
|
||||
#define STD_VIDEO_H265_SCALING_LIST_4X4_NUM_LISTS 6U
|
||||
#define STD_VIDEO_H265_SCALING_LIST_4X4_NUM_ELEMENTS 16U
|
||||
#define STD_VIDEO_H265_SCALING_LIST_8X8_NUM_LISTS 6U
|
||||
#define STD_VIDEO_H265_SCALING_LIST_8X8_NUM_ELEMENTS 64U
|
||||
#define STD_VIDEO_H265_SCALING_LIST_16X16_NUM_LISTS 6U
|
||||
#define STD_VIDEO_H265_SCALING_LIST_16X16_NUM_ELEMENTS 64U
|
||||
#define STD_VIDEO_H265_SCALING_LIST_32X32_NUM_LISTS 2U
|
||||
#define STD_VIDEO_H265_SCALING_LIST_32X32_NUM_ELEMENTS 64U
|
||||
#define STD_VIDEO_H265_CHROMA_QP_OFFSET_LIST_SIZE 6U
|
||||
#define STD_VIDEO_H265_CHROMA_QP_OFFSET_TILE_COLS_LIST_SIZE 19U
|
||||
#define STD_VIDEO_H265_CHROMA_QP_OFFSET_TILE_ROWS_LIST_SIZE 21U
|
||||
#define STD_VIDEO_H265_PREDICTOR_PALETTE_COMPONENTS_LIST_SIZE 3U
|
||||
#define STD_VIDEO_H265_PREDICTOR_PALETTE_COMP_ENTRIES_LIST_SIZE 128U
|
||||
#define STD_VIDEO_H265_MAX_NUM_LIST_REF 15U
|
||||
#define STD_VIDEO_H265_MAX_CHROMA_PLANES 2U
|
||||
#define STD_VIDEO_H265_MAX_SHORT_TERM_REF_PIC_SETS 64U
|
||||
#define STD_VIDEO_H265_MAX_DPB_SIZE 16U
|
||||
#define STD_VIDEO_H265_MAX_LONG_TERM_REF_PICS_SPS 32U
|
||||
#define STD_VIDEO_H265_MAX_LONG_TERM_PICS 16U
|
||||
#define STD_VIDEO_H265_MAX_DELTA_POC 48U
|
||||
#define STD_VIDEO_H265_NO_REFERENCE_PICTURE 0xFFU
|
||||
|
||||
typedef enum StdVideoH265ChromaFormatIdc {
|
||||
STD_VIDEO_H265_CHROMA_FORMAT_IDC_MONOCHROME = 0,
|
||||
STD_VIDEO_H265_CHROMA_FORMAT_IDC_420 = 1,
|
||||
STD_VIDEO_H265_CHROMA_FORMAT_IDC_422 = 2,
|
||||
STD_VIDEO_H265_CHROMA_FORMAT_IDC_444 = 3,
|
||||
STD_VIDEO_H265_CHROMA_FORMAT_IDC_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_H265_CHROMA_FORMAT_IDC_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoH265ChromaFormatIdc;
|
||||
|
||||
typedef enum StdVideoH265ProfileIdc {
|
||||
STD_VIDEO_H265_PROFILE_IDC_MAIN = 1,
|
||||
STD_VIDEO_H265_PROFILE_IDC_MAIN_10 = 2,
|
||||
STD_VIDEO_H265_PROFILE_IDC_MAIN_STILL_PICTURE = 3,
|
||||
STD_VIDEO_H265_PROFILE_IDC_FORMAT_RANGE_EXTENSIONS = 4,
|
||||
STD_VIDEO_H265_PROFILE_IDC_SCC_EXTENSIONS = 9,
|
||||
STD_VIDEO_H265_PROFILE_IDC_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_H265_PROFILE_IDC_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoH265ProfileIdc;
|
||||
|
||||
typedef enum StdVideoH265LevelIdc {
|
||||
STD_VIDEO_H265_LEVEL_IDC_1_0 = 0,
|
||||
STD_VIDEO_H265_LEVEL_IDC_2_0 = 1,
|
||||
STD_VIDEO_H265_LEVEL_IDC_2_1 = 2,
|
||||
STD_VIDEO_H265_LEVEL_IDC_3_0 = 3,
|
||||
STD_VIDEO_H265_LEVEL_IDC_3_1 = 4,
|
||||
STD_VIDEO_H265_LEVEL_IDC_4_0 = 5,
|
||||
STD_VIDEO_H265_LEVEL_IDC_4_1 = 6,
|
||||
STD_VIDEO_H265_LEVEL_IDC_5_0 = 7,
|
||||
STD_VIDEO_H265_LEVEL_IDC_5_1 = 8,
|
||||
STD_VIDEO_H265_LEVEL_IDC_5_2 = 9,
|
||||
STD_VIDEO_H265_LEVEL_IDC_6_0 = 10,
|
||||
STD_VIDEO_H265_LEVEL_IDC_6_1 = 11,
|
||||
STD_VIDEO_H265_LEVEL_IDC_6_2 = 12,
|
||||
STD_VIDEO_H265_LEVEL_IDC_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_H265_LEVEL_IDC_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoH265LevelIdc;
|
||||
|
||||
typedef enum StdVideoH265SliceType {
|
||||
STD_VIDEO_H265_SLICE_TYPE_B = 0,
|
||||
STD_VIDEO_H265_SLICE_TYPE_P = 1,
|
||||
STD_VIDEO_H265_SLICE_TYPE_I = 2,
|
||||
STD_VIDEO_H265_SLICE_TYPE_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_H265_SLICE_TYPE_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoH265SliceType;
|
||||
|
||||
typedef enum StdVideoH265PictureType {
|
||||
STD_VIDEO_H265_PICTURE_TYPE_P = 0,
|
||||
STD_VIDEO_H265_PICTURE_TYPE_B = 1,
|
||||
STD_VIDEO_H265_PICTURE_TYPE_I = 2,
|
||||
STD_VIDEO_H265_PICTURE_TYPE_IDR = 3,
|
||||
STD_VIDEO_H265_PICTURE_TYPE_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_H265_PICTURE_TYPE_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoH265PictureType;
|
||||
|
||||
typedef enum StdVideoH265AspectRatioIdc {
|
||||
STD_VIDEO_H265_ASPECT_RATIO_IDC_UNSPECIFIED = 0,
|
||||
STD_VIDEO_H265_ASPECT_RATIO_IDC_SQUARE = 1,
|
||||
STD_VIDEO_H265_ASPECT_RATIO_IDC_12_11 = 2,
|
||||
STD_VIDEO_H265_ASPECT_RATIO_IDC_10_11 = 3,
|
||||
STD_VIDEO_H265_ASPECT_RATIO_IDC_16_11 = 4,
|
||||
STD_VIDEO_H265_ASPECT_RATIO_IDC_40_33 = 5,
|
||||
STD_VIDEO_H265_ASPECT_RATIO_IDC_24_11 = 6,
|
||||
STD_VIDEO_H265_ASPECT_RATIO_IDC_20_11 = 7,
|
||||
STD_VIDEO_H265_ASPECT_RATIO_IDC_32_11 = 8,
|
||||
STD_VIDEO_H265_ASPECT_RATIO_IDC_80_33 = 9,
|
||||
STD_VIDEO_H265_ASPECT_RATIO_IDC_18_11 = 10,
|
||||
STD_VIDEO_H265_ASPECT_RATIO_IDC_15_11 = 11,
|
||||
STD_VIDEO_H265_ASPECT_RATIO_IDC_64_33 = 12,
|
||||
STD_VIDEO_H265_ASPECT_RATIO_IDC_160_99 = 13,
|
||||
STD_VIDEO_H265_ASPECT_RATIO_IDC_4_3 = 14,
|
||||
STD_VIDEO_H265_ASPECT_RATIO_IDC_3_2 = 15,
|
||||
STD_VIDEO_H265_ASPECT_RATIO_IDC_2_1 = 16,
|
||||
STD_VIDEO_H265_ASPECT_RATIO_IDC_EXTENDED_SAR = 255,
|
||||
STD_VIDEO_H265_ASPECT_RATIO_IDC_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_H265_ASPECT_RATIO_IDC_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoH265AspectRatioIdc;
|
||||
typedef struct StdVideoH265DecPicBufMgr {
|
||||
uint32_t max_latency_increase_plus1[STD_VIDEO_H265_SUBLAYERS_LIST_SIZE];
|
||||
uint8_t max_dec_pic_buffering_minus1[STD_VIDEO_H265_SUBLAYERS_LIST_SIZE];
|
||||
uint8_t max_num_reorder_pics[STD_VIDEO_H265_SUBLAYERS_LIST_SIZE];
|
||||
} StdVideoH265DecPicBufMgr;
|
||||
|
||||
typedef struct StdVideoH265SubLayerHrdParameters {
|
||||
uint32_t bit_rate_value_minus1[STD_VIDEO_H265_CPB_CNT_LIST_SIZE];
|
||||
uint32_t cpb_size_value_minus1[STD_VIDEO_H265_CPB_CNT_LIST_SIZE];
|
||||
uint32_t cpb_size_du_value_minus1[STD_VIDEO_H265_CPB_CNT_LIST_SIZE];
|
||||
uint32_t bit_rate_du_value_minus1[STD_VIDEO_H265_CPB_CNT_LIST_SIZE];
|
||||
uint32_t cbr_flag;
|
||||
} StdVideoH265SubLayerHrdParameters;
|
||||
|
||||
typedef struct StdVideoH265HrdFlags {
|
||||
uint32_t nal_hrd_parameters_present_flag : 1;
|
||||
uint32_t vcl_hrd_parameters_present_flag : 1;
|
||||
uint32_t sub_pic_hrd_params_present_flag : 1;
|
||||
uint32_t sub_pic_cpb_params_in_pic_timing_sei_flag : 1;
|
||||
uint32_t fixed_pic_rate_general_flag : 8;
|
||||
uint32_t fixed_pic_rate_within_cvs_flag : 8;
|
||||
uint32_t low_delay_hrd_flag : 8;
|
||||
} StdVideoH265HrdFlags;
|
||||
|
||||
typedef struct StdVideoH265HrdParameters {
|
||||
StdVideoH265HrdFlags flags;
|
||||
uint8_t tick_divisor_minus2;
|
||||
uint8_t du_cpb_removal_delay_increment_length_minus1;
|
||||
uint8_t dpb_output_delay_du_length_minus1;
|
||||
uint8_t bit_rate_scale;
|
||||
uint8_t cpb_size_scale;
|
||||
uint8_t cpb_size_du_scale;
|
||||
uint8_t initial_cpb_removal_delay_length_minus1;
|
||||
uint8_t au_cpb_removal_delay_length_minus1;
|
||||
uint8_t dpb_output_delay_length_minus1;
|
||||
uint8_t cpb_cnt_minus1[STD_VIDEO_H265_SUBLAYERS_LIST_SIZE];
|
||||
uint16_t elemental_duration_in_tc_minus1[STD_VIDEO_H265_SUBLAYERS_LIST_SIZE];
|
||||
uint16_t reserved[3];
|
||||
const StdVideoH265SubLayerHrdParameters* pSubLayerHrdParametersNal;
|
||||
const StdVideoH265SubLayerHrdParameters* pSubLayerHrdParametersVcl;
|
||||
} StdVideoH265HrdParameters;
|
||||
|
||||
typedef struct StdVideoH265VpsFlags {
|
||||
uint32_t vps_temporal_id_nesting_flag : 1;
|
||||
uint32_t vps_sub_layer_ordering_info_present_flag : 1;
|
||||
uint32_t vps_timing_info_present_flag : 1;
|
||||
uint32_t vps_poc_proportional_to_timing_flag : 1;
|
||||
} StdVideoH265VpsFlags;
|
||||
|
||||
typedef struct StdVideoH265ProfileTierLevelFlags {
|
||||
uint32_t general_tier_flag : 1;
|
||||
uint32_t general_progressive_source_flag : 1;
|
||||
uint32_t general_interlaced_source_flag : 1;
|
||||
uint32_t general_non_packed_constraint_flag : 1;
|
||||
uint32_t general_frame_only_constraint_flag : 1;
|
||||
} StdVideoH265ProfileTierLevelFlags;
|
||||
|
||||
typedef struct StdVideoH265ProfileTierLevel {
|
||||
StdVideoH265ProfileTierLevelFlags flags;
|
||||
StdVideoH265ProfileIdc general_profile_idc;
|
||||
StdVideoH265LevelIdc general_level_idc;
|
||||
} StdVideoH265ProfileTierLevel;
|
||||
|
||||
typedef struct StdVideoH265VideoParameterSet {
|
||||
StdVideoH265VpsFlags flags;
|
||||
uint8_t vps_video_parameter_set_id;
|
||||
uint8_t vps_max_sub_layers_minus1;
|
||||
uint8_t reserved1;
|
||||
uint8_t reserved2;
|
||||
uint32_t vps_num_units_in_tick;
|
||||
uint32_t vps_time_scale;
|
||||
uint32_t vps_num_ticks_poc_diff_one_minus1;
|
||||
uint32_t reserved3;
|
||||
const StdVideoH265DecPicBufMgr* pDecPicBufMgr;
|
||||
const StdVideoH265HrdParameters* pHrdParameters;
|
||||
const StdVideoH265ProfileTierLevel* pProfileTierLevel;
|
||||
} StdVideoH265VideoParameterSet;
|
||||
|
||||
typedef struct StdVideoH265ScalingLists {
|
||||
uint8_t ScalingList4x4[STD_VIDEO_H265_SCALING_LIST_4X4_NUM_LISTS][STD_VIDEO_H265_SCALING_LIST_4X4_NUM_ELEMENTS];
|
||||
uint8_t ScalingList8x8[STD_VIDEO_H265_SCALING_LIST_8X8_NUM_LISTS][STD_VIDEO_H265_SCALING_LIST_8X8_NUM_ELEMENTS];
|
||||
uint8_t ScalingList16x16[STD_VIDEO_H265_SCALING_LIST_16X16_NUM_LISTS][STD_VIDEO_H265_SCALING_LIST_16X16_NUM_ELEMENTS];
|
||||
uint8_t ScalingList32x32[STD_VIDEO_H265_SCALING_LIST_32X32_NUM_LISTS][STD_VIDEO_H265_SCALING_LIST_32X32_NUM_ELEMENTS];
|
||||
uint8_t ScalingListDCCoef16x16[STD_VIDEO_H265_SCALING_LIST_16X16_NUM_LISTS];
|
||||
uint8_t ScalingListDCCoef32x32[STD_VIDEO_H265_SCALING_LIST_32X32_NUM_LISTS];
|
||||
} StdVideoH265ScalingLists;
|
||||
|
||||
typedef struct StdVideoH265SpsVuiFlags {
|
||||
uint32_t aspect_ratio_info_present_flag : 1;
|
||||
uint32_t overscan_info_present_flag : 1;
|
||||
uint32_t overscan_appropriate_flag : 1;
|
||||
uint32_t video_signal_type_present_flag : 1;
|
||||
uint32_t video_full_range_flag : 1;
|
||||
uint32_t colour_description_present_flag : 1;
|
||||
uint32_t chroma_loc_info_present_flag : 1;
|
||||
uint32_t neutral_chroma_indication_flag : 1;
|
||||
uint32_t field_seq_flag : 1;
|
||||
uint32_t frame_field_info_present_flag : 1;
|
||||
uint32_t default_display_window_flag : 1;
|
||||
uint32_t vui_timing_info_present_flag : 1;
|
||||
uint32_t vui_poc_proportional_to_timing_flag : 1;
|
||||
uint32_t vui_hrd_parameters_present_flag : 1;
|
||||
uint32_t bitstream_restriction_flag : 1;
|
||||
uint32_t tiles_fixed_structure_flag : 1;
|
||||
uint32_t motion_vectors_over_pic_boundaries_flag : 1;
|
||||
uint32_t restricted_ref_pic_lists_flag : 1;
|
||||
} StdVideoH265SpsVuiFlags;
|
||||
|
||||
typedef struct StdVideoH265SequenceParameterSetVui {
|
||||
StdVideoH265SpsVuiFlags flags;
|
||||
StdVideoH265AspectRatioIdc aspect_ratio_idc;
|
||||
uint16_t sar_width;
|
||||
uint16_t sar_height;
|
||||
uint8_t video_format;
|
||||
uint8_t colour_primaries;
|
||||
uint8_t transfer_characteristics;
|
||||
uint8_t matrix_coeffs;
|
||||
uint8_t chroma_sample_loc_type_top_field;
|
||||
uint8_t chroma_sample_loc_type_bottom_field;
|
||||
uint8_t reserved1;
|
||||
uint8_t reserved2;
|
||||
uint16_t def_disp_win_left_offset;
|
||||
uint16_t def_disp_win_right_offset;
|
||||
uint16_t def_disp_win_top_offset;
|
||||
uint16_t def_disp_win_bottom_offset;
|
||||
uint32_t vui_num_units_in_tick;
|
||||
uint32_t vui_time_scale;
|
||||
uint32_t vui_num_ticks_poc_diff_one_minus1;
|
||||
uint16_t min_spatial_segmentation_idc;
|
||||
uint16_t reserved3;
|
||||
uint8_t max_bytes_per_pic_denom;
|
||||
uint8_t max_bits_per_min_cu_denom;
|
||||
uint8_t log2_max_mv_length_horizontal;
|
||||
uint8_t log2_max_mv_length_vertical;
|
||||
const StdVideoH265HrdParameters* pHrdParameters;
|
||||
} StdVideoH265SequenceParameterSetVui;
|
||||
|
||||
typedef struct StdVideoH265PredictorPaletteEntries {
|
||||
uint16_t PredictorPaletteEntries[STD_VIDEO_H265_PREDICTOR_PALETTE_COMPONENTS_LIST_SIZE][STD_VIDEO_H265_PREDICTOR_PALETTE_COMP_ENTRIES_LIST_SIZE];
|
||||
} StdVideoH265PredictorPaletteEntries;
|
||||
|
||||
typedef struct StdVideoH265SpsFlags {
|
||||
uint32_t sps_temporal_id_nesting_flag : 1;
|
||||
uint32_t separate_colour_plane_flag : 1;
|
||||
uint32_t conformance_window_flag : 1;
|
||||
uint32_t sps_sub_layer_ordering_info_present_flag : 1;
|
||||
uint32_t scaling_list_enabled_flag : 1;
|
||||
uint32_t sps_scaling_list_data_present_flag : 1;
|
||||
uint32_t amp_enabled_flag : 1;
|
||||
uint32_t sample_adaptive_offset_enabled_flag : 1;
|
||||
uint32_t pcm_enabled_flag : 1;
|
||||
uint32_t pcm_loop_filter_disabled_flag : 1;
|
||||
uint32_t long_term_ref_pics_present_flag : 1;
|
||||
uint32_t sps_temporal_mvp_enabled_flag : 1;
|
||||
uint32_t strong_intra_smoothing_enabled_flag : 1;
|
||||
uint32_t vui_parameters_present_flag : 1;
|
||||
uint32_t sps_extension_present_flag : 1;
|
||||
uint32_t sps_range_extension_flag : 1;
|
||||
uint32_t transform_skip_rotation_enabled_flag : 1;
|
||||
uint32_t transform_skip_context_enabled_flag : 1;
|
||||
uint32_t implicit_rdpcm_enabled_flag : 1;
|
||||
uint32_t explicit_rdpcm_enabled_flag : 1;
|
||||
uint32_t extended_precision_processing_flag : 1;
|
||||
uint32_t intra_smoothing_disabled_flag : 1;
|
||||
uint32_t high_precision_offsets_enabled_flag : 1;
|
||||
uint32_t persistent_rice_adaptation_enabled_flag : 1;
|
||||
uint32_t cabac_bypass_alignment_enabled_flag : 1;
|
||||
uint32_t sps_scc_extension_flag : 1;
|
||||
uint32_t sps_curr_pic_ref_enabled_flag : 1;
|
||||
uint32_t palette_mode_enabled_flag : 1;
|
||||
uint32_t sps_palette_predictor_initializers_present_flag : 1;
|
||||
uint32_t intra_boundary_filtering_disabled_flag : 1;
|
||||
} StdVideoH265SpsFlags;
|
||||
|
||||
typedef struct StdVideoH265ShortTermRefPicSetFlags {
|
||||
uint32_t inter_ref_pic_set_prediction_flag : 1;
|
||||
uint32_t delta_rps_sign : 1;
|
||||
} StdVideoH265ShortTermRefPicSetFlags;
|
||||
|
||||
typedef struct StdVideoH265ShortTermRefPicSet {
|
||||
StdVideoH265ShortTermRefPicSetFlags flags;
|
||||
uint32_t delta_idx_minus1;
|
||||
uint16_t use_delta_flag;
|
||||
uint16_t abs_delta_rps_minus1;
|
||||
uint16_t used_by_curr_pic_flag;
|
||||
uint16_t used_by_curr_pic_s0_flag;
|
||||
uint16_t used_by_curr_pic_s1_flag;
|
||||
uint16_t reserved1;
|
||||
uint8_t reserved2;
|
||||
uint8_t reserved3;
|
||||
uint8_t num_negative_pics;
|
||||
uint8_t num_positive_pics;
|
||||
uint16_t delta_poc_s0_minus1[STD_VIDEO_H265_MAX_DPB_SIZE];
|
||||
uint16_t delta_poc_s1_minus1[STD_VIDEO_H265_MAX_DPB_SIZE];
|
||||
} StdVideoH265ShortTermRefPicSet;
|
||||
|
||||
typedef struct StdVideoH265LongTermRefPicsSps {
|
||||
uint32_t used_by_curr_pic_lt_sps_flag;
|
||||
uint32_t lt_ref_pic_poc_lsb_sps[STD_VIDEO_H265_MAX_LONG_TERM_REF_PICS_SPS];
|
||||
} StdVideoH265LongTermRefPicsSps;
|
||||
|
||||
typedef struct StdVideoH265SequenceParameterSet {
|
||||
StdVideoH265SpsFlags flags;
|
||||
StdVideoH265ChromaFormatIdc chroma_format_idc;
|
||||
uint32_t pic_width_in_luma_samples;
|
||||
uint32_t pic_height_in_luma_samples;
|
||||
uint8_t sps_video_parameter_set_id;
|
||||
uint8_t sps_max_sub_layers_minus1;
|
||||
uint8_t sps_seq_parameter_set_id;
|
||||
uint8_t bit_depth_luma_minus8;
|
||||
uint8_t bit_depth_chroma_minus8;
|
||||
uint8_t log2_max_pic_order_cnt_lsb_minus4;
|
||||
uint8_t log2_min_luma_coding_block_size_minus3;
|
||||
uint8_t log2_diff_max_min_luma_coding_block_size;
|
||||
uint8_t log2_min_luma_transform_block_size_minus2;
|
||||
uint8_t log2_diff_max_min_luma_transform_block_size;
|
||||
uint8_t max_transform_hierarchy_depth_inter;
|
||||
uint8_t max_transform_hierarchy_depth_intra;
|
||||
uint8_t num_short_term_ref_pic_sets;
|
||||
uint8_t num_long_term_ref_pics_sps;
|
||||
uint8_t pcm_sample_bit_depth_luma_minus1;
|
||||
uint8_t pcm_sample_bit_depth_chroma_minus1;
|
||||
uint8_t log2_min_pcm_luma_coding_block_size_minus3;
|
||||
uint8_t log2_diff_max_min_pcm_luma_coding_block_size;
|
||||
uint8_t reserved1;
|
||||
uint8_t reserved2;
|
||||
uint8_t palette_max_size;
|
||||
uint8_t delta_palette_max_predictor_size;
|
||||
uint8_t motion_vector_resolution_control_idc;
|
||||
uint8_t sps_num_palette_predictor_initializers_minus1;
|
||||
uint32_t conf_win_left_offset;
|
||||
uint32_t conf_win_right_offset;
|
||||
uint32_t conf_win_top_offset;
|
||||
uint32_t conf_win_bottom_offset;
|
||||
const StdVideoH265ProfileTierLevel* pProfileTierLevel;
|
||||
const StdVideoH265DecPicBufMgr* pDecPicBufMgr;
|
||||
const StdVideoH265ScalingLists* pScalingLists;
|
||||
const StdVideoH265ShortTermRefPicSet* pShortTermRefPicSet;
|
||||
const StdVideoH265LongTermRefPicsSps* pLongTermRefPicsSps;
|
||||
const StdVideoH265SequenceParameterSetVui* pSequenceParameterSetVui;
|
||||
const StdVideoH265PredictorPaletteEntries* pPredictorPaletteEntries;
|
||||
} StdVideoH265SequenceParameterSet;
|
||||
|
||||
typedef struct StdVideoH265PpsFlags {
|
||||
uint32_t dependent_slice_segments_enabled_flag : 1;
|
||||
uint32_t output_flag_present_flag : 1;
|
||||
uint32_t sign_data_hiding_enabled_flag : 1;
|
||||
uint32_t cabac_init_present_flag : 1;
|
||||
uint32_t constrained_intra_pred_flag : 1;
|
||||
uint32_t transform_skip_enabled_flag : 1;
|
||||
uint32_t cu_qp_delta_enabled_flag : 1;
|
||||
uint32_t pps_slice_chroma_qp_offsets_present_flag : 1;
|
||||
uint32_t weighted_pred_flag : 1;
|
||||
uint32_t weighted_bipred_flag : 1;
|
||||
uint32_t transquant_bypass_enabled_flag : 1;
|
||||
uint32_t tiles_enabled_flag : 1;
|
||||
uint32_t entropy_coding_sync_enabled_flag : 1;
|
||||
uint32_t uniform_spacing_flag : 1;
|
||||
uint32_t loop_filter_across_tiles_enabled_flag : 1;
|
||||
uint32_t pps_loop_filter_across_slices_enabled_flag : 1;
|
||||
uint32_t deblocking_filter_control_present_flag : 1;
|
||||
uint32_t deblocking_filter_override_enabled_flag : 1;
|
||||
uint32_t pps_deblocking_filter_disabled_flag : 1;
|
||||
uint32_t pps_scaling_list_data_present_flag : 1;
|
||||
uint32_t lists_modification_present_flag : 1;
|
||||
uint32_t slice_segment_header_extension_present_flag : 1;
|
||||
uint32_t pps_extension_present_flag : 1;
|
||||
uint32_t cross_component_prediction_enabled_flag : 1;
|
||||
uint32_t chroma_qp_offset_list_enabled_flag : 1;
|
||||
uint32_t pps_curr_pic_ref_enabled_flag : 1;
|
||||
uint32_t residual_adaptive_colour_transform_enabled_flag : 1;
|
||||
uint32_t pps_slice_act_qp_offsets_present_flag : 1;
|
||||
uint32_t pps_palette_predictor_initializers_present_flag : 1;
|
||||
uint32_t monochrome_palette_flag : 1;
|
||||
uint32_t pps_range_extension_flag : 1;
|
||||
} StdVideoH265PpsFlags;
|
||||
|
||||
typedef struct StdVideoH265PictureParameterSet {
|
||||
StdVideoH265PpsFlags flags;
|
||||
uint8_t pps_pic_parameter_set_id;
|
||||
uint8_t pps_seq_parameter_set_id;
|
||||
uint8_t sps_video_parameter_set_id;
|
||||
uint8_t num_extra_slice_header_bits;
|
||||
uint8_t num_ref_idx_l0_default_active_minus1;
|
||||
uint8_t num_ref_idx_l1_default_active_minus1;
|
||||
int8_t init_qp_minus26;
|
||||
uint8_t diff_cu_qp_delta_depth;
|
||||
int8_t pps_cb_qp_offset;
|
||||
int8_t pps_cr_qp_offset;
|
||||
int8_t pps_beta_offset_div2;
|
||||
int8_t pps_tc_offset_div2;
|
||||
uint8_t log2_parallel_merge_level_minus2;
|
||||
uint8_t log2_max_transform_skip_block_size_minus2;
|
||||
uint8_t diff_cu_chroma_qp_offset_depth;
|
||||
uint8_t chroma_qp_offset_list_len_minus1;
|
||||
int8_t cb_qp_offset_list[STD_VIDEO_H265_CHROMA_QP_OFFSET_LIST_SIZE];
|
||||
int8_t cr_qp_offset_list[STD_VIDEO_H265_CHROMA_QP_OFFSET_LIST_SIZE];
|
||||
uint8_t log2_sao_offset_scale_luma;
|
||||
uint8_t log2_sao_offset_scale_chroma;
|
||||
int8_t pps_act_y_qp_offset_plus5;
|
||||
int8_t pps_act_cb_qp_offset_plus5;
|
||||
int8_t pps_act_cr_qp_offset_plus3;
|
||||
uint8_t pps_num_palette_predictor_initializers;
|
||||
uint8_t luma_bit_depth_entry_minus8;
|
||||
uint8_t chroma_bit_depth_entry_minus8;
|
||||
uint8_t num_tile_columns_minus1;
|
||||
uint8_t num_tile_rows_minus1;
|
||||
uint8_t reserved1;
|
||||
uint8_t reserved2;
|
||||
uint16_t column_width_minus1[STD_VIDEO_H265_CHROMA_QP_OFFSET_TILE_COLS_LIST_SIZE];
|
||||
uint16_t row_height_minus1[STD_VIDEO_H265_CHROMA_QP_OFFSET_TILE_ROWS_LIST_SIZE];
|
||||
uint32_t reserved3;
|
||||
const StdVideoH265ScalingLists* pScalingLists;
|
||||
const StdVideoH265PredictorPaletteEntries* pPredictorPaletteEntries;
|
||||
} StdVideoH265PictureParameterSet;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
#ifndef VULKAN_VIDEO_CODEC_H265STD_DECODE_H_
|
||||
#define VULKAN_VIDEO_CODEC_H265STD_DECODE_H_ 1
|
||||
|
||||
/*
|
||||
** Copyright 2015-2026 The Khronos Group Inc.
|
||||
**
|
||||
** SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
** This header is generated from the Khronos Vulkan XML API Registry.
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// vulkan_video_codec_h265std_decode is a preprocessor guard. Do not pass it to API calls.
|
||||
#define vulkan_video_codec_h265std_decode 1
|
||||
#include "vulkan_video_codec_h265std.h"
|
||||
|
||||
#define VK_STD_VULKAN_VIDEO_CODEC_H265_DECODE_API_VERSION_1_0_0 VK_MAKE_VIDEO_STD_VERSION(1, 0, 0)
|
||||
|
||||
#define VK_STD_VULKAN_VIDEO_CODEC_H265_DECODE_SPEC_VERSION VK_STD_VULKAN_VIDEO_CODEC_H265_DECODE_API_VERSION_1_0_0
|
||||
#define VK_STD_VULKAN_VIDEO_CODEC_H265_DECODE_EXTENSION_NAME "VK_STD_vulkan_video_codec_h265_decode"
|
||||
#define STD_VIDEO_DECODE_H265_REF_PIC_SET_LIST_SIZE 8U
|
||||
typedef struct StdVideoDecodeH265PictureInfoFlags {
|
||||
uint32_t IrapPicFlag : 1;
|
||||
uint32_t IdrPicFlag : 1;
|
||||
uint32_t IsReference : 1;
|
||||
uint32_t short_term_ref_pic_set_sps_flag : 1;
|
||||
} StdVideoDecodeH265PictureInfoFlags;
|
||||
|
||||
typedef struct StdVideoDecodeH265PictureInfo {
|
||||
StdVideoDecodeH265PictureInfoFlags flags;
|
||||
uint8_t sps_video_parameter_set_id;
|
||||
uint8_t pps_seq_parameter_set_id;
|
||||
uint8_t pps_pic_parameter_set_id;
|
||||
uint8_t NumDeltaPocsOfRefRpsIdx;
|
||||
int32_t PicOrderCntVal;
|
||||
uint16_t NumBitsForSTRefPicSetInSlice;
|
||||
uint16_t reserved;
|
||||
uint8_t RefPicSetStCurrBefore[STD_VIDEO_DECODE_H265_REF_PIC_SET_LIST_SIZE];
|
||||
uint8_t RefPicSetStCurrAfter[STD_VIDEO_DECODE_H265_REF_PIC_SET_LIST_SIZE];
|
||||
uint8_t RefPicSetLtCurr[STD_VIDEO_DECODE_H265_REF_PIC_SET_LIST_SIZE];
|
||||
} StdVideoDecodeH265PictureInfo;
|
||||
|
||||
typedef struct StdVideoDecodeH265ReferenceInfoFlags {
|
||||
uint32_t used_for_long_term_reference : 1;
|
||||
uint32_t unused_for_reference : 1;
|
||||
} StdVideoDecodeH265ReferenceInfoFlags;
|
||||
|
||||
typedef struct StdVideoDecodeH265ReferenceInfo {
|
||||
StdVideoDecodeH265ReferenceInfoFlags flags;
|
||||
int32_t PicOrderCntVal;
|
||||
} StdVideoDecodeH265ReferenceInfo;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
#ifndef VULKAN_VIDEO_CODEC_H265STD_ENCODE_H_
|
||||
#define VULKAN_VIDEO_CODEC_H265STD_ENCODE_H_ 1
|
||||
|
||||
/*
|
||||
** Copyright 2015-2026 The Khronos Group Inc.
|
||||
**
|
||||
** SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
** This header is generated from the Khronos Vulkan XML API Registry.
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// vulkan_video_codec_h265std_encode is a preprocessor guard. Do not pass it to API calls.
|
||||
#define vulkan_video_codec_h265std_encode 1
|
||||
#include "vulkan_video_codec_h265std.h"
|
||||
|
||||
#define VK_STD_VULKAN_VIDEO_CODEC_H265_ENCODE_API_VERSION_1_0_0 VK_MAKE_VIDEO_STD_VERSION(1, 0, 0)
|
||||
|
||||
#define VK_STD_VULKAN_VIDEO_CODEC_H265_ENCODE_SPEC_VERSION VK_STD_VULKAN_VIDEO_CODEC_H265_ENCODE_API_VERSION_1_0_0
|
||||
#define VK_STD_VULKAN_VIDEO_CODEC_H265_ENCODE_EXTENSION_NAME "VK_STD_vulkan_video_codec_h265_encode"
|
||||
typedef struct StdVideoEncodeH265WeightTableFlags {
|
||||
uint16_t luma_weight_l0_flag;
|
||||
uint16_t chroma_weight_l0_flag;
|
||||
uint16_t luma_weight_l1_flag;
|
||||
uint16_t chroma_weight_l1_flag;
|
||||
} StdVideoEncodeH265WeightTableFlags;
|
||||
|
||||
typedef struct StdVideoEncodeH265WeightTable {
|
||||
StdVideoEncodeH265WeightTableFlags flags;
|
||||
uint8_t luma_log2_weight_denom;
|
||||
int8_t delta_chroma_log2_weight_denom;
|
||||
int8_t delta_luma_weight_l0[STD_VIDEO_H265_MAX_NUM_LIST_REF];
|
||||
int8_t luma_offset_l0[STD_VIDEO_H265_MAX_NUM_LIST_REF];
|
||||
int8_t delta_chroma_weight_l0[STD_VIDEO_H265_MAX_NUM_LIST_REF][STD_VIDEO_H265_MAX_CHROMA_PLANES];
|
||||
int8_t delta_chroma_offset_l0[STD_VIDEO_H265_MAX_NUM_LIST_REF][STD_VIDEO_H265_MAX_CHROMA_PLANES];
|
||||
int8_t delta_luma_weight_l1[STD_VIDEO_H265_MAX_NUM_LIST_REF];
|
||||
int8_t luma_offset_l1[STD_VIDEO_H265_MAX_NUM_LIST_REF];
|
||||
int8_t delta_chroma_weight_l1[STD_VIDEO_H265_MAX_NUM_LIST_REF][STD_VIDEO_H265_MAX_CHROMA_PLANES];
|
||||
int8_t delta_chroma_offset_l1[STD_VIDEO_H265_MAX_NUM_LIST_REF][STD_VIDEO_H265_MAX_CHROMA_PLANES];
|
||||
} StdVideoEncodeH265WeightTable;
|
||||
|
||||
typedef struct StdVideoEncodeH265SliceSegmentHeaderFlags {
|
||||
uint32_t first_slice_segment_in_pic_flag : 1;
|
||||
uint32_t dependent_slice_segment_flag : 1;
|
||||
uint32_t slice_sao_luma_flag : 1;
|
||||
uint32_t slice_sao_chroma_flag : 1;
|
||||
uint32_t num_ref_idx_active_override_flag : 1;
|
||||
uint32_t mvd_l1_zero_flag : 1;
|
||||
uint32_t cabac_init_flag : 1;
|
||||
uint32_t cu_chroma_qp_offset_enabled_flag : 1;
|
||||
uint32_t deblocking_filter_override_flag : 1;
|
||||
uint32_t slice_deblocking_filter_disabled_flag : 1;
|
||||
uint32_t collocated_from_l0_flag : 1;
|
||||
uint32_t slice_loop_filter_across_slices_enabled_flag : 1;
|
||||
uint32_t reserved : 20;
|
||||
} StdVideoEncodeH265SliceSegmentHeaderFlags;
|
||||
|
||||
typedef struct StdVideoEncodeH265SliceSegmentHeader {
|
||||
StdVideoEncodeH265SliceSegmentHeaderFlags flags;
|
||||
StdVideoH265SliceType slice_type;
|
||||
uint32_t slice_segment_address;
|
||||
uint8_t collocated_ref_idx;
|
||||
uint8_t MaxNumMergeCand;
|
||||
int8_t slice_cb_qp_offset;
|
||||
int8_t slice_cr_qp_offset;
|
||||
int8_t slice_beta_offset_div2;
|
||||
int8_t slice_tc_offset_div2;
|
||||
int8_t slice_act_y_qp_offset;
|
||||
int8_t slice_act_cb_qp_offset;
|
||||
int8_t slice_act_cr_qp_offset;
|
||||
int8_t slice_qp_delta;
|
||||
uint16_t reserved1;
|
||||
const StdVideoEncodeH265WeightTable* pWeightTable;
|
||||
} StdVideoEncodeH265SliceSegmentHeader;
|
||||
|
||||
typedef struct StdVideoEncodeH265ReferenceListsInfoFlags {
|
||||
uint32_t ref_pic_list_modification_flag_l0 : 1;
|
||||
uint32_t ref_pic_list_modification_flag_l1 : 1;
|
||||
uint32_t reserved : 30;
|
||||
} StdVideoEncodeH265ReferenceListsInfoFlags;
|
||||
|
||||
typedef struct StdVideoEncodeH265ReferenceListsInfo {
|
||||
StdVideoEncodeH265ReferenceListsInfoFlags flags;
|
||||
uint8_t num_ref_idx_l0_active_minus1;
|
||||
uint8_t num_ref_idx_l1_active_minus1;
|
||||
uint8_t RefPicList0[STD_VIDEO_H265_MAX_NUM_LIST_REF];
|
||||
uint8_t RefPicList1[STD_VIDEO_H265_MAX_NUM_LIST_REF];
|
||||
uint8_t list_entry_l0[STD_VIDEO_H265_MAX_NUM_LIST_REF];
|
||||
uint8_t list_entry_l1[STD_VIDEO_H265_MAX_NUM_LIST_REF];
|
||||
} StdVideoEncodeH265ReferenceListsInfo;
|
||||
|
||||
typedef struct StdVideoEncodeH265PictureInfoFlags {
|
||||
uint32_t is_reference : 1;
|
||||
uint32_t IrapPicFlag : 1;
|
||||
uint32_t used_for_long_term_reference : 1;
|
||||
uint32_t discardable_flag : 1;
|
||||
uint32_t cross_layer_bla_flag : 1;
|
||||
uint32_t pic_output_flag : 1;
|
||||
uint32_t no_output_of_prior_pics_flag : 1;
|
||||
uint32_t short_term_ref_pic_set_sps_flag : 1;
|
||||
uint32_t slice_temporal_mvp_enabled_flag : 1;
|
||||
uint32_t reserved : 23;
|
||||
} StdVideoEncodeH265PictureInfoFlags;
|
||||
|
||||
typedef struct StdVideoEncodeH265LongTermRefPics {
|
||||
uint8_t num_long_term_sps;
|
||||
uint8_t num_long_term_pics;
|
||||
uint8_t lt_idx_sps[STD_VIDEO_H265_MAX_LONG_TERM_REF_PICS_SPS];
|
||||
uint8_t poc_lsb_lt[STD_VIDEO_H265_MAX_LONG_TERM_PICS];
|
||||
uint16_t used_by_curr_pic_lt_flag;
|
||||
uint8_t delta_poc_msb_present_flag[STD_VIDEO_H265_MAX_DELTA_POC];
|
||||
uint8_t delta_poc_msb_cycle_lt[STD_VIDEO_H265_MAX_DELTA_POC];
|
||||
} StdVideoEncodeH265LongTermRefPics;
|
||||
|
||||
typedef struct StdVideoEncodeH265PictureInfo {
|
||||
StdVideoEncodeH265PictureInfoFlags flags;
|
||||
StdVideoH265PictureType pic_type;
|
||||
uint8_t sps_video_parameter_set_id;
|
||||
uint8_t pps_seq_parameter_set_id;
|
||||
uint8_t pps_pic_parameter_set_id;
|
||||
uint8_t short_term_ref_pic_set_idx;
|
||||
int32_t PicOrderCntVal;
|
||||
uint8_t TemporalId;
|
||||
uint8_t reserved1[7];
|
||||
const StdVideoEncodeH265ReferenceListsInfo* pRefLists;
|
||||
const StdVideoH265ShortTermRefPicSet* pShortTermRefPicSet;
|
||||
const StdVideoEncodeH265LongTermRefPics* pLongTermRefPics;
|
||||
} StdVideoEncodeH265PictureInfo;
|
||||
|
||||
typedef struct StdVideoEncodeH265ReferenceInfoFlags {
|
||||
uint32_t used_for_long_term_reference : 1;
|
||||
uint32_t unused_for_reference : 1;
|
||||
uint32_t reserved : 30;
|
||||
} StdVideoEncodeH265ReferenceInfoFlags;
|
||||
|
||||
typedef struct StdVideoEncodeH265ReferenceInfo {
|
||||
StdVideoEncodeH265ReferenceInfoFlags flags;
|
||||
StdVideoH265PictureType pic_type;
|
||||
int32_t PicOrderCntVal;
|
||||
uint8_t TemporalId;
|
||||
} StdVideoEncodeH265ReferenceInfo;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Vendored
+151
@@ -0,0 +1,151 @@
|
||||
#ifndef VULKAN_VIDEO_CODEC_VP9STD_H_
|
||||
#define VULKAN_VIDEO_CODEC_VP9STD_H_ 1
|
||||
|
||||
/*
|
||||
** Copyright 2015-2026 The Khronos Group Inc.
|
||||
**
|
||||
** SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
** This header is generated from the Khronos Vulkan XML API Registry.
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// vulkan_video_codec_vp9std is a preprocessor guard. Do not pass it to API calls.
|
||||
#define vulkan_video_codec_vp9std 1
|
||||
#include "vulkan_video_codecs_common.h"
|
||||
#define STD_VIDEO_VP9_NUM_REF_FRAMES 8U
|
||||
#define STD_VIDEO_VP9_REFS_PER_FRAME 3U
|
||||
#define STD_VIDEO_VP9_MAX_REF_FRAMES 4U
|
||||
#define STD_VIDEO_VP9_LOOP_FILTER_ADJUSTMENTS 2U
|
||||
#define STD_VIDEO_VP9_MAX_SEGMENTS 8U
|
||||
#define STD_VIDEO_VP9_SEG_LVL_MAX 4U
|
||||
#define STD_VIDEO_VP9_MAX_SEGMENTATION_TREE_PROBS 7U
|
||||
#define STD_VIDEO_VP9_MAX_SEGMENTATION_PRED_PROB 3U
|
||||
|
||||
typedef enum StdVideoVP9Profile {
|
||||
STD_VIDEO_VP9_PROFILE_0 = 0,
|
||||
STD_VIDEO_VP9_PROFILE_1 = 1,
|
||||
STD_VIDEO_VP9_PROFILE_2 = 2,
|
||||
STD_VIDEO_VP9_PROFILE_3 = 3,
|
||||
STD_VIDEO_VP9_PROFILE_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_VP9_PROFILE_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoVP9Profile;
|
||||
|
||||
typedef enum StdVideoVP9Level {
|
||||
STD_VIDEO_VP9_LEVEL_1_0 = 0,
|
||||
STD_VIDEO_VP9_LEVEL_1_1 = 1,
|
||||
STD_VIDEO_VP9_LEVEL_2_0 = 2,
|
||||
STD_VIDEO_VP9_LEVEL_2_1 = 3,
|
||||
STD_VIDEO_VP9_LEVEL_3_0 = 4,
|
||||
STD_VIDEO_VP9_LEVEL_3_1 = 5,
|
||||
STD_VIDEO_VP9_LEVEL_4_0 = 6,
|
||||
STD_VIDEO_VP9_LEVEL_4_1 = 7,
|
||||
STD_VIDEO_VP9_LEVEL_5_0 = 8,
|
||||
STD_VIDEO_VP9_LEVEL_5_1 = 9,
|
||||
STD_VIDEO_VP9_LEVEL_5_2 = 10,
|
||||
STD_VIDEO_VP9_LEVEL_6_0 = 11,
|
||||
STD_VIDEO_VP9_LEVEL_6_1 = 12,
|
||||
STD_VIDEO_VP9_LEVEL_6_2 = 13,
|
||||
STD_VIDEO_VP9_LEVEL_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_VP9_LEVEL_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoVP9Level;
|
||||
|
||||
typedef enum StdVideoVP9FrameType {
|
||||
STD_VIDEO_VP9_FRAME_TYPE_KEY = 0,
|
||||
STD_VIDEO_VP9_FRAME_TYPE_NON_KEY = 1,
|
||||
STD_VIDEO_VP9_FRAME_TYPE_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_VP9_FRAME_TYPE_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoVP9FrameType;
|
||||
|
||||
typedef enum StdVideoVP9ReferenceName {
|
||||
STD_VIDEO_VP9_REFERENCE_NAME_INTRA_FRAME = 0,
|
||||
STD_VIDEO_VP9_REFERENCE_NAME_LAST_FRAME = 1,
|
||||
STD_VIDEO_VP9_REFERENCE_NAME_GOLDEN_FRAME = 2,
|
||||
STD_VIDEO_VP9_REFERENCE_NAME_ALTREF_FRAME = 3,
|
||||
STD_VIDEO_VP9_REFERENCE_NAME_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_VP9_REFERENCE_NAME_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoVP9ReferenceName;
|
||||
|
||||
typedef enum StdVideoVP9InterpolationFilter {
|
||||
STD_VIDEO_VP9_INTERPOLATION_FILTER_EIGHTTAP = 0,
|
||||
STD_VIDEO_VP9_INTERPOLATION_FILTER_EIGHTTAP_SMOOTH = 1,
|
||||
STD_VIDEO_VP9_INTERPOLATION_FILTER_EIGHTTAP_SHARP = 2,
|
||||
STD_VIDEO_VP9_INTERPOLATION_FILTER_BILINEAR = 3,
|
||||
STD_VIDEO_VP9_INTERPOLATION_FILTER_SWITCHABLE = 4,
|
||||
STD_VIDEO_VP9_INTERPOLATION_FILTER_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_VP9_INTERPOLATION_FILTER_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoVP9InterpolationFilter;
|
||||
|
||||
typedef enum StdVideoVP9ColorSpace {
|
||||
STD_VIDEO_VP9_COLOR_SPACE_UNKNOWN = 0,
|
||||
STD_VIDEO_VP9_COLOR_SPACE_BT_601 = 1,
|
||||
STD_VIDEO_VP9_COLOR_SPACE_BT_709 = 2,
|
||||
STD_VIDEO_VP9_COLOR_SPACE_SMPTE_170 = 3,
|
||||
STD_VIDEO_VP9_COLOR_SPACE_SMPTE_240 = 4,
|
||||
STD_VIDEO_VP9_COLOR_SPACE_BT_2020 = 5,
|
||||
STD_VIDEO_VP9_COLOR_SPACE_RESERVED = 6,
|
||||
STD_VIDEO_VP9_COLOR_SPACE_RGB = 7,
|
||||
STD_VIDEO_VP9_COLOR_SPACE_INVALID = 0x7FFFFFFF,
|
||||
STD_VIDEO_VP9_COLOR_SPACE_MAX_ENUM = 0x7FFFFFFF
|
||||
} StdVideoVP9ColorSpace;
|
||||
typedef struct StdVideoVP9ColorConfigFlags {
|
||||
uint32_t color_range : 1;
|
||||
uint32_t reserved : 31;
|
||||
} StdVideoVP9ColorConfigFlags;
|
||||
|
||||
typedef struct StdVideoVP9ColorConfig {
|
||||
StdVideoVP9ColorConfigFlags flags;
|
||||
uint8_t BitDepth;
|
||||
uint8_t subsampling_x;
|
||||
uint8_t subsampling_y;
|
||||
uint8_t reserved1;
|
||||
StdVideoVP9ColorSpace color_space;
|
||||
} StdVideoVP9ColorConfig;
|
||||
|
||||
typedef struct StdVideoVP9LoopFilterFlags {
|
||||
uint32_t loop_filter_delta_enabled : 1;
|
||||
uint32_t loop_filter_delta_update : 1;
|
||||
uint32_t reserved : 30;
|
||||
} StdVideoVP9LoopFilterFlags;
|
||||
|
||||
typedef struct StdVideoVP9LoopFilter {
|
||||
StdVideoVP9LoopFilterFlags flags;
|
||||
uint8_t loop_filter_level;
|
||||
uint8_t loop_filter_sharpness;
|
||||
uint8_t update_ref_delta;
|
||||
int8_t loop_filter_ref_deltas[STD_VIDEO_VP9_MAX_REF_FRAMES];
|
||||
uint8_t update_mode_delta;
|
||||
int8_t loop_filter_mode_deltas[STD_VIDEO_VP9_LOOP_FILTER_ADJUSTMENTS];
|
||||
} StdVideoVP9LoopFilter;
|
||||
|
||||
typedef struct StdVideoVP9SegmentationFlags {
|
||||
uint32_t segmentation_update_map : 1;
|
||||
uint32_t segmentation_temporal_update : 1;
|
||||
uint32_t segmentation_update_data : 1;
|
||||
uint32_t segmentation_abs_or_delta_update : 1;
|
||||
uint32_t reserved : 28;
|
||||
} StdVideoVP9SegmentationFlags;
|
||||
|
||||
typedef struct StdVideoVP9Segmentation {
|
||||
StdVideoVP9SegmentationFlags flags;
|
||||
uint8_t segmentation_tree_probs[STD_VIDEO_VP9_MAX_SEGMENTATION_TREE_PROBS];
|
||||
uint8_t segmentation_pred_prob[STD_VIDEO_VP9_MAX_SEGMENTATION_PRED_PROB];
|
||||
uint8_t FeatureEnabled[STD_VIDEO_VP9_MAX_SEGMENTS];
|
||||
int16_t FeatureData[STD_VIDEO_VP9_MAX_SEGMENTS][STD_VIDEO_VP9_SEG_LVL_MAX];
|
||||
} StdVideoVP9Segmentation;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Loaded 100 of 125 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user