Files
DroidDeck/tools/vr/src/presenter.cpp
T

797 lines
42 KiB
C++

#define VK_NO_PROTOTYPES
#include <vulkan/vulkan_core.h>
typedef struct _XDisplay Display;
typedef unsigned long Window;
typedef unsigned long VisualID;
#include <vulkan/vulkan_xlib.h>
#include <wayland-client.h>
#include <vulkan/vulkan_wayland.h>
#include "xdg-shell-client-protocol.h"
#include "presenter.h"
#include "xr_tracking_file.h"
#include "xr_frame_channel.h"
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <dlfcn.h>
#include <fcntl.h>
#include <mutex>
#include <sys/ioctl.h>
#include <thread>
#include <unistd.h>
#include <vector>
#include "openvr_driver.h"
namespace droiddeck {
namespace {
void Log(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
void Log(const char *fmt, ...) {
char buf[1024];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
if (vr::VRDriverContext() && vr::VRDriverLog()) vr::VRDriverLog()->Log(buf);
fprintf(stderr, "[droiddeck] %s\n", buf);
}
struct X11 {
void *lib = nullptr;
Display *(*OpenDisplay)(const char *);
Window (*DefaultRootWindow)(Display *);
int (*DefaultScreen)(Display *);
int (*DisplayWidth)(Display *, int);
int (*DisplayHeight)(Display *, int);
Window (*CreateSimpleWindow)(Display *, Window, int, int, unsigned, unsigned, unsigned, unsigned long, unsigned long);
unsigned long (*InternAtom)(Display *, const char *, int);
int (*ChangeProperty)(Display *, Window, unsigned long, unsigned long, int, int, const unsigned char *, int);
int (*MapWindow)(Display *, Window);
int (*StoreName)(Display *, Window, const char *);
int (*Flush)(Display *);
bool Load() {
lib = dlopen("libX11.so.6", RTLD_NOW | RTLD_LOCAL);
if (!lib) return false;
#define X(name) if (!(*(void **)&name = dlsym(lib, "X" #name))) return false;
X(OpenDisplay) X(DefaultRootWindow) X(DefaultScreen) X(DisplayWidth) X(DisplayHeight)
X(CreateSimpleWindow) X(InternAtom) X(ChangeProperty) X(MapWindow) X(StoreName) X(Flush)
#undef X
return true;
}
};
#define VK_GLOBAL(X) X(CreateInstance)
#define VK_INSTANCE(X) \
X(DestroyInstance) X(EnumeratePhysicalDevices) X(GetPhysicalDeviceQueueFamilyProperties) \
X(GetPhysicalDeviceMemoryProperties) X(CreateDevice) X(GetDeviceProcAddr) X(CreateXlibSurfaceKHR) \
X(CreateWaylandSurfaceKHR) \
X(GetPhysicalDeviceSurfaceSupportKHR) X(GetPhysicalDeviceSurfaceCapabilitiesKHR) \
X(GetPhysicalDeviceSurfaceFormatsKHR) X(GetPhysicalDeviceSurfacePresentModesKHR) X(GetPhysicalDeviceProperties) X(DestroySurfaceKHR)
#define VK_DEVICE(X) \
X(GetDeviceQueue) X(CreateSwapchainKHR) X(DestroySwapchainKHR) X(GetSwapchainImagesKHR) X(AcquireNextImageKHR) \
X(QueuePresentKHR) X(CreateCommandPool) X(AllocateCommandBuffers) X(BeginCommandBuffer) X(EndCommandBuffer) \
X(ResetCommandBuffer) X(CmdPipelineBarrier) X(CmdBlitImage) X(CmdClearColorImage) X(QueueSubmit) X(CreateFence) \
X(WaitForFences) X(ResetFences) X(CreateSemaphore) X(CreateImage) X(DestroyImage) X(GetImageMemoryRequirements) \
X(AllocateMemory) X(FreeMemory) X(BindImageMemory) X(DeviceWaitIdle) X(DestroyDevice) X(DestroyFence) X(DestroySemaphore) \
X(DestroyCommandPool) X(CreateBuffer) X(DestroyBuffer) X(GetBufferMemoryRequirements) X(BindBufferMemory) \
X(MapMemory) X(UnmapMemory) X(InvalidateMappedMemoryRanges) X(CmdCopyImageToBuffer)
struct Vk {
void *lib = nullptr;
PFN_vkGetInstanceProcAddr GetInstanceProcAddr = nullptr;
#define X(n) PFN_vk##n n = nullptr;
VK_GLOBAL(X) VK_INSTANCE(X) VK_DEVICE(X)
#undef X
};
struct Imported {
uint64_t handle;
VkImage image = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
uint32_t width = 0, height = 0;
int dmaFd = -1;
};
struct PendingImport {
uint64_t handle;
int fd;
uint32_t format, width, height;
};
VkFormat UnormOf(uint32_t f) {
switch (f) {
case VK_FORMAT_R8G8B8A8_SRGB: return VK_FORMAT_R8G8B8A8_UNORM;
case VK_FORMAT_B8G8R8A8_SRGB: return VK_FORMAT_B8G8R8A8_UNORM;
default: return (VkFormat)f;
}
}
class Impl {
public:
static Impl &Get() {
static Impl impl;
return impl;
}
void Add(const uint64_t handles[3], const int fds[3], uint32_t format, uint32_t w, uint32_t h) {
std::lock_guard<std::mutex> lock(mutex_);
if (failed_ || exit_) return;
for (int i = 0; i < 3; i++) {
if (fds[i] < 0) continue;
int fd = dup(fds[i]);
if (fd >= 0) pending_.push_back({handles[i], fd, format, w, h});
}
EnsureThread();
cv_.notify_all();
}
void Remove(uint64_t handle) {
std::lock_guard<std::mutex> lock(mutex_);
removals_.push_back(handle);
cv_.notify_all();
}
void Submit(const EyeFrame &f) {
std::unique_lock<std::mutex> lock(mutex_);
if (failed_ || exit_) return;
bool blocking = FrameChannelRequested();
if (blocking) cv_.wait(lock, [this] { return failed_ || exit_ || (!haveFrame_ && !inFlight_); });
if (failed_ || exit_) return;
EnsureThread();
frame_ = f;
frameAt_ = std::chrono::steady_clock::now();
haveFrame_ = true;
submitted_++;
uint64_t ticket = ++ticket_;
cv_.notify_all();
if (blocking) cv_.wait(lock, [this, ticket] { return failed_ || completed_ >= ticket; });
}
void Shutdown() {
{
std::lock_guard<std::mutex> lock(mutex_);
exit_ = true;
cv_.notify_all();
}
if (thread_.joinable()) thread_.join();
}
private:
static bool FrameChannelRequested() {
const char *mode = getenv("DROIDDECK_VR_PRESENT");
return mode && !strcmp(mode, "questframes");
}
void Fail() {
std::lock_guard<std::mutex> lock(mutex_);
failed_ = true;
for (const auto &pending : pending_) close(pending.fd);
pending_.clear(); cv_.notify_all();
}
void EnsureThread() {
if (!started_) {
started_ = true;
thread_ = std::thread([this] {
try { Run(); }
catch (const std::exception &error) { Log("presenter: %s", error.what()); Fail(); }
});
}
}
void Run() {
const char *mode = getenv("DROIDDECK_VR_PRESENT");
frameChannel_ = FrameChannelRequested();
if (!frameChannel_) wayland_ = !(mode && !strcmp(mode, "x11")) && InitWayland();
if (!frameChannel_ && !wayland_ && !InitWindow()) {
Log("presenter: unavailable, frames are not shown");
Fail(); return;
}
if (!InitVulkan()) {
Log("presenter: unavailable, frames are not shown");
Fail(); return;
}
uint64_t shown = 0;
auto lastLog = std::chrono::steady_clock::now();
while (true) {
EyeFrame f;
uint64_t ticket = 0;
std::chrono::steady_clock::time_point at;
std::vector<PendingImport> imports;
std::vector<uint64_t> removals;
{
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock, [this] { return exit_ || haveFrame_ || !pending_.empty() || !removals_.empty(); });
if (exit_) break;
imports.swap(pending_);
removals.swap(removals_);
f = frame_;
at = frameAt_;
bool draw = haveFrame_;
haveFrame_ = false;
inFlight_ = draw; ticket = ticket_;
if (!draw) f.texture[0] = f.texture[1] = 0;
}
if (wl_) { wl_display_dispatch_pending(wl_); wl_display_flush(wl_); }
for (auto &p : imports) Import(p);
for (auto h : removals) Forget(h);
if (f.texture[0] && !frameChannel_) std::this_thread::sleep_until(at + guard_);
if (f.texture[0] && (frameChannel_ ? Capture(f) : Draw(f))) shown++;
{
std::lock_guard<std::mutex> lock(mutex_);
inFlight_ = false; completed_ = ticket; cv_.notify_all();
if (failed_) break;
}
auto now = std::chrono::steady_clock::now();
if (now - lastLog > std::chrono::seconds(10)) {
Log("presenter: %llu frames shown, %llu submitted (last 10 s)", (unsigned long long)shown,
(unsigned long long)submitted_.exchange(0));
shown = 0;
lastLog = now;
}
}
if (dev_) vk_.DeviceWaitIdle(dev_);
DestroyCapture();
while (!textures_.empty()) Forget(textures_.back().handle);
if (dev_) {
if (swapchain_) vk_.DestroySwapchainKHR(dev_, swapchain_, nullptr);
if (acquired_) vk_.DestroySemaphore(dev_, acquired_, nullptr);
if (rendered_) vk_.DestroySemaphore(dev_, rendered_, nullptr);
if (fence_) vk_.DestroyFence(dev_, fence_, nullptr);
if (pool_) vk_.DestroyCommandPool(dev_, pool_, nullptr);
vk_.DestroyDevice(dev_, nullptr); dev_ = VK_NULL_HANDLE;
}
if (surface_) vk_.DestroySurfaceKHR(inst_, surface_, nullptr);
if (inst_) vk_.DestroyInstance(inst_, nullptr);
Fail();
}
std::chrono::microseconds guard_{4000};
struct wl_display *wl_ = nullptr;
struct wl_compositor *wlComp_ = nullptr;
struct xdg_wm_base *wm_ = nullptr;
struct wl_surface *wlSurf_ = nullptr;
struct xdg_surface *xdgSurf_ = nullptr;
struct xdg_toplevel *toplevel_ = nullptr;
bool wayland_ = false, configured_ = false;
int32_t cfgW_ = 0, cfgH_ = 0;
static void OnGlobal(void *data, struct wl_registry *reg, uint32_t name, const char *iface, uint32_t version) {
Impl *self = (Impl *)data;
if (!strcmp(iface, wl_compositor_interface.name))
self->wlComp_ = (struct wl_compositor *)wl_registry_bind(reg, name, &wl_compositor_interface, version < 4 ? version : 4);
else if (!strcmp(iface, xdg_wm_base_interface.name))
self->wm_ = (struct xdg_wm_base *)wl_registry_bind(reg, name, &xdg_wm_base_interface, 1);
}
static void OnGlobalRemove(void *, struct wl_registry *, uint32_t) {}
static void OnPing(void *, struct xdg_wm_base *wm, uint32_t serial) { xdg_wm_base_pong(wm, serial); }
static void OnSurfaceConfigure(void *data, struct xdg_surface *xs, uint32_t serial) {
xdg_surface_ack_configure(xs, serial);
((Impl *)data)->configured_ = true;
}
static void OnToplevelConfigure(void *data, struct xdg_toplevel *, int32_t w, int32_t h, struct wl_array *) {
Impl *self = (Impl *)data;
if (w > 0 && h > 0) { self->cfgW_ = w; self->cfgH_ = h; }
}
static void OnToplevelClose(void *, struct xdg_toplevel *) {}
bool InitWayland() {
const char *name = getenv("DROIDDECK_VR_WAYLAND_DISPLAY");
if (!name || !*name) name = "wayland-0";
wl_ = wl_display_connect(name);
if (!wl_) { Log("presenter: no Wayland display %s; using the X11 overlay", name); return false; }
static const struct wl_registry_listener regListener = {OnGlobal, OnGlobalRemove};
struct wl_registry *reg = wl_display_get_registry(wl_);
wl_registry_add_listener(reg, &regListener, this);
wl_display_roundtrip(wl_);
if (!wlComp_ || !wm_) { Log("presenter: %s lacks wl_compositor or xdg_wm_base", name); return false; }
static const struct xdg_wm_base_listener wmListener = {OnPing};
xdg_wm_base_add_listener(wm_, &wmListener, this);
wlSurf_ = wl_compositor_create_surface(wlComp_);
xdgSurf_ = xdg_wm_base_get_xdg_surface(wm_, wlSurf_);
static const struct xdg_surface_listener xsListener = {OnSurfaceConfigure};
xdg_surface_add_listener(xdgSurf_, &xsListener, this);
toplevel_ = xdg_surface_get_toplevel(xdgSurf_);
static const struct xdg_toplevel_listener tlListener = {OnToplevelConfigure, OnToplevelClose, nullptr, nullptr};
xdg_toplevel_add_listener(toplevel_, &tlListener, this);
xdg_toplevel_set_title(toplevel_, "DroidDeck Cardboard VR");
xdg_toplevel_set_app_id(toplevel_, "droiddeck-cardboard-vr");
xdg_toplevel_set_fullscreen(toplevel_, nullptr);
wl_surface_commit(wlSurf_);
for (int i = 0; i < 20 && !configured_; i++) wl_display_roundtrip(wl_);
width_ = height_ = 0;
int fd = open("/run/droiddeck/droiddeck-headpose", O_RDONLY | O_CLOEXEC);
if (fd >= 0) {
uint32_t eye[2] = {0, 0};
if (pread(fd, eye, sizeof(eye), 72) == (ssize_t)sizeof(eye) && eye[0] && eye[1]) { width_ = eye[0] * 2; height_ = eye[1]; }
close(fd);
}
XrTrackingFile xrFile;
DroidDeckXrTracking tracking;
if (xrFile.Read(&tracking) && XrTrackingFile::Fresh(tracking)) {
width_ = tracking.eyes[0].width * 2; height_ = tracking.eyes[0].height;
}
if (!width_ && cfgW_ > 0 && cfgH_ > 0) { width_ = (uint32_t)cfgW_; height_ = (uint32_t)cfgH_; }
if (!width_) { width_ = 1920; height_ = 1080; }
Log("presenter: %ux%u fullscreen window on Wayland display %s (configured %s)", width_, height_, name, configured_ ? "yes" : "no");
return true;
}
bool InitWindow() {
if (const char *g = getenv("DROIDDECK_VR_PRESENT_GUARD_MS")) guard_ = std::chrono::microseconds((long)(atof(g) * 1000));
if (!x_.Load()) { Log("presenter: no libX11"); return false; }
const char *name = getenv("DROIDDECK_VR_DISPLAY");
if (!name || !*name) name = ":0";
dpy_ = x_.OpenDisplay(name);
if (!dpy_) { Log("presenter: cannot open X display %s", name); return false; }
int scr = x_.DefaultScreen(dpy_);
width_ = (uint32_t)x_.DisplayWidth(dpy_, scr);
height_ = (uint32_t)x_.DisplayHeight(dpy_, scr);
win_ = x_.CreateSimpleWindow(dpy_, x_.DefaultRootWindow(dpy_), 0, 0, width_, height_, 0, 0, 0);
x_.StoreName(dpy_, win_, "DroidDeck Cardboard VR");
const char *atom = getenv("DROIDDECK_VR_OVERLAY_ATOM");
if (!atom || !*atom) atom = "STEAM_OVERLAY";
unsigned long cardinal = 6;
unsigned long one = 1;
x_.ChangeProperty(dpy_, win_, x_.InternAtom(dpy_, atom, 0), cardinal, 32, 0 ,
(const unsigned char *)&one, 1);
x_.MapWindow(dpy_, win_);
x_.Flush(dpy_);
Log("presenter: %ux%u overlay window (%s) on X display %s", width_, height_, atom, name);
return true;
}
bool InitVulkan() {
vk_.lib = dlopen("libvulkan.so.1", RTLD_NOW | RTLD_LOCAL);
if (!vk_.lib) { Log("presenter: no libvulkan"); return false; }
vk_.GetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)dlsym(vk_.lib, "vkGetInstanceProcAddr");
#define X(n) vk_.n = (PFN_vk##n)vk_.GetInstanceProcAddr(VK_NULL_HANDLE, "vk" #n);
VK_GLOBAL(X)
#undef X
const char *iexts[] = {"VK_KHR_surface", wayland_ ? "VK_KHR_wayland_surface" : "VK_KHR_xlib_surface"};
VkApplicationInfo app = {VK_STRUCTURE_TYPE_APPLICATION_INFO, nullptr, "droiddeck-vr-presenter", 1, nullptr, 0, VK_API_VERSION_1_1};
VkInstanceCreateInfo ici = {VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, nullptr, 0, &app, 0, nullptr,
frameChannel_ ? 0u : 2u, frameChannel_ ? nullptr : iexts};
VkResult r = vk_.CreateInstance(&ici, nullptr, &inst_);
if (r != VK_SUCCESS) { Log("presenter: vkCreateInstance %d", r); return false; }
#define X(n) vk_.n = (PFN_vk##n)vk_.GetInstanceProcAddr(inst_, "vk" #n);
VK_INSTANCE(X)
#undef X
uint32_t n = 1;
VkPhysicalDevice pds[4];
n = 4;
if (vk_.EnumeratePhysicalDevices(inst_, &n, pds) < 0 || n == 0) { Log("presenter: no GPU"); return false; }
pd_ = pds[0];
VkPhysicalDeviceProperties props;
vk_.GetPhysicalDeviceProperties(pd_, &props);
vk_.GetPhysicalDeviceMemoryProperties(pd_, &mem_);
if (!frameChannel_ && wayland_) {
VkWaylandSurfaceCreateInfoKHR wci = {VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, nullptr, 0, wl_, wlSurf_};
if ((r = vk_.CreateWaylandSurfaceKHR(inst_, &wci, nullptr, &surface_)) != VK_SUCCESS) { Log("presenter: wayland surface %d", r); return false; }
} else if (!frameChannel_) {
VkXlibSurfaceCreateInfoKHR sci = {VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, nullptr, 0, dpy_, win_};
if ((r = vk_.CreateXlibSurfaceKHR(inst_, &sci, nullptr, &surface_)) != VK_SUCCESS) { Log("presenter: xlib surface %d", r); return false; }
}
uint32_t nq = 0;
vk_.GetPhysicalDeviceQueueFamilyProperties(pd_, &nq, nullptr);
std::vector<VkQueueFamilyProperties> qf(nq);
vk_.GetPhysicalDeviceQueueFamilyProperties(pd_, &nq, qf.data());
qfam_ = UINT32_MAX;
for (uint32_t i = 0; i < nq; i++) {
VkBool32 present = VK_FALSE;
if (frameChannel_) present = VK_TRUE;
else vk_.GetPhysicalDeviceSurfaceSupportKHR(pd_, i, surface_, &present);
if ((qf[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && present) { qfam_ = i; break; }
}
if (qfam_ == UINT32_MAX) { Log("presenter: no queue can present"); return false; }
float prio = 1.0f;
VkDeviceQueueCreateInfo qci = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, nullptr, 0, qfam_, 1, &prio};
const char *dexts[] = {"VK_KHR_external_memory_fd", "VK_KHR_swapchain"};
VkDeviceCreateInfo dci = {VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, nullptr, 0, 1, &qci, 0, nullptr, frameChannel_ ? 1u : 2u, dexts, nullptr};
if ((r = vk_.CreateDevice(pd_, &dci, nullptr, &dev_)) != VK_SUCCESS) { Log("presenter: vkCreateDevice %d", r); return false; }
#define X(n) vk_.n = (PFN_vk##n)vk_.GetDeviceProcAddr(dev_, "vk" #n);
VK_DEVICE(X)
#undef X
vk_.GetDeviceQueue(dev_, qfam_, 0, &queue_);
VkCommandPoolCreateInfo pci = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, nullptr,
VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, qfam_};
vk_.CreateCommandPool(dev_, &pci, nullptr, &pool_);
VkCommandBufferAllocateInfo cai = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, nullptr, pool_, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1};
vk_.AllocateCommandBuffers(dev_, &cai, &cmd_);
VkFenceCreateInfo fci = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, nullptr, VK_FENCE_CREATE_SIGNALED_BIT};
vk_.CreateFence(dev_, &fci, nullptr, &fence_);
VkSemaphoreCreateInfo semci = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, 0};
vk_.CreateSemaphore(dev_, &semci, nullptr, &acquired_);
vk_.CreateSemaphore(dev_, &semci, nullptr, &rendered_);
if (!frameChannel_ && !CreateSwapchain()) return false;
Log("presenter: Vulkan on %s, queue family %u", props.deviceName, qfam_);
return true;
}
bool CreateSwapchain() {
VkSurfaceCapabilitiesKHR caps;
vk_.GetPhysicalDeviceSurfaceCapabilitiesKHR(pd_, surface_, &caps);
uint32_t nf = 0;
vk_.GetPhysicalDeviceSurfaceFormatsKHR(pd_, surface_, &nf, nullptr);
std::vector<VkSurfaceFormatKHR> fmts(nf);
vk_.GetPhysicalDeviceSurfaceFormatsKHR(pd_, surface_, &nf, fmts.data());
VkSurfaceFormatKHR fmt = fmts.empty() ? VkSurfaceFormatKHR{VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR} : fmts[0];
for (auto &f : fmts)
if (f.format == VK_FORMAT_B8G8R8A8_UNORM || f.format == VK_FORMAT_R8G8B8A8_UNORM) { fmt = f; break; }
uint32_t npm = 0;
vk_.GetPhysicalDeviceSurfacePresentModesKHR(pd_, surface_, &npm, nullptr);
std::vector<VkPresentModeKHR> pms(npm);
vk_.GetPhysicalDeviceSurfacePresentModesKHR(pd_, surface_, &npm, pms.data());
VkPresentModeKHR pm = VK_PRESENT_MODE_FIFO_KHR;
for (auto m : pms) if (m == VK_PRESENT_MODE_MAILBOX_KHR) pm = m;
VkExtent2D ext = caps.currentExtent.width != UINT32_MAX ? caps.currentExtent : VkExtent2D{width_, height_};
extent_ = ext;
uint32_t count = caps.minImageCount + 1;
if (caps.maxImageCount && count > caps.maxImageCount) count = caps.maxImageCount;
VkCompositeAlphaFlagBitsKHR alpha = (caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR)
? VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR : (VkCompositeAlphaFlagBitsKHR)(caps.supportedCompositeAlpha & -caps.supportedCompositeAlpha);
VkSwapchainCreateInfoKHR sci = {VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR};
sci.surface = surface_;
sci.minImageCount = count;
sci.imageFormat = fmt.format;
sci.imageColorSpace = fmt.colorSpace;
sci.imageExtent = ext;
sci.imageArrayLayers = 1;
sci.imageUsage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
sci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
sci.preTransform = caps.currentTransform;
sci.compositeAlpha = alpha;
sci.presentMode = pm;
sci.clipped = VK_TRUE;
VkResult r = vk_.CreateSwapchainKHR(dev_, &sci, nullptr, &swapchain_);
if (r != VK_SUCCESS) { Log("presenter: vkCreateSwapchainKHR %d", r); return false; }
uint32_t ni = 0;
vk_.GetSwapchainImagesKHR(dev_, swapchain_, &ni, nullptr);
images_.resize(ni);
vk_.GetSwapchainImagesKHR(dev_, swapchain_, &ni, images_.data());
Log("presenter: swapchain %ux%u, format %d, %u images, %s", ext.width, ext.height, fmt.format, ni,
pm == VK_PRESENT_MODE_MAILBOX_KHR ? "mailbox" : "fifo");
return true;
}
int MemoryType(uint32_t bits) {
for (uint32_t i = 0; i < mem_.memoryTypeCount; i++)
if ((bits & (1u << i)) && (mem_.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) return (int)i;
for (uint32_t i = 0; i < mem_.memoryTypeCount; i++) if (bits & (1u << i)) return (int)i;
return -1;
}
void Import(const PendingImport &p) {
Imported im{p.handle};
im.width = p.width;
im.height = p.height;
if (frameChannel_) im.dmaFd = dup(p.fd);
const char *fl = getenv("DROIDDECK_VR_IMPORT_FORMAT_LIST");
const bool formatList = !fl || strcmp(fl, "0") != 0;
VkFormat formats[2] = {(VkFormat)p.format, UnormOf(p.format)};
VkImageFormatListCreateInfo list = {VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO, nullptr, 2, formats};
VkExternalMemoryImageCreateInfo ext = {VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, formatList ? &list : nullptr,
VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT};
VkImageCreateInfo ici = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, &ext, VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT, VK_IMAGE_TYPE_2D,
UnormOf(p.format), {p.width, p.height, 1}, 1, 1, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT,
VK_SHARING_MODE_EXCLUSIVE, 0, nullptr, VK_IMAGE_LAYOUT_UNDEFINED};
VkResult r = vk_.CreateImage(dev_, &ici, nullptr, &im.image);
if (r != VK_SUCCESS) { Log("presenter: import image %d", r); close(p.fd); if (im.dmaFd >= 0) close(im.dmaFd); return; }
VkMemoryRequirements req;
vk_.GetImageMemoryRequirements(dev_, im.image, &req);
VkImportMemoryFdInfoKHR imp = {VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR, nullptr, VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT, p.fd};
VkMemoryDedicatedAllocateInfo ded = {VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, &imp, im.image, VK_NULL_HANDLE};
int memoryType = MemoryType(req.memoryTypeBits);
if (memoryType < 0) { vk_.DestroyImage(dev_, im.image, nullptr); close(p.fd); if (im.dmaFd >= 0) close(im.dmaFd); return; }
VkMemoryAllocateInfo mai = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, &ded, req.size, (uint32_t)memoryType};
r = vk_.AllocateMemory(dev_, &mai, nullptr, &im.memory);
if (r != VK_SUCCESS) {
Log("presenter: import memory (fd %d, %llu bytes) %d", p.fd, (unsigned long long)req.size, r);
close(p.fd);
if (im.dmaFd >= 0) close(im.dmaFd);
vk_.DestroyImage(dev_, im.image, nullptr);
return;
}
if (vk_.BindImageMemory(dev_, im.image, im.memory, 0) != VK_SUCCESS) {
vk_.DestroyImage(dev_, im.image, nullptr); vk_.FreeMemory(dev_, im.memory, nullptr);
if (im.dmaFd >= 0) close(im.dmaFd);
return;
}
textures_.push_back(im);
Log("presenter: imported texture 0x%llx (%ux%u, format %u)", (unsigned long long)p.handle, p.width, p.height, p.format);
}
void Forget(uint64_t handle) {
for (auto it = textures_.begin(); it != textures_.end(); ++it) {
if (it->handle == handle) {
vk_.DeviceWaitIdle(dev_);
vk_.DestroyImage(dev_, it->image, nullptr);
vk_.FreeMemory(dev_, it->memory, nullptr);
if (it->dmaFd >= 0) close(it->dmaFd);
textures_.erase(it);
return;
}
}
}
const Imported *Find(uint64_t h) const {
for (auto &t : textures_) if (t.handle == h) return &t;
return nullptr;
}
static VkImageMemoryBarrier Barrier(VkImage img, VkImageLayout from, VkImageLayout to, VkAccessFlags src, VkAccessFlags dst,
uint32_t srcQ = VK_QUEUE_FAMILY_IGNORED, uint32_t dstQ = VK_QUEUE_FAMILY_IGNORED) {
VkImageMemoryBarrier b = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, nullptr, src, dst, from, to, srcQ, dstQ, img,
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
return b;
}
bool ProducerReady(const Imported &image) {
struct { uint32_t flags; int32_t fd; } exported{1u, -1};
bool ready = false;
if (image.dmaFd >= 0 && ioctl(image.dmaFd, _IOWR('b', 2, decltype(exported)), &exported) == 0 && exported.fd >= 0) {
struct SyncInfo { char name[32]; int32_t status; uint32_t flags, count, padding; uint64_t fences; } info{};
if (ioctl(exported.fd, _IOWR('>', 4, SyncInfo), &info) == 0 && info.count > 0) {
pollfd signal{exported.fd, POLLIN, 0};
int64_t deadline = xr_frame_now() + 100000000LL;
while (xr_frame_now() < deadline) {
int64_t remaining = deadline - xr_frame_now();
if (remaining <= 0) break;
int result = poll(&signal, 1, static_cast<int>((remaining + 999999) / 1000000));
if (result < 0 && errno == EINTR) continue;
ready = result > 0 && (signal.revents & POLLIN) && !(signal.revents & (POLLERR | POLLNVAL));
break;
}
}
close(exported.fd);
}
if (!ready) {
static unsigned said = 0;
if (said++ < 3) Log("presenter: refusing Quest frame without a nonempty successful producer fence (texture 0x%llx)",
(unsigned long long)image.handle);
}
return ready;
}
void DestroyCapture() {
if (!dev_) return;
if (captureImage_) vk_.DestroyImage(dev_, captureImage_, nullptr);
if (captureMemory_) vk_.FreeMemory(dev_, captureMemory_, nullptr);
if (readbackBuffer_) vk_.DestroyBuffer(dev_, readbackBuffer_, nullptr);
if (readbackMemory_) vk_.FreeMemory(dev_, readbackMemory_, nullptr);
captureImage_ = VK_NULL_HANDLE; captureMemory_ = readbackMemory_ = VK_NULL_HANDLE;
readbackBuffer_ = VK_NULL_HANDLE; captureWidth_ = captureHeight_ = 0;
}
bool PrepareCapture(const DroidDeckXrFrameHeader &header) {
if (captureWidth_ == header.width && captureHeight_ == header.height) return true;
DestroyCapture();
auto fail = [this](VkResult result) { Log("presenter: capture allocation failed %d", result); DestroyCapture(); return false; };
VkImageCreateInfo image{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
image.imageType = VK_IMAGE_TYPE_2D; image.format = VK_FORMAT_R8G8B8A8_UNORM;
image.extent = {header.width, header.height, 1}; image.mipLevels = image.arrayLayers = 1;
image.samples = VK_SAMPLE_COUNT_1_BIT; image.tiling = VK_IMAGE_TILING_OPTIMAL;
image.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
VkResult result = vk_.CreateImage(dev_, &image, nullptr, &captureImage_);
if (result != VK_SUCCESS) return fail(result);
VkMemoryRequirements requirements{}; vk_.GetImageMemoryRequirements(dev_, captureImage_, &requirements);
int type = MemoryType(requirements.memoryTypeBits);
if (type < 0) return fail(VK_ERROR_FEATURE_NOT_PRESENT);
VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
allocation.allocationSize = requirements.size; allocation.memoryTypeIndex = type;
result = vk_.AllocateMemory(dev_, &allocation, nullptr, &captureMemory_);
if (result != VK_SUCCESS) return fail(result);
result = vk_.BindImageMemory(dev_, captureImage_, captureMemory_, 0);
if (result != VK_SUCCESS) return fail(result);
VkBufferCreateInfo buffer{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; buffer.size = header.bytes;
buffer.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
result = vk_.CreateBuffer(dev_, &buffer, nullptr, &readbackBuffer_);
if (result != VK_SUCCESS) return fail(result);
vk_.GetBufferMemoryRequirements(dev_, readbackBuffer_, &requirements);
type = -1;
for (uint32_t index = 0; index < mem_.memoryTypeCount; index++) {
if ((requirements.memoryTypeBits & (1u << index)) && (mem_.memoryTypes[index].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) {
type = index;
if (mem_.memoryTypes[index].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) break;
}
}
if (type < 0) return fail(VK_ERROR_FEATURE_NOT_PRESENT);
allocation.allocationSize = requirements.size; allocation.memoryTypeIndex = type;
result = vk_.AllocateMemory(dev_, &allocation, nullptr, &readbackMemory_);
if (result != VK_SUCCESS) return fail(result);
result = vk_.BindBufferMemory(dev_, readbackBuffer_, readbackMemory_, 0);
if (result != VK_SUCCESS) return fail(result);
captureWidth_ = header.width; captureHeight_ = header.height;
return true;
}
bool Capture(const EyeFrame &frame) {
if (frame.layers != 1 || !xr_frame_valid(frame.render)) {
static unsigned said = 0;
if (said++ < 3) Log("presenter: refusing Quest frame with unsupported layer count or render geometry (%u layers)", frame.layers);
return false;
}
const Imported *eye[2] = {Find(frame.texture[0]), Find(frame.texture[1])};
if (!eye[0] || !eye[1]) return false;
for (int index = 0; index < 2; index++) {
for (float value : frame.bounds[index]) if (!std::isfinite(value) || value < 0 || value > 1) return false;
if (frame.bounds[index][0] == frame.bounds[index][2] || frame.bounds[index][1] == frame.bounds[index][3]) return false;
if ((index == 0 || eye[0] != eye[1]) && !ProducerReady(*eye[index])) return false;
}
VkResult result = vk_.WaitForFences(dev_, 1, &fence_, VK_TRUE, UINT64_MAX);
if (result != VK_SUCCESS) { Fail(); return false; }
if (!PrepareCapture(frame.render)) return false;
if (vk_.ResetCommandBuffer(cmd_, 0) != VK_SUCCESS) { Fail(); return false; }
VkCommandBufferBeginInfo begin{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
if (vk_.BeginCommandBuffer(cmd_, &begin) != VK_SUCCESS) { Fail(); return false; }
VkImageMemoryBarrier before[] = {
Barrier(captureImage_, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, VK_ACCESS_TRANSFER_WRITE_BIT),
Barrier(eye[0]->image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, 0, VK_ACCESS_TRANSFER_READ_BIT, VK_QUEUE_FAMILY_EXTERNAL, qfam_),
Barrier(eye[1]->image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, 0, VK_ACCESS_TRANSFER_READ_BIT, VK_QUEUE_FAMILY_EXTERNAL, qfam_)};
vk_.CmdPipelineBarrier(cmd_, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr,
eye[0] == eye[1] ? 2 : 3, before);
int32_t half = frame.render.width / 2;
for (int index = 0; index < 2; index++) {
const float *bounds = frame.bounds[index];
VkImageBlit blit{}; blit.srcSubresource = blit.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
blit.srcOffsets[0] = {static_cast<int32_t>(bounds[0] * eye[index]->width), static_cast<int32_t>(bounds[1] * eye[index]->height), 0};
blit.srcOffsets[1] = {static_cast<int32_t>(bounds[2] * eye[index]->width), static_cast<int32_t>(bounds[3] * eye[index]->height), 1};
blit.dstOffsets[0] = {index * half, 0, 0}; blit.dstOffsets[1] = {(index + 1) * half, static_cast<int32_t>(frame.render.height), 1};
vk_.CmdBlitImage(cmd_, eye[index]->image, VK_IMAGE_LAYOUT_GENERAL, captureImage_, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_LINEAR);
}
VkImageMemoryBarrier after[] = {
Barrier(captureImage_, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT),
Barrier(eye[0]->image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, VK_ACCESS_TRANSFER_READ_BIT, 0, qfam_, VK_QUEUE_FAMILY_EXTERNAL),
Barrier(eye[1]->image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, VK_ACCESS_TRANSFER_READ_BIT, 0, qfam_, VK_QUEUE_FAMILY_EXTERNAL)};
vk_.CmdPipelineBarrier(cmd_, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr,
eye[0] == eye[1] ? 2 : 3, after);
VkBufferImageCopy copy{}; copy.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
copy.imageExtent = {frame.render.width, frame.render.height, 1};
vk_.CmdCopyImageToBuffer(cmd_, captureImage_, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, readbackBuffer_, 1, &copy);
VkBufferMemoryBarrier host{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER};
host.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; host.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
host.srcQueueFamilyIndex = host.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
host.buffer = readbackBuffer_; host.size = VK_WHOLE_SIZE;
vk_.CmdPipelineBarrier(cmd_, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 0, nullptr, 1, &host, 0, nullptr);
if (vk_.EndCommandBuffer(cmd_) != VK_SUCCESS || vk_.ResetFences(dev_, 1, &fence_) != VK_SUCCESS) { Fail(); return false; }
VkSubmitInfo submit{VK_STRUCTURE_TYPE_SUBMIT_INFO}; submit.commandBufferCount = 1; submit.pCommandBuffers = &cmd_;
result = vk_.QueueSubmit(queue_, 1, &submit, fence_);
if (result != VK_SUCCESS) { Fail(); return false; }
result = vk_.WaitForFences(dev_, 1, &fence_, VK_TRUE, UINT64_MAX);
if (result != VK_SUCCESS) { vk_.DeviceWaitIdle(dev_); Fail(); return false; }
void *mapping = nullptr;
if (vk_.MapMemory(dev_, readbackMemory_, 0, VK_WHOLE_SIZE, 0, &mapping) != VK_SUCCESS) return false;
VkMappedMemoryRange range{VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE}; range.memory = readbackMemory_; range.size = VK_WHOLE_SIZE;
result = vk_.InvalidateMappedMemoryRanges(dev_, 1, &range);
bool sent = result == VK_SUCCESS && sender_.Send("/run/droiddeck/" DROIDDECK_XR_FRAME_SOCKET,
frame.render, static_cast<unsigned char *>(mapping));
vk_.UnmapMemory(dev_, readbackMemory_);
return sent;
}
bool Draw(const EyeFrame &f) {
const Imported *eye[2] = {Find(f.texture[0]), Find(f.texture[1])};
if (!eye[0] || !eye[1]) return false;
vk_.WaitForFences(dev_, 1, &fence_, VK_TRUE, UINT64_MAX);
uint32_t idx = 0;
VkResult r = vk_.AcquireNextImageKHR(dev_, swapchain_, 100000000ull, acquired_, VK_NULL_HANDLE, &idx);
if (r != VK_SUCCESS && r != VK_SUBOPTIMAL_KHR) {
static int said;
if (said++ < 3) Log("presenter: acquire %d", r);
return false;
}
vk_.ResetFences(dev_, 1, &fence_);
vk_.ResetCommandBuffer(cmd_, 0);
VkCommandBufferBeginInfo bi = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
vk_.BeginCommandBuffer(cmd_, &bi);
VkImage dst = images_[idx];
VkImageMemoryBarrier pre[3] = {
Barrier(dst, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, VK_ACCESS_TRANSFER_WRITE_BIT),
Barrier(eye[0]->image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, 0, VK_ACCESS_TRANSFER_READ_BIT,
VK_QUEUE_FAMILY_EXTERNAL, qfam_),
Barrier(eye[1]->image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, 0, VK_ACCESS_TRANSFER_READ_BIT,
VK_QUEUE_FAMILY_EXTERNAL, qfam_)};
vk_.CmdPipelineBarrier(cmd_, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr,
eye[0] == eye[1] ? 2 : 3, pre);
VkClearColorValue black = {{0, 0, 0, 1}};
VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
vk_.CmdClearColorImage(cmd_, dst, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &black, 1, &range);
VkMemoryBarrier mb = {VK_STRUCTURE_TYPE_MEMORY_BARRIER, nullptr, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT};
vk_.CmdPipelineBarrier(cmd_, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &mb, 0, nullptr, 0, nullptr);
const int32_t half = (int32_t)extent_.width / 2;
for (int e = 0; e < 2; e++) {
const float *b = f.bounds[e];
VkImageBlit blit = {};
blit.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
blit.srcOffsets[0] = {(int32_t)(b[0] * eye[e]->width), (int32_t)(b[1] * eye[e]->height), 0};
blit.srcOffsets[1] = {(int32_t)(b[2] * eye[e]->width), (int32_t)(b[3] * eye[e]->height), 1};
blit.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
blit.dstOffsets[0] = {e * half, 0, 0};
blit.dstOffsets[1] = {e * half + half, (int32_t)extent_.height, 1};
vk_.CmdBlitImage(cmd_, eye[e]->image, VK_IMAGE_LAYOUT_GENERAL, dst, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit,
VK_FILTER_LINEAR);
}
VkImageMemoryBarrier post[3] = {
Barrier(dst, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_ACCESS_TRANSFER_WRITE_BIT, 0),
Barrier(eye[0]->image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, VK_ACCESS_TRANSFER_READ_BIT, 0, qfam_,
VK_QUEUE_FAMILY_EXTERNAL),
Barrier(eye[1]->image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, VK_ACCESS_TRANSFER_READ_BIT, 0, qfam_,
VK_QUEUE_FAMILY_EXTERNAL)};
vk_.CmdPipelineBarrier(cmd_, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr,
eye[0] == eye[1] ? 2 : 3, post);
vk_.EndCommandBuffer(cmd_);
VkPipelineStageFlags wait = VK_PIPELINE_STAGE_TRANSFER_BIT;
VkSubmitInfo si = {VK_STRUCTURE_TYPE_SUBMIT_INFO, nullptr, 1, &acquired_, &wait, 1, &cmd_, 1, &rendered_};
if ((r = vk_.QueueSubmit(queue_, 1, &si, fence_)) != VK_SUCCESS) { Log("presenter: submit %d", r); return false; }
VkPresentInfoKHR pi = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, nullptr, 1, &rendered_, 1, &swapchain_, &idx, nullptr};
r = vk_.QueuePresentKHR(queue_, &pi);
if (r != VK_SUCCESS && r != VK_SUBOPTIMAL_KHR) {
static int said;
if (said++ < 3) Log("presenter: present %d", r);
}
return true;
}
std::mutex mutex_;
std::condition_variable cv_;
std::vector<PendingImport> pending_;
std::vector<uint64_t> removals_;
EyeFrame frame_{};
std::chrono::steady_clock::time_point frameAt_{};
bool haveFrame_ = false, exit_ = false, started_ = false, inFlight_ = false, failed_ = false;
uint64_t ticket_ = 0, completed_ = 0;
bool frameChannel_ = false;
XrFrameSender sender_;
VkImage captureImage_ = VK_NULL_HANDLE;
VkDeviceMemory captureMemory_ = VK_NULL_HANDLE, readbackMemory_ = VK_NULL_HANDLE;
VkBuffer readbackBuffer_ = VK_NULL_HANDLE;
uint32_t captureWidth_ = 0, captureHeight_ = 0;
std::atomic<uint64_t> submitted_{0};
std::thread thread_;
X11 x_;
Display *dpy_ = nullptr;
Window win_ = 0;
uint32_t width_ = 0, height_ = 0;
Vk vk_;
VkInstance inst_ = VK_NULL_HANDLE;
VkPhysicalDevice pd_ = VK_NULL_HANDLE;
VkPhysicalDeviceMemoryProperties mem_{};
VkSurfaceKHR surface_ = VK_NULL_HANDLE;
uint32_t qfam_ = 0;
VkDevice dev_ = VK_NULL_HANDLE;
VkQueue queue_ = VK_NULL_HANDLE;
VkCommandPool pool_ = VK_NULL_HANDLE;
VkCommandBuffer cmd_ = VK_NULL_HANDLE;
VkFence fence_ = VK_NULL_HANDLE;
VkSemaphore acquired_ = VK_NULL_HANDLE, rendered_ = VK_NULL_HANDLE;
VkSwapchainKHR swapchain_ = VK_NULL_HANDLE;
VkExtent2D extent_{};
std::vector<VkImage> images_;
std::vector<Imported> textures_;
};
}
void Presenter::AddTextures(const uint64_t h[3], const int fds[3], uint32_t f, uint32_t w, uint32_t hh) { Impl::Get().Add(h, fds, f, w, hh); }
void Presenter::RemoveTexture(uint64_t h) { Impl::Get().Remove(h); }
void Presenter::Submit(const EyeFrame &f) { Impl::Get().Submit(f); }
void Presenter::Shutdown() { Impl::Get().Shutdown(); }
}