mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 19:27:09 -04:00
- A 3D attempt that ends at the 640x480 fallback is remembered for that glasses model, so Play SteamVR no longer blacks out the glasses after every restart; Retry clears it and "3D for VR games" stays reachable - A VITURE Mobile Dock on its own no longer counts as glasses; through a dock Play SteamVR is hidden and the sheet shows the output and why there is no control - HDR is probed on the glasses when the session starts there - Glasses frames with a mismatched checksum are marked in the status frame log - SteamVR layer and driver vblank counters stay monotonic across refresh changes; one-eye render scale applies from activation and on mono/3D switches - Compositor binds announce the refresh clients were last told; the presenter retries a failed first swapchain - Dead strings and helpers removed; docs updated
693 lines
33 KiB
C++
693 lines
33 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 "../../../app/src/main/cpp/waylandcomp/src/headpose_shm.h"
|
|
#include "presenter.h"
|
|
#include "present_sync.h"
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <condition_variable>
|
|
#include <cstdarg>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <dlfcn.h>
|
|
#include <fcntl.h>
|
|
#include <linux/futex.h>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <sys/mman.h>
|
|
#include <sys/syscall.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::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)
|
|
#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)
|
|
|
|
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;
|
|
};
|
|
|
|
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_);
|
|
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_one();
|
|
}
|
|
|
|
void Remove(uint64_t handle) {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
removals_.push_back(handle);
|
|
cv_.notify_one();
|
|
}
|
|
|
|
void Submit(const EyeFrame &f) {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
frame_ = f;
|
|
frameAt_ = std::chrono::steady_clock::now();
|
|
const droiddeck_present_sync *sync = MapSync(frameAt_);
|
|
const uint32_t issued = sync ? __atomic_load_n(&sync->issued, __ATOMIC_ACQUIRE) : 0;
|
|
frameTracked_ = sync && issued != lastIssued_;
|
|
frameMark_ = issued;
|
|
lastIssued_ = issued;
|
|
haveFrame_ = true;
|
|
submitted_++;
|
|
cv_.notify_one();
|
|
}
|
|
|
|
void Shutdown() {
|
|
{
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
exit_ = true;
|
|
cv_.notify_one();
|
|
}
|
|
if (thread_.joinable()) thread_.join();
|
|
}
|
|
|
|
private:
|
|
void EnsureThread() {
|
|
if (!started_) {
|
|
started_ = true;
|
|
thread_ = std::thread([this] { Run(); });
|
|
}
|
|
}
|
|
|
|
|
|
void Run() {
|
|
const char *mode = getenv("DROIDDECK_VR_PRESENT");
|
|
wayland_ = !(mode && !strcmp(mode, "x11")) && InitWayland();
|
|
if (!wayland_ && !InitWindow()) {
|
|
Log("presenter: unavailable, frames are not shown");
|
|
return;
|
|
}
|
|
if (!InitVulkan()) {
|
|
Log("presenter: unavailable, frames are not shown");
|
|
return;
|
|
}
|
|
if (const char *g = getenv("DROIDDECK_VR_PRESENT_GUARD_MS")) guard_ = std::chrono::microseconds((long)(atof(g) * 1000));
|
|
uint64_t shown = 0, waited = 0, late = 0;
|
|
std::chrono::steady_clock::duration longest{};
|
|
auto lastLog = std::chrono::steady_clock::now();
|
|
while (true) {
|
|
EyeFrame f;
|
|
std::chrono::steady_clock::time_point at;
|
|
uint32_t mark = 0;
|
|
bool tracked = false;
|
|
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_;
|
|
mark = frameMark_;
|
|
tracked = frameTracked_;
|
|
bool draw = haveFrame_;
|
|
haveFrame_ = false;
|
|
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] && tracked) {
|
|
const auto start = std::chrono::steady_clock::now();
|
|
const int r = WaitDrawn(mark, start + kDrawnTimeout);
|
|
if (r > 0) {
|
|
waited++;
|
|
longest = std::max(longest, std::chrono::steady_clock::now() - start);
|
|
} else if (r < 0) {
|
|
late++;
|
|
}
|
|
} else if (f.texture[0]) {
|
|
std::this_thread::sleep_until(at + guard_);
|
|
}
|
|
if (f.texture[0] && wayland_) FollowPanel();
|
|
if (f.texture[0] && Draw(f)) shown++;
|
|
auto now = std::chrono::steady_clock::now();
|
|
if (now - lastLog > std::chrono::seconds(10)) {
|
|
Log("presenter: %llu frames shown, %llu submitted, %llu waited for the GPU (longest %.1f ms), %llu late (last 10 s)",
|
|
(unsigned long long)shown, (unsigned long long)submitted_.exchange(0), (unsigned long long)waited,
|
|
std::chrono::duration<double, std::milli>(longest).count(), (unsigned long long)late);
|
|
shown = waited = late = 0;
|
|
longest = {};
|
|
lastLog = now;
|
|
}
|
|
}
|
|
if (dev_) vk_.DeviceWaitIdle(dev_);
|
|
}
|
|
|
|
bool PanelSize(uint32_t *w, uint32_t *h) {
|
|
if (!pose_) {
|
|
int fd = open("/run/droiddeck/" DROIDDECK_HEADPOSE_FILE, O_RDONLY | O_CLOEXEC);
|
|
if (fd < 0) return false;
|
|
void *m = mmap(nullptr, sizeof(droiddeck_headpose), PROT_READ, MAP_SHARED, fd, 0);
|
|
close(fd);
|
|
if (m == MAP_FAILED) return false;
|
|
pose_ = (const volatile droiddeck_headpose *)m;
|
|
}
|
|
for (int tries = 0; tries < 8; tries++) {
|
|
const uint32_t a = __atomic_load_n(&pose_->seq, __ATOMIC_ACQUIRE);
|
|
if (a & 1u) continue;
|
|
const uint32_t ew = pose_->eye_w, eh = pose_->eye_h;
|
|
__atomic_thread_fence(__ATOMIC_ACQUIRE);
|
|
if (__atomic_load_n(&pose_->seq, __ATOMIC_RELAXED) != a) continue;
|
|
if (!ew || !eh || pose_->magic != DROIDDECK_HEADPOSE_MAGIC) return false;
|
|
*w = ew * 2;
|
|
*h = eh;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FollowPanel() {
|
|
uint32_t w = 0, h = 0;
|
|
if (!PanelSize(&w, &h)) return;
|
|
const bool resized = w != width_ || h != height_;
|
|
const auto now = std::chrono::steady_clock::now();
|
|
if (!resized && (swapchain_ || now < retryAt_)) return;
|
|
vk_.DeviceWaitIdle(dev_);
|
|
if (swapchain_) vk_.DestroySwapchainKHR(dev_, swapchain_, nullptr);
|
|
swapchain_ = VK_NULL_HANDLE;
|
|
images_.clear();
|
|
if (resized) Log("presenter: panel now %ux%u (was %ux%u)", w, h, width_, height_);
|
|
width_ = w;
|
|
height_ = h;
|
|
if (!CreateSwapchain()) retryAt_ = now + std::chrono::seconds(1);
|
|
}
|
|
|
|
std::chrono::microseconds guard_{4000};
|
|
std::chrono::steady_clock::time_point retryAt_{};
|
|
static constexpr std::chrono::milliseconds kDrawnTimeout{100};
|
|
|
|
const droiddeck_present_sync *MapSync(std::chrono::steady_clock::time_point now) {
|
|
if (sync_) return sync_;
|
|
if (syncTried_.time_since_epoch().count() && now - syncTried_ < std::chrono::seconds(1)) return nullptr;
|
|
syncTried_ = now;
|
|
const char *rt = getenv("XDG_RUNTIME_DIR");
|
|
std::string path = std::string(rt && *rt ? rt : "/run/droiddeck") + "/droiddeck-steamvr/" DROIDDECK_PRESENT_SYNC_NAME;
|
|
int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
|
|
if (fd < 0) return nullptr;
|
|
void *m = mmap(nullptr, DROIDDECK_PRESENT_SYNC_SIZE, PROT_READ, MAP_SHARED, fd, 0);
|
|
close(fd);
|
|
if (m == MAP_FAILED) return nullptr;
|
|
sync_ = (const droiddeck_present_sync *)m;
|
|
Log("presenter: frames wait for the compositor's GPU work (%s)", path.c_str());
|
|
return sync_;
|
|
}
|
|
|
|
int WaitDrawn(uint32_t mark, std::chrono::steady_clock::time_point until) {
|
|
const droiddeck_present_sync *s = sync_;
|
|
int slept = 0;
|
|
while (true) {
|
|
const uint32_t done = __atomic_load_n(&s->done, __ATOMIC_ACQUIRE);
|
|
if ((int32_t)(done - mark) >= 0) return slept;
|
|
const auto left = until - std::chrono::steady_clock::now();
|
|
if (left <= std::chrono::steady_clock::duration::zero()) return -1;
|
|
const auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(left).count();
|
|
struct timespec ts = {(time_t)(ns / 1000000000), (long)(ns % 1000000000)};
|
|
syscall(SYS_futex, const_cast<uint32_t *>(&s->done), FUTEX_WAIT, done, &ts, nullptr, 0);
|
|
slept = 1;
|
|
}
|
|
}
|
|
|
|
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, ®Listener, 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;
|
|
if (!PanelSize(&width_, &height_)) width_ = height_ = 0;
|
|
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 (!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, 2, 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 (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 {
|
|
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;
|
|
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_swapchain", "VK_KHR_external_memory_fd"};
|
|
VkDeviceCreateInfo dci = {VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, nullptr, 0, 1, &qci, 0, nullptr, 2, 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 (!CreateSwapchain() && !wayland_) 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) {
|
|
swapchain_ = VK_NULL_HANDLE;
|
|
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;
|
|
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); 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};
|
|
VkMemoryAllocateInfo mai = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, &ded, req.size, (uint32_t)MemoryType(req.memoryTypeBits)};
|
|
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);
|
|
vk_.DestroyImage(dev_, im.image, nullptr);
|
|
return;
|
|
}
|
|
vk_.BindImageMemory(dev_, im.image, im.memory, 0);
|
|
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);
|
|
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 Draw(const EyeFrame &f) {
|
|
const Imported *eye[2] = {Find(f.texture[0]), Find(f.texture[1])};
|
|
if (!eye[0] || !eye[1] || !swapchain_) 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; }
|
|
if (vk_.WaitForFences(dev_, 1, &fence_, VK_TRUE, (uint64_t)std::chrono::nanoseconds(kDrawnTimeout).count()) == VK_TIMEOUT) {
|
|
static int said;
|
|
if (said++ < 3) Log("presenter: the copy took over %lld ms; showing it anyway", (long long)kDrawnTimeout.count());
|
|
}
|
|
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_{};
|
|
uint32_t frameMark_ = 0, lastIssued_ = 0;
|
|
bool frameTracked_ = false;
|
|
const droiddeck_present_sync *sync_ = nullptr;
|
|
const volatile droiddeck_headpose *pose_ = nullptr;
|
|
std::chrono::steady_clock::time_point syncTried_{};
|
|
bool haveFrame_ = false, exit_ = false, started_ = false;
|
|
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(); }
|
|
|
|
}
|