diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b645f663..6845dc06 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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/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/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', 'tools/vr/**') }} - name: Build libfakeinput.so and libblsession.so (glibc aarch64) if: steps.preloads.outputs.cache-hit != 'true' @@ -218,6 +218,9 @@ jobs: mkdir -p "$D/usr/local/bin" 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" + # Cardboard mode's SteamVR driver and Vulkan layer (tools/vr), Vulkan headers from the runner's NDK. + VK_INCLUDE=$(ls -d "${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"/toolchains/llvm/prebuilt/*/sysroot/usr/include | head -1) \ + tools/vr/build.sh --native "$D/usr/local/lib/droiddeck-vr" for script in tools/linuxfs/overlay/usr/local/bin/droiddeck-* tools/linuxfs/overlay/usr/local/bin/steam-compatibility; do install -Dm644 "$script" "$D/usr/local/bin/$(basename "$script")" done diff --git a/.gitignore b/.gitignore index a9e068c0..4293e3af 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ __pycache__/ # Local signing with the release key (tools/build_local.sh); holds a password. .signing.env app/src/main/assets/directaudio/ +/tools/vr/out/ diff --git a/app/src/main/cpp/waylandcomp/CMakeLists.txt b/app/src/main/cpp/waylandcomp/CMakeLists.txt index c2f9cb65..7cd4255d 100644 --- a/app/src/main/cpp/waylandcomp/CMakeLists.txt +++ b/app/src/main/cpp/waylandcomp/CMakeLists.txt @@ -8,6 +8,7 @@ add_library(droiddeckwayland SHARED src/compositor.c src/vk_present.c src/effects_chain.c + src/cardboard.c src/vk_loader.c src/sc_layer.c src/ahb_swapchain.c diff --git a/app/src/main/cpp/waylandcomp/src/cardboard.c b/app/src/main/cpp/waylandcomp/src/cardboard.c new file mode 100644 index 00000000..ffb1918b --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/cardboard.c @@ -0,0 +1,447 @@ +/* Cardboard pass - see cardboard.h. Compositor thread unless noted. + * + * The shader is cardboard.frag, pre-compiled like the rest of the compositor's SPIR-V: + * glslangValidator -V --vn cardboard_frag_code -o cardboard_frag.h cardboard.frag + */ +#define _POSIX_C_SOURCE 200809L +#include "cardboard.h" +#include +#include +#include + +#ifndef CARDBOARD_HOST_TEST +#include +#include +#include +#include +#include +#include "headpose_shm.h" +#endif + +#ifndef CARDBOARD_HOST_TEST +#include "vk_loader.h" +#include "vk_present.h" /* droiddeck_log */ +/* upscale_vert.h defines its array non-static, and effects_chain.c includes it too: a private name here. */ +#define upscale_vert_code cardboard_upscale_vert_code +#include "upscale_vert.h" +#undef upscale_vert_code +#include "cardboard_frag.h" +#endif + +/* ------------------------------------------------------------------ pure helpers */ + +static float clampf(float v, float lo, float hi) { return v < lo ? lo : v > hi ? hi : v; } + +void cardboard_clamp(struct cardboard_params *p) { + p->enabled = p->enabled ? 1 : 0; + p->source = p->source == CARDBOARD_SOURCE_SBS ? CARDBOARD_SOURCE_SBS : CARDBOARD_SOURCE_SCREEN; + p->ipd_m = clampf(p->ipd_m, 0.0f, 0.1f); + p->screen_dist_m = clampf(p->screen_dist_m, 0.5f, 50.0f); + p->screen_width_m = clampf(p->screen_width_m, 0.2f, 60.0f); + p->k1 = clampf(p->k1, -1.0f, 2.0f); + p->k2 = clampf(p->k2, -1.0f, 2.0f); + p->fov_deg = clampf(p->fov_deg, 30.0f, 140.0f); + p->lens_inset = clampf(p->lens_inset, -0.25f, 0.25f); + p->chroma = clampf(p->chroma, 0.0f, 0.05f); +} + +void cardboard_push_constants(const struct cardboard_params *p, const float pose[9], + int src_w, int src_h, int out_w, int out_h, float out[24]) { + /* rot0..rot2: the pose's rows, each with one scalar riding in w. */ + for (int r = 0; r < 3; r++) + for (int c = 0; c < 3; c++) out[r * 4 + c] = pose[r * 3 + c]; + out[3] = p->ipd_m * 0.5f; + out[7] = p->screen_dist_m; + out[11] = p->screen_width_m * 0.5f; + /* lens */ + out[12] = p->k1; + out[13] = p->k2; + out[14] = tanf(p->fov_deg * 0.5f * (float)M_PI / 180.0f); + out[15] = p->chroma; + /* view */ + out[16] = p->lens_inset; + out[17] = (src_w > 0 && src_h > 0) ? (float)src_w / (float)src_h : 16.0f / 9.0f; + if (p->source == CARDBOARD_SOURCE_SBS) out[17] *= 0.5f; /* one eye's half */ + out[18] = (float)p->source; + out[19] = (out_w > 0 && out_h > 0) ? ((float)out_w * 0.5f) / (float)out_h : 1.0f; + out[20] = out[21] = out[22] = out[23] = 0.0f; /* reserved */ +} + +#ifndef CARDBOARD_HOST_TEST + +/* ------------------------------------------------------------------ settings + pose */ + +static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER; +static struct cardboard_params g_set = { + .enabled = 0, .source = CARDBOARD_SOURCE_SCREEN, .ipd_m = 0.064f, .screen_dist_m = 3.0f, + .screen_width_m = 4.0f, .k1 = 0.22f, .k2 = 0.24f, .fov_deg = 90.0f, .lens_inset = 0.0f, .chroma = 0.0f}; +static float g_pose[9] = {1, 0, 0, 0, 1, 0, 0, 0, 1}; +static volatile int g_changed = 1; + +static int g_live; /* the last pose came from a sensor */ +static void shm_publish(int live); /* below: the guest's copy of the pose */ + +void vkp_cardboard_set(const struct cardboard_params *p) { + struct cardboard_params c = *p; + cardboard_clamp(&c); + pthread_mutex_lock(&g_lock); + int was = g_set.enabled; + g_set = c; + g_changed = 1; + shm_publish(g_live); + pthread_mutex_unlock(&g_lock); + if (was != c.enabled || c.enabled) + droiddeck_log("cardboard", "%s: %s, ipd %.0f mm, screen %.1f m wide at %.1f m, lens k1 %.2f k2 %.2f, fov %.0f, inset %.3f, chroma %.3f", + c.enabled ? "on" : "off", c.source == CARDBOARD_SOURCE_SBS ? "side-by-side source" : "virtual screen", + c.ipd_m * 1000.0f, c.screen_width_m, c.screen_dist_m, c.k1, c.k2, c.fov_deg, c.lens_inset, c.chroma); +} + +/* The guest's copy of the pose (headpose_shm.h), mapped once a path is known. */ +static struct droiddeck_headpose *g_shm; +static int g_eye_w, g_eye_h; /* the last eye viewport drawn, for the guest */ + +void vkp_cardboard_set_pose_file(const char *path) { + pthread_mutex_lock(&g_lock); + if (!g_shm && path && *path) { + int fd = open(path, O_RDWR | O_CREAT | O_CLOEXEC, 0600); + if (fd >= 0 && ftruncate(fd, sizeof(struct droiddeck_headpose)) == 0) { + void *p = mmap(NULL, sizeof(struct droiddeck_headpose), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (p != MAP_FAILED) { + g_shm = p; + memset(g_shm, 0, sizeof(*g_shm)); + g_shm->magic = DROIDDECK_HEADPOSE_MAGIC; + g_shm->version = DROIDDECK_HEADPOSE_VERSION; + memcpy(g_shm->rot, g_pose, sizeof(g_shm->rot)); + } + } + if (fd >= 0) close(fd); + droiddeck_log("cardboard", g_shm ? "head pose shared with the guest at %s" : "could not share the head pose at %s", path); + } + pthread_mutex_unlock(&g_lock); +} + +/* Under g_lock. */ +static void shm_publish(int live) { + if (!g_shm) return; + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + atomic_uint *seq = (atomic_uint *)&g_shm->seq; + unsigned s = atomic_load_explicit(seq, memory_order_relaxed); + atomic_store_explicit(seq, s | 1u, memory_order_relaxed); + atomic_thread_fence(memory_order_release); + memcpy(g_shm->rot, g_pose, sizeof(g_shm->rot)); + g_shm->ipd_m = g_set.ipd_m; + g_shm->flags = (live ? DROIDDECK_HEADPOSE_LIVE : 0u) | (g_set.enabled ? DROIDDECK_HEADPOSE_ENABLED : 0u); + g_shm->time_ns = (int64_t)ts.tv_sec * 1000000000LL + ts.tv_nsec; + g_shm->tan_half_fov = tanf(g_set.fov_deg * 0.5f * (float)M_PI / 180.0f); + g_shm->eye_w = (uint32_t)(g_eye_w > 0 ? g_eye_w : 0); + g_shm->eye_h = (uint32_t)(g_eye_h > 0 ? g_eye_h : 0); + g_shm->eye_aspect = g_eye_h > 0 ? (float)g_eye_w / (float)g_eye_h : 1.0f; + atomic_thread_fence(memory_order_release); + atomic_store_explicit(seq, (s | 1u) + 1u, memory_order_relaxed); +} + + +void vkp_cardboard_set_pose(const float m[9], int live) { + pthread_mutex_lock(&g_lock); + memcpy(g_pose, m, sizeof(g_pose)); + g_live = live; + if (g_set.enabled && g_set.source == CARDBOARD_SOURCE_SCREEN) g_changed = 1; + shm_publish(live); + pthread_mutex_unlock(&g_lock); +} + +static struct cardboard_params g_cur; +static float g_cur_pose[9]; + +static void snapshot(void) { + pthread_mutex_lock(&g_lock); + g_cur = g_set; + memcpy(g_cur_pose, g_pose, sizeof(g_cur_pose)); + pthread_mutex_unlock(&g_lock); +} + +int vkp_cardboard_active(void) { + pthread_mutex_lock(&g_lock); + int on = g_set.enabled; + pthread_mutex_unlock(&g_lock); + return on; +} + +int vkp_cardboard_pose_changed(void) { + if (!g_changed) return 0; + g_changed = 0; + return 1; +} + +/* ------------------------------------------------------------------ Vulkan objects */ + +#define CB_FORMAT VK_FORMAT_R8G8B8A8_UNORM +#define CB_PC_SIZE (4 * 4 + 24 * 4) /* ndc + the 24 floats: 112 bytes, under the 128 guaranteed */ +#define CB_MAX_SOURCES 8 /* source views per frame: the real frame + generated ones */ + +static VkDevice g_dev; +static VkPhysicalDeviceMemoryProperties g_mp; +static int g_ready; /* 0 = not built, 1 = ok, -1 = failed (said once) */ +static VkRenderPass g_rp; +static VkDescriptorSetLayout g_dsl; +static VkPipelineLayout g_pl; +static VkDescriptorPool g_dpool; +static VkSampler g_sampler; +static VkPipeline g_pipe; +static struct { VkImage img; VkDeviceMemory mem; VkImageView view; VkFramebuffer fb; int w, h; } g_t; +static struct { VkImageView view; VkDescriptorSet ds; } g_src[CB_MAX_SOURCES]; +static int g_nsrc; + +void vkp_cardboard_bind_device(VkDevice dev, const VkPhysicalDeviceMemoryProperties *memprops) { + g_dev = dev; + g_mp = *memprops; +} + +static int memtype(uint32_t bits, VkMemoryPropertyFlags want) { + for (uint32_t i = 0; i < g_mp.memoryTypeCount; i++) + if ((bits & (1u << i)) && (g_mp.memoryTypes[i].propertyFlags & want) == want) return (int)i; + return -1; +} + +static void sources_release(void) { + for (int i = 0; i < g_nsrc; i++) { + if (g_src[i].ds) g_vk.FreeDescriptorSets(g_dev, g_dpool, 1, &g_src[i].ds); + if (g_src[i].view) g_vk.DestroyImageView(g_dev, g_src[i].view, NULL); + } + memset(g_src, 0, sizeof(g_src)); + g_nsrc = 0; +} + +void vkp_cardboard_begin_frame(void) { + if (g_ready == 1) sources_release(); +} + +static void target_destroy(void) { + if (g_t.fb) g_vk.DestroyFramebuffer(g_dev, g_t.fb, NULL); + if (g_t.view) g_vk.DestroyImageView(g_dev, g_t.view, NULL); + if (g_t.img) g_vk.DestroyImage(g_dev, g_t.img, NULL); + if (g_t.mem) g_vk.FreeMemory(g_dev, g_t.mem, NULL); + memset(&g_t, 0, sizeof(g_t)); +} + +static VkImageView make_view(VkImage img) { + VkImageView v = VK_NULL_HANDLE; + VkImageViewCreateInfo ci = {.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = img, + .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = CB_FORMAT, + .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; + if (g_vk.CreateImageView(g_dev, &ci, NULL, &v) != VK_SUCCESS) return VK_NULL_HANDLE; + return v; +} + +/* The panel-sized output: rendered into, then blitted 1:1 to the swapchain. Kept uncompressed + * (mutable format) like the effect chain's targets: UBWC render-then-blit targets corrupt on + * Adreno 740. */ +static int target_ensure(int w, int h) { + if (g_t.img && g_t.w == w && g_t.h == h) return 0; + target_destroy(); + VkImageCreateInfo ii = {.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT, + .imageType = VK_IMAGE_TYPE_2D, .format = CB_FORMAT, .extent = {(uint32_t)w, (uint32_t)h, 1}, + .mipLevels = 1, .arrayLayers = 1, .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED}; + if (g_vk.CreateImage(g_dev, &ii, NULL, &g_t.img) != VK_SUCCESS) goto fail; + VkMemoryRequirements req; + g_vk.GetImageMemoryRequirements(g_dev, g_t.img, &req); + int idx = memtype(req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + if (idx < 0) idx = memtype(req.memoryTypeBits, 0); + VkMemoryAllocateInfo mai = {.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = req.size, + .memoryTypeIndex = (uint32_t)(idx < 0 ? 0 : idx)}; + if (g_vk.AllocateMemory(g_dev, &mai, NULL, &g_t.mem) != VK_SUCCESS) goto fail; + g_vk.BindImageMemory(g_dev, g_t.img, g_t.mem, 0); + if (!(g_t.view = make_view(g_t.img))) goto fail; + VkFramebufferCreateInfo fi = {.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .renderPass = g_rp, + .attachmentCount = 1, .pAttachments = &g_t.view, .width = (uint32_t)w, + .height = (uint32_t)h, .layers = 1}; + if (g_vk.CreateFramebuffer(g_dev, &fi, NULL, &g_t.fb) != VK_SUCCESS) goto fail; + g_t.w = w; + g_t.h = h; + droiddeck_log("cardboard", "eye target %dx%d (two %dx%d eyes)", w, h, w / 2, h); + return 0; +fail: + droiddeck_log("error", "cardboard: could not create the %dx%d eye target", w, h); + target_destroy(); + return -1; +} + +static VkShaderModule shader(const uint32_t *code, size_t size) { + VkShaderModule m = VK_NULL_HANDLE; + VkShaderModuleCreateInfo ci = {.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .codeSize = size, .pCode = code}; + if (g_vk.CreateShaderModule(g_dev, &ci, NULL, &m) != VK_SUCCESS) return VK_NULL_HANDLE; + return m; +} + +static void objects_destroy(void) { + if (!g_dev) return; + sources_release(); + target_destroy(); + if (g_pipe) g_vk.DestroyPipeline(g_dev, g_pipe, NULL); + if (g_dpool) g_vk.DestroyDescriptorPool(g_dev, g_dpool, NULL); + if (g_pl) g_vk.DestroyPipelineLayout(g_dev, g_pl, NULL); + if (g_dsl) g_vk.DestroyDescriptorSetLayout(g_dev, g_dsl, NULL); + if (g_sampler) g_vk.DestroySampler(g_dev, g_sampler, NULL); + if (g_rp) g_vk.DestroyRenderPass(g_dev, g_rp, NULL); + g_pipe = VK_NULL_HANDLE; g_dpool = VK_NULL_HANDLE; g_pl = VK_NULL_HANDLE; + g_dsl = VK_NULL_HANDLE; g_sampler = VK_NULL_HANDLE; g_rp = VK_NULL_HANDLE; +} + +static int objects_ensure(void) { + if (g_ready) return g_ready == 1 ? 0 : -1; + if (!g_dev || !g_vk.CreateGraphicsPipelines) goto fail; + { + /* One colour target, overwritten whole, left GENERAL for the 1:1 blit that follows. */ + VkAttachmentDescription att = {.format = CB_FORMAT, .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .finalLayout = VK_IMAGE_LAYOUT_GENERAL}; + VkAttachmentReference ref = {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; + VkSubpassDescription sub = {.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .colorAttachmentCount = 1, + .pColorAttachments = &ref}; + VkSubpassDependency deps[2] = { + {.srcSubpass = VK_SUBPASS_EXTERNAL, .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT}, + {.srcSubpass = 0, .dstSubpass = VK_SUBPASS_EXTERNAL, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT}}; + VkRenderPassCreateInfo rci = {.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .attachmentCount = 1, + .pAttachments = &att, .subpassCount = 1, .pSubpasses = &sub, + .dependencyCount = 2, .pDependencies = deps}; + if (g_vk.CreateRenderPass(g_dev, &rci, NULL, &g_rp) != VK_SUCCESS) goto fail; + + VkSamplerCreateInfo sci = {.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .magFilter = VK_FILTER_LINEAR, + .minFilter = VK_FILTER_LINEAR, .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE}; + if (g_vk.CreateSampler(g_dev, &sci, NULL, &g_sampler) != VK_SUCCESS) goto fail; + + VkDescriptorSetLayoutBinding b = {.binding = 0, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT}; + VkDescriptorSetLayoutCreateInfo dli = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .bindingCount = 1, .pBindings = &b}; + if (g_vk.CreateDescriptorSetLayout(g_dev, &dli, NULL, &g_dsl) != VK_SUCCESS) goto fail; + VkPushConstantRange pcr = {.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, + .offset = 0, .size = CB_PC_SIZE}; + VkPipelineLayoutCreateInfo pli = {.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1, + .pSetLayouts = &g_dsl, .pushConstantRangeCount = 1, .pPushConstantRanges = &pcr}; + if (g_vk.CreatePipelineLayout(g_dev, &pli, NULL, &g_pl) != VK_SUCCESS) goto fail; + VkDescriptorPoolSize ps = {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, CB_MAX_SOURCES}; + VkDescriptorPoolCreateInfo dpi = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, + .maxSets = CB_MAX_SOURCES, .poolSizeCount = 1, .pPoolSizes = &ps}; + if (g_vk.CreateDescriptorPool(g_dev, &dpi, NULL, &g_dpool) != VK_SUCCESS) goto fail; + + VkShaderModule vert = shader(cardboard_upscale_vert_code, sizeof(cardboard_upscale_vert_code)); + VkShaderModule frag = shader(cardboard_frag_code, sizeof(cardboard_frag_code)); + if (!vert || !frag) { + if (vert) g_vk.DestroyShaderModule(g_dev, vert, NULL); + if (frag) g_vk.DestroyShaderModule(g_dev, frag, NULL); + goto fail; + } + VkPipelineShaderStageCreateInfo st[2] = { + {.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = vert, .pName = "main"}, + {.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .module = frag, .pName = "main"}}; + VkPipelineVertexInputStateCreateInfo vi = {.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO}; + VkPipelineInputAssemblyStateCreateInfo ia = {.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP}; + VkDynamicState dyn[2] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; + VkPipelineDynamicStateCreateInfo ds = {.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .dynamicStateCount = 2, .pDynamicStates = dyn}; + VkPipelineViewportStateCreateInfo vp = {.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .viewportCount = 1, .scissorCount = 1}; + VkPipelineRasterizationStateCreateInfo rs = {.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .polygonMode = VK_POLYGON_MODE_FILL, .cullMode = VK_CULL_MODE_NONE, + .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, .lineWidth = 1.0f}; + VkPipelineMultisampleStateCreateInfo ms = {.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT}; + VkPipelineColorBlendAttachmentState ba = {.blendEnable = VK_FALSE, .colorWriteMask = 0xF}; + VkPipelineColorBlendStateCreateInfo cb = {.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .attachmentCount = 1, .pAttachments = &ba}; + VkGraphicsPipelineCreateInfo pi = {.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .stageCount = 2, + .pStages = st, .pVertexInputState = &vi, .pInputAssemblyState = &ia, + .pViewportState = &vp, .pRasterizationState = &rs, .pMultisampleState = &ms, + .pColorBlendState = &cb, .pDynamicState = &ds, .layout = g_pl, + .renderPass = g_rp, .subpass = 0}; + VkResult r = g_vk.CreateGraphicsPipelines(g_dev, VK_NULL_HANDLE, 1, &pi, NULL, &g_pipe); + g_vk.DestroyShaderModule(g_dev, vert, NULL); + g_vk.DestroyShaderModule(g_dev, frag, NULL); + if (r != VK_SUCCESS) goto fail; + } + g_ready = 1; + droiddeck_log("cardboard", "pass ready on %s", vkp_gpu_name()); + return 0; +fail: + objects_destroy(); + g_ready = -1; + droiddeck_log("error", "cardboard: the pass could not be built on this driver; frames are shown flat"); + return -1; +} + +static VkDescriptorSet source_ds(VkImage src) { + if (g_nsrc >= CB_MAX_SOURCES) return VK_NULL_HANDLE; + VkImageView view = make_view(src); + if (!view) return VK_NULL_HANDLE; + VkDescriptorSet ds = VK_NULL_HANDLE; + VkDescriptorSetAllocateInfo ai = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .descriptorPool = g_dpool, .descriptorSetCount = 1, .pSetLayouts = &g_dsl}; + if (g_vk.AllocateDescriptorSets(g_dev, &ai, &ds) != VK_SUCCESS) { + g_vk.DestroyImageView(g_dev, view, NULL); + return VK_NULL_HANDLE; + } + VkDescriptorImageInfo ii = {.sampler = g_sampler, .imageView = view, .imageLayout = VK_IMAGE_LAYOUT_GENERAL}; + VkWriteDescriptorSet wr = {.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = ds, .dstBinding = 0, + .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = &ii}; + g_vk.UpdateDescriptorSets(g_dev, 1, &wr, 0, NULL); + g_src[g_nsrc].view = view; + g_src[g_nsrc].ds = ds; + g_nsrc++; + return ds; +} + +VkImage vkp_cardboard_run(VkCommandBuffer cmd, VkImage src, int src_w, int src_h, int out_w, int out_h) { + if (out_w < 2 || out_h < 1 || objects_ensure() != 0 || target_ensure(out_w, out_h) != 0) return VK_NULL_HANDLE; + VkDescriptorSet ds = source_ds(src); + if (!ds) { + static int said; + if (!said) { said = 1; droiddeck_log("error", "cardboard: no descriptor for the frame; shown flat"); } + return VK_NULL_HANDLE; + } + if (g_eye_w != out_w / 2 || g_eye_h != out_h) { + pthread_mutex_lock(&g_lock); + g_eye_w = out_w / 2; + g_eye_h = out_h; + shm_publish(g_live); + pthread_mutex_unlock(&g_lock); + } + snapshot(); + struct { float ndc[4]; float rest[24]; } pc = {{-1.0f, -1.0f, 1.0f, 1.0f}, {0}}; + cardboard_push_constants(&g_cur, g_cur_pose, src_w, src_h, out_w, out_h, pc.rest); + + VkRenderPassBeginInfo rpi = {.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .renderPass = g_rp, + .framebuffer = g_t.fb, .renderArea = {{0, 0}, {(uint32_t)out_w, (uint32_t)out_h}}}; + VkViewport vp = {0, 0, (float)out_w, (float)out_h, 0, 1}; + VkRect2D sc = {{0, 0}, {(uint32_t)out_w, (uint32_t)out_h}}; + g_vk.CmdBeginRenderPass(cmd, &rpi, VK_SUBPASS_CONTENTS_INLINE); + g_vk.CmdSetViewport(cmd, 0, 1, &vp); + g_vk.CmdSetScissor(cmd, 0, 1, &sc); + g_vk.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, g_pipe); + g_vk.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, g_pl, 0, 1, &ds, 0, NULL); + g_vk.CmdPushConstants(cmd, g_pl, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(pc), &pc); + g_vk.CmdDraw(cmd, 4, 1, 0, 0); + g_vk.CmdEndRenderPass(cmd); + return g_t.img; +} + +#endif /* CARDBOARD_HOST_TEST */ diff --git a/app/src/main/cpp/waylandcomp/src/cardboard.frag b/app/src/main/cpp/waylandcomp/src/cardboard.frag new file mode 100644 index 00000000..55167fc8 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/cardboard.frag @@ -0,0 +1,83 @@ +#version 450 +// Cardboard pass (cardboard.c): the finished frame -> two eye viewports, side by side, each +// barrel-distorted for a phone-in-a-viewer lens. Runs on upscale.vert's full quad, so uv covers +// the whole panel with (0,0) at the top left. +// +// Source modes: +// 0 = virtual screen: the frame is a flat screen hanging in front of the viewer. Each eye casts +// a ray through its lens, rotated by the head pose, from its own position (half the IPD to +// either side), so the screen has real stereo depth and stays put while the head turns. +// 1 = side by side: the frame already holds a left and a right eye image (SteamVR, an SBS 3D +// game or video); each eye samples its own half. The head pose belongs to whoever rendered +// it, so none is applied here. +// +// Regenerate cardboard_frag.h after editing (glslangValidator, see cardboard.c). +layout(binding = 0) uniform sampler2D Source; +layout(location = 0) in vec2 uv; +layout(location = 0) out vec4 outColor; + +layout(push_constant) uniform PC { + vec4 ndc; // upscale.vert's quad + vec4 rot0; // xyz: head -> world rotation, row 0; w: half the IPD (m) + vec4 rot1; // xyz: row 1; w: distance to the virtual screen (m) + vec4 rot2; // xyz: row 2; w: half the virtual screen's width (m) + vec4 lens; // x: k1, y: k2, z: tan of half the eye's vertical field of view, w: chromatic spread + vec4 view; // x: lens centre inset (fraction of an eye viewport), y: source aspect w/h, + // z: source mode, w: eye viewport aspect w/h +} pc; + +const vec3 BACKDROP = vec3(0.015, 0.015, 0.02); + +// Lens: a point on the panel at tangent-space offset t (from the lens centre) shows the ray that +// leaves the eye at t * (1 + k1 r^2 + k2 r^4). Sampling outwards like this draws the picture +// barrel-shaped; the viewer's lens pincushions it back. +vec2 undistort(vec2 t, float scale) { + float r2 = dot(t, t); + return t * scale * (1.0 + pc.lens.x * r2 + pc.lens.y * r2 * r2); +} + +// What one eye sees along tangent direction tan (x right, y down): colour, or the backdrop. +vec3 eyeSample(vec2 tan, float eye) { + if (pc.view.z > 0.5) { + // Side by side: this eye's half of the source covers the eye's field of view. + float tanH = pc.lens.z; + vec2 s = vec2(tan.x / (tanH * pc.view.w), tan.y / tanH) * 0.5 + 0.5; + if (any(lessThan(s, vec2(0.0))) || any(greaterThan(s, vec2(1.0)))) return vec3(0.0); + return texture(Source, vec2((s.x + eye) * 0.5, s.y)).rgb; + } + // mat3() takes columns; the push constants carry rows. + mat3 R = transpose(mat3(pc.rot0.xyz, pc.rot1.xyz, pc.rot2.xyz)); + vec3 d = R * normalize(vec3(tan.x, -tan.y, -1.0)); + vec3 o = R * vec3((eye < 0.5 ? -1.0 : 1.0) * pc.rot0.w, 0.0, 0.0); + if (d.z > -1e-4) return BACKDROP; // looking away from the screen + float t = (-pc.rot1.w - o.z) / d.z; + vec3 hit = o + d * t; + float halfW = pc.rot2.w, halfH = halfW / pc.view.y; + vec2 s = vec2(hit.x / halfW, -hit.y / halfH) * 0.5 + 0.5; + if (any(lessThan(s, vec2(0.0))) || any(greaterThan(s, vec2(1.0)))) return BACKDROP; + return texture(Source, s).rgb; +} + +void main() { + float eye = uv.x < 0.5 ? 0.0 : 1.0; + vec2 local = vec2(uv.x * 2.0 - eye, uv.y); + // Lens centres sit inset towards the nose by view.x of a viewport. + float cx = 0.5 + (eye < 0.5 ? pc.view.x : -pc.view.x); + vec2 p = local - vec2(cx, 0.5); + float tanH = pc.lens.z; + vec2 t = vec2(p.x * pc.view.w, p.y) * 2.0 * tanH; + + vec3 c; + if (pc.lens.w > 0.0) { + // Chromatic aberration: the lens bends red least and blue most; pre-spread to match. + c.r = eyeSample(undistort(t, 1.0 - pc.lens.w), eye).r; + c.g = eyeSample(undistort(t, 1.0), eye).g; + c.b = eyeSample(undistort(t, 1.0 + pc.lens.w), eye).b; + } else { + c = eyeSample(undistort(t, 1.0), eye); + } + // Fade out towards each viewport's edge so the lens rim never shows a hard cut. + vec2 e = min(local, 1.0 - local); + float fade = smoothstep(0.0, 0.03, min(e.x * pc.view.w, e.y)); + outColor = vec4(c * fade, 1.0); +} diff --git a/app/src/main/cpp/waylandcomp/src/cardboard.h b/app/src/main/cpp/waylandcomp/src/cardboard.h new file mode 100644 index 00000000..ce39d864 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/cardboard.h @@ -0,0 +1,63 @@ +#ifndef CARDBOARD_H +#define CARDBOARD_H +/* + * Cardboard mode: the last stage of the compositor pass. The finished frame (after the effects and + * frame generation) is drawn twice, once per eye, side by side on the panel, barrel-distorted for a + * phone-in-a-viewer lens (cardboard.frag). The frame either hangs as a flat virtual screen in front + * of the viewer, world-locked by the phone's rotation sensor (3DoF), or is already a side-by-side + * stereo pair (SteamVR, SBS 3D content) that only gets the lens treatment. + * + * Present-pass order with Cardboard on (vk_present.c, render_impl): + * scene -> effects -> frame generation -> vkp_cardboard_run (whole panel) -> 1:1 blit -> present + * The Fullscreen Mode mapping does not apply: the pass fills the output itself. + * + * Settings and the head pose come from any thread (JNI, the sensor thread); the compositor thread + * snapshots them per frame. A new pose redraws even a static scene (vkp_cardboard_pose_changed), so + * the virtual screen stays put while the head turns. + */ +#define VK_USE_PLATFORM_ANDROID_KHR +#include + +enum { CARDBOARD_SOURCE_SCREEN = 0, CARDBOARD_SOURCE_SBS = 1 }; + +struct cardboard_params { + int enabled; + int source; /* CARDBOARD_SOURCE_* */ + float ipd_m; /* distance between the eyes, metres */ + float screen_dist_m; /* virtual screen distance, metres */ + float screen_width_m;/* virtual screen width, metres */ + float k1, k2; /* lens distortion coefficients */ + float fov_deg; /* vertical field of view of one eye before distortion */ + float lens_inset; /* lens centre inset towards the nose, fraction of an eye viewport */ + float chroma; /* chromatic aberration pre-correction (0 = off) */ +}; + +/* Any thread. Values are clamped to sane ranges. */ +void vkp_cardboard_set(const struct cardboard_params *p); +/* Any thread: the head -> world rotation, row-major 3x3. Identity = looking straight at the screen. + * live = a sensor produced it. Also published to the guest (headpose_shm.h) once a file is set. */ +void vkp_cardboard_set_pose(const float m[9], int live); +/* Any thread, once: where the guest's copy of the pose lives (/.wayland-rt/droiddeck-headpose). */ +void vkp_cardboard_set_pose_file(const char *path); + +/* Compositor thread. */ +void vkp_cardboard_bind_device(VkDevice dev, const VkPhysicalDeviceMemoryProperties *memprops); +/* 1 while Cardboard mode is on (the frame must take the compositor pass). */ +int vkp_cardboard_active(void); +/* 1 when the settings or the pose changed since the last call: the caller redraws. */ +int vkp_cardboard_pose_changed(void); +/* Called once per frame before any vkp_cardboard_run: releases the previous frame's source views + * (that frame is fenced by now). */ +void vkp_cardboard_begin_frame(void); +/* Records the pass into cmd: src (src_w x src_h, R8G8B8A8, in GENERAL) -> a panel-sized image + * (out_w x out_h), returned in GENERAL. VK_NULL_HANDLE when the pass is unavailable (the caller + * presents the frame the usual way). */ +VkImage vkp_cardboard_run(VkCommandBuffer cmd, VkImage src, int src_w, int src_h, int out_w, int out_h); + +/* Pure helpers, shared with the host test (tools/tests). */ +void cardboard_clamp(struct cardboard_params *p); +/* The 24 floats after the quad's ndc in the shader's push constants. */ +void cardboard_push_constants(const struct cardboard_params *p, const float pose[9], + int src_w, int src_h, int out_w, int out_h, float out[24]); + +#endif diff --git a/app/src/main/cpp/waylandcomp/src/cardboard_frag.h b/app/src/main/cpp/waylandcomp/src/cardboard_frag.h new file mode 100644 index 00000000..7b60cd1f --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/cardboard_frag.h @@ -0,0 +1,266 @@ + // 1116.2.0 + #pragma once +const uint32_t cardboard_frag_code[] = { + 0x07230203,0x00010000,0x0008000b,0x0000016d,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x000000ee,0x00000165,0x00030010, + 0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, + 0x00000000,0x00070005,0x0000000d,0x69646e75,0x726f7473,0x66762874,0x31663b32,0x0000003b, + 0x00030005,0x0000000b,0x00000074,0x00040005,0x0000000c,0x6c616373,0x00000065,0x00070005, + 0x00000013,0x53657965,0x6c706d61,0x66762865,0x31663b32,0x0000003b,0x00030005,0x00000011, + 0x006e6174,0x00030005,0x00000012,0x00657965,0x00030005,0x00000015,0x00003272,0x00030005, + 0x0000001e,0x00004350,0x00040006,0x0000001e,0x00000000,0x0063646e,0x00050006,0x0000001e, + 0x00000001,0x30746f72,0x00000000,0x00050006,0x0000001e,0x00000002,0x31746f72,0x00000000, + 0x00050006,0x0000001e,0x00000003,0x32746f72,0x00000000,0x00050006,0x0000001e,0x00000004, + 0x736e656c,0x00000000,0x00050006,0x0000001e,0x00000005,0x77656976,0x00000000,0x00030005, + 0x00000020,0x00006370,0x00040005,0x0000003f,0x486e6174,0x00000000,0x00030005,0x00000042, + 0x00000073,0x00040005,0x00000068,0x72756f53,0x00006563,0x00030005,0x00000077,0x00000052, + 0x00030005,0x00000094,0x00000064,0x00030005,0x0000009f,0x0000006f,0x00030005,0x000000b3, + 0x00000074,0x00030005,0x000000bd,0x00746968,0x00040005,0x000000c3,0x666c6168,0x00000057, + 0x00040005,0x000000c6,0x666c6168,0x00000048,0x00030005,0x000000cb,0x00000073,0x00030005, + 0x000000ec,0x00657965,0x00030005,0x000000ee,0x00007675,0x00040005,0x000000f4,0x61636f6c, + 0x0000006c,0x00030005,0x000000fe,0x00007863,0x00030005,0x0000010c,0x00000070,0x00040005, + 0x00000111,0x486e6174,0x00000000,0x00030005,0x00000114,0x00000074,0x00030005,0x00000125, + 0x00000063,0x00040005,0x00000129,0x61726170,0x0000006d,0x00040005,0x0000012b,0x61726170, + 0x0000006d,0x00040005,0x0000012d,0x61726170,0x0000006d,0x00040005,0x0000012e,0x61726170, + 0x0000006d,0x00040005,0x00000133,0x61726170,0x0000006d,0x00040005,0x00000135,0x61726170, + 0x0000006d,0x00040005,0x00000137,0x61726170,0x0000006d,0x00040005,0x00000138,0x61726170, + 0x0000006d,0x00040005,0x00000140,0x61726170,0x0000006d,0x00040005,0x00000142,0x61726170, + 0x0000006d,0x00040005,0x00000144,0x61726170,0x0000006d,0x00040005,0x00000145,0x61726170, + 0x0000006d,0x00040005,0x0000014b,0x61726170,0x0000006d,0x00040005,0x0000014d,0x61726170, + 0x0000006d,0x00040005,0x0000014f,0x61726170,0x0000006d,0x00040005,0x00000150,0x61726170, + 0x0000006d,0x00030005,0x00000153,0x00000065,0x00040005,0x00000159,0x65646166,0x00000000, + 0x00050005,0x00000165,0x4374756f,0x726f6c6f,0x00000000,0x00030047,0x0000001e,0x00000002, + 0x00050048,0x0000001e,0x00000000,0x00000023,0x00000000,0x00050048,0x0000001e,0x00000001, + 0x00000023,0x00000010,0x00050048,0x0000001e,0x00000002,0x00000023,0x00000020,0x00050048, + 0x0000001e,0x00000003,0x00000023,0x00000030,0x00050048,0x0000001e,0x00000004,0x00000023, + 0x00000040,0x00050048,0x0000001e,0x00000005,0x00000023,0x00000050,0x00040047,0x00000068, + 0x00000021,0x00000000,0x00040047,0x00000068,0x00000022,0x00000000,0x00040047,0x000000ee, + 0x0000001e,0x00000000,0x00040047,0x00000165,0x0000001e,0x00000000,0x00020013,0x00000002, + 0x00030021,0x00000003,0x00000002,0x00030016,0x00000006,0x00000020,0x00040017,0x00000007, + 0x00000006,0x00000002,0x00040020,0x00000008,0x00000007,0x00000007,0x00040020,0x00000009, + 0x00000007,0x00000006,0x00050021,0x0000000a,0x00000007,0x00000008,0x00000009,0x00040017, + 0x0000000f,0x00000006,0x00000003,0x00050021,0x00000010,0x0000000f,0x00000008,0x00000009, + 0x0004002b,0x00000006,0x0000001c,0x3f800000,0x00040017,0x0000001d,0x00000006,0x00000004, + 0x0008001e,0x0000001e,0x0000001d,0x0000001d,0x0000001d,0x0000001d,0x0000001d,0x0000001d, + 0x00040020,0x0000001f,0x00000009,0x0000001e,0x0004003b,0x0000001f,0x00000020,0x00000009, + 0x00040015,0x00000021,0x00000020,0x00000001,0x0004002b,0x00000021,0x00000022,0x00000004, + 0x00040015,0x00000023,0x00000020,0x00000000,0x0004002b,0x00000023,0x00000024,0x00000000, + 0x00040020,0x00000025,0x00000009,0x00000006,0x0004002b,0x00000023,0x0000002b,0x00000001, + 0x0004002b,0x00000021,0x00000036,0x00000005,0x0004002b,0x00000023,0x00000037,0x00000002, + 0x0004002b,0x00000006,0x0000003a,0x3f000000,0x00020014,0x0000003b,0x0004002b,0x00000023, + 0x00000046,0x00000003,0x0004002b,0x00000006,0x00000054,0x00000000,0x0005002c,0x00000007, + 0x00000055,0x00000054,0x00000054,0x00040017,0x00000056,0x0000003b,0x00000002,0x0005002c, + 0x00000007,0x0000005d,0x0000001c,0x0000001c,0x0006002c,0x0000000f,0x00000063,0x00000054, + 0x00000054,0x00000054,0x00090019,0x00000065,0x00000006,0x00000001,0x00000000,0x00000000, + 0x00000000,0x00000001,0x00000000,0x0003001b,0x00000066,0x00000065,0x00040020,0x00000067, + 0x00000000,0x00000066,0x0004003b,0x00000067,0x00000068,0x00000000,0x00040018,0x00000075, + 0x0000000f,0x00000003,0x00040020,0x00000076,0x00000007,0x00000075,0x0004002b,0x00000021, + 0x00000078,0x00000001,0x00040020,0x00000079,0x00000009,0x0000001d,0x0004002b,0x00000021, + 0x0000007d,0x00000002,0x0004002b,0x00000021,0x00000081,0x00000003,0x00040020,0x00000093, + 0x00000007,0x0000000f,0x0004002b,0x00000006,0x0000009b,0xbf800000,0x0004002b,0x00000006, + 0x000000ab,0xb8d1b717,0x0004002b,0x00000006,0x000000af,0x3c75c28f,0x0004002b,0x00000006, + 0x000000b0,0x3ca3d70a,0x0006002c,0x0000000f,0x000000b1,0x000000af,0x000000af,0x000000b0, + 0x00040020,0x000000ed,0x00000001,0x00000007,0x0004003b,0x000000ed,0x000000ee,0x00000001, + 0x00040020,0x000000ef,0x00000001,0x00000006,0x0004002b,0x00000006,0x000000f7,0x40000000, + 0x0004002b,0x00000006,0x0000015a,0x3cf5c28f,0x00040020,0x00000164,0x00000003,0x0000001d, + 0x0004003b,0x00000164,0x00000165,0x00000003,0x00050036,0x00000002,0x00000004,0x00000000, + 0x00000003,0x000200f8,0x00000005,0x0004003b,0x00000009,0x000000ec,0x00000007,0x0004003b, + 0x00000008,0x000000f4,0x00000007,0x0004003b,0x00000009,0x000000fe,0x00000007,0x0004003b, + 0x00000009,0x00000101,0x00000007,0x0004003b,0x00000008,0x0000010c,0x00000007,0x0004003b, + 0x00000009,0x00000111,0x00000007,0x0004003b,0x00000008,0x00000114,0x00000007,0x0004003b, + 0x00000093,0x00000125,0x00000007,0x0004003b,0x00000008,0x00000129,0x00000007,0x0004003b, + 0x00000009,0x0000012b,0x00000007,0x0004003b,0x00000008,0x0000012d,0x00000007,0x0004003b, + 0x00000009,0x0000012e,0x00000007,0x0004003b,0x00000008,0x00000133,0x00000007,0x0004003b, + 0x00000009,0x00000135,0x00000007,0x0004003b,0x00000008,0x00000137,0x00000007,0x0004003b, + 0x00000009,0x00000138,0x00000007,0x0004003b,0x00000008,0x00000140,0x00000007,0x0004003b, + 0x00000009,0x00000142,0x00000007,0x0004003b,0x00000008,0x00000144,0x00000007,0x0004003b, + 0x00000009,0x00000145,0x00000007,0x0004003b,0x00000008,0x0000014b,0x00000007,0x0004003b, + 0x00000009,0x0000014d,0x00000007,0x0004003b,0x00000008,0x0000014f,0x00000007,0x0004003b, + 0x00000009,0x00000150,0x00000007,0x0004003b,0x00000008,0x00000153,0x00000007,0x0004003b, + 0x00000009,0x00000159,0x00000007,0x00050041,0x000000ef,0x000000f0,0x000000ee,0x00000024, + 0x0004003d,0x00000006,0x000000f1,0x000000f0,0x000500b8,0x0000003b,0x000000f2,0x000000f1, + 0x0000003a,0x000600a9,0x00000006,0x000000f3,0x000000f2,0x00000054,0x0000001c,0x0003003e, + 0x000000ec,0x000000f3,0x00050041,0x000000ef,0x000000f5,0x000000ee,0x00000024,0x0004003d, + 0x00000006,0x000000f6,0x000000f5,0x00050085,0x00000006,0x000000f8,0x000000f6,0x000000f7, + 0x0004003d,0x00000006,0x000000f9,0x000000ec,0x00050083,0x00000006,0x000000fa,0x000000f8, + 0x000000f9,0x00050041,0x000000ef,0x000000fb,0x000000ee,0x0000002b,0x0004003d,0x00000006, + 0x000000fc,0x000000fb,0x00050050,0x00000007,0x000000fd,0x000000fa,0x000000fc,0x0003003e, + 0x000000f4,0x000000fd,0x0004003d,0x00000006,0x000000ff,0x000000ec,0x000500b8,0x0000003b, + 0x00000100,0x000000ff,0x0000003a,0x000300f7,0x00000103,0x00000000,0x000400fa,0x00000100, + 0x00000102,0x00000106,0x000200f8,0x00000102,0x00060041,0x00000025,0x00000104,0x00000020, + 0x00000036,0x00000024,0x0004003d,0x00000006,0x00000105,0x00000104,0x0003003e,0x00000101, + 0x00000105,0x000200f9,0x00000103,0x000200f8,0x00000106,0x00060041,0x00000025,0x00000107, + 0x00000020,0x00000036,0x00000024,0x0004003d,0x00000006,0x00000108,0x00000107,0x0004007f, + 0x00000006,0x00000109,0x00000108,0x0003003e,0x00000101,0x00000109,0x000200f9,0x00000103, + 0x000200f8,0x00000103,0x0004003d,0x00000006,0x0000010a,0x00000101,0x00050081,0x00000006, + 0x0000010b,0x0000003a,0x0000010a,0x0003003e,0x000000fe,0x0000010b,0x0004003d,0x00000007, + 0x0000010d,0x000000f4,0x0004003d,0x00000006,0x0000010e,0x000000fe,0x00050050,0x00000007, + 0x0000010f,0x0000010e,0x0000003a,0x00050083,0x00000007,0x00000110,0x0000010d,0x0000010f, + 0x0003003e,0x0000010c,0x00000110,0x00060041,0x00000025,0x00000112,0x00000020,0x00000022, + 0x00000037,0x0004003d,0x00000006,0x00000113,0x00000112,0x0003003e,0x00000111,0x00000113, + 0x00050041,0x00000009,0x00000115,0x0000010c,0x00000024,0x0004003d,0x00000006,0x00000116, + 0x00000115,0x00060041,0x00000025,0x00000117,0x00000020,0x00000036,0x00000046,0x0004003d, + 0x00000006,0x00000118,0x00000117,0x00050085,0x00000006,0x00000119,0x00000116,0x00000118, + 0x00050041,0x00000009,0x0000011a,0x0000010c,0x0000002b,0x0004003d,0x00000006,0x0000011b, + 0x0000011a,0x00050050,0x00000007,0x0000011c,0x00000119,0x0000011b,0x0005008e,0x00000007, + 0x0000011d,0x0000011c,0x000000f7,0x0004003d,0x00000006,0x0000011e,0x00000111,0x0005008e, + 0x00000007,0x0000011f,0x0000011d,0x0000011e,0x0003003e,0x00000114,0x0000011f,0x00060041, + 0x00000025,0x00000120,0x00000020,0x00000022,0x00000046,0x0004003d,0x00000006,0x00000121, + 0x00000120,0x000500ba,0x0000003b,0x00000122,0x00000121,0x00000054,0x000300f7,0x00000124, + 0x00000000,0x000400fa,0x00000122,0x00000123,0x0000014a,0x000200f8,0x00000123,0x00060041, + 0x00000025,0x00000126,0x00000020,0x00000022,0x00000046,0x0004003d,0x00000006,0x00000127, + 0x00000126,0x00050083,0x00000006,0x00000128,0x0000001c,0x00000127,0x0004003d,0x00000007, + 0x0000012a,0x00000114,0x0003003e,0x00000129,0x0000012a,0x0003003e,0x0000012b,0x00000128, + 0x00060039,0x00000007,0x0000012c,0x0000000d,0x00000129,0x0000012b,0x0003003e,0x0000012d, + 0x0000012c,0x0004003d,0x00000006,0x0000012f,0x000000ec,0x0003003e,0x0000012e,0x0000012f, + 0x00060039,0x0000000f,0x00000130,0x00000013,0x0000012d,0x0000012e,0x00050051,0x00000006, + 0x00000131,0x00000130,0x00000000,0x00050041,0x00000009,0x00000132,0x00000125,0x00000024, + 0x0003003e,0x00000132,0x00000131,0x0004003d,0x00000007,0x00000134,0x00000114,0x0003003e, + 0x00000133,0x00000134,0x0003003e,0x00000135,0x0000001c,0x00060039,0x00000007,0x00000136, + 0x0000000d,0x00000133,0x00000135,0x0003003e,0x00000137,0x00000136,0x0004003d,0x00000006, + 0x00000139,0x000000ec,0x0003003e,0x00000138,0x00000139,0x00060039,0x0000000f,0x0000013a, + 0x00000013,0x00000137,0x00000138,0x00050051,0x00000006,0x0000013b,0x0000013a,0x00000001, + 0x00050041,0x00000009,0x0000013c,0x00000125,0x0000002b,0x0003003e,0x0000013c,0x0000013b, + 0x00060041,0x00000025,0x0000013d,0x00000020,0x00000022,0x00000046,0x0004003d,0x00000006, + 0x0000013e,0x0000013d,0x00050081,0x00000006,0x0000013f,0x0000001c,0x0000013e,0x0004003d, + 0x00000007,0x00000141,0x00000114,0x0003003e,0x00000140,0x00000141,0x0003003e,0x00000142, + 0x0000013f,0x00060039,0x00000007,0x00000143,0x0000000d,0x00000140,0x00000142,0x0003003e, + 0x00000144,0x00000143,0x0004003d,0x00000006,0x00000146,0x000000ec,0x0003003e,0x00000145, + 0x00000146,0x00060039,0x0000000f,0x00000147,0x00000013,0x00000144,0x00000145,0x00050051, + 0x00000006,0x00000148,0x00000147,0x00000002,0x00050041,0x00000009,0x00000149,0x00000125, + 0x00000037,0x0003003e,0x00000149,0x00000148,0x000200f9,0x00000124,0x000200f8,0x0000014a, + 0x0004003d,0x00000007,0x0000014c,0x00000114,0x0003003e,0x0000014b,0x0000014c,0x0003003e, + 0x0000014d,0x0000001c,0x00060039,0x00000007,0x0000014e,0x0000000d,0x0000014b,0x0000014d, + 0x0003003e,0x0000014f,0x0000014e,0x0004003d,0x00000006,0x00000151,0x000000ec,0x0003003e, + 0x00000150,0x00000151,0x00060039,0x0000000f,0x00000152,0x00000013,0x0000014f,0x00000150, + 0x0003003e,0x00000125,0x00000152,0x000200f9,0x00000124,0x000200f8,0x00000124,0x0004003d, + 0x00000007,0x00000154,0x000000f4,0x0004003d,0x00000007,0x00000155,0x000000f4,0x00050050, + 0x00000007,0x00000156,0x0000001c,0x0000001c,0x00050083,0x00000007,0x00000157,0x00000156, + 0x00000155,0x0007000c,0x00000007,0x00000158,0x00000001,0x00000025,0x00000154,0x00000157, + 0x0003003e,0x00000153,0x00000158,0x00050041,0x00000009,0x0000015b,0x00000153,0x00000024, + 0x0004003d,0x00000006,0x0000015c,0x0000015b,0x00060041,0x00000025,0x0000015d,0x00000020, + 0x00000036,0x00000046,0x0004003d,0x00000006,0x0000015e,0x0000015d,0x00050085,0x00000006, + 0x0000015f,0x0000015c,0x0000015e,0x00050041,0x00000009,0x00000160,0x00000153,0x0000002b, + 0x0004003d,0x00000006,0x00000161,0x00000160,0x0007000c,0x00000006,0x00000162,0x00000001, + 0x00000025,0x0000015f,0x00000161,0x0008000c,0x00000006,0x00000163,0x00000001,0x00000031, + 0x00000054,0x0000015a,0x00000162,0x0003003e,0x00000159,0x00000163,0x0004003d,0x0000000f, + 0x00000166,0x00000125,0x0004003d,0x00000006,0x00000167,0x00000159,0x0005008e,0x0000000f, + 0x00000168,0x00000166,0x00000167,0x00050051,0x00000006,0x00000169,0x00000168,0x00000000, + 0x00050051,0x00000006,0x0000016a,0x00000168,0x00000001,0x00050051,0x00000006,0x0000016b, + 0x00000168,0x00000002,0x00070050,0x0000001d,0x0000016c,0x00000169,0x0000016a,0x0000016b, + 0x0000001c,0x0003003e,0x00000165,0x0000016c,0x000100fd,0x00010038,0x00050036,0x00000007, + 0x0000000d,0x00000000,0x0000000a,0x00030037,0x00000008,0x0000000b,0x00030037,0x00000009, + 0x0000000c,0x000200f8,0x0000000e,0x0004003b,0x00000009,0x00000015,0x00000007,0x0004003d, + 0x00000007,0x00000016,0x0000000b,0x0004003d,0x00000007,0x00000017,0x0000000b,0x00050094, + 0x00000006,0x00000018,0x00000016,0x00000017,0x0003003e,0x00000015,0x00000018,0x0004003d, + 0x00000007,0x00000019,0x0000000b,0x0004003d,0x00000006,0x0000001a,0x0000000c,0x0005008e, + 0x00000007,0x0000001b,0x00000019,0x0000001a,0x00060041,0x00000025,0x00000026,0x00000020, + 0x00000022,0x00000024,0x0004003d,0x00000006,0x00000027,0x00000026,0x0004003d,0x00000006, + 0x00000028,0x00000015,0x00050085,0x00000006,0x00000029,0x00000027,0x00000028,0x00050081, + 0x00000006,0x0000002a,0x0000001c,0x00000029,0x00060041,0x00000025,0x0000002c,0x00000020, + 0x00000022,0x0000002b,0x0004003d,0x00000006,0x0000002d,0x0000002c,0x0004003d,0x00000006, + 0x0000002e,0x00000015,0x00050085,0x00000006,0x0000002f,0x0000002d,0x0000002e,0x0004003d, + 0x00000006,0x00000030,0x00000015,0x00050085,0x00000006,0x00000031,0x0000002f,0x00000030, + 0x00050081,0x00000006,0x00000032,0x0000002a,0x00000031,0x0005008e,0x00000007,0x00000033, + 0x0000001b,0x00000032,0x000200fe,0x00000033,0x00010038,0x00050036,0x0000000f,0x00000013, + 0x00000000,0x00000010,0x00030037,0x00000008,0x00000011,0x00030037,0x00000009,0x00000012, + 0x000200f8,0x00000014,0x0004003b,0x00000009,0x0000003f,0x00000007,0x0004003b,0x00000008, + 0x00000042,0x00000007,0x0004003b,0x00000076,0x00000077,0x00000007,0x0004003b,0x00000093, + 0x00000094,0x00000007,0x0004003b,0x00000093,0x0000009f,0x00000007,0x0004003b,0x00000009, + 0x000000b3,0x00000007,0x0004003b,0x00000093,0x000000bd,0x00000007,0x0004003b,0x00000009, + 0x000000c3,0x00000007,0x0004003b,0x00000009,0x000000c6,0x00000007,0x0004003b,0x00000008, + 0x000000cb,0x00000007,0x00060041,0x00000025,0x00000038,0x00000020,0x00000036,0x00000037, + 0x0004003d,0x00000006,0x00000039,0x00000038,0x000500ba,0x0000003b,0x0000003c,0x00000039, + 0x0000003a,0x000300f7,0x0000003e,0x00000000,0x000400fa,0x0000003c,0x0000003d,0x0000003e, + 0x000200f8,0x0000003d,0x00060041,0x00000025,0x00000040,0x00000020,0x00000022,0x00000037, + 0x0004003d,0x00000006,0x00000041,0x00000040,0x0003003e,0x0000003f,0x00000041,0x00050041, + 0x00000009,0x00000043,0x00000011,0x00000024,0x0004003d,0x00000006,0x00000044,0x00000043, + 0x0004003d,0x00000006,0x00000045,0x0000003f,0x00060041,0x00000025,0x00000047,0x00000020, + 0x00000036,0x00000046,0x0004003d,0x00000006,0x00000048,0x00000047,0x00050085,0x00000006, + 0x00000049,0x00000045,0x00000048,0x00050088,0x00000006,0x0000004a,0x00000044,0x00000049, + 0x00050041,0x00000009,0x0000004b,0x00000011,0x0000002b,0x0004003d,0x00000006,0x0000004c, + 0x0000004b,0x0004003d,0x00000006,0x0000004d,0x0000003f,0x00050088,0x00000006,0x0000004e, + 0x0000004c,0x0000004d,0x00050050,0x00000007,0x0000004f,0x0000004a,0x0000004e,0x0005008e, + 0x00000007,0x00000050,0x0000004f,0x0000003a,0x00050050,0x00000007,0x00000051,0x0000003a, + 0x0000003a,0x00050081,0x00000007,0x00000052,0x00000050,0x00000051,0x0003003e,0x00000042, + 0x00000052,0x0004003d,0x00000007,0x00000053,0x00000042,0x000500b8,0x00000056,0x00000057, + 0x00000053,0x00000055,0x0004009a,0x0000003b,0x00000058,0x00000057,0x000400a8,0x0000003b, + 0x00000059,0x00000058,0x000300f7,0x0000005b,0x00000000,0x000400fa,0x00000059,0x0000005a, + 0x0000005b,0x000200f8,0x0000005a,0x0004003d,0x00000007,0x0000005c,0x00000042,0x000500ba, + 0x00000056,0x0000005e,0x0000005c,0x0000005d,0x0004009a,0x0000003b,0x0000005f,0x0000005e, + 0x000200f9,0x0000005b,0x000200f8,0x0000005b,0x000700f5,0x0000003b,0x00000060,0x00000058, + 0x0000003d,0x0000005f,0x0000005a,0x000300f7,0x00000062,0x00000000,0x000400fa,0x00000060, + 0x00000061,0x00000062,0x000200f8,0x00000061,0x000200fe,0x00000063,0x000200f8,0x00000062, + 0x0004003d,0x00000066,0x00000069,0x00000068,0x00050041,0x00000009,0x0000006a,0x00000042, + 0x00000024,0x0004003d,0x00000006,0x0000006b,0x0000006a,0x0004003d,0x00000006,0x0000006c, + 0x00000012,0x00050081,0x00000006,0x0000006d,0x0000006b,0x0000006c,0x00050085,0x00000006, + 0x0000006e,0x0000006d,0x0000003a,0x00050041,0x00000009,0x0000006f,0x00000042,0x0000002b, + 0x0004003d,0x00000006,0x00000070,0x0000006f,0x00050050,0x00000007,0x00000071,0x0000006e, + 0x00000070,0x00050057,0x0000001d,0x00000072,0x00000069,0x00000071,0x0008004f,0x0000000f, + 0x00000073,0x00000072,0x00000072,0x00000000,0x00000001,0x00000002,0x000200fe,0x00000073, + 0x000200f8,0x0000003e,0x00050041,0x00000079,0x0000007a,0x00000020,0x00000078,0x0004003d, + 0x0000001d,0x0000007b,0x0000007a,0x0008004f,0x0000000f,0x0000007c,0x0000007b,0x0000007b, + 0x00000000,0x00000001,0x00000002,0x00050041,0x00000079,0x0000007e,0x00000020,0x0000007d, + 0x0004003d,0x0000001d,0x0000007f,0x0000007e,0x0008004f,0x0000000f,0x00000080,0x0000007f, + 0x0000007f,0x00000000,0x00000001,0x00000002,0x00050041,0x00000079,0x00000082,0x00000020, + 0x00000081,0x0004003d,0x0000001d,0x00000083,0x00000082,0x0008004f,0x0000000f,0x00000084, + 0x00000083,0x00000083,0x00000000,0x00000001,0x00000002,0x00050051,0x00000006,0x00000085, + 0x0000007c,0x00000000,0x00050051,0x00000006,0x00000086,0x0000007c,0x00000001,0x00050051, + 0x00000006,0x00000087,0x0000007c,0x00000002,0x00050051,0x00000006,0x00000088,0x00000080, + 0x00000000,0x00050051,0x00000006,0x00000089,0x00000080,0x00000001,0x00050051,0x00000006, + 0x0000008a,0x00000080,0x00000002,0x00050051,0x00000006,0x0000008b,0x00000084,0x00000000, + 0x00050051,0x00000006,0x0000008c,0x00000084,0x00000001,0x00050051,0x00000006,0x0000008d, + 0x00000084,0x00000002,0x00060050,0x0000000f,0x0000008e,0x00000085,0x00000086,0x00000087, + 0x00060050,0x0000000f,0x0000008f,0x00000088,0x00000089,0x0000008a,0x00060050,0x0000000f, + 0x00000090,0x0000008b,0x0000008c,0x0000008d,0x00060050,0x00000075,0x00000091,0x0000008e, + 0x0000008f,0x00000090,0x00040054,0x00000075,0x00000092,0x00000091,0x0003003e,0x00000077, + 0x00000092,0x0004003d,0x00000075,0x00000095,0x00000077,0x00050041,0x00000009,0x00000096, + 0x00000011,0x00000024,0x0004003d,0x00000006,0x00000097,0x00000096,0x00050041,0x00000009, + 0x00000098,0x00000011,0x0000002b,0x0004003d,0x00000006,0x00000099,0x00000098,0x0004007f, + 0x00000006,0x0000009a,0x00000099,0x00060050,0x0000000f,0x0000009c,0x00000097,0x0000009a, + 0x0000009b,0x0006000c,0x0000000f,0x0000009d,0x00000001,0x00000045,0x0000009c,0x00050091, + 0x0000000f,0x0000009e,0x00000095,0x0000009d,0x0003003e,0x00000094,0x0000009e,0x0004003d, + 0x00000075,0x000000a0,0x00000077,0x0004003d,0x00000006,0x000000a1,0x00000012,0x000500b8, + 0x0000003b,0x000000a2,0x000000a1,0x0000003a,0x000600a9,0x00000006,0x000000a3,0x000000a2, + 0x0000009b,0x0000001c,0x00060041,0x00000025,0x000000a4,0x00000020,0x00000078,0x00000046, + 0x0004003d,0x00000006,0x000000a5,0x000000a4,0x00050085,0x00000006,0x000000a6,0x000000a3, + 0x000000a5,0x00060050,0x0000000f,0x000000a7,0x000000a6,0x00000054,0x00000054,0x00050091, + 0x0000000f,0x000000a8,0x000000a0,0x000000a7,0x0003003e,0x0000009f,0x000000a8,0x00050041, + 0x00000009,0x000000a9,0x00000094,0x00000037,0x0004003d,0x00000006,0x000000aa,0x000000a9, + 0x000500ba,0x0000003b,0x000000ac,0x000000aa,0x000000ab,0x000300f7,0x000000ae,0x00000000, + 0x000400fa,0x000000ac,0x000000ad,0x000000ae,0x000200f8,0x000000ad,0x000200fe,0x000000b1, + 0x000200f8,0x000000ae,0x00060041,0x00000025,0x000000b4,0x00000020,0x0000007d,0x00000046, + 0x0004003d,0x00000006,0x000000b5,0x000000b4,0x0004007f,0x00000006,0x000000b6,0x000000b5, + 0x00050041,0x00000009,0x000000b7,0x0000009f,0x00000037,0x0004003d,0x00000006,0x000000b8, + 0x000000b7,0x00050083,0x00000006,0x000000b9,0x000000b6,0x000000b8,0x00050041,0x00000009, + 0x000000ba,0x00000094,0x00000037,0x0004003d,0x00000006,0x000000bb,0x000000ba,0x00050088, + 0x00000006,0x000000bc,0x000000b9,0x000000bb,0x0003003e,0x000000b3,0x000000bc,0x0004003d, + 0x0000000f,0x000000be,0x0000009f,0x0004003d,0x0000000f,0x000000bf,0x00000094,0x0004003d, + 0x00000006,0x000000c0,0x000000b3,0x0005008e,0x0000000f,0x000000c1,0x000000bf,0x000000c0, + 0x00050081,0x0000000f,0x000000c2,0x000000be,0x000000c1,0x0003003e,0x000000bd,0x000000c2, + 0x00060041,0x00000025,0x000000c4,0x00000020,0x00000081,0x00000046,0x0004003d,0x00000006, + 0x000000c5,0x000000c4,0x0003003e,0x000000c3,0x000000c5,0x0004003d,0x00000006,0x000000c7, + 0x000000c3,0x00060041,0x00000025,0x000000c8,0x00000020,0x00000036,0x0000002b,0x0004003d, + 0x00000006,0x000000c9,0x000000c8,0x00050088,0x00000006,0x000000ca,0x000000c7,0x000000c9, + 0x0003003e,0x000000c6,0x000000ca,0x00050041,0x00000009,0x000000cc,0x000000bd,0x00000024, + 0x0004003d,0x00000006,0x000000cd,0x000000cc,0x0004003d,0x00000006,0x000000ce,0x000000c3, + 0x00050088,0x00000006,0x000000cf,0x000000cd,0x000000ce,0x00050041,0x00000009,0x000000d0, + 0x000000bd,0x0000002b,0x0004003d,0x00000006,0x000000d1,0x000000d0,0x0004007f,0x00000006, + 0x000000d2,0x000000d1,0x0004003d,0x00000006,0x000000d3,0x000000c6,0x00050088,0x00000006, + 0x000000d4,0x000000d2,0x000000d3,0x00050050,0x00000007,0x000000d5,0x000000cf,0x000000d4, + 0x0005008e,0x00000007,0x000000d6,0x000000d5,0x0000003a,0x00050050,0x00000007,0x000000d7, + 0x0000003a,0x0000003a,0x00050081,0x00000007,0x000000d8,0x000000d6,0x000000d7,0x0003003e, + 0x000000cb,0x000000d8,0x0004003d,0x00000007,0x000000d9,0x000000cb,0x000500b8,0x00000056, + 0x000000da,0x000000d9,0x00000055,0x0004009a,0x0000003b,0x000000db,0x000000da,0x000400a8, + 0x0000003b,0x000000dc,0x000000db,0x000300f7,0x000000de,0x00000000,0x000400fa,0x000000dc, + 0x000000dd,0x000000de,0x000200f8,0x000000dd,0x0004003d,0x00000007,0x000000df,0x000000cb, + 0x000500ba,0x00000056,0x000000e0,0x000000df,0x0000005d,0x0004009a,0x0000003b,0x000000e1, + 0x000000e0,0x000200f9,0x000000de,0x000200f8,0x000000de,0x000700f5,0x0000003b,0x000000e2, + 0x000000db,0x000000ae,0x000000e1,0x000000dd,0x000300f7,0x000000e4,0x00000000,0x000400fa, + 0x000000e2,0x000000e3,0x000000e4,0x000200f8,0x000000e3,0x000200fe,0x000000b1,0x000200f8, + 0x000000e4,0x0004003d,0x00000066,0x000000e6,0x00000068,0x0004003d,0x00000007,0x000000e7, + 0x000000cb,0x00050057,0x0000001d,0x000000e8,0x000000e6,0x000000e7,0x0008004f,0x0000000f, + 0x000000e9,0x000000e8,0x000000e8,0x00000000,0x00000001,0x00000002,0x000200fe,0x000000e9, + 0x00010038 +}; diff --git a/app/src/main/cpp/waylandcomp/src/compositor.c b/app/src/main/cpp/waylandcomp/src/compositor.c index c9935e17..36516a12 100644 --- a/app/src/main/cpp/waylandcomp/src/compositor.c +++ b/app/src/main/cpp/waylandcomp/src/compositor.c @@ -50,6 +50,7 @@ #include "framegen_bridge.h" #include #include "effects_chain.h" +#include "cardboard.h" #include "droiddeck_ext.h" #include "droiddeck_color.h" #include "compositor_internal.h" @@ -260,6 +261,7 @@ struct surface { /* Toplevel placement on the scene. */ int mapped; /* in g_toplevels */ struct wl_list toplevel_link; + int keep_on_top; /* Cardboard mode's SteamVR window: above every other toplevel (raise_kept_on_top) */ int x, y, placed; uint32_t hwnd; @@ -632,6 +634,8 @@ static struct surface *toplevel_by_hwnd(uint32_t hwnd) { return NULL; } +static void raise_kept_on_top(void); + /* Reorder the mapped toplevels to match the last reported Windows z-order. Windows the * report doesn't mention keep their relative order below the ones it does. */ static void apply_zorder(void) { @@ -641,6 +645,17 @@ static void apply_zorder(void) { wl_list_remove(&s->toplevel_link); wl_list_insert(g_toplevels.prev, &s->toplevel_link); } + raise_kept_on_top(); +} + +/* Windows marked keep_on_top go back to the top of the stack (the end of g_toplevels). */ +static void raise_kept_on_top(void) { + struct surface *s, *tmp, *kept[4]; + int n = 0; + wl_list_for_each_safe(s, tmp, &g_toplevels, toplevel_link) { + if (s->keep_on_top && n < 4) { wl_list_remove(&s->toplevel_link); kept[n++] = s; } + } + for (int i = 0; i < n; i++) wl_list_insert(g_toplevels.prev, &kept[i]->toplevel_link); } static void map_toplevel(struct surface *s) { @@ -655,6 +670,7 @@ static void map_toplevel(struct surface *s) { s->placed ? "" : " (no desktop position yet)"); } wl_list_insert(g_toplevels.prev, &s->toplevel_link); /* new windows start on top */ + raise_kept_on_top(); /* Without a Wine desktop the clients are whole programs (the Linux desktop, a gamescope a * program on it started over it): a window that opens on top takes the keys too, as it would * on any desktop, rather than the one clicked last that is now underneath it. */ @@ -1473,7 +1489,13 @@ static void xdg_toplevel_set_title(struct wl_client *c, struct wl_resource *r, c droiddeck_log("window", "renamed %s -> %s", before, after); } } -static void xdg_toplevel_set_app_id(struct wl_client *c, struct wl_resource *r, const char *id) {} +static void xdg_toplevel_set_app_id(struct wl_client *c, struct wl_resource *r, const char *id) { + struct surface *s = wl_resource_get_user_data(r); + /* Cardboard mode's SteamVR window (tools/vr/src/presenter.cpp) stays above every other window, + * gamescope's included: gamescope reopens its window when Steam notices VR, and a reopened window + * would otherwise come up over it (new windows start on top). */ + if (s && id && !strcmp(id, "droiddeck-cardboard-vr")) s->keep_on_top = 1; +} static void xdg_toplevel_show_menu(struct wl_client *c, struct wl_resource *r, struct wl_resource *seat, uint32_t serial, int32_t x, int32_t y) {} static void xdg_toplevel_move(struct wl_client *c, struct wl_resource *r, struct wl_resource *seat, @@ -2086,6 +2108,8 @@ static void render_scene(void) { int rendered = 0; int fx_on = vkp_effects_active(); int framegen = vkp_framegen_active(); + /* Cardboard mode draws the whole panel itself, so the game never gets a display layer of its own. */ + const int cardboard = vkp_cardboard_active(); /* HDR (see hdr_plan): route 0 = the old paths below; 1 / 2 = composed; 3 = layer only, effects and * frame generation skipped. Always 0 while the HDR gate is closed: nothing changes for that session. */ struct hdr_scene hs; @@ -2132,7 +2156,7 @@ static void render_scene(void) { * - screen effects DO when a window is drawn above the game: the chain has to see the whole * scene to look the way it does on the copy path, and here the game is only part of it. */ const int pass_on = fx_on || framegen; - int li = (g_zero_copy && !framegen) ? layer_candidate(&dl, w, h) : -1; + int li = (g_zero_copy && !framegen && !cardboard) ? layer_candidate(&dl, w, h) : -1; int over = li >= 0 ? dl.n - 1 - li : 0; /* draws above the fullscreen game (0 or 1) */ const int fx_blocks = (li >= 0 && fx_on && over > 0); if (fx_blocks) { li = -1; over = 0; } @@ -2162,6 +2186,7 @@ static void render_scene(void) { /* HDR: the same, for a game's SUBSURFACE swapchain whose 10-bit frame the compositor could not * import (NULL whenever the HDR gate is closed - nothing is ever noted then). */ if (li < 0 && !ls) ls = hdr_unimported_fullscreen(&dl, w, h); + if (cardboard) ls = NULL; if (ls && pass_on && li < 0 && !g_zero_copy_fx_skip_said) { g_zero_copy_fx_skip_said = 1; droiddeck_log(framegen ? "framegen" : "effects", @@ -2304,6 +2329,8 @@ static void on_vsync(int64_t frame_time_ns) { if (vkp_apply_window_request()) g_dirty = 1; /* A screen-effect setting changed (JNI, any thread): redraw so it shows on a static scene too. */ if (vkp_effects_sync()) g_dirty = 1; + /* Cardboard mode: a new head pose (or setting) moves the virtual screen, static scene or not. */ + if (vkp_cardboard_pose_changed()) g_dirty = 1; if (g_dirty && !scene_waits_for_game(now)) render_scene(); } diff --git a/app/src/main/cpp/waylandcomp/src/headpose_shm.h b/app/src/main/cpp/waylandcomp/src/headpose_shm.h new file mode 100644 index 00000000..13c71a7f --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/headpose_shm.h @@ -0,0 +1,49 @@ +#ifndef HEADPOSE_SHM_H +#define HEADPOSE_SHM_H +/* + * The head pose Cardboard mode tracks, shared with the guest: a 128-byte file the compositor maps + * at /.wayland-rt/droiddeck-headpose, which the guest sees as /run/droiddeck/droiddeck-headpose. + * Written by the app (cardboard.c) at the sensor's rate; read by guest VR code (the SteamVR driver in + * tools/vr) without any call into the app. + * + * Seqlock: the writer makes seq odd, writes the rest, then makes it even again. A reader copies the + * struct and keeps the copy only if seq was even and unchanged across the copy. + * + * Axes are OpenVR's (and the cardboard shader's): x right, y up, z back (-z is straight ahead). + * rot maps head directions to the recentred world, row-major. time_ns is CLOCK_MONOTONIC, the same + * clock inside the guest (one kernel). + */ +#include + +#define DROIDDECK_HEADPOSE_MAGIC 0x50484444u /* "DDHP" */ +#define DROIDDECK_HEADPOSE_VERSION 1u +#define DROIDDECK_HEADPOSE_FILE "droiddeck-headpose" + +enum { + DROIDDECK_HEADPOSE_LIVE = 1u << 0, /* a rotation sensor is feeding it (else identity) */ + DROIDDECK_HEADPOSE_ENABLED = 1u << 1, /* Cardboard mode is on */ +}; + +struct droiddeck_headpose { + uint32_t magic; + uint32_t version; + uint32_t seq; + uint32_t flags; + float rot[9]; + float ipd_m; + int64_t time_ns; + /* What the lens pass expects of a side-by-side source, so a VR renderer can match it: one eye's + * half of the frame covers tan_half_fov up and down and eye_aspect times that left and right. */ + float tan_half_fov; + float eye_aspect; + uint32_t eye_w, eye_h; /* one eye's viewport on the panel, pixels (0 = not drawn yet) */ + uint32_t reserved[12]; +}; + +#ifdef __cplusplus +static_assert(sizeof(struct droiddeck_headpose) == 128, "headpose layout"); +#else +_Static_assert(sizeof(struct droiddeck_headpose) == 128, "headpose layout"); +#endif + +#endif diff --git a/app/src/main/cpp/waylandcomp/src/vk_present.c b/app/src/main/cpp/waylandcomp/src/vk_present.c index f50e4471..1042635f 100644 --- a/app/src/main/cpp/waylandcomp/src/vk_present.c +++ b/app/src/main/cpp/waylandcomp/src/vk_present.c @@ -5,6 +5,7 @@ #include "vk_loader.h" #include "sc_layer.h" #include "effects_chain.h" +#include "cardboard.h" #include "framegen_bridge.h" #include "hdr_compose.h" #include "blend_pass.h" @@ -541,6 +542,7 @@ static int dev_init(void) { g_vk.CreateFence(g_dev, &fci, NULL, &g_fence); vkp_effects_bind_device(g_dev, g_pd, &g_memprops); + vkp_cardboard_bind_device(g_dev, &g_memprops); hdrc_bind_device(g_dev, &g_memprops); blendp_bind_device(g_dev); g_dev_state = 1; @@ -1342,8 +1344,28 @@ static const char *vk_result_name(VkResult r) { * standing for the whole scene) through the scene -> output mapping, and leave the image ready * to present. */ static void record_screen_blit(VkCommandBuffer cmd, VkImage src, int src_w, int src_h, - int scene_w, int scene_h, uint32_t img, VkFilter filter) { + int scene_w, int scene_h, uint32_t img, VkFilter filter, int cardboard) { VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + /* Cardboard mode (cardboard.h): the pass turns src into the two eye views over the whole panel, + * and that image is blitted 1:1 instead of src going through the scene mapping. */ + int direct = 0; + if (cardboard) { + VkImageMemoryBarrier rd = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = src, .subresourceRange = range, + .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, .dstAccessMask = VK_ACCESS_SHADER_READ_BIT}; + g_vk.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + 0, 0, NULL, 0, NULL, 1, &rd); + VkImage eyes = vkp_cardboard_run(cmd, src, src_w, src_h, (int)g_extent.width, (int)g_extent.height); + if (eyes) { + src = eyes; + src_w = (int)g_extent.width; + src_h = (int)g_extent.height; + direct = 1; + } + } VkImageMemoryBarrier pre[2] = { {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, @@ -1361,7 +1383,12 @@ static void record_screen_blit(VkCommandBuffer cmd, VkImage src, int src_w, int struct vkp_image tmp = {.w = src_w, .h = src_h}; /* only its size is looked at */ struct vkp_draw d = {&tmp, 0, 0, (float)src_w, (float)src_h, 0, 0, scene_w, scene_h}; VkImageBlit blit; - const int have_blit = draw_to_blit(&d, &blit); + const int have_blit = direct ? 1 : draw_to_blit(&d, &blit); + if (direct) + blit = (VkImageBlit){.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, + .srcOffsets = {{0, 0, 0}, {src_w, src_h, 1}}, + .dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, + .dstOffsets = {{0, 0, 0}, {src_w, src_h, 1}}}; /* The result fills its whole mapped rectangle, so only the letterbox bars around it need black. */ if (!have_blit || !record_bars_clear(cmd, g_images[img], &blit)) { VkClearColorValue black = {.float32 = {0.0f, 0.0f, 0.0f, 1.0f}}; @@ -1571,6 +1598,15 @@ static int render_impl(int scene_w, int scene_h, const struct vkp_draw *draws, i * blit filter follows the scaling mode. */ const int fx = !g_plain_frame && vkp_effects_active(); const int fg = !g_plain_frame && vkp_framegen_active(); + /* Cardboard mode needs the whole frame too; it works on the 8-bit picture only (an HDR frame + * is shown flat, said once). */ + int cb = !g_plain_frame && vkp_cardboard_active(); + if (cb && hf) { + static int said; + if (!said) { said = 1; droiddeck_log("cardboard", "HDR frames are shown flat: the cardboard pass is 8-bit only"); } + cb = 0; + } + vkp_cardboard_begin_frame(); /* HDR frames with frame generation through an HDR10 swapchain: the scene, the effects and the * engine's frames in FP16 (FG_HDR_FMT) where the engine can take that format - more than 8 bits of * PQ through interpolation - else the 8-bit scene, as before. */ @@ -1594,7 +1630,7 @@ static int render_impl(int scene_w, int scene_h, const struct vkp_draw *draws, i } } /* An HDR scene always takes the pass: its draws are composed into one encoding first. */ - const int pass = deep || ((fx || fg || (hf && n > 0)) && ensure_scene_image(scene_w, scene_h) == 0); + const int pass = deep || ((fx || fg || cb || (hf && n > 0)) && ensure_scene_image(scene_w, scene_h) == 0); VkImage scene_img = deep ? g_fgscene.img : g_scene.img; const VkFormat scene_fmt = deep ? FG_HDR_FMT : SCENE_FMT; const VkFilter blit_filter = vkp_effects_blit_filter(); @@ -1746,7 +1782,7 @@ static int render_impl(int scene_w, int scene_h, const struct vkp_draw *draws, i } /* 4. Mapping/blit: the first present carries generated frame 0, or the result itself. */ - record_screen_blit(cmd, ngen ? gens[0] : result, rw, rh, scene_w, scene_h, img, blit_filter); + record_screen_blit(cmd, ngen ? gens[0] : result, rw, rh, scene_w, scene_h, img, blit_filter, cb); } g_vk.EndCommandBuffer(cmd); @@ -1770,7 +1806,7 @@ static int render_impl(int scene_w, int scene_h, const struct vkp_draw *draws, i VkCommandBuffer ck = g_cmds[k]; g_vk.ResetCommandBuffer(ck, 0); g_vk.BeginCommandBuffer(ck, &bi); - record_screen_blit(ck, k < ngen ? gens[k] : result, rw, rh, scene_w, scene_h, img, blit_filter); + record_screen_blit(ck, k < ngen ? gens[k] : result, rw, rh, scene_w, scene_h, img, blit_filter, cb); g_vk.EndCommandBuffer(ck); } const int last = (k + 1 == presents); diff --git a/app/src/main/cpp/waylandcomp/src/waylandcomp_jni.c b/app/src/main/cpp/waylandcomp/src/waylandcomp_jni.c index 8ec4b96b..4a9dd4d2 100644 --- a/app/src/main/cpp/waylandcomp/src/waylandcomp_jni.c +++ b/app/src/main/cpp/waylandcomp/src/waylandcomp_jni.c @@ -20,6 +20,7 @@ #include "ahb_swapchain.h" #include "sc_layer.h" #include "effects_chain.h" +#include "cardboard.h" #include "droiddeck_color.h" extern int droiddeck_wayland_run(void); @@ -463,6 +464,36 @@ Java_com_droiddeck_launcher_wayland_WaylandCompositor_nativeSetScreenEffects(JNI vkp_effects_set_screen(brightness, contrast, gamma, saturation, fxaa ? 1 : 0, toon ? 1 : 0, crt ? 1 : 0, ntsc ? 1 : 0); } +/* Cardboard mode - WaylandCompositor.nativeSetCardboard. */ +JNIEXPORT void JNICALL +Java_com_droiddeck_launcher_wayland_WaylandCompositor_nativeSetCardboard(JNIEnv *env, jclass clazz, jboolean enabled, + jint source, jfloat ipd, jfloat dist, jfloat width, jfloat k1, jfloat k2, jfloat fov, jfloat inset, jfloat chroma) { + struct cardboard_params p = {.enabled = enabled ? 1 : 0, .source = source, .ipd_m = ipd, .screen_dist_m = dist, + .screen_width_m = width, .k1 = k1, .k2 = k2, .fov_deg = fov, .lens_inset = inset, + .chroma = chroma}; + vkp_cardboard_set(&p); +} + +/* The head pose (row-major 3x3, head -> world), from the sensor thread. */ +JNIEXPORT void JNICALL +Java_com_droiddeck_launcher_wayland_WaylandCompositor_nativeSetCardboardPose(JNIEnv *env, jclass clazz, jfloatArray rot, + jboolean live) { + float m[9]; + if (!rot || (*env)->GetArrayLength(env, rot) < 9) return; + (*env)->GetFloatArrayRegion(env, rot, 0, 9, m); + vkp_cardboard_set_pose(m, live ? 1 : 0); +} + +/* Where the head pose is shared with the guest - WaylandCompositor.nativeSetCardboardPoseFile. */ +JNIEXPORT void JNICALL +Java_com_droiddeck_launcher_wayland_WaylandCompositor_nativeSetCardboardPoseFile(JNIEnv *env, jclass clazz, jstring path) { + if (!path) return; + const char *p = (*env)->GetStringUTFChars(env, path, NULL); + if (!p) return; + vkp_cardboard_set_pose_file(p); + (*env)->ReleaseStringUTFChars(env, path, p); +} + /* One "display" line in the session log, written from Java: facts that live in the Android * framework (the panel's HDR capability) rather than in the compositor. droiddeck_log() mirrors to * logcat and appends to the session file when one is open, so this is safe at any point. */ diff --git a/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt b/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt index d523bd15..a8d9ccd0 100644 --- a/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt +++ b/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt @@ -49,7 +49,10 @@ import com.droiddeck.launcher.stores.StoreAchievements import com.droiddeck.launcher.stores.StoreGameSidecar import com.droiddeck.launcher.stores.StoreLaunchRequests import com.droiddeck.launcher.ui.SessionAchievements +import com.droiddeck.launcher.gpu.Cardboard import com.droiddeck.launcher.gpu.FrameGen +import com.droiddeck.launcher.gpu.HeadTracker +import com.droiddeck.launcher.gpu.SteamVr import com.droiddeck.launcher.gpu.ScreenEffectLooks import com.droiddeck.launcher.gpu.ScreenEffects import com.droiddeck.launcher.gpu.Lossless @@ -297,6 +300,25 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { private var upscaler by mutableStateOf(0) private var upscaleSharpness by mutableStateOf(75) private var effects by mutableStateOf(ScreenEffects.OFF) + /** Cardboard mode (gpu/Cardboard) and the sensor that turns the virtual screen with the head. */ + private var cardboard by mutableStateOf(Cardboard.OFF) + private var headTracker: HeadTracker? = null + private var steamVrStatus by mutableStateOf(null) + /** The picture was switched to side-by-side for SteamVR and goes back when it stops. */ + private var steamVrSwitchedPicture by mutableStateOf(false) + /** SteamVR has been seen running since the switch: only then does "not running" mean it stopped. */ + private var steamVrSeenRunning = false + private val hasRotationSensor by lazy { + (getSystemService(SENSOR_SERVICE) as android.hardware.SensorManager).let { + it.getDefaultSensor(android.hardware.Sensor.TYPE_GAME_ROTATION_VECTOR) + ?: it.getDefaultSensor(android.hardware.Sensor.TYPE_ROTATION_VECTOR) + } != null + } + // Held in a field: SharedPreferences keeps its listeners weakly. A change from anywhere (the + // drawer, tools/droiddeckctl prefs set) applies live. + private val cardboardPrefListener = android.content.SharedPreferences.OnSharedPreferenceChangeListener { _, key -> + if (key == SessionPrefs.CARDBOARD_KEY) uiHandler.post { applyCardboard(SessionPrefs.cardboard(this)) } + } private var textureAnisotropy by mutableStateOf(0) private var textureLodBias by mutableStateOf(TextureFiltering.LOD_BIAS_OFF) private var drawerTextureAnisotropy by mutableStateOf(null) @@ -571,6 +593,18 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { releaseDrawerDirection() } } + // SteamVR's state for the Cardboard group, refreshed while the drawer shows it. + // Watched for as long as Cardboard mode is on (or the picture is switched for it), drawer open or + // not: however SteamVR is started or quit, the picture follows it. + LaunchedEffect(cardboard.enabled, steamVrSwitchedPicture) { + while (cardboard.enabled || steamVrSwitchedPicture) { + val now = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) { + SteamVr.status(this@SessionActivity) + } + onSteamVrStatus(now) + kotlinx.coroutines.delay(3000) + } + } LaunchedEffect(drawerOpen, achievementsOpen, AgentGuest.focusRevision, StoreAchievements.revision) { if (!drawerOpen && !achievementsOpen) return@LaunchedEffect val found = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) { @@ -596,6 +630,12 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { hudOn = hudOn, upscaler = upscaler, upscaleSharpness = upscaleSharpness, effects = effects, + cardboard = cardboard, + cardboardSensor = hasRotationSensor, + onCardboard = { c -> SessionPrefs.setCardboard(this@SessionActivity, c); applyCardboard(c) }, + onCardboardRecenter = { headTracker?.recenter() }, + steamVr = steamVrStatus, + onSteamVr = { action -> steamVrAction(action) }, textureAnisotropy = if (drawerGameProfileId == null) textureAnisotropy else drawerTextureAnisotropy, textureLodBias = if (drawerGameProfileId == null) textureLodBias else drawerTextureLodBias, frameGen = frameGen, @@ -1126,6 +1166,13 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { upscaler = SessionPrefs.upscaler(this) upscaleSharpness = SessionPrefs.upscaleSharpness(this) effects = SessionPrefs.screenEffects(this) + applyCardboard(SessionPrefs.cardboard(this)) + // A switch left from before this activity (or this resume): SteamVR was running then, so the next + // "not running" hands the picture back. + if (SessionPrefs.cardboardSourceBeforeSteamVr(this) >= 0 && !steamVrSwitchedPicture) { + steamVrSwitchedPicture = true + steamVrSeenRunning = true + } textureAnisotropy = SessionPrefs.textureAnisotropy(this) textureLodBias = SessionPrefs.textureLodBias(this) val nextTouchMode = SessionPrefs.touchMode(this) @@ -1331,6 +1378,8 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { WaylandCompositor.nativeSetUpscaler(SessionPrefs.upscaler(this)) WaylandCompositor.nativeSetUpscaleSharpness(SessionPrefs.upscaleSharpness(this)) SessionPrefs.screenEffects(this).push(SessionPrefs.upscaler(this)) + panelSizeMm().let { (w, h) -> SessionPrefs.cardboard(this).push(w, h) } + WaylandCompositor.nativeSetCardboardPoseFile(File(filesDir, ".wayland-rt/droiddeck-headpose").path) // The session's folder, claimed here because the compositor starts before the service and // opens its log once. The compositor reads the path from its environment; setting it after // it has started changes nothing, which is why the service copies the file in at teardown. @@ -2515,6 +2564,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { readPrefs() if (::pip.isInitialized) pip.resumed() if (CompositorHost.isStarted) applyFrameGen() + SessionPrefs.sessionPrefs(this).registerOnSharedPreferenceChangeListener(cardboardPrefListener) } override fun onPause() { @@ -2533,9 +2583,103 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { updatePointerCapture() updatePadMotion() keyboard?.takeIf { it.shown }?.hide() + SessionPrefs.sessionPrefs(this).unregisterOnSharedPreferenceChangeListener(cardboardPrefListener) + updateHeadTracker() super.onPause() } + /** SteamVR for Cardboard mode: install, start or stop it in the guest. Starting switches the + * picture to side-by-side, which is what SteamVR draws. */ + private fun steamVrAction(action: String) { + if (action == "start") { + switchPictureForSteamVr() + steamVrSeenRunning = false + } + if (action == "stop") restoreSteamVrPicture() + if (action == "install") steamVrStatus = SteamVr.Status.INSTALLING + Thread({ + val ok = SteamVr.run(this, action) + val now = SteamVr.status(this) + uiHandler.post { + // A start that failed leaves nothing side-by-side to show: the picture goes back. + if (!ok && action == "start" && now != SteamVr.Status.RUNNING) restoreSteamVrPicture() + onSteamVrStatus(now) + if (!ok) Toast.makeText(this, getString(R.string.cardboard_steamvr_failed), Toast.LENGTH_LONG).show() + } + }, "steamvr-$action").start() + } + + /** SteamVR draws a side-by-side pair: the picture switches to that, remembering what it was. */ + private fun switchPictureForSteamVr() { + if (cardboard.source != Cardboard.SOURCE_SBS) { + SessionPrefs.setCardboardSourceBeforeSteamVr(this, cardboard.source) + val c = cardboard.copy(source = Cardboard.SOURCE_SBS) + SessionPrefs.setCardboard(this, c) + applyCardboard(c) + } + steamVrSwitchedPicture = SessionPrefs.cardboardSourceBeforeSteamVr(this) >= 0 + } + + private fun onSteamVrStatus(now: SteamVr.Status) { + steamVrStatus = now + // Started some other way than the drawer (a script, a VR game): the picture follows it all the same. + if (now == SteamVr.Status.RUNNING && cardboard.enabled && !steamVrSwitchedPicture && + cardboard.source != Cardboard.SOURCE_SBS) { + switchPictureForSteamVr() + steamVrSeenRunning = true + } + if (!steamVrSwitchedPicture) return + if (now == SteamVr.Status.RUNNING) steamVrSeenRunning = true + else if (steamVrSeenRunning && now != SteamVr.Status.UNAVAILABLE) restoreSteamVrPicture() + } + + /** SteamVR stopped: the Cardboard picture goes back to what it was before Start. */ + private fun restoreSteamVrPicture() { + val before = SessionPrefs.cardboardSourceBeforeSteamVr(this) + SessionPrefs.setCardboardSourceBeforeSteamVr(this, -1) + steamVrSwitchedPicture = false + steamVrSeenRunning = false + if (before < 0 || cardboard.source == before) return + val c = cardboard.copy(source = before) + SessionPrefs.setCardboard(this, c) + applyCardboard(c) + } + + /** Cardboard mode: hand the settings to the compositor and run the head tracker while it needs one. */ + private fun applyCardboard(c: Cardboard) { + val was = cardboard + cardboard = c + if (CompositorHost.isStarted) panelSizeMm().let { (w, h) -> c.push(w, h) } + if (c.enabled && !was.enabled) headTracker?.recenter() + updateHeadTracker() + } + + private fun updateHeadTracker() { + // Side-by-side sources track too: the compositor leaves their picture alone, but whoever renders + // it (the SteamVR driver, tools/vr) reads the pose from the shared file. + val want = resumed && cardboard.enabled && cardboard.headTracking + if (want) { + val t = headTracker ?: HeadTracker(this) { + @Suppress("DEPRECATION") + (if (Build.VERSION.SDK_INT >= 30) display else windowManager.defaultDisplay)?.rotation ?: android.view.Surface.ROTATION_0 + }.also { headTracker = it } + t.start() + } else { + headTracker?.stop() + } + } + + /** The panel's physical size in millimetres, long side first, for lining the eyes up with the lenses. */ + private fun panelSizeMm(): Pair { + val dm = resources.displayMetrics + val landscape = dm.widthPixels >= dm.heightPixels + val longPx = maxOf(dm.widthPixels, dm.heightPixels).toFloat() + val shortPx = minOf(dm.widthPixels, dm.heightPixels).toFloat() + val longDpi = if (landscape) dm.xdpi else dm.ydpi + val shortDpi = if (landscape) dm.ydpi else dm.xdpi + return (if (longDpi > 0f) longPx / longDpi * 25.4f else 0f) to (if (shortDpi > 0f) shortPx / shortDpi * 25.4f else 0f) + } + override fun onAttachedToWindow() { super.onAttachedToWindow() if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return diff --git a/app/src/main/java/com/droiddeck/launcher/gpu/Cardboard.kt b/app/src/main/java/com/droiddeck/launcher/gpu/Cardboard.kt new file mode 100644 index 00000000..b5ec1b54 --- /dev/null +++ b/app/src/main/java/com/droiddeck/launcher/gpu/Cardboard.kt @@ -0,0 +1,202 @@ +package com.droiddeck.launcher.gpu + +import com.droiddeck.launcher.wayland.WaylandCompositor +import org.json.JSONObject +import kotlin.math.sqrt + +/** + * Cardboard mode: the compositor draws the session as two lens-distorted eye views side by side + * (waylandcomp/src/cardboard.c), for a phone or handheld dropped into a Google Cardboard-style + * viewer. Units are the menu's own: + * + * - [ipdMm] distance between the eyes, millimetres + * - [screenWidthDm] the virtual screen's width, decimetres (40 = 4 m) + * - [screenDistanceDm] how far away it hangs, decimetres (30 = 3 m) + * - [viewer] a [Viewer] id: the lens it is drawn for + * + * [source] SCREEN shows the frame as a flat virtual screen, world-locked by the rotation sensor + * when [headTracking] is on (3DoF), with real stereo depth from the IPD. SBS takes a frame that is + * already a left/right pair (SteamVR, SBS 3D games and video) and only adds the lens. + */ +data class Cardboard( + val enabled: Boolean = false, + val source: Int = SOURCE_SCREEN, + val viewer: String = Viewer.CARDBOARD_V2.id, + val ipdMm: Int = 64, + val screenWidthDm: Int = 40, + val screenDistanceDm: Int = 30, + val headTracking: Boolean = true, +) { + val lens: Viewer get() = Viewer.byId(viewer) + + /** + * Hands the settings to the compositor. [panelWidthMm] x [panelHeightMm] is the physical size of + * the panel the eyes are drawn on (0 = unknown). The width decides how far each lens centre sits + * from its viewport's centre, so the two pictures line up with the viewer's lenses; the height, + * seen through the lens from its focal distance, is the field of view the distortion works in. + */ + fun push(panelWidthMm: Float, panelHeightMm: Float) { + val l = lens + WaylandCompositor.nativeSetCardboard(enabled, source, ipdMm / 1000f, screenDistanceDm / 10f, + screenWidthDm / 10f, l.k1, l.k2, fovDeg(l, panelHeightMm), lensInset(panelWidthMm, l.lensSeparationMm), l.chroma) + } + + fun encode(): JSONObject = JSONObject() + .put("enabled", enabled).put("source", source).put("viewer", viewer).put("ipdMm", ipdMm) + .put("screenWidthDm", screenWidthDm).put("screenDistanceDm", screenDistanceDm).put("headTracking", headTracking) + + /** + * A lens the eyes can be drawn for: its distortion (Cardboard viewer profile coefficients, in + * tangent units at the panel), how far it sits from the panel, and how far apart the two are. + * [fallbackFovDeg] is the eye's field of view when the panel's size is unknown, or with no lens. + */ + enum class Viewer(val id: String, val k1: Float, val k2: Float, val screenToLensMm: Float, + val lensSeparationMm: Float, val chroma: Float, val fallbackFovDeg: Float) { + /** Google Cardboard (2015): the common plastic and cardboard viewers. */ + CARDBOARD_V2("cardboard2", 0.34f, 0.55f, 39.3f, 63.9f, 0.006f, 90f), + /** The original 2014 Cardboard: narrower, gentler lenses. */ + CARDBOARD_V1("cardboard1", 0.441f, 0.156f, 42f, 60f, 0.004f, 80f), + /** No lens: side-by-side 3D glasses, a 3D TV, or just looking at the stereo pair. */ + NONE("none", 0f, 0f, 0f, 0f, 0f, 60f); + + companion object { + fun byId(id: String): Viewer = entries.firstOrNull { it.id == id } ?: CARDBOARD_V2 + } + } + + companion object { + const val SOURCE_SCREEN = 0 + const val SOURCE_SBS = 1 + + val OFF = Cardboard() + + fun decode(json: JSONObject): Cardboard = Cardboard( + enabled = json.optBoolean("enabled", false), + source = if (json.optInt("source", SOURCE_SCREEN) == SOURCE_SBS) SOURCE_SBS else SOURCE_SCREEN, + viewer = Viewer.byId(json.optString("viewer", Viewer.CARDBOARD_V2.id)).id, + ipdMm = json.optInt("ipdMm", 64).coerceIn(50, 80), + screenWidthDm = json.optInt("screenWidthDm", 40).coerceIn(5, 200), + screenDistanceDm = json.optInt("screenDistanceDm", 30).coerceIn(5, 200), + headTracking = json.optBoolean("headTracking", true), + ) + + /** + * One eye's vertical field of view on the panel side of the lens: half the panel's height + * seen from the lens's focal distance. The lens then spreads it wider (the distortion). + */ + fun fovDeg(viewer: Viewer, panelHeightMm: Float): Float { + if (panelHeightMm <= 0f || viewer.screenToLensMm <= 0f) return viewer.fallbackFovDeg + val half = Math.toDegrees(kotlin.math.atan((panelHeightMm / 2f / viewer.screenToLensMm).toDouble())).toFloat() + return (2f * half).coerceIn(30f, 140f) + } + + /** + * The lens centre's inset towards the nose, as a fraction of one eye viewport's width. Each + * eye gets half the panel, so its viewport centre is a quarter of the panel from the middle; + * the lens centre is half the lens separation from it. 0 when either is unknown. + */ + fun lensInset(panelWidthMm: Float, lensSeparationMm: Float): Float { + if (panelWidthMm <= 0f || lensSeparationMm <= 0f) return 0f + val inset = (panelWidthMm / 4f - lensSeparationMm / 2f) / (panelWidthMm / 2f) + return inset.coerceIn(-0.25f, 0.25f) + } + } +} + +/** + * Head pose maths for Cardboard mode, kept free of Android types so it is unit-tested on the JVM. + * Matrices are row-major 3x3 FloatArrays. "World" is Android's sensor world (x east, y north, + * z up); "screen" axes are x right, y up, z out of the panel towards the viewer's eyes. + */ +object HeadPose { + val IDENTITY = floatArrayOf(1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f) + + /** Unit quaternion (x, y, z, w) -> rotation matrix: what SensorManager.getRotationMatrixFromVector gives. */ + fun fromQuaternion(x: Float, y: Float, z: Float, w: Float): FloatArray { + val n = sqrt(x * x + y * y + z * z + w * w).takeIf { it > 1e-6f } ?: 1f + val qx = x / n; val qy = y / n; val qz = z / n; val qw = w / n + return floatArrayOf( + 1 - 2 * (qy * qy + qz * qz), 2 * (qx * qy - qz * qw), 2 * (qx * qz + qy * qw), + 2 * (qx * qy + qz * qw), 1 - 2 * (qx * qx + qz * qz), 2 * (qy * qz - qx * qw), + 2 * (qx * qz - qy * qw), 2 * (qy * qz + qx * qw), 1 - 2 * (qx * qx + qy * qy), + ) + } + + /** The rotation vector sensor's values -> (x, y, z, w), computing w when the sensor leaves it out. */ + fun quaternionFromRotationVector(v: FloatArray): FloatArray { + val x = v[0]; val y = v[1]; val z = v[2] + val w = if (v.size >= 4) v[3] else sqrt((1f - x * x - y * y - z * z).coerceAtLeast(0f)) + return floatArrayOf(x, y, z, w) + } + + /** + * Device -> screen axes for a Display rotation (Surface.ROTATION_* as 0..3): the returned matrix + * maps screen to world, given the device's own device -> world matrix. The same transform as + * input/PadMotion's and SensorManager.remapCoordinateSystem's (ROTATION_90 = AXIS_Y, AXIS_MINUS_X: + * the device's x is the screen's y, its y the screen's -x). + */ + fun toScreen(deviceToWorld: FloatArray, rotation: Int): FloatArray { + // Columns of A: where each screen axis points in device coordinates. + val a = when (rotation and 3) { + 1 -> floatArrayOf(0f, 1f, 0f, -1f, 0f, 0f, 0f, 0f, 1f) // screen x = -device y, y = device x + 2 -> floatArrayOf(-1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, 1f) // screen x = -device x, y = -device y + 3 -> floatArrayOf(0f, -1f, 0f, 1f, 0f, 0f, 0f, 0f, 1f) // screen x = device y, y = -device x + else -> IDENTITY + } + return multiply(deviceToWorld, a) + } + + /** sin(60 degrees): a view further from level than this is "held flat" for recentring. */ + private const val FLAT_SIN = 0.866f + + /** + * What a recentre locks to. The pose the compositor gets is basis^T * screenToWorld * tilt: + * - [basis]: a level frame (columns right, up, back in world) facing the recentred heading. Every + * rotation is measured in it, so turning about the real vertical always pans the view; + * - [tilt]: the device's own tilt at the recentre, taken out so that pose starts straight ahead. + * Identity when the device was held upright: the horizon then stays the true one. + */ + class Reference(val basis: FloatArray, val tilt: FloatArray) + + /** + * Recentre on the current orientation. Held upright (the screen within 60 degrees of vertical, as + * in a viewer), only the heading resets: forward is where the screen faces. Held flat (lying back, + * or the device on a table or in the hands like a tray), forward is where the top of the screen + * points and the tilt resets too; turning at the hips still pans, tipping the device still tilts. + */ + fun recenter(screenToWorld: FloatArray): Reference { + val fz = -screenToWorld[8] + val flat = kotlin.math.abs(fz) > FLAT_SIN + // Upright: the screen's -z. Flat: the screen's +y (its top edge), which is level then. + var fx = if (flat) screenToWorld[1] else -screenToWorld[2] + var fy = if (flat) screenToWorld[4] else -screenToWorld[5] + val len = sqrt(fx * fx + fy * fy).takeIf { it > 1e-4f } ?: run { fx = 0f; fy = 1f; 1f } + fx /= len; fy /= len + // right = forward x up(0,0,1) = (fy, -fx, 0); up = (0,0,1); back = -forward. + val basis = floatArrayOf( + fy, 0f, -fx, + -fx, 0f, -fy, + 0f, 1f, 0f, + ) + val tilt = if (flat) multiply(transpose(screenToWorld), basis) else IDENTITY + return Reference(basis, tilt) + } + + /** head -> recentred world: what the compositor takes. */ + fun relative(ref: Reference, screenToWorld: FloatArray): FloatArray = + multiply(multiply(transpose(ref.basis), screenToWorld), ref.tilt) + + fun multiply(a: FloatArray, b: FloatArray): FloatArray = FloatArray(9) { i -> + val r = i / 3; val c = i % 3 + a[r * 3] * b[c] + a[r * 3 + 1] * b[3 + c] + a[r * 3 + 2] * b[6 + c] + } + + fun transpose(m: FloatArray): FloatArray = floatArrayOf(m[0], m[3], m[6], m[1], m[4], m[7], m[2], m[5], m[8]) + + /** m * v for a 3-vector. */ + fun apply(m: FloatArray, v: FloatArray): FloatArray = floatArrayOf( + m[0] * v[0] + m[1] * v[1] + m[2] * v[2], + m[3] * v[0] + m[4] * v[1] + m[5] * v[2], + m[6] * v[0] + m[7] * v[1] + m[8] * v[2], + ) +} diff --git a/app/src/main/java/com/droiddeck/launcher/gpu/HeadTracker.kt b/app/src/main/java/com/droiddeck/launcher/gpu/HeadTracker.kt new file mode 100644 index 00000000..3841854e --- /dev/null +++ b/app/src/main/java/com/droiddeck/launcher/gpu/HeadTracker.kt @@ -0,0 +1,81 @@ +package com.droiddeck.launcher.gpu + +import android.content.Context +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener +import android.hardware.SensorManager +import android.os.Handler +import android.os.HandlerThread +import android.util.Log +import com.droiddeck.launcher.wayland.WaylandCompositor + +/** + * 3DoF head tracking for Cardboard mode: the rotation sensor -> the compositor's head pose + * ([HeadPose] for the maths). Game rotation vector first (gyro + accelerometer: no compass jumps + * indoors, and only the heading drifts, which a recentre fixes); the plain rotation vector where a + * device has no game one. Events arrive on a thread of their own and go straight to native code, + * so the session's UI thread never sits between the sensor and the screen. + * + * [rotation] is the Display rotation (Surface.ROTATION_*) the session is drawn in. + */ +class HeadTracker(context: Context, private val rotation: () -> Int) : SensorEventListener { + private val sensors = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager + private val sensor: Sensor? = sensors.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR) + ?: sensors.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR) + private var thread: HandlerThread? = null + @Volatile private var reference: HeadPose.Reference? = null + @Volatile private var recenterPending = true + @Volatile private var latest: FloatArray? = null + + /** Whether this device has a rotation sensor at all (no sensor = a fixed, head-locked screen). */ + val available: Boolean get() = sensor != null + val sensorName: String get() = sensor?.let { "${it.name} (${if (it.type == Sensor.TYPE_GAME_ROTATION_VECTOR) "game rotation vector" else "rotation vector"})" } ?: "none" + + fun start() { + val s = sensor ?: run { + WaylandCompositor.nativeLog("cardboard", "no rotation sensor: the virtual screen stays head-locked") + WaylandCompositor.nativeSetCardboardPose(HeadPose.IDENTITY, false) + return + } + if (thread != null) return + recenterPending = true + val t = HandlerThread("droiddeck-headtracker").also { it.start() } + thread = t + sensors.registerListener(this, s, SensorManager.SENSOR_DELAY_FASTEST, Handler(t.looper)) + WaylandCompositor.nativeLog("cardboard", "head tracking on: $sensorName") + } + + fun stop() { + val t = thread ?: return + sensors.unregisterListener(this) + t.quitSafely() + thread = null + WaylandCompositor.nativeSetCardboardPose(HeadPose.IDENTITY, false) + } + + /** Make where the viewer looks now straight ahead (see HeadPose.recenter). */ + fun recenter() { + recenterPending = true + latest?.let { apply(it) } + } + + override fun onSensorChanged(event: SensorEvent) { + val q = HeadPose.quaternionFromRotationVector(event.values) + val m = HeadPose.toScreen(HeadPose.fromQuaternion(q[0], q[1], q[2], q[3]), rotation()) + latest = m + apply(m) + } + + private fun apply(screenToWorld: FloatArray) { + val ref = reference.takeUnless { recenterPending } ?: HeadPose.recenter(screenToWorld).also { + reference = it + recenterPending = false + } + WaylandCompositor.nativeSetCardboardPose(HeadPose.relative(ref, screenToWorld), true) + } + + override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { + Log.d("DroidDeck", "head tracker accuracy $accuracy") + } +} diff --git a/app/src/main/java/com/droiddeck/launcher/gpu/SteamVr.kt b/app/src/main/java/com/droiddeck/launcher/gpu/SteamVr.kt new file mode 100644 index 00000000..c592b260 --- /dev/null +++ b/app/src/main/java/com/droiddeck/launcher/gpu/SteamVr.kt @@ -0,0 +1,58 @@ +package com.droiddeck.launcher.gpu + +import android.content.Context +import com.droiddeck.launcher.agent.AgentGuest +import org.json.JSONObject + +/** + * Cardboard mode's SteamVR (docs/cardboard-vr.md): Valve's ARM64 SteamVR in the guest, rendering + * through the DroidDeck driver as a side-by-side pair that the Cardboard pass puts the lens on. Driven + * through the guest agent's fixed "vr" request (droiddeck-steamvr), so it needs no agent commands. + * Every call blocks on the guest: run them off the main thread. + */ +object SteamVr { + enum class Status { + /** No session, no agent, or a runtime without the helper or the driver. */ + UNAVAILABLE, + NOT_INSTALLED, + /** Downloading or unpacking (571 MB). */ + INSTALLING, + READY, + RUNNING, + } + + fun status(context: Context): Status = runCatching { + parse(AgentGuest.call(context, JSONObject().put("kind", "vr").put("action", "status"), STATUS_TIMEOUT_MS)) + }.getOrDefault(Status.UNAVAILABLE) + + /** install, start or stop; true when the guest did it. */ + fun run(context: Context, action: String): Boolean = runCatching { + require(action in setOf("install", "start", "stop")) + succeeded(action, AgentGuest.call(context, JSONObject().put("kind", "vr").put("action", action), ACTION_TIMEOUT_MS)) + }.getOrDefault(false) + + /** + * The agent answers ok whenever it ran the request, even when droiddeck-steamvr then failed; the + * helper's own exit code says whether start or stop worked. install runs detached and only reports + * that it started (its progress shows in status). + */ + fun succeeded(action: String, answer: JSONObject): Boolean = when { + !answer.optBoolean("ok", false) -> false + action == "install" -> answer.optBoolean("started", false) + else -> answer.optInt("exitCode", -1) == 0 + } + + /** The agent's status answer -> what the menu shows. */ + fun parse(answer: JSONObject): Status = when { + !answer.optBoolean("ok", false) -> Status.UNAVAILABLE + answer.optInt("exitCode", 0) != 0 -> Status.UNAVAILABLE + answer.optBoolean("installing") -> Status.INSTALLING + !answer.optBoolean("driver") -> Status.UNAVAILABLE + !answer.optBoolean("installed") -> Status.NOT_INSTALLED + answer.optBoolean("running") -> Status.RUNNING + else -> Status.READY + } + + private const val STATUS_TIMEOUT_MS = 10_000L + private const val ACTION_TIMEOUT_MS = 40_000L +} diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionFiles.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionFiles.kt index 4d2c1c39..9b3d4e65 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionFiles.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionFiles.kt @@ -87,6 +87,8 @@ object SessionFiles { "usr/local/bin/droiddeck-open-url" to "usr/local/bin/droiddeck-open-url", "usr/local/bin/droiddeck-steam-games" to "usr/local/bin/droiddeck-steam-games", "usr/local/bin/droiddeck-pad-defaults" to "usr/local/bin/droiddeck-pad-defaults", + // Cardboard mode's SteamVR: install, start and stop (docs/cardboard-vr.md). + "usr/local/bin/droiddeck-steamvr" to "usr/local/bin/droiddeck-steamvr", // Flatpak: the bwrap stand-in, the store's helper and setup, and the front end's launcher. "usr/local/bin/droiddeck-bwrap" to "usr/local/bin/droiddeck-bwrap", "usr/local/bin/droiddeck-flatpak" to "usr/local/bin/droiddeck-flatpak", @@ -133,6 +135,18 @@ object SessionFiles { val msitools = listOf("msiinfo", "cabextract", "7z", "7z.so", "libmsi-1.0.so.0", "libgsf-1.so.114", "libgcab-1.0.so.0") .map { "usr/local/lib/droiddeck-msitools/$it" } + listOf("NOTICE", "GPL-2", "GPL-3", "LGPL-2.1", "7zip-License", "7zip-unRarLicense").map { "usr/local/share/licenses/droiddeck-msitools/$it" } + // Cardboard mode's SteamVR driver and the Vulkan layer its compositor needs (tools/vr). + val vr = listOf( + "droiddeck/bin/linuxarm64/driver_droiddeck.so", + "droiddeck/driver.vrdrivermanifest", + "droiddeck/resources/settings/default.vrsettings", + "layer/libVkLayer_droiddeck_vr.so", + "layer/VK_LAYER_DROIDDECK_vr.json", + ).map { "usr/local/lib/droiddeck-vr/$it" to "usr/local/lib/droiddeck-vr/$it" } + + // The same layer, implicit for the whole session: it passes everything through until SteamVR + // runs, then Steam's own web helper gets the shared-texture support it needs (tools/vr). + listOf("usr/local/lib/droiddeck-vr/layer/VK_LAYER_DROIDDECK_vr_implicit.json" to + "usr/share/vulkan/implicit_layer.d/droiddeck_vr.json") val fexPreloads = listOf("x86_64", "i386").flatMap { arch -> listOf("libblsession.so", "libfakeinput.so", "libfaultreport.so").map { "$arch/$it" to "usr/local/lib/droiddeck-fex/$arch/$it" } } + listOf("libthunkaudit.so", "libvulkan-thunk.so").map { "x86_64/$it" to "usr/local/lib/droiddeck-fex/x86_64/$it" } @@ -140,7 +154,7 @@ object SessionFiles { "usr/local/bin/gamescope" to "usr/local/bin/gamescope", "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 + 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, _) -> + ) + mangoapp + 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 } + vr + fexPreloads).filter { (asset, _) -> val dir = asset.substringBeforeLast('/') runCatching { context.assets.list("linuxfs/$dir")?.contains(asset.substringAfterLast('/')) == true }.getOrDefault(false) } diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt index 871bb17d..ff286eeb 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt @@ -7,6 +7,7 @@ import com.droiddeck.launcher.R import com.droiddeck.launcher.core.CpuAllocationDefaults import com.droiddeck.launcher.core.GameEnvironment import com.droiddeck.launcher.core.TextureFiltering +import com.droiddeck.launcher.gpu.Cardboard import com.droiddeck.launcher.gpu.ScreenEffects import org.json.JSONObject @@ -845,6 +846,31 @@ object SessionPrefs { prefs(context).edit().putString("screenEffects", effects.encode().toString()).apply() } + /** The key [cardboard] lives under; SessionActivity watches it so a change applies live. */ + const val CARDBOARD_KEY = "cardboard" + + /** Cardboard mode (gpu/Cardboard) as last set; off by default. */ + fun cardboard(context: Context): Cardboard { + val text = prefs(context).getString(CARDBOARD_KEY, null) ?: return Cardboard.OFF + return runCatching { Cardboard.decode(JSONObject(text)) }.getOrDefault(Cardboard.OFF) + } + + fun setCardboard(context: Context, cardboard: Cardboard) { + prefs(context).edit().putString(CARDBOARD_KEY, cardboard.encode().toString()).apply() + } + + /** The Cardboard picture (Cardboard.SOURCE_*) to go back to once SteamVR stops; -1 = none (it was + * not switched for SteamVR). Kept across activity restarts, so a SteamVR session that outlives the + * activity still hands the picture back. */ + fun cardboardSourceBeforeSteamVr(context: Context): Int = prefs(context).getInt("cardboardSourceBeforeSteamVr", -1) + + fun setCardboardSourceBeforeSteamVr(context: Context, source: Int) { + prefs(context).edit().putInt("cardboardSourceBeforeSteamVr", source).apply() + } + + /** The prefs file [cardboard] lives in, for a change listener. */ + fun sessionPrefs(context: Context): android.content.SharedPreferences = prefs(context) + val textureAnisotropyChoices = TextureFiltering.ANISOTROPY.map { it to if (it == 0) "Off" else "${it}x" } /** [textureAnisotropyChoices] labelled in the app's language. */ diff --git a/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt b/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt index 00a16631..18cd3681 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt @@ -137,6 +137,8 @@ import com.droiddeck.launcher.core.TextureFiltering import com.droiddeck.launcher.gpu.FrameGen import com.droiddeck.launcher.gpu.Lossless import com.droiddeck.launcher.gpu.ScreenEffectLooks +import com.droiddeck.launcher.gpu.Cardboard +import com.droiddeck.launcher.gpu.SteamVr import com.droiddeck.launcher.gpu.ScreenEffects import com.droiddeck.launcher.session.SessionPrefs import com.droiddeck.launcher.session.ComponentsManager @@ -209,6 +211,12 @@ class DrawerActions( val upscaleSharpness: Int = 75, /** The compositor's post chain (gpu/ScreenEffects), changed live. */ val effects: ScreenEffects = ScreenEffects.OFF, + /** Cardboard mode (gpu/Cardboard): side-by-side eyes for a phone VR viewer, changed live. */ + val cardboard: Cardboard = Cardboard.OFF, + /** Whether the device has a rotation sensor (head tracking possible). */ + val cardboardSensor: Boolean = false, + /** SteamVR in the guest for Cardboard mode (gpu/SteamVr); null while unknown. */ + val steamVr: SteamVr.Status? = null, /** Texture filtering for DirectX 9-11 games (core/TextureFiltering); lands on their next launch. */ val textureAnisotropy: Int? = 0, val textureLodBias: String? = TextureFiltering.LOD_BIAS_OFF, @@ -244,6 +252,10 @@ class DrawerActions( val onUpscaler: (Int) -> Unit = {}, val onUpscaleSharpness: (Int) -> Unit = {}, val onEffects: (ScreenEffects) -> Unit = {}, + val onCardboard: (Cardboard) -> Unit = {}, + val onCardboardRecenter: () -> Unit = {}, + /** install, start or stop SteamVR. */ + val onSteamVr: (String) -> Unit = {}, val onTextureAnisotropy: (Int?) -> Unit = {}, val onTextureLodBias: (String?) -> Unit = {}, val onSecondScreenMode: (SecondScreenMode) -> Unit, @@ -465,6 +477,7 @@ internal fun SessionDrawer(open: Boolean, requestedPage: SessionDrawerPage, cont stringResource(R.string.display_sharpness), null, a.upscaleSharpness, 0..100, step = 5, format = { "$it%" }, modifier = focus.track(page, "upscale-sharpness"), onChange = a.onUpscaleSharpness) } + CardboardGroup(host, a) { key -> focus.track(page, key) } } SessionDrawerPage.EFFECTS -> { ScreenEffectsGroup(host, a, advancedEffectsExpanded, @@ -903,6 +916,53 @@ private fun ScreenEffectsGroup( } } +/** Cardboard mode: the eyes, the lens and the virtual screen; everything applies live. */ +@Composable +private fun CardboardGroup(host: MenuHost, a: DrawerActions, track: (String) -> Modifier) { + val c = a.cardboard + SettingsGroup(stringResource(R.string.cardboard_title)) { + ToggleRow(host, "cardboard", stringResource(R.string.cardboard_mode), stringResource(R.string.cardboard_mode_hint), + c.enabled, chipModifier = track("cardboard")) { a.onCardboard(c.copy(enabled = it)) } + if (!c.enabled) return@SettingsGroup + ChoiceRow(host, "cardboard-source", stringResource(R.string.cardboard_source), null, + listOf(Cardboard.SOURCE_SCREEN to stringResource(R.string.cardboard_source_screen), + Cardboard.SOURCE_SBS to stringResource(R.string.cardboard_source_sbs)), + c.source, chipModifier = track("cardboard-source")) { a.onCardboard(c.copy(source = it)) } + ChoiceRow(host, "cardboard-viewer", stringResource(R.string.cardboard_viewer), null, + listOf(Cardboard.Viewer.CARDBOARD_V2.id to stringResource(R.string.cardboard_viewer_v2), + Cardboard.Viewer.CARDBOARD_V1.id to stringResource(R.string.cardboard_viewer_v1), + Cardboard.Viewer.NONE.id to stringResource(R.string.cardboard_viewer_none)), + c.viewer, chipModifier = track("cardboard-viewer")) { a.onCardboard(c.copy(viewer = it)) } + SliderRow(stringResource(R.string.cardboard_ipd), null, c.ipdMm, 50..80, step = 1, + format = { "$it mm" }, modifier = track("cardboard-ipd")) { a.onCardboard(c.copy(ipdMm = it)) } + if (c.source == Cardboard.SOURCE_SCREEN) { + SliderRow(stringResource(R.string.cardboard_screen_width), null, c.screenWidthDm, 5..200, step = 5, + format = { String.format(Locale.US, "%.1f m", it / 10f) }, modifier = track("cardboard-width")) { a.onCardboard(c.copy(screenWidthDm = it)) } + SliderRow(stringResource(R.string.cardboard_screen_distance), null, c.screenDistanceDm, 5..200, step = 5, + format = { String.format(Locale.US, "%.1f m", it / 10f) }, modifier = track("cardboard-distance")) { a.onCardboard(c.copy(screenDistanceDm = it)) } + } + ToggleRow(host, "cardboard-tracking", stringResource(R.string.cardboard_head_tracking), + if (a.cardboardSensor) null else stringResource(R.string.cardboard_no_sensor), + c.headTracking && a.cardboardSensor, enabled = a.cardboardSensor, chipModifier = track("cardboard-tracking")) { + a.onCardboard(c.copy(headTracking = it)) + } + if (c.headTracking && a.cardboardSensor) + ActionRow(stringResource(R.string.cardboard_recenter), null, stringResource(R.string.cardboard_recenter_button), a.onCardboardRecenter) + val vrLabel = stringResource(R.string.cardboard_steamvr) + when (a.steamVr) { + SteamVr.Status.NOT_INSTALLED -> ActionRow(vrLabel, stringResource(R.string.cardboard_steamvr_install_hint), + stringResource(R.string.cardboard_steamvr_install), { a.onSteamVr("install") }) + SteamVr.Status.INSTALLING -> SettingsRow(vrLabel, stringResource(R.string.cardboard_steamvr_installing)) {} + SteamVr.Status.READY -> ActionRow(vrLabel, stringResource(R.string.cardboard_steamvr_ready_hint), + stringResource(R.string.cardboard_steamvr_start), { a.onSteamVr("start") }) + SteamVr.Status.RUNNING -> ActionRow(vrLabel, stringResource(R.string.cardboard_steamvr_running_hint), + stringResource(R.string.cardboard_steamvr_stop), { a.onSteamVr("stop") }) + SteamVr.Status.UNAVAILABLE -> SettingsRow(vrLabel, stringResource(R.string.cardboard_steamvr_unavailable)) {} + null -> {} + } + } +} + /** Secondary-display modes are mutually exclusive and apply immediately. */ @Composable private fun SecondScreenGroup(host: MenuHost, a: DrawerActions, track: (String) -> Modifier) { diff --git a/app/src/main/java/com/droiddeck/launcher/wayland/WaylandCompositor.java b/app/src/main/java/com/droiddeck/launcher/wayland/WaylandCompositor.java index f5b369d0..14016b29 100644 --- a/app/src/main/java/com/droiddeck/launcher/wayland/WaylandCompositor.java +++ b/app/src/main/java/com/droiddeck/launcher/wayland/WaylandCompositor.java @@ -450,6 +450,20 @@ public final class WaylandCompositor { /** The Look the controls currently match ({@code null} = Custom); only named in the log line. */ public static native void nativeSetLookName(String name); + /** Cardboard mode (waylandcomp/src/cardboard.h): two lens-distorted eye views side by side. + * {@code source} 0 = the frame as a virtual screen, 1 = the frame is already a side-by-side + * stereo pair. Distances in metres, field of view in degrees. */ + public static native void nativeSetCardboard(boolean enabled, int source, float ipdM, float screenDistM, + float screenWidthM, float k1, float k2, float fovDeg, + float lensInset, float chroma); + + /** The head pose for Cardboard mode: head -> world rotation, row-major 3x3 (any thread). {@code live} + * = a sensor produced it. */ + public static native void nativeSetCardboardPose(float[] rotation, boolean live); + + /** Where the head pose is shared with the guest (waylandcomp/src/headpose_shm.h); set once. */ + public static native void nativeSetCardboardPoseFile(String path); + /** The in-game FPS limiter: frames per second, 0 = unlimited. Paces when replaced buffers go * back to the game, like the X11 IdleNotify pacer, so the game itself slows to the cap. */ public static native void nativeSetFpsLimit(int fps); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c0358f3b..d86d1824 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -575,6 +575,33 @@ Sharpening (CAS) Sharpening level Fake HDR + Cardboard VR + Cardboard mode + Left and right eye views for a phone VR viewer. + Picture + Virtual screen + Side-by-side 3D + Viewer + Cardboard + Cardboard (2014) + No lens + Eye distance + Screen width + Screen distance + Head tracking + This device has no rotation sensor. + Recenter view + Recenter + SteamVR (experimental) + Install + Downloads Valve\'s ARM64 SteamVR (571 MB). + Downloading and unpacking SteamVR… + Start + Runs SteamVR with your head as the headset; the picture switches to side-by-side 3D. + Stop + SteamVR is running. + Not available in this session. + SteamVR did not respond; see the session log. Debanding Debanding strength Brightness diff --git a/app/src/test/java/com/droiddeck/launcher/gpu/CardboardTest.kt b/app/src/test/java/com/droiddeck/launcher/gpu/CardboardTest.kt new file mode 100644 index 00000000..c388569c --- /dev/null +++ b/app/src/test/java/com/droiddeck/launcher/gpu/CardboardTest.kt @@ -0,0 +1,177 @@ +package com.droiddeck.launcher.gpu + +import org.json.JSONObject +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import kotlin.math.PI +import kotlin.math.cos +import kotlin.math.sin +import kotlin.math.sqrt + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [28], manifest = Config.NONE) +class CardboardTest { + private val eps = 1e-4f + + private fun assertVec(expected: FloatArray, actual: FloatArray) = assertArrayEquals(expected, actual, eps) + + /** Rotation about a unit axis by an angle, as a quaternion (x, y, z, w). */ + private fun quat(ax: Float, ay: Float, az: Float, deg: Double): FloatArray { + val h = deg * PI / 360.0 + val s = sin(h).toFloat() + return floatArrayOf(ax * s, ay * s, az * s, cos(h).toFloat()) + } + + private fun matrix(q: FloatArray) = HeadPose.fromQuaternion(q[0], q[1], q[2], q[3]) + + @Test fun settingsRoundTripAndClamp() { + val c = Cardboard(enabled = true, source = Cardboard.SOURCE_SBS, viewer = "none", ipdMm = 70, + screenWidthDm = 55, screenDistanceDm = 25, headTracking = false) + assertEquals(c, Cardboard.decode(c.encode())) + val wild = Cardboard.decode(JSONObject().put("ipdMm", 500).put("screenWidthDm", -3).put("viewer", "nope").put("source", 7)) + assertEquals(80, wild.ipdMm) + assertEquals(5, wild.screenWidthDm) + assertEquals(Cardboard.Viewer.CARDBOARD_V2.id, wild.viewer) + assertEquals(Cardboard.SOURCE_SCREEN, wild.source) + assertEquals(Cardboard.OFF, Cardboard.decode(JSONObject())) + } + + @Test fun lensInsetLinesTheEyesUpWithTheLenses() { + // A 128 mm panel with 64 mm lenses: each viewport centre is 32 mm out, exactly under its lens. + assertEquals(0f, Cardboard.lensInset(128f, 64f), eps) + // A wider 150 mm panel: viewport centres 37.5 mm out, lenses 32 mm: 5.5 mm of a 75 mm viewport inwards. + assertEquals(5.5f / 75f, Cardboard.lensInset(150f, 64f), eps) + // A narrow panel puts the lens centres outside the viewport centres (negative inset). + assertEquals(true, Cardboard.lensInset(110f, 64f) < 0f) + assertEquals(0f, Cardboard.lensInset(0f, 64f), eps) + assertEquals(0f, Cardboard.lensInset(150f, 0f), eps) + } + + @Test fun steamVrStatusFollowsTheAgentAnswer() { + fun answer(vararg pairs: Pair) = JSONObject().apply { pairs.forEach { put(it.first, it.second) } } + assertEquals(SteamVr.Status.UNAVAILABLE, SteamVr.parse(answer("ok" to false, "installed" to true, "driver" to true))) + assertEquals(SteamVr.Status.UNAVAILABLE, SteamVr.parse(answer("ok" to true, "installed" to true, "driver" to false))) + assertEquals(SteamVr.Status.NOT_INSTALLED, SteamVr.parse(answer("ok" to true, "driver" to true))) + assertEquals(SteamVr.Status.INSTALLING, SteamVr.parse(answer("ok" to true, "installing" to true))) + assertEquals(SteamVr.Status.READY, SteamVr.parse(answer("ok" to true, "installed" to true, "driver" to true))) + assertEquals(SteamVr.Status.RUNNING, + SteamVr.parse(answer("ok" to true, "installed" to true, "driver" to true, "running" to true))) + // The helper itself failed: the agent still says ok. + assertEquals(SteamVr.Status.UNAVAILABLE, + SteamVr.parse(answer("ok" to true, "installed" to true, "driver" to true).put("exitCode", 1))) + } + + @Test fun steamVrActionsSucceedOnlyWhenTheHelperDid() { + fun answer(ok: Boolean, exitCode: Int? = null, started: Boolean? = null) = JSONObject().apply { + put("ok", ok) + exitCode?.let { put("exitCode", it) } + started?.let { put("started", it) } + } + assertTrue(SteamVr.succeeded("start", answer(true, exitCode = 0))) + assertTrue(SteamVr.succeeded("stop", answer(true, exitCode = 0))) + // The agent ran the request, but droiddeck-steamvr start failed (not installed, no driver). + assertFalse(SteamVr.succeeded("start", answer(true, exitCode = 1))) + assertFalse(SteamVr.succeeded("stop", answer(true, exitCode = 2))) + assertFalse(SteamVr.succeeded("start", answer(true))) + assertFalse(SteamVr.succeeded("start", answer(false, exitCode = 0))) + assertTrue(SteamVr.succeeded("install", answer(true, started = true))) + assertFalse(SteamVr.succeeded("install", answer(true))) + assertFalse(SteamVr.succeeded("install", answer(false, started = true))) + } + + @Test fun quaternionMatchesKnownRotations() { + // 90 degrees about z: x -> y. + val m = matrix(quat(0f, 0f, 1f, 90.0)) + assertVec(floatArrayOf(0f, 1f, 0f), HeadPose.apply(m, floatArrayOf(1f, 0f, 0f))) + // Rotation vector without w: computed from the other three. + val q = HeadPose.quaternionFromRotationVector(floatArrayOf(0f, 0f, sin(PI / 4).toFloat())) + assertEquals(cos(PI / 4).toFloat(), q[3], eps) + } + + @Test fun recenterMakesTheCurrentViewStraightAhead() { + // Phone upright in landscape, screen facing the viewer, who faces north-east-ish. + // Upright device: screen up = world up; looking direction = the screen's -z. + for (heading in listOf(0.0, 37.0, 140.0, -100.0)) { + // Start from "screen faces south, viewer looks north": device x = east, y = up, z = south. + val upright = floatArrayOf(1f, 0f, 0f, 0f, 0f, -1f, 0f, 1f, 0f) + val yaw = matrix(quat(0f, 0f, 1f, heading)) + val screenToWorld = HeadPose.multiply(yaw, upright) + assertVec(HeadPose.IDENTITY, HeadPose.relative(HeadPose.recenter(screenToWorld), screenToWorld)) + } + } + + @Test fun recenterKeepsPitchButDropsHeading() { + val upright = floatArrayOf(1f, 0f, 0f, 0f, 0f, -1f, 0f, 1f, 0f) + // Looking 30 degrees down, then turning 50 degrees: after a recentre the view is still 30 degrees down. + val pitched = HeadPose.multiply(upright, matrix(quat(1f, 0f, 0f, -30.0))) + val turned = HeadPose.multiply(matrix(quat(0f, 0f, 1f, 50.0)), pitched) + val pose = HeadPose.relative(HeadPose.recenter(turned), turned) + val forward = HeadPose.apply(pose, floatArrayOf(0f, 0f, -1f)) + assertEquals(0f, forward[0], eps) + assertEquals(-sin(PI / 6).toFloat(), forward[1], eps) + assertEquals(-cos(PI / 6).toFloat(), forward[2], eps) + } + + @Test fun turningTheHeadTurnsTheView() { + val upright = floatArrayOf(1f, 0f, 0f, 0f, 0f, -1f, 0f, 1f, 0f) + val ref = HeadPose.recenter(upright) + // Turn the head 90 degrees to the left (counter-clockwise seen from above). + val turned = HeadPose.multiply(matrix(quat(0f, 0f, 1f, 90.0)), upright) + val forward = HeadPose.apply(HeadPose.relative(ref, turned), floatArrayOf(0f, 0f, -1f)) + // The view now points to the left of the recentred forward direction: -x. + assertVec(floatArrayOf(-1f, 0f, 0f), forward) + } + + @Test fun heldFlatTheViewStartsStraightAhead() { + // Screen facing up (device on a table, held like a tray, or lying back looking up at it). + for (m in listOf(HeadPose.IDENTITY, HeadPose.multiply(matrix(quat(0f, 0f, 1f, 30.0)), matrix(quat(1f, 0f, 0f, 20.0))))) { + assertVec(HeadPose.IDENTITY, HeadPose.relative(HeadPose.recenter(m), m)) + } + } + + @Test fun heldFlatTurningAtTheHipsPans() { + // Held flat, then the body turns 40 degrees to the left about the real vertical: the view + // turns left about its own up axis - it must not roll (the bug: yaw came out as a tilt). + val flat = HeadPose.IDENTITY + val ref = HeadPose.recenter(flat) + val turned = HeadPose.multiply(matrix(quat(0f, 0f, 1f, 40.0)), flat) + val pose = HeadPose.relative(ref, turned) + val forward = HeadPose.apply(pose, floatArrayOf(0f, 0f, -1f)) + val up = HeadPose.apply(pose, floatArrayOf(0f, 1f, 0f)) + assertVec(floatArrayOf(-sin(PI * 40 / 180).toFloat(), 0f, -cos(PI * 40 / 180).toFloat()), forward) + assertVec(floatArrayOf(0f, 1f, 0f), up) + // Tipping the top edge down (pitching the tray towards you) looks down. + val tipped = HeadPose.multiply(flat, matrix(quat(1f, 0f, 0f, -20.0))) + val down = HeadPose.apply(HeadPose.relative(ref, tipped), floatArrayOf(0f, 0f, -1f)) + assertEquals(-sin(PI / 9).toFloat(), down[1], eps) + } + + @Test fun fieldOfViewComesFromThePanelSeenThroughTheLens() { + val v2 = Cardboard.Viewer.CARDBOARD_V2 + // A 74.7 mm tall panel 39.3 mm behind the lens: atan(37.35 / 39.3) = 43.5 degrees each way. + assertEquals(87.07f, Cardboard.fovDeg(v2, 74.7f), 0.05f) + assertEquals(v2.fallbackFovDeg, Cardboard.fovDeg(v2, 0f), eps) + assertEquals(Cardboard.Viewer.NONE.fallbackFovDeg, Cardboard.fovDeg(Cardboard.Viewer.NONE, 74.7f), eps) + } + + @Test fun displayRotationRemapsTheScreenAxes() { + val r = matrix(quat(0.3f, -0.5f, 0.2f, 70.0).let { q -> + val n = sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]); floatArrayOf(q[0] / n, q[1] / n, q[2] / n, q[3] / n) + }) + assertVec(r, HeadPose.toScreen(r, 0)) + // ROTATION_90 (as input/PadMotion maps it): screen x is the device's -y, screen y the device's x. + val s90 = HeadPose.toScreen(r, 1) + assertVec(HeadPose.apply(r, floatArrayOf(0f, -1f, 0f)), HeadPose.apply(s90, floatArrayOf(1f, 0f, 0f))) + assertVec(HeadPose.apply(r, floatArrayOf(1f, 0f, 0f)), HeadPose.apply(s90, floatArrayOf(0f, 1f, 0f))) + // ROTATION_270 is the opposite turn; four quarter turns of 90 give the original back. + val s270 = HeadPose.toScreen(r, 3) + assertVec(HeadPose.apply(r, floatArrayOf(0f, 1f, 0f)), HeadPose.apply(s270, floatArrayOf(1f, 0f, 0f))) + assertVec(r, HeadPose.toScreen(HeadPose.toScreen(HeadPose.toScreen(HeadPose.toScreen(r, 1), 1), 1), 1)) + } +} diff --git a/docs/cardboard-vr.md b/docs/cardboard-vr.md new file mode 100644 index 00000000..780226cc --- /dev/null +++ b/docs/cardboard-vr.md @@ -0,0 +1,194 @@ +# Cardboard VR (experimental) + +Cardboard mode turns the session screen into a phone VR viewer (Google Cardboard and similar): the +panel is split into a left and a right eye, each drawn through a barrel distortion that the viewer's +lenses undo. Two sources can feed it: + +- **Virtual screen** (any game, Steam, the desktop): the session hangs as a flat screen in front of + you, with real stereo depth from the eye distance. With head tracking on, the phone's rotation + sensor keeps the screen fixed in the room while your head turns (3DoF). +- **Side-by-side 3D**: the frame already holds a left and a right image. This is what SteamVR draws + here (below), and what SBS 3D video or a stereo shader like SuperDepth3D produces. Only the lens is + added. + +The settings live in the session drawer, **Display > Cardboard VR**: mode, picture, viewer (lens +profile), eye distance, screen size and distance, head tracking and recenter, and SteamVR. Everything +applies live. `tools/droiddeckctl prefs set session cardboard '{"enabled":true,...}'` drives the same +settings (`gpu/Cardboard.kt` has the keys). + +## How the picture is made + +``` +game / Steam -> gamescope -> DroidDeck compositor -> effects -> frame generation + -> Cardboard pass (cardboard.frag) -> 1:1 blit -> panel +``` + +`app/src/main/cpp/waylandcomp/src/cardboard.c` is the last stage of the compositor pass. It forces the +copy path (no zero-copy layer: the pass draws the whole panel) and renders `cardboard.frag` into a +panel-sized target: + +- each output pixel picks its eye, finds its offset from that eye's lens centre in tangent space, and + applies the viewer's distortion polynomial `r(1 + k1 r² + k2 r⁴)` (sampling outwards draws the image + barrel-shaped; the lens pincushions it back), optionally per colour channel for chromatic + aberration; +- **virtual screen**: the ray from the eye (offset by half the IPD), rotated by the head pose, + meets a plane `screenDistance` away; outside it is a dark backdrop; +- **side-by-side**: the eye samples its own half of the frame over the same field of view. + +The lens centres sit inset towards the nose so they line up with the viewer's lenses on the panel +actually used (the panel's physical size from DisplayMetrics). The field of view is the panel's height +seen from the lens's focal distance (`Cardboard.fovDeg`). Viewer profiles are in `Cardboard.Viewer`. + +Head tracking (`gpu/HeadTracker.kt`) reads the game rotation vector (gyro and accelerometer, no compass) +on a thread of its own and hands a row-major head-to-world matrix straight to native code. Recenter +resets the heading only, so the horizon stays level. When the view is more than 60° from level (lying +back, or the device flat on a table) it takes the whole orientation as straight ahead. A new pose +redraws even a static scene (`vkp_cardboard_pose_changed` on the vsync tick). + +The pose is also published to the guest: a 128-byte seqlocked file, `headpose_shm.h`, at +`/run/droiddeck/droiddeck-headpose`. It carries the rotation, the IPD, and the frustum the side-by-side +pass expects of a source (`tan_half_fov`, `eye_aspect`, eye size), so a renderer in the guest can match +the lens exactly. + +## SteamVR + +Valve's ARM64 SteamVR, the build the Steam Frame runs, comes from Valve's public SteamOS package +server (`deckard-steamvr`, pinned by version and SHA-256 in `droiddeck-steamvr`; the same hash +[steam-frame-nixos](https://github.com/y0usaf/steam-frame-nixos) pins). `droiddeck-steamvr install` +unpacks it to `/opt/steamvr`. The drawer's **Install** button runs that (571 MB). **Start** then +switches the picture to side-by-side and runs SteamVR with three DroidDeck pieces (`tools/vr`): + +1. **`driver_droiddeck.so`**, an OpenVR driver with one HMD: + - pose: the shared head pose file (3DoF, standing height); + - projection: exactly the frustum the Cardboard pass samples, with identity distortion (the app does + the lens); + - a proximity sensor that reads "worn" while Cardboard mode is on. Without it SteamVR takes a still + headset (a phone on a table) for an unused one, enters standby after a few seconds, and the + compositor stops submitting frames; + - frames: `IVRDriverDirectModeComponent` with a free-running 60 Hz vsync announcer, as ALVR does on + Linux. SteamVR renders each eye into textures the driver allocates through its IPC resource + manager (dmabufs); + - `presenter.cpp` imports those eye textures into a Vulkan device of its own and blits them side + by side into a fullscreen window on DroidDeck's own compositor (`wayland-0`, the app_id + `droiddeck-cardboard-vr` keeps it above gamescope's window), sized to the Cardboard panel so + nothing scales it down. SteamVR hands the textures over with no fence the driver can wait on, so + the presenter gives the compositor's GPU work a few milliseconds first, as ALVR does. + `DROIDDECK_VR_PRESENT=x11` uses the older path instead: an X11 window on gamescope's Xwayland marked + `STEAM_OVERLAY` (the external overlay slot is mangoapp's), at the session's resolution; +2. **`VK_LAYER_DROIDDECK_vr`**, a Vulkan layer, enabled explicitly in SteamVR's processes and implicit + for the rest of the session (Steam's own web helper shares its in-VR overlay with SteamVR too, and + crashes without it until Steam quits). Implicitly it does nothing in an ordinary session: + - the display stand-in below happens only while SteamVR runs (`droiddeck-steamvr`'s `active` + marker, which a new session clears) or where the layer is enabled by name. Gamescope and games + asking for display extensions outside SteamVR get them untouched; + - the semaphore bridge runs in those same processes, and in Steam's web helper once SteamVR is + installed; + - with SteamVR not installed, every process gets the driver's functions unchanged. + + What it does in SteamVR's processes: + - `vrcompositor` always asks for `VK_KHR_display` and keeps a direct-mode window even with a + driver-direct HMD. Turnip on KGSL refuses to enumerate any GPU when `VK_KHR_display` is enabled + ("I can't KHR_display"). The layer takes the display extensions off the instance and stands in + for one display with one mode and one plane, whose surface is a headless one + (`VK_EXT_headless_surface`, sized from the mode because headless reports none). The vblank side + of `VK_EXT_display_control` is a 60 Hz clock; + - SteamVR shares a timeline semaphore with every shared image, exported as an opaque fd, which + Turnip/KGSL cannot export. The layer creates it unshared and hands out a memfd holding a 64-bit + counter instead. Every process with the semaphore keeps a real local one, and a poller thread + carries values both ways (reached locally: up into the counter; seen in the counter: signalled + locally from the host), so GPU waits work as written, a poll period late. Waits on an imported + binary semaphore, which cannot be bridged that way, are dropped. With the semaphores dropped + outright (the first version) the dashboard showed torn, half-drawn tiles; + - Turnip on KGSL does not defer a submit that waits on a timeline value nobody has signalled yet: + `vkQueueSubmit` blocks until it is. SteamVR's client waits on a compositor value that the + compositor signals only after the client has submitted and said so, which deadlocked every app + before its first frame (`helloxr`'s swapchain acquire, DXVK's submit thread in a Proton game). + A submit's wait on an imported shared value that is neither reached nor queued in that process + gets 3 ms for the other process to reach it, then is dropped; + - Steam's web helper has the semaphore bridge whenever SteamVR is installed: its GPU process + outlives SteamVR runs, and one started without the layer crashed when SteamVR showed Steam's + overlays, until Steam quit; + - `DROIDDECK_VR_CRASHTRACE=1` (or a `crashtrace` file beside the `active` marker, for processes like + Steam whose environment is not ours) prints a backtrace on a fatal signal. `vrcompositor`'s + launcher drops `LD_PRELOAD`, but the layer is loaded there. It only takes signals nobody handles + yet: Wine and FEX handle `SIGSEGV` as part of running a Proton game, and a replaced handler + crashed The Lab; + - a `semtrace` file beside the marker logs, every 2 s, each bridged semaphore's local and shared + value and the memfd inode that names it in every process, plus the first waits and signals + (values up to 3) submitted on them. +3. **`droiddeck-steamvr`**, the guest helper (`status | install | start | stop`), which the app drives + through the guest agent's fixed `vr` request (no agent commands needed). It also supplies an `xset` + stand-in, because the dashboard's web helper checks the X server with it. + +Imported eye textures carry the same format list SteamVR's mutable sRGB images do. With it Turnip keeps +UBWC compression on, and an import that disagreed read compressed data as garbage. + +Two more fixes outside `tools/vr` were needed for the dashboard (Steam's library, in VR): + +- **SysV semaphores** (`tools/linuxfs/preload/sysv.c`): the session emulates them, and `IPC_STAT` + reported `sem_otime` 0 forever. SteamVR's web helper does the usual race-free init (wait until + `sem_otime` is non-zero), so its zygotes slept in a `semctl`/`usleep` loop and the dashboard never + appeared. `semop` now stamps `sem_otime`, `SETVAL`/`SETALL`/`IPC_SET` stamp `sem_ctime`; + `test_sysv_preload.py` covers it. +- `vrwebhelper` gets the same tracer treatment as Steam's `steamwebhelper` (`tracer.c`). + +Verified on the AYN Thor (SD 8 Gen 2, Turnip): SteamVR 2.17.10 starts with the DroidDeck HMD active, +the compositor runs driver-direct at about 60 fps, the dashboard (Steam's library in VR, with the +controller's laser) shows cleanly in both eyes at the panel's resolution while Steam keeps running, and +a pose written to the shared file turns SteamVR's view. Started from the drawer button through the agent. + +### Not done yet + +- **Controllers**: the driver adds two Vive-profile hands driven by the session's game pad. It reads + the absolute-state snapshot the app already publishes in fake input ring 0 + (`FAKE_EVDEV_MEMFD_PATHS`), so the app needs no new plumbing. They are a gaze pointer: low in front + of each hand, pointing where the head looks. Mapping, right hand: right stick = trackpad, A/R3 = + trackpad click, RT = trigger, RB = grip, Start = menu, guide = system. Left hand: left stick, X/L3, + LT, LB, Select. Both hands activate in SteamVR and their laser shows on the dashboard; guide, Start and + A presses show up in the ring the driver reads. +- **Layers**: only the first layer of each frame is drawn. SteamVR's compositor composites the + dashboard into it, so this has not mattered yet. +- **Timing**: no reprojection, and the presenter blits whatever was submitted last, a guard delay + after it arrived. The chain is SteamVR → presenter → DroidDeck compositor → lens pass. +- **Cross-process GPU sync** is a poll away from exact (see the semaphore note above); binary + semaphores are not bridged. +- **Apps and games**: Windows VR games run through Proton ARM64 (FEX for the x86-64 game, DXVK, and + Proton's `vrclient_x64` bridging to SteamVR's ARM64 `vrclient.so`). Tested on the Thor: + - **Thumper** (356400, its own D3D11 engine), started with its VR launch option: menus and levels + at 60 Hz, about 7 ms CPU and 13 ms GPU a frame, 23 dropped frames in 6485; + - **The Thrill of the Fight** (494150, Unity): loads into its gym, 60 Hz with 6.5 ms CPU and 8.5 ms + GPU a frame; + - **The Lab** (6.6 GB) loads, but Android's low-memory killer took the whole app while it did, next + to Steam and SteamVR. Two VR games at once do the same, so quit one (`droiddeckctl quit `) + before starting the next; + - **SUPERHOT VR** stops at its EULA, which SteamVR's dashboard shows in VR; + - **SteamVR Home** is not in the ARM64 package. Steam's own Windows SteamVR (250820) has it + (`tools/steamvr_environments`, `steamtours.exe`), and it starts under Proton, but Proton's + `vrclient_x64` implements only `IVROverlayView_003` and Home asks for `_002`, so it stops at + "SteamVR initialization failed!"; + - SteamVR's ARM64 OpenXR sample, `helloxr -g Vulkan2`, reaches a focused session and submits frames + (it was stuck before the first one until the wait fix above), but its picture shows up as torn + tiles: next to chase. `hellovr_sdl` (OpenGL) needs GLEW, which the runtime lacks. + + SteamVR opens its game menu over a newly started game; `vrcmd --hidedashboard` (in + `/opt/steamvr/bin/linuxarm64`) closes it. +- **Quest 3**: unrelated to this; on the Quest DroidDeck stays a 2D panel (`docs/quest.md`). + +## Building and testing + +- `tools/vr/build.sh [OUT]` builds the driver and the layer in the local cross image, or + `--native OUT` with aarch64 compilers on PATH (CI). `tools/build_local.sh` and CI put them in the + APK under `assets/linuxfs/usr/local/lib/droiddeck-vr`; `SessionFiles` stages them, and + `droiddeck-steamvr`, into the rootfs. +- Shader: `cardboard_frag.h` is generated from `cardboard.frag` + (`glslangValidator -V --vn cardboard_frag_code -o cardboard_frag.h cardboard.frag`). + `tools/tests/test_cardboard.py` checks it is current, and checks the push-constant packing on the + host. +- `CardboardTest` covers the settings, lens inset and field of view, the head-pose maths (quaternions, + display-rotation remaps, recentring) and the SteamVR status. `test_droiddeck_agent.py` covers the + `vr` request. +- Logs: the compositor's `[cardboard]` lines; SteamVR's in `~/.local/share/Steam/logs` + (`vrserver.txt`, `vrcompositor.txt`) and `/run/droiddeck/droiddeck-steamvr/vrstartup.log`. The + driver's lines start with `droiddeck:`. + +Lessons from bringing this up on the Thor, and how to debug a live session: `docs/development/cardboard-vr-learnings.md`. diff --git a/docs/development/cardboard-vr-learnings.md b/docs/development/cardboard-vr-learnings.md new file mode 100644 index 00000000..34c89789 --- /dev/null +++ b/docs/development/cardboard-vr-learnings.md @@ -0,0 +1,131 @@ +# Cardboard VR and SteamVR on Turnip: what we learned + +Written 2026-10-13, after the first stretch of Cardboard mode work on the AYN Thor (Snapdragon 8 Gen 2, +Adreno 740, Turnip on KGSL). `docs/cardboard-vr.md` describes how the feature works. This page records +what it took to get there: the traps, why each fix looks the way it does, how to debug a live session, +and what is still open. Read it before the next round of VR work. + +## Where it stands + +| What | Result | +| --- | --- | +| Cardboard mode (lens pass, 3DoF head tracking, recentre) | Works on any picture; picture follows SteamVR on and off | +| SteamVR 2.17.10 ARM64 (the Steam Frame's `deckard-steamvr`) | Starts from the drawer; driver-direct at 60 Hz, dashboard and Steam library in VR | +| Thumper (356400, D3D11, VR launch option) | Menus and levels at 60 Hz, 7 ms CPU / 13 ms GPU, 23 of 6485 frames dropped | +| The Thrill of the Fight (494150, Unity) | Gym loads, 60 Hz, 6.5 ms CPU / 8.5 ms GPU | +| The Lab (450390) | Loads, then Android's low-memory killer takes the whole app | +| SUPERHOT VR (617830) | Reaches its EULA in the dashboard (not accepted in testing) | +| SteamVR Home | Blocked: Proton's `vrclient_x64` lacks `IVROverlayView_002` | +| `helloxr -g Vulkan2` (OpenXR) | Session focused and submitting, picture shows as torn tiles | + +Screenshots from the Thor: [Thumper gameplay](evidence/cardboard-thumper-vr-gameplay-2026-10-13.png), +[Thumper title](evidence/cardboard-thumper-vr-cardboard-2026-10-13.png), +[The Thrill of the Fight](evidence/cardboard-thrill-of-the-fight-cardboard-2026-10-13.png), +[the SteamVR dashboard with a game's EULA](evidence/cardboard-steamvr-dashboard-2026-10-13.png). + +Games run as x86-64 Windows builds through Proton ARM64 (FEX, DXVK). Proton's `vrclient_x64` forwards +OpenVR to SteamVR's ARM64 `vrclient.so`, so no x86 SteamVR is involved. + +## Turnip on KGSL: the four things SteamVR trips over + +1. **`VK_KHR_display` makes the GPU disappear.** Turnip on KGSL enumerates no physical device when an + instance enables it ("I can't KHR_display"). `vrcompositor` always asks for it. The layer takes the + display extensions off the instance and fakes one display, mode and plane backed by a headless + surface. Headless surfaces report a `UINT32_MAX` extent, so the swapchain is sized from the mode. +2. **No exportable semaphores.** SteamVR shares a timeline semaphore with every shared image as an + opaque fd, and Turnip/KGSL cannot export one. The layer creates the semaphore unshared and hands out + a memfd (`droiddeck-shared-sem`) holding a 64-bit counter. A poller thread in each process carries + values both ways every 300 µs. Dropping the semaphores outright, the first attempt, gave torn + dashboard tiles. +3. **Wait-before-signal blocks the submitting thread.** Turnip does not defer a submit that waits on + a timeline value nobody has signalled yet; `vkQueueSubmit` sleeps until it is. SteamVR's client + waits on a compositor value that the compositor signals only after the client has submitted and + said so, so every client deadlocked before its first frame: + - `helloxr` blocked in a wait-idle in `vrclient.so`; + - DXVK's `dxvk-queue` thread blocked in Proton games. + + The layer now gives a submit's wait on an imported shared value 3 ms, then drops it. It is kept + when the value is already reached, or when this process has queued the signal itself. Two details + mattered: + - the waits are judged before the submit's own signals count, because an acquire waits on one + value and signals the next on the same semaphore; + - SteamVR's client interop device is Vulkan 1.0, so only the `KHR` entry points resolve there. +4. **UBWC layouts must agree.** An imported image has to be created with the same + `VkImageFormatListCreateInfo` (sRGB + UNORM) as its creator, or Turnip lays out the compression + differently and the reader sees garbage. The presenter's eye-texture import does this. + +## Other traps, in the order they cost time + +- **A still headset goes to standby.** A phone on a table looks unworn, SteamVR enters standby after + about 15 s, and the compositor stops submitting. The driver's proximity sensor reads "worn" while + Cardboard is on. +- **Emulated SysV semaphores never set `sem_otime`.** SteamVR's web helper waits for it to become + non-zero before using a semaphore set, so the dashboard never appeared. `sysv.c` now stamps + `sem_otime` and `sem_ctime`. +- **Steam's web helper needs the layer too.** It shares Steam's in-VR overlays with SteamVR and crashed + without the layer; after a few crashes Steam quits. Its GPU process outlives SteamVR runs, so + `steamwebhelper` gets the semaphore bridge whenever SteamVR is installed, not only while SteamVR's + `active` marker exists. It never gets the display stand-in. +- **Keep the layer out of ordinary sessions.** As an implicit layer it loads everywhere. Display + extensions are only taken off while SteamVR runs, a new session clears a leftover `active` marker, + and without SteamVR installed every process gets the driver's functions unchanged. Review feedback + on the first version pointed out that gamescope asking for a display extension would have been + rewritten in an ordinary session. +- **The `active` marker race.** Clearing a stale marker whenever `vrserver` is not running also + cleared it during startup, before `vrserver` appeared, because the app polls status every 3 s. Steam + quit as a result. Only a marker older than 2 minutes is cleared now. +- **Signal handlers in Proton games.** The layer's crash tracer replaced the `SIGSEGV` handler that FEX + and Wine rely on, and The Lab crashed in FEX's JIT. It now installs only where nothing handles the + signal. +- **mangoapp owns gamescope's single external overlay slot**, so an X11 presenter has to use + `STEAM_OVERLAY`. The default presenter is now a Wayland window on DroidDeck's own compositor, kept + on top by app id. +- **Memory.** Steam, SteamVR and one VR game fit in the Thor's 12 GB. A large game such as The Lab, or + two games at once, push the device into critical pressure, and the low-memory killer takes + DroidDeck itself (`ended-without-teardown.txt` in the session logs, `lowmemorykiller` in logcat). +- **Head-tracking axes.** With the display at `ROTATION_90`, screen x is minus device y and screen y is + device x. Getting this wrong made a turn at the hips tilt the view. Recentring now keeps the world + vertical and resets the heading only. + +## Debugging a live session + +- **Logs:** `~/.local/share/Steam/logs/{vrserver,vrcompositor,xrclient_*}.txt` and + `/run/droiddeck/droiddeck-steamvr/vrstartup.log`. Lines from the driver start with `droiddeck:`, + lines from the layer with `[droiddeck-vr-layer]`. A game's layer messages land in the session log. +- **Markers beside `active`** in `/run/droiddeck/droiddeck-steamvr/`: + - `crashtrace` prints backtraces on fatal signals; + - `semtrace` logs each bridged semaphore's local and shared value every 2 s, named by the memfd + inode so the same semaphore can be matched across processes, plus the first waits, signals and + shared image create infos. + + Both are read when a process starts. +- **Frame stats:** in `/opt/steamvr/bin/linuxarm64`, `LD_LIBRARY_PATH=. ./vrcmd --stats` reports + submits, drops and app CPU/GPU times. `./vrcmd --hidedashboard` closes the menu SteamVR opens over a + new game. +- **Where a process is stuck:** a stack scan of `/proc//mem` from the saved stack pointer (a small + Python helper) gives module+offset frames. That is how the wait-idle in `vrclient.so` and the + blocked `dxvk-queue` thread were found. +- **VR launch options:** `SteamClient.Apps.GetLaunchOptionsForApp(id)` lists them, and + `SteamClient.Apps.RunGame("", "", , 100)` starts one, through `droiddeckctl cdp eval`. +- **Quitting:** `droiddeckctl quit `. Without the id the game keeps running. +- **Pitfalls when testing:** + - never `pkill -f` or `pgrep -f` a pattern inside a guest command, because it matches the + command's own shell; use `pgrep -x`; + - replace a loaded `.so` by copying to a temporary name and `mv`, never `cp` over it; + - a session start restages the driver and layer from the APK assets, so a hand-copied build is + lost on the next start. + +## Open items + +- **`helloxr` picture.** Both sides create the swapchain images with identical parameters, so this is + not the UBWC import issue. Reading an image mid-render (the dropped wait) or binning tiles is the + next suspicion. +- **SteamVR Home** needs `IVROverlayView_002` in Proton's `vrclient`, or an ARM64 build of Home. +- **The Lab and other large games** need memory headroom. Candidates: stop Steam's own web helpers + while a VR game runs, or lower SteamVR's footprint. +- **Wait drops are a heuristic.** A 3 ms grace keeps the common case in sync. A submit thread in the + layer, deferring submits until their waits are reachable, would be exact. +- **Layers:** only the first layer of each frame is presented. No reprojection. +- **Controllers** are a gaze pointer driven by the pad. Pad presses did not close the SteamVR menu in + testing. Untested: whether that came from gamescope focus being on Steam, or from the system button + mapping. diff --git a/docs/development/evidence/cardboard-steamvr-dashboard-2026-10-13.png b/docs/development/evidence/cardboard-steamvr-dashboard-2026-10-13.png new file mode 100644 index 00000000..b50ead73 Binary files /dev/null and b/docs/development/evidence/cardboard-steamvr-dashboard-2026-10-13.png differ diff --git a/docs/development/evidence/cardboard-thrill-of-the-fight-cardboard-2026-10-13.png b/docs/development/evidence/cardboard-thrill-of-the-fight-cardboard-2026-10-13.png new file mode 100644 index 00000000..8b04fdc9 Binary files /dev/null and b/docs/development/evidence/cardboard-thrill-of-the-fight-cardboard-2026-10-13.png differ diff --git a/docs/development/evidence/cardboard-thumper-vr-cardboard-2026-10-13.png b/docs/development/evidence/cardboard-thumper-vr-cardboard-2026-10-13.png new file mode 100644 index 00000000..ef463ba6 Binary files /dev/null and b/docs/development/evidence/cardboard-thumper-vr-cardboard-2026-10-13.png differ diff --git a/docs/development/evidence/cardboard-thumper-vr-gameplay-2026-10-13.png b/docs/development/evidence/cardboard-thumper-vr-gameplay-2026-10-13.png new file mode 100644 index 00000000..0f7678b6 Binary files /dev/null and b/docs/development/evidence/cardboard-thumper-vr-gameplay-2026-10-13.png differ diff --git a/tools/build_local.sh b/tools/build_local.sh index 224e9b83..0487104e 100755 --- a/tools/build_local.sh +++ b/tools/build_local.sh @@ -131,6 +131,9 @@ docker run --rm --platform linux/amd64 \ chown -R '"$(id -u):$(id -g)"' app/src/main/assets/linuxfs ' +# Cardboard mode's SteamVR driver and Vulkan layer (tools/vr, docs/cardboard-vr.md). +DROIDDECK_BUILD_IMAGE="${image_name}" "${repo_root}/tools/vr/build.sh" "${linuxfs_dir}/usr/local/lib/droiddeck-vr" + github_repo=${DROIDDECK_GITHUB_REPOSITORY:-} if [[ -z "${github_repo}" ]]; then origin_url=$(git -C "${repo_root}" remote get-url origin) diff --git a/tools/cardboard/test_cardboard_params.c b/tools/cardboard/test_cardboard_params.c new file mode 100644 index 00000000..360f0885 --- /dev/null +++ b/tools/cardboard/test_cardboard_params.c @@ -0,0 +1,51 @@ +/* Host test for the cardboard pass's pure helpers (waylandcomp/src/cardboard.c, built with + * CARDBOARD_HOST_TEST): clamping and the push-constant layout cardboard.frag reads. */ +#include "cardboard.h" +#include +#include + +static int failures; +#define CHECK(c) do { if (!(c)) { printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #c); failures++; } } while (0) +static int near(float a, float b) { return fabsf(a - b) < 1e-4f; } + +int main(void) { + struct cardboard_params p = {.enabled = 7, .source = 9, .ipd_m = 1.0f, .screen_dist_m = 0.0f, + .screen_width_m = 100.0f, .k1 = 5.0f, .k2 = -5.0f, .fov_deg = 500.0f, + .lens_inset = 1.0f, .chroma = -1.0f}; + cardboard_clamp(&p); + CHECK(p.enabled == 1); + CHECK(p.source == CARDBOARD_SOURCE_SCREEN); + CHECK(near(p.ipd_m, 0.1f)); + CHECK(near(p.screen_dist_m, 0.5f)); + CHECK(near(p.screen_width_m, 60.0f)); + CHECK(near(p.k1, 2.0f) && near(p.k2, -1.0f)); + CHECK(near(p.fov_deg, 140.0f)); + CHECK(near(p.lens_inset, 0.25f) && near(p.chroma, 0.0f)); + + struct cardboard_params q = {.enabled = 1, .source = CARDBOARD_SOURCE_SCREEN, .ipd_m = 0.064f, .screen_dist_m = 3.0f, + .screen_width_m = 4.0f, .k1 = 0.34f, .k2 = 0.55f, .fov_deg = 90.0f, + .lens_inset = 0.05f, .chroma = 0.006f}; + const float pose[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + float pc[24]; + cardboard_push_constants(&q, pose, 1920, 1080, 2400, 1080, pc); + /* rot rows with the scalars in w */ + CHECK(near(pc[0], 1) && near(pc[1], 2) && near(pc[2], 3) && near(pc[3], 0.032f)); + CHECK(near(pc[4], 4) && near(pc[5], 5) && near(pc[6], 6) && near(pc[7], 3.0f)); + CHECK(near(pc[8], 7) && near(pc[9], 8) && near(pc[10], 9) && near(pc[11], 2.0f)); + /* lens: k1, k2, tan(fov/2), chroma */ + CHECK(near(pc[12], 0.34f) && near(pc[13], 0.55f) && near(pc[14], 1.0f) && near(pc[15], 0.006f)); + /* view: inset, source aspect, mode, eye viewport aspect (half the panel) */ + CHECK(near(pc[16], 0.05f) && near(pc[17], 1920.0f / 1080.0f) && near(pc[18], 0.0f) && near(pc[19], 1200.0f / 1080.0f)); + + /* A side-by-side source: each eye's aspect is half the frame's. */ + q.source = CARDBOARD_SOURCE_SBS; + cardboard_push_constants(&q, pose, 3840, 1080, 2400, 1080, pc); + CHECK(near(pc[17], 1920.0f / 1080.0f) && near(pc[18], 1.0f)); + /* Unknown sizes fall back instead of dividing by zero. */ + cardboard_push_constants(&q, pose, 0, 0, 0, 0, pc); + CHECK(isfinite(pc[17]) && isfinite(pc[19])); + + if (failures) return 1; + printf("PASS: cardboard params\n"); + return 0; +} diff --git a/tools/linuxfs/overlay/usr/local/bin/droiddeck-agent b/tools/linuxfs/overlay/usr/local/bin/droiddeck-agent index b0e8efcf..c8ca1196 100755 --- a/tools/linuxfs/overlay/usr/local/bin/droiddeck-agent +++ b/tools/linuxfs/overlay/usr/local/bin/droiddeck-agent @@ -540,6 +540,38 @@ def quit_game(agent_dir, app_id, timeout): # --- Requests ------------------------------------------------------------------------------------ +VR_HELPER = "/usr/local/bin/droiddeck-steamvr" +VR_ACTIONS = ("status", "install", "start", "stop") + + +def steamvr(req): + """Cardboard mode's SteamVR: a fixed helper with four actions, unlike exec (no command line from + the request). install downloads 571 MB, so it runs detached and status reports it.""" + action = req.get("action", "status") + if action not in VR_ACTIONS: + raise AgentError("INVALID_REQUEST", "vr action must be one of " + ", ".join(VR_ACTIONS)) + helper = os.environ.get("DROIDDECK_VR_HELPER", VR_HELPER) + if not os.path.isfile(helper): + raise AgentError("UNAVAILABLE", "this runtime has no " + helper) + if action == "install": + state = Path(os.environ.get("XDG_RUNTIME_DIR", "/tmp")) / "droiddeck-steamvr" + state.mkdir(parents=True, exist_ok=True) + with open(state / "install.log", "ab") as log_file: + subprocess.Popen(["sh", helper, "install"], stdin=subprocess.DEVNULL, stdout=log_file, + stderr=subprocess.STDOUT, start_new_session=True) + return {"action": action, "started": True} + done = subprocess.run(["sh", helper, action], stdin=subprocess.DEVNULL, capture_output=True, + timeout=request_timeout(req, 30)) + out = done.stdout.decode("utf-8", "replace").strip() + result = {"action": action, "exitCode": done.returncode, "output": out[-2000:]} + if action == "status": + for part in out.split(): + key, _, value = part.partition("=") + if key in ("installed", "running", "installing", "driver"): + result[key] = value == "1" + return result + + def run_exec(req): argv = req.get("argv") if not isinstance(argv, list) or not argv or not all(isinstance(a, str) and "\0" not in a for a in argv): @@ -656,6 +688,8 @@ def handle(agent_dir, req): return steam_selection(agent_dir, request_timeout(req, 5)) if kind == "quit": return quit_game(agent_dir, req.get("appId"), request_timeout(req, 15)) + if kind == "vr": + return steamvr(req) raise AgentError("UNKNOWN_REQUEST", "Unknown agent request '%s'" % kind) diff --git a/tools/linuxfs/overlay/usr/local/bin/droiddeck-session b/tools/linuxfs/overlay/usr/local/bin/droiddeck-session index 5ffc2a38..ba25dcc2 100755 --- a/tools/linuxfs/overlay/usr/local/bin/droiddeck-session +++ b/tools/linuxfs/overlay/usr/local/bin/droiddeck-session @@ -165,6 +165,9 @@ export XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share} export XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config} export XDG_CACHE_HOME=${XDG_CACHE_HOME:-$HOME/.cache} mkdir -p "$XDG_DATA_HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" +# No SteamVR runs at session start. A marker left by a session that died with SteamVR up would put +# the Cardboard VR layer's display stand-in into this one (docs/cardboard-vr.md). +rm -f "${XDG_RUNTIME_DIR:-/run/droiddeck}/droiddeck-steamvr/active" if [ "$mode" = lxqt ] || [ "$mode" = desktop ]; then /usr/local/bin/droiddeck-desktop-bookmarks || true fi diff --git a/tools/linuxfs/overlay/usr/local/bin/droiddeck-steamvr b/tools/linuxfs/overlay/usr/local/bin/droiddeck-steamvr new file mode 100644 index 00000000..162e471d --- /dev/null +++ b/tools/linuxfs/overlay/usr/local/bin/droiddeck-steamvr @@ -0,0 +1,117 @@ +#!/bin/sh +# SteamVR for Cardboard mode (docs/cardboard-vr.md). Experimental. +# +# droiddeck-steamvr status one line: installed=0|1 running=0|1 installing=0|1 driver=0|1 +# droiddeck-steamvr install fetch Valve's ARM64 SteamVR (the Steam Frame's package) into /opt/steamvr +# droiddeck-steamvr start start SteamVR with the DroidDeck Cardboard driver +# droiddeck-steamvr stop stop it +# +# SteamVR renders through the driver (tools/vr/src/driver_droiddeck.cpp) into an overlay window that +# gamescope shows over everything; the app's Cardboard pass, with a side-by-side source, puts the lens +# on it. vrcompositor needs the Vulkan layer (tools/vr/src/vk_layer_droiddeck_vr.c) on Turnip/KGSL. +set -u + +steamvr=${STEAMVR:-/opt/steamvr} +vrdir=${DROIDDECK_VR_DIR:-/usr/local/lib/droiddeck-vr} +driver=$vrdir/droiddeck +layer=$vrdir/layer +logs=$HOME/.local/share/Steam/logs +state=${XDG_RUNTIME_DIR:-/tmp}/droiddeck-steamvr +mkdir -p "$state" + +# The Steam Frame's SteamVR, from Valve's package server; the same build the Frame's SteamOS ships. +PKG_URL=https://steamdeck-packages.steamos.cloud/archlinux-deckard-hotfixes/release/0.3.x/deckard-steamvr-rel-r25358740%2B28b72a4f-1-aarch64.pkg.tar.zst +PKG_SHA256=90a600775eb3e8856dc475f83166a93a290a0b1f3b4e82a69c8e2a8f06b9ddc9 + +running() { pgrep -x vrserver >/dev/null 2>&1; } +installed() { [ -x "$steamvr/bin/linuxarm64/vrstartup" ]; } + +stop() { + rm -f "$state/active" + for n in vrcompositor vrserver vrmonitor vrdashboard vrstartup vrwebhelper; do + for p in $(pgrep -x "$n"); do kill "$p" 2>/dev/null; done + done + # pgrep -x matches 15 characters of the name at most. + for p in $(pgrep -f "$steamvr/bin/linuxarm64/vrcompositor-launcher"); do kill "$p" 2>/dev/null; done + # A process that exited on its own meanwhile is not a failure: the app reads this exit code. + return 0 +} + +case "${1:-status}" in +status) + i=0; installed && i=1 + r=0; running && r=1 + # A session that died with SteamVR up leaves the marker behind; it would keep the layer active. + # Only an old one: vrserver takes a while to appear after start, and Steam's web helper needs the + # marker from the start. + if [ "$r" = 0 ] && [ -n "$(find "$state/active" -mmin +2 2>/dev/null)" ]; then rm -f "$state/active"; fi + n=0; [ -f "$state/installing" ] && n=1 + d=0; [ -f "$driver/bin/linuxarm64/driver_droiddeck.so" ] && [ -f "$layer/libVkLayer_droiddeck_vr.so" ] && d=1 + echo "installed=$i running=$r installing=$n driver=$d" + ;; +install) + installed && { echo "SteamVR is already in $steamvr"; exit 0; } + [ -f "$state/installing" ] && { echo "already installing"; exit 0; } + : > "$state/installing" + trap 'rm -f "$state/installing"' EXIT + cache=$HOME/.cache/droiddeck-vr + mkdir -p "$cache" + pkg=$cache/steamvr.pkg.tar.zst + echo "downloading SteamVR (571 MB) from Valve's package server" + curl -fsSL --retry 5 -C - -o "$pkg" "$PKG_URL" || { echo "download failed"; exit 1; } + echo "$PKG_SHA256 $pkg" | sha256sum -c - >/dev/null 2>&1 || { echo "checksum mismatch: removing the download"; rm -f "$pkg"; exit 1; } + tmp=$cache/extract + rm -rf "$tmp" && mkdir -p "$tmp" + zstd -dc "$pkg" | tar -x -C "$tmp" opt/steamvr || { echo "extract failed"; rm -rf "$tmp"; exit 1; } + mkdir -p "$(dirname "$steamvr")" + rm -rf "$steamvr" && mv "$tmp/opt/steamvr" "$steamvr" && rm -rf "$tmp" "$pkg" + echo "SteamVR installed in $steamvr" + ;; +stop) + stop + ;; +start) + installed || { echo "SteamVR is not installed (droiddeck-steamvr install)"; exit 1; } + [ -f "$driver/bin/linuxarm64/driver_droiddeck.so" ] || { echo "the Cardboard driver is missing from $driver"; exit 1; } + stop + sleep 1 + mkdir -p "$HOME/.config/openvr" "$HOME/.config/openxr/1" "$logs" "$HOME/.local/share/Steam/config" + cat > "$HOME/.config/openvr/openvrpaths.vrpath" </dev/null 2>&1; then + printf '#!/bin/sh\n# xset stand-in for vrwebhelper.sh: "q" only checks the display answers.\nexec xprop -root -display "${DISPLAY:-:0}" _NET_SUPPORTED >/dev/null 2>&1\n' > "$shims/xset" + chmod +x "$shims/xset" + export PATH="$shims:$PATH" + fi + + # While this file exists, the layer is active in every Vulkan instance of the session, not only + # SteamVR's: Steam's web helper shares its in-VR overlay with SteamVR and needs it too. + : > "$state/active" + cd "$steamvr/bin/linuxarm64" || exit 1 + setsid ./vrstartup > "$state/vrstartup.log" 2>&1 < /dev/null & + echo "SteamVR starting; logs in $logs and $state/vrstartup.log" + ;; +*) + echo "usage: droiddeck-steamvr status|install|start|stop" >&2 + exit 2 + ;; +esac diff --git a/tools/linuxfs/preload/sysv.c b/tools/linuxfs/preload/sysv.c index 36d7bdd7..5c1416b2 100644 --- a/tools/linuxfs/preload/sysv.c +++ b/tools/linuxfs/preload/sysv.c @@ -43,8 +43,14 @@ struct sem_header { uint32_t key; uint32_t mode; int32_t creator_pid; - int32_t pad[11]; + /* What IPC_STAT reports as sem_otime / sem_ctime (seconds). The usual race-free init has the + * creator semop the new set once, and every other process wait until sem_otime is non-zero; + * SteamVR's web helper spins on exactly that. */ + _Atomic uint32_t otime; + _Atomic uint32_t ctime; + int32_t pad[9]; }; +_Static_assert(sizeof(struct sem_header) == 64, "semaphore files written by older sessions keep their layout"); /* The counters follow the header, one 32-bit word each (a futex word). */ struct sem_map { int id; struct sem_header *hdr; size_t len; }; @@ -176,7 +182,8 @@ int semget(key_t key, int nsems, int semflg) { size_t len = sizeof(struct sem_header) + (size_t)nsems * sizeof(int32_t); if (ftruncate(fd, (off_t)len) != 0) { close(fd); return -1; } struct sem_header h = {.magic = SEM_MAGIC, .nsems = (uint32_t)nsems, .key = (uint32_t)key, - .mode = (uint32_t)(semflg & 0777), .creator_pid = (int32_t)getpid()}; + .mode = (uint32_t)(semflg & 0777), .creator_pid = (int32_t)getpid(), + .ctime = (uint32_t)time(NULL)}; if (pwrite(fd, &h, sizeof(h), 0) != (ssize_t)sizeof(h)) { close(fd); return -1; } } else { struct sem_header h; @@ -222,6 +229,7 @@ int semtimedop(int semid, struct sembuf *sops, size_t nsops, const struct timesp } if (sops[i].sem_op < 0) futex(w, FUTEX_WAKE, INT_MAX, NULL); /* zero-waiters see the change */ } + atomic_store(&h->otime, (uint32_t)time(NULL)); return 0; } @@ -256,6 +264,7 @@ int semctl(int semid, int semnum, int cmd, ...) { if (semnum < 0 || (uint32_t)semnum >= h->nsems) { errno = EINVAL; return -1; } atomic_store(&words[semnum], (int32_t)(intptr_t)arg.buf); futex(&words[semnum], FUTEX_WAKE, INT_MAX, NULL); + atomic_store(&h->ctime, (uint32_t)time(NULL)); return 0; case GETALL: for (uint32_t i = 0; i < h->nsems; i++) arg.array[i] = (unsigned short)atomic_load(&words[i]); @@ -265,6 +274,7 @@ int semctl(int semid, int semnum, int cmd, ...) { atomic_store(&words[i], arg.array[i]); futex(&words[i], FUTEX_WAKE, INT_MAX, NULL); } + atomic_store(&h->ctime, (uint32_t)time(NULL)); return 0; case GETPID: return h->creator_pid; case GETNCNT: case GETZCNT: return 0; @@ -275,8 +285,10 @@ int semctl(int semid, int semnum, int cmd, ...) { arg.buf->sem_perm.gid = arg.buf->sem_perm.cgid = getgid(); arg.buf->sem_perm.mode = (unsigned short)h->mode; arg.buf->sem_nsems = h->nsems; + arg.buf->sem_otime = (time_t)atomic_load(&h->otime); + arg.buf->sem_ctime = (time_t)atomic_load(&h->ctime); return 0; - case IPC_SET: return 0; + case IPC_SET: atomic_store(&h->ctime, (uint32_t)time(NULL)); return 0; default: errno = EINVAL; return -1; } } diff --git a/tools/linuxfs/preload/tracer.c b/tools/linuxfs/preload/tracer.c index afcd37ee..326a8f35 100644 --- a/tools/linuxfs/preload/tracer.c +++ b/tools/linuxfs/preload/tracer.c @@ -47,9 +47,11 @@ static int is_status_path(const char *path) { } /* Chromium stands its hang watchdogs down when it sees a tracer, and the client's web helper does - * not come up within their patience here: it is left reading the truth. */ + * not come up within their patience here: it is left reading the truth. SteamVR's web helper (the + * dashboard, Cardboard mode's SteamVR) is the same Chromium and stalls the same way. */ static int is_web_helper(void) { - return strcmp(program_invocation_short_name, "steamwebhelper") == 0; + return strcmp(program_invocation_short_name, "steamwebhelper") == 0 || + strcmp(program_invocation_short_name, "vrwebhelper") == 0; } static int write_all(int fd, const char *data, size_t len) { diff --git a/tools/release/runtime_inputs.py b/tools/release/runtime_inputs.py index eaccde51..c9716edf 100644 --- a/tools/release/runtime_inputs.py +++ b/tools/release/runtime_inputs.py @@ -17,7 +17,8 @@ SOURCE_PATHS = ['app/build.gradle', 'app/src/main/cpp/fakeinput_steam.cpp', 'app 'tools/gamescope/release.env', 'tools/droiddeck-esync/release.env', 'tools/mangoapp', 'tools/msitools', 'tools/comet', 'tools/build_local.sh', 'tools/release/runtime_inputs.py', 'app/src/main/assets/desktop-removal', - 'tools/quest', 'app/src/main/cpp/adrenotools/src/hook/kgsl.h', + 'tools/quest', 'tools/vr/src', 'tools/vr/driver_droiddeck', 'tools/vr/layer', 'tools/vr/build.sh', + 'app/src/main/cpp/adrenotools/src/hook/kgsl.h', 'app/src/main/assets/graphics_driver/adrenotools-turnip25.1.0.tzst'] @@ -67,6 +68,10 @@ def required(): paths += ['assets/linuxfs/i386/libfaultreport.so'] paths += ['assets/linuxfs/usr/local/lib/droiddeck-msitools/' + name for name in ['msiinfo', 'cabextract', '7z', '7z.so', 'libmsi-1.0.so.0', 'libgsf-1.so.114', 'libgcab-1.0.so.0']] + paths += ['assets/linuxfs/usr/local/lib/droiddeck-vr/' + name for name in + ['droiddeck/bin/linuxarm64/driver_droiddeck.so', 'droiddeck/driver.vrdrivermanifest', + 'droiddeck/resources/settings/default.vrsettings', 'layer/libVkLayer_droiddeck_vr.so', + 'layer/VK_LAYER_DROIDDECK_vr.json', 'layer/VK_LAYER_DROIDDECK_vr_implicit.json']] paths += ['assets/linuxfs/usr/local/lib/mangoapp/' + name for name in ['mangoapp', 'libfmt.so.10', 'libspdlog.so.1.13', 'libglfw.so.3', 'libtraceevent.so.1', 'libtracefs.so.1']] paths += ['assets/linuxfs/usr/local/lib/droiddeck-comet/' + name for name in diff --git a/tools/tests/sysv_otime_harness.c b/tools/tests/sysv_otime_harness.c new file mode 100644 index 00000000..2c362189 --- /dev/null +++ b/tools/tests/sysv_otime_harness.c @@ -0,0 +1,28 @@ +/* Host test for sysv.c: IPC_STAT's sem_otime turns non-zero with the first semop, which the usual + * race-free init (and SteamVR's web helper) waits for. Linked with sysv.c, whose semget/semop/semctl + * replace libc's. */ +#define _GNU_SOURCE +#include +#include +#include + +int main(void) { + int creator = semget(0x5eed0001, 1, IPC_CREAT | IPC_EXCL | 0600); + if (creator < 0) { perror("semget"); return 1; } + struct semid_ds ds; + if (semctl(creator, 0, IPC_STAT, &ds) != 0 || ds.sem_otime != 0 || ds.sem_ctime == 0) { + printf("FAIL: a new set has otime %ld ctime %ld\n", (long)ds.sem_otime, (long)ds.sem_ctime); + return 1; + } + struct sembuf up = {0, 1, 0}; + if (semop(creator, &up, 1) != 0) { perror("semop"); return 1; } + int other = semget(0x5eed0001, 1, 0600); + if (other < 0 || semctl(other, 0, IPC_STAT, &ds) != 0 || ds.sem_otime == 0) { + printf("FAIL: otime still 0 after semop\n"); + return 1; + } + if (semctl(other, 0, GETVAL) != 1) { printf("FAIL: value\n"); return 1; } + semctl(creator, 0, IPC_RMID); + printf("PASS: sysv otime\n"); + return 0; +} diff --git a/tools/tests/test_cardboard.py b/tools/tests/test_cardboard.py new file mode 100644 index 00000000..fa6f3d12 --- /dev/null +++ b/tools/tests/test_cardboard.py @@ -0,0 +1,60 @@ +from pathlib import Path +import re +import shutil +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] +SRC = ROOT / 'app' / 'src' / 'main' / 'cpp' / 'waylandcomp' / 'src' +TOOLS = ROOT / 'tools' / 'cardboard' + + +def spirv_words(header: str) -> list[str]: + return re.findall(r'0x[0-9a-fA-F]{8}', header) + + +@unittest.skipUnless(shutil.which('cc'), 'needs a C compiler') +class CardboardParamsTest(unittest.TestCase): + def test_clamp_and_push_constant_layout(self): + with tempfile.TemporaryDirectory() as folder: + binary = str(Path(folder) / 'test-cardboard') + subprocess.run(['cc', '-std=c11', '-Wall', '-Wextra', '-Werror', '-O2', '-DCARDBOARD_HOST_TEST', + '-D_DEFAULT_SOURCE', f'-I{SRC}', f'-I{Path(folder)}', '-o', binary, + str(TOOLS / 'test_cardboard_params.c'), str(SRC / 'cardboard.c'), '-lm'], + check=True, cwd=folder, env=self._env(folder)) + result = subprocess.run([binary], capture_output=True, text=True, check=True, timeout=10) + self.assertIn('PASS: cardboard params', result.stdout) + + @staticmethod + def _env(folder): + # cardboard.h includes ; the host test only needs the types, so a stub + # header stands in when the Vulkan SDK is not installed. + import os + stub = Path(folder) / 'vulkan' + stub.mkdir(exist_ok=True) + (stub / 'vulkan.h').write_text('typedef struct VkDevice_T *VkDevice; typedef struct VkImage_T *VkImage;\n' + 'typedef struct VkCommandBuffer_T *VkCommandBuffer;\n' + 'typedef struct { int unused; } VkPhysicalDeviceMemoryProperties;\n') + return dict(os.environ) + + +@unittest.skipUnless(shutil.which('glslangValidator'), 'needs glslangValidator') +class CardboardShaderTest(unittest.TestCase): + def test_committed_spirv_matches_the_glsl(self): + with tempfile.TemporaryDirectory() as folder: + out = Path(folder) / 'cardboard_frag.h' + subprocess.run(['glslangValidator', '-V', '--vn', 'cardboard_frag_code', '-o', str(out), + str(SRC / 'cardboard.frag')], check=True, capture_output=True) + self.assertEqual(spirv_words(out.read_text()), spirv_words((SRC / 'cardboard_frag.h').read_text()), + 'cardboard_frag.h is stale: regenerate it from cardboard.frag (see cardboard.c)') + + def test_push_constants_fit_the_guaranteed_range(self): + # 7 vec4 = 112 bytes in the shader; Vulkan guarantees 128. + glsl = (SRC / 'cardboard.frag').read_text() + block = glsl[glsl.index('uniform PC {'):glsl.index('} pc;')] + self.assertLessEqual(16 * len(re.findall(r'\bvec4\b', block)), 128) + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/tests/test_droiddeck_agent.py b/tools/tests/test_droiddeck_agent.py index 8cc103e3..3e3a1a26 100644 --- a/tools/tests/test_droiddeck_agent.py +++ b/tools/tests/test_droiddeck_agent.py @@ -91,6 +91,37 @@ class AgentTest(unittest.TestCase): self.assertFalse(path.exists(), "the request file is consumed") return json.loads((self.dir / "resp" / "req.json").read_text()) + def vr_helper(self, body): + helper = self.dir / "droiddeck-steamvr" + helper.write_text("#!/bin/sh\n" + body) + old = os.environ.get("DROIDDECK_VR_HELPER") + os.environ["DROIDDECK_VR_HELPER"] = str(helper) + self.addCleanup(lambda: os.environ.pop("DROIDDECK_VR_HELPER") if old is None + else os.environ.__setitem__("DROIDDECK_VR_HELPER", old)) + return helper + + def test_vr_status_is_parsed(self): + self.vr_helper('echo "installed=1 running=0 installing=0 driver=1"\n') + answer = self.ask({"kind": "vr", "action": "status"}) + self.assertTrue(answer["ok"]) + self.assertEqual((True, False, False, True), + (answer["installed"], answer["running"], answer["installing"], answer["driver"])) + + def test_vr_runs_only_its_own_actions(self): + log = self.dir / "args" + self.vr_helper('echo "$@" > %s\n' % log) + self.assertTrue(self.ask({"kind": "vr", "action": "start"})["ok"]) + self.assertEqual("start", log.read_text().strip()) + answer = self.ask({"kind": "vr", "action": "rm -rf /"}) + self.assertFalse(answer["ok"]) + self.assertEqual("INVALID_REQUEST", answer["error"]["code"]) + + def test_vr_without_a_helper_is_unavailable(self): + os.environ["DROIDDECK_VR_HELPER"] = str(self.dir / "missing") + self.addCleanup(os.environ.pop, "DROIDDECK_VR_HELPER") + answer = self.ask({"kind": "vr", "action": "status"}) + self.assertEqual("UNAVAILABLE", answer["error"]["code"]) + def test_exec_output_and_status(self): answer = self.ask({"kind": "exec", "argv": ["sh", "-c", "echo out; echo err >&2; exit 3"], "env": {"X": "1"}}) self.assertTrue(answer["ok"]) diff --git a/tools/tests/test_sysv_preload.py b/tools/tests/test_sysv_preload.py new file mode 100644 index 00000000..75828567 --- /dev/null +++ b/tools/tests/test_sysv_preload.py @@ -0,0 +1,25 @@ +from pathlib import Path +import os +import shutil +import subprocess +import sys +import tempfile +import unittest + +PRELOAD = Path(__file__).resolve().parents[1] / 'linuxfs' / 'preload' + + +@unittest.skipUnless(sys.platform == 'linux' and shutil.which('gcc'), 'needs Linux and gcc') +class SysvPreloadTest(unittest.TestCase): + def test_semop_sets_otime_for_waiters(self): + with tempfile.TemporaryDirectory() as folder: + binary = str(Path(folder) / 'test-sysv') + subprocess.run(['gcc', '-Wall', '-Wextra', '-O2', '-pthread', '-o', binary, + str(Path(__file__).resolve().parent / 'sysv_otime_harness.c'), str(PRELOAD / 'sysv.c')], check=True) + env = dict(os.environ, BL_SYSV_DIR=folder) + result = subprocess.run([binary], capture_output=True, text=True, timeout=10, env=env) + self.assertIn('PASS: sysv otime', result.stdout, result.stdout + result.stderr) + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/vr/build.sh b/tools/vr/build.sh new file mode 100755 index 00000000..f9e4fa49 --- /dev/null +++ b/tools/vr/build.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# Builds Cardboard mode's SteamVR pieces for the guest (glibc aarch64): +# /droiddeck the SteamVR driver: driver_droiddeck.so, its manifest and default settings +# /layer VK_LAYER_DROIDDECK_vr, the Vulkan layer SteamVR's compositor needs on Turnip/KGSL +# +# tools/vr/build.sh [OUT] in the local cross image (default OUT: tools/vr/out) +# tools/vr/build.sh --native OUT with aarch64-linux-gnu-gcc/g++ on PATH (CI, or inside the image) +# +# Vulkan headers: VK_INCLUDE (a directory holding vulkan/), else the NDK's (ANDROID_NDK_HOME, the +# runner's latest NDK, or the newest under ANDROID_HOME). Only vulkan/ is copied out of it: the rest of +# the NDK sysroot is Android's libc, not the guest's. +set -euo pipefail +here=$(cd "$(dirname "$0")" && pwd) +repo=$(cd "$here/../.." && pwd) + +native=0 +if [ "${1:-}" = --native ]; then native=1; shift; fi +out=${1:-$here/out} +case "$out" in /*) ;; *) out="$PWD/$out" ;; esac + +vkinc=${VK_INCLUDE:-} +if [ -z "$vkinc" ]; then + for ndk in "${ANDROID_NDK_HOME:-}" "${ANDROID_NDK_LATEST_HOME:-}" \ + $(ls -d "${ANDROID_HOME:-$HOME/Library/Android/sdk}"/ndk/* 2>/dev/null | sort -r); do + [ -n "$ndk" ] || continue + cand=$(ls -d "$ndk"/toolchains/llvm/prebuilt/*/sysroot/usr/include 2>/dev/null | head -1) + if [ -n "$cand" ] && [ -f "$cand/vulkan/vk_layer.h" ]; then vkinc=$cand; break; fi + done +fi +[ -n "$vkinc" ] && [ -f "$vkinc/vulkan/vulkan_core.h" ] || { echo "tools/vr/build.sh: no Vulkan headers (set VK_INCLUDE)" >&2; exit 1; } + +# Everything the compilers read sits under the repo, so the cross image (which mounts only the repo) +# sees it too. +work="$repo/build/vr" +rm -rf "$work" && mkdir -p "$work/include" +cp -R "$vkinc/vulkan" "$work/include/" +if [ -d "$vkinc/vk_video" ]; then cp -R "$vkinc/vk_video" "$work/include/"; fi +mkdir -p "$work/out/droiddeck/bin/linuxarm64" "$work/out/layer" + +compile() { + local cmd=(sh -c ' + set -e + # libwayland-client: linked against a stub with its soname (the guest has the real one). + mkdir -p build/vr/stub + aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wl,-soname,libwayland-client.so.0 -Itools/vr/third_party/wayland \ + -o build/vr/stub/libwayland-client.so tools/vr/src/wayland_client_stub.c + aarch64-linux-gnu-gcc -c -fPIC -O2 -Itools/vr/third_party/wayland -o build/vr/xdg-shell-protocol.o \ + app/src/main/cpp/waylandcomp/generated/xdg-shell-protocol.c + aarch64-linux-gnu-g++ -std=c++17 -shared -fPIC -O2 -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers \ + -fvisibility=hidden -static-libstdc++ -static-libgcc -pthread -Wl,-z,defs -Ibuild/vr/include \ + -Itools/vr/third_party/wayland -Iapp/src/main/cpp/waylandcomp/generated \ + -o build/vr/out/droiddeck/bin/linuxarm64/driver_droiddeck.so tools/vr/src/driver_droiddeck.cpp tools/vr/src/presenter.cpp \ + build/vr/xdg-shell-protocol.o -Lbuild/vr/stub -lwayland-client -ldl + aarch64-linux-gnu-gcc -std=c11 -shared -fPIC -O2 -Wall -Wextra -Wno-unused-parameter -fvisibility=hidden -pthread \ + -Wl,-z,defs -Ibuild/vr/include -o build/vr/out/layer/libVkLayer_droiddeck_vr.so tools/vr/src/vk_layer_droiddeck_vr.c -ldl + aarch64-linux-gnu-strip --strip-unneeded build/vr/out/droiddeck/bin/linuxarm64/driver_droiddeck.so \ + build/vr/out/layer/libVkLayer_droiddeck_vr.so') + if [ "$native" = 1 ]; then + (cd "$repo" && "${cmd[@]}") + else + docker run --rm --platform linux/amd64 --user "$(id -u):$(id -g)" -v "$repo:/src" -w /src \ + "${DROIDDECK_BUILD_IMAGE:-droiddeck-local-cross:24.04-v3}" "${cmd[@]}" + fi +} +compile + +cp -R "$here/driver_droiddeck/." "$work/out/droiddeck/" +cp "$here/layer/VK_LAYER_DROIDDECK_vr.json" "$here/layer/VK_LAYER_DROIDDECK_vr_implicit.json" "$work/out/layer/" +rm -rf "$out/droiddeck" "$out/layer" +mkdir -p "$out" +cp -R "$work/out/droiddeck" "$work/out/layer" "$out/" +echo "built $out/droiddeck and $out/layer" diff --git a/tools/vr/driver_droiddeck/driver.vrdrivermanifest b/tools/vr/driver_droiddeck/driver.vrdrivermanifest new file mode 100644 index 00000000..762ada4c --- /dev/null +++ b/tools/vr/driver_droiddeck/driver.vrdrivermanifest @@ -0,0 +1,7 @@ +{ + "alwaysActivate": true, + "name": "droiddeck", + "directory": "", + "resourceOnly": false, + "hmd_presence": ["*.*"] +} diff --git a/tools/vr/driver_droiddeck/resources/settings/default.vrsettings b/tools/vr/driver_droiddeck/resources/settings/default.vrsettings new file mode 100644 index 00000000..fac91585 --- /dev/null +++ b/tools/vr/driver_droiddeck/resources/settings/default.vrsettings @@ -0,0 +1,11 @@ +{ + "driver_droiddeck": { + "enable": true, + "windowWidth": 1920, + "windowHeight": 1080, + "displayFrequency": 60.0, + "virtualDisplay": false, + "directMode": true, + "gamepadControllers": true + } +} diff --git a/tools/vr/layer/VK_LAYER_DROIDDECK_vr.json b/tools/vr/layer/VK_LAYER_DROIDDECK_vr.json new file mode 100644 index 00000000..3e2fe8fb --- /dev/null +++ b/tools/vr/layer/VK_LAYER_DROIDDECK_vr.json @@ -0,0 +1,14 @@ +{ + "file_format_version": "1.2.0", + "layer": { + "name": "VK_LAYER_DROIDDECK_vr", + "type": "GLOBAL", + "library_path": "./libVkLayer_droiddeck_vr.so", + "api_version": "1.3.0", + "implementation_version": "1", + "description": "Lets SteamVR's compositor start on Turnip/KGSL: stands in for the display extensions", + "functions": { + "vkNegotiateLoaderLayerInterfaceVersion": "DroidDeckVr_NegotiateLoaderLayerInterfaceVersion" + } + } +} diff --git a/tools/vr/layer/VK_LAYER_DROIDDECK_vr_implicit.json b/tools/vr/layer/VK_LAYER_DROIDDECK_vr_implicit.json new file mode 100644 index 00000000..2fd3d680 --- /dev/null +++ b/tools/vr/layer/VK_LAYER_DROIDDECK_vr_implicit.json @@ -0,0 +1,17 @@ +{ + "file_format_version": "1.2.0", + "layer": { + "name": "VK_LAYER_DROIDDECK_vr", + "type": "GLOBAL", + "library_path": "/usr/local/lib/droiddeck-vr/layer/libVkLayer_droiddeck_vr.so", + "api_version": "1.3.0", + "implementation_version": "1", + "description": "Cardboard SteamVR: shared-texture semaphores and a stand-in display on Turnip/KGSL; passes through unless SteamVR runs", + "functions": { + "vkNegotiateLoaderLayerInterfaceVersion": "DroidDeckVr_NegotiateLoaderLayerInterfaceVersion" + }, + "disable_environment": { + "DROIDDECK_VR_LAYER_DISABLE": "1" + } + } +} diff --git a/tools/vr/src/OPENVR_LICENSE b/tools/vr/src/OPENVR_LICENSE new file mode 100644 index 00000000..ee83337d --- /dev/null +++ b/tools/vr/src/OPENVR_LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2015, Valve Corporation +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/tools/vr/src/driver_droiddeck.cpp b/tools/vr/src/driver_droiddeck.cpp new file mode 100644 index 00000000..eaded9eb --- /dev/null +++ b/tools/vr/src/driver_droiddeck.cpp @@ -0,0 +1,666 @@ +// SteamVR driver for DroidDeck's Cardboard mode: one 3DoF HMD whose pose is the phone's rotation +// sensor, read from the head pose file the app shares with the guest +// (app/src/main/cpp/waylandcomp/src/headpose_shm.h, /run/droiddeck/droiddeck-headpose). +// +// The HMD renders an undistorted side-by-side pair: the app's Cardboard pass, in its side-by-side +// mode, does the lens distortion for the viewer the user picked. So the projection here is exactly +// the frustum that pass samples (tan_half_fov, eye_aspect from the pose file), and the distortion is +// the identity. +// +// How the frames reach the screen is the open question this driver is for: see docs/cardboard-vr.md. +// It exposes IVRDisplayComponent with the display "on the desktop" (extended mode) at the session's +// size, and logs what vrserver / vrcompositor ask of it. +#include "openvr_driver.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../../app/src/main/cpp/waylandcomp/src/headpose_shm.h" +#include "presenter.h" + +#define DD_EXPORT extern "C" __attribute__((visibility("default"))) + +using namespace vr; + +namespace { + +const char *const kSettingsSection = "driver_droiddeck"; +const char *const kPosePath = "/run/droiddeck/" DROIDDECK_HEADPOSE_FILE; + +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 (VRDriverLog()) VRDriverLog()->Log(buf); + fprintf(stderr, "[droiddeck] %s\n", buf); +} + +// The shared pose file, mapped read-only; reads follow the seqlock in headpose_shm.h. +class PoseFile { +public: + bool Read(droiddeck_headpose *out) { + if (!map_ && !Open()) return false; + for (int tries = 0; tries < 8; tries++) { + uint32_t a = __atomic_load_n(&map_->seq, __ATOMIC_ACQUIRE); + if (a & 1u) continue; + memcpy(out, (const void *)map_, sizeof(*out)); + __atomic_thread_fence(__ATOMIC_ACQUIRE); + if (__atomic_load_n(&map_->seq, __ATOMIC_RELAXED) == a) + return out->magic == DROIDDECK_HEADPOSE_MAGIC && out->version == DROIDDECK_HEADPOSE_VERSION; + } + return false; + } + ~PoseFile() { if (map_) munmap((void *)map_, sizeof(*map_)); } + +private: + bool Open() { + int fd = open(kPosePath, O_RDONLY | O_CLOEXEC); + if (fd < 0) return false; + void *p = mmap(nullptr, sizeof(droiddeck_headpose), PROT_READ, MAP_SHARED, fd, 0); + close(fd); + if (p == MAP_FAILED) return false; + map_ = (volatile droiddeck_headpose *)p; + Log("head pose file mapped: %s", kPosePath); + return true; + } + volatile droiddeck_headpose *map_ = nullptr; +}; + +HmdQuaternion_t QuatFromMatrix(const float m[9]) { + // Row-major rotation -> quaternion (Shepperd's method). + HmdQuaternion_t q; + float tr = m[0] + m[4] + m[8]; + if (tr > 0.0f) { + float s = sqrtf(tr + 1.0f) * 2.0f; + q.w = 0.25f * s; q.x = (m[7] - m[5]) / s; q.y = (m[2] - m[6]) / s; q.z = (m[3] - m[1]) / s; + } else if (m[0] > m[4] && m[0] > m[8]) { + float s = sqrtf(1.0f + m[0] - m[4] - m[8]) * 2.0f; + q.w = (m[7] - m[5]) / s; q.x = 0.25f * s; q.y = (m[1] + m[3]) / s; q.z = (m[2] + m[6]) / s; + } else if (m[4] > m[8]) { + float s = sqrtf(1.0f + m[4] - m[0] - m[8]) * 2.0f; + q.w = (m[2] - m[6]) / s; q.x = (m[1] + m[3]) / s; q.y = 0.25f * s; q.z = (m[5] + m[7]) / s; + } else { + float s = sqrtf(1.0f + m[8] - m[0] - m[4]) * 2.0f; + q.w = (m[3] - m[1]) / s; q.x = (m[2] + m[6]) / s; q.y = (m[5] + m[7]) / s; q.z = 0.25f * s; + } + return q; +} + +// IVRVirtualDisplay: the compositor renders the final frame into its own backbuffer and hands it +// here instead of scanning it out. What the handle is on Linux is what this logs (experiment). +class VirtualDisplay : public IVRVirtualDisplay { +public: + void Present(const PresentInfo_t *info, uint32_t size) override { + frames_++; + if (frames_ <= 5 || frames_ % 600 == 0) + Log("virtual display Present #%llu: handle 0x%llx vsync %d frame %llu (info %u bytes)", + (unsigned long long)frames_, (unsigned long long)info->backbufferTextureHandle, (int)info->vsync, + (unsigned long long)info->nFrameId, size); + last_ = std::chrono::steady_clock::now(); + } + void WaitForPresent() override {} + bool GetTimeSinceLastVsync(float *seconds, uint64_t *counter) override { + // A free-running 60 Hz vsync. + using namespace std::chrono; + double t = duration(steady_clock::now().time_since_epoch()).count(); + double period = 1.0 / 60.0; + *seconds = (float)fmod(t, period); + *counter = (uint64_t)(t / period); + return true; + } + +private: + uint64_t frames_ = 0; + std::chrono::steady_clock::time_point last_; +}; + +// The vsync grid SteamVR paces against in driver-direct mode (Prop_DriverDirectModeSendsVsyncEvents): +// a free-running 60 Hz tick, declared a little ahead of time (ALVR's announcer, alvr/server_openvr/src/vsync.rs). +class Vsync { +public: + static constexpr std::chrono::nanoseconds kInterval{16666667}; + static constexpr std::chrono::microseconds kLead{2000}; + + static void Start() { + if (running_.exchange(true)) return; + thread_ = std::thread([] { + auto next = std::chrono::steady_clock::now() + kInterval; + while (running_) { + auto announce = next - kLead; + std::this_thread::sleep_until(announce); + auto now = std::chrono::steady_clock::now(); + double offset = std::chrono::duration(announce - now).count(); + VRServerDriverHost()->VsyncEvent(offset); + { + std::lock_guard lock(mutex_); + last_ = next; + } + next += kInterval; + now = std::chrono::steady_clock::now(); + while (next <= now) next += kInterval; + } + }); + } + static void Stop() { + running_ = false; + if (thread_.joinable()) thread_.join(); + } + static std::chrono::steady_clock::time_point LastTick() { + std::lock_guard lock(mutex_); + return last_; + } + +private: + static inline std::atomic running_{false}; + static inline std::thread thread_; + static inline std::mutex mutex_; + static inline std::chrono::steady_clock::time_point last_{}; +}; + +// IVRDriverDirectModeComponent: SteamVR renders each eye into textures this driver allocates +// (shared Vulkan images from SteamVR's IPC resource manager, dmabufs on Linux) and hands them +// over per frame. This is how ALVR gets frames on Linux; it avoids the compositor's own direct mode, +// which needs VK_KHR_display - something Turnip on KGSL refuses outright. +class DirectMode : public IVRDriverDirectModeComponent { +public: + struct Set { + SharedTextureHandle_t handles[3] = {}; + int fds[3] = {-1, -1, -1}; + uint32_t pid = 0, index = 0; + SwapTextureSetDesc_t desc = {}; + }; + + void CreateSwapTextureSet(uint32_t pid, const SwapTextureSetDesc_t *d, SwapTextureSet_t *out) override { + Log("CreateSwapTextureSet pid %u: format %u, %ux%u, %u samples", pid, d->nFormat, d->nWidth, d->nHeight, d->nSampleCount); + Set set; + set.pid = pid; + set.desc = *d; + const uint32_t usage = 0x1 /* TRANSFER_SRC */ | 0x4 /* SAMPLED */ | 0x80 /* INPUT_ATTACHMENT */; + const uint32_t create = 0x8 /* MUTABLE_FORMAT */; + for (int i = 0; i < 3; i++) { + SharedTextureHandle_t h = 0; + if (!VRIPCResourceManager()->NewSharedVulkanImage(d->nFormat, d->nWidth, d->nHeight, true, false, true, 1, 1, + create, usage, &h)) { + Log("NewSharedVulkanImage failed"); + return; + } + uint64_t ipc = 0; + VRIPCResourceManager()->RefResource(h, &ipc); + int fd = -1; + if (!VRIPCResourceManager()->ReceiveSharedFd(ipc, &fd)) Log("ReceiveSharedFd failed"); + set.handles[i] = h; + set.fds[i] = fd; + out->rSharedTextureHandles[i] = h; + } + out->unTextureFlags = 0; + Log(" -> handles 0x%llx 0x%llx 0x%llx, fds %d %d %d", (unsigned long long)set.handles[0], + (unsigned long long)set.handles[1], (unsigned long long)set.handles[2], set.fds[0], set.fds[1], set.fds[2]); + droiddeck::Presenter::AddTextures(set.handles, set.fds, d->nFormat, d->nWidth, d->nHeight); + std::lock_guard lock(mutex_); + sets_.push_back(set); + } + + void DestroySwapTextureSet(SharedTextureHandle_t h) override { + std::lock_guard lock(mutex_); + for (auto it = sets_.begin(); it != sets_.end(); ++it) { + if (it->handles[0] == h || it->handles[1] == h || it->handles[2] == h) { + Release(*it); + sets_.erase(it); + return; + } + } + } + + void DestroyAllSwapTextureSets(uint32_t pid) override { + std::lock_guard lock(mutex_); + for (auto it = sets_.begin(); it != sets_.end();) { + if (it->pid == pid) { Release(*it); it = sets_.erase(it); } else ++it; + } + } + + void GetNextSwapTextureSetIndex(SharedTextureHandle_t handles[2], uint32_t (*indices)[2]) override { + std::lock_guard lock(mutex_); + for (int e = 0; e < 2; e++) { + Set *s = Find(handles[e]); + if (!s) { (*indices)[e] = 0; continue; } + s->index = (s->index + 1) % 3; + (*indices)[e] = s->index; + } + } + + void SubmitLayer(const SubmitLayerPerEye_t (&eyes)[2]) override { + layers_++; + // The first layer of a frame is the compositor's own picture; overlays after it are not drawn yet. + if (layerInFrame_++ == 0) { + for (int e = 0; e < 2; e++) { + frame_.texture[e] = eyes[e].hTexture; + frame_.bounds[e][0] = eyes[e].bounds.uMin; frame_.bounds[e][1] = eyes[e].bounds.vMin; + frame_.bounds[e][2] = eyes[e].bounds.uMax; frame_.bounds[e][3] = eyes[e].bounds.vMax; + } + } + if (frames_ < 3) + Log("SubmitLayer: textures 0x%llx / 0x%llx, bounds %.2f,%.2f-%.2f,%.2f", + (unsigned long long)eyes[0].hTexture, (unsigned long long)eyes[1].hTexture, + eyes[0].bounds.uMin, eyes[0].bounds.vMin, eyes[0].bounds.uMax, eyes[0].bounds.vMax); + } + + void Present(SharedTextureHandle_t sync) override { + frames_++; + if (layerInFrame_) droiddeck::Presenter::Submit(frame_); + if (layerInFrame_ != lastLayerCount_) { + static int said; + if (said++ < 20) Log("frame %llu: %d layer(s) (was %d)", (unsigned long long)frames_, layerInFrame_, lastLayerCount_); + lastLayerCount_ = layerInFrame_; + } + layerInFrame_ = 0; + if (frames_ <= 3 || frames_ % 600 == 0) + Log("Present #%llu (%llu layers so far), sync 0x%llx", (unsigned long long)frames_, + (unsigned long long)layers_, (unsigned long long)sync); + } + + void PostPresent(const Throttling_t *t) override { + // Hold until the tick after the last one declared (ALVR's pacing): a slow frame costs one frame. + auto last = Vsync::LastTick(); + auto until = last + Vsync::kInterval * (1 + (t ? t->nFramesToThrottle : 0)); + if (until > std::chrono::steady_clock::now()) std::this_thread::sleep_until(until); + } + + void GetFrameTiming(DriverDirectMode_FrameTiming *t) override { t->m_nReprojectionFlags = 0; } + +private: + Set *Find(SharedTextureHandle_t h) { + for (auto &s : sets_) + for (int i = 0; i < 3; i++) + if (s.handles[i] == h) return &s; + return nullptr; + } + static void Release(Set &s) { + for (int i = 0; i < 3; i++) { + if (s.handles[i]) droiddeck::Presenter::RemoveTexture(s.handles[i]); + if (s.fds[i] >= 0) close(s.fds[i]); + if (s.handles[i]) VRIPCResourceManager()->UnrefResource(s.handles[i]); + } + } + + std::mutex mutex_; + std::vector sets_; + uint64_t frames_ = 0, layers_ = 0; + int layerInFrame_ = 0, lastLayerCount_ = -1; + droiddeck::EyeFrame frame_{}; +}; + +// The session's game pad, as the app publishes it for the guest: the absolute-state snapshot at the +// head of fake input ring 0 (app/src/main/cpp/fakeinput_steam.cpp, FakeInputRingHeader), under a +// seqlock. FAKE_EVDEV_MEMFD_PATHS names the rings in every session process's environment. +struct PadState { + uint32_t buttons = 0; // bit order of FakeInputWriter.BUTTON_MAP; bit 11 = the QAM button + int16_t axes[8] = {}; // X, Y, RX, RY (+-32767), GAS = right trigger, BRAKE = left (0..255), HAT0X, HAT0Y +}; +enum : uint32_t { + PAD_A = 1u << 0, PAD_B = 1u << 1, PAD_X = 1u << 2, PAD_Y = 1u << 3, PAD_LB = 1u << 4, PAD_RB = 1u << 5, + PAD_SELECT = 1u << 6, PAD_START = 1u << 7, PAD_L3 = 1u << 8, PAD_R3 = 1u << 9, PAD_GUIDE = 1u << 10, +}; + +class PadRing { +public: + bool Read(PadState *out) { + if (!map_ && !Open()) return false; + for (int tries = 0; tries < 8; tries++) { + uint64_t a = __atomic_load_n((const uint64_t *)(map_ + 32), __ATOMIC_ACQUIRE); + if (a & 1u) continue; + memcpy(&out->buttons, (const void *)(map_ + 40), 4); + memcpy(out->axes, (const void *)(map_ + 44), 16); + __atomic_thread_fence(__ATOMIC_ACQUIRE); + if (__atomic_load_n((const uint64_t *)(map_ + 32), __ATOMIC_RELAXED) == a) return true; + } + return false; + } + ~PadRing() { if (map_) munmap((void *)map_, 64); } + +private: + bool Open() { + auto now = std::chrono::steady_clock::now(); + if (now < retry_) return false; + retry_ = now + std::chrono::seconds(2); + std::string path; + if (const char *env = getenv("DROIDDECK_VR_PAD_RING")) path = env; + if (path.empty()) { + const char *paths = getenv("FAKE_EVDEV_MEMFD_PATHS"); // "0=/path;1=/path;..." + if (paths && !strncmp(paths, "0=", 2)) path.assign(paths + 2, strcspn(paths + 2, ";")); + } + if (path.empty()) { + if (const char *dir = getenv("BL_LAUNCH_DIR")) path = std::string(dir) + "/dev/fakeinput-rings/ring0"; + } + if (path.empty()) return false; + int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC); + if (fd < 0) return false; + void *p = mmap(nullptr, 64, PROT_READ, MAP_SHARED, fd, 0); + close(fd); + if (p == MAP_FAILED) return false; + uint32_t magic; + memcpy(&magic, p, 4); + if (magic != 0x46494252u) { munmap(p, 64); return false; } + map_ = (const volatile uint8_t *)p; + Log("game pad ring mapped: %s", path.c_str()); + return true; + } + const volatile uint8_t *map_ = nullptr; + std::chrono::steady_clock::time_point retry_{}; +}; + +// One hand: a Vive-profile controller driven by the game pad. 3DoF, so it is a gaze pointer: it sits +// low in front of its side of the body and points where the head looks, and SteamVR's laser follows the +// view. Right hand: right stick = trackpad, A = trackpad click, RT = trigger, RB = grip, Start = menu, +// the Steam/guide button = system. Left: left stick, X, LT, LB, Select. +class CardboardController : public ITrackedDeviceServerDriver { +public: + explicit CardboardController(bool right) : right_(right) {} + + EVRInitError Activate(uint32_t id) override { + id_ = id; + PropertyContainerHandle_t c = VRProperties()->TrackedDeviceToPropertyContainer(id_); + VRProperties()->SetStringProperty(c, Prop_ModelNumber_String, "DroidDeck Pad Hand"); + VRProperties()->SetStringProperty(c, Prop_ManufacturerName_String, "DroidDeck"); + VRProperties()->SetStringProperty(c, Prop_TrackingSystemName_String, "droiddeck"); + VRProperties()->SetStringProperty(c, Prop_RenderModelName_String, "generic_controller"); + VRProperties()->SetStringProperty(c, Prop_ControllerType_String, "vive_controller"); + VRProperties()->SetStringProperty(c, Prop_InputProfilePath_String, "{htc}/input/vive_controller_profile.json"); + VRProperties()->SetInt32Property(c, Prop_ControllerRoleHint_Int32, + right_ ? TrackedControllerRole_RightHand : TrackedControllerRole_LeftHand); + VRProperties()->SetInt32Property(c, Prop_DeviceClass_Int32, TrackedDeviceClass_Controller); + auto in = VRDriverInput(); + in->CreateBooleanComponent(c, "/input/system/click", &system_); + in->CreateBooleanComponent(c, "/input/application_menu/click", &menu_); + in->CreateBooleanComponent(c, "/input/grip/click", &grip_); + in->CreateBooleanComponent(c, "/input/trigger/click", &triggerClick_); + in->CreateScalarComponent(c, "/input/trigger/value", &trigger_, VRScalarType_Absolute, VRScalarUnits_NormalizedOneSided); + in->CreateScalarComponent(c, "/input/trackpad/x", &padX_, VRScalarType_Absolute, VRScalarUnits_NormalizedTwoSided); + in->CreateScalarComponent(c, "/input/trackpad/y", &padY_, VRScalarType_Absolute, VRScalarUnits_NormalizedTwoSided); + in->CreateBooleanComponent(c, "/input/trackpad/click", &padClick_); + in->CreateBooleanComponent(c, "/input/trackpad/touch", &padTouch_); + in->CreateHapticComponent(c, "/output/haptic", &haptic_); + Log("%s hand activated (game pad)", right_ ? "right" : "left"); + return VRInitError_None; + } + void Deactivate() override { id_ = k_unTrackedDeviceIndexInvalid; } + void EnterStandby() override {} + void *GetComponent(const char *) override { return nullptr; } + void DebugRequest(const char *, char *resp, uint32_t size) override { if (size) resp[0] = 0; } + + DriverPose_t GetPose() override { return pose_; } + + // From the driver's pose thread: the head's orientation and the pad. + void Update(const HmdQuaternion_t &head, const PadState *pad) { + if (id_ == k_unTrackedDeviceIndexInvalid) return; + DriverPose_t p = {}; + p.qWorldFromDriverRotation.w = 1; + p.qDriverFromHeadRotation.w = 1; + p.qRotation = head; + // Low and in front of its side, in head space, then into the room with the head's rotation. + const double off[3] = {right_ ? 0.18 : -0.18, -0.35, -0.30}; + double m[3][3]; + const double w = head.w, x = head.x, y = head.y, z = head.z; + m[0][0] = 1 - 2 * (y * y + z * z); m[0][1] = 2 * (x * y - z * w); m[0][2] = 2 * (x * z + y * w); + m[1][0] = 2 * (x * y + z * w); m[1][1] = 1 - 2 * (x * x + z * z); m[1][2] = 2 * (y * z - x * w); + m[2][0] = 2 * (x * z - y * w); m[2][1] = 2 * (y * z + x * w); m[2][2] = 1 - 2 * (x * x + y * y); + for (int i = 0; i < 3; i++) p.vecPosition[i] = (i == 1 ? 1.6 : 0.0) + m[i][0] * off[0] + m[i][1] * off[1] + m[i][2] * off[2]; + p.poseIsValid = true; + p.result = TrackingResult_Running_OK; + p.deviceIsConnected = true; + pose_ = p; + VRServerDriverHost()->TrackedDevicePoseUpdated(id_, p, sizeof(p)); + if (!pad) return; + auto in = VRDriverInput(); + const uint32_t b = pad->buttons; + const float stickX = pad->axes[right_ ? 2 : 0] / 32767.0f, stickY = -pad->axes[right_ ? 3 : 1] / 32767.0f; + const float trig = pad->axes[right_ ? 4 : 5] / 255.0f; + const bool touch = stickX * stickX + stickY * stickY > 0.01f; + in->UpdateBooleanComponent(system_, right_ && (b & PAD_GUIDE), 0); + in->UpdateBooleanComponent(menu_, (b & (right_ ? PAD_START : PAD_SELECT)) != 0, 0); + in->UpdateBooleanComponent(grip_, (b & (right_ ? PAD_RB : PAD_LB)) != 0, 0); + in->UpdateScalarComponent(trigger_, trig, 0); + in->UpdateBooleanComponent(triggerClick_, trig > 0.6f, 0); + in->UpdateScalarComponent(padX_, touch ? stickX : 0.0f, 0); + in->UpdateScalarComponent(padY_, touch ? stickY : 0.0f, 0); + in->UpdateBooleanComponent(padTouch_, touch || (b & (right_ ? PAD_A : PAD_X)), 0); + in->UpdateBooleanComponent(padClick_, (b & (right_ ? (PAD_A | PAD_R3) : (PAD_X | PAD_L3))) != 0, 0); + } + +private: + bool right_; + uint32_t id_ = k_unTrackedDeviceIndexInvalid; + DriverPose_t pose_ = {}; + VRInputComponentHandle_t system_ = 0, menu_ = 0, grip_ = 0, triggerClick_ = 0, trigger_ = 0, padX_ = 0, padY_ = 0, + padClick_ = 0, padTouch_ = 0, haptic_ = 0; +}; + +class CardboardHmd : public ITrackedDeviceServerDriver, public IVRDisplayComponent { +public: + EVRInitError Activate(uint32_t unObjectId) override { + id_ = unObjectId; + PropertyContainerHandle_t c = VRProperties()->TrackedDeviceToPropertyContainer(id_); + width_ = VRSettings()->GetInt32(kSettingsSection, "windowWidth"); + height_ = VRSettings()->GetInt32(kSettingsSection, "windowHeight"); + if (width_ <= 0) width_ = 1920; + if (height_ <= 0) height_ = 1080; + float hz = VRSettings()->GetFloat(kSettingsSection, "displayFrequency"); + if (hz <= 0.0f) hz = 60.0f; + // Until the app has drawn an eye: the Cardboard pass's defaults for a 1920x1080 panel. + tanHalf_ = 1.0f; + eyeAspect_ = (width_ * 0.5f) / height_; + droiddeck_headpose hp; + if (pose_.Read(&hp) && hp.tan_half_fov > 0.0f) { + tanHalf_ = hp.tan_half_fov; + if (hp.eye_aspect > 0.0f) eyeAspect_ = hp.eye_aspect; + } + VRProperties()->SetStringProperty(c, Prop_ModelNumber_String, "DroidDeck Cardboard"); + VRProperties()->SetStringProperty(c, Prop_ManufacturerName_String, "DroidDeck"); + VRProperties()->SetStringProperty(c, Prop_TrackingSystemName_String, "droiddeck"); + VRProperties()->SetFloatProperty(c, Prop_UserIpdMeters_Float, 0.064f); + VRProperties()->SetFloatProperty(c, Prop_UserHeadToEyeDepthMeters_Float, 0.0f); + VRProperties()->SetFloatProperty(c, Prop_DisplayFrequency_Float, hz); + VRProperties()->SetFloatProperty(c, Prop_SecondsFromVsyncToPhotons_Float, 0.011f); + VRProperties()->SetUint64Property(c, Prop_CurrentUniverseId_Uint64, 2); + // Off the desktop: with a direct mode component that sends SteamVR's compositor down the + // driver-direct path; on it, the compositor opens its own direct mode window (VK_KHR_display). + VRProperties()->SetBoolProperty(c, Prop_IsOnDesktop_Bool, false); + VRProperties()->SetBoolProperty(c, Prop_DisplayDebugMode_Bool, false); + directMode_ = VRSettings()->GetBool(kSettingsSection, "directMode"); + VRProperties()->SetBoolProperty(c, Prop_HasDriverDirectModeComponent_Bool, directMode_); + if (directMode_) { + VRProperties()->SetBoolProperty(c, Prop_DriverDirectModeSendsVsyncEvents_Bool, true); + VRProperties()->SetBoolProperty(c, Prop_Hmd_AllowsClientToControlTextureIndex, true); + } + VRProperties()->SetBoolProperty(c, Prop_HasDisplayComponent_Bool, true); + virtualDisplay_ = VRSettings()->GetBool(kSettingsSection, "virtualDisplay"); + VRProperties()->SetBoolProperty(c, Prop_HasVirtualDisplayComponent_Bool, virtualDisplay_); + // A proximity sensor that reads "worn" while Cardboard mode is on: otherwise SteamVR sees a headset + // lying still (a phone on a table between sessions) and puts the display in standby, after which + // the compositor submits no layers at all. + VRProperties()->SetBoolProperty(c, Prop_ContainsProximitySensor_Bool, true); + VRDriverInput()->CreateBooleanComponent(c, "/proximity", &proximity_); + VRDriverInput()->UpdateBooleanComponent(proximity_, true, 0); + Log("HMD activated: %dx%d at %.0f Hz, eye tan(half fov) %.3f, eye aspect %.3f", width_, height_, hz, tanHalf_, eyeAspect_); + + running_ = true; + thread_ = std::thread([this] { PoseLoop(); }); + if (directMode_) Vsync::Start(); + return VRInitError_None; + } + + void Deactivate() override { + Vsync::Stop(); + droiddeck::Presenter::Shutdown(); + running_ = false; + if (thread_.joinable()) thread_.join(); + id_ = k_unTrackedDeviceIndexInvalid; + } + + void EnterStandby() override {} + + void *GetComponent(const char *name) override { + Log("component asked for: %s", name); + if (!strncmp(name, "IVRDisplayComponent_", 20)) return static_cast(this); + if (virtualDisplay_ && !strcmp(name, IVRVirtualDisplay_Version)) return static_cast(&vd_); + if (directMode_ && !strncmp(name, "IVRDriverDirectModeComponent_", 29)) return static_cast(&dm_); + return nullptr; + } + + void DebugRequest(const char *, char *resp, uint32_t size) override { if (size) resp[0] = 0; } + + DriverPose_t GetPose() override { + DriverPose_t p = {}; + p.qWorldFromDriverRotation.w = 1; + p.qDriverFromHeadRotation.w = 1; + p.qRotation.w = 1; + p.vecPosition[1] = 1.6; // standing eye height: 3DoF, the head never moves + droiddeck_headpose hp; + bool ok = pose_.Read(&hp); + if (ok) { + p.qRotation = QuatFromMatrix(hp.rot); + p.poseTimeOffset = 0; + if (hp.tan_half_fov > 0.0f) tanHalf_ = hp.tan_half_fov; + if (hp.eye_aspect > 0.0f) eyeAspect_ = hp.eye_aspect; + } + p.poseIsValid = true; + p.result = TrackingResult_Running_OK; + p.deviceIsConnected = true; + return p; + } + + // ---- IVRDisplayComponent + void GetWindowBounds(int32_t *x, int32_t *y, uint32_t *w, uint32_t *h) override { + *x = 0; *y = 0; *w = (uint32_t)width_; *h = (uint32_t)height_; + } + bool IsDisplayOnDesktop() override { return !directMode_; } + bool IsDisplayRealDisplay() override { return false; } + void GetRecommendedRenderTargetSize(uint32_t *w, uint32_t *h) override { + // Rendered a little larger than the panel: the lens pass magnifies the middle. + *w = (uint32_t)(width_ / 2 * 1.4f); + *h = (uint32_t)(height_ * 1.4f); + } + void GetEyeOutputViewport(EVREye eye, uint32_t *x, uint32_t *y, uint32_t *w, uint32_t *h) override { + *y = 0; *w = (uint32_t)width_ / 2; *h = (uint32_t)height_; + *x = eye == Eye_Left ? 0 : (uint32_t)width_ / 2; + } + void GetProjectionRaw(EVREye, float *left, float *right, float *top, float *bottom) override { + // Exactly what the Cardboard pass samples from each half of a side-by-side frame. + *left = -tanHalf_ * eyeAspect_; *right = tanHalf_ * eyeAspect_; + *top = -tanHalf_; *bottom = tanHalf_; + } + DistortionCoordinates_t ComputeDistortion(EVREye, float u, float v) override { + DistortionCoordinates_t d; + d.rfRed[0] = d.rfGreen[0] = d.rfBlue[0] = u; + d.rfRed[1] = d.rfGreen[1] = d.rfBlue[1] = v; + return d; + } + bool ComputeInverseDistortion(HmdVector2_t *result, EVREye, uint32_t, float u, float v) override { + result->v[0] = u; result->v[1] = v; + return true; + } + +private: + void PoseLoop() { + uint32_t lastSeq = 0; + auto lastSent = std::chrono::steady_clock::now(); + while (running_) { + droiddeck_headpose hp; + bool fresh = pose_.Read(&hp) && hp.seq != lastSeq; + auto now = std::chrono::steady_clock::now(); + // A new sample, or a heartbeat so SteamVR never thinks the HMD went away. + PadState pad; + const bool havePad = pad_.Read(&pad); + const bool padChanged = havePad && (pad.buttons != lastPad_.buttons || memcmp(pad.axes, lastPad_.axes, sizeof(pad.axes))); + if ((fresh || padChanged || now - lastSent > std::chrono::milliseconds(100)) && id_ != k_unTrackedDeviceIndexInvalid) { + if (fresh) { + lastSeq = hp.seq; + const bool worn = (hp.flags & DROIDDECK_HEADPOSE_ENABLED) != 0; + if (worn != worn_) { + worn_ = worn; + VRDriverInput()->UpdateBooleanComponent(proximity_, worn, 0); + Log("headset %s (Cardboard mode %s)", worn ? "worn" : "taken off", worn ? "on" : "off"); + } + } + DriverPose_t head = GetPose(); + VRServerDriverHost()->TrackedDevicePoseUpdated(id_, head, sizeof(DriverPose_t)); + for (CardboardController *hand : hands_) if (hand) hand->Update(head.qRotation, havePad ? &pad : nullptr); + if (havePad) lastPad_ = pad; + lastSent = now; + } + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + } + } + + uint32_t id_ = k_unTrackedDeviceIndexInvalid; + int32_t width_ = 1920, height_ = 1080; + float tanHalf_ = 1.0f, eyeAspect_ = 0.889f; +public: + void SetHands(CardboardController *left, CardboardController *right) { hands_[0] = left; hands_[1] = right; } + +private: + PoseFile pose_; + PadRing pad_; + PadState lastPad_; + VRInputComponentHandle_t proximity_ = 0; + bool worn_ = true; + CardboardController *hands_[2] = {nullptr, nullptr}; + bool virtualDisplay_ = false, directMode_ = false; + VirtualDisplay vd_; + DirectMode dm_; + std::atomic running_{false}; + std::thread thread_; +}; + +class Provider : public IServerTrackedDeviceProvider { +public: + EVRInitError Init(IVRDriverContext *ctx) override { + VR_INIT_SERVER_DRIVER_CONTEXT(ctx); + Log("DroidDeck Cardboard driver loaded"); + if (!VRServerDriverHost()->TrackedDeviceAdded("droiddeck-cardboard-hmd", TrackedDeviceClass_HMD, &hmd_)) + Log("TrackedDeviceAdded failed"); + if (VRSettings()->GetBool(kSettingsSection, "gamepadControllers")) { + VRServerDriverHost()->TrackedDeviceAdded("droiddeck-pad-left", TrackedDeviceClass_Controller, &left_); + VRServerDriverHost()->TrackedDeviceAdded("droiddeck-pad-right", TrackedDeviceClass_Controller, &right_); + hmd_.SetHands(&left_, &right_); + } + return VRInitError_None; + } + void Cleanup() override { VR_CLEANUP_SERVER_DRIVER_CONTEXT(); } + const char *const *GetInterfaceVersions() override { return k_InterfaceVersions; } + void RunFrame() override { + VREvent_t e; + while (VRServerDriverHost()->PollNextEvent(&e, sizeof(e))) {} + } + bool ShouldBlockStandbyMode() override { return false; } + void EnterStandby() override {} + void LeaveStandby() override {} + +private: + CardboardHmd hmd_; + CardboardController left_{false}, right_{true}; +}; + +Provider g_provider; + +} // namespace + +DD_EXPORT void *HmdDriverFactory(const char *interfaceName, int *returnCode) { + if (!strcmp(interfaceName, IServerTrackedDeviceProvider_Version)) return &g_provider; + if (returnCode) *returnCode = VRInitError_Init_InterfaceNotFound; + return nullptr; +} diff --git a/tools/vr/src/openvr_driver.h b/tools/vr/src/openvr_driver.h new file mode 100644 index 00000000..fcd67558 --- /dev/null +++ b/tools/vr/src/openvr_driver.h @@ -0,0 +1,4703 @@ +#pragma once + +// openvr_driver.h +//========= Copyright Valve Corporation ============// +// Dynamically generated file. Do not modify this file directly. + +#ifndef _OPENVR_DRIVER_API +#define _OPENVR_DRIVER_API + +#include + + + +// version.h + +namespace vr +{ + static const uint32_t k_nSteamVRVersionMajor = 2; + static const uint32_t k_nSteamVRVersionMinor = 15; + static const uint32_t k_nSteamVRVersionBuild = 6; +} // namespace vr + +// public_vrtypes.h + +#ifndef _INCLUDE_CORE_VRTYPES_PUBLIC_H +#define _INCLUDE_CORE_VRTYPES_PUBLIC_H + +namespace vr +{ +#pragma pack( push, 8 ) + +typedef uint32_t PropertyTypeTag_t; + +// right-handed system +// +y is up +// +x is to the right +// -z is forward +// Distance unit is meters +struct HmdMatrix34_t +{ + float m[3][4]; +}; + +struct HmdMatrix33_t +{ + float m[3][3]; +}; + +struct HmdMatrix44_t +{ + float m[4][4]; +}; + +struct HmdVector3_t +{ + float v[3]; +}; + +struct HmdVector4_t +{ + float v[4]; +}; + +struct HmdVector3d_t +{ + double v[3]; +}; + +struct HmdVector2_t +{ + float v[2]; +}; + +struct HmdQuaternion_t +{ + double w, x, y, z; +}; + +struct HmdQuaternionf_t +{ + float w, x, y, z; +}; + +struct HmdColor_t +{ + float r, g, b, a; +}; + +struct HmdQuad_t +{ + HmdVector3_t vCorners[ 4 ]; +}; + +struct HmdRect2_t +{ + HmdVector2_t vTopLeft; + HmdVector2_t vBottomRight; +}; + +/** Holds the transform for a single bone */ +struct VRBoneTransform_t +{ + HmdVector4_t position; + HmdQuaternionf_t orientation; +}; + +struct VREyeTrackingData_t +{ + bool bActive; + bool bValid; + bool bTracked; + + vr::HmdVector3_t vGazeOrigin; // Ray origin + vr::HmdVector3_t vGazeTarget; // Gaze target (fixation point) +}; + +/** Used to return the post-distortion UVs for each color channel. +* UVs range from 0 to 1 with 0,0 in the upper left corner of the +* source render target. The 0,0 to 1,1 range covers a single eye. */ +struct DistortionCoordinates_t +{ + float rfRed[2]; + float rfGreen[2]; + float rfBlue[2]; +}; + +enum EVREye +{ + Eye_Left = 0, + Eye_Right = 1 +}; + +enum ETextureType +{ + TextureType_Invalid = -1, // Handle has been invalidated + TextureType_DirectX = 0, // Handle is an ID3D11Texture + TextureType_OpenGL = 1, // Handle is an OpenGL texture name or an OpenGL render buffer name, depending on submit flags + TextureType_Vulkan = 2, // Handle is a pointer to a VRVulkanTextureData_t structure + TextureType_IOSurface = 3, // Handle is a macOS cross-process-sharable IOSurfaceRef, deprecated in favor of TextureType_Metal on supported platforms + TextureType_DirectX12 = 4, // Handle is a pointer to a D3D12TextureData_t structure + TextureType_DXGISharedHandle = 5, // Handle is a HANDLE DXGI share handle, only supported for Overlay render targets. + // this texture is used directly by our renderer, so only perform atomic (copyresource or resolve) on it + TextureType_Metal = 6, // Handle is a MTLTexture conforming to the MTLSharedTexture protocol. Textures submitted to IVRCompositor::Submit which + // are of type MTLTextureType2DArray assume layer 0 is the left eye texture (vr::EVREye::Eye_left), layer 1 is the right + // eye texture (vr::EVREye::Eye_Right) + + TextureType_Reserved = 7, + TextureType_SharedTextureHandle = 8, // A pointer to a vr::SharedTextureHandle_t that was imported via, eg. ImportDmabuf. +}; + +enum EColorSpace +{ + ColorSpace_Auto = 0, // Assumes 'gamma' for 8-bit per component formats, otherwise 'linear'. This mirrors the DXGI formats which have _SRGB variants. + ColorSpace_Gamma = 1, // Texture data can be displayed directly on the display without any conversion (a.k.a. display native format). + ColorSpace_Linear = 2, // Same as gamma but has been converted to a linear representation using DXGI's sRGB conversion algorithm. +}; + +struct Texture_t +{ + void* handle; // See ETextureType definition above + ETextureType eType; + EColorSpace eColorSpace; +}; + +/** Allows the application to control what part of the provided texture will be used in the +* frame buffer. */ +struct VRTextureBounds_t +{ + float uMin, vMin; + float uMax, vMax; +}; + +/** Allows specifying pose used to render provided scene texture (if different from value returned by WaitGetPoses). */ +struct VRTextureWithPose_t : public Texture_t +{ + HmdMatrix34_t mDeviceToAbsoluteTracking; // Actual pose used to render scene textures. +}; + +struct VRTextureDepthInfo_t +{ + void* handle; // See ETextureType definition above + HmdMatrix44_t mProjection; + HmdVector2_t vRange; // 0..1 +}; + +struct VRTextureWithDepth_t : public Texture_t +{ + VRTextureDepthInfo_t depth; +}; + +struct VRTextureWithPoseAndDepth_t : public VRTextureWithPose_t +{ + VRTextureDepthInfo_t depth; +}; + +struct VRTextureMotionInfo_t +{ + void *handle; // See ETextureType definition above + HmdMatrix44_t mDeltaPose; // Incremental application-applied transform, if any, since the previous frame that affects the view. +}; + +struct VRTextureWithMotion_t : VRTextureWithPoseAndDepth_t +{ + VRTextureMotionInfo_t motion; +}; + +// 64-bit types that are part of public structures +// that are replicated in shared memory. +#if defined(__linux__) || defined(__APPLE__) +typedef uint64_t vrshared_uint64_t __attribute__ ((aligned(8))); +typedef double vrshared_double __attribute__ ((aligned(8))); +#else +typedef uint64_t vrshared_uint64_t; +typedef double vrshared_double; +#endif + +static const uint32_t MaxDmabufPlaneCount = 4; + +struct DmabufPlane_t +{ + uint32_t unOffset; + uint32_t unStride; + int32_t nFd; // This is not consumed, it is dup'ed. +}; + +struct DmabufAttributes_t +{ + void *pNext; // MUST be NULL. Unused right now, but could be used to extend this structure in the future. + + uint32_t unWidth; + uint32_t unHeight; + uint32_t unDepth; + uint32_t unMipLevels; + uint32_t unArrayLayers; + uint32_t unSampleCount; + uint32_t unFormat; // DRM_FORMAT_ + uint64_t ulModifier; // DRM_FORMAT_MOD_ + + uint32_t unPlaneCount; + DmabufPlane_t plane[MaxDmabufPlaneCount]; +}; + +#pragma pack( pop ) + +} // namespace vr + +#endif + +// vrtypes.h + +#ifndef _INCLUDE_VRTYPES_H +#define _INCLUDE_VRTYPES_H + +// Forward declarations to avoid requiring vulkan.h +struct VkDevice_T; +struct VkPhysicalDevice_T; +struct VkInstance_T; +struct VkQueue_T; + +// Forward declarations to avoid requiring d3d12.h +struct ID3D12Resource; +struct ID3D12CommandQueue; + +namespace vr +{ +#pragma pack( push, 8 ) + +/** A handle for a spatial anchor. This handle is only valid during the session it was created in. +* Anchors that live beyond one session should be saved by their string descriptors. */ +typedef uint32_t SpatialAnchorHandle_t; + +typedef void* glSharedTextureHandle_t; +typedef int32_t glInt_t; +typedef uint32_t glUInt_t; + + +// Handle to a shared texture (HANDLE on Windows obtained using OpenSharedResource). +typedef uint64_t SharedTextureHandle_t; +#define INVALID_SHARED_TEXTURE_HANDLE ((vr::SharedTextureHandle_t)0) + +enum ETrackingResult +{ + TrackingResult_Uninitialized = 1, + + TrackingResult_Calibrating_InProgress = 100, + TrackingResult_Calibrating_OutOfRange = 101, + + TrackingResult_Running_OK = 200, + TrackingResult_Running_OutOfRange = 201, + + TrackingResult_Fallback_RotationOnly = 300, +}; + +typedef uint32_t DriverId_t; +static const uint32_t k_nDriverNone = 0xFFFFFFFF; + +static const uint32_t k_unMaxDriverDebugResponseSize = 32768; + +/** Used to pass device IDs to API calls */ +typedef uint32_t TrackedDeviceIndex_t; +static const uint32_t k_unTrackedDeviceIndex_Hmd = 0; +static const uint32_t k_unMaxTrackedDeviceCount = 64; +static const uint32_t k_unTrackedDeviceIndexOther = 0xFFFFFFFE; +static const uint32_t k_unTrackedDeviceIndexInvalid = 0xFFFFFFFF; + +/** Describes what kind of object is being tracked at a given ID */ +enum ETrackedDeviceClass +{ + TrackedDeviceClass_Invalid = 0, // the ID was not valid. + TrackedDeviceClass_HMD = 1, // Head-Mounted Displays + TrackedDeviceClass_Controller = 2, // Tracked controllers + TrackedDeviceClass_GenericTracker = 3, // Generic trackers, similar to controllers + TrackedDeviceClass_TrackingReference = 4, // Camera and base stations that serve as tracking reference points + TrackedDeviceClass_DisplayRedirect = 5, // Accessories that aren't necessarily tracked themselves, but may redirect video output from other tracked devices + + TrackedDeviceClass_Max +}; + + +/** Describes what specific role associated with a tracked device */ +enum ETrackedControllerRole +{ + TrackedControllerRole_Invalid = 0, // Invalid value for controller type + TrackedControllerRole_LeftHand = 1, // Tracked device associated with the left hand + TrackedControllerRole_RightHand = 2, // Tracked device associated with the right hand + TrackedControllerRole_OptOut = 3, // Tracked device is opting out of left/right hand selection + TrackedControllerRole_Treadmill = 4, // Tracked device is a treadmill or other locomotion device + TrackedControllerRole_Stylus = 5, // Tracked device is a stylus + TrackedControllerRole_Max = 5 +}; + + +/** Returns true if the tracked controller role is allowed to be a hand */ +inline bool IsRoleAllowedAsHand( ETrackedControllerRole eRole ) +{ + switch ( eRole ) + { + case TrackedControllerRole_Invalid: + case TrackedControllerRole_LeftHand: + case TrackedControllerRole_RightHand: + return true; + default: + return false; + } +} + + +/** describes a single pose for a tracked object */ +struct TrackedDevicePose_t +{ + HmdMatrix34_t mDeviceToAbsoluteTracking; + HmdVector3_t vVelocity; // velocity in tracker space in m/s + HmdVector3_t vAngularVelocity; // angular velocity in radians/s (?) + ETrackingResult eTrackingResult; + bool bPoseIsValid; + + // This indicates that there is a device connected for this spot in the pose array. + // It could go from true to false if the user unplugs the device. + bool bDeviceIsConnected; +}; + +/** Identifies which style of tracking origin the application wants to use +* for the poses it is requesting */ +enum ETrackingUniverseOrigin +{ + TrackingUniverseSeated = 0, // Poses are provided relative to the seated zero pose + TrackingUniverseStanding = 1, // Poses are provided relative to the safe bounds configured by the user + TrackingUniverseRawAndUncalibrated = 2, // Poses are provided in the coordinate system defined by the driver. It has Y up and is unified for devices of the same driver. You usually don't want this one. +}; + +enum EAdditionalRadioFeatures +{ + AdditionalRadioFeatures_None = 0x00000000, + AdditionalRadioFeatures_HTCLinkBox = 0x00000001, + AdditionalRadioFeatures_InternalDongle = 0x00000002, + AdditionalRadioFeatures_ExternalDongle = 0x00000004, +}; + +typedef uint64_t WebConsoleHandle_t; +#define INVALID_WEB_CONSOLE_HANDLE ((vr::WebConsoleHandle_t)0) + +// Refers to a single container of properties +typedef uint64_t PropertyContainerHandle_t; +typedef uint32_t PropertyTypeTag_t; + +static const PropertyContainerHandle_t k_ulInvalidPropertyContainer = 0; +static const PropertyTypeTag_t k_unInvalidPropertyTag = 0; + +typedef PropertyContainerHandle_t DriverHandle_t; +static const PropertyContainerHandle_t k_ulInvalidDriverHandle = 0; + +// Use these tags to set/get common types as struct properties +static const PropertyTypeTag_t k_unFloatPropertyTag = 1; +static const PropertyTypeTag_t k_unInt32PropertyTag = 2; +static const PropertyTypeTag_t k_unUint64PropertyTag = 3; +static const PropertyTypeTag_t k_unBoolPropertyTag = 4; +static const PropertyTypeTag_t k_unStringPropertyTag = 5; +static const PropertyTypeTag_t k_unErrorPropertyTag = 6; +static const PropertyTypeTag_t k_unDoublePropertyTag = 7; + +static const PropertyTypeTag_t k_unHmdMatrix34PropertyTag = 20; +static const PropertyTypeTag_t k_unHmdMatrix44PropertyTag = 21; +static const PropertyTypeTag_t k_unHmdVector3PropertyTag = 22; +static const PropertyTypeTag_t k_unHmdVector4PropertyTag = 23; +static const PropertyTypeTag_t k_unHmdVector2PropertyTag = 24; +static const PropertyTypeTag_t k_unHmdQuadPropertyTag = 25; + +static const PropertyTypeTag_t k_unHiddenAreaPropertyTag = 30; +static const PropertyTypeTag_t k_unPathHandleInfoTag = 31; +static const PropertyTypeTag_t k_unActionPropertyTag = 32; +static const PropertyTypeTag_t k_unInputValuePropertyTag = 33; +static const PropertyTypeTag_t k_unWildcardPropertyTag = 34; +static const PropertyTypeTag_t k_unHapticVibrationPropertyTag = 35; +static const PropertyTypeTag_t k_unSkeletonPropertyTag = 36; + +static const PropertyTypeTag_t k_unSpatialAnchorPosePropertyTag = 40; +static const PropertyTypeTag_t k_unJsonPropertyTag = 41; +static const PropertyTypeTag_t k_unActiveActionSetPropertyTag = 42; + +static const PropertyTypeTag_t k_unOpenVRInternalReserved_Start = 1000; +static const PropertyTypeTag_t k_unOpenVRInternalReserved_End = 10000; + + +/** Each entry in this enum represents a property that can be retrieved about a +* tracked device. Many fields are only valid for one ETrackedDeviceClass. */ +enum ETrackedDeviceProperty +{ + Prop_Invalid = 0, + + // general properties that apply to all device classes + Prop_TrackingSystemName_String = 1000, + Prop_ModelNumber_String = 1001, + Prop_SerialNumber_String = 1002, + Prop_RenderModelName_String = 1003, + Prop_WillDriftInYaw_Bool = 1004, + Prop_ManufacturerName_String = 1005, + Prop_TrackingFirmwareVersion_String = 1006, + Prop_HardwareRevision_String = 1007, + Prop_AllWirelessDongleDescriptions_String = 1008, + Prop_ConnectedWirelessDongle_String = 1009, + Prop_DeviceIsWireless_Bool = 1010, + Prop_DeviceIsCharging_Bool = 1011, + Prop_DeviceBatteryPercentage_Float = 1012, // 0 is empty, 1 is full + Prop_StatusDisplayTransform_Matrix34 = 1013, + Prop_Firmware_UpdateAvailable_Bool = 1014, + Prop_Firmware_ManualUpdate_Bool = 1015, + Prop_Firmware_ManualUpdateURL_String = 1016, + Prop_HardwareRevision_Uint64 = 1017, + Prop_FirmwareVersion_Uint64 = 1018, + Prop_FPGAVersion_Uint64 = 1019, + Prop_VRCVersion_Uint64 = 1020, + Prop_RadioVersion_Uint64 = 1021, + Prop_DongleVersion_Uint64 = 1022, + Prop_BlockServerShutdown_Bool = 1023, + Prop_CanUnifyCoordinateSystemWithHmd_Bool = 1024, + Prop_ContainsProximitySensor_Bool = 1025, + Prop_DeviceProvidesBatteryStatus_Bool = 1026, + Prop_DeviceCanPowerOff_Bool = 1027, + Prop_Firmware_ProgrammingTarget_String = 1028, + Prop_DeviceClass_Int32 = 1029, + Prop_HasCamera_Bool = 1030, + Prop_DriverVersion_String = 1031, + Prop_Firmware_ForceUpdateRequired_Bool = 1032, + Prop_ViveSystemButtonFixRequired_Bool = 1033, + Prop_ParentDriver_Uint64 = 1034, + Prop_ResourceRoot_String = 1035, + Prop_RegisteredDeviceType_String = 1036, + Prop_InputProfilePath_String = 1037, // input profile to use for this device in the input system. Will default to tracking system name if this isn't provided + Prop_NeverTracked_Bool = 1038, // Used for devices that will never have a valid pose by design + Prop_NumCameras_Int32 = 1039, + Prop_CameraFrameLayout_Int32 = 1040, // EVRTrackedCameraFrameLayout value + Prop_CameraStreamFormat_Int32 = 1041, // ECameraVideoStreamFormat value + Prop_AdditionalDeviceSettingsPath_String = 1042, // driver-relative path to additional device and global configuration settings + Prop_Identifiable_Bool = 1043, // Whether device supports being identified from vrmonitor (e.g. blink LED, vibrate haptics, etc) + Prop_BootloaderVersion_Uint64 = 1044, + Prop_AdditionalSystemReportData_String = 1045, // additional string to include in system reports about a tracked device + Prop_CompositeFirmwareVersion_String = 1046, // additional FW components from a device that gets propagated into reports + Prop_Firmware_RemindUpdate_Bool = 1047, + Prop_PeripheralApplicationVersion_Uint64 = 1048, + Prop_ManufacturerSerialNumber_String = 1049, + Prop_ComputedSerialNumber_String = 1050, + Prop_EstimatedDeviceFirstUseTime_Int32 = 1051, + Prop_DevicePowerUsage_Float = 1052, + Prop_IgnoreMotionForStandby_Bool = 1053, + Prop_ActualTrackingSystemName_String = 1054, // the literal local driver name in case someone is playing games with prop 1000 + Prop_AllowCameraToggle_Bool = 1055, // Shows the Enable/Disable camera option. Hide this for certain headsets if they have the camera tracking (since it's always on) + Prop_AllowLightSourceFrequency_Bool = 1056, // Shows the Anti-Flicker option in camera settings. + Prop_SteamRemoteClientID_Uint64 = 1057, // For vrlink + Prop_Reserved_1058 = 1058, + Prop_Reserved_1059 = 1059, + Prop_Reserved_1060 = 1060, + + // Properties that are unique to TrackedDeviceClass_HMD + Prop_ReportsTimeSinceVSync_Bool = 2000, + Prop_SecondsFromVsyncToPhotons_Float = 2001, + Prop_DisplayFrequency_Float = 2002, + Prop_UserIpdMeters_Float = 2003, + Prop_CurrentUniverseId_Uint64 = 2004, + Prop_PreviousUniverseId_Uint64_deprecated = Prop_Invalid, + Prop_DisplayFirmwareVersion_Uint64 = 2006, + Prop_IsOnDesktop_Bool = 2007, + Prop_DisplayMCType_Int32 = 2008, + Prop_DisplayMCOffset_Float = 2009, + Prop_DisplayMCScale_Float = 2010, + Prop_EdidVendorID_Int32 = 2011, + Prop_DisplayMCImageLeft_String = 2012, + Prop_DisplayMCImageRight_String = 2013, + Prop_DisplayGCBlackClamp_Float = 2014, + Prop_EdidProductID_Int32 = 2015, + Prop_CameraToHeadTransform_Matrix34 = 2016, + Prop_DisplayGCType_Int32 = 2017, + Prop_DisplayGCOffset_Float = 2018, + Prop_DisplayGCScale_Float = 2019, + Prop_DisplayGCPrescale_Float = 2020, + Prop_DisplayGCImage_String = 2021, + Prop_LensCenterLeftU_Float = 2022, + Prop_LensCenterLeftV_Float = 2023, + Prop_LensCenterRightU_Float = 2024, + Prop_LensCenterRightV_Float = 2025, + Prop_UserHeadToEyeDepthMeters_Float = 2026, + Prop_CameraFirmwareVersion_Uint64 = 2027, + Prop_CameraFirmwareDescription_String = 2028, + Prop_DisplayFPGAVersion_Uint64 = 2029, + Prop_DisplayBootloaderVersion_Uint64 = 2030, + Prop_DisplayHardwareVersion_Uint64 = 2031, + Prop_AudioFirmwareVersion_Uint64 = 2032, + Prop_CameraCompatibilityMode_Int32 = 2033, + Prop_ScreenshotHorizontalFieldOfViewDegrees_Float = 2034, + Prop_ScreenshotVerticalFieldOfViewDegrees_Float = 2035, + Prop_DisplaySuppressed_Bool = 2036, + Prop_DisplayAllowNightMode_Bool = 2037, + Prop_DisplayMCImageWidth_Int32 = 2038, + Prop_DisplayMCImageHeight_Int32 = 2039, + Prop_DisplayMCImageNumChannels_Int32 = 2040, + Prop_DisplayMCImageData_Binary = 2041, + Prop_SecondsFromPhotonsToVblank_Float = 2042, + Prop_DriverDirectModeSendsVsyncEvents_Bool = 2043, + Prop_DisplayDebugMode_Bool = 2044, + Prop_GraphicsAdapterLuid_Uint64 = 2045, + Prop_DriverProvidedChaperonePath_String = 2048, + Prop_ExpectedTrackingReferenceCount_Int32 = 2049, // expected number of sensors or basestations to reserve UI space for + Prop_ExpectedControllerCount_Int32 = 2050, // expected number of tracked controllers to reserve UI space for + Prop_NamedIconPathControllerLeftDeviceOff_String = 2051, // placeholder icon for "left" controller if not yet detected/loaded + Prop_NamedIconPathControllerRightDeviceOff_String = 2052, // placeholder icon for "right" controller if not yet detected/loaded + Prop_NamedIconPathTrackingReferenceDeviceOff_String = 2053, // placeholder icon for sensor/base if not yet detected/loaded + Prop_DoNotApplyPrediction_Bool = 2054, // currently no effect. was used to disable HMD pose prediction on MR, which is now done by MR driver setting velocity=0 + Prop_CameraToHeadTransforms_Matrix34_Array = 2055, + Prop_DistortionMeshResolution_Int32 = 2056, // custom resolution of compositor calls to IVRSystem::ComputeDistortion + Prop_DriverIsDrawingControllers_Bool = 2057, + Prop_DriverRequestsApplicationPause_Bool = 2058, + Prop_DriverRequestsReducedRendering_Bool = 2059, + Prop_MinimumIpdStepMeters_Float = 2060, + Prop_AudioBridgeFirmwareVersion_Uint64 = 2061, + Prop_ImageBridgeFirmwareVersion_Uint64 = 2062, + Prop_ImuToHeadTransform_Matrix34 = 2063, + Prop_ImuFactoryGyroBias_Vector3 = 2064, + Prop_ImuFactoryGyroScale_Vector3 = 2065, + Prop_ImuFactoryAccelerometerBias_Vector3 = 2066, + Prop_ImuFactoryAccelerometerScale_Vector3 = 2067, + // reserved 2068 + Prop_ConfigurationIncludesLighthouse20Features_Bool = 2069, + Prop_AdditionalRadioFeatures_Uint64 = 2070, + Prop_CameraWhiteBalance_Vector4_Array = 2071, // Prop_NumCameras_Int32-sized array of float[4] RGBG white balance calibration data (max size is vr::k_unMaxCameras) + Prop_CameraDistortionFunction_Int32_Array = 2072, // Prop_NumCameras_Int32-sized array of vr::EVRDistortionFunctionType values (max size is vr::k_unMaxCameras) + Prop_CameraDistortionCoefficients_Float_Array = 2073, // Prop_NumCameras_Int32-sized array of double[vr::k_unMaxDistortionFunctionParameters] (max size is vr::k_unMaxCameras) + Prop_ExpectedControllerType_String = 2074, + Prop_HmdTrackingStyle_Int32 = 2075, // one of EHmdTrackingStyle + Prop_DriverProvidedChaperoneVisibility_Bool = 2076, + Prop_HmdColumnCorrectionSettingPrefix_String = 2077, + Prop_CameraSupportsCompatibilityModes_Bool = 2078, + Prop_SupportsRoomViewDepthProjection_Bool = 2079, + Prop_DisplayAvailableFrameRates_Float_Array = 2080, // populated by compositor from actual EDID list when available from GPU driver + Prop_DisplaySupportsMultipleFramerates_Bool = 2081, // if this is true but Prop_DisplayAvailableFrameRates_Float_Array is empty, explain to user + Prop_DisplayColorMultLeft_Vector3 = 2082, + Prop_DisplayColorMultRight_Vector3 = 2083, + Prop_DisplaySupportsRuntimeFramerateChange_Bool = 2084, + Prop_DisplaySupportsAnalogGain_Bool = 2085, + Prop_DisplayMinAnalogGain_Float = 2086, + Prop_DisplayMaxAnalogGain_Float = 2087, + Prop_CameraExposureTime_Float = 2088, + Prop_CameraGlobalGain_Float = 2089, + // Prop_DashboardLayoutPathName_String = 2090, // DELETED + Prop_DashboardScale_Float = 2091, + // Prop_PeerButtonInfo_String = 2092, // DELETED + Prop_Hmd_SupportsHDR10_Bool = 2093, + Prop_Hmd_EnableParallelRenderCameras_Bool = 2094, + Prop_DriverProvidedChaperoneJson_String = 2095, // higher priority than Prop_DriverProvidedChaperonePath_String + Prop_ForceSystemLayerUseAppPoses_Bool = 2096, + Prop_DashboardLinkSupport_Int32 = 2097, + Prop_DisplayMinUIAnalogGain_Float = 2098, + + Prop_IpdUIRangeMinMeters_Float = 2100, + Prop_IpdUIRangeMaxMeters_Float = 2101, + Prop_Hmd_SupportsHDCP14LegacyCompat_Bool = 2102, + Prop_Hmd_SupportsMicMonitoring_Bool = 2103, + Prop_Hmd_SupportsDisplayPortTrainingMode_Bool = 2104, + Prop_Hmd_SupportsRoomViewDirect_Bool = 2105, + Prop_Hmd_SupportsAppThrottling_Bool = 2106, + Prop_Hmd_SupportsGpuBusMonitoring_Bool = 2107, + Prop_DriverDisplaysIPDChanges_Bool = 2108, + // Prop_Driver_RecenterSupport_Int32 = 2109, // DELETED + Prop_Reserved_2110 = 2110, + Prop_Reserved_2111 = 2111, + Prop_Reserved_2112 = 2112, + + Prop_Hmd_MaxDistortedTextureWidth_Int32 = 2113, + Prop_Hmd_MaxDistortedTextureHeight_Int32 = 2114, + Prop_Hmd_AllowSupersampleFiltering_Bool = 2115, + + Prop_Hmd_AllowsClientToControlTextureIndex = 2116, + Prop_Reserved_2117 = 2117, + + // Driver requested mura correction properties + Prop_DriverRequestedMuraCorrectionMode_Int32 = 2200, + Prop_DriverRequestedMuraFeather_InnerLeft_Int32 = 2201, + Prop_DriverRequestedMuraFeather_InnerRight_Int32 = 2202, + Prop_DriverRequestedMuraFeather_InnerTop_Int32 = 2203, + Prop_DriverRequestedMuraFeather_InnerBottom_Int32 = 2204, + Prop_DriverRequestedMuraFeather_OuterLeft_Int32 = 2205, + Prop_DriverRequestedMuraFeather_OuterRight_Int32 = 2206, + Prop_DriverRequestedMuraFeather_OuterTop_Int32 = 2207, + Prop_DriverRequestedMuraFeather_OuterBottom_Int32 = 2208, + + Prop_Audio_DefaultPlaybackDeviceId_String = 2300, + Prop_Audio_DefaultRecordingDeviceId_String = 2301, + Prop_Audio_DefaultPlaybackDeviceVolume_Float = 2302, + Prop_Audio_SupportsDualSpeakerAndJackOutput_Bool = 2303, + Prop_Audio_DriverManagesPlaybackVolumeControl_Bool = 2304, + Prop_Audio_DriverPlaybackVolume_Float = 2305, + Prop_Audio_DriverPlaybackMute_Bool = 2306, + Prop_Audio_DriverManagesRecordingVolumeControl_Bool = 2307, + Prop_Audio_DriverRecordingVolume_Float = 2308, + Prop_Audio_DriverRecordingMute_Bool = 2309, + + // Pipewire Audio Stuff + Prop_Audio_PipewirePlaybackNode_Int32 = 2400, + Prop_Audio_PipewireRecordingNode_Int32 = 2401, + + // Properties that are unique to TrackedDeviceClass_Controller + Prop_AttachedDeviceId_String = 3000, + Prop_SupportedButtons_Uint64 = 3001, + Prop_Axis0Type_Int32 = 3002, // Return value is of type EVRControllerAxisType + Prop_Axis1Type_Int32 = 3003, // Return value is of type EVRControllerAxisType + Prop_Axis2Type_Int32 = 3004, // Return value is of type EVRControllerAxisType + Prop_Axis3Type_Int32 = 3005, // Return value is of type EVRControllerAxisType + Prop_Axis4Type_Int32 = 3006, // Return value is of type EVRControllerAxisType + Prop_ControllerRoleHint_Int32 = 3007, // Return value is of type ETrackedControllerRole + + // Properties that are unique to TrackedDeviceClass_TrackingReference + Prop_FieldOfViewLeftDegrees_Float = 4000, + Prop_FieldOfViewRightDegrees_Float = 4001, + Prop_FieldOfViewTopDegrees_Float = 4002, + Prop_FieldOfViewBottomDegrees_Float = 4003, + Prop_TrackingRangeMinimumMeters_Float = 4004, + Prop_TrackingRangeMaximumMeters_Float = 4005, + Prop_ModeLabel_String = 4006, + Prop_CanWirelessIdentify_Bool = 4007, // volatile, based on radio presence and fw discovery + Prop_Nonce_Int32 = 4008, + + // Properties that are used for user interface like icons names + Prop_IconPathName_String = 5000, // DEPRECATED. Value not referenced. Now expected to be part of icon path properties. + Prop_NamedIconPathDeviceOff_String = 5001, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others + Prop_NamedIconPathDeviceSearching_String = 5002, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others + Prop_NamedIconPathDeviceSearchingAlert_String = 5003, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others + Prop_NamedIconPathDeviceReady_String = 5004, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others + Prop_NamedIconPathDeviceReadyAlert_String = 5005, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others + Prop_NamedIconPathDeviceNotReady_String = 5006, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others + Prop_NamedIconPathDeviceStandby_String = 5007, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others + Prop_NamedIconPathDeviceAlertLow_String = 5008, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others + Prop_NamedIconPathDeviceStandbyAlert_String = 5009, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others + + // Properties that are used by helpers, but are opaque to applications + Prop_DisplayHiddenArea_Binary_Start = 5100, + Prop_DisplayHiddenArea_Binary_End = 5150, + Prop_ParentContainer = 5151, + Prop_OverrideContainer_Uint64 = 5152, + + // Properties that are unique to drivers + Prop_UserConfigPath_String = 6000, + Prop_InstallPath_String = 6001, + Prop_HasDisplayComponent_Bool = 6002, + Prop_HasControllerComponent_Bool = 6003, + Prop_HasCameraComponent_Bool = 6004, + Prop_HasDriverDirectModeComponent_Bool = 6005, + Prop_HasVirtualDisplayComponent_Bool = 6006, + Prop_HasSpatialAnchorsSupport_Bool = 6007, + Prop_SupportsXrTextureSets_Bool = 6008, + Prop_SupportsXrEyeGazeInteraction_Bool = 6009, + Prop_DeviceHasNoIMU_Bool = 6010, + Prop_UseAdvancedPrediction_Bool = 6011, + + // Properties that are set internally based on other information provided by drivers + Prop_ControllerType_String = 7000, + //Prop_LegacyInputProfile_String = 7001, // This is no longer used. See "legacy_binding" in the input profile instead. + Prop_ControllerHandSelectionPriority_Int32 = 7002, // Allows hand assignments to prefer some controllers over others. High numbers are selected over low numbers + + // Vendors are free to expose private debug data in this reserved region + Prop_VendorSpecific_Reserved_Start = 10000, + Prop_VendorSpecific_Reserved_End = 10999, + + // Addl SteamVR Reserved Space + Prop_Reserved_11000 = 11000, + Prop_Reserved_11001 = 11001, + Prop_Reserved_11002 = 11002, + Prop_Reserved_11003 = 11003, + Prop_Reserved_11004 = 11004, + + Prop_TrackedDeviceProperty_Max = 1000000, +}; + +/** No string property will ever be longer than this length */ +static const uint32_t k_unMaxPropertyStringSize = 32 * 1024; + +/** Used to return errors that occur when reading properties. */ +enum ETrackedPropertyError +{ + TrackedProp_Success = 0, + TrackedProp_WrongDataType = 1, + TrackedProp_WrongDeviceClass = 2, + TrackedProp_BufferTooSmall = 3, + TrackedProp_UnknownProperty = 4, // Driver has not set the property (and may not ever). + TrackedProp_InvalidDevice = 5, + TrackedProp_CouldNotContactServer = 6, + TrackedProp_ValueNotProvidedByDevice = 7, + TrackedProp_StringExceedsMaximumLength = 8, + TrackedProp_NotYetAvailable = 9, // The property value isn't known yet, but is expected soon. Call again later. + TrackedProp_PermissionDenied = 10, + TrackedProp_InvalidOperation = 11, + TrackedProp_CannotWriteToWildcards = 12, + TrackedProp_IPCReadFailure = 13, + TrackedProp_OutOfMemory = 14, + TrackedProp_InvalidContainer = 15, +}; + +/** Used to drive certain text in the UI when talking about the tracking system for the HMD */ +enum EHmdTrackingStyle +{ + HmdTrackingStyle_Unknown = 0, + + HmdTrackingStyle_Lighthouse = 1, // base stations and lasers + HmdTrackingStyle_OutsideInCameras = 2, // Cameras and LED, Rift 1 style + HmdTrackingStyle_InsideOutCameras = 3, // Cameras on HMD looking at the world +}; + +typedef uint64_t VRActionHandle_t; +typedef uint64_t VRActionSetHandle_t; +typedef uint64_t VRInputValueHandle_t; +typedef uint64_t VRInputComponentHandle_t; + +static const VRActionHandle_t k_ulInvalidActionHandle = 0; +static const VRActionSetHandle_t k_ulInvalidActionSetHandle = 0; +static const VRInputValueHandle_t k_ulInvalidInputValueHandle = 0; +static const VRInputComponentHandle_t k_ulInvalidInputComponentHandle = 0; + + +/** Allows the application to control how scene textures are used by the compositor when calling Submit. */ +enum EVRSubmitFlags +{ + // Simple render path. App submits rendered left and right eye images with no lens distortion correction applied. + Submit_Default = 0x00, + + // App submits final left and right eye images with lens distortion already applied (lens distortion makes the images appear + // barrel distorted with chromatic aberration correction applied). The app would have used the data returned by + // vr::IVRSystem::ComputeDistortion() to apply the correct distortion to the rendered images before calling Submit(). + Submit_LensDistortionAlreadyApplied = 0x01, + + // If the texture pointer passed in is actually a renderbuffer (e.g. for MSAA in OpenGL) then set this flag. + Submit_GlRenderBuffer = 0x02, + + // Do not use + Submit_Reserved = 0x04, + + // Set to indicate that pTexture is a pointer to a VRTextureWithPose_t. + // This flag can be combined with Submit_TextureWithDepth to pass a VRTextureWithPoseAndDepth_t. + Submit_TextureWithPose = 0x08, + + // Set to indicate that pTexture is a pointer to a VRTextureWithDepth_t. + // This flag can be combined with Submit_TextureWithPose to pass a VRTextureWithPoseAndDepth_t. + Submit_TextureWithDepth = 0x10, + + // Set to indicate a discontinuity between this and the last frame. + // This will prevent motion smoothing from attempting to extrapolate using the pair. + Submit_FrameDiscontinuity = 0x20, + + // Set to indicate that pTexture->handle is a contains VRVulkanTextureArrayData_t + Submit_VulkanTextureWithArrayData = 0x40, + + // If the texture pointer passed in is an OpenGL Array texture, set this flag + Submit_GlArrayTexture = 0x80, + + // If the texture is an EGL texture and not an glX/wGL texture (Linux only, currently) + Submit_IsEgl = 0x100, + + // Set to indicate that pTexture is a pointer to a VRTextureWithMotion_t. + Submit_TextureWithMotion = 0x200 | Submit_TextureWithPose | Submit_TextureWithDepth, + + // Do not use + Submit_Reserved2 = 0x08000, + Submit_Reserved3 = 0x10000, + Submit_Reserved4 = 0x20000, + Submit_Reserved5 = 0x40000, + Submit_Reserved6 = 0x80000, +}; + +/** Data required for passing Vulkan textures to IVRCompositor::Submit. +* Be sure to call OpenVR_Shutdown before destroying these resources. +* Please see https://github.com/ValveSoftware/openvr/wiki/Vulkan for Vulkan-specific documentation */ +struct VRVulkanTextureData_t +{ + uint64_t m_nImage; // VkImage + VkDevice_T *m_pDevice; + VkPhysicalDevice_T *m_pPhysicalDevice; + VkInstance_T *m_pInstance; + VkQueue_T *m_pQueue; + uint32_t m_nQueueFamilyIndex; + uint32_t m_nWidth, m_nHeight, m_nFormat, m_nSampleCount; +}; + +/** Data required for passing Vulkan texture arrays to IVRCompositor::Submit. +* Be sure to call OpenVR_Shutdown before destroying these resources. +* Please see https://github.com/ValveSoftware/openvr/wiki/Vulkan for Vulkan-specific documentation */ +struct VRVulkanTextureArrayData_t : public VRVulkanTextureData_t +{ + uint32_t m_unArrayIndex; + uint32_t m_unArraySize; +}; + +/** Data required for passing D3D12 textures to IVRCompositor::Submit. +* Be sure to call OpenVR_Shutdown before destroying these resources. */ +struct D3D12TextureData_t +{ + ID3D12Resource *m_pResource; + ID3D12CommandQueue *m_pCommandQueue; + uint32_t m_nNodeMask; +}; + +/** Status of the overall system or tracked objects */ +enum EVRState +{ + VRState_Undefined = -1, + VRState_Off = 0, + VRState_Searching = 1, + VRState_Searching_Alert = 2, + VRState_Ready = 3, + VRState_Ready_Alert = 4, + VRState_NotReady = 5, + VRState_Standby = 6, + VRState_Ready_Alert_Low = 7, +}; + +/** The types of events that could be posted (and what the parameters mean for each event type) */ +enum EVREventType +{ + VREvent_None = 0, + + VREvent_TrackedDeviceActivated = 100, + VREvent_TrackedDeviceDeactivated = 101, + VREvent_TrackedDeviceUpdated = 102, + VREvent_TrackedDeviceUserInteractionStarted = 103, + VREvent_TrackedDeviceUserInteractionEnded = 104, + VREvent_IpdChanged = 105, + VREvent_EnterStandbyMode = 106, + VREvent_LeaveStandbyMode = 107, + VREvent_TrackedDeviceRoleChanged = 108, + VREvent_WatchdogWakeUpRequested = 109, + VREvent_LensDistortionChanged = 110, + VREvent_PropertyChanged = 111, + VREvent_WirelessDisconnect = 112, + VREvent_WirelessReconnect = 113, + VREvent_Reserved_0114 = 114, + VREvent_Reserved_0115 = 115, + + VREvent_ButtonPress = 200, // data is controller + VREvent_ButtonUnpress = 201, // data is controller + VREvent_ButtonTouch = 202, // data is controller + VREvent_ButtonUntouch = 203, // data is controller + + // VREvent_DualAnalog_Press = 250, // No longer sent + // VREvent_DualAnalog_Unpress = 251, // No longer sent + // VREvent_DualAnalog_Touch = 252, // No longer sent + // VREvent_DualAnalog_Untouch = 253, // No longer sent + // VREvent_DualAnalog_Move = 254, // No longer sent + // VREvent_DualAnalog_ModeSwitch1 = 255, // No longer sent + // VREvent_DualAnalog_ModeSwitch2 = 256, // No longer sent + VREvent_Modal_Cancel = 257, // Sent to overlays with the + + VREvent_MouseMove = 300, // data is mouse + VREvent_MouseButtonDown = 301, // data is mouse + VREvent_MouseButtonUp = 302, // data is mouse + VREvent_FocusEnter = 303, // data is overlay + VREvent_FocusLeave = 304, // data is overlay + VREvent_ScrollDiscrete = 305, // data is scroll + VREvent_TouchPadMove = 306, // data is mouse + VREvent_OverlayFocusChanged = 307, // data is overlay, global event + VREvent_ReloadOverlays = 308, + VREvent_ScrollSmooth = 309, // data is scroll + VREvent_LockMousePosition = 310, // data is mouse + VREvent_UnlockMousePosition = 311, // data is mouse + + VREvent_InputFocusCaptured = 400, // data is process DEPRECATED + VREvent_InputFocusReleased = 401, // data is process DEPRECATED + // VREvent_SceneFocusLost = 402, // data is process + // VREvent_SceneFocusGained = 403, // data is process + VREvent_SceneApplicationChanged = 404, // data is process - The App actually drawing the scene changed (usually to or from the compositor) + // VREvent_SceneFocusChanged = 405, // data is process - This is defunct and will not be called. + VREvent_InputFocusChanged = 406, // data is process + // VREvent_SceneApplicationSecondaryRenderingStarted = 407, + VREvent_SceneApplicationUsingWrongGraphicsAdapter = 408, // data is process + VREvent_ActionBindingReloaded = 409, // data is process - The App that action binds reloaded for + + VREvent_HideRenderModels = 410, // Sent to the scene application to request hiding render models temporarily + VREvent_ShowRenderModels = 411, // Sent to the scene application to request restoring render model visibility + + VREvent_SceneApplicationStateChanged = 412, // No data; but query VRApplications()->GetSceneApplicationState(); + + VREvent_SceneAppPipeDisconnected = 413, // data is process - Called when the scene app's pipe has been closed. + + VREvent_ConsoleOpened = 420, + VREvent_ConsoleClosed = 421, + + VREvent_OverlayShown = 500, // Indicates that an overlay is now visible to someone and should be rendering normally. Reflects IVROverlay::IsOverlayVisible() becoming true. + VREvent_OverlayHidden = 501, // Indicates that an overlay is no longer visible to someone and doesn't need to render frames. Reflects IVROverlay::IsOverlayVisible() becoming false. + VREvent_DashboardActivated = 502, + VREvent_DashboardDeactivated = 503, + //VREvent_DashboardThumbSelected = 504, // Sent to the overlay manager - data is overlay - No longer sent + //VREvent_DashboardRequested = 505, // Sent to the overlay manager - data is overlay + VREvent_ResetDashboard = 506, // Send to the overlay manager + //VREvent_RenderToast = 507, // Send to the dashboard to render a toast - data is the notification ID -- no longer sent + VREvent_ImageLoaded = 508, // Sent to overlays when a SetOverlayRaw or SetOverlayFromFile call finishes loading + VREvent_ShowKeyboard = 509, // Sent to keyboard renderer in the dashboard to invoke it + VREvent_HideKeyboard = 510, // Sent to keyboard renderer in the dashboard to hide it + VREvent_OverlayGamepadFocusGained = 511, // Sent to an overlay when IVROverlay::SetFocusOverlay is called on it + VREvent_OverlayGamepadFocusLost = 512, // Send to an overlay when it previously had focus and IVROverlay::SetFocusOverlay is called on something else + VREvent_OverlaySharedTextureChanged = 513, + //VREvent_DashboardGuideButtonDown = 514, // These are no longer sent + //VREvent_DashboardGuideButtonUp = 515, + VREvent_ScreenshotTriggered = 516, // Screenshot button combo was pressed, Dashboard should request a screenshot + VREvent_ImageFailed = 517, // Sent to overlays when a SetOverlayRaw or SetOverlayfromFail fails to load + VREvent_DashboardOverlayCreated = 518, + VREvent_SwitchGamepadFocus = 519, + + // Screenshot API + VREvent_RequestScreenshot = 520, // Sent by vrclient application to compositor to take a screenshot + VREvent_ScreenshotTaken = 521, // Sent by compositor to the application that the screenshot has been taken + VREvent_ScreenshotFailed = 522, // Sent by compositor to the application that the screenshot failed to be taken + VREvent_SubmitScreenshotToDashboard = 523, // Sent by compositor to the dashboard that a completed screenshot was submitted + VREvent_ScreenshotProgressToDashboard = 524, // Sent by compositor to the dashboard that a completed screenshot was submitted + + VREvent_PrimaryDashboardDeviceChanged = 525, + VREvent_RoomViewShown = 526, // Sent by compositor whenever room-view is enabled (for scene apps only - not for construct or transient bounds) + VREvent_RoomViewHidden = 527, // Sent by compositor whenever room-view is disabled (for scene apps only - not for construct or transient bounds) + VREvent_ShowUI = 528, // data is showUi + VREvent_ShowDevTools = 529, // data is showDevTools + VREvent_DesktopViewUpdating = 530, + VREvent_DesktopViewReady = 531, + + VREvent_StartDashboard = 532, + VREvent_ElevatePrism = 533, + VREvent_OverlayClosed = 534, // The overlay's close button is pressed. + VREvent_DashboardThumbChanged = 535, // Sent when a dashboard thumbnail image changes + VREvent_DesktopMightBeVisible = 536, // Sent when any known desktop related overlay is visible + VREvent_DesktopMightBeHidden = 537, // Sent when all known desktop related overlays are hidden + VREvent_MutualSteamCapabilitiesChanged = 538, // Sent when the set of capabilities common between both Steam and SteamVR have changed. + VREvent_OverlayCreated = 539, // An OpenVR overlay of any sort was created. Data is overlay. + VREvent_OverlayDestroyed = 540, // An OpenVR overlay of any sort was destroyed. Data is overlay. + VREvent_OverlayNameChanged = 544, // An OpenVR overlay's name changed. Data is overlay. + + VREvent_TrackingRecordingStarted = 541, + VREvent_TrackingRecordingStopped = 542, + VREvent_SetTrackingRecordingPath = 543, + + VREvent_Reserved_0560 = 560, // No data + VREvent_Reserved_0561 = 561, // No data + VREvent_Reserved_0562 = 562, // No data + VREvent_Reserved_0563 = 563, // No data + + VREvent_Notification_Shown = 600, + VREvent_Notification_Hidden = 601, + VREvent_Notification_BeginInteraction = 602, + VREvent_Notification_Destroyed = 603, + + VREvent_Quit = 700, // data is process + VREvent_ProcessQuit = 701, // data is process + //VREvent_QuitAborted_UserPrompt = 702, // data is process + VREvent_QuitAcknowledged = 703, // data is process + VREvent_DriverRequestedQuit = 704, // The driver has requested that SteamVR shut down + VREvent_RestartRequested = 705, // A driver or other component wants the user to restart SteamVR + VREvent_InvalidateSwapTextureSets = 706, + VREvent_RequestDisconnectWirelessHMD = 707, // vrserver asks vrlink to disconnect + + VREvent_ChaperoneDataHasChanged = 800, // this will never happen with the new chaperone system + VREvent_ChaperoneUniverseHasChanged = 801, + VREvent_ChaperoneTempDataHasChanged = 802, // this will never happen with the new chaperone system + VREvent_ChaperoneSettingsHaveChanged = 803, + VREvent_SeatedZeroPoseReset = 804, + VREvent_ChaperoneFlushCache = 805, // Sent when the process needs to reload any cached data it retrieved from VRChaperone() + VREvent_ChaperoneRoomSetupStarting = 806, // Triggered by CVRChaperoneClient::RoomSetupStarting + VREvent_ChaperoneRoomSetupCommitted = 807, // Triggered by CVRChaperoneClient::CommitWorkingCopy (formerly VREvent_ChaperoneRoomSetupFinished) + VREvent_StandingZeroPoseReset = 808, + VREvent_Reserved_0809 = 809, + VREvent_Reserved_0810 = 810, + VREvent_Reserved_0811 = 811, + VREvent_Reserved_0812 = 812, + VREvent_Reserved_0813 = 813, + VREvent_Reserved_0814 = 814, + + VREvent_AudioSettingsHaveChanged = 820, + + VREvent_BackgroundSettingHasChanged = 850, + VREvent_CameraSettingsHaveChanged = 851, + VREvent_ReprojectionSettingHasChanged = 852, + VREvent_ModelSkinSettingsHaveChanged = 853, + VREvent_EnvironmentSettingsHaveChanged = 854, + VREvent_PowerSettingsHaveChanged = 855, + VREvent_EnableHomeAppSettingsHaveChanged = 856, + VREvent_SteamVRSectionSettingChanged = 857, + VREvent_LighthouseSectionSettingChanged = 858, + VREvent_NullSectionSettingChanged = 859, + VREvent_UserInterfaceSectionSettingChanged = 860, + VREvent_NotificationsSectionSettingChanged = 861, + VREvent_KeyboardSectionSettingChanged = 862, + VREvent_PerfSectionSettingChanged = 863, + VREvent_DashboardSectionSettingChanged = 864, + VREvent_WebInterfaceSectionSettingChanged = 865, + VREvent_TrackersSectionSettingChanged = 866, + VREvent_LastKnownSectionSettingChanged = 867, + VREvent_DismissedWarningsSectionSettingChanged = 868, + VREvent_GpuSpeedSectionSettingChanged = 869, + VREvent_WindowsMRSectionSettingChanged = 870, + VREvent_OtherSectionSettingChanged = 871, + VREvent_AnyDriverSettingsChanged = 872, + VREvent_Reserved_0873 = 873, + + VREvent_StatusUpdate = 900, + + VREvent_WebInterface_InstallDriverCompleted = 950, + + VREvent_MCImageUpdated = 1000, + + VREvent_FirmwareUpdateStarted = 1100, + VREvent_FirmwareUpdateFinished = 1101, + + VREvent_KeyboardClosed = 1200, // DEPRECATED: Sent only to the overlay it closed for, or globally if it was closed for a scene app + VREvent_KeyboardCharInput = 1201, // Sent on keyboard input. Warning: event type appears as both global event and overlay event + VREvent_KeyboardDone = 1202, // Sent when DONE button clicked on keyboard. Warning: event type appears as both global event and overlay event + VREvent_KeyboardOpened_Global = 1203, // Sent globally when the keyboard is opened. data.keyboard.overlayHandle is who it was opened for (scene app if k_ulOverlayHandleInvalid) + VREvent_KeyboardClosed_Global = 1204, // Sent globally when the keyboard is closed. data.keyboard.overlayHandle is who it was opened for (scene app if k_ulOverlayHandleInvalid) + + //VREvent_ApplicationTransitionStarted = 1300, + //VREvent_ApplicationTransitionAborted = 1301, + //VREvent_ApplicationTransitionNewAppStarted = 1302, + VREvent_ApplicationListUpdated = 1303, + VREvent_ApplicationMimeTypeLoad = 1304, + // VREvent_ApplicationTransitionNewAppLaunchComplete = 1305, + VREvent_ProcessConnected = 1306, + VREvent_ProcessDisconnected = 1307, + + //VREvent_Compositor_MirrorWindowShown = 1400, // DEPRECATED + //VREvent_Compositor_MirrorWindowHidden = 1401, // DEPRECATED + VREvent_Compositor_ChaperoneBoundsShown = 1410, + VREvent_Compositor_ChaperoneBoundsHidden = 1411, + VREvent_Compositor_DisplayDisconnected = 1412, + VREvent_Compositor_DisplayReconnected = 1413, + VREvent_Compositor_HDCPError = 1414, // data is hdcpError + VREvent_Compositor_ApplicationNotResponding = 1415, + VREvent_Compositor_ApplicationResumed = 1416, + VREvent_Compositor_OutOfVideoMemory = 1417, + VREvent_Compositor_DisplayModeNotSupported = 1418, // k_pch_SteamVR_PreferredRefreshRate + VREvent_Compositor_StageOverrideReady = 1419, + VREvent_Compositor_RequestDisconnectReconnect = 1420, + + VREvent_TrackedCamera_StartVideoStream = 1500, + VREvent_TrackedCamera_StopVideoStream = 1501, + VREvent_TrackedCamera_PauseVideoStream = 1502, + VREvent_TrackedCamera_ResumeVideoStream = 1503, + VREvent_TrackedCamera_EditingSurface = 1550, + + VREvent_PerformanceTest_EnableCapture = 1600, + VREvent_PerformanceTest_DisableCapture = 1601, + VREvent_PerformanceTest_FidelityLevel = 1602, + + VREvent_MessageOverlay_Closed = 1650, + VREvent_MessageOverlayCloseRequested = 1651, + + VREvent_Input_HapticVibration = 1700, // data is hapticVibration + VREvent_Input_BindingLoadFailed = 1701, // data is inputBinding + VREvent_Input_BindingLoadSuccessful = 1702, // data is inputBinding + VREvent_Input_ActionManifestReloaded = 1703, // no data + VREvent_Input_ActionManifestLoadFailed = 1704, // data is actionManifest + VREvent_Input_ProgressUpdate = 1705, // data is progressUpdate + VREvent_Input_TrackerActivated = 1706, + VREvent_Input_BindingsUpdated = 1707, + VREvent_Input_BindingSubscriptionChanged = 1708, + + VREvent_SpatialAnchors_PoseUpdated = 1800, // data is spatialAnchor. broadcast + VREvent_SpatialAnchors_DescriptorUpdated = 1801, // data is spatialAnchor. broadcast + VREvent_SpatialAnchors_RequestPoseUpdate = 1802, // data is spatialAnchor. sent to specific driver + VREvent_SpatialAnchors_RequestDescriptorUpdate = 1803, // data is spatialAnchor. sent to specific driver + + VREvent_SystemReport_Started = 1900, // user or system initiated generation of a system report. broadcast + + VREvent_Monitor_ShowHeadsetView = 2000, // data is process + VREvent_Monitor_HideHeadsetView = 2001, // data is process + + VREvent_Audio_SetSpeakersVolume = 2100, + VREvent_Audio_SetSpeakersMute = 2101, + VREvent_Audio_SetMicrophoneVolume = 2102, + VREvent_Audio_SetMicrophoneMute = 2103, + + VREvent_RenderModel_CountChanged = 2200, //Number of RenderModels in the system has changed + + // Vendors are free to expose private events in this reserved region + VREvent_VendorSpecific_Reserved_Start = 10000, + VREvent_VendorSpecific_Reserved_End = 19999, +}; + + +/** Level of Hmd activity */ +// UserInteraction_Timeout means the device is in the process of timing out. +// InUse = ( k_EDeviceActivityLevel_UserInteraction || k_EDeviceActivityLevel_UserInteraction_Timeout ) +// VREvent_TrackedDeviceUserInteractionStarted fires when the devices transitions from Standby -> UserInteraction or Idle -> UserInteraction. +// VREvent_TrackedDeviceUserInteractionEnded fires when the devices transitions from UserInteraction_Timeout -> Idle +enum EDeviceActivityLevel +{ + k_EDeviceActivityLevel_Unknown = -1, + k_EDeviceActivityLevel_Idle = 0, // No activity for the last 10 seconds + k_EDeviceActivityLevel_UserInteraction = 1, // Activity (movement or prox sensor) is happening now + k_EDeviceActivityLevel_UserInteraction_Timeout = 2, // No activity for the last 0.5 seconds + k_EDeviceActivityLevel_Standby = 3, // Idle for at least 5 seconds (configurable in Settings -> Power Management) + k_EDeviceActivityLevel_Idle_Timeout = 4, +}; + + +/** VR controller button and axis IDs */ +enum EVRButtonId +{ + k_EButton_System = 0, + k_EButton_ApplicationMenu = 1, + k_EButton_Grip = 2, + k_EButton_DPad_Left = 3, + k_EButton_DPad_Up = 4, + k_EButton_DPad_Right = 5, + k_EButton_DPad_Down = 6, + k_EButton_A = 7, + + k_EButton_ProximitySensor = 31, + + k_EButton_Axis0 = 32, + k_EButton_Axis1 = 33, + k_EButton_Axis2 = 34, + k_EButton_Axis3 = 35, + k_EButton_Axis4 = 36, + + // aliases for well known controllers + k_EButton_SteamVR_Touchpad = k_EButton_Axis0, + k_EButton_SteamVR_Trigger = k_EButton_Axis1, + + k_EButton_Dashboard_Back = k_EButton_Grip, + + k_EButton_IndexController_A = k_EButton_Grip, + k_EButton_IndexController_B = k_EButton_ApplicationMenu, + k_EButton_IndexController_JoyStick = k_EButton_Axis3, + + k_EButton_Reserved0 = 50, + k_EButton_Reserved1 = 51, + + k_EButton_Max = 64 +}; + +inline uint64_t ButtonMaskFromId( EVRButtonId id ) { return 1ull << id; } + +/** used for controller button events */ +struct VREvent_Controller_t +{ + uint32_t button; // EVRButtonId enum +}; + + +/** used for simulated mouse events in overlay space */ +enum EVRMouseButton +{ + VRMouseButton_Left = 0x0001, + VRMouseButton_Right = 0x0002, + VRMouseButton_Middle = 0x0004, +}; + + +/** used for simulated mouse events in overlay space */ +struct VREvent_Mouse_t +{ + float x, y; // co-ords are in GL space, bottom left of the texture is 0,0 + uint32_t button; // EVRMouseButton enum + + // if from an event triggered by cursor input on an overlay that supports multiple cursors, this is the index of + // which tracked cursor the event is for + uint32_t cursorIndex; +}; + +/** used for simulated mouse wheel scroll */ +struct VREvent_Scroll_t +{ + float xdelta, ydelta; + uint32_t unused; + float viewportscale; // For scrolling on an overlay with laser mouse, this is the overlay's vertical size relative to the overlay height. Range: [0,1] + + // if from an event triggered by cursor input on an overlay that supports multiple cursors, this is the index of + // which tracked cursor the event is for + uint32_t cursorIndex; +}; + +/** when in mouse input mode you can receive data from the touchpad, these events are only sent if the users finger + is on the touchpad (or just released from it). These events are sent to overlays with the VROverlayFlags_SendVRTouchpadEvents + flag set. +**/ +struct VREvent_TouchPadMove_t +{ + // true if the users finger is detected on the touch pad + bool bFingerDown; + + // How long the finger has been down in seconds + float flSecondsFingerDown; + + // These values indicate the starting finger position (so you can do some basic swipe stuff) + float fValueXFirst; + float fValueYFirst; + + // This is the raw sampled coordinate without deadzoning + float fValueXRaw; + float fValueYRaw; +}; + +/** notification related events. Details will still change at this point */ +struct VREvent_Notification_t +{ + uint64_t ulUserValue; + uint32_t notificationId; +}; + +/** Used for events about processes */ +struct VREvent_Process_t +{ + uint32_t pid; + uint32_t oldPid; + bool bForced; + // If the associated event was triggered by a connection loss + bool bConnectionLost; +}; + + +/** Used for a few events about overlays */ +struct VREvent_Overlay_t +{ + uint64_t overlayHandle; // VROverlayHandle_t + uint64_t devicePath; + uint64_t memoryBlockId; + + // if from an event triggered by cursor input on an overlay that supports multiple cursors, this is the index of + // which tracked cursor the event is for + uint32_t cursorIndex; +}; + + +/** Used for a few events about overlays */ +struct VREvent_Status_t +{ + uint32_t statusState; // EVRState enum +}; + +/** Used for keyboard events */ +struct VREvent_Keyboard_t +{ + char cNewInput[8]; // 7 bytes of utf8 + null + uint64_t uUserValue; // caller specified opaque token + uint64_t overlayHandle; // VROverlayHandle_t +}; + +struct VREvent_Ipd_t +{ + float ipdMeters; +}; + +struct VREvent_Chaperone_t +{ + uint64_t m_nPreviousUniverse_deprecated; + uint64_t m_nCurrentUniverse; +}; + +/** Not actually used for any events */ +struct VREvent_Reserved_t +{ + uint64_t reserved0; + uint64_t reserved1; + uint64_t reserved2; + uint64_t reserved3; + uint64_t reserved4; + uint64_t reserved5; +}; + +struct VREvent_PerformanceTest_t +{ + uint32_t m_nFidelityLevel; +}; + +struct VREvent_SeatedZeroPoseReset_t +{ + bool bResetBySystemMenu; +}; + +struct VREvent_Screenshot_t +{ + uint32_t handle; + uint32_t type; +}; + +struct VREvent_ScreenshotProgress_t +{ + float progress; +}; + +struct VREvent_ApplicationLaunch_t +{ + uint32_t pid; + uint32_t unArgsHandle; +}; + +struct VREvent_EditingCameraSurface_t +{ + uint64_t overlayHandle; + uint32_t nVisualMode; +}; + +struct VREvent_MessageOverlay_t +{ + uint32_t unVRMessageOverlayResponse; // vr::VRMessageOverlayResponse enum +}; + +struct VREvent_Property_t +{ + PropertyContainerHandle_t container; + ETrackedDeviceProperty prop; +}; + +struct VREvent_HapticVibration_t +{ + uint64_t containerHandle; // property container handle of the device with the haptic component + uint64_t componentHandle; // Which haptic component needs to vibrate + float fDurationSeconds; + float fFrequency; + float fAmplitude; +}; + +struct VREvent_WebConsole_t +{ + WebConsoleHandle_t webConsoleHandle; +}; + +struct VREvent_InputBindingLoad_t +{ + vr::PropertyContainerHandle_t ulAppContainer; + uint64_t pathMessage; + uint64_t pathUrl; + uint64_t pathControllerType; +}; + +struct VREvent_InputActionManifestLoad_t +{ + uint64_t pathAppKey; + uint64_t pathMessage; + uint64_t pathMessageParam; + uint64_t pathManifestPath; +}; + +struct VREvent_SpatialAnchor_t +{ + SpatialAnchorHandle_t unHandle; +}; + +struct VREvent_ProgressUpdate_t +{ + uint64_t ulApplicationPropertyContainer; + uint64_t pathDevice; + uint64_t pathInputSource; + uint64_t pathProgressAction; + uint64_t pathIcon; + float fProgress; +}; + +enum EShowUIType +{ + ShowUI_ControllerBinding = 0, + ShowUI_ManageTrackers = 1, + // ShowUI_QuickStart = 2, // Deprecated + ShowUI_Pairing = 3, + ShowUI_Settings = 4, + ShowUI_DebugCommands = 5, + ShowUI_FullControllerBinding = 6, + ShowUI_ManageDrivers = 7, +}; + +struct VREvent_ShowUI_t +{ + EShowUIType eType; +}; + +struct VREvent_ShowDevTools_t +{ + int32_t nBrowserIdentifier; +}; + +enum EHDCPError +{ + HDCPError_None = 0, + HDCPError_LinkLost = 1, + HDCPError_Tampered = 2, + HDCPError_DeviceRevoked = 3, + HDCPError_Unknown = 4 +}; + +struct VREvent_HDCPError_t +{ + EHDCPError eCode; +}; + +struct VREvent_AudioVolumeControl_t +{ + float fVolumeLevel; +}; + +struct VREvent_AudioMuteControl_t +{ + bool bMute; +}; + +typedef union +{ + VREvent_Reserved_t reserved; + VREvent_Controller_t controller; + VREvent_Mouse_t mouse; + VREvent_Scroll_t scroll; + VREvent_Process_t process; + VREvent_Notification_t notification; + VREvent_Overlay_t overlay; + VREvent_Status_t status; + VREvent_Keyboard_t keyboard; + VREvent_Ipd_t ipd; + VREvent_Chaperone_t chaperone; + VREvent_PerformanceTest_t performanceTest; + VREvent_TouchPadMove_t touchPadMove; + VREvent_SeatedZeroPoseReset_t seatedZeroPoseReset; + VREvent_Screenshot_t screenshot; + VREvent_ScreenshotProgress_t screenshotProgress; + VREvent_ApplicationLaunch_t applicationLaunch; + VREvent_EditingCameraSurface_t cameraSurface; + VREvent_MessageOverlay_t messageOverlay; + VREvent_Property_t property; + VREvent_HapticVibration_t hapticVibration; + VREvent_WebConsole_t webConsole; + VREvent_InputBindingLoad_t inputBinding; + VREvent_InputActionManifestLoad_t actionManifest; + VREvent_SpatialAnchor_t spatialAnchor; + VREvent_ProgressUpdate_t progressUpdate; + VREvent_ShowUI_t showUi; + VREvent_ShowDevTools_t showDevTools; + VREvent_HDCPError_t hdcpError; + VREvent_AudioVolumeControl_t audioVolumeControl; + VREvent_AudioMuteControl_t audioMuteControl; + /** NOTE!!! If you change this you MUST manually update openvr_interop.cs.py and openvr_api_flat.h.py */ +} VREvent_Data_t; + + +#if defined(__linux__) || defined(__APPLE__) +// This structure was originally defined mis-packed on Linux, preserved for +// compatibility. +#pragma pack( push, 4 ) +#endif + +/** An event posted by the server to all running applications */ +struct VREvent_t +{ + uint32_t eventType; // EVREventType enum + TrackedDeviceIndex_t trackedDeviceIndex; + float eventAgeSeconds; + // event data must be the end of the struct as its size is variable + VREvent_Data_t data; +}; + +#if defined(__linux__) || defined(__APPLE__) +#pragma pack( pop ) +#endif + +typedef uint32_t VRComponentProperties; + +enum EVRComponentProperty +{ + VRComponentProperty_IsStatic = (1 << 0), + VRComponentProperty_IsVisible = (1 << 1), + VRComponentProperty_IsTouched = (1 << 2), + VRComponentProperty_IsPressed = (1 << 3), + VRComponentProperty_IsScrolled = (1 << 4), + VRComponentProperty_IsHighlighted = (1 << 5), +}; + + +/** Describes state information about a render-model component, including transforms and other dynamic properties */ +struct RenderModel_ComponentState_t +{ + HmdMatrix34_t mTrackingToComponentRenderModel; // Transform required when drawing the component render model + HmdMatrix34_t mTrackingToComponentLocal; // Transform available for attaching to a local component coordinate system (-Z out from surface ) + VRComponentProperties uProperties; +}; + + +enum EVRInputError +{ + VRInputError_None = 0, + VRInputError_NameNotFound = 1, + VRInputError_WrongType = 2, + VRInputError_InvalidHandle = 3, + VRInputError_InvalidParam = 4, + VRInputError_NoSteam = 5, + VRInputError_MaxCapacityReached = 6, + VRInputError_IPCError = 7, + VRInputError_NoActiveActionSet = 8, + VRInputError_InvalidDevice = 9, + VRInputError_InvalidSkeleton = 10, + VRInputError_InvalidBoneCount = 11, + VRInputError_InvalidCompressedData = 12, + VRInputError_NoData = 13, + VRInputError_BufferTooSmall = 14, + VRInputError_MismatchedActionManifest = 15, + VRInputError_MissingSkeletonData = 16, + VRInputError_InvalidBoneIndex = 17, + VRInputError_InvalidPriority = 18, + VRInputError_PermissionDenied = 19, + VRInputError_InvalidRenderModel = 20, +}; + +enum EVRSpatialAnchorError +{ + VRSpatialAnchorError_Success = 0, + VRSpatialAnchorError_Internal = 1, + VRSpatialAnchorError_UnknownHandle = 2, + VRSpatialAnchorError_ArrayTooSmall = 3, + VRSpatialAnchorError_InvalidDescriptorChar = 4, + VRSpatialAnchorError_NotYetAvailable = 5, + VRSpatialAnchorError_NotAvailableInThisUniverse = 6, + VRSpatialAnchorError_PermanentlyUnavailable = 7, + VRSpatialAnchorError_WrongDriver = 8, + VRSpatialAnchorError_DescriptorTooLong = 9, + VRSpatialAnchorError_Unknown = 10, + VRSpatialAnchorError_NoRoomCalibration = 11, + VRSpatialAnchorError_InvalidArgument = 12, + VRSpatialAnchorError_UnknownDriver = 13, +}; + +/** The mesh to draw into the stencil (or depth) buffer to perform +* early stencil (or depth) kills of pixels that will never appear on the HMD. +* This mesh draws on all the pixels that will be hidden after distortion. +* +* If the HMD does not provide a visible area mesh pVertexData will be +* NULL and unTriangleCount will be 0. */ +struct HiddenAreaMesh_t +{ + const HmdVector2_t *pVertexData; + uint32_t unTriangleCount; +}; + + +enum EHiddenAreaMeshType +{ + k_eHiddenAreaMesh_Standard = 0, + k_eHiddenAreaMesh_Inverse = 1, + k_eHiddenAreaMesh_LineLoop = 2, + + k_eHiddenAreaMesh_Max = 3, +}; + + +/** Identifies what kind of axis is on the controller at index n. Read this type +* with pVRSystem->Get( nControllerDeviceIndex, Prop_Axis0Type_Int32 + n ); +*/ +enum EVRControllerAxisType +{ + k_eControllerAxis_None = 0, + k_eControllerAxis_TrackPad = 1, + k_eControllerAxis_Joystick = 2, + k_eControllerAxis_Trigger = 3, // Analog trigger data is in the X axis +}; + + +/** contains information about one axis on the controller */ +struct VRControllerAxis_t +{ + float x; // Ranges from -1.0 to 1.0 for joysticks and track pads. Ranges from 0.0 to 1.0 for triggers were 0 is fully released. + float y; // Ranges from -1.0 to 1.0 for joysticks and track pads. Is always 0.0 for triggers. +}; + + +/** the number of axes in the controller state */ +static const uint32_t k_unControllerStateAxisCount = 5; + + +#if defined(__linux__) || defined(__APPLE__) +// This structure was originally defined mis-packed on Linux, preserved for +// compatibility. +#pragma pack( push, 4 ) +#endif + +/** Holds all the state of a controller at one moment in time. */ +struct VRControllerState001_t +{ + // If packet num matches that on your prior call, then the controller state hasn't been changed since + // your last call and there is no need to process it + uint32_t unPacketNum; + + // bit flags for each of the buttons. Use ButtonMaskFromId to turn an ID into a mask + uint64_t ulButtonPressed; + uint64_t ulButtonTouched; + + // Axis data for the controller's analog inputs + VRControllerAxis_t rAxis[ k_unControllerStateAxisCount ]; +}; +#if defined(__linux__) || defined(__APPLE__) +#pragma pack( pop ) +#endif + + +typedef VRControllerState001_t VRControllerState_t; + + +/** determines how to provide output to the application of various event processing functions. */ +enum EVRControllerEventOutputType +{ + ControllerEventOutput_OSEvents = 0, + ControllerEventOutput_VREvents = 1, +}; + + + +/** Collision Bounds Style */ +enum ECollisionBoundsStyle +{ + COLLISION_BOUNDS_STYLE_BEGINNER = 0, + COLLISION_BOUNDS_STYLE_INTERMEDIATE, + COLLISION_BOUNDS_STYLE_SQUARES, + COLLISION_BOUNDS_STYLE_ADVANCED, + COLLISION_BOUNDS_STYLE_NONE, + + COLLISION_BOUNDS_STYLE_COUNT +}; + +/** used to refer to a single VR overlay */ +typedef uint64_t VROverlayHandle_t; + +static const VROverlayHandle_t k_ulOverlayHandleInvalid = 0; + +/** Errors that can occur around VR overlays */ +enum EVROverlayError +{ + VROverlayError_None = 0, + + VROverlayError_UnknownOverlay = 10, + VROverlayError_InvalidHandle = 11, + VROverlayError_PermissionDenied = 12, + VROverlayError_OverlayLimitExceeded = 13, // No more overlays could be created because the maximum number already exist + VROverlayError_WrongVisibilityType = 14, + VROverlayError_KeyTooLong = 15, + VROverlayError_NameTooLong = 16, + VROverlayError_KeyInUse = 17, + VROverlayError_WrongTransformType = 18, + VROverlayError_InvalidTrackedDevice = 19, + VROverlayError_InvalidParameter = 20, + VROverlayError_ThumbnailCantBeDestroyed = 21, + VROverlayError_ArrayTooSmall = 22, + VROverlayError_RequestFailed = 23, + VROverlayError_InvalidTexture = 24, + VROverlayError_UnableToLoadFile = 25, + VROverlayError_KeyboardAlreadyInUse = 26, + VROverlayError_NoNeighbor = 27, + VROverlayError_TooManyMaskPrimitives = 29, + VROverlayError_BadMaskPrimitive = 30, + VROverlayError_TextureAlreadyLocked = 31, + VROverlayError_TextureLockCapacityReached = 32, + VROverlayError_TextureNotLocked = 33, + VROverlayError_TimedOut = 34, +}; + +/** enum values to pass in to VR_Init to identify whether the application will +* draw a 3D scene. */ +enum EVRApplicationType +{ + VRApplication_Other = 0, // Some other kind of application that isn't covered by the other entries + VRApplication_Scene = 1, // Application will submit 3D frames + VRApplication_Overlay = 2, // Application only interacts with overlays + VRApplication_Background = 3, // Application should not start SteamVR if it's not already running, and should not + // keep it running if everything else quits. + VRApplication_Utility = 4, // Init should not try to load any drivers. The application needs access to utility + // interfaces (like IVRSettings and IVRApplications) but not hardware. + VRApplication_VRMonitor = 5, // Reserved for vrmonitor + VRApplication_SteamWatchdog = 6,// Reserved for Steam + VRApplication_Bootstrapper = 7, // reserved for vrstartup + VRApplication_WebHelper = 8, // reserved for vrwebhelper + VRApplication_OpenXRInstance = 9, // reserved for openxr (created instance, but not session yet) + VRApplication_OpenXRScene = 10, // reserved for openxr (started session) + VRApplication_OpenXROverlay = 11, // reserved for openxr (started overlay session) + VRApplication_Prism = 12, // reserved for the vrprismhost process + VRApplication_RoomView = 13, // reserved for the RoomView process + + VRApplication_Max +}; + + +/** returns true if the specified application type is one of the +* OpenXR types */ +inline bool IsOpenXRAppType( EVRApplicationType eType ) +{ + return eType == VRApplication_OpenXRInstance + || eType == VRApplication_OpenXRScene + || eType == VRApplication_OpenXROverlay; +} + + +/** returns true if the specified application type submits eye buffers */ +inline bool BAppTypeSubmitsEyeBuffers( EVRApplicationType eType ) +{ + return eType == VRApplication_Scene + || eType == VRApplication_OpenXRScene + || eType == VRApplication_RoomView; +} + + +/** error codes for firmware */ +enum EVRFirmwareError +{ + VRFirmwareError_None = 0, + VRFirmwareError_Success = 1, + VRFirmwareError_Fail = 2, +}; + + +/** error codes for notifications */ +enum EVRNotificationError +{ + VRNotificationError_OK = 0, + VRNotificationError_InvalidNotificationId = 100, + VRNotificationError_NotificationQueueFull = 101, + VRNotificationError_InvalidOverlayHandle = 102, + VRNotificationError_SystemWithUserValueAlreadyExists = 103, + VRNotificationError_ServiceUnavailable = 104, +}; + + +enum EVRSkeletalMotionRange +{ + // The range of motion of the skeleton takes into account any physical limits imposed by + // the controller itself. This will tend to be the most accurate pose compared to the user's + // actual hand pose, but might not allow a closed fist for example + VRSkeletalMotionRange_WithController = 0, + + // Retarget the range of motion provided by the input device to make the hand appear to move + // as if it was not holding a controller. eg: map "hand grasping controller" to "closed fist" + VRSkeletalMotionRange_WithoutController = 1, +}; + +enum EVRSkeletalTrackingLevel +{ + // body part location can't be directly determined by the device. Any skeletal pose provided by + // the device is estimated by assuming the position required to active buttons, triggers, joysticks, + // or other input sensors. + // E.g. Vive Controller, Gamepad + VRSkeletalTracking_Estimated = 0, + + // body part location can be measured directly but with fewer degrees of freedom than the actual body + // part. Certain body part positions may be unmeasured by the device and estimated from other input data. + // E.g. Index Controllers, gloves that only measure finger curl + VRSkeletalTracking_Partial = 1, + + // Body part location can be measured directly throughout the entire range of motion of the body part. + // E.g. Mocap suit for the full body, gloves that measure rotation of each finger segment + VRSkeletalTracking_Full = 2, + + VRSkeletalTrackingLevel_Count, + VRSkeletalTrackingLevel_Max = VRSkeletalTrackingLevel_Count - 1 +}; + + +/** Type used for referring to bones by their index */ +typedef int32_t BoneIndex_t; +const BoneIndex_t k_unInvalidBoneIndex = -1; + + +/** error codes returned by Vr_Init */ + +// Please add adequate error description to https://developer.valvesoftware.com/w/index.php?title=Category:SteamVRHelp +enum EVRInitError +{ + VRInitError_None = 0, + VRInitError_Unknown = 1, + + VRInitError_Init_InstallationNotFound = 100, + VRInitError_Init_InstallationCorrupt = 101, + VRInitError_Init_VRClientDLLNotFound = 102, + VRInitError_Init_FileNotFound = 103, + VRInitError_Init_FactoryNotFound = 104, + VRInitError_Init_InterfaceNotFound = 105, + VRInitError_Init_InvalidInterface = 106, + VRInitError_Init_UserConfigDirectoryInvalid = 107, + VRInitError_Init_HmdNotFound = 108, + VRInitError_Init_NotInitialized = 109, + VRInitError_Init_PathRegistryNotFound = 110, + VRInitError_Init_NoConfigPath = 111, + VRInitError_Init_NoLogPath = 112, + VRInitError_Init_PathRegistryNotWritable = 113, + VRInitError_Init_AppInfoInitFailed = 114, + VRInitError_Init_Retry = 115, // Used internally to cause retries to vrserver + VRInitError_Init_InitCanceledByUser = 116, // The calling application should silently exit. The user canceled app startup + VRInitError_Init_AnotherAppLaunching = 117, + VRInitError_Init_SettingsInitFailed = 118, + VRInitError_Init_ShuttingDown = 119, + VRInitError_Init_TooManyObjects = 120, + VRInitError_Init_NoServerForBackgroundApp = 121, + VRInitError_Init_NotSupportedWithCompositor = 122, + VRInitError_Init_NotAvailableToUtilityApps = 123, + VRInitError_Init_Internal = 124, + VRInitError_Init_HmdDriverIdIsNone = 125, + VRInitError_Init_HmdNotFoundPresenceFailed = 126, + VRInitError_Init_VRMonitorNotFound = 127, + VRInitError_Init_VRMonitorStartupFailed = 128, + VRInitError_Init_LowPowerWatchdogNotSupported = 129, + VRInitError_Init_InvalidApplicationType = 130, + VRInitError_Init_NotAvailableToWatchdogApps = 131, + VRInitError_Init_WatchdogDisabledInSettings = 132, + VRInitError_Init_VRDashboardNotFound = 133, + VRInitError_Init_VRDashboardStartupFailed = 134, + VRInitError_Init_VRHomeNotFound = 135, + VRInitError_Init_VRHomeStartupFailed = 136, + VRInitError_Init_RebootingBusy = 137, + VRInitError_Init_FirmwareUpdateBusy = 138, + VRInitError_Init_FirmwareRecoveryBusy = 139, + VRInitError_Init_USBServiceBusy = 140, + VRInitError_Init_VRWebHelperStartupFailed = 141, + VRInitError_Init_TrackerManagerInitFailed = 142, + VRInitError_Init_AlreadyRunning = 143, + VRInitError_Init_FailedForVrMonitor = 144, + VRInitError_Init_PropertyManagerInitFailed = 145, + VRInitError_Init_WebServerFailed = 146, + VRInitError_Init_IllegalTypeTransition = 147, + VRInitError_Init_MismatchedRuntimes = 148, + VRInitError_Init_InvalidProcessId = 149, + VRInitError_Init_VRServiceStartupFailed = 150, + VRInitError_Init_PrismNeedsNewDrivers = 151, + VRInitError_Init_PrismStartupTimedOut = 152, + VRInitError_Init_CouldNotStartPrism = 153, + VRInitError_Init_PrismClientInitFailed = 154, + VRInitError_Init_PrismClientStartFailed = 155, + VRInitError_Init_PrismExitedUnexpectedly = 156, + VRInitError_Init_BadLuid = 157, + VRInitError_Init_NoServerForAppContainer = 158, + VRInitError_Init_DuplicateBootstrapper = 159, + VRInitError_Init_VRDashboardServicePending = 160, + VRInitError_Init_VRDashboardServiceTimeout = 161, + VRInitError_Init_VRDashboardServiceStopped = 162, + VRInitError_Init_VRDashboardAlreadyStarted = 163, + VRInitError_Init_VRDashboardCopyFailed = 164, + VRInitError_Init_VRDashboardTokenFailure = 165, + VRInitError_Init_VRDashboardEnvironmentFailure = 166, + VRInitError_Init_VRDashboardPathFailure = 167, + VRInitError_Init_InstallationTooOld = 168, + VRInitError_Init_ClientVersionAlreadyProvided = 169, + + VRInitError_Driver_Failed = 200, + VRInitError_Driver_Unknown = 201, + VRInitError_Driver_HmdUnknown = 202, + VRInitError_Driver_NotLoaded = 203, + VRInitError_Driver_RuntimeOutOfDate = 204, + VRInitError_Driver_HmdInUse = 205, + VRInitError_Driver_NotCalibrated = 206, + VRInitError_Driver_CalibrationInvalid = 207, + VRInitError_Driver_HmdDisplayNotFound = 208, + VRInitError_Driver_TrackedDeviceInterfaceUnknown = 209, + // VRInitError_Driver_HmdDisplayNotFoundAfterFix = 210, // not needed: here for historic reasons + VRInitError_Driver_HmdDriverIdOutOfBounds = 211, + VRInitError_Driver_HmdDisplayMirrored = 212, + VRInitError_Driver_HmdDisplayNotFoundLaptop = 213, + VRInitError_Driver_PeerDriverNotInstalled = 214, + VRInitError_Driver_WirelessHmdNotConnected = 215, + + // Never make error 259 because we return it from main and it would conflict with STILL_ACTIVE + + VRInitError_IPC_ServerInitFailed = 300, + VRInitError_IPC_ConnectFailed = 301, + VRInitError_IPC_SharedStateInitFailed = 302, + VRInitError_IPC_CompositorInitFailed = 303, + VRInitError_IPC_MutexInitFailed = 304, + VRInitError_IPC_Failed = 305, + VRInitError_IPC_CompositorConnectFailed = 306, + VRInitError_IPC_CompositorInvalidConnectResponse = 307, + VRInitError_IPC_ConnectFailedAfterMultipleAttempts = 308, + VRInitError_IPC_ConnectFailedAfterTargetExited = 309, + VRInitError_IPC_NamespaceUnavailable = 310, + + VRInitError_Compositor_Failed = 400, + VRInitError_Compositor_D3D11HardwareRequired = 401, + VRInitError_Compositor_FirmwareRequiresUpdate = 402, + VRInitError_Compositor_OverlayInitFailed = 403, + VRInitError_Compositor_ScreenshotsInitFailed = 404, + VRInitError_Compositor_UnableToCreateDevice = 405, + VRInitError_Compositor_SharedStateIsNull = 406, + VRInitError_Compositor_NotificationManagerIsNull = 407, + VRInitError_Compositor_ResourceManagerClientIsNull = 408, + VRInitError_Compositor_MessageOverlaySharedStateInitFailure = 409, + VRInitError_Compositor_PropertiesInterfaceIsNull = 410, + VRInitError_Compositor_CreateFullscreenWindowFailed = 411, + VRInitError_Compositor_SettingsInterfaceIsNull = 412, + VRInitError_Compositor_FailedToShowWindow = 413, + VRInitError_Compositor_DistortInterfaceIsNull = 414, + VRInitError_Compositor_DisplayFrequencyFailure = 415, + VRInitError_Compositor_RendererInitializationFailed = 416, + VRInitError_Compositor_DXGIFactoryInterfaceIsNull = 417, + VRInitError_Compositor_DXGIFactoryCreateFailed = 418, + VRInitError_Compositor_DXGIFactoryQueryFailed = 419, + VRInitError_Compositor_InvalidAdapterDesktop = 420, + VRInitError_Compositor_InvalidHmdAttachment = 421, + VRInitError_Compositor_InvalidOutputDesktop = 422, + VRInitError_Compositor_InvalidDeviceProvided = 423, + VRInitError_Compositor_D3D11RendererInitializationFailed = 424, + VRInitError_Compositor_FailedToFindDisplayMode = 425, + VRInitError_Compositor_FailedToCreateSwapChain = 426, + VRInitError_Compositor_FailedToGetBackBuffer = 427, + VRInitError_Compositor_FailedToCreateRenderTarget = 428, + VRInitError_Compositor_FailedToCreateDXGI2SwapChain = 429, + VRInitError_Compositor_FailedtoGetDXGI2BackBuffer = 430, + VRInitError_Compositor_FailedToCreateDXGI2RenderTarget = 431, + VRInitError_Compositor_FailedToGetDXGIDeviceInterface = 432, + VRInitError_Compositor_SelectDisplayMode = 433, + VRInitError_Compositor_FailedToCreateNvAPIRenderTargets = 434, + VRInitError_Compositor_NvAPISetDisplayMode = 435, + VRInitError_Compositor_FailedToCreateDirectModeDisplay = 436, + VRInitError_Compositor_InvalidHmdPropertyContainer = 437, + VRInitError_Compositor_UpdateDisplayFrequency = 438, + VRInitError_Compositor_CreateRasterizerState = 439, + VRInitError_Compositor_CreateWireframeRasterizerState = 440, + VRInitError_Compositor_CreateSamplerState = 441, + VRInitError_Compositor_CreateClampToBorderSamplerState = 442, + VRInitError_Compositor_CreateAnisoSamplerState = 443, + VRInitError_Compositor_CreateOverlaySamplerState = 444, + VRInitError_Compositor_CreatePanoramaSamplerState = 445, + VRInitError_Compositor_CreateFontSamplerState = 446, + VRInitError_Compositor_CreateNoBlendState = 447, + VRInitError_Compositor_CreateBlendState = 448, + VRInitError_Compositor_CreateAlphaBlendState = 449, + VRInitError_Compositor_CreateBlendStateMaskR = 450, + VRInitError_Compositor_CreateBlendStateMaskG = 451, + VRInitError_Compositor_CreateBlendStateMaskB = 452, + VRInitError_Compositor_CreateDepthStencilState = 453, + VRInitError_Compositor_CreateDepthStencilStateNoWrite = 454, + VRInitError_Compositor_CreateDepthStencilStateNoDepth = 455, + VRInitError_Compositor_CreateFlushTexture = 456, + VRInitError_Compositor_CreateDistortionSurfaces = 457, + VRInitError_Compositor_CreateConstantBuffer = 458, + VRInitError_Compositor_CreateHmdPoseConstantBuffer = 459, + VRInitError_Compositor_CreateHmdPoseStagingConstantBuffer = 460, + VRInitError_Compositor_CreateSharedFrameInfoConstantBuffer = 461, + VRInitError_Compositor_CreateOverlayConstantBuffer = 462, + VRInitError_Compositor_CreateSceneTextureIndexConstantBuffer = 463, + VRInitError_Compositor_CreateReadableSceneTextureIndexConstantBuffer = 464, + VRInitError_Compositor_CreateLayerGraphicsTextureIndexConstantBuffer = 465, + VRInitError_Compositor_CreateLayerComputeTextureIndexConstantBuffer = 466, + VRInitError_Compositor_CreateLayerComputeSceneTextureIndexConstantBuffer = 467, + VRInitError_Compositor_CreateComputeHmdPoseConstantBuffer = 468, + VRInitError_Compositor_CreateGeomConstantBuffer = 469, + VRInitError_Compositor_CreatePanelMaskConstantBuffer = 470, + VRInitError_Compositor_CreatePixelSimUBO = 471, + VRInitError_Compositor_CreateMSAARenderTextures = 472, + VRInitError_Compositor_CreateResolveRenderTextures = 473, + VRInitError_Compositor_CreateComputeResolveRenderTextures = 474, + VRInitError_Compositor_CreateDriverDirectModeResolveTextures = 475, + VRInitError_Compositor_OpenDriverDirectModeResolveTextures = 476, + VRInitError_Compositor_CreateFallbackSyncTexture = 477, + VRInitError_Compositor_ShareFallbackSyncTexture = 478, + VRInitError_Compositor_CreateOverlayIndexBuffer = 479, + VRInitError_Compositor_CreateOverlayVertexBuffer = 480, + VRInitError_Compositor_CreateTextVertexBuffer = 481, + VRInitError_Compositor_CreateTextIndexBuffer = 482, + VRInitError_Compositor_CreateMirrorTextures = 483, + VRInitError_Compositor_CreateLastFrameRenderTexture = 484, + VRInitError_Compositor_CreateMirrorOverlay = 485, + VRInitError_Compositor_FailedToCreateVirtualDisplayBackbuffer = 486, + VRInitError_Compositor_DisplayModeNotSupported = 487, + VRInitError_Compositor_CreateOverlayInvalidCall = 488, + VRInitError_Compositor_CreateOverlayAlreadyInitialized = 489, + VRInitError_Compositor_FailedToCreateMailbox = 490, + VRInitError_Compositor_WindowInterfaceIsNull = 491, + VRInitError_Compositor_SystemLayerCreateInstance = 492, + VRInitError_Compositor_SystemLayerCreateSession = 493, + VRInitError_Compositor_CreateInverseDistortUVs = 494, + VRInitError_Compositor_CreateBackbufferDepth = 495, + VRInitError_Compositor_CannotDRMLeaseDisplay = 496, + VRInitError_Compositor_CannotConnectToDisplayServer = 497, + VRInitError_Compositor_GnomeNoDRMLeasing = 498, + VRInitError_Compositor_FailedToInitializeEncoder = 499, + VRInitError_Compositor_CreateBlurTexture = 500, + + VRInitError_VendorSpecific_UnableToConnectToOculusRuntime = 1000, + VRInitError_VendorSpecific_WindowsNotInDevMode = 1001, + VRInitError_VendorSpecific_OculusLinkNotEnabled = 1002, + + VRInitError_VendorSpecific_HmdFound_CantOpenDevice = 1101, + VRInitError_VendorSpecific_HmdFound_UnableToRequestConfigStart = 1102, + VRInitError_VendorSpecific_HmdFound_NoStoredConfig = 1103, + VRInitError_VendorSpecific_HmdFound_ConfigTooBig = 1104, + VRInitError_VendorSpecific_HmdFound_ConfigTooSmall = 1105, + VRInitError_VendorSpecific_HmdFound_UnableToInitZLib = 1106, + VRInitError_VendorSpecific_HmdFound_CantReadFirmwareVersion = 1107, + VRInitError_VendorSpecific_HmdFound_UnableToSendUserDataStart = 1108, + VRInitError_VendorSpecific_HmdFound_UnableToGetUserDataStart = 1109, + VRInitError_VendorSpecific_HmdFound_UnableToGetUserDataNext = 1110, + VRInitError_VendorSpecific_HmdFound_UserDataAddressRange = 1111, + VRInitError_VendorSpecific_HmdFound_UserDataError = 1112, + VRInitError_VendorSpecific_HmdFound_ConfigFailedSanityCheck = 1113, + VRInitError_VendorSpecific_OculusRuntimeBadInstall = 1114, + VRInitError_VendorSpecific_HmdFound_UnexpectedConfiguration_1 = 1115, + + VRInitError_VendorSpecific_Oasis_UnlockRequired = 1150, + + VRInitError_VendorSpecific_VRLink_OutdatedDriverMESA = 1200, + VRInitError_VendorSpecific_VRLink_OutdatedDriverNVIDIA = 1201, + VRInitError_VendorSpecific_VRLink_NoVideoSupport = 1202, + + VRInitError_Steam_SteamInstallationNotFound = 2000, + + // Strictly a placeholder + VRInitError_LastError +}; + +enum EVRScreenshotType +{ + VRScreenshotType_None = 0, + VRScreenshotType_Mono = 1, // left eye only + VRScreenshotType_Stereo = 2, + VRScreenshotType_Cubemap = 3, + VRScreenshotType_MonoPanorama = 4, + VRScreenshotType_StereoPanorama = 5 +}; + +enum EVRScreenshotPropertyFilenames +{ + VRScreenshotPropertyFilenames_Preview = 0, + VRScreenshotPropertyFilenames_VR = 1, +}; + +enum EVRTrackedCameraError +{ + VRTrackedCameraError_None = 0, + VRTrackedCameraError_OperationFailed = 100, + VRTrackedCameraError_InvalidHandle = 101, + VRTrackedCameraError_InvalidFrameHeaderVersion = 102, + VRTrackedCameraError_OutOfHandles = 103, + VRTrackedCameraError_IPCFailure = 104, + VRTrackedCameraError_NotSupportedForThisDevice = 105, + VRTrackedCameraError_SharedMemoryFailure = 106, + VRTrackedCameraError_FrameBufferingFailure = 107, + VRTrackedCameraError_StreamSetupFailure = 108, + VRTrackedCameraError_InvalidGLTextureId = 109, + VRTrackedCameraError_InvalidSharedTextureHandle = 110, + VRTrackedCameraError_FailedToGetGLTextureId = 111, + VRTrackedCameraError_SharedTextureFailure = 112, + VRTrackedCameraError_NoFrameAvailable = 113, + VRTrackedCameraError_InvalidArgument = 114, + VRTrackedCameraError_InvalidFrameBufferSize = 115, +}; + +enum EVRTrackedCameraFrameLayout +{ + EVRTrackedCameraFrameLayout_Mono = 0x0001, + EVRTrackedCameraFrameLayout_Stereo = 0x0002, + EVRTrackedCameraFrameLayout_VerticalLayout = 0x0010, // Stereo frames are Top/Bottom (left/right) + EVRTrackedCameraFrameLayout_HorizontalLayout = 0x0020, // Stereo frames are Left/Right +}; + +enum EVRTrackedCameraFrameType +{ + VRTrackedCameraFrameType_Distorted = 0, // This is the camera video frame size in pixels, still distorted. + VRTrackedCameraFrameType_Undistorted, // In pixels, an undistorted inscribed rectangle region without invalid regions. This size is subject to changes shortly. + VRTrackedCameraFrameType_MaximumUndistorted, // In pixels, maximum undistorted with invalid regions. Non zero alpha component identifies valid regions. + MAX_CAMERA_FRAME_TYPES +}; + +enum EVRDistortionFunctionType +{ + VRDistortionFunctionType_None, + VRDistortionFunctionType_FTheta, + VRDistortionFunctionType_Extended_FTheta, + MAX_DISTORTION_FUNCTION_TYPES, +}; + +static const uint32_t k_unMaxDistortionFunctionParameters = 8; + +typedef uint64_t TrackedCameraHandle_t; +#define INVALID_TRACKED_CAMERA_HANDLE ((vr::TrackedCameraHandle_t)0) + +struct CameraVideoStreamFrameHeader_t +{ + EVRTrackedCameraFrameType eFrameType; + + uint32_t nWidth; + uint32_t nHeight; + uint32_t nBytesPerPixel; + + uint32_t nFrameSequence; + + TrackedDevicePose_t trackedDevicePose; + + uint64_t ulFrameExposureTime; // mid-point of the exposure of the image in host system ticks +}; + +// Screenshot types +typedef uint32_t ScreenshotHandle_t; + +static const uint32_t k_unScreenshotHandleInvalid = 0; + +/** Compositor frame timing reprojection flags. */ +const uint32_t VRCompositor_ReprojectionReason_Cpu = 0x01; +const uint32_t VRCompositor_ReprojectionReason_Gpu = 0x02; +const uint32_t VRCompositor_ReprojectionAsync = 0x04; // This flag indicates the async reprojection mode is active, + // but does not indicate if reprojection actually happened or not. + // Use the ReprojectionReason flags above to check if reprojection + // was actually applied (i.e. scene texture was reused). + // NumFramePresents > 1 also indicates the scene texture was reused, + // and also the number of times that it was presented in total. + +const uint32_t VRCompositor_ReprojectionMotion = 0x08; // This flag indicates whether or not motion smoothing was triggered for this frame + +const uint32_t VRCompositor_PredictionMask = 0xF0; // The runtime may predict more than one frame ahead if + // it detects the application is taking too long to render. These + // bits will contain the count of additional frames (normally zero). + // Use the VR_COMPOSITOR_ADDITIONAL_PREDICTED_FRAMES macro to read from + // the latest frame timing entry. + +const uint32_t VRCompositor_ThrottleMask = 0xF00; // Number of frames the compositor is throttling the application. + // Use the VR_COMPOSITOR_NUMBER_OF_THROTTLED_FRAMES macro to read from + // the latest frame timing entry. + +#define VR_COMPOSITOR_ADDITIONAL_PREDICTED_FRAMES( timing ) ( ( ( timing ).m_nReprojectionFlags & vr::VRCompositor_PredictionMask ) >> 4 ) +#define VR_COMPOSITOR_NUMBER_OF_THROTTLED_FRAMES( timing ) ( ( ( timing ).m_nReprojectionFlags & vr::VRCompositor_ThrottleMask ) >> 8 ) + +#if defined(__linux__) || defined(__APPLE__) +#pragma pack( push, 4 ) +#endif +/** Provides a single frame's timing information to the app */ +struct Compositor_FrameTiming +{ + uint32_t m_nSize; // Set to sizeof( Compositor_FrameTiming ) + uint32_t m_nFrameIndex; + uint32_t m_nNumFramePresents; // number of times this frame was presented + uint32_t m_nNumMisPresented; // number of times this frame was presented on a vsync other than it was originally predicted to + uint32_t m_nNumDroppedFrames; // number of additional times previous frame was scanned out + uint32_t m_nReprojectionFlags; + + /** Absolute time reference for comparing frames. This aligns with the vsync that running start is relative to. */ + double m_flSystemTimeInSeconds; + + /** These times may include work from other processes due to OS scheduling. + * The fewer packets of work these are broken up into, the less likely this will happen. + * GPU work can be broken up by calling Flush. This can sometimes be useful to get the GPU started + * processing that work earlier in the frame. */ + float m_flPreSubmitGpuMs; // time spent rendering the scene (gpu work submitted between WaitGetPoses and second Submit) + float m_flPostSubmitGpuMs; // additional time spent rendering by application (e.g. companion window) + float m_flTotalRenderGpuMs; // time between work submitted immediately after present (ideally vsync) until the end of compositor submitted work + float m_flCompositorRenderGpuMs; // time spend performing distortion correction, rendering chaperone, overlays, etc. + float m_flCompositorRenderCpuMs; // time spent on cpu submitting the above work for this frame + float m_flCompositorIdleCpuMs; // time spent waiting for running start (application could have used this much more time) + + /** Miscellaneous measured intervals. */ + float m_flClientFrameIntervalMs; // time between calls to WaitGetPoses + float m_flPresentCallCpuMs; // time blocked on call to present (usually 0.0, but can go long) + float m_flWaitForPresentCpuMs; // time spent spin-waiting for frame index to change (not near-zero indicates wait object failure) + float m_flSubmitFrameMs; // time spent in IVRCompositor::Submit (not near-zero indicates driver issue) + + /** The following are all relative to this frame's SystemTimeInSeconds */ + float m_flWaitGetPosesCalledMs; + float m_flNewPosesReadyMs; + float m_flNewFrameReadyMs; // second call to IVRCompositor::Submit + float m_flCompositorUpdateStartMs; + float m_flCompositorUpdateEndMs; + float m_flCompositorRenderStartMs; + + vr::TrackedDevicePose_t m_HmdPose; // pose used by app to render this frame + + uint32_t m_nNumVSyncsReadyForUse; + uint32_t m_nNumVSyncsToFirstView; + + float m_flTransferLatencyMs; +}; +#if defined(__linux__) || defined(__APPLE__) +#pragma pack( pop ) +#endif + +/** Provides compositor benchmark results to the app */ +struct Compositor_BenchmarkResults +{ + float m_flMegaPixelsPerSecond; // Measurement of GPU MP/s performed by compositor benchmark + float m_flHmdRecommendedMegaPixelsPerSecond; // Recommended default MP/s given the HMD resolution, refresh, and panel mask. +}; + +/** Frame timing data provided by direct mode drivers. */ +struct DriverDirectMode_FrameTiming +{ + uint32_t m_nSize; // Set to sizeof( DriverDirectMode_FrameTiming ) + uint32_t m_nNumFramePresents; // number of times frame was presented + uint32_t m_nNumMisPresented; // number of times frame was presented on a vsync other than it was originally predicted to + uint32_t m_nNumDroppedFrames; // number of additional times previous frame was scanned out (i.e. compositor missed vsync) + uint32_t m_nReprojectionFlags; +}; + +/** These flags will be set on DriverDirectMode_FrameTiming::m_nReprojectionFlags when IVRDriverDirectModeComponent::GetFrameTiming is called for drivers to optionally respond to. */ +const uint32_t VRCompositor_ReprojectionMotion_Enabled = 0x100; // Motion Smoothing is enabled in the UI for the currently running application +const uint32_t VRCompositor_ReprojectionMotion_ForcedOn = 0x200; // Motion Smoothing is forced on in the UI for the currently running application +const uint32_t VRCompositor_ReprojectionMotion_AppThrottled = 0x400; // Application is requesting throttling via ForceInterleavedReprojectionOn + + +enum EVSync +{ + VSync_None, + VSync_WaitRender, // block following render work until vsync + VSync_NoWaitRender, // do not block following render work (allow to get started early) +}; + +enum EVRMuraCorrectionMode +{ + EVRMuraCorrectionMode_Default = 0, + EVRMuraCorrectionMode_NoCorrection +}; + +/** raw IMU data provided by IVRIOBuffer from paths to tracked devices with IMUs */ +enum Imu_OffScaleFlags +{ + OffScale_AccelX = 0x01, + OffScale_AccelY = 0x02, + OffScale_AccelZ = 0x04, + OffScale_GyroX = 0x08, + OffScale_GyroY = 0x10, + OffScale_GyroZ = 0x20, +}; + +struct ImuSample_t +{ + double fSampleTime; + HmdVector3d_t vAccel; + HmdVector3d_t vGyro; + uint32_t unOffScaleFlags; +}; + +enum class EVRDistortionChannel : uint32_t +{ + Red = 0, // given a coordinate in distorted panel space, returns the coordinate to sample in rectilinear render space for the red channel + Green, // given a coordinate in distorted panel space, returns the coordinate to sample in rectilinear render space for the green channel + Blue, // given a coordinate in distorted panel space, returns the coordinate to sample in rectilinear render space for the blue channel + InverseRed, // given a coordinate in rectilinear render space, returns the corresponding coordinate in distorted panel space for the red channel + InverseGreen, // given a coordinate in rectilinear render space, returns the corresponding coordinate in distorted panel space for the green channel + InverseBlue, // given a coordinate in rectilinear render space, returns the corresponding coordinate in distorted panel space for the blue channel + Count +}; + +struct DistortionCoordinate_t +{ + float u, v; // 0..1 +}; + +#pragma pack( pop ) + +// figure out how to import from the VR API dll +#if defined(_WIN32) + + #if !defined(OPENVR_BUILD_STATIC) + #ifdef VR_API_EXPORT + #define VR_INTERFACE extern "C" __declspec( dllexport ) + #else + #define VR_INTERFACE extern "C" __declspec( dllimport ) + #endif + #else + #define VR_INTERFACE extern "C" + #endif + +#elif defined(__GNUC__) || defined(COMPILER_GCC) || defined(__APPLE__) + +#ifdef VR_API_EXPORT + #define VR_INTERFACE extern "C" __attribute__((visibility("default"))) +#else + #define VR_INTERFACE extern "C" +#endif + +#else + #error "Unsupported Platform." +#endif + + +#if defined( _WIN32 ) + #define VR_CALLTYPE __cdecl +#else + #define VR_CALLTYPE +#endif + +} // namespace vr +#endif // _INCLUDE_VRTYPES_H + +// vrannotation.h + +#ifdef API_GEN +# define VR_CLANG_ATTR(ATTR) __attribute__((annotate( ATTR ))) +#else +# define VR_CLANG_ATTR(ATTR) +#endif + +#define VR_METHOD_DESC(DESC) VR_CLANG_ATTR( "desc:" #DESC ";" ) +#define VR_IGNOREATTR() VR_CLANG_ATTR( "ignore" ) +#define VR_OUT_STRUCT() VR_CLANG_ATTR( "out_struct: ;" ) +#define VR_OUT_STRING() VR_CLANG_ATTR( "out_string: ;" ) +#define VR_OUT_ARRAY_CALL(COUNTER,FUNCTION,PARAMS) VR_CLANG_ATTR( "out_array_call:" #COUNTER "," #FUNCTION "," #PARAMS ";" ) +#define VR_OUT_ARRAY_COUNT(COUNTER) VR_CLANG_ATTR( "out_array_count:" #COUNTER ";" ) +#define VR_ARRAY_COUNT(COUNTER) VR_CLANG_ATTR( "array_count:" #COUNTER ";" ) +#define VR_ARRAY_COUNT_D(COUNTER, DESC) VR_CLANG_ATTR( "array_count:" #COUNTER ";desc:" #DESC ) +#define VR_BUFFER_COUNT(COUNTER) VR_CLANG_ATTR( "buffer_count:" #COUNTER ";" ) +#define VR_OUT_BUFFER_COUNT(COUNTER) VR_CLANG_ATTR( "out_buffer_count:" #COUNTER ";" ) +#define VR_OUT_STRING_COUNT(COUNTER) VR_CLANG_ATTR( "out_string_count:" #COUNTER ";" ) + +// vrtrackedcameratypes.h + +#ifndef _VRTRACKEDCAMERATYPES_H +#define _VRTRACKEDCAMERATYPES_H + +namespace vr +{ + +#pragma pack( push, 8 ) + +enum ECameraVideoStreamFormat +{ + CVS_FORMAT_UNKNOWN = 0, + CVS_FORMAT_RAW10 = 1, // 10 bits per pixel + CVS_FORMAT_NV12 = 2, // 12 bits per pixel + CVS_FORMAT_RGB24 = 3, // 24 bits per pixel + CVS_FORMAT_NV12_2 = 4, // 12 bits per pixel, 2x height + CVS_FORMAT_YUYV16 = 5, // 16 bits per pixel + CVS_FORMAT_BAYER16BG = 6, // 16 bits per pixel, 10-bit BG-format Bayer, see https://docs.opencv.org/3.1.0/de/d25/imgproc_color_conversions.html + CVS_FORMAT_MJPEG = 7, // variable-sized MJPEG Open DML format, see https://www.loc.gov/preservation/digital/formats/fdd/fdd000063.shtml + CVS_FORMAT_RGBX32 = 8, // Full-sized pixels, 4BPP, LSB = RED + CVS_MAX_FORMATS +}; + +enum ECameraCompatibilityMode +{ + CAMERA_COMPAT_MODE_BULK_DEFAULT = 0, + CAMERA_COMPAT_MODE_BULK_64K_DMA = 1, + CAMERA_COMPAT_MODE_BULK_16K_DMA = 2, + CAMERA_COMPAT_MODE_BULK_8K_DMA = 3, + CAMERA_COMPAT_MODE_ISO_52FPS = 4, + CAMERA_COMPAT_MODE_ISO_50FPS = 5, + CAMERA_COMPAT_MODE_ISO_48FPS = 6, + CAMERA_COMPAT_MODE_ISO_46FPS = 7, + CAMERA_COMPAT_MODE_ISO_44FPS = 8, + CAMERA_COMPAT_MODE_ISO_42FPS = 9, + CAMERA_COMPAT_MODE_ISO_40FPS = 10, + CAMERA_COMPAT_MODE_ISO_35FPS = 11, + CAMERA_COMPAT_MODE_ISO_30FPS = 12, + CAMERA_COMPAT_MODE_ISO_15FPS = 13, + MAX_CAMERA_COMPAT_MODES +}; + +enum ECameraRoomViewStyle +{ + CAMERA_ROOMVIEW_STYLE_DEFAULT = 0, + CAMERA_ROOMVIEW_STYLE_EDGE_A = 1, + CAMERA_ROOMVIEW_STYLE_EDGE_B = 2, + CAMERA_ROOMVIEW_STYLE_VIDEO_TRANSLUSCENT = 3, + CAMERA_ROOMVIEW_STYLE_VIDEO_OPAQUE = 4, + CAMERA_ROOMVIEW_STYLE_COUNT = 5, +}; + +#ifdef _MSC_VER +#define VR_CAMERA_DECL_ALIGN( x ) __declspec( align( x ) ) +#else +#define VR_CAMERA_DECL_ALIGN( x ) // +#endif + +static const uint32_t k_unMaxCameras = 4; +static const uint32_t k_unMaxCameraFrameSharedHandles = 4; + +VR_CAMERA_DECL_ALIGN( 8 ) struct CameraVideoStreamFrame_t +{ + ECameraVideoStreamFormat m_nStreamFormat; + + uint32_t m_nWidth; + uint32_t m_nHeight; + + uint32_t m_nImageDataSize; // Based on stream format, width, height + + uint32_t m_nFrameSequence; // Starts from 0 when stream starts. + + uint32_t m_nBufferIndex; // Identifies which buffer the image data is hosted + uint32_t m_nBufferCount; // Total number of configured buffers + + uint32_t m_nExposureTime; + + uint32_t m_nISPFrameTimeStamp; // Driver provided time stamp per driver centric time base + uint32_t m_nISPReferenceTimeStamp; + uint32_t m_nSyncCounter; + + uint32_t m_nCamSyncEvents; + uint32_t m_nISPSyncEvents; + + double m_flReferenceCamSyncTime; + + double m_flFrameElapsedTime; // Starts from 0 when stream starts. In seconds. + double m_flFrameDeliveryRate; + + double m_flFrameCaptureTime_DriverAbsolute; // In USB time, via AuxEvent + double m_flFrameCaptureTime_ServerRelative; // In System time within the server + uint64_t m_nFrameCaptureTicks_ServerAbsolute; // In system ticks within the server + double m_flFrameCaptureTime_ClientRelative; // At the client, relative to when the frame was exposed/captured. + + double m_flSyncMarkerError; + + TrackedDevicePose_t m_RawTrackedDevicePose; // Raw-and-uncalibrated pose, supplied by HMD layer when used as a tracked camera + + uint64_t m_pImageData; +}; + +#pragma pack( pop ) + +} + +#endif // _VRTRACKEDCAMERATYPES_H + +// ivrsettings.h + +#ifndef OPENVR_NO_STL +#include +#endif + +namespace vr +{ + enum EVRSettingsError + { + VRSettingsError_None = 0, + VRSettingsError_IPCFailed = 1, + VRSettingsError_WriteFailed = 2, + VRSettingsError_ReadFailed = 3, + VRSettingsError_JsonParseFailed = 4, + VRSettingsError_UnsetSettingHasNoDefault = 5, // This will be returned if the setting does not appear in the appropriate default file and has not been set + VRSettingsError_AccessDenied = 6, + }; + + // The maximum length of a settings key + static const uint32_t k_unMaxSettingsKeyLength = 128; + + class IVRSettings + { + public: + virtual const char *GetSettingsErrorNameFromEnum( EVRSettingsError eError ) = 0; + + virtual void SetBool( const char *pchSection, const char *pchSettingsKey, bool bValue, EVRSettingsError *peError = nullptr ) = 0; + virtual void SetInt32( const char *pchSection, const char *pchSettingsKey, int32_t nValue, EVRSettingsError *peError = nullptr ) = 0; + virtual void SetFloat( const char *pchSection, const char *pchSettingsKey, float flValue, EVRSettingsError *peError = nullptr ) = 0; + virtual void SetString( const char *pchSection, const char *pchSettingsKey, const char *pchValue, EVRSettingsError *peError = nullptr ) = 0; + + // Users of the system need to provide a proper default in default.vrsettings in the resources/settings/ directory + // of either the runtime or the driver_xxx directory. Otherwise the default will be false, 0, 0.0 or "" + virtual bool GetBool( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr ) = 0; + virtual int32_t GetInt32( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr ) = 0; + virtual float GetFloat( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr ) = 0; + virtual void GetString( const char *pchSection, const char *pchSettingsKey, VR_OUT_STRING() char *pchValue, uint32_t unValueLen, EVRSettingsError *peError = nullptr ) = 0; + + virtual void RemoveSection( const char *pchSection, EVRSettingsError *peError = nullptr ) = 0; + virtual void RemoveKeyInSection( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr ) = 0; + }; + + //----------------------------------------------------------------------------- + static const char * const IVRSettings_Version = "IVRSettings_003"; + + class CVRSettingHelper + { + IVRSettings *m_pSettings; + public: + CVRSettingHelper( IVRSettings *pSettings ) + { + m_pSettings = pSettings; + } + + const char *GetSettingsErrorNameFromEnum( EVRSettingsError eError ) + { + return m_pSettings->GetSettingsErrorNameFromEnum( eError ); + } + + void SetBool( const char *pchSection, const char *pchSettingsKey, bool bValue, EVRSettingsError *peError = nullptr ) + { + m_pSettings->SetBool( pchSection, pchSettingsKey, bValue, peError ); + } + + void SetInt32( const char *pchSection, const char *pchSettingsKey, int32_t nValue, EVRSettingsError *peError = nullptr ) + { + m_pSettings->SetInt32( pchSection, pchSettingsKey, nValue, peError ); + } + void SetFloat( const char *pchSection, const char *pchSettingsKey, float flValue, EVRSettingsError *peError = nullptr ) + { + m_pSettings->SetFloat( pchSection, pchSettingsKey, flValue, peError ); + } + void SetString( const char *pchSection, const char *pchSettingsKey, const char *pchValue, EVRSettingsError *peError = nullptr ) + { + m_pSettings->SetString( pchSection, pchSettingsKey, pchValue, peError ); + } +#ifndef OPENVR_NO_STL + void SetString( const std::string & sSection, const std::string & sSettingsKey, const std::string & sValue, EVRSettingsError *peError = nullptr ) + { + m_pSettings->SetString( sSection.c_str(), sSettingsKey.c_str(), sValue.c_str(), peError ); + } +#endif + + bool GetBool( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr ) + { + return m_pSettings->GetBool( pchSection, pchSettingsKey, peError ); + } + int32_t GetInt32( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr ) + { + return m_pSettings->GetInt32( pchSection, pchSettingsKey, peError ); + } + float GetFloat( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr ) + { + return m_pSettings->GetFloat( pchSection, pchSettingsKey, peError ); + } + void GetString( const char *pchSection, const char *pchSettingsKey, VR_OUT_STRING() char *pchValue, uint32_t unValueLen, EVRSettingsError *peError = nullptr ) + { + m_pSettings->GetString( pchSection, pchSettingsKey, pchValue, unValueLen, peError ); + } +#ifndef OPENVR_NO_STL + std::string GetString( const std::string & sSection, const std::string & sSettingsKey, EVRSettingsError *peError = nullptr ) + { + char buf[4096]; + vr::EVRSettingsError eError; + m_pSettings->GetString( sSection.c_str(), sSettingsKey.c_str(), buf, sizeof( buf ), &eError ); + if ( peError ) + *peError = eError; + if ( eError == vr::VRSettingsError_None ) + return buf; + else + return ""; + } +#endif + + void RemoveSection( const char *pchSection, EVRSettingsError *peError = nullptr ) + { + m_pSettings->RemoveSection( pchSection, peError ); + } + void RemoveKeyInSection( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr ) + { + m_pSettings->RemoveKeyInSection( pchSection, pchSettingsKey, peError ); + } + }; + + + //----------------------------------------------------------------------------- + // steamvr keys + static const char * const k_pch_SteamVR_Section = "steamvr"; + static const char * const k_pch_SteamVR_Contrast_Float = "contrast"; + static const char * const k_pch_SteamVR_RequireHmd_String = "requireHmd"; + static const char * const k_pch_SteamVR_ForcedDriverKey_String = "forcedDriver"; + static const char * const k_pch_SteamVR_ForcedHmdKey_String = "forcedHmd"; + static const char * const k_pch_SteamVR_DisplayDebug_Bool = "displayDebug"; + static const char * const k_pch_SteamVR_DebugProcessPipe_String = "debugProcessPipe"; + static const char * const k_pch_SteamVR_DisplayDebugX_Int32 = "displayDebugX"; + static const char * const k_pch_SteamVR_DisplayDebugY_Int32 = "displayDebugY"; + static const char * const k_pch_SteamVR_SendSystemButtonToAllApps_Bool= "sendSystemButtonToAllApps"; + static const char * const k_pch_SteamVR_LogLevel_Int32 = "loglevel"; + static const char * const k_pch_SteamVR_IPD_Float = "ipd"; + static const char * const k_pch_SteamVR_Background_String = "background"; + static const char * const k_pch_SteamVR_BackgroundUseDomeProjection_Bool = "backgroundUseDomeProjection"; + static const char * const k_pch_SteamVR_BackgroundCameraHeight_Float = "backgroundCameraHeight"; + static const char * const k_pch_SteamVR_BackgroundDomeRadius_Float = "backgroundDomeRadius"; + static const char * const k_pch_SteamVR_GridColor_String = "gridColor"; + static const char * const k_pch_SteamVR_PlayAreaColor_String = "playAreaColor"; + static const char * const k_pch_SteamVR_TrackingLossColor_String = "trackingLossColor"; + static const char * const k_pch_SteamVR_StartColor_String = "startColor"; + static const char * const k_pch_SteamVR_ShowStage_Bool = "showStage"; + static const char * const k_pch_SteamVR_DrawTrackingReferences_Bool = "drawTrackingReferences"; + static const char * const k_pch_SteamVR_ActivateMultipleDrivers_Bool = "activateMultipleDrivers"; + static const char * const k_pch_SteamVR_UsingSpeakers_Bool = "usingSpeakers"; + static const char * const k_pch_SteamVR_SpeakersForwardYawOffsetDegrees_Float = "speakersForwardYawOffsetDegrees"; + static const char * const k_pch_SteamVR_BaseStationPowerManagement_Int32 = "basestationPowerManagement"; + static const char * const k_pch_SteamVR_ShowBaseStationPowerManagementTip_Int32 = "ShowBaseStationPowerManagementTip"; + static const char * const k_pch_SteamVR_NeverKillProcesses_Bool = "neverKillProcesses"; + static const char * const k_pch_SteamVR_SupersampleScale_Float = "supersampleScale"; + static const char * const k_pch_SteamVR_MaxRecommendedResolution_Int32 = "maxRecommendedResolution"; + static const char * const k_pch_SteamVR_MotionSmoothing_Bool = "motionSmoothing"; + static const char * const k_pch_SteamVR_MotionSmoothingOverride_Int32 = "motionSmoothingOverride"; + static const char * const k_pch_SteamVR_FoveatedSharpening_Bool = "sharpening"; + static const char * const k_pch_SteamVR_FoveatedSharpeningOverride_Int32 = "sharpeningOverride"; + static const char * const k_pch_SteamVR_FramesToThrottle_Int32 = "framesToThrottle"; + static const char * const k_pch_SteamVR_AdditionalFramesToPredict_Int32 = "additionalFramesToPredict"; + static const char * const k_pch_SteamVR_WorldScale_Float = "worldScale"; + static const char * const k_pch_SteamVR_FovScale_Int32 = "fovScale"; + static const char * const k_pch_SteamVR_FovScaleInner_Int32 = "fovScaleInner"; + static const char * const k_pch_SteamVR_FovScaleUpper_Int32 = "fovScaleUpper"; + static const char * const k_pch_SteamVR_FovScaleLower_Int32 = "fovScaleLower"; + static const char * const k_pch_SteamVR_FovScaleFormat_Int32 = "fovScaleFormat"; + static const char * const k_pch_SteamVR_FovScaleLetterboxed_Bool = "fovScaleLetterboxed"; + static const char * const k_pch_SteamVR_DisableAsyncReprojection_Bool = "disableAsync"; + static const char * const k_pch_SteamVR_ForceFadeOnBadTracking_Bool = "forceFadeOnBadTracking"; + static const char * const k_pch_SteamVR_DefaultMirrorView_Int32 = "mirrorView"; + static const char * const k_pch_SteamVR_ShowLegacyMirrorView_Bool = "showLegacyMirrorView"; + static const char * const k_pch_SteamVR_MirrorViewVisibility_Bool = "showMirrorView"; + static const char * const k_pch_SteamVR_MirrorViewDisplayMode_Int32 = "mirrorViewDisplayMode"; + static const char * const k_pch_SteamVR_MirrorViewEye_Int32 = "mirrorViewEye"; + static const char * const k_pch_SteamVR_MirrorViewGeometry_String = "mirrorViewGeometry"; + static const char * const k_pch_SteamVR_MirrorViewGeometryMaximized_String = "mirrorViewGeometryMaximized"; + static const char * const k_pch_SteamVR_PerfGraphVisibility_Bool = "showPerfGraph"; + static const char * const k_pch_SteamVR_StartMonitorFromAppLaunch = "startMonitorFromAppLaunch"; + static const char * const k_pch_SteamVR_StartCompositorFromAppLaunch_Bool = "startCompositorFromAppLaunch"; + static const char * const k_pch_SteamVR_StartDashboardFromAppLaunch_Bool = "startDashboardFromAppLaunch"; + static const char * const k_pch_SteamVR_StartOverlayAppsFromDashboard_Bool = "startOverlayAppsFromDashboard"; + static const char * const k_pch_SteamVR_EnableHomeApp = "enableHomeApp"; + static const char * const k_pch_SteamVR_CycleBackgroundImageTimeSec_Int32 = "CycleBackgroundImageTimeSec"; + static const char * const k_pch_SteamVR_RetailDemo_Bool = "retailDemo"; + static const char * const k_pch_SteamVR_IpdOffset_Float = "ipdOffset"; + static const char * const k_pch_SteamVR_AllowSupersampleFiltering_Bool = "allowSupersampleFiltering"; + static const char * const k_pch_SteamVR_SupersampleManualOverride_Bool = "supersampleManualOverride"; + static const char * const k_pch_SteamVR_EnableLinuxVulkanAsync_Bool = "enableLinuxVulkanAsync"; + static const char * const k_pch_SteamVR_AllowDisplayLockedMode_Bool = "allowDisplayLockedMode"; + static const char * const k_pch_SteamVR_HaveStartedTutorialForNativeChaperoneDriver_Bool = "haveStartedTutorialForNativeChaperoneDriver"; + static const char * const k_pch_SteamVR_DebugInputBinding = "debugInputBinding"; + static const char * const k_pch_SteamVR_DoNotFadeToGrid = "doNotFadeToGrid"; + static const char * const k_pch_SteamVR_EnableSharedResourceJournaling = "enableSharedResourceJournaling"; + static const char * const k_pch_SteamVR_EnableSafeMode = "enableSafeMode"; + static const char * const k_pch_SteamVR_PreferredRefreshRate = "preferredRefreshRate"; + static const char * const k_pch_SteamVR_LastVersionNotice = "lastVersionNotice"; + static const char * const k_pch_SteamVR_LastVersionNoticeDate = "lastVersionNoticeDate"; + static const char * const k_pch_SteamVR_HmdDisplayColorGainR_Float = "hmdDisplayColorGainR"; + static const char * const k_pch_SteamVR_HmdDisplayColorGainG_Float = "hmdDisplayColorGainG"; + static const char * const k_pch_SteamVR_HmdDisplayColorGainB_Float = "hmdDisplayColorGainB"; + static const char * const k_pch_SteamVR_CustomIconStyle_String = "customIconStyle"; + static const char * const k_pch_SteamVR_CustomOffIconStyle_String = "customOffIconStyle"; + static const char * const k_pch_SteamVR_CustomIconForceUpdate_String = "customIconForceUpdate"; + static const char * const k_pch_SteamVR_AllowGlobalActionSetPriority = "globalActionSetPriority"; + static const char * const k_pch_SteamVR_OverlayRenderQuality = "overlayRenderQuality_2"; + static const char * const k_pch_SteamVR_BlockOculusSDKOnOpenVRLaunchOption_Bool = "blockOculusSDKOnOpenVRLaunchOption"; + static const char * const k_pch_SteamVR_BlockOculusSDKOnAllLaunches_Bool = "blockOculusSDKOnAllLaunches"; + static const char * const k_pch_SteamVR_HDCPLegacyCompatibility_Bool = "hdcp14legacyCompatibility"; + static const char * const k_pch_SteamVR_DisplayPortTrainingMode_Int = "displayPortTrainingMode"; + static const char * const k_pch_SteamVR_UsePrism_Bool = "usePrism"; + static const char * const k_pch_SteamVR_AllowFallbackMirrorWindowLinux_Bool = "allowFallbackMirrorWindowLinux"; + static const char * const k_pch_SteamVR_DisableKeyboardPrivacy_Bool = "disableKeyboardPrivacy"; + + //----------------------------------------------------------------------------- + // openxr keys + static const char * const k_pch_OpenXR_Section = "openxr"; + static const char * const k_pch_OpenXR_MetaUnityPluginCompatibility_Int32 = "metaUnityPluginCompatibility"; + + //----------------------------------------------------------------------------- + // direct mode keys + static const char * const k_pch_DirectMode_Section = "direct_mode"; + static const char * const k_pch_DirectMode_Enable_Bool = "enable"; + static const char * const k_pch_DirectMode_Count_Int32 = "count"; + static const char * const k_pch_DirectMode_EdidVid_Int32 = "edidVid"; + static const char * const k_pch_DirectMode_EdidPid_Int32 = "edidPid"; + + //----------------------------------------------------------------------------- + // lighthouse keys + static const char * const k_pch_Lighthouse_Section = "driver_lighthouse"; + static const char * const k_pch_Lighthouse_DisableIMU_Bool = "disableimu"; + static const char * const k_pch_Lighthouse_DisableIMUExceptHMD_Bool = "disableimuexcepthmd"; + static const char * const k_pch_Lighthouse_UseDisambiguation_String = "usedisambiguation"; + static const char * const k_pch_Lighthouse_DisambiguationDebug_Int32 = "disambiguationdebug"; + static const char * const k_pch_Lighthouse_PrimaryBasestation_Int32 = "primarybasestation"; + static const char * const k_pch_Lighthouse_DBHistory_Bool = "dbhistory"; + static const char * const k_pch_Lighthouse_EnableBluetooth_Bool = "enableBluetooth"; + static const char * const k_pch_Lighthouse_PowerManagedBaseStations_String = "PowerManagedBaseStations"; + static const char * const k_pch_Lighthouse_PowerManagedBaseStations2_String = "PowerManagedBaseStations2"; + static const char * const k_pch_Lighthouse_InactivityTimeoutForBaseStations_Int32 = "InactivityTimeoutForBaseStations"; + static const char * const k_pch_Lighthouse_EnableImuFallback_Bool = "enableImuFallback"; + + //----------------------------------------------------------------------------- + // null keys + static const char * const k_pch_Null_Section = "driver_null"; + static const char * const k_pch_Null_SerialNumber_String = "serialNumber"; + static const char * const k_pch_Null_ModelNumber_String = "modelNumber"; + static const char * const k_pch_Null_WindowX_Int32 = "windowX"; + static const char * const k_pch_Null_WindowY_Int32 = "windowY"; + static const char * const k_pch_Null_WindowWidth_Int32 = "windowWidth"; + static const char * const k_pch_Null_WindowHeight_Int32 = "windowHeight"; + static const char * const k_pch_Null_RenderWidth_Int32 = "renderWidth"; + static const char * const k_pch_Null_RenderHeight_Int32 = "renderHeight"; + static const char * const k_pch_Null_SecondsFromVsyncToPhotons_Float = "secondsFromVsyncToPhotons"; + static const char * const k_pch_Null_DisplayFrequency_Float = "displayFrequency"; + + //----------------------------------------------------------------------------- + // Windows MR keys + static const char * const k_pch_WindowsMR_Section = "driver_holographic"; + + //----------------------------------------------------------------------------- + // user interface keys + static const char * const k_pch_UserInterface_Section = "userinterface"; + static const char * const k_pch_UserInterface_StatusAlwaysOnTop_Bool = "StatusAlwaysOnTop"; + static const char * const k_pch_UserInterface_MinimizeToTray_Bool = "MinimizeToTray"; + static const char * const k_pch_UserInterface_HidePopupsWhenStatusMinimized_Bool = "HidePopupsWhenStatusMinimized"; + static const char * const k_pch_UserInterface_Screenshots_Bool = "screenshots"; + static const char * const k_pch_UserInterface_ScreenshotType_Int = "screenshotType"; + static const char * const k_pch_UserInterface_CheckStatusInterval_Int = "vrmStatusCheckInterval"; + static const char * const k_pch_UserInterface_CheckForSteam_Bool = "vrmCheckForSteam"; + + //----------------------------------------------------------------------------- + // notification keys + static const char * const k_pch_Notifications_Section = "notifications"; + static const char * const k_pch_Notifications_DoNotDisturb_Bool = "DoNotDisturb"; + + //----------------------------------------------------------------------------- + // keyboard keys + static const char * const k_pch_Keyboard_Section = "keyboard"; + static const char * const k_pch_Keyboard_TutorialCompletions = "TutorialCompletions"; + static const char * const k_pch_Keyboard_ScaleX = "ScaleX"; + static const char * const k_pch_Keyboard_ScaleY = "ScaleY"; + static const char * const k_pch_Keyboard_OffsetLeftX = "OffsetLeftX"; + static const char * const k_pch_Keyboard_OffsetRightX = "OffsetRightX"; + static const char * const k_pch_Keyboard_OffsetY = "OffsetY"; + static const char * const k_pch_Keyboard_Smoothing = "Smoothing"; + + //----------------------------------------------------------------------------- + // perf keys + static const char * const k_pch_Perf_Section = "perfcheck"; + static const char * const k_pch_Perf_PerfGraphInHMD_Bool = "perfGraphInHMD"; + static const char * const k_pch_Perf_AllowTimingStore_Bool = "allowTimingStore"; + static const char * const k_pch_Perf_SaveTimingsOnExit_Bool = "saveTimingsOnExit"; + static const char * const k_pch_Perf_TestData_Float = "perfTestData"; + static const char * const k_pch_Perf_GPUProfiling_Bool = "GPUProfiling"; + static const char * const k_pch_Perf_GpuBusMonitoring_Bool = "gpuBusMonitoring"; + + //----------------------------------------------------------------------------- + // collision bounds keys + static const char * const k_pch_CollisionBounds_Section = "collisionBounds"; + static const char * const k_pch_CollisionBounds_Style_Int32 = "CollisionBoundsStyle"; + static const char * const k_pch_CollisionBounds_GroundPerimeterOn_Bool = "CollisionBoundsGroundPerimeterOn"; + static const char * const k_pch_CollisionBounds_CenterMarkerOn_Bool = "CollisionBoundsCenterMarkerOn"; + static const char * const k_pch_CollisionBounds_PlaySpaceOn_Bool = "CollisionBoundsPlaySpaceOn"; + static const char * const k_pch_CollisionBounds_FadeDistance_Float = "CollisionBoundsFadeDistance"; + static const char * const k_pch_CollisionBounds_WallHeight_Float = "CollisionBoundsWallHeight"; + static const char * const k_pch_CollisionBounds_ColorGammaR_Int32 = "CollisionBoundsColorGammaR"; + static const char * const k_pch_CollisionBounds_ColorGammaG_Int32 = "CollisionBoundsColorGammaG"; + static const char * const k_pch_CollisionBounds_ColorGammaB_Int32 = "CollisionBoundsColorGammaB"; + static const char * const k_pch_CollisionBounds_ColorGammaA_Int32 = "CollisionBoundsColorGammaA"; + static const char * const k_pch_CollisionBounds_EnableDriverImport = "enableDriverBoundsImport"; + + //----------------------------------------------------------------------------- + // camera keys + static const char * const k_pch_Camera_Section = "camera"; + static const char * const k_pch_Camera_EnableCamera_Bool = "enableCamera"; + static const char * const k_pch_Camera_ShowOnController_Bool = "showOnController"; + static const char * const k_pch_Camera_EnableCameraForCollisionBounds_Bool = "enableCameraForCollisionBounds"; + static const char * const k_pch_Camera_RoomView_Int32 = "roomView"; + static const char * const k_pch_Camera_BoundsColorGammaR_Int32 = "cameraBoundsColorGammaR"; + static const char * const k_pch_Camera_BoundsColorGammaG_Int32 = "cameraBoundsColorGammaG"; + static const char * const k_pch_Camera_BoundsColorGammaB_Int32 = "cameraBoundsColorGammaB"; + static const char * const k_pch_Camera_BoundsColorGammaA_Int32 = "cameraBoundsColorGammaA"; + static const char * const k_pch_Camera_BoundsStrength_Int32 = "cameraBoundsStrength"; + static const char * const k_pch_Camera_RoomViewStyle_Int32 = "roomViewStyle"; + + //----------------------------------------------------------------------------- + // audio keys + static const char * const k_pch_audio_Section = "audio"; + static const char * const k_pch_audio_SetOsDefaultPlaybackDevice_Bool = "setOsDefaultPlaybackDevice"; + static const char * const k_pch_audio_EnablePlaybackDeviceOverride_Bool = "enablePlaybackDeviceOverride"; + static const char * const k_pch_audio_PlaybackDeviceOverride_String = "playbackDeviceOverride"; + static const char * const k_pch_audio_PlaybackDeviceOverrideName_String = "playbackDeviceOverrideName"; + static const char * const k_pch_audio_SetOsDefaultRecordingDevice_Bool = "setOsDefaultRecordingDevice"; + static const char * const k_pch_audio_EnableRecordingDeviceOverride_Bool = "enableRecordingDeviceOverride"; + static const char * const k_pch_audio_RecordingDeviceOverride_String = "recordingDeviceOverride"; + static const char * const k_pch_audio_RecordingDeviceOverrideName_String = "recordingDeviceOverrideName"; + static const char * const k_pch_audio_EnablePlaybackMirror_Bool = "enablePlaybackMirror"; + static const char * const k_pch_audio_PlaybackMirrorDevice_String = "playbackMirrorDevice"; + static const char * const k_pch_audio_PlaybackMirrorDeviceName_String = "playbackMirrorDeviceName"; + static const char * const k_pch_audio_OldPlaybackMirrorDevice_String = "onPlaybackMirrorDevice"; + static const char * const k_pch_audio_ActiveMirrorDevice_String = "activePlaybackMirrorDevice"; + static const char * const k_pch_audio_EnablePlaybackMirrorIndependentVolume_Bool = "enablePlaybackMirrorIndependentVolume"; + static const char * const k_pch_audio_LastHmdPlaybackDeviceId_String = "lastHmdPlaybackDeviceId"; + static const char * const k_pch_audio_VIVEHDMIGain = "viveHDMIGain"; + static const char * const k_pch_audio_DualSpeakerAndJackOutput_Bool = "dualSpeakerAndJackOutput"; + static const char * const k_pch_audio_MuteMicMonitor_Bool = "muteMicMonitor"; + + //----------------------------------------------------------------------------- + // power management keys + static const char * const k_pch_Power_Section = "power"; + static const char * const k_pch_Power_PowerOffOnExit_Bool = "powerOffOnExit"; + static const char * const k_pch_Power_TurnOffScreensTimeout_Float = "turnOffScreensTimeout"; + static const char * const k_pch_Power_TurnOffControllersTimeout_Float = "turnOffControllersTimeout"; + static const char * const k_pch_Power_ReturnToWatchdogTimeout_Float = "returnToWatchdogTimeout"; + static const char * const k_pch_Power_AutoLaunchSteamVROnButtonPress = "autoLaunchSteamVROnButtonPress"; + static const char * const k_pch_Power_PauseCompositorOnStandby_Bool = "pauseCompositorOnStandby"; + static const char * const k_pch_Power_OverrideWindowsPowerScheme_Bool = "overrideWindowsPowerScheme"; + + //----------------------------------------------------------------------------- + // dashboard keys + static const char * const k_pch_Dashboard_Section = "dashboard"; + static const char * const k_pch_Dashboard_EnableDashboard_Bool = "enableDashboard"; + static const char * const k_pch_Dashboard_ArcadeMode_Bool = "arcadeMode"; + static const char * const k_pch_Dashboard_Position = "position"; + static const char * const k_pch_Dashboard_DashboardScale = "dashboardScale"; + static const char * const k_pch_Dashboard_UseStandaloneSystemLayer = "standaloneSystemLayer"; + static const char * const k_pch_Dashboard_AllowSteamOverlays_Bool = "allowSteamOverlays"; + static const char * const k_pch_Dashboard_AllowVRGamepadUI_Bool = "allowVRGamepadUI"; + static const char * const k_pch_Dashboard_SteamMatchesHMDFramerate = "steamMatchesHMDFramerate"; + static const char * const k_pch_Dashboard_GrabHandleAcceleration = "grabHandleAcceleration"; + static const char * const k_pch_Dashboard_OverlayBacksideColor_String = "overlayBacksideColor"; + + //----------------------------------------------------------------------------- + // model skin keys + static const char * const k_pch_modelskin_Section = "modelskins"; + + //----------------------------------------------------------------------------- + // driver keys - These could be checked in any driver_ section + static const char * const k_pch_Driver_Enable_Bool = "enable"; + static const char * const k_pch_Driver_BlockedBySafemode_Bool = "blocked_by_safe_mode"; + static const char * const k_pch_Driver_LoadPriority_Int32 = "loadPriority"; + static const char * const k_pch_Driver_Hmd_AllowsClientToControlTextureIndex_Bool = "hmdAllowsClientToControlTextureIndex"; + static const char * const k_pch_Driver_ForceSystemLayerUseAppPoses_Bool = "forceSystemLayerUseAppPoses"; + + //----------------------------------------------------------------------------- + // web interface keys + static const char* const k_pch_WebInterface_Section = "WebInterface"; + + //----------------------------------------------------------------------------- + // vrwebhelper keys + static const char* const k_pch_VRWebHelper_Section = "VRWebHelper"; + static const char* const k_pch_VRWebHelper_DebuggerEnabled_Bool = "DebuggerEnabled"; + static const char* const k_pch_VRWebHelper_DebuggerPort_Int32 = "DebuggerPort"; + + //----------------------------------------------------------------------------- + // tracking overrides - keys are device paths, values are the device paths their + // tracking/pose information overrides + static const char* const k_pch_TrackingOverride_Section = "TrackingOverrides"; + + //----------------------------------------------------------------------------- + // per-app keys - the section name for these is the app key itself. Some of these are prefixed by the controller type + static const char* const k_pch_App_BindingAutosaveURLSuffix_String = "AutosaveURL"; + static const char* const k_pch_App_BindingLegacyAPISuffix_String = "_legacy"; + static const char *const k_pch_App_BindingSteamVRInputAPISuffix_String = "_steamvrinput"; + static const char *const k_pch_App_BindingOpenXRAPISuffix_String = "_openxr"; + static const char* const k_pch_App_BindingCurrentURLSuffix_String = "CurrentURL"; + static const char* const k_pch_App_BindingPreviousURLSuffix_String = "PreviousURL"; + static const char* const k_pch_App_NeedToUpdateAutosaveSuffix_Bool = "NeedToUpdateAutosave"; + static const char* const k_pch_App_DominantHand_Int32 = "DominantHand"; + static const char* const k_pch_App_BlockOculusSDK_Bool = "blockOculusSDK"; + + //----------------------------------------------------------------------------- + // configuration for trackers + static const char * const k_pch_Trackers_Section = "trackers"; + + //----------------------------------------------------------------------------- + // configuration for desktop UI windows + static const char * const k_pch_DesktopUI_Section = "DesktopUI"; + + //----------------------------------------------------------------------------- + // Last known keys for righting recovery + static const char * const k_pch_LastKnown_Section = "LastKnown"; + static const char* const k_pch_LastKnown_HMDManufacturer_String = "HMDManufacturer"; + static const char *const k_pch_LastKnown_HMDModel_String = "HMDModel"; + static const char* const k_pch_LastKnown_ActualHMDDriver_String = "ActualHMDDriver"; + static const char* const k_pch_LastKnown_HMDSerialNumber_String = "HMDSerialNumber"; + static const char* const k_pch_LastKnown_HMDRemoteClientID_String = "RemoteClientID"; // uint64 in string + + //----------------------------------------------------------------------------- + // Dismissed warnings + static const char * const k_pch_DismissedWarnings_Section = "DismissedWarnings"; + + //----------------------------------------------------------------------------- + // Input Settings + static const char * const k_pch_Input_Section = "input"; + static const char* const k_pch_Input_LeftThumbstickRotation_Float = "leftThumbstickRotation"; + static const char* const k_pch_Input_RightThumbstickRotation_Float = "rightThumbstickRotation"; + static const char* const k_pch_Input_ThumbstickDeadzone_Float = "thumbstickDeadzone"; + + //----------------------------------------------------------------------------- + // Log of GPU performance + static const char * const k_pch_GpuSpeed_Section = "GpuSpeed"; + + //----------------------------------------------------------------------------- + // OpenXR Render Model Extension keys + static const char *const k_pch_XRRenderModelCache_Section = "XRRenderModelUuidCache"; + +} // namespace vr + +// iservertrackeddevicedriver.h + +namespace vr +{ + + +struct DriverPoseQuaternion_t +{ + double w, x, y, z; +}; + +struct DriverPose_t +{ + /* Time offset of this pose, in seconds from the actual time of the pose, + * relative to the time of the PoseUpdated() call made by the driver. + */ + double poseTimeOffset; + + /* Generally, the pose maintained by a driver + * is in an inertial coordinate system different + * from the world system of x+ right, y+ up, z+ back. + * Also, the driver is not usually tracking the "head" position, + * but instead an internal IMU or another reference point in the HMD. + * The following two transforms transform positions and orientations + * to app world space from driver world space, + * and to HMD head space from driver local body space. + * + * We maintain the driver pose state in its internal coordinate system, + * so we can do the pose prediction math without having to + * use angular acceleration. A driver's angular acceleration is generally not measured, + * and is instead calculated from successive samples of angular velocity. + * This leads to a noisy angular acceleration values, which are also + * lagged due to the filtering required to reduce noise to an acceptable level. + */ + vr::HmdQuaternion_t qWorldFromDriverRotation; + double vecWorldFromDriverTranslation[ 3 ]; + + vr::HmdQuaternion_t qDriverFromHeadRotation; + double vecDriverFromHeadTranslation[ 3 ]; + + /* State of driver pose, in meters and radians. */ + /* Position of the driver tracking reference in driver world space + * +[0] (x) is right + * +[1] (y) is up + * -[2] (z) is forward + */ + double vecPosition[ 3 ]; + + /* Velocity of the pose in meters/second */ + double vecVelocity[ 3 ]; + + /* Acceleration of the pose in meters/second */ + double vecAcceleration[ 3 ]; + + /* Orientation of the tracker, represented as a quaternion */ + vr::HmdQuaternion_t qRotation; + + /* Angular velocity of the pose in axis-angle + * representation. The direction is the angle of + * rotation and the magnitude is the angle around + * that axis in radians/second. */ + double vecAngularVelocity[ 3 ]; + + /* Angular acceleration of the pose in axis-angle + * representation. The direction is the angle of + * rotation and the magnitude is the angle around + * that axis in radians/second^2. */ + double vecAngularAcceleration[ 3 ]; + + ETrackingResult result; + + bool poseIsValid; + bool willDriftInYaw; + bool shouldApplyHeadModel; + bool deviceIsConnected; +}; + + +// ---------------------------------------------------------------------------------------------- +// Purpose: Represents a single tracked device in a driver +// ---------------------------------------------------------------------------------------------- +class ITrackedDeviceServerDriver +{ +public: + + // ------------------------------------ + // Management Methods + // ------------------------------------ + /** This is called before an HMD is returned to the application. It will always be + * called before any display or tracking methods. Memory and processor use by the + * ITrackedDeviceServerDriver object should be kept to a minimum until it is activated. + * The pose listener is guaranteed to be valid until Deactivate is called, but + * should not be used after that point. */ + virtual EVRInitError Activate( uint32_t unObjectId ) = 0; + + /** This is called when The VR system is switching from this Hmd being the active display + * to another Hmd being the active display. The driver should clean whatever memory + * and thread use it can when it is deactivated */ + virtual void Deactivate() = 0; + + /** Handles a request from the system to put this device into standby mode. What that means is defined per-device. */ + virtual void EnterStandby() = 0; + + /** Requests a component interface of the driver for device-specific functionality. The driver should return NULL + * if the requested interface or version is not supported. */ + virtual void *GetComponent( const char *pchComponentNameAndVersion ) = 0; + + /** A VR Client has made this debug request of the driver. The set of valid requests is entirely + * up to the driver and the client to figure out, as is the format of the response. Responses that + * exceed the length of the supplied buffer should be truncated and null terminated */ + virtual void DebugRequest( const char *pchRequest, char *pchResponseBuffer, uint32_t unResponseBufferSize ) = 0; + + /** This interface is unused, and will never be called. */ + virtual DriverPose_t GetPose() = 0; +}; + + + +static const char *ITrackedDeviceServerDriver_Version = "ITrackedDeviceServerDriver_005"; + +} + +// ivrdisplaycomponent.h + +namespace vr +{ + + + // ---------------------------------------------------------------------------------------------- + // Purpose: The display component on a single tracked device + // ---------------------------------------------------------------------------------------------- + class IVRDisplayComponent + { + public: + + // ------------------------------------ + // Display Methods + // ------------------------------------ + + /** Size and position that the window needs to be on the VR display. */ + virtual void GetWindowBounds( int32_t *pnX, int32_t *pnY, uint32_t *pnWidth, uint32_t *pnHeight ) = 0; + + /** Returns true if the display is extending the desktop. */ + virtual bool IsDisplayOnDesktop( ) = 0; + + /** Returns true if the display is real and not a fictional display. */ + virtual bool IsDisplayRealDisplay( ) = 0; + + /** Suggested size for the intermediate render target that the distortion pulls from. */ + virtual void GetRecommendedRenderTargetSize( uint32_t *pnWidth, uint32_t *pnHeight ) = 0; + + /** Gets the viewport in the frame buffer to draw the output of the distortion into */ + virtual void GetEyeOutputViewport( EVREye eEye, uint32_t *pnX, uint32_t *pnY, uint32_t *pnWidth, uint32_t *pnHeight ) = 0; + + /** The components necessary to build your own projection matrix in case your + * application is doing something fancy like infinite Z */ + virtual void GetProjectionRaw( EVREye eEye, float *pfLeft, float *pfRight, float *pfTop, float *pfBottom ) = 0; + + /** Returns the result of the distortion function for the specified eye and input UVs. UVs go from 0,0 in + * the upper left of that eye's viewport and 1,1 in the lower right of that eye's viewport. */ + virtual DistortionCoordinates_t ComputeDistortion( EVREye eEye, float fU, float fV ) = 0; + + /** Computes the result of the inverse distortion function for the specified eye, channel, and input UV. + * Operation can fail, returns success/failure, on success result is stored in *pResult */ + virtual bool ComputeInverseDistortion( HmdVector2_t *pResult, EVREye eEye, uint32_t unChannel, float fU, float fV ) = 0; + }; + + static const char *IVRDisplayComponent_Version = "IVRDisplayComponent_003"; + +} + +// ivrdriverdirectmodecomponent.h + +namespace vr +{ + enum VRSwapTextureFlag + { + // Specify that the shared texture resource was created with the SHARED_NTHANDLE option (Windows) + VRSwapTextureFlag_Shared_NTHandle = 1 << 0, + }; + + // ---------------------------------------------------------------------------------------------- + // Purpose: This component is used for drivers that implement direct mode entirely on their own + // without allowing the VR Compositor to own the window/device. + // ---------------------------------------------------------------------------------------------- + class IVRDriverDirectModeComponent + { + public: + + // ----------------------------------- + // Direct mode methods + // ----------------------------------- + + struct SwapTextureSetDesc_t + { + uint32_t nWidth; + uint32_t nHeight; + uint32_t nFormat; + uint32_t nSampleCount; + }; + + struct SwapTextureSet_t + { + vr::SharedTextureHandle_t rSharedTextureHandles[ 3 ]; + uint32_t unTextureFlags; + }; + + /** Called to allocate textures for applications to render into. One of these per eye will be passed back to SubmitLayer each frame. */ + virtual void CreateSwapTextureSet( uint32_t unPid, const SwapTextureSetDesc_t *pSwapTextureSetDesc, SwapTextureSet_t *pOutSwapTextureSet ) {} + + /** Used to textures created using CreateSwapTextureSet. Only one of the set's handles needs to be used to destroy the entire set. */ + virtual void DestroySwapTextureSet( vr::SharedTextureHandle_t sharedTextureHandle ) {} + + /** Used to purge all texture sets for a given process. */ + virtual void DestroyAllSwapTextureSets( uint32_t unPid ) {} + + /** After Present returns, calls this to get the next index to use for rendering. */ + virtual void GetNextSwapTextureSetIndex( vr::SharedTextureHandle_t sharedTextureHandles[ 2 ], uint32_t( *pIndices )[ 2 ] ) {} + + /** Call once per layer to draw for this frame. One shared texture handle per eye. Textures must be created + * using CreateSwapTextureSet and should be alternated per frame. Call Present once all layers have been submitted. */ + struct SubmitLayerPerEye_t + { + // Shared texture handles (depth not always provided). + vr::SharedTextureHandle_t hTexture, hDepthTexture; + + // Valid region of provided texture (and depth). + vr::VRTextureBounds_t bounds; + + // Projection matrix used to render the depth buffer. + vr::HmdMatrix44_t mProjection; + + // Hmd pose used to render this layer. + vr::HmdMatrix34_t mHmdPose; + + // Time in seconds from now that mHmdPose was predicted to. + float flHmdPosePredictionTimeInSecondsFromNow; + }; + virtual void SubmitLayer( const SubmitLayerPerEye_t( &perEye )[ 2 ] ) {} + + /** Submits queued layers for display. */ + virtual void Present( vr::SharedTextureHandle_t syncTexture ) {} + + /** Called after Present to allow driver to take more time until vsync after they've successfully acquired the sync texture in Present. + * Set Prop_Hmd_SupportsAppThrottling_Bool to enable throttling / prediction UI in per-app video settings. */ + struct Throttling_t + { + uint32_t nFramesToThrottle; + uint32_t nAdditionalFramesToPredict; + }; + virtual void PostPresent( const Throttling_t *pThrottling ) {} + + /** Called to get additional frame timing stats from driver. Check m_nSize for versioning (new members will be added to end only). */ + virtual void GetFrameTiming( DriverDirectMode_FrameTiming *pFrameTiming ) + { + /** VRCompositor_ReprojectionMotion_XXX flags get passed in, and since these overlap with VRCompositor_ThrottleMask, they need + * to be cleared out if this function isn't implemented; otherwise, those settings will get interpreted as throttling. */ + pFrameTiming->m_nReprojectionFlags = 0; + } + }; + + static const char *IVRDriverDirectModeComponent_Version = "IVRDriverDirectModeComponent_009"; + +} + +// ivrcameracomponent.h + +namespace vr +{ + //----------------------------------------------------------------------------- + //----------------------------------------------------------------------------- + class ICameraVideoSinkCallback + { + public: + virtual void OnCameraVideoSinkCallback() = 0; + }; + + // ---------------------------------------------------------------------------------------------- + // Purpose: The camera on a single tracked device + // ---------------------------------------------------------------------------------------------- + class IVRCameraComponent + { + public: + // ------------------------------------ + // Camera Methods + // ------------------------------------ + virtual bool GetCameraFrameDimensions( vr::ECameraVideoStreamFormat nVideoStreamFormat, uint32_t *pWidth, uint32_t *pHeight ) = 0; + virtual bool GetCameraFrameBufferingRequirements( int *pDefaultFrameQueueSize, uint32_t *pFrameBufferDataSize ) = 0; + virtual bool SetCameraFrameBuffering( int nFrameBufferCount, void **ppFrameBuffers, uint32_t nFrameBufferDataSize ) = 0; + virtual bool SetCameraVideoStreamFormat( vr::ECameraVideoStreamFormat nVideoStreamFormat ) = 0; + virtual vr::ECameraVideoStreamFormat GetCameraVideoStreamFormat() = 0; + virtual bool StartVideoStream() = 0; + virtual void StopVideoStream() = 0; + virtual bool IsVideoStreamActive( bool *pbPaused, float *pflElapsedTime ) = 0; + virtual const vr::CameraVideoStreamFrame_t *GetVideoStreamFrame() = 0; + virtual void ReleaseVideoStreamFrame( const vr::CameraVideoStreamFrame_t *pFrameImage ) = 0; + virtual bool SetAutoExposure( bool bEnable ) = 0; + virtual bool PauseVideoStream() = 0; + virtual bool ResumeVideoStream() = 0; + virtual bool GetCameraDistortion( uint32_t nCameraIndex, float flInputU, float flInputV, float *pflOutputU, float *pflOutputV ) = 0; + virtual bool GetCameraProjection( uint32_t nCameraIndex, vr::EVRTrackedCameraFrameType eFrameType, float flZNear, float flZFar, vr::HmdMatrix44_t *pProjection ) = 0; + virtual bool SetFrameRate( int nISPFrameRate, int nSensorFrameRate ) = 0; + virtual bool SetCameraVideoSinkCallback( vr::ICameraVideoSinkCallback *pCameraVideoSinkCallback ) = 0; + virtual bool GetCameraCompatibilityMode( vr::ECameraCompatibilityMode *pCameraCompatibilityMode ) = 0; + virtual bool SetCameraCompatibilityMode( vr::ECameraCompatibilityMode nCameraCompatibilityMode ) = 0; + virtual bool GetCameraFrameBounds( vr::EVRTrackedCameraFrameType eFrameType, uint32_t *pLeft, uint32_t *pTop, uint32_t *pWidth, uint32_t *pHeight ) = 0; + virtual bool GetCameraIntrinsics( uint32_t nCameraIndex, vr::EVRTrackedCameraFrameType eFrameType, HmdVector2_t *pFocalLength, HmdVector2_t *pCenter, vr::EVRDistortionFunctionType *peDistortionType, double rCoefficients[ k_unMaxDistortionFunctionParameters ] ) = 0; + }; + + static const char *IVRCameraComponent_Version = "IVRCameraComponent_003"; +} + +// itrackeddevicedriverprovider.h + +namespace vr +{ + +class ITrackedDeviceServerDriver; +struct TrackedDeviceDriverInfo_t; +struct DriverPose_t; + +/** This interface is provided by vrserver to allow the driver to notify +* the system when something changes about a device. These changes must +* not change the serial number or class of the device because those values +* are permanently associated with the device's index. */ +class IVRDriverContext +{ +public: + /** Returns the requested interface. If the interface was not available it will return NULL and fill + * out the error. */ + virtual void *GetGenericInterface( const char *pchInterfaceVersion, EVRInitError *peError = nullptr ) = 0; + + /** Returns the property container handle for this driver */ + virtual DriverHandle_t GetDriverHandle() = 0; +}; + + +/** This interface must be implemented in each driver. It will be loaded in vrserver.exe */ +class IServerTrackedDeviceProvider +{ +public: + /** initializes the driver. This will be called before any other methods are called. + * If Init returns anything other than VRInitError_None the driver DLL will be unloaded. + * + * pDriverHost will never be NULL, and will always be a pointer to a IServerDriverHost interface + * + * pchUserDriverConfigDir - The absolute path of the directory where the driver should store user + * config files. + * pchDriverInstallDir - The absolute path of the root directory for the driver. + */ + virtual EVRInitError Init( IVRDriverContext *pDriverContext ) = 0; + + /** cleans up the driver right before it is unloaded */ + virtual void Cleanup() = 0; + + /** Returns the version of the ITrackedDeviceServerDriver interface used by this driver */ + virtual const char * const *GetInterfaceVersions() = 0; + + /** Allows the driver do to some work in the main loop of the server. */ + virtual void RunFrame() = 0; + + + // ------------ Power State Functions ----------------------- // + + /** Returns true if the driver wants to block Standby mode. */ + virtual bool ShouldBlockStandbyMode() = 0; + + /** Called when the system is entering Standby mode. The driver should switch itself into whatever sort of low-power + * state it has. */ + virtual void EnterStandby() = 0; + + /** Called when the system is leaving Standby mode. The driver should switch itself back to + full operation. */ + virtual void LeaveStandby() = 0; + +}; + + +static const char *IServerTrackedDeviceProvider_Version = "IServerTrackedDeviceProvider_004"; + + + + +/** This interface must be implemented in each driver. It will be loaded in vrclient.dll */ +class IVRWatchdogProvider +{ +public: + /** initializes the driver in watchdog mode. */ + virtual EVRInitError Init( IVRDriverContext *pDriverContext ) = 0; + + /** cleans up the driver right before it is unloaded */ + virtual void Cleanup() = 0; +}; + +static const char *IVRWatchdogProvider_Version = "IVRWatchdogProvider_001"; + + + + +/** This is an optional interface drivers may implement. It will be loaded in vrcompositor.exe */ +class IVRCompositorPluginProvider +{ +public: + /** initializes the driver when used to load compositor plugins */ + virtual EVRInitError Init( IVRDriverContext *pDriverContext ) = 0; + + /** cleans up the driver right before it is unloaded */ + virtual void Cleanup() = 0; + + /** Returns the versions of interfaces used by this driver */ + virtual const char * const *GetInterfaceVersions() = 0; + + /** Requests a component interface of the driver for specific functionality. The driver should return NULL + * if the requested interface or version is not supported. */ + virtual void *GetComponent( const char *pchComponentNameAndVersion ) = 0; +}; + +static const char *IVRCompositorPluginProvider_Version = "IVRCompositorPluginProvider_001"; + +} + +// ivrproperties.h + +#include +#include + +namespace vr +{ + + /** This container is automatically created before a display redirect device is activated. + * Any properties in this container will be returned when that property is read from the HMD's + * property container. */ + static const PropertyContainerHandle_t k_ulDisplayRedirectContainer = 0x600000003; + + enum EPropertyWriteType + { + PropertyWrite_Set = 0, + PropertyWrite_Erase = 1, + PropertyWrite_SetError = 2 + }; + + struct PropertyWrite_t + { + ETrackedDeviceProperty prop; + EPropertyWriteType writeType; + ETrackedPropertyError eSetError; + void *pvBuffer; + uint32_t unBufferSize; + PropertyTypeTag_t unTag; + ETrackedPropertyError eError; + }; + + struct PropertyRead_t + { + ETrackedDeviceProperty prop; + void *pvBuffer; + uint32_t unBufferSize; + PropertyTypeTag_t unTag; + uint32_t unRequiredBufferSize; + ETrackedPropertyError eError; + }; + + +class IVRProperties +{ +public: + + /** Reads a set of properties atomically. See the PropertyReadBatch_t struct for more information. */ + virtual ETrackedPropertyError ReadPropertyBatch( PropertyContainerHandle_t ulContainerHandle, PropertyRead_t *pBatch, uint32_t unBatchEntryCount ) = 0; + + /** Writes a set of properties atomically. See the PropertyWriteBatch_t struct for more information. */ + virtual ETrackedPropertyError WritePropertyBatch( PropertyContainerHandle_t ulContainerHandle, PropertyWrite_t *pBatch, uint32_t unBatchEntryCount ) = 0; + + /** returns a string that corresponds with the specified property error. The string will be the name + * of the error enum value for all valid error codes */ + virtual const char *GetPropErrorNameFromEnum( ETrackedPropertyError error ) = 0; + + /** Returns a container handle given a tracked device index */ + virtual PropertyContainerHandle_t TrackedDeviceToPropertyContainer( TrackedDeviceIndex_t nDevice ) = 0; +}; + +static const char * const IVRProperties_Version = "IVRProperties_001"; + +class CVRPropertyHelpers +{ +public: + CVRPropertyHelpers( IVRProperties * pProperties ) : m_pProperties( pProperties ) {} + + /** Gets the typed property according to the caller's expected return type. */ + template + T GetTypedProperty( vr::PropertyContainerHandle_t ulContainer, vr::ETrackedDeviceProperty prop, vr::ETrackedPropertyError *peError ); + + /** Returns a scaler property. If the device index is not valid or the property value type does not match, + * this function will return false. */ + bool GetBoolProperty( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, ETrackedPropertyError *pError = 0L ); + float GetFloatProperty( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, ETrackedPropertyError *pError = 0L ); + int32_t GetInt32Property( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, ETrackedPropertyError *pError = 0L ); + uint64_t GetUint64Property( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, ETrackedPropertyError *pError = 0L ); + HmdVector2_t GetVec2Property( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, ETrackedPropertyError *pError = 0L ); + HmdVector3_t GetVec3Property( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, ETrackedPropertyError *pError = 0L ); + HmdVector4_t GetVec4Property( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, ETrackedPropertyError *pError = 0L ); + double GetDoubleProperty( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, ETrackedPropertyError *pError = 0L ); + + /** Returns a single typed property. If the device index is not valid or the property is not a string type this function will + * return 0. Otherwise it returns the length of the number of bytes necessary to hold this string including the trailing + * null. Strings will always fit in buffers of k_unMaxPropertyStringSize characters. */ + uint32_t GetProperty( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, VR_OUT_STRING() void *pvBuffer, uint32_t unBufferSize, PropertyTypeTag_t *punTag, ETrackedPropertyError *pError = 0L ); + + /** Returns a string property. If the device index is not valid or the property is not a string type this function will + * return 0. Otherwise it returns the length of the number of bytes necessary to hold this string including the trailing + * null. Strings will always fit in buffers of k_unMaxPropertyStringSize characters. */ + uint32_t GetStringProperty( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, VR_OUT_STRING() char *pchValue, uint32_t unBufferSize, ETrackedPropertyError *pError = 0L ); + + /** Returns a string property as a std::string. If the device index is not valid or the property is not a string type this function will + * return an empty string. */ + std::string GetStringProperty( vr::PropertyContainerHandle_t ulContainer, vr::ETrackedDeviceProperty prop, vr::ETrackedPropertyError *peError = nullptr ); + + /** Reads a std::vector of data from a property. */ + template< typename T> + ETrackedPropertyError GetPropertyVector( PropertyContainerHandle_t ulContainer, ETrackedDeviceProperty prop, PropertyTypeTag_t unExpectedTag, std::vector *pvecResults ); + + /** Sets a scaler property. The new value will be returned on any subsequent call to get this property in any process. */ + ETrackedPropertyError SetBoolProperty( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, bool bNewValue ); + ETrackedPropertyError SetFloatProperty( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, float fNewValue ); + ETrackedPropertyError SetInt32Property( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, int32_t nNewValue ); + ETrackedPropertyError SetUint64Property( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, uint64_t ulNewValue ); + ETrackedPropertyError SetVec2Property( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, const HmdVector2_t & vNewValue ); + ETrackedPropertyError SetVec3Property( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, const HmdVector3_t & vNewValue ); + ETrackedPropertyError SetVec4Property( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, const HmdVector4_t & vNewValue ); + ETrackedPropertyError SetDoubleProperty( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, double vNewValue ); + + /** Sets a string property. The new value will be returned on any subsequent call to get this property in any process. */ + ETrackedPropertyError SetStringProperty( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, const char *pchNewValue ); + + /** Sets a single typed property. The new value will be returned on any subsequent call to get this property in any process. */ + ETrackedPropertyError SetProperty( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, void *pvNewValue, uint32_t unNewValueSize, PropertyTypeTag_t unTag ); + + /** Sets the error return value for a property. This value will be returned on all subsequent requests to get the property */ + ETrackedPropertyError SetPropertyError( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, ETrackedPropertyError eError ); + + /** Clears any value or error set for the property. */ + ETrackedPropertyError EraseProperty( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop ); + + /* Turns a device index into a property container handle. */ + PropertyContainerHandle_t TrackedDeviceToPropertyContainer( TrackedDeviceIndex_t nDevice ) { return m_pProperties->TrackedDeviceToPropertyContainer( nDevice ); } + + /** Sets a std::vector of typed data to a property. */ + template< typename T> + ETrackedPropertyError SetPropertyVector( PropertyContainerHandle_t ulContainer, ETrackedDeviceProperty prop, PropertyTypeTag_t unExpectedTag, std::vector *vecProperties ); + + /** Returns true if the specified property is set on the specified container */ + bool IsPropertySet( PropertyContainerHandle_t ulContainer, ETrackedDeviceProperty prop, ETrackedPropertyError *peError = nullptr ); +private: + template + T GetPropertyHelper( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, ETrackedPropertyError *pError, T bDefault, PropertyTypeTag_t unTypeTag ); + + IVRProperties *m_pProperties; +}; + + +/** Returns a string property as a std::string. If the device index is not valid or the property is not a string type this function will +* return an empty string. */ +template <> +inline std::string CVRPropertyHelpers::GetTypedProperty( vr::PropertyContainerHandle_t ulContainer, vr::ETrackedDeviceProperty prop, vr::ETrackedPropertyError *peError ) +{ + return GetStringProperty( ulContainer, prop, peError ); +} + + +template <> +inline bool CVRPropertyHelpers::GetTypedProperty( vr::PropertyContainerHandle_t ulContainer, vr::ETrackedDeviceProperty prop, vr::ETrackedPropertyError *peError ) +{ + return GetBoolProperty( ulContainer, prop, peError ); +} + + +template <> +inline float CVRPropertyHelpers::GetTypedProperty( vr::PropertyContainerHandle_t ulContainer, vr::ETrackedDeviceProperty prop, vr::ETrackedPropertyError *peError ) +{ + return GetFloatProperty( ulContainer, prop, peError ); +} + + +template <> +inline int32_t CVRPropertyHelpers::GetTypedProperty( vr::PropertyContainerHandle_t ulContainer, vr::ETrackedDeviceProperty prop, vr::ETrackedPropertyError *peError ) +{ + return GetInt32Property( ulContainer, prop, peError ); +} + + +template <> +inline uint64_t CVRPropertyHelpers::GetTypedProperty( vr::PropertyContainerHandle_t ulContainer, vr::ETrackedDeviceProperty prop, vr::ETrackedPropertyError *peError ) +{ + return GetUint64Property( ulContainer, prop, peError ); +} + + +template <> +inline HmdVector2_t CVRPropertyHelpers::GetTypedProperty( vr::PropertyContainerHandle_t ulContainer, vr::ETrackedDeviceProperty prop, vr::ETrackedPropertyError *peError ) +{ + return GetVec2Property( ulContainer, prop, peError ); +} + + +template <> +inline HmdVector3_t CVRPropertyHelpers::GetTypedProperty( vr::PropertyContainerHandle_t ulContainer, vr::ETrackedDeviceProperty prop, vr::ETrackedPropertyError *peError ) +{ + return GetVec3Property( ulContainer, prop, peError ); +} + + +template <> +inline HmdVector4_t CVRPropertyHelpers::GetTypedProperty( vr::PropertyContainerHandle_t ulContainer, vr::ETrackedDeviceProperty prop, vr::ETrackedPropertyError *peError ) +{ + return GetVec4Property( ulContainer, prop, peError ); +} + + +template <> +inline double CVRPropertyHelpers::GetTypedProperty( vr::PropertyContainerHandle_t ulContainer, vr::ETrackedDeviceProperty prop, vr::ETrackedPropertyError *peError ) +{ + return GetDoubleProperty( ulContainer, prop, peError ); +} + + +inline uint32_t CVRPropertyHelpers::GetProperty( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, VR_OUT_STRING() void *pvBuffer, uint32_t unBufferSize, PropertyTypeTag_t *punTag, ETrackedPropertyError *pError ) +{ + PropertyRead_t batch; + batch.prop = prop; + batch.pvBuffer = pvBuffer; + batch.unBufferSize = unBufferSize; + + m_pProperties->ReadPropertyBatch( ulContainerHandle, &batch, 1 ); + + if ( pError ) + { + *pError = batch.eError; + } + + if ( punTag ) + { + *punTag = batch.unTag; + } + + return batch.unRequiredBufferSize; +} + + +/** Sets a single typed property. The new value will be returned on any subsequent call to get this property in any process. */ +inline ETrackedPropertyError CVRPropertyHelpers::SetProperty( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, void *pvNewValue, uint32_t unNewValueSize, PropertyTypeTag_t unTag ) +{ + PropertyWrite_t batch; + batch.writeType = PropertyWrite_Set; + batch.prop = prop; + batch.pvBuffer = pvNewValue; + batch.unBufferSize = unNewValueSize; + batch.unTag = unTag; + + m_pProperties->WritePropertyBatch( ulContainerHandle, &batch, 1 ); + + return batch.eError; +} + + +/** Returns a string property. If the device index is not valid or the property is not a string type this function will +* return 0. Otherwise it returns the length of the number of bytes necessary to hold this string including the trailing +* null. Strings will always fit in buffers of k_unMaxPropertyStringSize characters. */ +inline uint32_t CVRPropertyHelpers::GetStringProperty( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, VR_OUT_STRING() char *pchValue, uint32_t unBufferSize, ETrackedPropertyError *pError ) +{ + PropertyTypeTag_t unTag; + ETrackedPropertyError error; + uint32_t unRequiredSize = GetProperty( ulContainerHandle, prop, pchValue, unBufferSize, &unTag, &error ); + if ( unTag != k_unStringPropertyTag && error == TrackedProp_Success ) + { + error = TrackedProp_WrongDataType; + } + + if ( pError ) + { + *pError = error; + } + + if ( error != TrackedProp_Success ) + { + if ( pchValue && unBufferSize ) + { + *pchValue = '\0'; + } + } + + return unRequiredSize; +} + + +/** Returns a string property as a std::string. If the device index is not valid or the property is not a string type this function will +* return an empty string. */ +inline std::string CVRPropertyHelpers::GetStringProperty( vr::PropertyContainerHandle_t ulContainer, vr::ETrackedDeviceProperty prop, vr::ETrackedPropertyError *peError ) +{ + char buf[1024]; + vr::ETrackedPropertyError err; + uint32_t unRequiredBufferLen = GetStringProperty( ulContainer, prop, buf, sizeof(buf), &err ); + + std::string sResult; + + if ( err == TrackedProp_Success ) + { + sResult = buf; + } + else if ( err == TrackedProp_BufferTooSmall ) + { + char *pchBuffer = new char[unRequiredBufferLen]; + unRequiredBufferLen = GetStringProperty( ulContainer, prop, pchBuffer, unRequiredBufferLen, &err ); + sResult = pchBuffer; + delete[] pchBuffer; + } + + if ( peError ) + { + *peError = err; + } + + return sResult; +} + + +/** Sets a string property. The new value will be returned on any subsequent call to get this property in any process. */ +inline ETrackedPropertyError CVRPropertyHelpers::SetStringProperty( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, const char *pchNewValue ) +{ + if ( !pchNewValue ) + return TrackedProp_InvalidOperation; + + // this is strlen without the dependency on string.h + const char *pchCurr = pchNewValue; + while ( *pchCurr ) + { + pchCurr++; + } + + return SetProperty( ulContainerHandle, prop, (void *)pchNewValue, (uint32_t)(pchCurr - pchNewValue) + 1, k_unStringPropertyTag ); +} + + +template +inline T CVRPropertyHelpers::GetPropertyHelper( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, ETrackedPropertyError *pError, T bDefault, PropertyTypeTag_t unTypeTag ) +{ + T bValue; + ETrackedPropertyError eError; + PropertyTypeTag_t unReadTag; + GetProperty( ulContainerHandle, prop, &bValue, sizeof( bValue ), &unReadTag, &eError ); + if ( unReadTag != unTypeTag && eError == TrackedProp_Success ) + { + eError = TrackedProp_WrongDataType; + }; + + if ( pError ) + *pError = eError; + if ( eError != TrackedProp_Success ) + { + return bDefault; + } + else + { + return bValue; + } +} + + +inline bool CVRPropertyHelpers::GetBoolProperty( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, ETrackedPropertyError *pError ) +{ + return GetPropertyHelper( ulContainerHandle, prop, pError, false, k_unBoolPropertyTag ); +} + +inline float CVRPropertyHelpers::GetFloatProperty( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, ETrackedPropertyError *pError ) +{ + return GetPropertyHelper( ulContainerHandle, prop, pError, 0.f, k_unFloatPropertyTag ); +} + +inline double CVRPropertyHelpers::GetDoubleProperty( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, ETrackedPropertyError *pError ) +{ + return GetPropertyHelper( ulContainerHandle, prop, pError, 0., k_unDoublePropertyTag ); +} + +inline int32_t CVRPropertyHelpers::GetInt32Property( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, ETrackedPropertyError *pError ) +{ + return GetPropertyHelper( ulContainerHandle, prop, pError, 0, k_unInt32PropertyTag ); +} + +inline uint64_t CVRPropertyHelpers::GetUint64Property( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, ETrackedPropertyError *pError ) +{ + return GetPropertyHelper( ulContainerHandle, prop, pError, 0, k_unUint64PropertyTag ); +} + +inline HmdVector2_t CVRPropertyHelpers::GetVec2Property( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, ETrackedPropertyError *pError ) +{ + HmdVector2_t defaultval = { { 0, 0 } }; + return GetPropertyHelper( ulContainerHandle, prop, pError, defaultval, k_unHmdVector2PropertyTag ); +} + +inline HmdVector3_t CVRPropertyHelpers::GetVec3Property( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, ETrackedPropertyError *pError ) +{ + HmdVector3_t defaultval = { { 0, 0, 0 } }; + return GetPropertyHelper( ulContainerHandle, prop, pError, defaultval, k_unHmdVector3PropertyTag ); +} + +inline HmdVector4_t CVRPropertyHelpers::GetVec4Property( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, ETrackedPropertyError *pError ) +{ + HmdVector4_t defaultval = { { 0, 0, 0, 0 } }; + return GetPropertyHelper( ulContainerHandle, prop, pError, defaultval, k_unHmdVector4PropertyTag ); +} + +inline ETrackedPropertyError CVRPropertyHelpers::SetBoolProperty( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, bool bNewValue ) +{ + return SetProperty( ulContainerHandle, prop, &bNewValue, sizeof( bNewValue ), k_unBoolPropertyTag ); +} + +inline ETrackedPropertyError CVRPropertyHelpers::SetFloatProperty( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, float fNewValue ) +{ + return SetProperty( ulContainerHandle, prop, &fNewValue, sizeof( fNewValue ), k_unFloatPropertyTag ); +} + +inline ETrackedPropertyError CVRPropertyHelpers::SetDoubleProperty( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, double fNewValue ) +{ + return SetProperty( ulContainerHandle, prop, &fNewValue, sizeof( fNewValue ), k_unDoublePropertyTag ); +} + +inline ETrackedPropertyError CVRPropertyHelpers::SetInt32Property( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, int32_t nNewValue ) +{ + return SetProperty( ulContainerHandle, prop, &nNewValue, sizeof( nNewValue ), k_unInt32PropertyTag ); +} + +inline ETrackedPropertyError CVRPropertyHelpers::SetUint64Property( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, uint64_t ulNewValue ) +{ + return SetProperty( ulContainerHandle, prop, &ulNewValue, sizeof( ulNewValue ), k_unUint64PropertyTag ); +} + +inline ETrackedPropertyError CVRPropertyHelpers::SetVec2Property( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, const HmdVector2_t & vNewValue ) +{ + return SetProperty( ulContainerHandle, prop, ( void * ) &vNewValue, sizeof( HmdVector2_t ), k_unHmdVector2PropertyTag ); +} + +inline ETrackedPropertyError CVRPropertyHelpers::SetVec3Property( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, const HmdVector3_t & vNewValue ) +{ + return SetProperty( ulContainerHandle, prop, ( void * ) &vNewValue, sizeof( HmdVector3_t ), k_unHmdVector3PropertyTag ); +} + +inline ETrackedPropertyError CVRPropertyHelpers::SetVec4Property( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, const HmdVector4_t & vNewValue ) +{ + return SetProperty( ulContainerHandle, prop, ( void * ) &vNewValue, sizeof( HmdVector4_t ), k_unHmdVector4PropertyTag ); +} + +/** Sets the error return value for a property. This value will be returned on all subsequent requests to get the property */ +inline ETrackedPropertyError CVRPropertyHelpers::SetPropertyError( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop, ETrackedPropertyError eError ) +{ + PropertyWrite_t batch; + batch.writeType = PropertyWrite_SetError; + batch.prop = prop; + batch.eSetError = eError; + + m_pProperties->WritePropertyBatch( ulContainerHandle, &batch, 1 ); + + return batch.eError; +} + +/** Clears any value or error set for the property. */ +inline ETrackedPropertyError CVRPropertyHelpers::EraseProperty( PropertyContainerHandle_t ulContainerHandle, ETrackedDeviceProperty prop ) +{ + PropertyWrite_t batch; + batch.writeType = PropertyWrite_Erase; + batch.prop = prop; + + m_pProperties->WritePropertyBatch( ulContainerHandle, &batch, 1 ); + + return batch.eError; + +} + +template< typename T > +ETrackedPropertyError CVRPropertyHelpers::SetPropertyVector(PropertyContainerHandle_t ulContainer, vr::ETrackedDeviceProperty prop, PropertyTypeTag_t unTag, std::vector *pvecProperties) +{ + return SetProperty( ulContainer, prop, &(*pvecProperties)[0], (uint32_t)(pvecProperties->size() * sizeof( T )), unTag ); +} + +template< typename T > +ETrackedPropertyError CVRPropertyHelpers::GetPropertyVector( PropertyContainerHandle_t ulContainer, ETrackedDeviceProperty prop, PropertyTypeTag_t unExpectedTag, std::vector *pvecResults ) +{ + ETrackedPropertyError err; + PropertyTypeTag_t unTag; + uint32_t unNeeded; + if ( pvecResults->empty() ) + unNeeded = GetProperty( ulContainer, prop, nullptr, 0, &unTag, &err ); + else + unNeeded = GetProperty( ulContainer, prop, &(*pvecResults)[0], (uint32_t)(pvecResults->size() * sizeof( T )), &unTag, &err ); + uint32_t unFound = unNeeded / sizeof( T ); + if ( err == TrackedProp_Success ) + { + if ( unTag != unExpectedTag && unFound > 0 ) + { + return TrackedProp_WrongDataType; + } + + pvecResults->resize( unFound ); + return TrackedProp_Success; + } + else if ( err == TrackedProp_BufferTooSmall ) + { + pvecResults->resize( unFound ); + unNeeded = GetProperty( ulContainer, prop, &(*pvecResults)[0], (uint32_t)(pvecResults->size() * sizeof( T )), &unTag, &err ); + unFound = unNeeded / sizeof( T ); + + if ( err == TrackedProp_Success ) + { + if ( unTag != unExpectedTag ) + { + return TrackedProp_WrongDataType; + } + + pvecResults->resize( unFound ); + return TrackedProp_Success; + } + } + return err; +} + +inline bool CVRPropertyHelpers::IsPropertySet( PropertyContainerHandle_t ulContainer, ETrackedDeviceProperty prop, ETrackedPropertyError *peError ) +{ + ETrackedPropertyError error; + GetProperty( ulContainer, prop, nullptr, 0, nullptr, &error ); + if ( peError ) + *peError = error; + return error == TrackedProp_Success || error == TrackedProp_BufferTooSmall; +} + +} + + + +// ivrdriverinput.h + +namespace vr +{ + enum EVRScalarType + { + VRScalarType_Absolute = 0, + VRScalarType_Relative = 1, + }; + + + enum EVRScalarUnits + { + VRScalarUnits_NormalizedOneSided = 0, // Value ranges from 0 to 1 + VRScalarUnits_NormalizedTwoSided = 1, // Value ranges from -1 to 1 + }; + + class IVRDriverInput + { + public: + + /** Creates a boolean input component for the device */ + virtual EVRInputError CreateBooleanComponent( PropertyContainerHandle_t ulContainer, const char *pchName, VRInputComponentHandle_t *pHandle ) = 0; + + /** Updates a boolean component */ + virtual EVRInputError UpdateBooleanComponent( VRInputComponentHandle_t ulComponent, bool bNewValue, double fTimeOffset ) = 0; + + /** Creates a scalar input component for the device */ + virtual EVRInputError CreateScalarComponent( PropertyContainerHandle_t ulContainer, const char *pchName, VRInputComponentHandle_t *pHandle, EVRScalarType eType, EVRScalarUnits eUnits ) = 0; + + /** Updates a boolean component */ + virtual EVRInputError UpdateScalarComponent( VRInputComponentHandle_t ulComponent, float fNewValue, double fTimeOffset ) = 0; + + /** Creates a haptic component for the device */ + virtual EVRInputError CreateHapticComponent( PropertyContainerHandle_t ulContainer, const char *pchName, VRInputComponentHandle_t *pHandle ) = 0; + + /** Creates a skeleton component. */ + virtual EVRInputError CreateSkeletonComponent( PropertyContainerHandle_t ulContainer, const char *pchName, const char *pchSkeletonPath, const char *pchBasePosePath, EVRSkeletalTrackingLevel eSkeletalTrackingLevel, const VRBoneTransform_t *pGripLimitTransforms, uint32_t unGripLimitTransformCount, VRInputComponentHandle_t *pHandle ) = 0; + + /** Updates a skeleton component. */ + virtual EVRInputError UpdateSkeletonComponent( VRInputComponentHandle_t ulComponent, EVRSkeletalMotionRange eMotionRange, const VRBoneTransform_t *pTransforms, uint32_t unTransformCount ) = 0; + + /** Creates a pose component */ + virtual EVRInputError CreatePoseComponent( PropertyContainerHandle_t ulContainer, const char *pchName, VRInputComponentHandle_t *pHandle ) = 0; + + /** Updates a pose component. */ + virtual EVRInputError UpdatePoseComponent( VRInputComponentHandle_t ulComponent, const HmdMatrix34_t *pMatPoseOffset, double fTimeOffset ) = 0; + + /** Creates an eye tracking component **/ + virtual EVRInputError CreateEyeTrackingComponent( PropertyContainerHandle_t ulContainer, const char *pchName, VRInputComponentHandle_t *pHandle ) = 0; + + /** Updates an eye tracking component. */ + virtual EVRInputError UpdateEyeTrackingComponent( VRInputComponentHandle_t ulComponent, const VREyeTrackingData_t *pEyeTrackingData, double fTimeOffset ) = 0; + }; + + static const char * const IVRDriverInput_Version = "IVRDriverInput_004"; + +} // namespace vr + +// ivrdriverlog.h + +namespace vr +{ + +class IVRDriverLog +{ +public: + /** Writes a log message to the log file prefixed with the driver name */ + virtual void Log( const char *pchLogMessage ) = 0; +}; + + +static const char *IVRDriverLog_Version = "IVRDriverLog_001"; + +} + +// ivrserverdriverhost.h + +namespace vr +{ + +class ITrackedDeviceServerDriver; +struct TrackedDeviceDriverInfo_t; +struct DriverPose_t; + +/** This interface is provided by vrserver to allow the driver to notify +* the system when something changes about a device. These changes must +* not change the serial number or class of the device because those values +* are permanently associated with the device's index. */ +class IVRServerDriverHost +{ +public: + /** Notifies the server that a tracked device has been added. If this function returns true + * the server will call Activate on the device. If it returns false some kind of error + * has occurred and the device will not be activated. */ + virtual bool TrackedDeviceAdded( const char *pchDeviceSerialNumber, ETrackedDeviceClass eDeviceClass, ITrackedDeviceServerDriver *pDriver ) = 0; + + /** Notifies the server that a tracked device's pose has been updated */ + virtual void TrackedDevicePoseUpdated( uint32_t unWhichDevice, const DriverPose_t & newPose, uint32_t unPoseStructSize ) = 0; + + /** Notifies the server that vsync has occurred on the the display attached to the device. This is + * only permitted on devices of the HMD class. */ + virtual void VsyncEvent( double vsyncTimeOffsetSeconds ) = 0; + + /** Sends a vendor specific event (VREvent_VendorSpecific_Reserved_Start..VREvent_VendorSpecific_Reserved_End */ + virtual void VendorSpecificEvent( uint32_t unWhichDevice, vr::EVREventType eventType, const VREvent_Data_t & eventData, double eventTimeOffset ) = 0; + + /** Returns true if SteamVR is exiting */ + virtual bool IsExiting() = 0; + + /** Returns true and fills the event with the next event on the queue if there is one. If there are no events + * this method returns false. uncbVREvent should be the size in bytes of the VREvent_t struct */ + virtual bool PollNextEvent( VREvent_t *pEvent, uint32_t uncbVREvent ) = 0; + + /** Provides access to device poses for drivers. Poses are in their "raw" tracking space which is uniquely + * defined by each driver providing poses for its devices. It is up to clients of this function to correlate + * poses across different drivers. Poses are indexed by their device id, and their associated driver and + * other properties can be looked up via IVRProperties. */ + virtual void GetRawTrackedDevicePoses( float fPredictedSecondsFromNow, TrackedDevicePose_t *pTrackedDevicePoseArray, uint32_t unTrackedDevicePoseArrayCount ) = 0; + + /** Requests that SteamVR be restarted. The provided reason will be displayed to the user and should be in the current locale. */ + virtual void RequestRestart( const char *pchLocalizedReason, const char *pchExecutableToStart, const char *pchArguments, const char *pchWorkingDirectory ) = 0; + + /** Interface for copying a range of timing data. Frames are returned in ascending order (oldest to newest) with the last being the most recent frame. + * Only the first entry's m_nSize needs to be set, as the rest will be inferred from that. Returns total number of entries filled out. */ + virtual uint32_t GetFrameTimings( Compositor_FrameTiming *pTiming, uint32_t nFrames ) = 0; + + /** Notifies the server that a tracked device's display component transforms have been updated. + * only permitted on devices of the HMD class. */ + virtual void SetDisplayEyeToHead( uint32_t unWhichDevice, const HmdMatrix34_t & eyeToHeadLeft, const HmdMatrix34_t & eyeToHeadRight ) = 0; + + /** Notifies the server that a tracked device's display projection has changed. + * only permitted on devices of the HMD class. */ + virtual void SetDisplayProjectionRaw( uint32_t unWhichDevice, const HmdRect2_t & eyeLeft, const HmdRect2_t & eyeRight ) = 0; + + /** Notifies the server that a tracked device's recommended render target resolution has changed. + * only permitted on devices of the HMD class. */ + virtual void SetRecommendedRenderTargetSize( uint32_t unWhichDevice, uint32_t nWidth, uint32_t nHeight ) = 0; +}; + +static const char *IVRServerDriverHost_Version = "IVRServerDriverHost_006"; + +} + +// ivrcompositordriverhost.h + +namespace vr +{ + +class IVRCompositorDriverHost +{ +public: + /** Returns true and fills the event with the next event on the queue if there is one. If there are no events + * this method returns false. uncbVREvent should be the size in bytes of the VREvent_t struct */ + virtual bool PollNextEvent( VREvent_t *pEvent, uint32_t uncbVREvent ) = 0; +}; + +static const char *IVRCompositorDriverHost_Version = "IVRCompositorDriverHost_001"; + +} + +// ivrhiddenarea.h + +namespace vr +{ + +class CVRHiddenAreaHelpers +{ +public: + CVRHiddenAreaHelpers( IVRProperties *pProperties ) : m_pProperties( pProperties ) {} + + /** Stores a hidden area mesh in a property */ + ETrackedPropertyError SetHiddenArea( EVREye eEye, EHiddenAreaMeshType type, HmdVector2_t *pVerts, uint32_t unVertCount ); + + /** retrieves a hidden area mesh from a property. Returns the vert count read out of the property. */ + uint32_t GetHiddenArea( EVREye eEye, EHiddenAreaMeshType type, HmdVector2_t *pVerts, uint32_t unVertCount, ETrackedPropertyError *peError ); + +private: + ETrackedDeviceProperty GetPropertyEnum( EVREye eEye, EHiddenAreaMeshType type ) + { + return (ETrackedDeviceProperty)(Prop_DisplayHiddenArea_Binary_Start + ((int)type * 2) + (int)eEye); + } + + IVRProperties *m_pProperties; +}; + + +inline ETrackedPropertyError CVRHiddenAreaHelpers::SetHiddenArea( EVREye eEye, EHiddenAreaMeshType type, HmdVector2_t *pVerts, uint32_t unVertCount ) +{ + ETrackedDeviceProperty prop = GetPropertyEnum( eEye, type ); + CVRPropertyHelpers propHelpers( m_pProperties ); + return propHelpers.SetProperty( propHelpers.TrackedDeviceToPropertyContainer( k_unTrackedDeviceIndex_Hmd ), prop, pVerts, sizeof( HmdVector2_t ) * unVertCount, k_unHiddenAreaPropertyTag ); +} + + +inline uint32_t CVRHiddenAreaHelpers::GetHiddenArea( EVREye eEye, EHiddenAreaMeshType type, HmdVector2_t *pVerts, uint32_t unVertCount, ETrackedPropertyError *peError ) +{ + ETrackedDeviceProperty prop = GetPropertyEnum( eEye, type ); + CVRPropertyHelpers propHelpers( m_pProperties ); + ETrackedPropertyError propError; + PropertyTypeTag_t unTag; + uint32_t unBytesNeeded = propHelpers.GetProperty( propHelpers.TrackedDeviceToPropertyContainer( k_unTrackedDeviceIndex_Hmd ), prop, pVerts, sizeof( HmdVector2_t )*unVertCount, &unTag, &propError ); + if ( propError == TrackedProp_Success && unTag != k_unHiddenAreaPropertyTag ) + { + propError = TrackedProp_WrongDataType; + unBytesNeeded = 0; + } + + if ( peError ) + { + *peError = propError; + } + + return unBytesNeeded / sizeof( HmdVector2_t ); +} + +} + +// ivrwatchdoghost.h + +namespace vr +{ + +/** This interface is provided by vrclient to allow the driver to make everything wake up */ +class IVRWatchdogHost +{ +public: + /** Client drivers in watchdog mode should call this when they have received a signal from hardware that should + * cause SteamVR to start */ + virtual void WatchdogWakeUp( vr::ETrackedDeviceClass eDeviceClass ) = 0; +}; + +static const char *IVRWatchdogHost_Version = "IVRWatchdogHost_002"; + +}; + + + +// ivrvirtualdisplay.h + +namespace vr +{ + struct PresentInfo_t + { + SharedTextureHandle_t backbufferTextureHandle; + EVSync vsync; + uint64_t nFrameId; + double flVSyncTimeInSeconds; + }; + + // ---------------------------------------------------------------------------------------------- + // Purpose: This component is used for drivers that implement a virtual display (e.g. wireless). + // ---------------------------------------------------------------------------------------------- + class IVRVirtualDisplay + { + public: + + /** Submits final backbuffer for display. */ + virtual void Present( const PresentInfo_t *pPresentInfo, uint32_t unPresentInfoSize ) = 0; + + /** Block until the last presented buffer start scanning out. */ + virtual void WaitForPresent() = 0; + + /** Provides timing data for synchronizing with display. */ + virtual bool GetTimeSinceLastVsync( float *pfSecondsSinceLastVsync, uint64_t *pulFrameCounter ) = 0; + }; + + static const char *IVRVirtualDisplay_Version = "IVRVirtualDisplay_002"; +} + + +// ivrresources.h + +namespace vr +{ + +class IVRResources +{ +public: + + // ------------------------------------ + // Shared Resource Methods + // ------------------------------------ + + /** Loads the specified resource into the provided buffer if large enough. + * Returns the size in bytes of the buffer required to hold the specified resource. */ + virtual uint32_t LoadSharedResource( const char *pchResourceName, char *pchBuffer, uint32_t unBufferLen ) = 0; + + /** Provides the full path to the specified resource. Resource names can include named directories for + * drivers and other things, and this resolves all of those and returns the actual physical path. + * pchResourceTypeDirectory is the subdirectory of resources to look in. */ + virtual uint32_t GetResourceFullPath( const char *pchResourceName, const char *pchResourceTypeDirectory, VR_OUT_STRING() char *pchPathBuffer, uint32_t unBufferLen ) = 0; +}; + +static const char * const IVRResources_Version = "IVRResources_001"; + + +} + +// ivriobuffer.h + +namespace vr +{ + +typedef uint64_t IOBufferHandle_t; +static const uint64_t k_ulInvalidIOBufferHandle = 0; + + enum EIOBufferError + { + IOBuffer_Success = 0, + IOBuffer_OperationFailed = 100, + IOBuffer_InvalidHandle = 101, + IOBuffer_InvalidArgument = 102, + IOBuffer_PathExists = 103, + IOBuffer_PathDoesNotExist = 104, + IOBuffer_Permission = 105, + }; + + enum EIOBufferMode + { + IOBufferMode_Read = 0x0001, + IOBufferMode_Write = 0x0002, + IOBufferMode_Create = 0x0200, + }; + + // ---------------------------------------------------------------------------------------------- + // Purpose: + // ---------------------------------------------------------------------------------------------- + class IVRIOBuffer + { + public: + /** opens an existing or creates a new IOBuffer of unSize bytes */ + virtual vr::EIOBufferError Open( const char *pchPath, vr::EIOBufferMode mode, uint32_t unElementSize, uint32_t unElements, vr::IOBufferHandle_t *pulBuffer ) = 0; + + /** closes a previously opened or created buffer */ + virtual vr::EIOBufferError Close( vr::IOBufferHandle_t ulBuffer ) = 0; + + /** reads up to unBytes from buffer into *pDst, returning number of bytes read in *punRead */ + virtual vr::EIOBufferError Read( vr::IOBufferHandle_t ulBuffer, void *pDst, uint32_t unBytes, uint32_t *punRead ) = 0; + + /** writes unBytes of data from *pSrc into a buffer. */ + virtual vr::EIOBufferError Write( vr::IOBufferHandle_t ulBuffer, void *pSrc, uint32_t unBytes ) = 0; + + /** retrieves the property container of an buffer. */ + virtual vr::PropertyContainerHandle_t PropertyContainer( vr::IOBufferHandle_t ulBuffer ) = 0; + + /** inexpensively checks for readers to allow writers to fast-fail potentially expensive copies and writes. */ + virtual bool HasReaders( vr::IOBufferHandle_t ulBuffer ) = 0; + }; + + static const char * const IVRIOBuffer_Version = "IVRIOBuffer_002"; +} + +// ivrdrivermanager.h + +namespace vr +{ + +class IVRDriverManager +{ +public: + virtual uint32_t GetDriverCount() const = 0; + + /** Returns the length of the number of bytes necessary to hold this string including the trailing null. */ + virtual uint32_t GetDriverName( vr::DriverId_t nDriver, VR_OUT_STRING() char *pchValue, uint32_t unBufferSize ) = 0; + + virtual DriverHandle_t GetDriverHandle( const char *pchDriverName ) = 0; + + virtual bool IsEnabled( vr::DriverId_t nDriver ) const = 0; +}; + +static const char * const IVRDriverManager_Version = "IVRDriverManager_001"; + +} // namespace vr + + + +// ivrdriverspatialanchors.h + +namespace vr +{ + struct SpatialAnchorDriverPose_t + { + /** This position is in the same "world" space (+Y up) as provided by DriverPose_t. */ + vr::HmdQuaternion_t qWorldRotation; + vr::HmdVector3d_t vWorldTranslation; + + /** The pose will automatically start returning VRSpatialAnchorError_NotAvailableInThisUniverse + * if this is nonzero and does not match the current universe ID. */ + uint64_t ulRequiredUniverseId; + + /** When this time expires, SteamVR will start generating + * VREvent_SpatialAnchors_RequestPoseUpdate when the pose is read by an application + * to let the driver know it is still worth updating. + * You can use this facility in several ways: + * 1. Set to -1 to never receive an update request for this pose. The driver + * may still update poses at any time. + * 2. Set to 0 to always receive an update request *after* each time the pose + * is read. The rate of requests could be very high if the application gets + * the pose at framerate. + * 3. If the driver knows there is no reason to update the pose for some amount of + * time, it can set that time here and receive the update request reminder later. + * 4. If the driver plans to automatically update this pose for some amount of time + * (as it gets better information about the virtual location of this anchor) it can + * set that duration here to indicate that no "update requested" reminders are needed. + * When that automatic update period expires, any future interest in the pose will + * be indicated by a pose update request. + * The driver may always update the pose, including during the valid duration. */ + double fValidDuration; // seconds + }; + + class IVRDriverSpatialAnchors + { + public: + + /* NOTE: You must declare support for spatial anchors in your driver manifest. Add + * "spatialAnchorsSupport": true to your manifest. Without that setting, SteamVR + * will short-circuit anchor requests from applications and provide a generic descriptor + * that does not have any of the advantages of true spatial anchors. */ + + /* The driver should monitor for events VREvent_SpatialAnchors_RequestPoseUpdate (for new + * descriptors from applications that need UpdateSpatialAnchorPose()) and for + * VREvent_SpatialAnchors_RequestDescriptorUpdate (for new poses that need UpdateSpatialAnchorDescriptor()). + * For automatic pose updates over time, the driver should keep track of the handles it + * has seen and provide updates when conditions change. If the driver uses fValidDuration, + * it may wait for VREvent_SpatialAnchors_RequestPoseUpdate instead of keeping track itself. */ + + /** Update a pose for a spatial anchor. Should be called when an event notifies the driver that a + * new descriptor has been registered by an application. May be called for any anchor whenever the + * driver has better information about the best virtual coordinate to represent the anchor. Should + * be called on all active handles whenever driver state changes in a way that changes how physical + * world locations map to virtual coordinates (e.g. anything that would cause a universe ID change). + * This fires an event when it is called for the first time (to alert whoever submitted the descriptor). */ + virtual EVRSpatialAnchorError UpdateSpatialAnchorPose( SpatialAnchorHandle_t unHandle, const SpatialAnchorDriverPose_t *pPose ) = 0; + + /** Invalidate any pose associated with the handle and cause future calls to GetSpatialAnchorPose (on + * both the client and driver side) to return the specified error. eError must be one of + * VRSpatialAnchorError_NotYetAvailable, VRSpatialAnchorError_NotAvailableInThisUniverse, or + * VRSpatialAnchorError_PermanentlyUnavailable */ + virtual EVRSpatialAnchorError SetSpatialAnchorPoseError( SpatialAnchorHandle_t unHandle, EVRSpatialAnchorError eError, double fValidDuration ) = 0; + + /** Update the descriptor for a spatial anchor. Should be called when an event notifies the driver + * that a new pose has been registered by an application. May be called for any anchor whenever the + * driver has better or additional information it wants to include in the anchor descriptor. Note, + * however, that the application may never fetch the updated anchor descriptor and may request the + * original descriptor in a future session having ignored the update. + * The supplied descriptor should be only the driver's opaque internal data, not the decorated form that + * is used by clients (wrapped by runtime metadata). The descriptor must not contain non-ASCII characters or + * the two special characters ~ or " + * This fires an event every time it is called. */ + virtual EVRSpatialAnchorError UpdateSpatialAnchorDescriptor( SpatialAnchorHandle_t unHandle, const char *pchDescriptor ) = 0; + + /** Get the pose for a given handle. */ + virtual EVRSpatialAnchorError GetSpatialAnchorPose( SpatialAnchorHandle_t unHandle, SpatialAnchorDriverPose_t *pDriverPoseOut ) = 0; + + /** Get the descriptor for a given handle. This will be VRSpatialAnchorError_NotYetAvailable for handles + * where the driver has not yet built a descriptor. It will be the application-supplied descriptor for previously + * saved anchors that the application is requesting poses for. If the driver has called UpdateSpatialAnchorDescriptor() + * already in this session, it will be the descriptor provided by the driver. + * If bDecorated, returns the descriptor wrapped with runtime metadata suitable for a client to save. Else returns only + * the driver's opaque internal data. + */ + virtual EVRSpatialAnchorError GetSpatialAnchorDescriptor( SpatialAnchorHandle_t unHandle, VR_OUT_STRING() char *pchDescriptorOut, uint32_t *punDescriptorBufferLenInOut, bool bDecorated ) = 0; + + }; + + static const char * const IVRDriverSpatialAnchors_Version = "IVRDriverSpatialAnchors_001"; + +} // namespace vr + +// ivripcresourcemanagerclient.h + +namespace vr +{ + +// ----------------------------------------------------------------------------- +// Purpose: Interact with the IPCResourceManager +// ----------------------------------------------------------------------------- +class IVRIPCResourceManagerClient +{ +public: + /** Create a new tracked Vulkan Image + * + * nImageFormat: in VkFormat + */ + virtual bool NewSharedVulkanImage( uint32_t nImageFormat, uint32_t nWidth, uint32_t nHeight, bool bRenderable, bool bMappable, bool bComputeAccess, uint32_t unMipLevels, uint32_t unArrayLayerCount, uint32_t unAdditionalVkCreateFlags, uint32_t unAdditionalVkUsageFlags, vr::SharedTextureHandle_t *pSharedHandle ) = 0; + + /** Create a new tracked Vulkan Buffer */ + virtual bool NewSharedVulkanBuffer( uint32_t nSize, uint32_t nUsageFlags, vr::SharedTextureHandle_t *pSharedHandle ) = 0; + + /** Create a new tracked Vulkan Semaphore */ + virtual bool NewSharedVulkanSemaphore( bool bCounting, vr::SharedTextureHandle_t *pSharedHandle ) = 0; + + /** Grab a reference to hSharedHandle, and optionally generate a new IPC handle if pNewIpcHandle is not nullptr */ + virtual bool RefResource( vr::SharedTextureHandle_t hSharedHandle, uint64_t *pNewIpcHandle ) = 0; + + /** Drop a reference to hSharedHandle */ + virtual bool UnrefResource( vr::SharedTextureHandle_t hSharedHandle ) = 0; + + /* Get all the DRM formats we support using DMA-BUF images for. + * + * pOutFormatCount and pOutFormats function like Vulkan: + * - If pOutFormats is NULL, then pOutFormatCount will be overwritten with the format count. + * - If pOutFormats is not NULL, then pOutFormatCount specifies the size of the pOutFormats array, + * and will be overwritten with the number of formats written to the array. + * + * If the function fails, false is returned, and pOutFormatCount will be 0. + * Supported on Linux only. + */ + virtual bool GetDmabufFormats( uint32_t *pOutFormatCount, uint32_t *pOutFormats ) = 0; + + /** Get dmabuf modifiers we are allowed to use. + * + * pOutModifierCount and pOutModifiers function like Vulkan: + * - If pOutModifiers is NULL, then pOutModifierCount will be overwritten with the modifier count. + * - If pOutModifiers is not NULL, then pOutModifierCount specifies the size of the pOutModifiers array, + * and will be overwritten with the number of modifiers written to the array. + * + * If modifiers are not supported, a single DRM_FORMAT_MOD_INVALID entry will be returned. + * + * If the function fails, false is returned, and pOutModifierCount will be 0. + * Supported on Linux only. + */ + virtual bool GetDmabufModifiers( vr::EVRApplicationType eApplicationType, uint32_t unDRMFormat, uint32_t *pOutModifierCount, uint64_t *pOutModifiers ) = 0; + + /** Import a dmabuf directly. + * Note: the FD you pass in will be dup'ed, so you must close it yourself. + * This function does NOT take ownership of the fd you pass in. + * Supported on Linux only. + */ + virtual bool ImportDmabuf( vr::EVRApplicationType eApplicationType, vr::DmabufAttributes_t *pDmabufAttributes, vr::SharedTextureHandle_t *pSharedHandle ) = 0; + + /** Consumes an IPC handle (eg. from RefResource) and returns a file-descriptor. + * Caller acquires ownership of fd and is responsible for closing it. + * Supported on Linux only. + */ + virtual bool ReceiveSharedFd( uint64_t ulIpcHandle, int *pOutFd ) = 0; + +protected: + /** Non-deletable */ + virtual ~IVRIPCResourceManagerClient() {}; +}; + +static const char *IVRIPCResourceManagerClient_Version = "IVRIPCResourceManagerClient_003"; + +} + + + +namespace vr +{ +#if !defined( OPENVR_INTERFACE_INTERNAL ) + static const char * const k_InterfaceVersions[] = + { + IVRSettings_Version, + ITrackedDeviceServerDriver_Version, + IVRDisplayComponent_Version, + IVRDriverDirectModeComponent_Version, + IVRCameraComponent_Version, + IServerTrackedDeviceProvider_Version, + IVRWatchdogProvider_Version, + IVRVirtualDisplay_Version, + IVRDriverManager_Version, + IVRResources_Version, + IVRCompositorPluginProvider_Version, + IVRIPCResourceManagerClient_Version, + nullptr + }; + + inline IVRDriverContext *&VRDriverContext() + { + static IVRDriverContext *pHost; + return pHost; + } + + class COpenVRDriverContext + { + public: + COpenVRDriverContext() : m_propertyHelpers(nullptr), m_hiddenAreaHelpers(nullptr) { Clear(); } + void Clear(); + + EVRInitError InitServer(); + EVRInitError InitWatchdog(); + EVRInitError InitCompositor(); + + IVRSettings *VRSettings() + { + if ( m_pVRSettings == nullptr ) + { + EVRInitError eError; + m_pVRSettings = (IVRSettings *)VRDriverContext()->GetGenericInterface( IVRSettings_Version, &eError ); + } + return m_pVRSettings; + } + + IVRProperties *VRPropertiesRaw() + { + if ( m_pVRProperties == nullptr ) + { + EVRInitError eError; + m_pVRProperties = (IVRProperties *)VRDriverContext()->GetGenericInterface( IVRProperties_Version, &eError ); + m_propertyHelpers = CVRPropertyHelpers( m_pVRProperties ); + m_hiddenAreaHelpers = CVRHiddenAreaHelpers( m_pVRProperties ); + } + return m_pVRProperties; + } + + CVRPropertyHelpers *VRProperties() + { + VRPropertiesRaw(); + return &m_propertyHelpers; + } + + CVRHiddenAreaHelpers *VRHiddenArea() + { + VRPropertiesRaw(); + return &m_hiddenAreaHelpers; + } + + IVRServerDriverHost *VRServerDriverHost() + { + if ( m_pVRServerDriverHost == nullptr ) + { + EVRInitError eError; + m_pVRServerDriverHost = (IVRServerDriverHost *)VRDriverContext()->GetGenericInterface( IVRServerDriverHost_Version, &eError ); + } + return m_pVRServerDriverHost; + } + + IVRWatchdogHost *VRWatchdogHost() + { + if ( m_pVRWatchdogHost == nullptr ) + { + EVRInitError eError; + m_pVRWatchdogHost = (IVRWatchdogHost *)VRDriverContext()->GetGenericInterface( IVRWatchdogHost_Version, &eError ); + } + return m_pVRWatchdogHost; + } + + IVRCompositorDriverHost *VRCompositorDriverHost() + { + if ( m_pVRCompositorDriverHost == nullptr ) + { + EVRInitError eError; + m_pVRCompositorDriverHost = ( IVRCompositorDriverHost * )VRDriverContext()->GetGenericInterface( IVRCompositorDriverHost_Version, &eError ); + } + return m_pVRCompositorDriverHost; + } + + IVRDriverLog *VRDriverLog() + { + if ( m_pVRDriverLog == nullptr ) + { + EVRInitError eError; + m_pVRDriverLog = (IVRDriverLog *)VRDriverContext()->GetGenericInterface( IVRDriverLog_Version, &eError ); + } + return m_pVRDriverLog; + } + + DriverHandle_t VR_CALLTYPE VRDriverHandle() + { + return VRDriverContext()->GetDriverHandle(); + } + + IVRDriverManager *VRDriverManager() + { + if ( !m_pVRDriverManager ) + { + EVRInitError eError; + m_pVRDriverManager = (IVRDriverManager *)VRDriverContext()->GetGenericInterface( IVRDriverManager_Version, &eError ); + } + return m_pVRDriverManager; + } + + IVRResources *VRResources() + { + if ( !m_pVRResources ) + { + EVRInitError eError; + m_pVRResources = (IVRResources *)VRDriverContext()->GetGenericInterface( IVRResources_Version, &eError ); + } + return m_pVRResources; + } + + IVRDriverInput *VRDriverInput() + { + if ( !m_pVRDriverInput ) + { + EVRInitError eError; + m_pVRDriverInput = (IVRDriverInput *)VRDriverContext()->GetGenericInterface( IVRDriverInput_Version, &eError ); + } + return m_pVRDriverInput; + } + + IVRIOBuffer *VRIOBuffer() + { + if ( !m_pVRIOBuffer ) + { + EVRInitError eError; + m_pVRIOBuffer = (IVRIOBuffer *)VRDriverContext()->GetGenericInterface( IVRIOBuffer_Version, &eError ); + } + return m_pVRIOBuffer; + } + + IVRDriverSpatialAnchors *VRDriverSpatialAnchors() + { + if ( !m_pVRDriverSpatialAnchors ) + { + EVRInitError eError; + m_pVRDriverSpatialAnchors = (IVRDriverSpatialAnchors *)VRDriverContext()->GetGenericInterface( IVRDriverSpatialAnchors_Version, &eError ); + } + return m_pVRDriverSpatialAnchors; + } + + IVRIPCResourceManagerClient *VRIPCResourceManager() + { + if ( m_pVRIPCResourceManager == nullptr ) + { + EVRInitError eError; + m_pVRIPCResourceManager = ( IVRIPCResourceManagerClient * )VRDriverContext()->GetGenericInterface( IVRIPCResourceManagerClient_Version, &eError ); + } + return m_pVRIPCResourceManager; + } + + private: + CVRPropertyHelpers m_propertyHelpers; + CVRHiddenAreaHelpers m_hiddenAreaHelpers; + + IVRSettings *m_pVRSettings; + IVRProperties *m_pVRProperties; + IVRServerDriverHost *m_pVRServerDriverHost; + IVRWatchdogHost *m_pVRWatchdogHost; + IVRCompositorDriverHost *m_pVRCompositorDriverHost; + IVRDriverLog *m_pVRDriverLog; + IVRDriverManager *m_pVRDriverManager; + IVRResources *m_pVRResources; + IVRDriverInput *m_pVRDriverInput; + IVRIOBuffer *m_pVRIOBuffer; + IVRDriverSpatialAnchors *m_pVRDriverSpatialAnchors; + IVRIPCResourceManagerClient *m_pVRIPCResourceManager; + }; + + inline COpenVRDriverContext &OpenVRInternal_ModuleServerDriverContext() + { + static void *ctx[sizeof( COpenVRDriverContext ) / sizeof( void * )]; + return *(COpenVRDriverContext *)ctx; // bypass zero-init constructor + } + + inline IVRSettings *VR_CALLTYPE VRSettings() { return OpenVRInternal_ModuleServerDriverContext().VRSettings(); } + inline IVRProperties *VR_CALLTYPE VRPropertiesRaw() { return OpenVRInternal_ModuleServerDriverContext().VRPropertiesRaw(); } + inline CVRPropertyHelpers *VR_CALLTYPE VRProperties() { return OpenVRInternal_ModuleServerDriverContext().VRProperties(); } + inline CVRHiddenAreaHelpers *VR_CALLTYPE VRHiddenArea() { return OpenVRInternal_ModuleServerDriverContext().VRHiddenArea(); } + inline IVRDriverLog *VR_CALLTYPE VRDriverLog() { return OpenVRInternal_ModuleServerDriverContext().VRDriverLog(); } + inline IVRServerDriverHost *VR_CALLTYPE VRServerDriverHost() { return OpenVRInternal_ModuleServerDriverContext().VRServerDriverHost(); } + inline IVRWatchdogHost *VR_CALLTYPE VRWatchdogHost() { return OpenVRInternal_ModuleServerDriverContext().VRWatchdogHost(); } + inline IVRCompositorDriverHost *VR_CALLTYPE VRCompositorDriverHost() { return OpenVRInternal_ModuleServerDriverContext().VRCompositorDriverHost(); } + inline DriverHandle_t VR_CALLTYPE VRDriverHandle() { return OpenVRInternal_ModuleServerDriverContext().VRDriverHandle(); } + inline IVRDriverManager *VR_CALLTYPE VRDriverManager() { return OpenVRInternal_ModuleServerDriverContext().VRDriverManager(); } + inline IVRResources *VR_CALLTYPE VRResources() { return OpenVRInternal_ModuleServerDriverContext().VRResources(); } + inline IVRDriverInput *VR_CALLTYPE VRDriverInput() { return OpenVRInternal_ModuleServerDriverContext().VRDriverInput(); } + inline IVRIOBuffer *VR_CALLTYPE VRIOBuffer() { return OpenVRInternal_ModuleServerDriverContext().VRIOBuffer(); } + inline IVRDriverSpatialAnchors *VR_CALLTYPE VRDriverSpatialAnchors() { return OpenVRInternal_ModuleServerDriverContext().VRDriverSpatialAnchors(); } + inline IVRIPCResourceManagerClient *VR_CALLTYPE VRIPCResourceManager() { return OpenVRInternal_ModuleServerDriverContext().VRIPCResourceManager(); } + + inline void COpenVRDriverContext::Clear() + { + m_pVRSettings = nullptr; + m_pVRProperties = nullptr; + m_pVRServerDriverHost = nullptr; + m_pVRWatchdogHost = nullptr; + m_pVRCompositorDriverHost = nullptr; + m_pVRDriverLog = nullptr; + m_pVRDriverManager = nullptr; + m_pVRResources = nullptr; + m_pVRDriverInput = nullptr; + m_pVRIOBuffer = nullptr; + m_pVRDriverSpatialAnchors = nullptr; + m_pVRIPCResourceManager = nullptr; + } + + inline EVRInitError COpenVRDriverContext::InitServer() + { + Clear(); + // VRIPCResourceManager initialized async. + if ( !VRServerDriverHost() + || !VRSettings() + || !VRProperties() + || !VRDriverLog() + || !VRDriverManager() + || !VRResources() ) + return VRInitError_Init_InterfaceNotFound; + return VRInitError_None; + } + + inline EVRInitError COpenVRDriverContext::InitWatchdog() + { + Clear(); + if ( !VRWatchdogHost() + || !VRSettings() + || !VRDriverLog() ) + return VRInitError_Init_InterfaceNotFound; + return VRInitError_None; + } + + inline EVRInitError COpenVRDriverContext::InitCompositor() + { + Clear(); + if ( !VRCompositorDriverHost() + || !VRSettings() + || !VRProperties() + || !VRDriverLog() + || !VRDriverManager() + || !VRResources() ) + return VRInitError_Init_InterfaceNotFound; + return VRInitError_None; + } + + inline EVRInitError InitServerDriverContext( IVRDriverContext *pContext ) + { + VRDriverContext() = pContext; + return OpenVRInternal_ModuleServerDriverContext().InitServer(); + } + + inline EVRInitError InitWatchdogDriverContext( IVRDriverContext *pContext ) + { + VRDriverContext() = pContext; + return OpenVRInternal_ModuleServerDriverContext().InitWatchdog(); + } + + inline EVRInitError InitCompositorDriverContext( IVRDriverContext *pContext ) + { + VRDriverContext() = pContext; + return OpenVRInternal_ModuleServerDriverContext().InitCompositor(); + } + + inline void CleanupDriverContext() + { + VRDriverContext() = nullptr; + OpenVRInternal_ModuleServerDriverContext().Clear(); + } + + #define VR_INIT_SERVER_DRIVER_CONTEXT( pContext ) \ + { \ + vr::EVRInitError eError = vr::InitServerDriverContext( pContext ); \ + if( eError != vr::VRInitError_None ) \ + return eError; \ + } + + #define VR_CLEANUP_SERVER_DRIVER_CONTEXT() \ + vr::CleanupDriverContext(); + + #define VR_INIT_WATCHDOG_DRIVER_CONTEXT( pContext ) \ + { \ + vr::EVRInitError eError = vr::InitWatchdogDriverContext( pContext ); \ + if( eError != vr::VRInitError_None ) \ + return eError; \ + } + + #define VR_CLEANUP_WATCHDOG_DRIVER_CONTEXT() \ + vr::CleanupDriverContext(); + +#define VR_INIT_COMPOSITOR_DRIVER_CONTEXT( pContext ) \ + { \ + vr::EVRInitError eError = vr::InitCompositorDriverContext( pContext ); \ + if( eError != vr::VRInitError_None ) \ + return eError; \ + } + +#define VR_CLEANUP_COMPOSITOR_DRIVER_CONTEXT() \ + vr::CleanupDriverContext(); + + +#endif // OPENVR_INTERFACE_INTERNAL + +} +// End + +#endif // _OPENVR_DRIVER_API + + diff --git a/tools/vr/src/presenter.cpp b/tools/vr/src/presenter.cpp new file mode 100644 index 00000000..d807c076 --- /dev/null +++ b/tools/vr/src/presenter.cpp @@ -0,0 +1,607 @@ +// See presenter.h. +#define VK_NO_PROTOTYPES +#include +typedef struct _XDisplay Display; +typedef unsigned long Window; +typedef unsigned long VisualID; +#include +#include +#include +#include "xdg-shell-client-protocol.h" + +#include "presenter.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); +} + +// ---------------------------------------------------------------- Xlib, loaded at runtime + +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; + } +}; + +// ---------------------------------------------------------------- Vulkan, loaded at runtime + +#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 lock(mutex_); + for (int i = 0; i < 3; i++) { + if (fds[i] < 0) continue; + int fd = dup(fds[i]); // the import owns its fd; the driver keeps its own + if (fd >= 0) pending_.push_back({handles[i], fd, format, w, h}); + } + EnsureThread(); + cv_.notify_one(); + } + + void Remove(uint64_t handle) { + std::lock_guard lock(mutex_); + removals_.push_back(handle); + cv_.notify_one(); + } + + void Submit(const EyeFrame &f) { + std::lock_guard lock(mutex_); + frame_ = f; + frameAt_ = std::chrono::steady_clock::now(); + haveFrame_ = true; + submitted_++; + cv_.notify_one(); + } + + void Shutdown() { + { + std::lock_guard lock(mutex_); + exit_ = true; + cv_.notify_one(); + } + if (thread_.joinable()) thread_.join(); + } + +private: + void EnsureThread() { + if (!started_) { + started_ = true; + thread_ = std::thread([this] { Run(); }); + } + } + + // ------------------------------------------------------------ the presenting thread + + 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; + } + uint64_t shown = 0; + auto lastLog = std::chrono::steady_clock::now(); + while (true) { + EyeFrame f; + std::chrono::steady_clock::time_point at; + std::vector imports; + std::vector removals; + { + std::unique_lock 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; + 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); + // SteamVR hands the textures over at Present with no fence the driver can wait on: give the + // compositor's GPU work a moment to land before copying, as ALVR does (a torn or half-drawn + // frame otherwise). + if (f.texture[0]) std::this_thread::sleep_until(at + guard_); + 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 (last 10 s)", (unsigned long long)shown, + (unsigned long long)submitted_.exchange(0)); + shown = 0; + lastLog = now; + } + } + if (dev_) vk_.DeviceWaitIdle(dev_); + } + + std::chrono::microseconds guard_{4000}; + + // ------------------------------------------------------------ Wayland: straight to DroidDeck's compositor + // The compositor the app runs (wayland-0 in the session's runtime dir, beside gamescope's socket) + // takes a fullscreen toplevel at the panel's size: one hop and one scale fewer than the X11 overlay + // through gamescope, whose output is the session's (often smaller) resolution. + 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_); + // The size: the panel the Cardboard pass draws (two eyes, from the shared pose file), so nothing + // scales the picture down on the way. The compositor's configure carries the session's output + // size, which is often smaller; it is the fallback. + 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); + } + 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; } + // gamescope's first Xwayland (:0) is where its overlays (mangoapp, Steam's) live; SteamVR's + // processes inherit the games' display (:1), so it is not taken from DISPLAY. + 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"); + // gamescope shows an external overlay above whatever has focus (mangoapp is one). + // gamescope composites one external overlay (mangoapp, the perf HUD, usually holds it) and one Steam + // overlay; the Steam overlay slot is free while nothing is shown over a game. + const char *atom = getenv("DROIDDECK_VR_OVERLAY_ATOM"); + if (!atom || !*atom) atom = "STEAM_OVERLAY"; + unsigned long cardinal = 6; // XA_CARDINAL + unsigned long one = 1; // format 32 properties are longs in Xlib + x_.ChangeProperty(dpy_, win_, x_.InternAtom(dpy_, atom, 0), cardinal, 32, 0 /* PropModeReplace */, + (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 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()) 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 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 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; + } + + // Imports one of SteamVR's shared images with the parameters it was made with (ALVR's recipe): + // optimal tiling, mutable format (which keeps it uncompressed), opaque fd, dedicated memory. UNORM so a + // blit copies the bytes rather than decoding sRGB. + void Import(const PendingImport &p) { + Imported im{p.handle}; + im.width = p.width; + im.height = p.height; + // The same format list SteamVR's mutable sRGB images carry: with it Turnip keeps UBWC compression on, + // and the import has to agree on that or it reads compressed data as plain pixels. + 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]) 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]; + // The eye textures come from SteamVR's compositor (another process): take them from the external + // queue family as they are (GENERAL keeps the contents; they are uncompressed). + 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 pending_; + std::vector removals_; + EyeFrame frame_{}; + std::chrono::steady_clock::time_point frameAt_{}; + bool haveFrame_ = false, exit_ = false, started_ = false; + std::atomic 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 images_; + std::vector textures_; +}; + +} // namespace + +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(); } + +} // namespace droiddeck diff --git a/tools/vr/src/presenter.h b/tools/vr/src/presenter.h new file mode 100644 index 00000000..47c6c8ac --- /dev/null +++ b/tools/vr/src/presenter.h @@ -0,0 +1,34 @@ +// Puts SteamVR's eye textures on the screen for the Cardboard driver (driver_droiddeck.cpp). +// +// The driver-direct component hands each frame over as two eye textures: shared Vulkan images, +// dmabufs on Linux. The presenter imports them into a Vulkan device of its own and draws them side by +// side into a fullscreen X11 window on gamescope's Xwayland, marked as an external overlay so gamescope +// shows it above whatever app has focus. DroidDeck's compositor, in Cardboard mode with a +// side-by-side source, then puts the lens distortion on it for the viewer. +// +// SteamVR compositor -> eye textures -> [presenter: blit L|R] -> X11 overlay -> gamescope +// -> DroidDeck compositor -> Cardboard lens pass -> panel +// +// Everything is loaded at runtime (libvulkan.so.1, libX11.so.6): the driver links against nothing +// the cross build image lacks. +#pragma once +#include + +namespace droiddeck { + +struct EyeFrame { + uint64_t texture[2]; // SharedTextureHandle_t per eye + float bounds[2][4]; // uMin, vMin, uMax, vMax per eye +}; + +class Presenter { +public: + // Called from CreateSwapTextureSet: the three images of one set, as SteamVR made them. + static void AddTextures(const uint64_t handles[3], const int fds[3], uint32_t vkFormat, uint32_t width, uint32_t height); + static void RemoveTexture(uint64_t handle); + // Called from Present: the frame's eye textures; returns at once (a thread of its own presents). + static void Submit(const EyeFrame &frame); + static void Shutdown(); +}; + +} // namespace droiddeck diff --git a/tools/vr/src/vk_layer_droiddeck_vr.c b/tools/vr/src/vk_layer_droiddeck_vr.c new file mode 100644 index 00000000..8ac54888 --- /dev/null +++ b/tools/vr/src/vk_layer_droiddeck_vr.c @@ -0,0 +1,1101 @@ +#define _GNU_SOURCE +/* + * VK_LAYER_DROIDDECK_vr: lets SteamVR's vrcompositor create its Vulkan instance on Turnip/KGSL. + * + * vrcompositor always asks for VK_KHR_display and the direct-mode extensions around it (it opens a + * direct mode window even when a driver-direct-mode driver - tools/vr/src/driver_droiddeck.cpp - + * will receive the frames). Turnip's KGSL backend has no displays and refuses to enumerate any GPU + * on an instance with VK_KHR_display enabled. This layer takes those extensions out of the instance + * it creates below itself and answers the display queries on its own: there are no displays. + * + * Shared semaphores: SteamVR shares a timeline semaphore with every image it shares between its + * processes, exported as an opaque fd. Turnip/KGSL cannot export one. Such a semaphore is created + * unshared here instead, its "fd" is a placeholder (a memfd named droiddeck-detached-sem), and a + * process importing a placeholder keeps a detached semaphore whose GPU waits are dropped and whose + * host waits return at once. Each process still orders its own work; what is lost is only the GPU + * ordering between processes, which SteamVR's own IPC (one frame at a time) mostly covers. + * + * Where it runs: droiddeck-steamvr enables it explicitly in SteamVR's own processes (VK_INSTANCE_LAYERS + * + VK_LAYER_PATH). It is also an implicit layer for the whole session, because processes SteamVR did + * not start share textures with it too: Steam's web helper draws its in-VR overlay through SteamVR, + * needs the exportable semaphores, and without them crashes until Steam quits. As an implicit layer + * it stays out of the way: + * - the display stand-in (taking display extensions off, the fake display) happens only while + * SteamVR runs (droiddeck-steamvr's marker file) or where the layer was enabled by name, never in + * an ordinary session: gamescope and games see the driver's extensions unchanged; + * - the semaphore bridge is active in those same processes, and in Steam's web helper once SteamVR + * is installed (its GPU process outlives SteamVR runs); + * - everything else - every process of a session without SteamVR installed - gets the next layer's + * functions untouched. + */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LAYER_NAME "VK_LAYER_DROIDDECK_vr" +#define EXPORT __attribute__((visibility("default"))) + +static void say(const char *fmt, const char *arg) { + if (getenv("DROIDDECK_VR_LAYER_QUIET")) return; + fprintf(stderr, "[droiddeck-vr-layer] "); + fprintf(stderr, fmt, arg); + fputc('\n', stderr); +} + +/* ------------------------------------------------------------------ crash trace (debug) */ + +/* DROIDDECK_VR_CRASHTRACE=1: print a backtrace on a fatal signal. vrcompositor's launcher drops + * LD_PRELOAD, but this layer is loaded into it, so this is the way to see where it dies. */ +#include +#include +#include +#include + +static void crash_handler(int sig, siginfo_t *si, void *uc_) { + ucontext_t *uc = uc_; + char buf[512]; + int n = snprintf(buf, sizeof(buf), "\n[crashtrace] %s: signal %d addr %p pc %p\n", program_invocation_short_name, sig, + si->si_addr, (void *)uc->uc_mcontext.pc); + if (write(2, buf, n)) {} + void *frames[48]; + int count = backtrace(frames, 48); + if (count > 1) frames[1] = (void *)uc->uc_mcontext.pc; + for (int i = 1; i < count; i++) { + Dl_info info; + if (dladdr(frames[i], &info) && info.dli_fname) + n = snprintf(buf, sizeof(buf), "[crashtrace] #%d %s+0x%lx (%s)\n", i, info.dli_fname, + (unsigned long)((char *)frames[i] - (char *)info.dli_fbase), info.dli_sname ? info.dli_sname : "?"); + else + n = snprintf(buf, sizeof(buf), "[crashtrace] #%d %p\n", i, frames[i]); + if (write(2, buf, n)) {} + } + signal(sig, SIG_DFL); + raise(sig); +} + +static void crash_trace_install(void) { + static int done; + if (done++) return; + if (!getenv("DROIDDECK_VR_CRASHTRACE")) { + /* Or a marker beside droiddeck-steamvr's, for processes whose environment is not ours (Steam). */ + const char *rt = getenv("XDG_RUNTIME_DIR"); + char path[512]; + snprintf(path, sizeof(path), "%s/droiddeck-steamvr/crashtrace", rt && *rt ? rt : "/run/droiddeck"); + if (access(path, F_OK) != 0) return; + } + static char stack[65536]; + stack_t old_ss, ss = {.ss_sp = stack, .ss_size = sizeof(stack)}; + if (sigaltstack(NULL, &old_ss) == 0 && (old_ss.ss_flags & SS_DISABLE)) sigaltstack(&ss, NULL); + struct sigaction sa = {0}; + sa.sa_sigaction = crash_handler; + sa.sa_flags = SA_SIGINFO | SA_ONSTACK; + int sigs[] = {SIGSEGV, SIGABRT, SIGBUS, SIGILL, SIGFPE}; + for (unsigned i = 0; i < sizeof(sigs) / sizeof(*sigs); i++) { + /* Only where nothing handles the signal yet: Wine and FEX (Proton games) take SIGSEGV as part + * of their normal work, and replacing their handler crashes the game. */ + struct sigaction old; + if (sigaction(sigs[i], NULL, &old) == 0 && !(old.sa_flags & SA_SIGINFO) && old.sa_handler == SIG_DFL) + sigaction(sigs[i], &sa, NULL); + } +} + +/* ------------------------------------------------------------------ dispatch */ + +struct inst { + VkInstance handle; + PFN_vkGetInstanceProcAddr gipa; + PFN_vkDestroyInstance destroy; + int stripped; /* the app asked for display extensions we took out */ + int active; /* see the top of the file */ +}; +struct dev { + VkDevice handle; + PFN_vkGetDeviceProcAddr gdpa; + PFN_vkDestroyDevice destroy; + int active; + int display; /* its instance has the pretend display */ +}; + +/* SteamVR is running (droiddeck-steamvr start/stop keeps this file), or the layer was asked for by + * name. Only then does the layer stand in for a display. */ +static int steamvr_running(void) { + const char *layers = getenv("VK_INSTANCE_LAYERS"); + if (layers && strstr(layers, "VK_LAYER_DROIDDECK_vr")) return 1; + if (getenv("DROIDDECK_VR_LAYER_FORCE")) return 1; + const char *rt = getenv("XDG_RUNTIME_DIR"); + char path[512]; + snprintf(path, sizeof(path), "%s/droiddeck-steamvr/active", rt && *rt ? rt : "/run/droiddeck"); + return access(path, F_OK) == 0; +} + +/* Whether an instance gets the semaphore bridge. Steam's web helper shares its in-VR overlays with + * SteamVR, and its GPU process lives across SteamVR runs: one created before SteamVR started crashes + * once SteamVR shows the overlay, and after a few of those Steam quits. So it is bridged whenever + * SteamVR is installed, not only while it runs. */ +static int bridge_wanted(void) { + if (steamvr_running()) return 1; + if (strcmp(program_invocation_short_name, "steamwebhelper")) return 0; + const char *steamvr = getenv("STEAMVR"); + char path[512]; + snprintf(path, sizeof(path), "%s/bin/linuxarm64/vrstartup", steamvr && *steamvr ? steamvr : "/opt/steamvr"); + return access(path, F_OK) == 0; +} + +#define MAX_OBJS 64 +static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER; +static struct inst g_inst[MAX_OBJS]; +static struct dev g_dev[MAX_OBJS]; + +/* The dispatch key: the loader's dispatch table pointer at the start of every dispatchable object. */ +static void *key(const void *obj) { return *(void **)obj; } + +static struct inst *inst_for(const void *obj) { + struct inst *r = NULL; + pthread_mutex_lock(&g_lock); + for (int i = 0; i < MAX_OBJS; i++) + if (g_inst[i].handle && key(g_inst[i].handle) == key(obj)) { r = &g_inst[i]; break; } + pthread_mutex_unlock(&g_lock); + return r; +} +static struct dev *dev_for(const void *obj) { + struct dev *r = NULL; + pthread_mutex_lock(&g_lock); + for (int i = 0; i < MAX_OBJS; i++) + if (g_dev[i].handle && key(g_dev[i].handle) == key(obj)) { r = &g_dev[i]; break; } + pthread_mutex_unlock(&g_lock); + return r; +} + +#define NEXT(dev, fn) ((PFN_vk##fn)dev_for(dev)->gdpa(dev, "vk" #fn)) +#define HOOK_AS(str, fn) if (!strcmp(name, str)) return (PFN_vkVoidFunction)fn; + +/* Extensions this layer takes off the instance and stands in for. */ +static const char *const DISPLAY_EXTS[] = { + "VK_KHR_display", "VK_EXT_direct_mode_display", "VK_EXT_display_surface_counter", + "VK_KHR_get_display_properties2", "VK_EXT_acquire_xlib_display", "VK_EXT_acquire_drm_display", +}; +static int is_display_ext(const char *name) { + for (size_t i = 0; i < sizeof(DISPLAY_EXTS) / sizeof(*DISPLAY_EXTS); i++) + if (!strcmp(name, DISPLAY_EXTS[i])) return 1; + return 0; +} + +/* ------------------------------------------------------------------ instance */ + +static VkResult VKAPI_CALL CreateInstance(const VkInstanceCreateInfo *ci, const VkAllocationCallbacks *alloc, + VkInstance *out) { + crash_trace_install(); + VkLayerInstanceCreateInfo *chain = (VkLayerInstanceCreateInfo *)ci->pNext; + while (chain && !(chain->sType == VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO && chain->function == VK_LAYER_LINK_INFO)) + chain = (VkLayerInstanceCreateInfo *)chain->pNext; + if (!chain) return VK_ERROR_INITIALIZATION_FAILED; + PFN_vkGetInstanceProcAddr next_gipa = chain->u.pLayerInfo->pfnNextGetInstanceProcAddr; + chain->u.pLayerInfo = chain->u.pLayerInfo->pNext; + PFN_vkCreateInstance next_create = (PFN_vkCreateInstance)next_gipa(VK_NULL_HANDLE, "vkCreateInstance"); + if (!next_create) return VK_ERROR_INITIALIZATION_FAILED; + + /* Outside SteamVR the instance is created exactly as asked: display extensions stay on. */ + const int vr = steamvr_running(); + VkInstanceCreateInfo mine = *ci; + const char **names = calloc(ci->enabledExtensionCount + 1, sizeof(*names)); + uint32_t n = 0; + int stripped = 0; + for (uint32_t i = 0; i < ci->enabledExtensionCount; i++) { + if (vr && is_display_ext(ci->ppEnabledExtensionNames[i])) { say("taking %s off the instance", ci->ppEnabledExtensionNames[i]); stripped = 1; continue; } + names[n++] = ci->ppEnabledExtensionNames[i]; + } + if (stripped) { + int have = 0; + for (uint32_t i = 0; i < n; i++) have |= !strcmp(names[i], "VK_EXT_headless_surface"); + /* The pretend display's surface is a headless one; it needs VK_KHR_surface too. */ + const char *more[] = {"VK_EXT_headless_surface", "VK_KHR_surface"}; + names = realloc(names, (n + 3) * sizeof(*names)); + if (!have) names[n++] = more[0]; + int surf = 0; + for (uint32_t i = 0; i < n; i++) surf |= !strcmp(names[i], "VK_KHR_surface"); + if (!surf) names[n++] = more[1]; + } + mine.enabledExtensionCount = n; + mine.ppEnabledExtensionNames = names; + VkResult r = next_create(&mine, alloc, out); + free(names); + if (r != VK_SUCCESS) return r; + + const int active = vr || bridge_wanted(); + pthread_mutex_lock(&g_lock); + for (int i = 0; i < MAX_OBJS; i++) { + if (!g_inst[i].handle) { + g_inst[i] = (struct inst){*out, next_gipa, + (PFN_vkDestroyInstance)next_gipa(*out, "vkDestroyInstance"), stripped, active}; + break; + } + } + pthread_mutex_unlock(&g_lock); + return VK_SUCCESS; +} + +static void VKAPI_CALL DestroyInstance(VkInstance instance, const VkAllocationCallbacks *alloc) { + struct inst *in = inst_for(instance); + if (!in) return; + PFN_vkDestroyInstance d = in->destroy; + pthread_mutex_lock(&g_lock); + memset(in, 0, sizeof(*in)); + pthread_mutex_unlock(&g_lock); + if (d) d(instance, alloc); +} + +/* ------------------------------------------------------------------ device */ + +static VkResult VKAPI_CALL CreateDevice(VkPhysicalDevice pd, const VkDeviceCreateInfo *ci, + const VkAllocationCallbacks *alloc, VkDevice *out) { + VkLayerDeviceCreateInfo *chain = (VkLayerDeviceCreateInfo *)ci->pNext; + while (chain && !(chain->sType == VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO && chain->function == VK_LAYER_LINK_INFO)) + chain = (VkLayerDeviceCreateInfo *)chain->pNext; + if (!chain) return VK_ERROR_INITIALIZATION_FAILED; + PFN_vkGetInstanceProcAddr next_gipa = chain->u.pLayerInfo->pfnNextGetInstanceProcAddr; + PFN_vkGetDeviceProcAddr next_gdpa = chain->u.pLayerInfo->pfnNextGetDeviceProcAddr; + chain->u.pLayerInfo = chain->u.pLayerInfo->pNext; + struct inst *in = inst_for(pd); + PFN_vkCreateDevice next_create = (PFN_vkCreateDevice)next_gipa(in ? in->handle : VK_NULL_HANDLE, "vkCreateDevice"); + if (!next_create) return VK_ERROR_INITIALIZATION_FAILED; + const int active = in && in->active; + const int display = in && in->stripped; + + /* VK_EXT_display_control (vblank events on a display) goes too: there is no display. */ + VkDeviceCreateInfo mine = *ci; + const char **names = calloc(ci->enabledExtensionCount + 1, sizeof(*names)); + uint32_t n = 0; + for (uint32_t i = 0; i < ci->enabledExtensionCount; i++) { + if (display && !strcmp(ci->ppEnabledExtensionNames[i], "VK_EXT_display_control")) { say("taking %s off the device", ci->ppEnabledExtensionNames[i]); continue; } + names[n++] = ci->ppEnabledExtensionNames[i]; + } + mine.enabledExtensionCount = n; + mine.ppEnabledExtensionNames = names; + VkResult r = next_create(pd, &mine, alloc, out); + free(names); + if (r != VK_SUCCESS) return r; + pthread_mutex_lock(&g_lock); + for (int i = 0; i < MAX_OBJS; i++) { + if (!g_dev[i].handle) { + g_dev[i] = (struct dev){*out, next_gdpa, (PFN_vkDestroyDevice)next_gdpa(*out, "vkDestroyDevice"), active, display}; + break; + } + } + pthread_mutex_unlock(&g_lock); + return VK_SUCCESS; +} + +static void forget_device_semaphores(VkDevice dev); + +static void VKAPI_CALL DestroyDevice(VkDevice device, const VkAllocationCallbacks *alloc) { + struct dev *d = dev_for(device); + if (!d) return; + forget_device_semaphores(device); + PFN_vkDestroyDevice f = d->destroy; + pthread_mutex_lock(&g_lock); + memset(d, 0, sizeof(*d)); + pthread_mutex_unlock(&g_lock); + if (f) f(device, alloc); +} + +/* ------------------------------------------------------------------ the display stand-in + * + * One pretend display with one mode and one plane. vrcompositor keeps a direct mode window on it even + * when a driver-direct driver takes the frames, and waits on that window's presents, so the display + * needs a working swapchain: its surface is a headless one (VK_EXT_headless_surface, Mesa's WSI). + * The vblank side of VK_EXT_display_control is a 60 Hz clock. Size and rate: + * DROIDDECK_VR_DISPLAY_MODE=WIDTHxHEIGHT@HZ (default 1920x1080@60). */ + +#include + +#define FAKE_DISPLAY ((VkDisplayKHR)(uintptr_t)0xdd0001) +#define FAKE_MODE ((VkDisplayModeKHR)(uintptr_t)0xdd0002) +static uint32_t g_mode_w = 1920, g_mode_h = 1080, g_mode_mhz = 60000; + +static void mode_init(void) { + static int done; + if (done++) return; + const char *m = getenv("DROIDDECK_VR_DISPLAY_MODE"); + unsigned w, h, hz; + if (m && sscanf(m, "%ux%u@%u", &w, &h, &hz) == 3 && w && h && hz) { g_mode_w = w; g_mode_h = h; g_mode_mhz = hz * 1000; } +} + +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 uint64_t frame_ns(void) { return 1000000000000ull / g_mode_mhz; } + +static VkDisplayPropertiesKHR display_props(void) { + mode_init(); + VkDisplayPropertiesKHR p = {0}; + p.display = FAKE_DISPLAY; + p.displayName = "DroidDeck Cardboard"; + p.physicalDimensions = (VkExtent2D){120, 68}; + p.physicalResolution = (VkExtent2D){g_mode_w, g_mode_h}; + p.supportedTransforms = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + p.planeReorderPossible = VK_FALSE; + p.persistentContent = VK_FALSE; + return p; +} + +#define ONE(count, out, value) do { \ + if (!(out)) { *(count) = 1; return VK_SUCCESS; } \ + if (*(count) < 1) return VK_INCOMPLETE; \ + (out)[0] = (value); *(count) = 1; return VK_SUCCESS; } while (0) + +static VkResult VKAPI_CALL GetPhysicalDeviceDisplayPropertiesKHR(VkPhysicalDevice pd, uint32_t *count, VkDisplayPropertiesKHR *p) { + ONE(count, p, display_props()); +} +static VkResult VKAPI_CALL GetPhysicalDeviceDisplayProperties2KHR(VkPhysicalDevice pd, uint32_t *count, VkDisplayProperties2KHR *p) { + if (!p) { *count = 1; return VK_SUCCESS; } + if (*count < 1) return VK_INCOMPLETE; + p[0].displayProperties = display_props(); + *count = 1; + return VK_SUCCESS; +} +static VkDisplayPlanePropertiesKHR plane_props(void) { + VkDisplayPlanePropertiesKHR p = {FAKE_DISPLAY, 0}; + return p; +} +static VkResult VKAPI_CALL GetPhysicalDeviceDisplayPlanePropertiesKHR(VkPhysicalDevice pd, uint32_t *count, VkDisplayPlanePropertiesKHR *p) { + ONE(count, p, plane_props()); +} +static VkResult VKAPI_CALL GetPhysicalDeviceDisplayPlaneProperties2KHR(VkPhysicalDevice pd, uint32_t *count, VkDisplayPlaneProperties2KHR *p) { + if (!p) { *count = 1; return VK_SUCCESS; } + if (*count < 1) return VK_INCOMPLETE; + p[0].displayPlaneProperties = plane_props(); + *count = 1; + return VK_SUCCESS; +} +static VkResult VKAPI_CALL GetDisplayPlaneSupportedDisplaysKHR(VkPhysicalDevice pd, uint32_t plane, uint32_t *count, VkDisplayKHR *d) { + if (plane != 0) { *count = 0; return VK_SUCCESS; } + ONE(count, d, FAKE_DISPLAY); +} +static VkDisplayModePropertiesKHR mode_props(void) { + mode_init(); + VkDisplayModePropertiesKHR m = {FAKE_MODE, {{g_mode_w, g_mode_h}, g_mode_mhz}}; + return m; +} +static VkResult VKAPI_CALL GetDisplayModePropertiesKHR(VkPhysicalDevice pd, VkDisplayKHR d, uint32_t *count, VkDisplayModePropertiesKHR *p) { + ONE(count, p, mode_props()); +} +static VkResult VKAPI_CALL GetDisplayModeProperties2KHR(VkPhysicalDevice pd, VkDisplayKHR d, uint32_t *count, VkDisplayModeProperties2KHR *p) { + if (!p) { *count = 1; return VK_SUCCESS; } + if (*count < 1) return VK_INCOMPLETE; + p[0].displayModeProperties = mode_props(); + *count = 1; + return VK_SUCCESS; +} +static VkResult VKAPI_CALL CreateDisplayModeKHR(VkPhysicalDevice pd, VkDisplayKHR d, const VkDisplayModeCreateInfoKHR *ci, + const VkAllocationCallbacks *a, VkDisplayModeKHR *m) { + *m = FAKE_MODE; + return VK_SUCCESS; +} +static VkResult VKAPI_CALL GetDisplayPlaneCapabilitiesKHR(VkPhysicalDevice pd, VkDisplayModeKHR m, uint32_t plane, VkDisplayPlaneCapabilitiesKHR *c) { + mode_init(); + memset(c, 0, sizeof(*c)); + c->supportedAlpha = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR; + c->maxSrcExtent = c->maxDstExtent = (VkExtent2D){g_mode_w, g_mode_h}; + c->minSrcExtent = c->minDstExtent = (VkExtent2D){1, 1}; + c->maxSrcPosition = c->maxDstPosition = (VkOffset2D){0, 0}; + return VK_SUCCESS; +} +static VkResult VKAPI_CALL GetDisplayPlaneCapabilities2KHR(VkPhysicalDevice pd, const VkDisplayPlaneInfo2KHR *info, VkDisplayPlaneCapabilities2KHR *c) { + return GetDisplayPlaneCapabilitiesKHR(pd, info->mode, info->planeIndex, &c->capabilities); +} +static VkResult VKAPI_CALL CreateDisplayPlaneSurfaceKHR(VkInstance i, const VkDisplaySurfaceCreateInfoKHR *ci, + const VkAllocationCallbacks *a, VkSurfaceKHR *s) { + struct inst *in = inst_for(i); + PFN_vkCreateHeadlessSurfaceEXT headless = in ? (PFN_vkCreateHeadlessSurfaceEXT)in->gipa(i, "vkCreateHeadlessSurfaceEXT") : NULL; + if (!headless) { say("%s: no headless surface to stand in", "vkCreateDisplayPlaneSurfaceKHR"); return VK_ERROR_INITIALIZATION_FAILED; } + VkHeadlessSurfaceCreateInfoEXT hci = {VK_STRUCTURE_TYPE_HEADLESS_SURFACE_CREATE_INFO_EXT, NULL, 0}; + VkResult r = headless(i, &hci, a, s); + say("the display's surface is a headless one (%s)", r == VK_SUCCESS ? "ok" : "failed"); + return r; +} +static VkResult VKAPI_CALL GetPhysicalDeviceSurfaceCapabilities2EXT(VkPhysicalDevice pd, VkSurfaceKHR surface, VkSurfaceCapabilities2EXT *c) { + struct inst *in = inst_for(pd); + PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR caps = + in ? (PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR)in->gipa(in->handle, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR") : NULL; + if (!caps) return VK_ERROR_INITIALIZATION_FAILED; + VkSurfaceCapabilitiesKHR k; + VkResult r = caps(pd, surface, &k); + if (r != VK_SUCCESS) return r; + mode_init(); + if (k.currentExtent.width == UINT32_MAX) k.currentExtent = (VkExtent2D){g_mode_w, g_mode_h}; + c->minImageCount = k.minImageCount; c->maxImageCount = k.maxImageCount; + c->currentExtent = k.currentExtent; c->minImageExtent = k.minImageExtent; c->maxImageExtent = k.maxImageExtent; + c->maxImageArrayLayers = k.maxImageArrayLayers; c->supportedTransforms = k.supportedTransforms; + c->currentTransform = k.currentTransform; c->supportedCompositeAlpha = k.supportedCompositeAlpha; + c->supportedUsageFlags = k.supportedUsageFlags; + c->supportedSurfaceCounters = VK_SURFACE_COUNTER_VBLANK_BIT_EXT; + return VK_SUCCESS; +} +static VkResult VKAPI_CALL ReleaseDisplayEXT(VkPhysicalDevice pd, VkDisplayKHR d) { return VK_SUCCESS; } +static VkResult VKAPI_CALL AcquireDrmDisplayEXT(VkPhysicalDevice pd, int32_t fd, VkDisplayKHR d) { return VK_SUCCESS; } +static VkResult VKAPI_CALL GetDrmDisplayEXT(VkPhysicalDevice pd, int32_t fd, uint32_t conn, VkDisplayKHR *d) { + *d = FAKE_DISPLAY; return VK_SUCCESS; +} +static VkResult VKAPI_CALL AcquireXlibDisplayEXT(VkPhysicalDevice pd, void *dpy, VkDisplayKHR d) { return VK_SUCCESS; } +static VkResult VKAPI_CALL GetRandROutputDisplayEXT(VkPhysicalDevice pd, void *dpy, unsigned long out, VkDisplayKHR *d) { + *d = FAKE_DISPLAY; return VK_SUCCESS; +} + +/* VK_EXT_display_control on the device: a 60 Hz clock. A vblank event's fence comes back signalled + * once the next tick has passed (the caller's vblank thread waits right after registering). */ +static VkResult VKAPI_CALL DisplayPowerControlEXT(VkDevice d, VkDisplayKHR disp, const VkDisplayPowerInfoEXT *info) { return VK_SUCCESS; } +static VkResult signalled_fence(VkDevice d, const VkAllocationCallbacks *a, VkFence *fence) { + VkFenceCreateInfo fci = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, NULL, VK_FENCE_CREATE_SIGNALED_BIT}; + return NEXT(d, CreateFence)(d, &fci, a, fence); +} +static VkResult VKAPI_CALL RegisterDeviceEventEXT(VkDevice d, const VkDeviceEventInfoEXT *info, const VkAllocationCallbacks *a, VkFence *fence) { + return signalled_fence(d, a, fence); +} +static VkResult VKAPI_CALL RegisterDisplayEventEXT(VkDevice d, VkDisplayKHR disp, const VkDisplayEventInfoEXT *info, + const VkAllocationCallbacks *a, VkFence *fence) { + mode_init(); + uint64_t f = frame_ns(), t = now_ns(); + uint64_t next = (t / f + 1) * f; + struct timespec ts = {(time_t)(next / 1000000000ull), (long)(next % 1000000000ull)}; + clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, NULL); + return signalled_fence(d, a, fence); +} +static VkResult VKAPI_CALL GetSwapchainCounterEXT(VkDevice d, VkSwapchainKHR sc, VkSurfaceCounterFlagBitsEXT counter, uint64_t *value) { + mode_init(); + *value = now_ns() / frame_ns(); + return VK_SUCCESS; +} + +/* The pretend display's swapchain, on the headless surface: no swapchain counter (that belongs to + * VK_EXT_display_control, which the device does not have), and a request the headless WSI accepts. */ +static VkResult VKAPI_CALL CreateSwapchainKHR(VkDevice d, const VkSwapchainCreateInfoKHR *ci, const VkAllocationCallbacks *a, + VkSwapchainKHR *out) { + PFN_vkCreateSwapchainKHR next = NEXT(d, CreateSwapchainKHR); + VkSwapchainCreateInfoKHR sized = *ci; + /* A headless surface has no size of its own (UINT32_MAX), and SteamVR passes that through: the + * pretend display's mode is the size. */ + mode_init(); + if (sized.imageExtent.width == UINT32_MAX || sized.imageExtent.height == UINT32_MAX) + sized.imageExtent = (VkExtent2D){g_mode_w, g_mode_h}; + ci = &sized; + VkResult r = next(d, ci, a, out); + if (r == VK_SUCCESS) return r; + char line[256]; + int n = snprintf(line, sizeof(line), "format %d space %d mode %d images %u %ux%u usage 0x%x chain:", ci->imageFormat, + ci->imageColorSpace, ci->presentMode, ci->minImageCount, ci->imageExtent.width, ci->imageExtent.height, + ci->imageUsage); + for (const VkBaseInStructure *p = ci->pNext; p && n < (int)sizeof(line) - 16; p = p->pNext) n += snprintf(line + n, sizeof(line) - n, " %d", p->sType); + say("vkCreateSwapchainKHR refused (%s); retrying for the headless surface", line); + VkSwapchainCreateInfoKHR mine = *ci; + mine.pNext = NULL; /* drops VkSwapchainCounterCreateInfoEXT and friends */ + mine.presentMode = VK_PRESENT_MODE_FIFO_KHR; + mine.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + if (mine.minImageCount < 2) mine.minImageCount = 2; + r = next(d, &mine, a, out); + if (r != VK_SUCCESS) { + /* Last resort: the plainest swapchain the surface can take. */ + mine.imageFormat = VK_FORMAT_B8G8R8A8_UNORM; + mine.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + mine.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + mine.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + r = next(d, &mine, a, out); + } + say("headless swapchain: %s", r == VK_SUCCESS ? "ok" : "still refused"); + return r; +} + +static PFN_vkVoidFunction display_device_hook(const char *name) { + HOOK_AS("vkCreateSwapchainKHR", CreateSwapchainKHR) + HOOK_AS("vkDisplayPowerControlEXT", DisplayPowerControlEXT) + HOOK_AS("vkRegisterDeviceEventEXT", RegisterDeviceEventEXT) + HOOK_AS("vkRegisterDisplayEventEXT", RegisterDisplayEventEXT) + HOOK_AS("vkGetSwapchainCounterEXT", GetSwapchainCounterEXT) + return NULL; +} + +/* ------------------------------------------------------------------ bridged shared semaphores + * + * SteamVR shares a timeline semaphore with every image it shares between processes, exported as an + * opaque fd; Turnip/KGSL cannot export one. Here such a semaphore is created unshared, and its "fd" is + * a memfd (named droiddeck-shared-sem) holding a 64-bit counter. Every process that has the semaphore - + * the creator and each importer - keeps its own real timeline semaphore, and a poller thread in each + * process carries values both ways: a value reached locally goes up into the counter, a value the + * counter shows (another process got there) is signalled locally from the host. GPU waits then work + * as written, about a poll period late. A binary semaphore cannot be bridged that way; GPU waits on an + * imported binary one are dropped (each process still orders its own work). */ + +#include +#include + +#define MAX_SEMS 256 +#define MAX_TYPED 2048 +#define SHARED_NAME "droiddeck-shared-sem" +#define POLL_US 300 + +struct bridged { + VkSemaphore sem; + VkDevice dev; + _Atomic uint64_t *counter; + int fd; /* the memfd, kept by the creator so every export can dup it; -1 for an importer */ + int timeline; /* 0: binary, bridged only by dropping its waits */ + unsigned long ino; /* the memfd's inode, which names the semaphore across processes in traces */ + uint64_t submitted; /* the highest value this process has queued a signal of */ +}; +static pthread_mutex_t g_br_lock = PTHREAD_MUTEX_INITIALIZER; +static struct bridged g_br[MAX_SEMS]; +static VkSemaphore g_timeline_sems[MAX_TYPED]; /* every timeline semaphore this process made */ +static pthread_t g_poller; +static int g_poller_started; + +static int timeline_known(VkSemaphore s) { + int r = 0; + pthread_mutex_lock(&g_br_lock); + for (int i = 0; i < MAX_TYPED && !r; i++) r = g_timeline_sems[i] == s; + pthread_mutex_unlock(&g_br_lock); + return r; +} +static void timeline_note(VkSemaphore s, int add) { + pthread_mutex_lock(&g_br_lock); + for (int i = 0; i < MAX_TYPED; i++) { + if (add ? !g_timeline_sems[i] : g_timeline_sems[i] == s) { g_timeline_sems[i] = add ? s : VK_NULL_HANDLE; break; } + } + pthread_mutex_unlock(&g_br_lock); +} + +/* Under g_br_lock. */ +static struct bridged *bridged_find(VkSemaphore s) { + for (int i = 0; i < MAX_SEMS; i++) if (g_br[i].sem == s) return &g_br[i]; + return NULL; +} +static void bridged_drop(struct bridged *b) { + if (b->counter) munmap((void *)b->counter, 4096); + if (b->fd >= 0) close(b->fd); + memset(b, 0, sizeof(*b)); + b->fd = -1; +} + +/* A `semtrace` file beside droiddeck-steamvr's marker: log every bridged semaphore every 2 s. */ +static int sem_trace_on(void) { + static int checked, on; + if (!checked++) { + const char *rt = getenv("XDG_RUNTIME_DIR"); + char path[512]; + snprintf(path, sizeof(path), "%s/droiddeck-steamvr/semtrace", rt && *rt ? rt : "/run/droiddeck"); + on = access(path, F_OK) == 0; + } + return on; +} + +static void *poller(void *arg) { + unsigned ticks = 0; + for (;;) { + const int trace = sem_trace_on() && (++ticks % (2000000 / POLL_US) == 0); + pthread_mutex_lock(&g_br_lock); + for (int i = 0; i < MAX_SEMS; i++) { + struct bridged *b = &g_br[i]; + if (!b->sem || !b->timeline) continue; + struct dev *d = dev_for(b->dev); + if (!d) continue; + PFN_vkGetSemaphoreCounterValue get = (PFN_vkGetSemaphoreCounterValue)d->gdpa(b->dev, "vkGetSemaphoreCounterValue"); + if (!get) get = (PFN_vkGetSemaphoreCounterValue)d->gdpa(b->dev, "vkGetSemaphoreCounterValueKHR"); + PFN_vkSignalSemaphore sig = (PFN_vkSignalSemaphore)d->gdpa(b->dev, "vkSignalSemaphore"); + if (!sig) sig = (PFN_vkSignalSemaphore)d->gdpa(b->dev, "vkSignalSemaphoreKHR"); + uint64_t local = 0; + if (!get || get(b->dev, b->sem, &local) != VK_SUCCESS) continue; + uint64_t shared = atomic_load(b->counter); + if (trace) { + char line[192]; + snprintf(line, sizeof(line), "%s sem %p %s counter %lu local %llu shared %llu", program_invocation_short_name, + (void *)b->sem, b->fd >= 0 ? "made here" : "imported", b->ino, (unsigned long long)local, (unsigned long long)shared); + say("%s", line); + } + if (local > shared) { + while (local > shared && !atomic_compare_exchange_weak(b->counter, &shared, local)) {} + } else if (shared > local && sig) { + VkSemaphoreSignalInfo si = {VK_STRUCTURE_TYPE_SEMAPHORE_SIGNAL_INFO, NULL, b->sem, shared}; + sig(b->dev, &si); + } + } + pthread_mutex_unlock(&g_br_lock); + usleep(POLL_US); + } + return arg; +} + +static void poller_start(void) { + if (__atomic_exchange_n(&g_poller_started, 1, __ATOMIC_ACQ_REL)) return; + if (pthread_create(&g_poller, NULL, poller, NULL) == 0) pthread_detach(g_poller); + say("%s", "shared semaphores are bridged between processes"); +} + +static _Atomic uint64_t *map_counter(int fd) { + void *p = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + return p == MAP_FAILED ? NULL : (_Atomic uint64_t *)p; +} + +static unsigned long fd_ino(int fd) { + struct stat st; + return fstat(fd, &st) == 0 ? (unsigned long)st.st_ino : 0; +} + +static int is_shared_counter(int fd) { + char path[64], target[128]; + snprintf(path, sizeof(path), "/proc/self/fd/%d", fd); + ssize_t n = readlink(path, target, sizeof(target) - 1); + if (n <= 0) return 0; + target[n] = 0; + return strstr(target, SHARED_NAME) != NULL; +} + +static VkResult VKAPI_CALL CreateSemaphore(VkDevice d, const VkSemaphoreCreateInfo *ci, const VkAllocationCallbacks *a, VkSemaphore *out) { + PFN_vkCreateSemaphore next = NEXT(d, CreateSemaphore); + const VkBaseInStructure *export_info = NULL; + const VkSemaphoreTypeCreateInfo *type_info = NULL; + for (const VkBaseInStructure *p = ci->pNext; p; p = p->pNext) { + if (p->sType == VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO) export_info = p; + if (p->sType == VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO) type_info = (const void *)p; + } + const int timeline = type_info && type_info->semaphoreType == VK_SEMAPHORE_TYPE_TIMELINE; + VkResult r = next(d, ci, a, out); + if (r == VK_SUCCESS) { + if (timeline) timeline_note(*out, 1); + return r; + } + if (!export_info) return r; + /* Rebuild the chain without the export request. */ + VkSemaphoreTypeCreateInfo type = {0}; + VkSemaphoreCreateInfo mine = *ci; + mine.pNext = NULL; + if (type_info) { + type = *type_info; + type.pNext = NULL; + mine.pNext = &type; + } + r = next(d, &mine, a, out); + if (r != VK_SUCCESS) return r; + if (timeline) timeline_note(*out, 1); + int fd = memfd_create(SHARED_NAME, MFD_CLOEXEC); + _Atomic uint64_t *counter = NULL; + if (fd >= 0 && ftruncate(fd, 4096) == 0) counter = map_counter(fd); + if (!counter) { if (fd >= 0) close(fd); return r; } /* unshared, unbridged */ + atomic_store(counter, timeline ? type_info->initialValue : 0); + pthread_mutex_lock(&g_br_lock); + struct bridged *slot = bridged_find(VK_NULL_HANDLE); + if (slot) *slot = (struct bridged){*out, d, counter, fd, timeline, fd_ino(fd), 0}; + pthread_mutex_unlock(&g_br_lock); + if (!slot) { munmap((void *)counter, 4096); close(fd); return r; } + if (timeline) poller_start(); + return r; +} + +static void forget_semaphore(VkSemaphore s) { + pthread_mutex_lock(&g_br_lock); + struct bridged *b = bridged_find(s); + if (b) bridged_drop(b); + pthread_mutex_unlock(&g_br_lock); + timeline_note(s, 0); +} + +static void forget_device_semaphores(VkDevice dev) { + pthread_mutex_lock(&g_br_lock); + for (int i = 0; i < MAX_SEMS; i++) if (g_br[i].sem && g_br[i].dev == dev) bridged_drop(&g_br[i]); + pthread_mutex_unlock(&g_br_lock); +} + +static void VKAPI_CALL DestroySemaphore(VkDevice d, VkSemaphore s, const VkAllocationCallbacks *a) { + forget_semaphore(s); + NEXT(d, DestroySemaphore)(d, s, a); +} + +static VkResult VKAPI_CALL GetSemaphoreFdKHR(VkDevice d, const VkSemaphoreGetFdInfoKHR *info, int *fd) { + pthread_mutex_lock(&g_br_lock); + struct bridged *b = bridged_find(info->semaphore); + int mine = b && b->fd >= 0 ? dup(b->fd) : -2; + pthread_mutex_unlock(&g_br_lock); + if (mine == -2) return NEXT(d, GetSemaphoreFdKHR)(d, info, fd); + *fd = mine; + return mine >= 0 ? VK_SUCCESS : VK_ERROR_TOO_MANY_OBJECTS; +} + +static VkResult VKAPI_CALL ImportSemaphoreFdKHR(VkDevice d, const VkImportSemaphoreFdInfoKHR *info) { + if (info->fd < 0 || !is_shared_counter(info->fd)) return NEXT(d, ImportSemaphoreFdKHR)(d, info); + _Atomic uint64_t *counter = map_counter(info->fd); + const unsigned long ino = fd_ino(info->fd); + close(info->fd); /* a successful import owns the fd */ + const int timeline = timeline_known(info->semaphore); + pthread_mutex_lock(&g_br_lock); + struct bridged *b = bridged_find(info->semaphore); + if (b) bridged_drop(b); + b = bridged_find(VK_NULL_HANDLE); + if (b && counter) *b = (struct bridged){info->semaphore, d, counter, -1, timeline, ino, 0}; + else if (counter) munmap((void *)counter, 4096); + pthread_mutex_unlock(&g_br_lock); + if (timeline) poller_start(); + static int said; + if (said++ < 4) say(timeline ? "imported a shared timeline semaphore (%s): bridged" : "imported a shared binary semaphore (%s): its waits are dropped", + program_invocation_short_name); + return VK_SUCCESS; +} + +/* An imported binary one: nothing here will ever signal it. */ +static int drop_wait(VkSemaphore s) { + pthread_mutex_lock(&g_br_lock); + struct bridged *b = bridged_find(s); + int r = b && !b->timeline && b->fd < 0; + pthread_mutex_unlock(&g_br_lock); + return r; +} + +static uint64_t now_us(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000000u + (uint64_t)ts.tv_nsec / 1000u; +} + +/* How long a submit may wait for another process to reach a shared value before the wait is dropped. */ +#define SUBMIT_GRACE_US 3000 + +/* Whether a submit should drop its GPU wait on s for value. + * + * Turnip on KGSL does not defer a submit that waits on a timeline value nobody has signalled yet ("wait + * before signal"): vkQueueSubmit blocks the calling thread until the value is signalled. SteamVR's + * client waits on a value of the compositor's that the compositor signals only after the client has + * submitted and told it so, which on Turnip deadlocks the client before its first frame (helloxr's + * acquire, DXVK's submit thread in Proton games). For an imported timeline semaphore whose value is + * neither reached nor queued here, the counter gets a short grace period, then the wait goes. */ +static int submit_drop_wait(VkSemaphore s, uint64_t value) { + pthread_mutex_lock(&g_br_lock); + struct bridged *b = bridged_find(s); + if (!b) { pthread_mutex_unlock(&g_br_lock); return 0; } + if (!b->timeline) { int r = b->fd < 0; pthread_mutex_unlock(&g_br_lock); return r; } + const int imported = b->fd < 0; + const uint64_t queued = b->submitted; + VkDevice dev = b->dev; + _Atomic uint64_t *counter = b->counter; + pthread_mutex_unlock(&g_br_lock); + if (!imported || !value || value <= queued) return 0; + struct dev *d = dev_for(dev); + if (!d) return 0; + /* A Vulkan 1.0 device (SteamVR's client interop) has only the KHR names. */ + PFN_vkGetSemaphoreCounterValue get = (PFN_vkGetSemaphoreCounterValue)d->gdpa(dev, "vkGetSemaphoreCounterValue"); + if (!get) get = (PFN_vkGetSemaphoreCounterValue)d->gdpa(dev, "vkGetSemaphoreCounterValueKHR"); + PFN_vkSignalSemaphore sig = (PFN_vkSignalSemaphore)d->gdpa(dev, "vkSignalSemaphore"); + if (!sig) sig = (PFN_vkSignalSemaphore)d->gdpa(dev, "vkSignalSemaphoreKHR"); + if (!get) return 0; + const uint64_t until = now_us() + SUBMIT_GRACE_US; + for (;;) { + uint64_t local = 0; + if (get(dev, s, &local) == VK_SUCCESS && local >= value) return 0; + uint64_t shared = atomic_load(counter); + if (shared >= value && sig) { + VkSemaphoreSignalInfo si = {VK_STRUCTURE_TYPE_SEMAPHORE_SIGNAL_INFO, NULL, s, shared}; + if (sig(dev, &si) == VK_SUCCESS) return 0; + } + if (now_us() >= until) break; + usleep(100); + } + static _Atomic int dropped; + if (atomic_fetch_add(&dropped, 1) == 0) + say("%s: dropping a submit's wait on a shared value nobody has signalled yet (Turnip would block the submit)", + program_invocation_short_name); + return 1; +} + +/* Notes the values this process queues signals of, so its own later waits on them are kept. */ +static void note_signal(VkSemaphore s, uint64_t value) { + pthread_mutex_lock(&g_br_lock); + struct bridged *b = bridged_find(s); + if (b && b->timeline && value > b->submitted) b->submitted = value; + pthread_mutex_unlock(&g_br_lock); +} + +static void note_submit_signals(uint32_t count, const VkSubmitInfo *submits) { + for (uint32_t i = 0; i < count; i++) { + const VkTimelineSemaphoreSubmitInfo *t = NULL; + for (const VkBaseInStructure *p = submits[i].pNext; p; p = p->pNext) + if (p->sType == VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO) t = (const void *)p; + for (uint32_t j = 0; t && j < submits[i].signalSemaphoreCount && j < t->signalSemaphoreValueCount; j++) + note_signal(submits[i].pSignalSemaphores[j], t->pSignalSemaphoreValues[j]); + } +} + +static void note_submit2_signals(uint32_t count, const VkSubmitInfo2 *submits) { + for (uint32_t i = 0; i < count; i++) + for (uint32_t j = 0; j < submits[i].signalSemaphoreInfoCount; j++) + note_signal(submits[i].pSignalSemaphoreInfos[j].semaphore, submits[i].pSignalSemaphoreInfos[j].value); +} + +/* Drops waits on detached semaphores from a list; returns the new count. */ +static uint32_t keep_waits(const VkSemaphore *in, const VkPipelineStageFlags *stages, const uint64_t *values, uint32_t n, + VkSemaphore *out, VkPipelineStageFlags *out_stages, uint64_t *out_values) { + uint32_t k = 0; + for (uint32_t i = 0; i < n; i++) { + if (submit_drop_wait(in[i], values ? values[i] : 0)) continue; + out[k] = in[i]; + if (stages) out_stages[k] = stages[i]; + if (values) out_values[k] = values[i]; + k++; + } + return k; +} + +/* semtrace: the first wait and the first signal of each shared semaphore, and its first few values. */ +static void trace_sem(const char *what, VkSemaphore sem, uint64_t value) { + if (!sem_trace_on() || value > 3) return; + pthread_mutex_lock(&g_br_lock); + struct bridged *b = bridged_find(sem); + unsigned long ino = b ? b->ino : 0; + pthread_mutex_unlock(&g_br_lock); + if (!ino) return; + char line[160]; + snprintf(line, sizeof(line), "%s %s %p value %llu counter %lu", program_invocation_short_name, what, (void *)sem, + (unsigned long long)value, ino); + say("%s", line); +} + +static VkResult VKAPI_CALL QueueSubmit(VkQueue q, uint32_t count, const VkSubmitInfo *submits, VkFence fence) { + PFN_vkQueueSubmit next = (PFN_vkQueueSubmit)dev_for(q)->gdpa(dev_for(q)->handle, "vkQueueSubmit"); + if (sem_trace_on()) + for (uint32_t i = 0; i < count; i++) { + const VkTimelineSemaphoreSubmitInfo *t = NULL; + for (const VkBaseInStructure *p = submits[i].pNext; p; p = p->pNext) + if (p->sType == VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO) t = (const void *)p; + for (uint32_t j = 0; j < submits[i].waitSemaphoreCount; j++) + trace_sem("waits", submits[i].pWaitSemaphores[j], t && j < t->waitSemaphoreValueCount ? t->pWaitSemaphoreValues[j] : 0); + for (uint32_t j = 0; j < submits[i].signalSemaphoreCount; j++) + trace_sem("signals", submits[i].pSignalSemaphores[j], t && j < t->signalSemaphoreValueCount ? t->pSignalSemaphoreValues[j] : 0); + } + /* Waits are judged before this submit's own signals count as queued: an acquire may wait on one + * value and signal the next on the same semaphore. */ + int any = 0; + for (uint32_t i = 0; i < count && !any; i++) { + const VkTimelineSemaphoreSubmitInfo *t = NULL; + for (const VkBaseInStructure *p = submits[i].pNext; p; p = p->pNext) + if (p->sType == VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO) t = (const void *)p; + for (uint32_t j = 0; j < submits[i].waitSemaphoreCount && !any; j++) + any = submit_drop_wait(submits[i].pWaitSemaphores[j], t && j < t->waitSemaphoreValueCount ? t->pWaitSemaphoreValues[j] : 0); + } + if (!any) { + VkResult r = next(q, count, submits, fence); + note_submit_signals(count, submits); + return r; + } + VkSubmitInfo *copy = calloc(count, sizeof(*copy)); + VkTimelineSemaphoreSubmitInfo *tl = calloc(count, sizeof(*tl)); + void **bufs = calloc(count * 3, sizeof(void *)); + for (uint32_t i = 0; i < count; i++) { + copy[i] = submits[i]; + uint32_t n = submits[i].waitSemaphoreCount; + if (!n) continue; + VkSemaphore *sems = calloc(n, sizeof(*sems)); + VkPipelineStageFlags *stages = calloc(n, sizeof(*stages)); + uint64_t *vals = calloc(n, sizeof(*vals)); + bufs[i * 3] = sems; bufs[i * 3 + 1] = stages; bufs[i * 3 + 2] = vals; + const VkTimelineSemaphoreSubmitInfo *t = NULL; + for (const VkBaseInStructure *p = submits[i].pNext; p; p = p->pNext) + if (p->sType == VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO) t = (const void *)p; + uint32_t k = keep_waits(submits[i].pWaitSemaphores, submits[i].pWaitDstStageMask, + t && t->waitSemaphoreValueCount ? t->pWaitSemaphoreValues : NULL, n, sems, stages, vals); + copy[i].waitSemaphoreCount = k; + copy[i].pWaitSemaphores = sems; + copy[i].pWaitDstStageMask = stages; + if (t) { + /* Only this struct is replaced; the rest of the chain is kept behind it. */ + tl[i] = *t; + tl[i].waitSemaphoreValueCount = t->waitSemaphoreValueCount ? k : 0; + tl[i].pWaitSemaphoreValues = vals; + const void *rest = NULL; + const VkBaseInStructure *prev = NULL; + for (const VkBaseInStructure *p = submits[i].pNext; p; prev = p, p = p->pNext) if (p == (const void *)t) { rest = p->pNext; break; } + (void)prev; + tl[i].pNext = rest; + copy[i].pNext = &tl[i]; /* other structs before it are dropped: SteamVR chains only this one */ + } + } + VkResult r = next(q, count, copy, fence); + note_submit_signals(count, submits); + for (uint32_t i = 0; i < count * 3; i++) free(bufs[i]); + free(bufs); free(tl); free(copy); + return r; +} + +static VkResult VKAPI_CALL QueueSubmit2(VkQueue q, uint32_t count, const VkSubmitInfo2 *submits, VkFence fence) { + PFN_vkQueueSubmit2 next = (PFN_vkQueueSubmit2)dev_for(q)->gdpa(dev_for(q)->handle, "vkQueueSubmit2"); + if (!next) next = (PFN_vkQueueSubmit2)dev_for(q)->gdpa(dev_for(q)->handle, "vkQueueSubmit2KHR"); + if (sem_trace_on()) + for (uint32_t i = 0; i < count; i++) { + for (uint32_t j = 0; j < submits[i].waitSemaphoreInfoCount; j++) + trace_sem("waits", submits[i].pWaitSemaphoreInfos[j].semaphore, submits[i].pWaitSemaphoreInfos[j].value); + for (uint32_t j = 0; j < submits[i].signalSemaphoreInfoCount; j++) + trace_sem("signals", submits[i].pSignalSemaphoreInfos[j].semaphore, submits[i].pSignalSemaphoreInfos[j].value); + } + int any = 0; + for (uint32_t i = 0; i < count && !any; i++) + for (uint32_t j = 0; j < submits[i].waitSemaphoreInfoCount && !any; j++) + any = submit_drop_wait(submits[i].pWaitSemaphoreInfos[j].semaphore, submits[i].pWaitSemaphoreInfos[j].value); + if (!any) { + VkResult r = next(q, count, submits, fence); + note_submit2_signals(count, submits); + return r; + } + VkSubmitInfo2 *copy = calloc(count, sizeof(*copy)); + VkSemaphoreSubmitInfo **waits = calloc(count, sizeof(*waits)); + for (uint32_t i = 0; i < count; i++) { + copy[i] = submits[i]; + uint32_t n = submits[i].waitSemaphoreInfoCount, k = 0; + waits[i] = calloc(n + 1, sizeof(VkSemaphoreSubmitInfo)); + for (uint32_t j = 0; j < n; j++) + if (!submit_drop_wait(submits[i].pWaitSemaphoreInfos[j].semaphore, submits[i].pWaitSemaphoreInfos[j].value)) + waits[i][k++] = submits[i].pWaitSemaphoreInfos[j]; + copy[i].waitSemaphoreInfoCount = k; + copy[i].pWaitSemaphoreInfos = waits[i]; + } + VkResult r = next(q, count, copy, fence); + note_submit2_signals(count, submits); + for (uint32_t i = 0; i < count; i++) free(waits[i]); + free(waits); free(copy); + return r; +} + +static VkResult VKAPI_CALL WaitSemaphores(VkDevice d, const VkSemaphoreWaitInfo *info, uint64_t timeout) { + PFN_vkWaitSemaphores next = (PFN_vkWaitSemaphores)dev_for(d)->gdpa(d, "vkWaitSemaphores"); + if (!next) next = (PFN_vkWaitSemaphores)dev_for(d)->gdpa(d, "vkWaitSemaphoresKHR"); + VkSemaphore sems[64]; uint64_t vals[64]; uint32_t k = 0; + for (uint32_t i = 0; i < info->semaphoreCount && k < 64; i++) + if (!drop_wait(info->pSemaphores[i])) { sems[k] = info->pSemaphores[i]; vals[k] = info->pValues[i]; k++; } + if (k == info->semaphoreCount) { + return next(d, info, timeout); + } + if (!k) return VK_SUCCESS; + VkSemaphoreWaitInfo mine = *info; + mine.semaphoreCount = k; mine.pSemaphores = sems; mine.pValues = vals; + return next(d, &mine, timeout); +} + +static VkResult VKAPI_CALL GetSemaphoreCounterValue(VkDevice d, VkSemaphore s, uint64_t *value) { + PFN_vkGetSemaphoreCounterValue next = (PFN_vkGetSemaphoreCounterValue)dev_for(d)->gdpa(d, "vkGetSemaphoreCounterValue"); + if (!next) next = (PFN_vkGetSemaphoreCounterValue)dev_for(d)->gdpa(d, "vkGetSemaphoreCounterValueKHR"); + return next(d, s, value); +} + +/* semtrace also logs how shared (external memory) images are created, to compare both sides. */ +static VkResult VKAPI_CALL CreateImage(VkDevice d, const VkImageCreateInfo *ci, const VkAllocationCallbacks *a, VkImage *out) { + VkResult r = NEXT(d, CreateImage)(d, ci, a, out); + if (!sem_trace_on()) return r; + const VkExternalMemoryImageCreateInfo *ext = NULL; + const VkImageFormatListCreateInfo *list = NULL; + for (const VkBaseInStructure *p = ci->pNext; p; p = p->pNext) { + if (p->sType == VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO) ext = (const void *)p; + if (p->sType == VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO) list = (const void *)p; + } + if (!ext) return r; + char line[256]; + int n = snprintf(line, sizeof(line), "%s image %ux%u format %d flags 0x%x usage 0x%x tiling %d handles 0x%x list", + program_invocation_short_name, ci->extent.width, ci->extent.height, ci->format, ci->flags, ci->usage, + ci->tiling, ext->handleTypes); + for (uint32_t i = 0; list && i < list->viewFormatCount && n < (int)sizeof(line) - 8; i++) + n += snprintf(line + n, sizeof(line) - n, " %d", list->pViewFormats[i]); + say("%s", line); + return r; +} + +static PFN_vkVoidFunction semaphore_hook(const char *name) { + HOOK_AS("vkCreateImage", CreateImage) + HOOK_AS("vkCreateSemaphore", CreateSemaphore) + HOOK_AS("vkDestroySemaphore", DestroySemaphore) + HOOK_AS("vkGetSemaphoreFdKHR", GetSemaphoreFdKHR) + HOOK_AS("vkImportSemaphoreFdKHR", ImportSemaphoreFdKHR) + HOOK_AS("vkQueueSubmit", QueueSubmit) + HOOK_AS("vkQueueSubmit2", QueueSubmit2) + HOOK_AS("vkQueueSubmit2KHR", QueueSubmit2) + HOOK_AS("vkWaitSemaphores", WaitSemaphores) + HOOK_AS("vkWaitSemaphoresKHR", WaitSemaphores) + HOOK_AS("vkGetSemaphoreCounterValue", GetSemaphoreCounterValue) + HOOK_AS("vkGetSemaphoreCounterValueKHR", GetSemaphoreCounterValue) + return NULL; +} + +/* ------------------------------------------------------------------ proc addr */ + +#define HOOK(fn) if (!strcmp(name, "vk" #fn)) return (PFN_vkVoidFunction)fn; + +static PFN_vkVoidFunction display_hook(const char *name) { + HOOK(GetPhysicalDeviceDisplayPropertiesKHR) + HOOK(GetPhysicalDeviceDisplayProperties2KHR) + HOOK(GetPhysicalDeviceDisplayPlanePropertiesKHR) + HOOK(GetPhysicalDeviceDisplayPlaneProperties2KHR) + HOOK(GetDisplayPlaneSupportedDisplaysKHR) + HOOK(GetDisplayModePropertiesKHR) + HOOK(GetDisplayModeProperties2KHR) + HOOK(CreateDisplayModeKHR) + HOOK(GetDisplayPlaneCapabilitiesKHR) + HOOK(GetDisplayPlaneCapabilities2KHR) + HOOK(GetPhysicalDeviceSurfaceCapabilities2EXT) + HOOK(CreateDisplayPlaneSurfaceKHR) + HOOK(ReleaseDisplayEXT) + HOOK(AcquireDrmDisplayEXT) + HOOK(GetDrmDisplayEXT) + HOOK(AcquireXlibDisplayEXT) + HOOK(GetRandROutputDisplayEXT) + return NULL; +} + +EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL DroidDeckVr_GetDeviceProcAddr(VkDevice device, const char *name); + +EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL DroidDeckVr_GetInstanceProcAddr(VkInstance instance, const char *name) { + HOOK(CreateInstance) + HOOK(DestroyInstance) + HOOK(CreateDevice) + HOOK(DestroyDevice) + if (!strcmp(name, "vkGetInstanceProcAddr")) return (PFN_vkVoidFunction)DroidDeckVr_GetInstanceProcAddr; + if (!strcmp(name, "vkGetDeviceProcAddr")) return (PFN_vkVoidFunction)DroidDeckVr_GetDeviceProcAddr; + if (!instance) return NULL; + struct inst *in = inst_for(instance); + if (in && !in->active) return in->gipa(instance, name); + if (in && in->stripped) { + PFN_vkVoidFunction f = display_hook(name); + if (!f) f = display_device_hook(name); + if (f) return f; + } + if (in) { + PFN_vkVoidFunction f = semaphore_hook(name); + if (f && in->gipa(instance, name)) return f; + } + return in ? in->gipa(instance, name) : NULL; +} + +EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL DroidDeckVr_GetDeviceProcAddr(VkDevice device, const char *name) { + HOOK(DestroyDevice) + if (!strcmp(name, "vkGetDeviceProcAddr")) return (PFN_vkVoidFunction)DroidDeckVr_GetDeviceProcAddr; + struct dev *d = dev_for(device); + if (d && !d->active) return d->gdpa(device, name); + if (d && d->display) { + PFN_vkVoidFunction f = display_device_hook(name); + if (f) return f; + } + if (d) { + /* Only where the driver has the function: the hooks call through to it. */ + PFN_vkVoidFunction mine = semaphore_hook(name); + if (mine && d->gdpa(device, name)) return mine; + } + return d ? d->gdpa(device, name) : NULL; +} + +EXPORT VKAPI_ATTR VkResult VKAPI_CALL DroidDeckVr_NegotiateLoaderLayerInterfaceVersion(VkNegotiateLayerInterface *v) { + if (v->loaderLayerInterfaceVersion > 2) v->loaderLayerInterfaceVersion = 2; + v->pfnGetInstanceProcAddr = DroidDeckVr_GetInstanceProcAddr; + v->pfnGetDeviceProcAddr = DroidDeckVr_GetDeviceProcAddr; + v->pfnGetPhysicalDeviceProcAddr = NULL; + return VK_SUCCESS; +} diff --git a/tools/vr/src/wayland_client_stub.c b/tools/vr/src/wayland_client_stub.c new file mode 100644 index 00000000..8b1b6cdf --- /dev/null +++ b/tools/vr/src/wayland_client_stub.c @@ -0,0 +1,34 @@ +/* Link-time stand-in for libwayland-client.so.0 (tools/vr/build.sh): the symbols the presenter uses, so + * the driver links with -z defs against a library the build machine does not have. Never loaded: the + * runtime's real libwayland-client.so.0 satisfies the dependency on the device. */ +#include +#include +#include "wayland-util.h" + +struct wl_display; +struct wl_proxy; +struct wl_event_queue; + +struct wl_display *wl_display_connect(const char *name) { return NULL; } +void wl_display_disconnect(struct wl_display *d) {} +int wl_display_roundtrip(struct wl_display *d) { return -1; } +int wl_display_dispatch(struct wl_display *d) { return -1; } +int wl_display_dispatch_pending(struct wl_display *d) { return -1; } +int wl_display_flush(struct wl_display *d) { return -1; } +int wl_display_get_error(struct wl_display *d) { return 0; } +struct wl_proxy *wl_proxy_marshal_flags(struct wl_proxy *p, uint32_t opcode, const struct wl_interface *i, uint32_t v, + uint32_t flags, ...) { return NULL; } +int wl_proxy_add_listener(struct wl_proxy *p, void (**impl)(void), void *data) { return -1; } +uint32_t wl_proxy_get_version(struct wl_proxy *p) { return 0; } +void wl_proxy_destroy(struct wl_proxy *p) {} +void wl_proxy_set_user_data(struct wl_proxy *p, void *d) {} +void *wl_proxy_get_user_data(struct wl_proxy *p) { return NULL; } + +const struct wl_interface wl_registry_interface; +const struct wl_interface wl_compositor_interface; +const struct wl_interface wl_surface_interface; +const struct wl_interface wl_output_interface; +const struct wl_interface wl_seat_interface; +const struct wl_interface wl_callback_interface; +const struct wl_interface wl_region_interface; +const struct wl_interface wl_buffer_interface; diff --git a/tools/vr/third_party/wayland/README.md b/tools/vr/third_party/wayland/README.md new file mode 100644 index 00000000..d186fff8 --- /dev/null +++ b/tools/vr/third_party/wayland/README.md @@ -0,0 +1,4 @@ +Wayland client headers (MIT, wayland 1.26.0, from the runtime's Arch Linux ARM package) for the SteamVR +driver's presenter (tools/vr/src/presenter.cpp). `src/wayland_client_stub.c` is only linked against at +build time, so the driver gets a `libwayland-client.so.0` dependency without the build needing the guest's +library; the runtime's real one is used on the device. diff --git a/tools/vr/third_party/wayland/wayland-client-core.h b/tools/vr/third_party/wayland/wayland-client-core.h new file mode 100644 index 00000000..e0523e49 --- /dev/null +++ b/tools/vr/third_party/wayland/wayland-client-core.h @@ -0,0 +1,330 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WAYLAND_CLIENT_CORE_H +#define WAYLAND_CLIENT_CORE_H + +#include +#include "wayland-util.h" +#include "wayland-version.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct timespec; + +/** \class wl_proxy + * + * \brief Represents a protocol object on the client side. + * + * A wl_proxy acts as a client side proxy to an object existing in the + * compositor. The proxy is responsible for converting requests made by the + * clients with \ref wl_proxy_marshal() into Wayland's wire format. Events + * coming from the compositor are also handled by the proxy, which will in + * turn call the handler set with \ref wl_proxy_add_listener(). + * + * \note With the exception of function \ref wl_proxy_set_queue(), functions + * accessing a wl_proxy are not normally used by client code. Clients + * should normally use the higher level interface generated by the scanner to + * interact with compositor objects. + * + */ +struct wl_proxy; + +/** \class wl_display + * + * \brief Represents a connection to the compositor and acts as a proxy to + * the wl_display singleton object. + * + * A wl_display object represents a client connection to a Wayland + * compositor. It is created with either \ref wl_display_connect() or + * \ref wl_display_connect_to_fd(). A connection is terminated using + * \ref wl_display_disconnect(). + * + * A wl_display is also used as the \ref wl_proxy for the wl_display + * singleton object on the compositor side. + * + * A wl_display object handles all the data sent from and to the + * compositor. When a \ref wl_proxy marshals a request, it will write its wire + * representation to the display's write buffer. The data is sent to the + * compositor when the client calls \ref wl_display_flush(). + * + * Incoming data is handled in two steps: queueing and dispatching. In the + * queue step, the data coming from the display fd is interpreted and + * added to a queue. On the dispatch step, the handler for the incoming + * event set by the client on the corresponding \ref wl_proxy is called. + * + * A wl_display has at least one event queue, called the default + * queue. Clients can create additional event queues with \ref + * wl_display_create_queue() and assign \ref wl_proxy's to it. Events + * occurring in a particular proxy are always queued in its assigned queue. + * A client can ensure that a certain assumption, such as holding a lock + * or running from a given thread, is true when a proxy event handler is + * called by assigning that proxy to an event queue and making sure that + * this queue is only dispatched when the assumption holds. + * + * The default queue is dispatched by calling \ref wl_display_dispatch(). + * This will dispatch any events queued on the default queue and attempt + * to read from the display fd if it's empty. Events read are then queued + * on the appropriate queues according to the proxy assignment. + * + * A user created queue is dispatched with \ref wl_display_dispatch_queue(). + * This function behaves exactly the same as wl_display_dispatch() + * but it dispatches given queue instead of the default queue. + * + * A real world example of event queue usage is Mesa's implementation of + * eglSwapBuffers() for the Wayland platform. This function might need + * to block until a frame callback is received, but dispatching the default + * queue could cause an event handler on the client to start drawing + * again. This problem is solved using another event queue, so that only + * the events handled by the EGL code are dispatched during the block. + * + * This creates a problem where a thread dispatches a non-default + * queue, reading all the data from the display fd. If the application + * would call \em poll(2) after that it would block, even though there + * might be events queued on the default queue. Those events should be + * dispatched with \ref wl_display_dispatch_pending() or \ref + * wl_display_dispatch_queue_pending() before flushing and blocking. + */ +struct wl_display; + +/** \class wl_event_queue + * + * \brief A queue for \ref wl_proxy object events. + * + * Event queues allows the events on a display to be handled in a thread-safe + * manner. See \ref wl_display for details. + * + */ +struct wl_event_queue; + +/** Destroy proxy after marshalling + * \relates wl_proxy + */ +#define WL_MARSHAL_FLAG_DESTROY (1 << 0) + +void +wl_event_queue_destroy(struct wl_event_queue *queue); + +struct wl_proxy * +wl_proxy_marshal_flags(struct wl_proxy *proxy, uint32_t opcode, + const struct wl_interface *interface, + uint32_t version, + uint32_t flags, ...); + +struct wl_proxy * +wl_proxy_marshal_array_flags(struct wl_proxy *proxy, uint32_t opcode, + const struct wl_interface *interface, + uint32_t version, + uint32_t flags, + union wl_argument *args); + +void +wl_proxy_marshal(struct wl_proxy *p, uint32_t opcode, ...); + +void +wl_proxy_marshal_array(struct wl_proxy *p, uint32_t opcode, + union wl_argument *args); + +struct wl_proxy * +wl_proxy_create(struct wl_proxy *factory, + const struct wl_interface *interface); + +void * +wl_proxy_create_wrapper(void *proxy); + +void +wl_proxy_wrapper_destroy(void *proxy_wrapper); + +struct wl_proxy * +wl_proxy_marshal_constructor(struct wl_proxy *proxy, + uint32_t opcode, + const struct wl_interface *interface, + ...); + +struct wl_proxy * +wl_proxy_marshal_constructor_versioned(struct wl_proxy *proxy, + uint32_t opcode, + const struct wl_interface *interface, + uint32_t version, + ...); + +struct wl_proxy * +wl_proxy_marshal_array_constructor(struct wl_proxy *proxy, + uint32_t opcode, union wl_argument *args, + const struct wl_interface *interface); + +struct wl_proxy * +wl_proxy_marshal_array_constructor_versioned(struct wl_proxy *proxy, + uint32_t opcode, + union wl_argument *args, + const struct wl_interface *interface, + uint32_t version); + +void +wl_proxy_destroy(struct wl_proxy *proxy); + +int +wl_proxy_add_listener(struct wl_proxy *proxy, + void (**implementation)(void), void *data); + +const void * +wl_proxy_get_listener(struct wl_proxy *proxy); + +int +wl_proxy_add_dispatcher(struct wl_proxy *proxy, + wl_dispatcher_func_t dispatcher_func, + const void * dispatcher_data, void *data); + +void +wl_proxy_set_user_data(struct wl_proxy *proxy, void *user_data); + +void * +wl_proxy_get_user_data(struct wl_proxy *proxy); + +uint32_t +wl_proxy_get_version(struct wl_proxy *proxy); + +uint32_t +wl_proxy_get_id(struct wl_proxy *proxy); + +void +wl_proxy_set_tag(struct wl_proxy *proxy, + const char * const *tag); + +const char * const * +wl_proxy_get_tag(struct wl_proxy *proxy); + +const char * +wl_proxy_get_class(struct wl_proxy *proxy); + +const struct wl_interface * +wl_proxy_get_interface(struct wl_proxy *proxy); + +struct wl_display * +wl_proxy_get_display(struct wl_proxy *proxy); + +void +wl_proxy_set_queue(struct wl_proxy *proxy, struct wl_event_queue *queue); + +struct wl_event_queue * +wl_proxy_get_queue(const struct wl_proxy *proxy); + +const char * +wl_event_queue_get_name(const struct wl_event_queue *queue); + +struct wl_display * +wl_display_connect(const char *name); + +struct wl_display * +wl_display_connect_to_fd(int fd); + +void +wl_display_disconnect(struct wl_display *display); + +int +wl_display_get_fd(struct wl_display *display); + +int +wl_display_dispatch(struct wl_display *display); + +int +wl_display_dispatch_queue(struct wl_display *display, + struct wl_event_queue *queue); + +int +wl_display_dispatch_timeout(struct wl_display *display, + const struct timespec *timeout); + +int +wl_display_dispatch_queue_timeout(struct wl_display *display, + struct wl_event_queue *queue, + const struct timespec *timeout); + +int +wl_display_dispatch_queue_pending(struct wl_display *display, + struct wl_event_queue *queue); + +int +wl_display_dispatch_queue_pending_single(struct wl_display *display, + struct wl_event_queue *queue); + +int +wl_display_dispatch_pending(struct wl_display *display); + +int +wl_display_dispatch_pending_single(struct wl_display *display); + +int +wl_display_get_error(struct wl_display *display); + +uint32_t +wl_display_get_protocol_error(struct wl_display *display, + const struct wl_interface **interface, + uint32_t *id); + +int +wl_display_flush(struct wl_display *display); + +int +wl_display_roundtrip_queue(struct wl_display *display, + struct wl_event_queue *queue); + +int +wl_display_roundtrip(struct wl_display *display); + +struct wl_event_queue * +wl_display_create_queue(struct wl_display *display); + +struct wl_event_queue * +wl_display_create_queue_with_name(struct wl_display *display, + const char *name); + +int +wl_display_prepare_read_queue(struct wl_display *display, + struct wl_event_queue *queue); + +int +wl_display_prepare_read(struct wl_display *display); + +void +wl_display_cancel_read(struct wl_display *display); + +int +wl_display_read_events(struct wl_display *display); + +void +wl_log_set_handler_client(wl_log_func_t handler); + +void +wl_display_set_max_buffer_size(struct wl_display *display, + size_t max_buffer_size); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/vr/third_party/wayland/wayland-client-protocol.h b/tools/vr/third_party/wayland/wayland-client-protocol.h new file mode 100644 index 00000000..43a1e4e5 --- /dev/null +++ b/tools/vr/third_party/wayland/wayland-client-protocol.h @@ -0,0 +1,6807 @@ +/* Generated by wayland-scanner 1.26.0 */ + +#ifndef WAYLAND_CLIENT_PROTOCOL_H +#define WAYLAND_CLIENT_PROTOCOL_H + +#include +#include +#include "wayland-client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @page page_wayland The wayland protocol + * @section page_ifaces_wayland Interfaces + * - @subpage page_iface_wl_display - core global object + * - @subpage page_iface_wl_registry - global registry object + * - @subpage page_iface_wl_callback - callback object + * - @subpage page_iface_wl_compositor - the compositor singleton + * - @subpage page_iface_wl_shm_pool - a shared memory pool + * - @subpage page_iface_wl_shm - shared memory support + * - @subpage page_iface_wl_buffer - content for a wl_surface + * - @subpage page_iface_wl_data_offer - offer to transfer data + * - @subpage page_iface_wl_data_source - offer to transfer data + * - @subpage page_iface_wl_data_device - data transfer device + * - @subpage page_iface_wl_data_device_manager - data transfer interface + * - @subpage page_iface_wl_shell - create desktop-style surfaces + * - @subpage page_iface_wl_shell_surface - desktop-style metadata interface + * - @subpage page_iface_wl_surface - an onscreen surface + * - @subpage page_iface_wl_seat - group of input devices + * - @subpage page_iface_wl_pointer - pointer input device + * - @subpage page_iface_wl_keyboard - keyboard input device + * - @subpage page_iface_wl_touch - touchscreen input device + * - @subpage page_iface_wl_output - compositor output region + * - @subpage page_iface_wl_region - region interface + * - @subpage page_iface_wl_subcompositor - sub-surface compositing + * - @subpage page_iface_wl_subsurface - sub-surface interface to a wl_surface + * - @subpage page_iface_wl_fixes - wayland protocol fixes + * @section page_copyright_wayland Copyright + *
+ *
+ * Copyright © 2008-2011 Kristian Høgsberg
+ * Copyright © 2010-2011 Intel Corporation
+ * Copyright © 2012-2013 Collabora, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ * 
+ */ +struct wl_buffer; +struct wl_callback; +struct wl_compositor; +struct wl_data_device; +struct wl_data_device_manager; +struct wl_data_offer; +struct wl_data_source; +struct wl_display; +struct wl_fixes; +struct wl_keyboard; +struct wl_output; +struct wl_pointer; +struct wl_region; +struct wl_registry; +struct wl_seat; +struct wl_shell; +struct wl_shell_surface; +struct wl_shm; +struct wl_shm_pool; +struct wl_subcompositor; +struct wl_subsurface; +struct wl_surface; +struct wl_touch; + +#ifndef WL_DISPLAY_INTERFACE +#define WL_DISPLAY_INTERFACE +/** + * @page page_iface_wl_display wl_display + * @section page_iface_wl_display_desc Description + * + * The core global object. This is a special singleton object. It + * is used for internal Wayland protocol features. + * @section page_iface_wl_display_api API + * See @ref iface_wl_display. + */ +/** + * @defgroup iface_wl_display The wl_display interface + * + * The core global object. This is a special singleton object. It + * is used for internal Wayland protocol features. + */ +extern const struct wl_interface wl_display_interface; +#endif +#ifndef WL_REGISTRY_INTERFACE +#define WL_REGISTRY_INTERFACE +/** + * @page page_iface_wl_registry wl_registry + * @section page_iface_wl_registry_desc Description + * + * The singleton global registry object. The server has a number of + * global objects that are available to all clients. These objects + * typically represent an actual object in the server (for example, + * an input device) or they are singleton objects that provide + * extension functionality. + * + * When a client creates a registry object, the registry object + * will emit a global event for each global currently in the + * registry. Globals come and go as a result of device or + * monitor hotplugs, reconfiguration or other events, and the + * registry will send out global and global_remove events to + * keep the client up to date with the changes. To mark the end + * of the initial burst of events, the client can use the + * wl_display.sync request immediately after calling + * wl_display.get_registry. + * + * A client can bind to a global object by using the bind + * request. This creates a client-side handle that lets the object + * emit events to the client and lets the client invoke requests on + * the object. + * @section page_iface_wl_registry_api API + * See @ref iface_wl_registry. + */ +/** + * @defgroup iface_wl_registry The wl_registry interface + * + * The singleton global registry object. The server has a number of + * global objects that are available to all clients. These objects + * typically represent an actual object in the server (for example, + * an input device) or they are singleton objects that provide + * extension functionality. + * + * When a client creates a registry object, the registry object + * will emit a global event for each global currently in the + * registry. Globals come and go as a result of device or + * monitor hotplugs, reconfiguration or other events, and the + * registry will send out global and global_remove events to + * keep the client up to date with the changes. To mark the end + * of the initial burst of events, the client can use the + * wl_display.sync request immediately after calling + * wl_display.get_registry. + * + * A client can bind to a global object by using the bind + * request. This creates a client-side handle that lets the object + * emit events to the client and lets the client invoke requests on + * the object. + */ +extern const struct wl_interface wl_registry_interface; +#endif +#ifndef WL_CALLBACK_INTERFACE +#define WL_CALLBACK_INTERFACE +/** + * @page page_iface_wl_callback wl_callback + * @section page_iface_wl_callback_desc Description + * + * Clients can handle the 'done' event to get notified when + * the related request is done. + * + * Note, because wl_callback objects are created from multiple independent + * factory interfaces, the wl_callback interface is frozen at version 1. + * @section page_iface_wl_callback_api API + * See @ref iface_wl_callback. + */ +/** + * @defgroup iface_wl_callback The wl_callback interface + * + * Clients can handle the 'done' event to get notified when + * the related request is done. + * + * Note, because wl_callback objects are created from multiple independent + * factory interfaces, the wl_callback interface is frozen at version 1. + */ +extern const struct wl_interface wl_callback_interface; +#endif +#ifndef WL_COMPOSITOR_INTERFACE +#define WL_COMPOSITOR_INTERFACE +/** + * @page page_iface_wl_compositor wl_compositor + * @section page_iface_wl_compositor_desc Description + * + * A compositor. This object is a singleton global. The + * compositor is in charge of combining the contents of multiple + * surfaces into one displayable output. + * @section page_iface_wl_compositor_api API + * See @ref iface_wl_compositor. + */ +/** + * @defgroup iface_wl_compositor The wl_compositor interface + * + * A compositor. This object is a singleton global. The + * compositor is in charge of combining the contents of multiple + * surfaces into one displayable output. + */ +extern const struct wl_interface wl_compositor_interface; +#endif +#ifndef WL_SHM_POOL_INTERFACE +#define WL_SHM_POOL_INTERFACE +/** + * @page page_iface_wl_shm_pool wl_shm_pool + * @section page_iface_wl_shm_pool_desc Description + * + * The wl_shm_pool object encapsulates a piece of memory shared + * between the compositor and client. Through the wl_shm_pool + * object, the client can allocate shared memory wl_buffer objects. + * All objects created through the same pool share the same + * underlying mapped memory. Reusing the mapped memory avoids the + * setup/teardown overhead and is useful when interactively resizing + * a surface or for many small buffers. + * @section page_iface_wl_shm_pool_api API + * See @ref iface_wl_shm_pool. + */ +/** + * @defgroup iface_wl_shm_pool The wl_shm_pool interface + * + * The wl_shm_pool object encapsulates a piece of memory shared + * between the compositor and client. Through the wl_shm_pool + * object, the client can allocate shared memory wl_buffer objects. + * All objects created through the same pool share the same + * underlying mapped memory. Reusing the mapped memory avoids the + * setup/teardown overhead and is useful when interactively resizing + * a surface or for many small buffers. + */ +extern const struct wl_interface wl_shm_pool_interface; +#endif +#ifndef WL_SHM_INTERFACE +#define WL_SHM_INTERFACE +/** + * @page page_iface_wl_shm wl_shm + * @section page_iface_wl_shm_desc Description + * + * A singleton global object that provides support for shared + * memory. + * + * Clients can create wl_shm_pool objects using the create_pool + * request. + * + * On binding the wl_shm object one or more format events + * are emitted to inform clients about the valid pixel formats + * that can be used for buffers. + * @section page_iface_wl_shm_api API + * See @ref iface_wl_shm. + */ +/** + * @defgroup iface_wl_shm The wl_shm interface + * + * A singleton global object that provides support for shared + * memory. + * + * Clients can create wl_shm_pool objects using the create_pool + * request. + * + * On binding the wl_shm object one or more format events + * are emitted to inform clients about the valid pixel formats + * that can be used for buffers. + */ +extern const struct wl_interface wl_shm_interface; +#endif +#ifndef WL_BUFFER_INTERFACE +#define WL_BUFFER_INTERFACE +/** + * @page page_iface_wl_buffer wl_buffer + * @section page_iface_wl_buffer_desc Description + * + * A buffer provides the content for a wl_surface. Buffers are + * created through factory interfaces such as wl_shm, wp_linux_buffer_params + * (from the linux-dmabuf protocol extension) or similar. It has a width and + * a height and can be attached to a wl_surface, but the mechanism by which a + * client provides and updates the contents is defined by the buffer factory + * interface. + * + * Color channels are assumed to be electrical rather than optical (in other + * words, encoded with a transfer function) unless otherwise specified. If + * the buffer uses a format that has an alpha channel, the alpha channel is + * assumed to be premultiplied into the electrical color channel values + * (after transfer function encoding) unless otherwise specified. + * + * Note, because wl_buffer objects are created from multiple independent + * factory interfaces, the wl_buffer interface is frozen at version 1. + * @section page_iface_wl_buffer_api API + * See @ref iface_wl_buffer. + */ +/** + * @defgroup iface_wl_buffer The wl_buffer interface + * + * A buffer provides the content for a wl_surface. Buffers are + * created through factory interfaces such as wl_shm, wp_linux_buffer_params + * (from the linux-dmabuf protocol extension) or similar. It has a width and + * a height and can be attached to a wl_surface, but the mechanism by which a + * client provides and updates the contents is defined by the buffer factory + * interface. + * + * Color channels are assumed to be electrical rather than optical (in other + * words, encoded with a transfer function) unless otherwise specified. If + * the buffer uses a format that has an alpha channel, the alpha channel is + * assumed to be premultiplied into the electrical color channel values + * (after transfer function encoding) unless otherwise specified. + * + * Note, because wl_buffer objects are created from multiple independent + * factory interfaces, the wl_buffer interface is frozen at version 1. + */ +extern const struct wl_interface wl_buffer_interface; +#endif +#ifndef WL_DATA_OFFER_INTERFACE +#define WL_DATA_OFFER_INTERFACE +/** + * @page page_iface_wl_data_offer wl_data_offer + * @section page_iface_wl_data_offer_desc Description + * + * A wl_data_offer represents a piece of data offered for transfer + * by another client (the source client). It is used by the + * copy-and-paste and drag-and-drop mechanisms. The offer + * describes the different mime types that the data can be + * converted to and provides the mechanism for transferring the + * data directly from the source client. + * @section page_iface_wl_data_offer_api API + * See @ref iface_wl_data_offer. + */ +/** + * @defgroup iface_wl_data_offer The wl_data_offer interface + * + * A wl_data_offer represents a piece of data offered for transfer + * by another client (the source client). It is used by the + * copy-and-paste and drag-and-drop mechanisms. The offer + * describes the different mime types that the data can be + * converted to and provides the mechanism for transferring the + * data directly from the source client. + */ +extern const struct wl_interface wl_data_offer_interface; +#endif +#ifndef WL_DATA_SOURCE_INTERFACE +#define WL_DATA_SOURCE_INTERFACE +/** + * @page page_iface_wl_data_source wl_data_source + * @section page_iface_wl_data_source_desc Description + * + * The wl_data_source object is the source side of a wl_data_offer. + * It is created by the source client in a data transfer and + * provides a way to describe the offered data and a way to respond + * to requests to transfer the data. + * @section page_iface_wl_data_source_api API + * See @ref iface_wl_data_source. + */ +/** + * @defgroup iface_wl_data_source The wl_data_source interface + * + * The wl_data_source object is the source side of a wl_data_offer. + * It is created by the source client in a data transfer and + * provides a way to describe the offered data and a way to respond + * to requests to transfer the data. + */ +extern const struct wl_interface wl_data_source_interface; +#endif +#ifndef WL_DATA_DEVICE_INTERFACE +#define WL_DATA_DEVICE_INTERFACE +/** + * @page page_iface_wl_data_device wl_data_device + * @section page_iface_wl_data_device_desc Description + * + * There is one wl_data_device per seat which can be obtained + * from the global wl_data_device_manager singleton. + * + * A wl_data_device provides access to inter-client data transfer + * mechanisms such as copy-and-paste and drag-and-drop. + * @section page_iface_wl_data_device_api API + * See @ref iface_wl_data_device. + */ +/** + * @defgroup iface_wl_data_device The wl_data_device interface + * + * There is one wl_data_device per seat which can be obtained + * from the global wl_data_device_manager singleton. + * + * A wl_data_device provides access to inter-client data transfer + * mechanisms such as copy-and-paste and drag-and-drop. + */ +extern const struct wl_interface wl_data_device_interface; +#endif +#ifndef WL_DATA_DEVICE_MANAGER_INTERFACE +#define WL_DATA_DEVICE_MANAGER_INTERFACE +/** + * @page page_iface_wl_data_device_manager wl_data_device_manager + * @section page_iface_wl_data_device_manager_desc Description + * + * The wl_data_device_manager is a singleton global object that + * provides access to inter-client data transfer mechanisms such as + * copy-and-paste and drag-and-drop. These mechanisms are tied to + * a wl_seat and this interface lets a client get a wl_data_device + * corresponding to a wl_seat. + * + * Depending on the version bound, the objects created from the bound + * wl_data_device_manager object will have different requirements for + * functioning properly. See wl_data_source.set_actions, + * wl_data_offer.accept and wl_data_offer.finish for details. + * @section page_iface_wl_data_device_manager_api API + * See @ref iface_wl_data_device_manager. + */ +/** + * @defgroup iface_wl_data_device_manager The wl_data_device_manager interface + * + * The wl_data_device_manager is a singleton global object that + * provides access to inter-client data transfer mechanisms such as + * copy-and-paste and drag-and-drop. These mechanisms are tied to + * a wl_seat and this interface lets a client get a wl_data_device + * corresponding to a wl_seat. + * + * Depending on the version bound, the objects created from the bound + * wl_data_device_manager object will have different requirements for + * functioning properly. See wl_data_source.set_actions, + * wl_data_offer.accept and wl_data_offer.finish for details. + */ +extern const struct wl_interface wl_data_device_manager_interface; +#endif +#ifndef WL_SHELL_INTERFACE +#define WL_SHELL_INTERFACE +/** + * @page page_iface_wl_shell wl_shell + * @section page_iface_wl_shell_desc Description + * + * This interface is implemented by servers that provide + * desktop-style user interfaces. + * + * It allows clients to associate a wl_shell_surface with + * a basic surface. + * + * Note! This protocol is deprecated and not intended for production use. + * For desktop-style user interfaces, use xdg_shell. Compositors and clients + * should not implement this interface. + * @section page_iface_wl_shell_api API + * See @ref iface_wl_shell. + */ +/** + * @defgroup iface_wl_shell The wl_shell interface + * + * This interface is implemented by servers that provide + * desktop-style user interfaces. + * + * It allows clients to associate a wl_shell_surface with + * a basic surface. + * + * Note! This protocol is deprecated and not intended for production use. + * For desktop-style user interfaces, use xdg_shell. Compositors and clients + * should not implement this interface. + */ +extern const struct wl_interface wl_shell_interface; +#endif +#ifndef WL_SHELL_SURFACE_INTERFACE +#define WL_SHELL_SURFACE_INTERFACE +/** + * @page page_iface_wl_shell_surface wl_shell_surface + * @section page_iface_wl_shell_surface_desc Description + * + * An interface that may be implemented by a wl_surface, for + * implementations that provide a desktop-style user interface. + * + * It provides requests to treat surfaces like toplevel, fullscreen + * or popup windows, move, resize or maximize them, associate + * metadata like title and class, etc. + * + * On the server side the object is automatically destroyed when + * the related wl_surface is destroyed. On the client side, + * wl_shell_surface_destroy() must be called before destroying + * the wl_surface object. + * @section page_iface_wl_shell_surface_api API + * See @ref iface_wl_shell_surface. + */ +/** + * @defgroup iface_wl_shell_surface The wl_shell_surface interface + * + * An interface that may be implemented by a wl_surface, for + * implementations that provide a desktop-style user interface. + * + * It provides requests to treat surfaces like toplevel, fullscreen + * or popup windows, move, resize or maximize them, associate + * metadata like title and class, etc. + * + * On the server side the object is automatically destroyed when + * the related wl_surface is destroyed. On the client side, + * wl_shell_surface_destroy() must be called before destroying + * the wl_surface object. + */ +extern const struct wl_interface wl_shell_surface_interface; +#endif +#ifndef WL_SURFACE_INTERFACE +#define WL_SURFACE_INTERFACE +/** + * @page page_iface_wl_surface wl_surface + * @section page_iface_wl_surface_desc Description + * + * A surface is a rectangular area that may be displayed on zero + * or more outputs, and shown any number of times at the compositor's + * discretion. They can present wl_buffers, receive user input, and + * define a local coordinate system. + * + * The size of a surface (and relative positions on it) is described + * in surface-local coordinates, which may differ from the buffer + * coordinates of the pixel content, in case a buffer_transform + * or a buffer_scale is used. + * + * A surface without a "role" is fairly useless: a compositor does + * not know where, when or how to present it. The role is the + * purpose of a wl_surface. Examples of roles are a cursor for a + * pointer (as set by wl_pointer.set_cursor), a drag icon + * (wl_data_device.start_drag), a sub-surface + * (wl_subcompositor.get_subsurface), and a window as defined by a + * shell protocol (e.g. wl_shell.get_shell_surface). + * + * A surface can have only one role at a time. Initially a + * wl_surface does not have a role. Once a wl_surface is given a + * role, it is set permanently for the whole lifetime of the + * wl_surface object. Giving the current role again is allowed, + * unless explicitly forbidden by the relevant interface + * specification. + * + * Surface roles are given by requests in other interfaces such as + * wl_pointer.set_cursor. The request should explicitly mention + * that this request gives a role to a wl_surface. Often, this + * request also creates a new protocol object that represents the + * role and adds additional functionality to wl_surface. When a + * client wants to destroy a wl_surface, they must destroy this role + * object before the wl_surface, otherwise a defunct_role_object error is + * sent. + * + * Destroying the role object does not remove the role from the + * wl_surface, but it may stop the wl_surface from "playing the role". + * For instance, if a wl_subsurface object is destroyed, the wl_surface + * it was created for will be unmapped and forget its position and + * z-order. It is allowed to create a wl_subsurface for the same + * wl_surface again, but it is not allowed to use the wl_surface as + * a cursor (cursor is a different role than sub-surface, and role + * switching is not allowed). + * @section page_iface_wl_surface_api API + * See @ref iface_wl_surface. + */ +/** + * @defgroup iface_wl_surface The wl_surface interface + * + * A surface is a rectangular area that may be displayed on zero + * or more outputs, and shown any number of times at the compositor's + * discretion. They can present wl_buffers, receive user input, and + * define a local coordinate system. + * + * The size of a surface (and relative positions on it) is described + * in surface-local coordinates, which may differ from the buffer + * coordinates of the pixel content, in case a buffer_transform + * or a buffer_scale is used. + * + * A surface without a "role" is fairly useless: a compositor does + * not know where, when or how to present it. The role is the + * purpose of a wl_surface. Examples of roles are a cursor for a + * pointer (as set by wl_pointer.set_cursor), a drag icon + * (wl_data_device.start_drag), a sub-surface + * (wl_subcompositor.get_subsurface), and a window as defined by a + * shell protocol (e.g. wl_shell.get_shell_surface). + * + * A surface can have only one role at a time. Initially a + * wl_surface does not have a role. Once a wl_surface is given a + * role, it is set permanently for the whole lifetime of the + * wl_surface object. Giving the current role again is allowed, + * unless explicitly forbidden by the relevant interface + * specification. + * + * Surface roles are given by requests in other interfaces such as + * wl_pointer.set_cursor. The request should explicitly mention + * that this request gives a role to a wl_surface. Often, this + * request also creates a new protocol object that represents the + * role and adds additional functionality to wl_surface. When a + * client wants to destroy a wl_surface, they must destroy this role + * object before the wl_surface, otherwise a defunct_role_object error is + * sent. + * + * Destroying the role object does not remove the role from the + * wl_surface, but it may stop the wl_surface from "playing the role". + * For instance, if a wl_subsurface object is destroyed, the wl_surface + * it was created for will be unmapped and forget its position and + * z-order. It is allowed to create a wl_subsurface for the same + * wl_surface again, but it is not allowed to use the wl_surface as + * a cursor (cursor is a different role than sub-surface, and role + * switching is not allowed). + */ +extern const struct wl_interface wl_surface_interface; +#endif +#ifndef WL_SEAT_INTERFACE +#define WL_SEAT_INTERFACE +/** + * @page page_iface_wl_seat wl_seat + * @section page_iface_wl_seat_desc Description + * + * A seat is a group of keyboards, pointer and touch devices. This + * object is published as a global during start up, or when such a + * device is hot plugged. A seat typically has a pointer and + * maintains a keyboard focus and a pointer focus. + * @section page_iface_wl_seat_api API + * See @ref iface_wl_seat. + */ +/** + * @defgroup iface_wl_seat The wl_seat interface + * + * A seat is a group of keyboards, pointer and touch devices. This + * object is published as a global during start up, or when such a + * device is hot plugged. A seat typically has a pointer and + * maintains a keyboard focus and a pointer focus. + */ +extern const struct wl_interface wl_seat_interface; +#endif +#ifndef WL_POINTER_INTERFACE +#define WL_POINTER_INTERFACE +/** + * @page page_iface_wl_pointer wl_pointer + * @section page_iface_wl_pointer_desc Description + * + * The wl_pointer interface represents one or more input devices, + * such as mice, which control the pointer location and pointer_focus + * of a seat. + * + * The wl_pointer interface generates motion, enter and leave + * events for the surfaces that the pointer is located over, + * and button and axis events for button presses, button releases + * and scrolling. + * @section page_iface_wl_pointer_api API + * See @ref iface_wl_pointer. + */ +/** + * @defgroup iface_wl_pointer The wl_pointer interface + * + * The wl_pointer interface represents one or more input devices, + * such as mice, which control the pointer location and pointer_focus + * of a seat. + * + * The wl_pointer interface generates motion, enter and leave + * events for the surfaces that the pointer is located over, + * and button and axis events for button presses, button releases + * and scrolling. + */ +extern const struct wl_interface wl_pointer_interface; +#endif +#ifndef WL_KEYBOARD_INTERFACE +#define WL_KEYBOARD_INTERFACE +/** + * @page page_iface_wl_keyboard wl_keyboard + * @section page_iface_wl_keyboard_desc Description + * + * The wl_keyboard interface represents one or more keyboards + * associated with a seat. + * + * Each wl_keyboard has the following logical state: + * + * - an active surface (possibly null), + * - the keys currently logically down, + * - the active modifiers, + * - the active group. + * + * By default, the active surface is null, the keys currently logically down + * are empty, the active modifiers and the active group are 0. + * @section page_iface_wl_keyboard_api API + * See @ref iface_wl_keyboard. + */ +/** + * @defgroup iface_wl_keyboard The wl_keyboard interface + * + * The wl_keyboard interface represents one or more keyboards + * associated with a seat. + * + * Each wl_keyboard has the following logical state: + * + * - an active surface (possibly null), + * - the keys currently logically down, + * - the active modifiers, + * - the active group. + * + * By default, the active surface is null, the keys currently logically down + * are empty, the active modifiers and the active group are 0. + */ +extern const struct wl_interface wl_keyboard_interface; +#endif +#ifndef WL_TOUCH_INTERFACE +#define WL_TOUCH_INTERFACE +/** + * @page page_iface_wl_touch wl_touch + * @section page_iface_wl_touch_desc Description + * + * The wl_touch interface represents a touchscreen + * associated with a seat. + * + * Touch interactions can consist of one or more contacts. + * For each contact, a series of events is generated, starting + * with a down event, followed by zero or more motion events, + * and ending with an up event. Events relating to the same + * contact point can be identified by the ID of the sequence. + * @section page_iface_wl_touch_api API + * See @ref iface_wl_touch. + */ +/** + * @defgroup iface_wl_touch The wl_touch interface + * + * The wl_touch interface represents a touchscreen + * associated with a seat. + * + * Touch interactions can consist of one or more contacts. + * For each contact, a series of events is generated, starting + * with a down event, followed by zero or more motion events, + * and ending with an up event. Events relating to the same + * contact point can be identified by the ID of the sequence. + */ +extern const struct wl_interface wl_touch_interface; +#endif +#ifndef WL_OUTPUT_INTERFACE +#define WL_OUTPUT_INTERFACE +/** + * @page page_iface_wl_output wl_output + * @section page_iface_wl_output_desc Description + * + * An output describes part of the compositor geometry. The + * compositor works in the 'compositor coordinate system' and an + * output corresponds to a rectangular area in that space that is + * actually visible. This typically corresponds to a monitor that + * displays part of the compositor space. This object is published + * as global during start up, or when a monitor is hotplugged. + * @section page_iface_wl_output_api API + * See @ref iface_wl_output. + */ +/** + * @defgroup iface_wl_output The wl_output interface + * + * An output describes part of the compositor geometry. The + * compositor works in the 'compositor coordinate system' and an + * output corresponds to a rectangular area in that space that is + * actually visible. This typically corresponds to a monitor that + * displays part of the compositor space. This object is published + * as global during start up, or when a monitor is hotplugged. + */ +extern const struct wl_interface wl_output_interface; +#endif +#ifndef WL_REGION_INTERFACE +#define WL_REGION_INTERFACE +/** + * @page page_iface_wl_region wl_region + * @section page_iface_wl_region_desc Description + * + * A region object describes an area. + * + * Region objects are used to describe the opaque and input + * regions of a surface. + * @section page_iface_wl_region_api API + * See @ref iface_wl_region. + */ +/** + * @defgroup iface_wl_region The wl_region interface + * + * A region object describes an area. + * + * Region objects are used to describe the opaque and input + * regions of a surface. + */ +extern const struct wl_interface wl_region_interface; +#endif +#ifndef WL_SUBCOMPOSITOR_INTERFACE +#define WL_SUBCOMPOSITOR_INTERFACE +/** + * @page page_iface_wl_subcompositor wl_subcompositor + * @section page_iface_wl_subcompositor_desc Description + * + * The global interface exposing sub-surface compositing capabilities. + * A wl_surface, that has sub-surfaces associated, is called the + * parent surface. Sub-surfaces can be arbitrarily nested and create + * a tree of sub-surfaces. + * + * The root surface in a tree of sub-surfaces is the main + * surface. The main surface cannot be a sub-surface, because + * sub-surfaces must always have a parent. + * + * A main surface with its sub-surfaces forms a (compound) window. + * For window management purposes, this set of wl_surface objects is + * to be considered as a single window, and it should also behave as + * such. + * + * The aim of sub-surfaces is to offload some of the compositing work + * within a window from clients to the compositor. A prime example is + * a video player with decorations and video in separate wl_surface + * objects. This should allow the compositor to pass YUV video buffer + * processing to dedicated overlay hardware when possible. + * @section page_iface_wl_subcompositor_api API + * See @ref iface_wl_subcompositor. + */ +/** + * @defgroup iface_wl_subcompositor The wl_subcompositor interface + * + * The global interface exposing sub-surface compositing capabilities. + * A wl_surface, that has sub-surfaces associated, is called the + * parent surface. Sub-surfaces can be arbitrarily nested and create + * a tree of sub-surfaces. + * + * The root surface in a tree of sub-surfaces is the main + * surface. The main surface cannot be a sub-surface, because + * sub-surfaces must always have a parent. + * + * A main surface with its sub-surfaces forms a (compound) window. + * For window management purposes, this set of wl_surface objects is + * to be considered as a single window, and it should also behave as + * such. + * + * The aim of sub-surfaces is to offload some of the compositing work + * within a window from clients to the compositor. A prime example is + * a video player with decorations and video in separate wl_surface + * objects. This should allow the compositor to pass YUV video buffer + * processing to dedicated overlay hardware when possible. + */ +extern const struct wl_interface wl_subcompositor_interface; +#endif +#ifndef WL_SUBSURFACE_INTERFACE +#define WL_SUBSURFACE_INTERFACE +/** + * @page page_iface_wl_subsurface wl_subsurface + * @section page_iface_wl_subsurface_desc Description + * + * An additional interface to a wl_surface object, which has been + * made a sub-surface. A sub-surface has one parent surface. A + * sub-surface's size and position are not limited to that of the parent. + * Particularly, a sub-surface is not automatically clipped to its + * parent's area. + * + * A sub-surface becomes mapped, when a non-NULL wl_buffer is applied + * and the parent surface is mapped. The order of which one happens + * first is irrelevant. A sub-surface is hidden if the parent becomes + * hidden, or if a NULL wl_buffer is applied. These rules apply + * recursively through the tree of surfaces. + * + * A sub-surface can be in one of two modes. The possible modes are + * synchronized and desynchronized, see methods wl_subsurface.set_sync and + * wl_subsurface.set_desync. + * + * The main surface can be thought to be always in desynchronized mode, + * since it does not have a parent in the sub-surfaces sense. + * + * Even if a sub-surface is in desynchronized mode, it will behave as + * in synchronized mode, if its parent surface behaves as in + * synchronized mode. This rule is applied recursively throughout the + * tree of surfaces. This means, that one can set a sub-surface into + * synchronized mode, and then assume that all its child and grand-child + * sub-surfaces are synchronized, too, without explicitly setting them. + * + * If a surface behaves as in synchronized mode, it is effectively + * synchronized, otherwise it is effectively desynchronized. + * + * A sub-surface is initially in the synchronized mode. + * + * The wl_subsurface interface has requests which modify double-buffered + * state of the parent surface (wl_subsurface.set_position, .place_above and + * .place_below). + * + * Destroying a sub-surface takes effect immediately. If you need to + * synchronize the removal of a sub-surface to the parent surface update, + * unmap the sub-surface first by attaching a NULL wl_buffer, update parent, + * and then destroy the sub-surface. + * + * If the parent wl_surface object is destroyed, the sub-surface is + * unmapped. + * + * A sub-surface never has the keyboard focus of any seat. + * + * The wl_surface.offset request is ignored: clients must use set_position + * instead to move the sub-surface. + * @section page_iface_wl_subsurface_api API + * See @ref iface_wl_subsurface. + */ +/** + * @defgroup iface_wl_subsurface The wl_subsurface interface + * + * An additional interface to a wl_surface object, which has been + * made a sub-surface. A sub-surface has one parent surface. A + * sub-surface's size and position are not limited to that of the parent. + * Particularly, a sub-surface is not automatically clipped to its + * parent's area. + * + * A sub-surface becomes mapped, when a non-NULL wl_buffer is applied + * and the parent surface is mapped. The order of which one happens + * first is irrelevant. A sub-surface is hidden if the parent becomes + * hidden, or if a NULL wl_buffer is applied. These rules apply + * recursively through the tree of surfaces. + * + * A sub-surface can be in one of two modes. The possible modes are + * synchronized and desynchronized, see methods wl_subsurface.set_sync and + * wl_subsurface.set_desync. + * + * The main surface can be thought to be always in desynchronized mode, + * since it does not have a parent in the sub-surfaces sense. + * + * Even if a sub-surface is in desynchronized mode, it will behave as + * in synchronized mode, if its parent surface behaves as in + * synchronized mode. This rule is applied recursively throughout the + * tree of surfaces. This means, that one can set a sub-surface into + * synchronized mode, and then assume that all its child and grand-child + * sub-surfaces are synchronized, too, without explicitly setting them. + * + * If a surface behaves as in synchronized mode, it is effectively + * synchronized, otherwise it is effectively desynchronized. + * + * A sub-surface is initially in the synchronized mode. + * + * The wl_subsurface interface has requests which modify double-buffered + * state of the parent surface (wl_subsurface.set_position, .place_above and + * .place_below). + * + * Destroying a sub-surface takes effect immediately. If you need to + * synchronize the removal of a sub-surface to the parent surface update, + * unmap the sub-surface first by attaching a NULL wl_buffer, update parent, + * and then destroy the sub-surface. + * + * If the parent wl_surface object is destroyed, the sub-surface is + * unmapped. + * + * A sub-surface never has the keyboard focus of any seat. + * + * The wl_surface.offset request is ignored: clients must use set_position + * instead to move the sub-surface. + */ +extern const struct wl_interface wl_subsurface_interface; +#endif +#ifndef WL_FIXES_INTERFACE +#define WL_FIXES_INTERFACE +/** + * @page page_iface_wl_fixes wl_fixes + * @section page_iface_wl_fixes_desc Description + * + * This global fixes problems with other core-protocol interfaces that + * cannot be fixed in these interfaces themselves. + * @section page_iface_wl_fixes_api API + * See @ref iface_wl_fixes. + */ +/** + * @defgroup iface_wl_fixes The wl_fixes interface + * + * This global fixes problems with other core-protocol interfaces that + * cannot be fixed in these interfaces themselves. + */ +extern const struct wl_interface wl_fixes_interface; +#endif + +#ifndef WL_DISPLAY_ERROR_ENUM +#define WL_DISPLAY_ERROR_ENUM +/** + * @ingroup iface_wl_display + * global error values + * + * These errors are global and can be emitted in response to any + * server request. + */ +enum wl_display_error { + /** + * server couldn't find object + */ + WL_DISPLAY_ERROR_INVALID_OBJECT = 0, + /** + * method doesn't exist on the specified interface or malformed request + */ + WL_DISPLAY_ERROR_INVALID_METHOD = 1, + /** + * server is out of memory + */ + WL_DISPLAY_ERROR_NO_MEMORY = 2, + /** + * implementation error in compositor + */ + WL_DISPLAY_ERROR_IMPLEMENTATION = 3, +}; +#endif /* WL_DISPLAY_ERROR_ENUM */ + +/** + * @ingroup iface_wl_display + * @struct wl_display_listener + */ +struct wl_display_listener { + /** + * fatal error event + * + * The error event is sent out when a fatal (non-recoverable) + * error has occurred. The object_id argument is the object where + * the error occurred, most often in response to a request to that + * object. The code identifies the error and is defined by the + * object interface. As such, each interface defines its own set of + * error codes. The message is a brief description of the error, + * for (debugging) convenience. + * @param object_id object where the error occurred + * @param code error code + * @param message error description + */ + void (*error)(void *data, + struct wl_display *wl_display, + void *object_id, + uint32_t code, + const char *message); + /** + * acknowledge object ID deletion + * + * This event is used internally by the object ID management + * logic. + * + * When the server stops using an object created by the client, the + * server sends this event. In particular, after sending this + * event, the server will no longer send any events that contain + * the object as the receiver or as an argument. + * + * When the client receives this event, it knows that it can reuse + * the object ID. + * @param id deleted object ID + */ + void (*delete_id)(void *data, + struct wl_display *wl_display, + uint32_t id); +}; + +/** + * @ingroup iface_wl_display + */ +static inline int +wl_display_add_listener(struct wl_display *wl_display, + const struct wl_display_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) wl_display, + (void (**)(void)) listener, data); +} + +#define WL_DISPLAY_SYNC 0 +#define WL_DISPLAY_GET_REGISTRY 1 + +/** + * @ingroup iface_wl_display + */ +#define WL_DISPLAY_ERROR_SINCE_VERSION 1 +/** + * @ingroup iface_wl_display + */ +#define WL_DISPLAY_DELETE_ID_SINCE_VERSION 1 + +/** + * @ingroup iface_wl_display + */ +#define WL_DISPLAY_SYNC_SINCE_VERSION 1 +/** + * @ingroup iface_wl_display + */ +#define WL_DISPLAY_GET_REGISTRY_SINCE_VERSION 1 + +/** @ingroup iface_wl_display */ +static inline void +wl_display_set_user_data(struct wl_display *wl_display, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) wl_display, user_data); +} + +/** @ingroup iface_wl_display */ +static inline void * +wl_display_get_user_data(struct wl_display *wl_display) +{ + return wl_proxy_get_user_data((struct wl_proxy *) wl_display); +} + +static inline uint32_t +wl_display_get_version(struct wl_display *wl_display) +{ + return wl_proxy_get_version((struct wl_proxy *) wl_display); +} + +/** + * @ingroup iface_wl_display + * + * The sync request asks the server to emit the 'done' event + * on the returned wl_callback object. Since requests are + * handled in-order and events are delivered in-order, this can + * be used as a barrier to ensure all previous requests and the + * resulting events have been handled. + * + * The object returned by this request will be destroyed by the + * compositor after the callback is fired and as such the client must not + * attempt to use it after that point. + * + * The callback_data passed in the callback is undefined and should be ignored. + */ +static inline struct wl_callback * +wl_display_sync(struct wl_display *wl_display) +{ + struct wl_proxy *callback; + + callback = wl_proxy_marshal_flags((struct wl_proxy *) wl_display, + WL_DISPLAY_SYNC, &wl_callback_interface, wl_proxy_get_version((struct wl_proxy *) wl_display), 0, NULL); + + return (struct wl_callback *) callback; +} + +/** + * @ingroup iface_wl_display + * + * This request creates a registry object that allows the client + * to list and bind the global objects available from the + * compositor. + * + * It should be noted that the server side resources consumed in + * response to a get_registry request can only be released when the + * client disconnects, not when the client side proxy is destroyed. + * Therefore, clients should invoke get_registry as infrequently as + * possible to avoid wasting memory. + */ +static inline struct wl_registry * +wl_display_get_registry(struct wl_display *wl_display) +{ + struct wl_proxy *registry; + + registry = wl_proxy_marshal_flags((struct wl_proxy *) wl_display, + WL_DISPLAY_GET_REGISTRY, &wl_registry_interface, wl_proxy_get_version((struct wl_proxy *) wl_display), 0, NULL); + + return (struct wl_registry *) registry; +} + +/** + * @ingroup iface_wl_registry + * @struct wl_registry_listener + */ +struct wl_registry_listener { + /** + * announce global object + * + * Notify the client of global objects. + * + * The event notifies the client that a global object with the + * given name is now available, and it implements the given version + * of the given interface. + * @param name numeric name of the global object + * @param interface interface implemented by the object + * @param version interface version + */ + void (*global)(void *data, + struct wl_registry *wl_registry, + uint32_t name, + const char *interface, + uint32_t version); + /** + * announce removal of global object + * + * Notify the client of removed global objects. + * + * This event notifies the client that the global identified by + * name is no longer available. If the client bound to the global + * using the bind request, the client should now destroy that + * object. + * + * The object remains valid and requests to the object will be + * ignored until the client destroys it, to avoid races between the + * global going away and a client sending a request to it. + * @param name numeric name of the global object + */ + void (*global_remove)(void *data, + struct wl_registry *wl_registry, + uint32_t name); +}; + +/** + * @ingroup iface_wl_registry + */ +static inline int +wl_registry_add_listener(struct wl_registry *wl_registry, + const struct wl_registry_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) wl_registry, + (void (**)(void)) listener, data); +} + +#define WL_REGISTRY_BIND 0 + +/** + * @ingroup iface_wl_registry + */ +#define WL_REGISTRY_GLOBAL_SINCE_VERSION 1 +/** + * @ingroup iface_wl_registry + */ +#define WL_REGISTRY_GLOBAL_REMOVE_SINCE_VERSION 1 + +/** + * @ingroup iface_wl_registry + */ +#define WL_REGISTRY_BIND_SINCE_VERSION 1 + +/** @ingroup iface_wl_registry */ +static inline void +wl_registry_set_user_data(struct wl_registry *wl_registry, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) wl_registry, user_data); +} + +/** @ingroup iface_wl_registry */ +static inline void * +wl_registry_get_user_data(struct wl_registry *wl_registry) +{ + return wl_proxy_get_user_data((struct wl_proxy *) wl_registry); +} + +static inline uint32_t +wl_registry_get_version(struct wl_registry *wl_registry) +{ + return wl_proxy_get_version((struct wl_proxy *) wl_registry); +} + +/** @ingroup iface_wl_registry */ +static inline void +wl_registry_destroy(struct wl_registry *wl_registry) +{ + wl_proxy_destroy((struct wl_proxy *) wl_registry); +} + +/** + * @ingroup iface_wl_registry + * + * Binds a new, client-created object to the server using the + * specified name as the identifier. + */ +static inline void * +wl_registry_bind(struct wl_registry *wl_registry, uint32_t name, const struct wl_interface *interface, uint32_t version) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) wl_registry, + WL_REGISTRY_BIND, interface, version, 0, name, interface->name, version, NULL); + + return (void *) id; +} + +/** + * @ingroup iface_wl_callback + * @struct wl_callback_listener + */ +struct wl_callback_listener { + /** + * done event + * + * Notify the client when the related request is done. + * @param callback_data request-specific data for the callback + */ + void (*done)(void *data, + struct wl_callback *wl_callback, + uint32_t callback_data); +}; + +/** + * @ingroup iface_wl_callback + */ +static inline int +wl_callback_add_listener(struct wl_callback *wl_callback, + const struct wl_callback_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) wl_callback, + (void (**)(void)) listener, data); +} + +/** + * @ingroup iface_wl_callback + */ +#define WL_CALLBACK_DONE_SINCE_VERSION 1 + + +/** @ingroup iface_wl_callback */ +static inline void +wl_callback_set_user_data(struct wl_callback *wl_callback, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) wl_callback, user_data); +} + +/** @ingroup iface_wl_callback */ +static inline void * +wl_callback_get_user_data(struct wl_callback *wl_callback) +{ + return wl_proxy_get_user_data((struct wl_proxy *) wl_callback); +} + +static inline uint32_t +wl_callback_get_version(struct wl_callback *wl_callback) +{ + return wl_proxy_get_version((struct wl_proxy *) wl_callback); +} + +/** @ingroup iface_wl_callback */ +static inline void +wl_callback_destroy(struct wl_callback *wl_callback) +{ + wl_proxy_destroy((struct wl_proxy *) wl_callback); +} + +#define WL_COMPOSITOR_CREATE_SURFACE 0 +#define WL_COMPOSITOR_CREATE_REGION 1 +#define WL_COMPOSITOR_RELEASE 2 + + +/** + * @ingroup iface_wl_compositor + */ +#define WL_COMPOSITOR_CREATE_SURFACE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_compositor + */ +#define WL_COMPOSITOR_CREATE_REGION_SINCE_VERSION 1 +/** + * @ingroup iface_wl_compositor + */ +#define WL_COMPOSITOR_RELEASE_SINCE_VERSION 7 + +/** @ingroup iface_wl_compositor */ +static inline void +wl_compositor_set_user_data(struct wl_compositor *wl_compositor, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) wl_compositor, user_data); +} + +/** @ingroup iface_wl_compositor */ +static inline void * +wl_compositor_get_user_data(struct wl_compositor *wl_compositor) +{ + return wl_proxy_get_user_data((struct wl_proxy *) wl_compositor); +} + +static inline uint32_t +wl_compositor_get_version(struct wl_compositor *wl_compositor) +{ + return wl_proxy_get_version((struct wl_proxy *) wl_compositor); +} + +/** @ingroup iface_wl_compositor */ +static inline void +wl_compositor_destroy(struct wl_compositor *wl_compositor) +{ + wl_proxy_destroy((struct wl_proxy *) wl_compositor); +} + +/** + * @ingroup iface_wl_compositor + * + * Ask the compositor to create a new surface. + */ +static inline struct wl_surface * +wl_compositor_create_surface(struct wl_compositor *wl_compositor) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) wl_compositor, + WL_COMPOSITOR_CREATE_SURFACE, &wl_surface_interface, wl_proxy_get_version((struct wl_proxy *) wl_compositor), 0, NULL); + + return (struct wl_surface *) id; +} + +/** + * @ingroup iface_wl_compositor + * + * Ask the compositor to create a new region. + */ +static inline struct wl_region * +wl_compositor_create_region(struct wl_compositor *wl_compositor) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) wl_compositor, + WL_COMPOSITOR_CREATE_REGION, &wl_region_interface, wl_proxy_get_version((struct wl_proxy *) wl_compositor), 0, NULL); + + return (struct wl_region *) id; +} + +/** + * @ingroup iface_wl_compositor + * + * This request destroys the wl_compositor. This has no effect on any other objects. + */ +static inline void +wl_compositor_release(struct wl_compositor *wl_compositor) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_compositor, + WL_COMPOSITOR_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_compositor), WL_MARSHAL_FLAG_DESTROY); +} + +#ifndef WL_SHM_POOL_ERROR_ENUM +#define WL_SHM_POOL_ERROR_ENUM +/** + * @ingroup iface_wl_shm_pool + * wl_shm_pool error values + * + * These errors can be emitted in response to wl_shm_pool requests. + */ +enum wl_shm_pool_error { + /** + * buffer format is not known + */ + WL_SHM_POOL_ERROR_INVALID_FORMAT = 0, + /** + * invalid size or stride during buffer creation + */ + WL_SHM_POOL_ERROR_INVALID_STRIDE = 1, +}; +#endif /* WL_SHM_POOL_ERROR_ENUM */ + +#define WL_SHM_POOL_CREATE_BUFFER 0 +#define WL_SHM_POOL_DESTROY 1 +#define WL_SHM_POOL_RESIZE 2 + + +/** + * @ingroup iface_wl_shm_pool + */ +#define WL_SHM_POOL_CREATE_BUFFER_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shm_pool + */ +#define WL_SHM_POOL_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shm_pool + */ +#define WL_SHM_POOL_RESIZE_SINCE_VERSION 1 + +/** @ingroup iface_wl_shm_pool */ +static inline void +wl_shm_pool_set_user_data(struct wl_shm_pool *wl_shm_pool, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) wl_shm_pool, user_data); +} + +/** @ingroup iface_wl_shm_pool */ +static inline void * +wl_shm_pool_get_user_data(struct wl_shm_pool *wl_shm_pool) +{ + return wl_proxy_get_user_data((struct wl_proxy *) wl_shm_pool); +} + +static inline uint32_t +wl_shm_pool_get_version(struct wl_shm_pool *wl_shm_pool) +{ + return wl_proxy_get_version((struct wl_proxy *) wl_shm_pool); +} + +/** + * @ingroup iface_wl_shm_pool + * + * Create a wl_buffer object from the pool. + * + * The buffer is created offset bytes into the pool and has + * width and height as specified. The stride argument specifies + * the number of bytes from the beginning of one row to the beginning + * of the next. The format is the pixel format of the buffer and + * must be one of those advertised through the wl_shm.format event. + * + * A buffer will keep a reference to the pool it was created from + * so it is valid to destroy the pool immediately after creating + * a buffer from it. + */ +static inline struct wl_buffer * +wl_shm_pool_create_buffer(struct wl_shm_pool *wl_shm_pool, int32_t offset, int32_t width, int32_t height, int32_t stride, uint32_t format) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) wl_shm_pool, + WL_SHM_POOL_CREATE_BUFFER, &wl_buffer_interface, wl_proxy_get_version((struct wl_proxy *) wl_shm_pool), 0, NULL, offset, width, height, stride, format); + + return (struct wl_buffer *) id; +} + +/** + * @ingroup iface_wl_shm_pool + * + * Destroy the shared memory pool. + * + * The mmapped memory will be released when all + * buffers that have been created from this pool + * are gone. + */ +static inline void +wl_shm_pool_destroy(struct wl_shm_pool *wl_shm_pool) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_shm_pool, + WL_SHM_POOL_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shm_pool), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_wl_shm_pool + * + * This request will cause the server to remap the backing memory + * for the pool from the file descriptor passed when the pool was + * created, but using the new size. This request can only be + * used to make the pool bigger. + * + * This request only changes the amount of bytes that are mmapped + * by the server and does not touch the file corresponding to the + * file descriptor passed at creation time. It is the client's + * responsibility to ensure that the file is at least as big as + * the new pool size. + */ +static inline void +wl_shm_pool_resize(struct wl_shm_pool *wl_shm_pool, int32_t size) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_shm_pool, + WL_SHM_POOL_RESIZE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shm_pool), 0, size); +} + +#ifndef WL_SHM_ERROR_ENUM +#define WL_SHM_ERROR_ENUM +/** + * @ingroup iface_wl_shm + * wl_shm error values + * + * These errors can be emitted in response to wl_shm requests. + */ +enum wl_shm_error { + /** + * buffer format is not known + * @deprecated Deprecated since version 3 + */ + WL_SHM_ERROR_INVALID_FORMAT = 0, + /** + * invalid size or stride during pool creation + */ + WL_SHM_ERROR_INVALID_STRIDE = 1, + /** + * mmapping the file descriptor failed + */ + WL_SHM_ERROR_INVALID_FD = 2, +}; +#endif /* WL_SHM_ERROR_ENUM */ + +#ifndef WL_SHM_FORMAT_ENUM +#define WL_SHM_FORMAT_ENUM +/** + * @ingroup iface_wl_shm + * pixel formats + * + * This describes the memory layout of an individual pixel. + * + * All renderers should support argb8888 and xrgb8888 but any other + * formats are optional and may not be supported by the particular + * renderer in use. + * + * The drm format codes match the macros defined in drm_fourcc.h, except + * argb8888 and xrgb8888. The formats actually supported by the compositor + * will be reported by the format event. See drm_fourcc.h for more detailed + * format descriptions. + * + * For all wl_shm formats and unless specified in another protocol + * extension, pre-multiplied alpha is used for pixel values. + */ +enum wl_shm_format { + /** + * 32-bit ARGB format, [31:0] A:R:G:B 8:8:8:8 little endian + */ + WL_SHM_FORMAT_ARGB8888 = 0, + /** + * 32-bit RGB format, [31:0] x:R:G:B 8:8:8:8 little endian + */ + WL_SHM_FORMAT_XRGB8888 = 1, + /** + * 8-bit color index format, [7:0] C + */ + WL_SHM_FORMAT_C8 = 0x20203843, + /** + * 8-bit RGB format, [7:0] R:G:B 3:3:2 + */ + WL_SHM_FORMAT_RGB332 = 0x38424752, + /** + * 8-bit BGR format, [7:0] B:G:R 2:3:3 + */ + WL_SHM_FORMAT_BGR233 = 0x38524742, + /** + * 16-bit xRGB format, [15:0] x:R:G:B 4:4:4:4 little endian + */ + WL_SHM_FORMAT_XRGB4444 = 0x32315258, + /** + * 16-bit xBGR format, [15:0] x:B:G:R 4:4:4:4 little endian + */ + WL_SHM_FORMAT_XBGR4444 = 0x32314258, + /** + * 16-bit RGBx format, [15:0] R:G:B:x 4:4:4:4 little endian + */ + WL_SHM_FORMAT_RGBX4444 = 0x32315852, + /** + * 16-bit BGRx format, [15:0] B:G:R:x 4:4:4:4 little endian + */ + WL_SHM_FORMAT_BGRX4444 = 0x32315842, + /** + * 16-bit ARGB format, [15:0] A:R:G:B 4:4:4:4 little endian + */ + WL_SHM_FORMAT_ARGB4444 = 0x32315241, + /** + * 16-bit ABGR format, [15:0] A:B:G:R 4:4:4:4 little endian + */ + WL_SHM_FORMAT_ABGR4444 = 0x32314241, + /** + * 16-bit RGBA format, [15:0] R:G:B:A 4:4:4:4 little endian + */ + WL_SHM_FORMAT_RGBA4444 = 0x32314152, + /** + * 16-bit BGRA format, [15:0] B:G:R:A 4:4:4:4 little endian + */ + WL_SHM_FORMAT_BGRA4444 = 0x32314142, + /** + * 16-bit xRGB format, [15:0] x:R:G:B 1:5:5:5 little endian + */ + WL_SHM_FORMAT_XRGB1555 = 0x35315258, + /** + * 16-bit xBGR 1555 format, [15:0] x:B:G:R 1:5:5:5 little endian + */ + WL_SHM_FORMAT_XBGR1555 = 0x35314258, + /** + * 16-bit RGBx 5551 format, [15:0] R:G:B:x 5:5:5:1 little endian + */ + WL_SHM_FORMAT_RGBX5551 = 0x35315852, + /** + * 16-bit BGRx 5551 format, [15:0] B:G:R:x 5:5:5:1 little endian + */ + WL_SHM_FORMAT_BGRX5551 = 0x35315842, + /** + * 16-bit ARGB 1555 format, [15:0] A:R:G:B 1:5:5:5 little endian + */ + WL_SHM_FORMAT_ARGB1555 = 0x35315241, + /** + * 16-bit ABGR 1555 format, [15:0] A:B:G:R 1:5:5:5 little endian + */ + WL_SHM_FORMAT_ABGR1555 = 0x35314241, + /** + * 16-bit RGBA 5551 format, [15:0] R:G:B:A 5:5:5:1 little endian + */ + WL_SHM_FORMAT_RGBA5551 = 0x35314152, + /** + * 16-bit BGRA 5551 format, [15:0] B:G:R:A 5:5:5:1 little endian + */ + WL_SHM_FORMAT_BGRA5551 = 0x35314142, + /** + * 16-bit RGB 565 format, [15:0] R:G:B 5:6:5 little endian + */ + WL_SHM_FORMAT_RGB565 = 0x36314752, + /** + * 16-bit BGR 565 format, [15:0] B:G:R 5:6:5 little endian + */ + WL_SHM_FORMAT_BGR565 = 0x36314742, + /** + * 24-bit RGB format, [23:0] R:G:B little endian + */ + WL_SHM_FORMAT_RGB888 = 0x34324752, + /** + * 24-bit BGR format, [23:0] B:G:R little endian + */ + WL_SHM_FORMAT_BGR888 = 0x34324742, + /** + * 32-bit xBGR format, [31:0] x:B:G:R 8:8:8:8 little endian + */ + WL_SHM_FORMAT_XBGR8888 = 0x34324258, + /** + * 32-bit RGBx format, [31:0] R:G:B:x 8:8:8:8 little endian + */ + WL_SHM_FORMAT_RGBX8888 = 0x34325852, + /** + * 32-bit BGRx format, [31:0] B:G:R:x 8:8:8:8 little endian + */ + WL_SHM_FORMAT_BGRX8888 = 0x34325842, + /** + * 32-bit ABGR format, [31:0] A:B:G:R 8:8:8:8 little endian + */ + WL_SHM_FORMAT_ABGR8888 = 0x34324241, + /** + * 32-bit RGBA format, [31:0] R:G:B:A 8:8:8:8 little endian + */ + WL_SHM_FORMAT_RGBA8888 = 0x34324152, + /** + * 32-bit BGRA format, [31:0] B:G:R:A 8:8:8:8 little endian + */ + WL_SHM_FORMAT_BGRA8888 = 0x34324142, + /** + * 32-bit xRGB format, [31:0] x:R:G:B 2:10:10:10 little endian + */ + WL_SHM_FORMAT_XRGB2101010 = 0x30335258, + /** + * 32-bit xBGR format, [31:0] x:B:G:R 2:10:10:10 little endian + */ + WL_SHM_FORMAT_XBGR2101010 = 0x30334258, + /** + * 32-bit RGBx format, [31:0] R:G:B:x 10:10:10:2 little endian + */ + WL_SHM_FORMAT_RGBX1010102 = 0x30335852, + /** + * 32-bit BGRx format, [31:0] B:G:R:x 10:10:10:2 little endian + */ + WL_SHM_FORMAT_BGRX1010102 = 0x30335842, + /** + * 32-bit ARGB format, [31:0] A:R:G:B 2:10:10:10 little endian + */ + WL_SHM_FORMAT_ARGB2101010 = 0x30335241, + /** + * 32-bit ABGR format, [31:0] A:B:G:R 2:10:10:10 little endian + */ + WL_SHM_FORMAT_ABGR2101010 = 0x30334241, + /** + * 32-bit RGBA format, [31:0] R:G:B:A 10:10:10:2 little endian + */ + WL_SHM_FORMAT_RGBA1010102 = 0x30334152, + /** + * 32-bit BGRA format, [31:0] B:G:R:A 10:10:10:2 little endian + */ + WL_SHM_FORMAT_BGRA1010102 = 0x30334142, + /** + * packed YCbCr format, [31:0] Cr0:Y1:Cb0:Y0 8:8:8:8 little endian + */ + WL_SHM_FORMAT_YUYV = 0x56595559, + /** + * packed YCbCr format, [31:0] Cb0:Y1:Cr0:Y0 8:8:8:8 little endian + */ + WL_SHM_FORMAT_YVYU = 0x55595659, + /** + * packed YCbCr format, [31:0] Y1:Cr0:Y0:Cb0 8:8:8:8 little endian + */ + WL_SHM_FORMAT_UYVY = 0x59565955, + /** + * packed YCbCr format, [31:0] Y1:Cb0:Y0:Cr0 8:8:8:8 little endian + */ + WL_SHM_FORMAT_VYUY = 0x59555956, + /** + * packed AYCbCr format, [31:0] A:Y:Cb:Cr 8:8:8:8 little endian + */ + WL_SHM_FORMAT_AYUV = 0x56555941, + /** + * 2 plane YCbCr Cr:Cb format, 2x2 subsampled Cr:Cb plane + */ + WL_SHM_FORMAT_NV12 = 0x3231564e, + /** + * 2 plane YCbCr Cb:Cr format, 2x2 subsampled Cb:Cr plane + */ + WL_SHM_FORMAT_NV21 = 0x3132564e, + /** + * 2 plane YCbCr Cr:Cb format, 2x1 subsampled Cr:Cb plane + */ + WL_SHM_FORMAT_NV16 = 0x3631564e, + /** + * 2 plane YCbCr Cb:Cr format, 2x1 subsampled Cb:Cr plane + */ + WL_SHM_FORMAT_NV61 = 0x3136564e, + /** + * 3 plane YCbCr format, 4x4 subsampled Cb (1) and Cr (2) planes + */ + WL_SHM_FORMAT_YUV410 = 0x39565559, + /** + * 3 plane YCbCr format, 4x4 subsampled Cr (1) and Cb (2) planes + */ + WL_SHM_FORMAT_YVU410 = 0x39555659, + /** + * 3 plane YCbCr format, 4x1 subsampled Cb (1) and Cr (2) planes + */ + WL_SHM_FORMAT_YUV411 = 0x31315559, + /** + * 3 plane YCbCr format, 4x1 subsampled Cr (1) and Cb (2) planes + */ + WL_SHM_FORMAT_YVU411 = 0x31315659, + /** + * 3 plane YCbCr format, 2x2 subsampled Cb (1) and Cr (2) planes + */ + WL_SHM_FORMAT_YUV420 = 0x32315559, + /** + * 3 plane YCbCr format, 2x2 subsampled Cr (1) and Cb (2) planes + */ + WL_SHM_FORMAT_YVU420 = 0x32315659, + /** + * 3 plane YCbCr format, 2x1 subsampled Cb (1) and Cr (2) planes + */ + WL_SHM_FORMAT_YUV422 = 0x36315559, + /** + * 3 plane YCbCr format, 2x1 subsampled Cr (1) and Cb (2) planes + */ + WL_SHM_FORMAT_YVU422 = 0x36315659, + /** + * 3 plane YCbCr format, non-subsampled Cb (1) and Cr (2) planes + */ + WL_SHM_FORMAT_YUV444 = 0x34325559, + /** + * 3 plane YCbCr format, non-subsampled Cr (1) and Cb (2) planes + */ + WL_SHM_FORMAT_YVU444 = 0x34325659, + /** + * [7:0] R + */ + WL_SHM_FORMAT_R8 = 0x20203852, + /** + * [15:0] R little endian + */ + WL_SHM_FORMAT_R16 = 0x20363152, + /** + * [15:0] R:G 8:8 little endian + */ + WL_SHM_FORMAT_RG88 = 0x38384752, + /** + * [15:0] G:R 8:8 little endian + */ + WL_SHM_FORMAT_GR88 = 0x38385247, + /** + * [31:0] R:G 16:16 little endian + */ + WL_SHM_FORMAT_RG1616 = 0x32334752, + /** + * [31:0] G:R 16:16 little endian + */ + WL_SHM_FORMAT_GR1616 = 0x32335247, + /** + * [63:0] x:R:G:B 16:16:16:16 little endian + */ + WL_SHM_FORMAT_XRGB16161616F = 0x48345258, + /** + * [63:0] x:B:G:R 16:16:16:16 little endian + */ + WL_SHM_FORMAT_XBGR16161616F = 0x48344258, + /** + * [63:0] A:R:G:B 16:16:16:16 little endian + */ + WL_SHM_FORMAT_ARGB16161616F = 0x48345241, + /** + * [63:0] A:B:G:R 16:16:16:16 little endian + */ + WL_SHM_FORMAT_ABGR16161616F = 0x48344241, + /** + * [31:0] X:Y:Cb:Cr 8:8:8:8 little endian + */ + WL_SHM_FORMAT_XYUV8888 = 0x56555958, + /** + * [23:0] Cr:Cb:Y 8:8:8 little endian + */ + WL_SHM_FORMAT_VUY888 = 0x34325556, + /** + * Y followed by U then V, 10:10:10. Non-linear modifier only + */ + WL_SHM_FORMAT_VUY101010 = 0x30335556, + /** + * [63:0] Cr0:0:Y1:0:Cb0:0:Y0:0 10:6:10:6:10:6:10:6 little endian per 2 Y pixels + */ + WL_SHM_FORMAT_Y210 = 0x30313259, + /** + * [63:0] Cr0:0:Y1:0:Cb0:0:Y0:0 12:4:12:4:12:4:12:4 little endian per 2 Y pixels + */ + WL_SHM_FORMAT_Y212 = 0x32313259, + /** + * [63:0] Cr0:Y1:Cb0:Y0 16:16:16:16 little endian per 2 Y pixels + */ + WL_SHM_FORMAT_Y216 = 0x36313259, + /** + * [31:0] A:Cr:Y:Cb 2:10:10:10 little endian + */ + WL_SHM_FORMAT_Y410 = 0x30313459, + /** + * [63:0] A:0:Cr:0:Y:0:Cb:0 12:4:12:4:12:4:12:4 little endian + */ + WL_SHM_FORMAT_Y412 = 0x32313459, + /** + * [63:0] A:Cr:Y:Cb 16:16:16:16 little endian + */ + WL_SHM_FORMAT_Y416 = 0x36313459, + /** + * [31:0] X:Cr:Y:Cb 2:10:10:10 little endian + */ + WL_SHM_FORMAT_XVYU2101010 = 0x30335658, + /** + * [63:0] X:0:Cr:0:Y:0:Cb:0 12:4:12:4:12:4:12:4 little endian + */ + WL_SHM_FORMAT_XVYU12_16161616 = 0x36335658, + /** + * [63:0] X:Cr:Y:Cb 16:16:16:16 little endian + */ + WL_SHM_FORMAT_XVYU16161616 = 0x38345658, + /** + * [63:0] A3:A2:Y3:0:Cr0:0:Y2:0:A1:A0:Y1:0:Cb0:0:Y0:0 1:1:8:2:8:2:8:2:1:1:8:2:8:2:8:2 little endian + */ + WL_SHM_FORMAT_Y0L0 = 0x304c3059, + /** + * [63:0] X3:X2:Y3:0:Cr0:0:Y2:0:X1:X0:Y1:0:Cb0:0:Y0:0 1:1:8:2:8:2:8:2:1:1:8:2:8:2:8:2 little endian + */ + WL_SHM_FORMAT_X0L0 = 0x304c3058, + /** + * [63:0] A3:A2:Y3:Cr0:Y2:A1:A0:Y1:Cb0:Y0 1:1:10:10:10:1:1:10:10:10 little endian + */ + WL_SHM_FORMAT_Y0L2 = 0x324c3059, + /** + * [63:0] X3:X2:Y3:Cr0:Y2:X1:X0:Y1:Cb0:Y0 1:1:10:10:10:1:1:10:10:10 little endian + */ + WL_SHM_FORMAT_X0L2 = 0x324c3058, + WL_SHM_FORMAT_YUV420_8BIT = 0x38305559, + WL_SHM_FORMAT_YUV420_10BIT = 0x30315559, + WL_SHM_FORMAT_XRGB8888_A8 = 0x38415258, + WL_SHM_FORMAT_XBGR8888_A8 = 0x38414258, + WL_SHM_FORMAT_RGBX8888_A8 = 0x38415852, + WL_SHM_FORMAT_BGRX8888_A8 = 0x38415842, + WL_SHM_FORMAT_RGB888_A8 = 0x38413852, + WL_SHM_FORMAT_BGR888_A8 = 0x38413842, + WL_SHM_FORMAT_RGB565_A8 = 0x38413552, + WL_SHM_FORMAT_BGR565_A8 = 0x38413542, + /** + * non-subsampled Cr:Cb plane + */ + WL_SHM_FORMAT_NV24 = 0x3432564e, + /** + * non-subsampled Cb:Cr plane + */ + WL_SHM_FORMAT_NV42 = 0x3234564e, + /** + * 2x1 subsampled Cr:Cb plane, 10 bit per channel + */ + WL_SHM_FORMAT_P210 = 0x30313250, + /** + * 2x2 subsampled Cr:Cb plane 10 bits per channel + */ + WL_SHM_FORMAT_P010 = 0x30313050, + /** + * 2x2 subsampled Cr:Cb plane 12 bits per channel + */ + WL_SHM_FORMAT_P012 = 0x32313050, + /** + * 2x2 subsampled Cr:Cb plane 16 bits per channel + */ + WL_SHM_FORMAT_P016 = 0x36313050, + /** + * [63:0] A:x:B:x:G:x:R:x 10:6:10:6:10:6:10:6 little endian + */ + WL_SHM_FORMAT_AXBXGXRX106106106106 = 0x30314241, + /** + * 2x2 subsampled Cr:Cb plane + */ + WL_SHM_FORMAT_NV15 = 0x3531564e, + WL_SHM_FORMAT_Q410 = 0x30313451, + WL_SHM_FORMAT_Q401 = 0x31303451, + /** + * [63:0] x:R:G:B 16:16:16:16 little endian + */ + WL_SHM_FORMAT_XRGB16161616 = 0x38345258, + /** + * [63:0] x:B:G:R 16:16:16:16 little endian + */ + WL_SHM_FORMAT_XBGR16161616 = 0x38344258, + /** + * [63:0] A:R:G:B 16:16:16:16 little endian + */ + WL_SHM_FORMAT_ARGB16161616 = 0x38345241, + /** + * [63:0] A:B:G:R 16:16:16:16 little endian + */ + WL_SHM_FORMAT_ABGR16161616 = 0x38344241, + /** + * [7:0] C0:C1:C2:C3:C4:C5:C6:C7 1:1:1:1:1:1:1:1 eight pixels/byte + */ + WL_SHM_FORMAT_C1 = 0x20203143, + /** + * [7:0] C0:C1:C2:C3 2:2:2:2 four pixels/byte + */ + WL_SHM_FORMAT_C2 = 0x20203243, + /** + * [7:0] C0:C1 4:4 two pixels/byte + */ + WL_SHM_FORMAT_C4 = 0x20203443, + /** + * [7:0] D0:D1:D2:D3:D4:D5:D6:D7 1:1:1:1:1:1:1:1 eight pixels/byte + */ + WL_SHM_FORMAT_D1 = 0x20203144, + /** + * [7:0] D0:D1:D2:D3 2:2:2:2 four pixels/byte + */ + WL_SHM_FORMAT_D2 = 0x20203244, + /** + * [7:0] D0:D1 4:4 two pixels/byte + */ + WL_SHM_FORMAT_D4 = 0x20203444, + /** + * [7:0] D + */ + WL_SHM_FORMAT_D8 = 0x20203844, + /** + * [7:0] R0:R1:R2:R3:R4:R5:R6:R7 1:1:1:1:1:1:1:1 eight pixels/byte + */ + WL_SHM_FORMAT_R1 = 0x20203152, + /** + * [7:0] R0:R1:R2:R3 2:2:2:2 four pixels/byte + */ + WL_SHM_FORMAT_R2 = 0x20203252, + /** + * [7:0] R0:R1 4:4 two pixels/byte + */ + WL_SHM_FORMAT_R4 = 0x20203452, + /** + * [15:0] x:R 6:10 little endian + */ + WL_SHM_FORMAT_R10 = 0x20303152, + /** + * [15:0] x:R 4:12 little endian + */ + WL_SHM_FORMAT_R12 = 0x20323152, + /** + * [31:0] A:Cr:Cb:Y 8:8:8:8 little endian + */ + WL_SHM_FORMAT_AVUY8888 = 0x59555641, + /** + * [31:0] X:Cr:Cb:Y 8:8:8:8 little endian + */ + WL_SHM_FORMAT_XVUY8888 = 0x59555658, + /** + * 2x2 subsampled Cr:Cb plane 10 bits per channel packed + */ + WL_SHM_FORMAT_P030 = 0x30333050, + /** + * [47:0] R:G:B 16:16:16 little endian + */ + WL_SHM_FORMAT_RGB161616 = 0x38344752, + /** + * [47:0] B:G:R 16:16:16 little endian + */ + WL_SHM_FORMAT_BGR161616 = 0x38344742, + /** + * [15:0] R 16 little endian + */ + WL_SHM_FORMAT_R16F = 0x48202052, + /** + * [31:0] G:R 16:16 little endian + */ + WL_SHM_FORMAT_GR1616F = 0x48205247, + /** + * [47:0] B:G:R 16:16:16 little endian + */ + WL_SHM_FORMAT_BGR161616F = 0x48524742, + /** + * [31:0] R 32 little endian + */ + WL_SHM_FORMAT_R32F = 0x46202052, + /** + * [63:0] G:R 32:32 little endian + */ + WL_SHM_FORMAT_GR3232F = 0x46205247, + /** + * [95:0] B:G:R 32:32:32 little endian + */ + WL_SHM_FORMAT_BGR323232F = 0x46524742, + /** + * [127:0] A:B:G:R 32:32:32:32 little endian + */ + WL_SHM_FORMAT_ABGR32323232F = 0x46384241, + /** + * 2x1 subsampled Cr:Cb plane + */ + WL_SHM_FORMAT_NV20 = 0x3032564e, + /** + * non-subsampled Cr:Cb plane + */ + WL_SHM_FORMAT_NV30 = 0x3033564e, + /** + * 2x2 subsampled Cb (1) and Cr (2) planes 10 bits per channel + */ + WL_SHM_FORMAT_S010 = 0x30313053, + /** + * 2x1 subsampled Cb (1) and Cr (2) planes 10 bits per channel + */ + WL_SHM_FORMAT_S210 = 0x30313253, + /** + * non-subsampled Cb (1) and Cr (2) planes 10 bits per channel + */ + WL_SHM_FORMAT_S410 = 0x30313453, + /** + * 2x2 subsampled Cb (1) and Cr (2) planes 12 bits per channel + */ + WL_SHM_FORMAT_S012 = 0x32313053, + /** + * 2x1 subsampled Cb (1) and Cr (2) planes 12 bits per channel + */ + WL_SHM_FORMAT_S212 = 0x32313253, + /** + * non-subsampled Cb (1) and Cr (2) planes 12 bits per channel + */ + WL_SHM_FORMAT_S412 = 0x32313453, + /** + * 2x2 subsampled Cb (1) and Cr (2) planes 16 bits per channel + */ + WL_SHM_FORMAT_S016 = 0x36313053, + /** + * 2x1 subsampled Cb (1) and Cr (2) planes 16 bits per channel + */ + WL_SHM_FORMAT_S216 = 0x36313253, + /** + * non-subsampled Cb (1) and Cr (2) planes 16 bits per channel + */ + WL_SHM_FORMAT_S416 = 0x36313453, + /** + * [31:0] x:Cr:Cb:Y 2:10:10:10 little endian + */ + WL_SHM_FORMAT_XVUY2101010 = 0x30335958, + /** + * 2x1 subsampled Cr:Cb plane 10 bits per channel packed + */ + WL_SHM_FORMAT_P230 = 0x30333250, + WL_SHM_FORMAT_T430 = 0x30333454, + /** + * 8-bit Y-only + */ + WL_SHM_FORMAT_Y8 = 0x59455247, + /** + * [31:0] x:Y2:Y1:Y0 2:10:10:10 little endian + */ + WL_SHM_FORMAT_XYYY2101010 = 0x34415059, +}; +#endif /* WL_SHM_FORMAT_ENUM */ + +/** + * @ingroup iface_wl_shm + * @struct wl_shm_listener + */ +struct wl_shm_listener { + /** + * pixel format description + * + * Informs the client about a valid pixel format that can be used + * for buffers. Known formats include argb8888 and xrgb8888. + * + * Extensions to drm_fourcc.h (or the format enum) do not require + * increasing the wl_shm version; as a result, clients may receive + * format codes which were not in the list at the time the client + * was made. + * @param format buffer pixel format + */ + void (*format)(void *data, + struct wl_shm *wl_shm, + uint32_t format); +}; + +/** + * @ingroup iface_wl_shm + */ +static inline int +wl_shm_add_listener(struct wl_shm *wl_shm, + const struct wl_shm_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) wl_shm, + (void (**)(void)) listener, data); +} + +#define WL_SHM_CREATE_POOL 0 +#define WL_SHM_RELEASE 1 + +/** + * @ingroup iface_wl_shm + */ +#define WL_SHM_FORMAT_SINCE_VERSION 1 + +/** + * @ingroup iface_wl_shm + */ +#define WL_SHM_CREATE_POOL_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shm + */ +#define WL_SHM_RELEASE_SINCE_VERSION 2 + +/** @ingroup iface_wl_shm */ +static inline void +wl_shm_set_user_data(struct wl_shm *wl_shm, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) wl_shm, user_data); +} + +/** @ingroup iface_wl_shm */ +static inline void * +wl_shm_get_user_data(struct wl_shm *wl_shm) +{ + return wl_proxy_get_user_data((struct wl_proxy *) wl_shm); +} + +static inline uint32_t +wl_shm_get_version(struct wl_shm *wl_shm) +{ + return wl_proxy_get_version((struct wl_proxy *) wl_shm); +} + +/** @ingroup iface_wl_shm */ +static inline void +wl_shm_destroy(struct wl_shm *wl_shm) +{ + wl_proxy_destroy((struct wl_proxy *) wl_shm); +} + +/** + * @ingroup iface_wl_shm + * + * Create a new wl_shm_pool object. + * + * The pool can be used to create shared memory based buffer + * objects. The server will mmap size bytes of the passed file + * descriptor, to use as backing memory for the pool. + */ +static inline struct wl_shm_pool * +wl_shm_create_pool(struct wl_shm *wl_shm, int32_t fd, int32_t size) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) wl_shm, + WL_SHM_CREATE_POOL, &wl_shm_pool_interface, wl_proxy_get_version((struct wl_proxy *) wl_shm), 0, NULL, fd, size); + + return (struct wl_shm_pool *) id; +} + +/** + * @ingroup iface_wl_shm + * + * Using this request a client can tell the server that it is not going to + * use the shm object anymore. + * + * Objects created via this interface remain unaffected. + */ +static inline void +wl_shm_release(struct wl_shm *wl_shm) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_shm, + WL_SHM_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shm), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_wl_buffer + * @struct wl_buffer_listener + */ +struct wl_buffer_listener { + /** + * compositor releases buffer + * + * Sent when this wl_buffer is no longer used by the compositor. + * + * For more information on when release events may or may not be + * sent, and what consequences it has, please see the description + * of wl_surface.attach. + * + * If a client receives a release event before the frame callback + * requested in the same wl_surface.commit that attaches this + * wl_buffer to a surface, then the client is immediately free to + * reuse the buffer and its backing storage, and does not need a + * second buffer for the next surface content update. Typically + * this is possible, when the compositor maintains a copy of the + * wl_surface contents, e.g. as a GL texture. This is an important + * optimization for GL(ES) compositors with wl_shm clients. + */ + void (*release)(void *data, + struct wl_buffer *wl_buffer); +}; + +/** + * @ingroup iface_wl_buffer + */ +static inline int +wl_buffer_add_listener(struct wl_buffer *wl_buffer, + const struct wl_buffer_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) wl_buffer, + (void (**)(void)) listener, data); +} + +#define WL_BUFFER_DESTROY 0 + +/** + * @ingroup iface_wl_buffer + */ +#define WL_BUFFER_RELEASE_SINCE_VERSION 1 + +/** + * @ingroup iface_wl_buffer + */ +#define WL_BUFFER_DESTROY_SINCE_VERSION 1 + +/** @ingroup iface_wl_buffer */ +static inline void +wl_buffer_set_user_data(struct wl_buffer *wl_buffer, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) wl_buffer, user_data); +} + +/** @ingroup iface_wl_buffer */ +static inline void * +wl_buffer_get_user_data(struct wl_buffer *wl_buffer) +{ + return wl_proxy_get_user_data((struct wl_proxy *) wl_buffer); +} + +static inline uint32_t +wl_buffer_get_version(struct wl_buffer *wl_buffer) +{ + return wl_proxy_get_version((struct wl_proxy *) wl_buffer); +} + +/** + * @ingroup iface_wl_buffer + * + * Destroy a buffer. If and how you need to release the backing + * storage is defined by the buffer factory interface. + * + * For possible side-effects to a surface, see wl_surface.attach. + */ +static inline void +wl_buffer_destroy(struct wl_buffer *wl_buffer) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_buffer, + WL_BUFFER_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_buffer), WL_MARSHAL_FLAG_DESTROY); +} + +#ifndef WL_DATA_OFFER_ERROR_ENUM +#define WL_DATA_OFFER_ERROR_ENUM +enum wl_data_offer_error { + /** + * finish request was called untimely + */ + WL_DATA_OFFER_ERROR_INVALID_FINISH = 0, + /** + * action mask contains invalid values + */ + WL_DATA_OFFER_ERROR_INVALID_ACTION_MASK = 1, + /** + * action argument has an invalid value + */ + WL_DATA_OFFER_ERROR_INVALID_ACTION = 2, + /** + * offer doesn't accept this request + */ + WL_DATA_OFFER_ERROR_INVALID_OFFER = 3, +}; +#endif /* WL_DATA_OFFER_ERROR_ENUM */ + +/** + * @ingroup iface_wl_data_offer + * @struct wl_data_offer_listener + */ +struct wl_data_offer_listener { + /** + * advertise offered mime type + * + * Sent immediately after creating the wl_data_offer object. One + * event per offered mime type. + * @param mime_type offered mime type + */ + void (*offer)(void *data, + struct wl_data_offer *wl_data_offer, + const char *mime_type); + /** + * notify the source-side available actions + * + * This event indicates the actions offered by the data source. + * It will be sent immediately after creating the wl_data_offer + * object, or anytime the source side changes its offered actions + * through wl_data_source.set_actions. + * @param source_actions actions offered by the data source + * @since 3 + */ + void (*source_actions)(void *data, + struct wl_data_offer *wl_data_offer, + uint32_t source_actions); + /** + * notify the selected action + * + * This event indicates the action selected by the compositor + * after matching the source/destination side actions. Only one + * action (or none) will be offered here. + * + * This event can be emitted multiple times during the + * drag-and-drop operation in response to destination side action + * changes through wl_data_offer.set_actions. + * + * This event will no longer be emitted after wl_data_device.drop + * happened on the drag-and-drop destination, the client must honor + * the last action received, or the last preferred one set through + * wl_data_offer.set_actions when handling an "ask" action. + * + * Compositors may also change the selected action on the fly, + * mainly in response to keyboard modifier changes during the + * drag-and-drop operation. + * + * The most recent action received is always the valid one. Prior + * to receiving wl_data_device.drop, the chosen action may change + * (e.g. due to keyboard modifiers being pressed). At the time of + * receiving wl_data_device.drop the drag-and-drop destination must + * honor the last action received. + * + * Action changes may still happen after wl_data_device.drop, + * especially on "ask" actions, where the drag-and-drop destination + * may choose another action afterwards. Action changes happening + * at this stage are always the result of inter-client negotiation, + * the compositor shall no longer be able to induce a different + * action. + * + * Upon "ask" actions, it is expected that the drag-and-drop + * destination may potentially choose a different action and/or + * mime type, based on wl_data_offer.source_actions and finally + * chosen by the user (e.g. popping up a menu with the available + * options). The final wl_data_offer.set_actions and + * wl_data_offer.accept requests must happen before the call to + * wl_data_offer.finish. + * @param dnd_action action selected by the compositor + * @since 3 + */ + void (*action)(void *data, + struct wl_data_offer *wl_data_offer, + uint32_t dnd_action); +}; + +/** + * @ingroup iface_wl_data_offer + */ +static inline int +wl_data_offer_add_listener(struct wl_data_offer *wl_data_offer, + const struct wl_data_offer_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) wl_data_offer, + (void (**)(void)) listener, data); +} + +#define WL_DATA_OFFER_ACCEPT 0 +#define WL_DATA_OFFER_RECEIVE 1 +#define WL_DATA_OFFER_DESTROY 2 +#define WL_DATA_OFFER_FINISH 3 +#define WL_DATA_OFFER_SET_ACTIONS 4 + +/** + * @ingroup iface_wl_data_offer + */ +#define WL_DATA_OFFER_OFFER_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_offer + */ +#define WL_DATA_OFFER_SOURCE_ACTIONS_SINCE_VERSION 3 +/** + * @ingroup iface_wl_data_offer + */ +#define WL_DATA_OFFER_ACTION_SINCE_VERSION 3 + +/** + * @ingroup iface_wl_data_offer + */ +#define WL_DATA_OFFER_ACCEPT_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_offer + */ +#define WL_DATA_OFFER_RECEIVE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_offer + */ +#define WL_DATA_OFFER_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_offer + */ +#define WL_DATA_OFFER_FINISH_SINCE_VERSION 3 +/** + * @ingroup iface_wl_data_offer + */ +#define WL_DATA_OFFER_SET_ACTIONS_SINCE_VERSION 3 + +/** @ingroup iface_wl_data_offer */ +static inline void +wl_data_offer_set_user_data(struct wl_data_offer *wl_data_offer, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) wl_data_offer, user_data); +} + +/** @ingroup iface_wl_data_offer */ +static inline void * +wl_data_offer_get_user_data(struct wl_data_offer *wl_data_offer) +{ + return wl_proxy_get_user_data((struct wl_proxy *) wl_data_offer); +} + +static inline uint32_t +wl_data_offer_get_version(struct wl_data_offer *wl_data_offer) +{ + return wl_proxy_get_version((struct wl_proxy *) wl_data_offer); +} + +/** + * @ingroup iface_wl_data_offer + * + * Indicate that the client can accept the given mime type, or + * NULL for not accepted. + * + * For objects of version 2 or older, this request is used by the + * client to give feedback whether the client can receive the given + * mime type, or NULL if none is accepted; the feedback does not + * determine whether the drag-and-drop operation succeeds or not. + * + * For objects of version 3 or newer, this request determines the + * final result of the drag-and-drop operation. If the end result + * is that no mime types were accepted, the drag-and-drop operation + * will be cancelled and the corresponding drag source will receive + * wl_data_source.cancelled. Clients may still use this event in + * conjunction with wl_data_source.action for feedback. + */ +static inline void +wl_data_offer_accept(struct wl_data_offer *wl_data_offer, uint32_t serial, const char *mime_type) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_data_offer, + WL_DATA_OFFER_ACCEPT, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_offer), 0, serial, mime_type); +} + +/** + * @ingroup iface_wl_data_offer + * + * To transfer the offered data, the client issues this request + * and indicates the mime type it wants to receive. The transfer + * happens through the passed file descriptor (typically created + * with the pipe system call). The source client writes the data + * in the mime type representation requested and then closes the + * file descriptor. + * + * The receiving client reads from the read end of the pipe until + * EOF and then closes its end, at which point the transfer is + * complete. + * + * This request may happen multiple times for different mime types, + * both before and after wl_data_device.drop. Drag-and-drop destination + * clients may preemptively fetch data or examine it more closely to + * determine acceptance. + */ +static inline void +wl_data_offer_receive(struct wl_data_offer *wl_data_offer, const char *mime_type, int32_t fd) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_data_offer, + WL_DATA_OFFER_RECEIVE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_offer), 0, mime_type, fd); +} + +/** + * @ingroup iface_wl_data_offer + * + * Destroy the data offer. + */ +static inline void +wl_data_offer_destroy(struct wl_data_offer *wl_data_offer) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_data_offer, + WL_DATA_OFFER_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_offer), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_wl_data_offer + * + * Notifies the compositor that the drag destination successfully + * finished the drag-and-drop operation. + * + * Upon receiving this request, the compositor will emit + * wl_data_source.dnd_finished on the drag source client. + * + * It is a client error to perform other requests than + * wl_data_offer.destroy after this one. It is also an error to perform + * this request after a NULL mime type has been set in + * wl_data_offer.accept or no action was received through + * wl_data_offer.action. + * + * If wl_data_offer.finish request is received for a non drag and drop + * operation, the invalid_finish protocol error is raised. + */ +static inline void +wl_data_offer_finish(struct wl_data_offer *wl_data_offer) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_data_offer, + WL_DATA_OFFER_FINISH, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_offer), 0); +} + +/** + * @ingroup iface_wl_data_offer + * + * Sets the actions that the destination side client supports for + * this operation. This request may trigger the emission of + * wl_data_source.action and wl_data_offer.action events if the compositor + * needs to change the selected action. + * + * This request can be called multiple times throughout the + * drag-and-drop operation, typically in response to wl_data_device.enter + * or wl_data_device.motion events. + * + * This request determines the final result of the drag-and-drop + * operation. If the end result is that no action is accepted, + * the drag source will receive wl_data_source.cancelled. + * + * The dnd_actions argument must contain only values expressed in the + * wl_data_device_manager.dnd_actions enum, and the preferred_action + * argument must only contain one of those values set, otherwise it + * will result in a protocol error. + * + * While managing an "ask" action, the destination drag-and-drop client + * may perform further wl_data_offer.receive requests, and is expected + * to perform one last wl_data_offer.set_actions request with a preferred + * action other than "ask" (and optionally wl_data_offer.accept) before + * requesting wl_data_offer.finish, in order to convey the action selected + * by the user. If the preferred action is not in the + * wl_data_offer.source_actions mask, an error will be raised. + * + * If the "ask" action is dismissed (e.g. user cancellation), the client + * is expected to perform wl_data_offer.destroy right away. + * + * This request can only be made on drag-and-drop offers, a protocol error + * will be raised otherwise. + */ +static inline void +wl_data_offer_set_actions(struct wl_data_offer *wl_data_offer, uint32_t dnd_actions, uint32_t preferred_action) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_data_offer, + WL_DATA_OFFER_SET_ACTIONS, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_offer), 0, dnd_actions, preferred_action); +} + +#ifndef WL_DATA_SOURCE_ERROR_ENUM +#define WL_DATA_SOURCE_ERROR_ENUM +enum wl_data_source_error { + /** + * action mask contains invalid values + */ + WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK = 0, + /** + * source doesn't accept this request + */ + WL_DATA_SOURCE_ERROR_INVALID_SOURCE = 1, +}; +#endif /* WL_DATA_SOURCE_ERROR_ENUM */ + +/** + * @ingroup iface_wl_data_source + * @struct wl_data_source_listener + */ +struct wl_data_source_listener { + /** + * a target accepts an offered mime type + * + * Sent when a target accepts pointer_focus or motion events. If + * a target does not accept any of the offered types, type is NULL. + * + * Used for feedback during drag-and-drop. + * @param mime_type mime type accepted by the target + */ + void (*target)(void *data, + struct wl_data_source *wl_data_source, + const char *mime_type); + /** + * send the data + * + * Request for data from the client. Send the data as the + * specified mime type over the passed file descriptor, then close + * it. + * @param mime_type mime type for the data + * @param fd file descriptor for the data + */ + void (*send)(void *data, + struct wl_data_source *wl_data_source, + const char *mime_type, + int32_t fd); + /** + * selection was cancelled + * + * This data source is no longer valid. There are several reasons + * why this could happen: + * + * - The data source has been replaced by another data source. - + * The drag-and-drop operation was performed, but the drop + * destination did not accept any of the mime types offered through + * wl_data_source.target. - The drag-and-drop operation was + * performed, but the drop destination did not select any of the + * actions present in the mask offered through + * wl_data_source.action. - The drag-and-drop operation was + * performed but didn't happen over a surface. - The compositor + * cancelled the drag-and-drop operation (e.g. compositor dependent + * timeouts to avoid stale drag-and-drop transfers). + * + * The client should clean up and destroy this data source. + * + * For objects of version 2 or older, wl_data_source.cancelled will + * only be emitted if the data source was replaced by another data + * source. + */ + void (*cancelled)(void *data, + struct wl_data_source *wl_data_source); + /** + * the drag-and-drop operation physically finished + * + * The user performed the drop action. This event does not + * indicate acceptance, wl_data_source.cancelled may still be + * emitted afterwards if the drop destination does not accept any + * mime type. + * + * However, this event might not be received if the compositor + * cancelled the drag-and-drop operation before this event could + * happen. + * + * Note that the data_source may still be used in the future and + * should not be destroyed here. + * @since 3 + */ + void (*dnd_drop_performed)(void *data, + struct wl_data_source *wl_data_source); + /** + * the drag-and-drop operation concluded + * + * The drop destination finished interoperating with this data + * source, so the client is now free to destroy this data source + * and free all associated data. + * + * If the action used to perform the operation was "move", the + * source can now delete the transferred data. + * @since 3 + */ + void (*dnd_finished)(void *data, + struct wl_data_source *wl_data_source); + /** + * notify the selected action + * + * This event indicates the action selected by the compositor + * after matching the source/destination side actions. Only one + * action (or none) will be offered here. + * + * This event can be emitted multiple times during the + * drag-and-drop operation, mainly in response to destination side + * changes through wl_data_offer.set_actions, and as the data + * device enters/leaves surfaces. + * + * It is only possible to receive this event after + * wl_data_source.dnd_drop_performed if the drag-and-drop operation + * ended in an "ask" action, in which case the final + * wl_data_source.action event will happen immediately before + * wl_data_source.dnd_finished. + * + * Compositors may also change the selected action on the fly, + * mainly in response to keyboard modifier changes during the + * drag-and-drop operation. + * + * The most recent action received is always the valid one. The + * chosen action may change alongside negotiation (e.g. an "ask" + * action can turn into a "move" operation), so the effects of the + * final action must always be applied in + * wl_data_source.dnd_finished. + * + * Clients can trigger cursor surface changes from this point, so + * they reflect the current action. + * @param dnd_action action selected by the compositor + * @since 3 + */ + void (*action)(void *data, + struct wl_data_source *wl_data_source, + uint32_t dnd_action); +}; + +/** + * @ingroup iface_wl_data_source + */ +static inline int +wl_data_source_add_listener(struct wl_data_source *wl_data_source, + const struct wl_data_source_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) wl_data_source, + (void (**)(void)) listener, data); +} + +#define WL_DATA_SOURCE_OFFER 0 +#define WL_DATA_SOURCE_DESTROY 1 +#define WL_DATA_SOURCE_SET_ACTIONS 2 + +/** + * @ingroup iface_wl_data_source + */ +#define WL_DATA_SOURCE_TARGET_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_source + */ +#define WL_DATA_SOURCE_SEND_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_source + */ +#define WL_DATA_SOURCE_CANCELLED_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_source + */ +#define WL_DATA_SOURCE_DND_DROP_PERFORMED_SINCE_VERSION 3 +/** + * @ingroup iface_wl_data_source + */ +#define WL_DATA_SOURCE_DND_FINISHED_SINCE_VERSION 3 +/** + * @ingroup iface_wl_data_source + */ +#define WL_DATA_SOURCE_ACTION_SINCE_VERSION 3 + +/** + * @ingroup iface_wl_data_source + */ +#define WL_DATA_SOURCE_OFFER_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_source + */ +#define WL_DATA_SOURCE_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_source + */ +#define WL_DATA_SOURCE_SET_ACTIONS_SINCE_VERSION 3 + +/** @ingroup iface_wl_data_source */ +static inline void +wl_data_source_set_user_data(struct wl_data_source *wl_data_source, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) wl_data_source, user_data); +} + +/** @ingroup iface_wl_data_source */ +static inline void * +wl_data_source_get_user_data(struct wl_data_source *wl_data_source) +{ + return wl_proxy_get_user_data((struct wl_proxy *) wl_data_source); +} + +static inline uint32_t +wl_data_source_get_version(struct wl_data_source *wl_data_source) +{ + return wl_proxy_get_version((struct wl_proxy *) wl_data_source); +} + +/** + * @ingroup iface_wl_data_source + * + * This request adds a mime type to the set of mime types + * advertised to targets. Can be called several times to offer + * multiple types. + */ +static inline void +wl_data_source_offer(struct wl_data_source *wl_data_source, const char *mime_type) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_data_source, + WL_DATA_SOURCE_OFFER, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_source), 0, mime_type); +} + +/** + * @ingroup iface_wl_data_source + * + * Destroy the data source. + */ +static inline void +wl_data_source_destroy(struct wl_data_source *wl_data_source) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_data_source, + WL_DATA_SOURCE_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_source), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_wl_data_source + * + * Sets the actions that the source side client supports for this + * operation. This request may trigger wl_data_source.action and + * wl_data_offer.action events if the compositor needs to change the + * selected action. + * + * The dnd_actions argument must contain only values expressed in the + * wl_data_device_manager.dnd_actions enum, otherwise it will result + * in a protocol error. + * + * This request must be made once only, and can only be made on sources + * used in drag-and-drop, so it must be performed before + * wl_data_device.start_drag. Attempting to use the source other than + * for drag-and-drop will raise a protocol error. + */ +static inline void +wl_data_source_set_actions(struct wl_data_source *wl_data_source, uint32_t dnd_actions) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_data_source, + WL_DATA_SOURCE_SET_ACTIONS, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_source), 0, dnd_actions); +} + +#ifndef WL_DATA_DEVICE_ERROR_ENUM +#define WL_DATA_DEVICE_ERROR_ENUM +enum wl_data_device_error { + /** + * given wl_surface has another role + */ + WL_DATA_DEVICE_ERROR_ROLE = 0, + /** + * source has already been used + */ + WL_DATA_DEVICE_ERROR_USED_SOURCE = 1, +}; +#endif /* WL_DATA_DEVICE_ERROR_ENUM */ + +/** + * @ingroup iface_wl_data_device + * @struct wl_data_device_listener + */ +struct wl_data_device_listener { + /** + * introduce a new wl_data_offer + * + * The data_offer event introduces a new wl_data_offer object, + * which will subsequently be used in either the data_device.enter + * event (for drag-and-drop) or the data_device.selection event + * (for selections). Immediately following the + * data_device.data_offer event, the new data_offer object will + * send out data_offer.offer events to describe the mime types it + * offers. + * @param id the new data_offer object + */ + void (*data_offer)(void *data, + struct wl_data_device *wl_data_device, + struct wl_data_offer *id); + /** + * initiate drag-and-drop session + * + * This event is sent when an active drag-and-drop pointer enters + * a surface owned by the client. The position of the pointer at + * enter time is provided by the x and y arguments, in + * surface-local coordinates. + * @param serial serial number of the enter event + * @param surface client surface entered + * @param x surface-local x coordinate + * @param y surface-local y coordinate + * @param id source data_offer object + */ + void (*enter)(void *data, + struct wl_data_device *wl_data_device, + uint32_t serial, + struct wl_surface *surface, + wl_fixed_t x, + wl_fixed_t y, + struct wl_data_offer *id); + /** + * end drag-and-drop session + * + * This event is sent when the drag-and-drop pointer leaves the + * surface and the session ends. The client must destroy the + * wl_data_offer introduced at enter time at this point. + */ + void (*leave)(void *data, + struct wl_data_device *wl_data_device); + /** + * drag-and-drop session motion + * + * This event is sent when the drag-and-drop pointer moves within + * the currently focused surface. The new position of the pointer + * is provided by the x and y arguments, in surface-local + * coordinates. + * @param time timestamp with millisecond granularity + * @param x surface-local x coordinate + * @param y surface-local y coordinate + */ + void (*motion)(void *data, + struct wl_data_device *wl_data_device, + uint32_t time, + wl_fixed_t x, + wl_fixed_t y); + /** + * end drag-and-drop session successfully + * + * The event is sent when a drag-and-drop operation is ended + * because the implicit grab is removed. + * + * The drag-and-drop destination is expected to honor the last + * action received through wl_data_offer.action, if the resulting + * action is "copy" or "move", the destination can still perform + * wl_data_offer.receive requests, and is expected to end all + * transfers with a wl_data_offer.finish request. + * + * If the resulting action is "ask", the action will not be + * considered final. The drag-and-drop destination is expected to + * perform one last wl_data_offer.set_actions request, or + * wl_data_offer.destroy in order to cancel the operation. + */ + void (*drop)(void *data, + struct wl_data_device *wl_data_device); + /** + * advertise new selection + * + * The selection event is sent out to notify the client of a new + * wl_data_offer for the selection for this device. The + * data_device.data_offer and the data_offer.offer events are sent + * out immediately before this event to introduce the data offer + * object. The selection event is sent to a client immediately + * before receiving keyboard focus and when a new selection is set + * while the client has keyboard focus. The data_offer is valid + * until a new data_offer or NULL is received or until the client + * loses keyboard focus. Switching surface with keyboard focus + * within the same client doesn't mean a new selection will be + * sent. The client must destroy the previous selection data_offer, + * if any, upon receiving this event. + * @param id selection data_offer object + */ + void (*selection)(void *data, + struct wl_data_device *wl_data_device, + struct wl_data_offer *id); +}; + +/** + * @ingroup iface_wl_data_device + */ +static inline int +wl_data_device_add_listener(struct wl_data_device *wl_data_device, + const struct wl_data_device_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) wl_data_device, + (void (**)(void)) listener, data); +} + +#define WL_DATA_DEVICE_START_DRAG 0 +#define WL_DATA_DEVICE_SET_SELECTION 1 +#define WL_DATA_DEVICE_RELEASE 2 + +/** + * @ingroup iface_wl_data_device + */ +#define WL_DATA_DEVICE_DATA_OFFER_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_device + */ +#define WL_DATA_DEVICE_ENTER_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_device + */ +#define WL_DATA_DEVICE_LEAVE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_device + */ +#define WL_DATA_DEVICE_MOTION_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_device + */ +#define WL_DATA_DEVICE_DROP_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_device + */ +#define WL_DATA_DEVICE_SELECTION_SINCE_VERSION 1 + +/** + * @ingroup iface_wl_data_device + */ +#define WL_DATA_DEVICE_START_DRAG_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_device + */ +#define WL_DATA_DEVICE_SET_SELECTION_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_device + */ +#define WL_DATA_DEVICE_RELEASE_SINCE_VERSION 2 + +/** @ingroup iface_wl_data_device */ +static inline void +wl_data_device_set_user_data(struct wl_data_device *wl_data_device, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) wl_data_device, user_data); +} + +/** @ingroup iface_wl_data_device */ +static inline void * +wl_data_device_get_user_data(struct wl_data_device *wl_data_device) +{ + return wl_proxy_get_user_data((struct wl_proxy *) wl_data_device); +} + +static inline uint32_t +wl_data_device_get_version(struct wl_data_device *wl_data_device) +{ + return wl_proxy_get_version((struct wl_proxy *) wl_data_device); +} + +/** @ingroup iface_wl_data_device */ +static inline void +wl_data_device_destroy(struct wl_data_device *wl_data_device) +{ + wl_proxy_destroy((struct wl_proxy *) wl_data_device); +} + +/** + * @ingroup iface_wl_data_device + * + * This request asks the compositor to start a drag-and-drop + * operation on behalf of the client. + * + * The source argument is the data source that provides the data + * for the eventual data transfer. If source is NULL, enter, leave + * and motion events are sent only to the client that initiated the + * drag and the client is expected to handle the data passing + * internally. If source is destroyed, the drag-and-drop session will be + * cancelled. + * + * The origin surface is the surface where the drag originates and + * the client must have an active implicit grab that matches the + * serial. + * + * The icon surface is an optional (can be NULL) surface that + * provides an icon to be moved around with the cursor. Initially, + * the top-left corner of the icon surface is placed at the cursor + * hotspot, but subsequent wl_surface.offset requests can move the + * relative position. Attach requests must be confirmed with + * wl_surface.commit as usual. The icon surface is given the role of + * a drag-and-drop icon. If the icon surface already has another role, + * it raises a protocol error. + * + * The input region is ignored for wl_surfaces with the role of a + * drag-and-drop icon. + * + * The given source may not be used in any further set_selection or + * start_drag requests. Attempting to reuse a previously-used source + * may send a used_source error. + */ +static inline void +wl_data_device_start_drag(struct wl_data_device *wl_data_device, struct wl_data_source *source, struct wl_surface *origin, struct wl_surface *icon, uint32_t serial) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_data_device, + WL_DATA_DEVICE_START_DRAG, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_device), 0, source, origin, icon, serial); +} + +/** + * @ingroup iface_wl_data_device + * + * This request asks the compositor to set the selection + * to the data from the source on behalf of the client. + * + * To unset the selection, set the source to NULL. + * + * The given source may not be used in any further set_selection or + * start_drag requests. Attempting to reuse a previously-used source + * may send a used_source error. + */ +static inline void +wl_data_device_set_selection(struct wl_data_device *wl_data_device, struct wl_data_source *source, uint32_t serial) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_data_device, + WL_DATA_DEVICE_SET_SELECTION, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_device), 0, source, serial); +} + +/** + * @ingroup iface_wl_data_device + * + * This request destroys the data device. + */ +static inline void +wl_data_device_release(struct wl_data_device *wl_data_device) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_data_device, + WL_DATA_DEVICE_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_device), WL_MARSHAL_FLAG_DESTROY); +} + +#ifndef WL_DATA_DEVICE_MANAGER_DND_ACTION_ENUM +#define WL_DATA_DEVICE_MANAGER_DND_ACTION_ENUM +/** + * @ingroup iface_wl_data_device_manager + * drag and drop actions + * + * This is a bitmask of the available/preferred actions in a + * drag-and-drop operation. + * + * In the compositor, the selected action is a result of matching the + * actions offered by the source and destination sides. "action" events + * with a "none" action will be sent to both source and destination if + * there is no match. All further checks will effectively happen on + * (source actions ∩ destination actions). + * + * In addition, compositors may also pick different actions in + * reaction to key modifiers being pressed. One common design that + * is used in major toolkits (and the behavior recommended for + * compositors) is: + * + * - If no modifiers are pressed, the first match (in bit order) + * will be used. + * - Pressing Shift selects "move", if enabled in the mask. + * - Pressing Control selects "copy", if enabled in the mask. + * + * Behavior beyond that is considered implementation-dependent. + * Compositors may for example bind other modifiers (like Alt/Meta) + * or drags initiated with other buttons than BTN_LEFT to specific + * actions (e.g. "ask"). + */ +enum wl_data_device_manager_dnd_action { + /** + * no action + */ + WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE = 0, + /** + * copy action + */ + WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY = 1, + /** + * move action + */ + WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE = 2, + /** + * ask action + */ + WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK = 4, +}; +#endif /* WL_DATA_DEVICE_MANAGER_DND_ACTION_ENUM */ + +#define WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE 0 +#define WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE 1 +#define WL_DATA_DEVICE_MANAGER_RELEASE 2 + + +/** + * @ingroup iface_wl_data_device_manager + */ +#define WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_device_manager + */ +#define WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_device_manager + */ +#define WL_DATA_DEVICE_MANAGER_RELEASE_SINCE_VERSION 4 + +/** @ingroup iface_wl_data_device_manager */ +static inline void +wl_data_device_manager_set_user_data(struct wl_data_device_manager *wl_data_device_manager, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) wl_data_device_manager, user_data); +} + +/** @ingroup iface_wl_data_device_manager */ +static inline void * +wl_data_device_manager_get_user_data(struct wl_data_device_manager *wl_data_device_manager) +{ + return wl_proxy_get_user_data((struct wl_proxy *) wl_data_device_manager); +} + +static inline uint32_t +wl_data_device_manager_get_version(struct wl_data_device_manager *wl_data_device_manager) +{ + return wl_proxy_get_version((struct wl_proxy *) wl_data_device_manager); +} + +/** @ingroup iface_wl_data_device_manager */ +static inline void +wl_data_device_manager_destroy(struct wl_data_device_manager *wl_data_device_manager) +{ + wl_proxy_destroy((struct wl_proxy *) wl_data_device_manager); +} + +/** + * @ingroup iface_wl_data_device_manager + * + * Create a new data source. + */ +static inline struct wl_data_source * +wl_data_device_manager_create_data_source(struct wl_data_device_manager *wl_data_device_manager) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) wl_data_device_manager, + WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE, &wl_data_source_interface, wl_proxy_get_version((struct wl_proxy *) wl_data_device_manager), 0, NULL); + + return (struct wl_data_source *) id; +} + +/** + * @ingroup iface_wl_data_device_manager + * + * Create a new data device for a given seat. + */ +static inline struct wl_data_device * +wl_data_device_manager_get_data_device(struct wl_data_device_manager *wl_data_device_manager, struct wl_seat *seat) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) wl_data_device_manager, + WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE, &wl_data_device_interface, wl_proxy_get_version((struct wl_proxy *) wl_data_device_manager), 0, NULL, seat); + + return (struct wl_data_device *) id; +} + +/** + * @ingroup iface_wl_data_device_manager + * + * This request destroys the wl_data_device_manager. This has no effect on any other + * objects. + */ +static inline void +wl_data_device_manager_release(struct wl_data_device_manager *wl_data_device_manager) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_data_device_manager, + WL_DATA_DEVICE_MANAGER_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_device_manager), WL_MARSHAL_FLAG_DESTROY); +} + +#ifndef WL_SHELL_ERROR_ENUM +#define WL_SHELL_ERROR_ENUM +enum wl_shell_error { + /** + * given wl_surface has another role + */ + WL_SHELL_ERROR_ROLE = 0, +}; +#endif /* WL_SHELL_ERROR_ENUM */ + +#define WL_SHELL_GET_SHELL_SURFACE 0 + + +/** + * @ingroup iface_wl_shell + */ +#define WL_SHELL_GET_SHELL_SURFACE_SINCE_VERSION 1 + +/** @ingroup iface_wl_shell */ +static inline void +wl_shell_set_user_data(struct wl_shell *wl_shell, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) wl_shell, user_data); +} + +/** @ingroup iface_wl_shell */ +static inline void * +wl_shell_get_user_data(struct wl_shell *wl_shell) +{ + return wl_proxy_get_user_data((struct wl_proxy *) wl_shell); +} + +static inline uint32_t +wl_shell_get_version(struct wl_shell *wl_shell) +{ + return wl_proxy_get_version((struct wl_proxy *) wl_shell); +} + +/** @ingroup iface_wl_shell */ +static inline void +wl_shell_destroy(struct wl_shell *wl_shell) +{ + wl_proxy_destroy((struct wl_proxy *) wl_shell); +} + +/** + * @ingroup iface_wl_shell + * + * Create a shell surface for an existing surface. This gives + * the wl_surface the role of a shell surface. If the wl_surface + * already has another role, it raises a protocol error. + * + * Only one shell surface can be associated with a given surface. + */ +static inline struct wl_shell_surface * +wl_shell_get_shell_surface(struct wl_shell *wl_shell, struct wl_surface *surface) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) wl_shell, + WL_SHELL_GET_SHELL_SURFACE, &wl_shell_surface_interface, wl_proxy_get_version((struct wl_proxy *) wl_shell), 0, NULL, surface); + + return (struct wl_shell_surface *) id; +} + +#ifndef WL_SHELL_SURFACE_RESIZE_ENUM +#define WL_SHELL_SURFACE_RESIZE_ENUM +/** + * @ingroup iface_wl_shell_surface + * edge values for resizing + * + * These values are used to indicate which edge of a surface + * is being dragged in a resize operation. The server may + * use this information to adapt its behavior, e.g. choose + * an appropriate cursor image. + */ +enum wl_shell_surface_resize { + /** + * no edge + */ + WL_SHELL_SURFACE_RESIZE_NONE = 0, + /** + * top edge + */ + WL_SHELL_SURFACE_RESIZE_TOP = 1, + /** + * bottom edge + */ + WL_SHELL_SURFACE_RESIZE_BOTTOM = 2, + /** + * left edge + */ + WL_SHELL_SURFACE_RESIZE_LEFT = 4, + /** + * top and left edges + */ + WL_SHELL_SURFACE_RESIZE_TOP_LEFT = 5, + /** + * bottom and left edges + */ + WL_SHELL_SURFACE_RESIZE_BOTTOM_LEFT = 6, + /** + * right edge + */ + WL_SHELL_SURFACE_RESIZE_RIGHT = 8, + /** + * top and right edges + */ + WL_SHELL_SURFACE_RESIZE_TOP_RIGHT = 9, + /** + * bottom and right edges + */ + WL_SHELL_SURFACE_RESIZE_BOTTOM_RIGHT = 10, +}; +#endif /* WL_SHELL_SURFACE_RESIZE_ENUM */ + +#ifndef WL_SHELL_SURFACE_TRANSIENT_ENUM +#define WL_SHELL_SURFACE_TRANSIENT_ENUM +/** + * @ingroup iface_wl_shell_surface + * details of transient behaviour + * + * These flags specify details of the expected behaviour + * of transient surfaces. Used in the set_transient request. + */ +enum wl_shell_surface_transient { + /** + * do not set keyboard focus + */ + WL_SHELL_SURFACE_TRANSIENT_INACTIVE = 0x1, +}; +#endif /* WL_SHELL_SURFACE_TRANSIENT_ENUM */ + +#ifndef WL_SHELL_SURFACE_FULLSCREEN_METHOD_ENUM +#define WL_SHELL_SURFACE_FULLSCREEN_METHOD_ENUM +/** + * @ingroup iface_wl_shell_surface + * different method to set the surface fullscreen + * + * Hints to indicate to the compositor how to deal with a conflict + * between the dimensions of the surface and the dimensions of the + * output. The compositor is free to ignore this parameter. + */ +enum wl_shell_surface_fullscreen_method { + /** + * no preference, apply default policy + */ + WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT = 0, + /** + * scale, preserve the surface's aspect ratio and center on output + */ + WL_SHELL_SURFACE_FULLSCREEN_METHOD_SCALE = 1, + /** + * switch output mode to the smallest mode that can fit the surface, add black borders to compensate size mismatch + */ + WL_SHELL_SURFACE_FULLSCREEN_METHOD_DRIVER = 2, + /** + * no upscaling, center on output and add black borders to compensate size mismatch + */ + WL_SHELL_SURFACE_FULLSCREEN_METHOD_FILL = 3, +}; +#endif /* WL_SHELL_SURFACE_FULLSCREEN_METHOD_ENUM */ + +/** + * @ingroup iface_wl_shell_surface + * @struct wl_shell_surface_listener + */ +struct wl_shell_surface_listener { + /** + * ping client + * + * Ping a client to check if it is receiving events and sending + * requests. A client is expected to reply with a pong request. + * @param serial serial number of the ping + */ + void (*ping)(void *data, + struct wl_shell_surface *wl_shell_surface, + uint32_t serial); + /** + * suggest resize + * + * The configure event asks the client to resize its surface. + * + * The size is a hint, in the sense that the client is free to + * ignore it if it doesn't resize, pick a smaller size (to satisfy + * aspect ratio or resize in steps of NxM pixels). + * + * The edges parameter provides a hint about how the surface was + * resized. The client may use this information to decide how to + * adjust its content to the new size (e.g. a scrolling area might + * adjust its content position to leave the viewable content + * unmoved). + * + * The client is free to dismiss all but the last configure event + * it received. + * + * The width and height arguments specify the size of the window in + * surface-local coordinates. + * @param edges how the surface was resized + * @param width new width of the surface + * @param height new height of the surface + */ + void (*configure)(void *data, + struct wl_shell_surface *wl_shell_surface, + uint32_t edges, + int32_t width, + int32_t height); + /** + * popup interaction is done + * + * The popup_done event is sent out when a popup grab is broken, + * that is, when the user clicks a surface that doesn't belong to + * the client owning the popup surface. + */ + void (*popup_done)(void *data, + struct wl_shell_surface *wl_shell_surface); +}; + +/** + * @ingroup iface_wl_shell_surface + */ +static inline int +wl_shell_surface_add_listener(struct wl_shell_surface *wl_shell_surface, + const struct wl_shell_surface_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) wl_shell_surface, + (void (**)(void)) listener, data); +} + +#define WL_SHELL_SURFACE_PONG 0 +#define WL_SHELL_SURFACE_MOVE 1 +#define WL_SHELL_SURFACE_RESIZE 2 +#define WL_SHELL_SURFACE_SET_TOPLEVEL 3 +#define WL_SHELL_SURFACE_SET_TRANSIENT 4 +#define WL_SHELL_SURFACE_SET_FULLSCREEN 5 +#define WL_SHELL_SURFACE_SET_POPUP 6 +#define WL_SHELL_SURFACE_SET_MAXIMIZED 7 +#define WL_SHELL_SURFACE_SET_TITLE 8 +#define WL_SHELL_SURFACE_SET_CLASS 9 + +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_PING_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_CONFIGURE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_POPUP_DONE_SINCE_VERSION 1 + +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_PONG_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_MOVE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_RESIZE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_SET_TOPLEVEL_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_SET_TRANSIENT_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_SET_FULLSCREEN_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_SET_POPUP_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_SET_MAXIMIZED_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_SET_TITLE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_SET_CLASS_SINCE_VERSION 1 + +/** @ingroup iface_wl_shell_surface */ +static inline void +wl_shell_surface_set_user_data(struct wl_shell_surface *wl_shell_surface, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) wl_shell_surface, user_data); +} + +/** @ingroup iface_wl_shell_surface */ +static inline void * +wl_shell_surface_get_user_data(struct wl_shell_surface *wl_shell_surface) +{ + return wl_proxy_get_user_data((struct wl_proxy *) wl_shell_surface); +} + +static inline uint32_t +wl_shell_surface_get_version(struct wl_shell_surface *wl_shell_surface) +{ + return wl_proxy_get_version((struct wl_proxy *) wl_shell_surface); +} + +/** @ingroup iface_wl_shell_surface */ +static inline void +wl_shell_surface_destroy(struct wl_shell_surface *wl_shell_surface) +{ + wl_proxy_destroy((struct wl_proxy *) wl_shell_surface); +} + +/** + * @ingroup iface_wl_shell_surface + * + * A client must respond to a ping event with a pong request or + * the client may be deemed unresponsive. + */ +static inline void +wl_shell_surface_pong(struct wl_shell_surface *wl_shell_surface, uint32_t serial) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, + WL_SHELL_SURFACE_PONG, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, serial); +} + +/** + * @ingroup iface_wl_shell_surface + * + * Start a pointer-driven move of the surface. + * + * This request must be used in response to a button press event. + * The server may ignore move requests depending on the state of + * the surface (e.g. fullscreen or maximized). + */ +static inline void +wl_shell_surface_move(struct wl_shell_surface *wl_shell_surface, struct wl_seat *seat, uint32_t serial) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, + WL_SHELL_SURFACE_MOVE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, seat, serial); +} + +/** + * @ingroup iface_wl_shell_surface + * + * Start a pointer-driven resizing of the surface. + * + * This request must be used in response to a button press event. + * The server may ignore resize requests depending on the state of + * the surface (e.g. fullscreen or maximized). + */ +static inline void +wl_shell_surface_resize(struct wl_shell_surface *wl_shell_surface, struct wl_seat *seat, uint32_t serial, uint32_t edges) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, + WL_SHELL_SURFACE_RESIZE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, seat, serial, edges); +} + +/** + * @ingroup iface_wl_shell_surface + * + * Map the surface as a toplevel surface. + * + * A toplevel surface is not fullscreen, maximized or transient. + */ +static inline void +wl_shell_surface_set_toplevel(struct wl_shell_surface *wl_shell_surface) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, + WL_SHELL_SURFACE_SET_TOPLEVEL, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0); +} + +/** + * @ingroup iface_wl_shell_surface + * + * Map the surface relative to an existing surface. + * + * The x and y arguments specify the location of the upper left + * corner of the surface relative to the upper left corner of the + * parent surface, in surface-local coordinates. + * + * The flags argument controls details of the transient behaviour. + */ +static inline void +wl_shell_surface_set_transient(struct wl_shell_surface *wl_shell_surface, struct wl_surface *parent, int32_t x, int32_t y, uint32_t flags) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, + WL_SHELL_SURFACE_SET_TRANSIENT, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, parent, x, y, flags); +} + +/** + * @ingroup iface_wl_shell_surface + * + * Map the surface as a fullscreen surface. + * + * If an output parameter is given then the surface will be made + * fullscreen on that output. If the client does not specify the + * output then the compositor will apply its policy - usually + * choosing the output on which the surface has the biggest surface + * area. + * + * The client may specify a method to resolve a size conflict + * between the output size and the surface size - this is provided + * through the method parameter. + * + * The framerate parameter is used only when the method is set + * to "driver", to indicate the preferred framerate. A value of 0 + * indicates that the client does not care about framerate. The + * framerate is specified in mHz, that is framerate of 60000 is 60Hz. + * + * A method of "scale" or "driver" implies a scaling operation of + * the surface, either via a direct scaling operation or a change of + * the output mode. This will override any kind of output scaling, so + * that mapping a surface with a buffer size equal to the mode can + * fill the screen independent of buffer_scale. + * + * A method of "fill" means we don't scale up the buffer, however + * any output scale is applied. This means that you may run into + * an edge case where the application maps a buffer with the same + * size of the output mode but buffer_scale 1 (thus making a + * surface larger than the output). In this case it is allowed to + * downscale the results to fit the screen. + * + * The compositor must reply to this request with a configure event + * with the dimensions for the output on which the surface will + * be made fullscreen. + */ +static inline void +wl_shell_surface_set_fullscreen(struct wl_shell_surface *wl_shell_surface, uint32_t method, uint32_t framerate, struct wl_output *output) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, + WL_SHELL_SURFACE_SET_FULLSCREEN, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, method, framerate, output); +} + +/** + * @ingroup iface_wl_shell_surface + * + * Map the surface as a popup. + * + * A popup surface is a transient surface with an added pointer + * grab. + * + * An existing implicit grab will be changed to owner-events mode, + * and the popup grab will continue after the implicit grab ends + * (i.e. releasing the mouse button does not cause the popup to + * be unmapped). + * + * The popup grab continues until the window is destroyed or a + * mouse button is pressed in any other client's window. A click + * in any of the client's surfaces is reported as normal, however, + * clicks in other clients' surfaces will be discarded and trigger + * the callback. + * + * The x and y arguments specify the location of the upper left + * corner of the surface relative to the upper left corner of the + * parent surface, in surface-local coordinates. + */ +static inline void +wl_shell_surface_set_popup(struct wl_shell_surface *wl_shell_surface, struct wl_seat *seat, uint32_t serial, struct wl_surface *parent, int32_t x, int32_t y, uint32_t flags) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, + WL_SHELL_SURFACE_SET_POPUP, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, seat, serial, parent, x, y, flags); +} + +/** + * @ingroup iface_wl_shell_surface + * + * Map the surface as a maximized surface. + * + * If an output parameter is given then the surface will be + * maximized on that output. If the client does not specify the + * output then the compositor will apply its policy - usually + * choosing the output on which the surface has the biggest surface + * area. + * + * The compositor will reply with a configure event telling + * the expected new surface size. The operation is completed + * on the next buffer attach to this surface. + * + * A maximized surface typically fills the entire output it is + * bound to, except for desktop elements such as panels. This is + * the main difference between a maximized shell surface and a + * fullscreen shell surface. + * + * The details depend on the compositor implementation. + */ +static inline void +wl_shell_surface_set_maximized(struct wl_shell_surface *wl_shell_surface, struct wl_output *output) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, + WL_SHELL_SURFACE_SET_MAXIMIZED, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, output); +} + +/** + * @ingroup iface_wl_shell_surface + * + * Set a short title for the surface. + * + * This string may be used to identify the surface in a task bar, + * window list, or other user interface elements provided by the + * compositor. + * + * The string must be encoded in UTF-8. + */ +static inline void +wl_shell_surface_set_title(struct wl_shell_surface *wl_shell_surface, const char *title) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, + WL_SHELL_SURFACE_SET_TITLE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, title); +} + +/** + * @ingroup iface_wl_shell_surface + * + * Set a class for the surface. + * + * The surface class identifies the general class of applications + * to which the surface belongs. A common convention is to use the + * file name (or the full path if it is a non-standard location) of + * the application's .desktop file as the class. + */ +static inline void +wl_shell_surface_set_class(struct wl_shell_surface *wl_shell_surface, const char *class_) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, + WL_SHELL_SURFACE_SET_CLASS, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, class_); +} + +#ifndef WL_SURFACE_ERROR_ENUM +#define WL_SURFACE_ERROR_ENUM +/** + * @ingroup iface_wl_surface + * wl_surface error values + * + * These errors can be emitted in response to wl_surface requests. + */ +enum wl_surface_error { + /** + * buffer scale value is invalid + */ + WL_SURFACE_ERROR_INVALID_SCALE = 0, + /** + * buffer transform value is invalid + */ + WL_SURFACE_ERROR_INVALID_TRANSFORM = 1, + /** + * buffer size is invalid + */ + WL_SURFACE_ERROR_INVALID_SIZE = 2, + /** + * buffer offset is invalid + */ + WL_SURFACE_ERROR_INVALID_OFFSET = 3, + /** + * surface was destroyed before its role object + */ + WL_SURFACE_ERROR_DEFUNCT_ROLE_OBJECT = 4, + /** + * no buffer was attached + */ + WL_SURFACE_ERROR_NO_BUFFER = 5, +}; +#endif /* WL_SURFACE_ERROR_ENUM */ + +/** + * @ingroup iface_wl_surface + * @struct wl_surface_listener + */ +struct wl_surface_listener { + /** + * surface enters an output + * + * This is emitted whenever a surface's creation, movement, or + * resizing results in some part of it being within the scanout + * region of an output. + * + * Note that a surface may be overlapping with zero or more + * outputs. + * @param output output entered by the surface + */ + void (*enter)(void *data, + struct wl_surface *wl_surface, + struct wl_output *output); + /** + * surface leaves an output + * + * This is emitted whenever a surface's creation, movement, or + * resizing results in it no longer having any part of it within + * the scanout region of an output. + * + * Clients should not use the number of outputs the surface is on + * for frame throttling purposes. The surface might be hidden even + * if no leave event has been sent, and the compositor might expect + * new surface content updates even if no enter event has been + * sent. The frame event should be used instead. + * @param output output left by the surface + */ + void (*leave)(void *data, + struct wl_surface *wl_surface, + struct wl_output *output); + /** + * preferred buffer scale for the surface + * + * This event indicates the preferred buffer scale for this + * surface. It is sent whenever the compositor's preference + * changes. + * + * Before receiving this event the preferred buffer scale for this + * surface is 1. + * + * It is intended that scaling aware clients use this event to + * scale their content and use wl_surface.set_buffer_scale to + * indicate the scale they have rendered with. This allows clients + * to supply a higher detail buffer. + * + * The compositor shall emit a scale value greater than 0. + * @param factor preferred scaling factor + * @since 6 + */ + void (*preferred_buffer_scale)(void *data, + struct wl_surface *wl_surface, + int32_t factor); + /** + * preferred buffer transform for the surface + * + * This event indicates the preferred buffer transform for this + * surface. It is sent whenever the compositor's preference + * changes. + * + * Before receiving this event the preferred buffer transform for + * this surface is normal. + * + * Applying this transformation to the surface buffer contents and + * using wl_surface.set_buffer_transform might allow the compositor + * to use the surface buffer more efficiently. + * @param transform preferred transform + * @since 6 + */ + void (*preferred_buffer_transform)(void *data, + struct wl_surface *wl_surface, + uint32_t transform); +}; + +/** + * @ingroup iface_wl_surface + */ +static inline int +wl_surface_add_listener(struct wl_surface *wl_surface, + const struct wl_surface_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) wl_surface, + (void (**)(void)) listener, data); +} + +#define WL_SURFACE_DESTROY 0 +#define WL_SURFACE_ATTACH 1 +#define WL_SURFACE_DAMAGE 2 +#define WL_SURFACE_FRAME 3 +#define WL_SURFACE_SET_OPAQUE_REGION 4 +#define WL_SURFACE_SET_INPUT_REGION 5 +#define WL_SURFACE_COMMIT 6 +#define WL_SURFACE_SET_BUFFER_TRANSFORM 7 +#define WL_SURFACE_SET_BUFFER_SCALE 8 +#define WL_SURFACE_DAMAGE_BUFFER 9 +#define WL_SURFACE_OFFSET 10 +#define WL_SURFACE_GET_RELEASE 11 + +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_ENTER_SINCE_VERSION 1 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_LEAVE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION 6 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_PREFERRED_BUFFER_TRANSFORM_SINCE_VERSION 6 + +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_ATTACH_SINCE_VERSION 1 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_DAMAGE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_FRAME_SINCE_VERSION 1 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_SET_OPAQUE_REGION_SINCE_VERSION 1 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_SET_INPUT_REGION_SINCE_VERSION 1 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_COMMIT_SINCE_VERSION 1 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_SET_BUFFER_TRANSFORM_SINCE_VERSION 2 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_SET_BUFFER_SCALE_SINCE_VERSION 3 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION 4 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_OFFSET_SINCE_VERSION 5 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_GET_RELEASE_SINCE_VERSION 7 + +/** @ingroup iface_wl_surface */ +static inline void +wl_surface_set_user_data(struct wl_surface *wl_surface, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) wl_surface, user_data); +} + +/** @ingroup iface_wl_surface */ +static inline void * +wl_surface_get_user_data(struct wl_surface *wl_surface) +{ + return wl_proxy_get_user_data((struct wl_proxy *) wl_surface); +} + +static inline uint32_t +wl_surface_get_version(struct wl_surface *wl_surface) +{ + return wl_proxy_get_version((struct wl_proxy *) wl_surface); +} + +/** + * @ingroup iface_wl_surface + * + * Deletes the surface and invalidates its object ID. + */ +static inline void +wl_surface_destroy(struct wl_surface *wl_surface) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, + WL_SURFACE_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_wl_surface + * + * Set a buffer as the content of this surface. + * + * The new size of the surface is calculated based on the buffer + * size transformed by the inverse buffer_transform and the + * inverse buffer_scale. This means that at commit time the supplied + * buffer size must be an integer multiple of the buffer_scale. If + * that's not the case, an invalid_size error is sent. + * + * The x and y arguments specify the location of the new pending + * buffer's upper left corner, relative to the current buffer's upper + * left corner, in surface-local coordinates. In other words, the + * x and y, combined with the new surface size define in which + * directions the surface's size changes. Setting anything other than 0 + * as x and y arguments is discouraged, and should instead be replaced + * with using the separate wl_surface.offset request. + * + * When the bound wl_surface version is 5 or higher, passing any + * non-zero x or y is a protocol violation, and will result in an + * 'invalid_offset' error being raised. The x and y arguments are ignored + * and do not change the pending state. To achieve equivalent semantics, + * use wl_surface.offset. + * + * Surface contents are double-buffered state, see wl_surface.commit. + * + * The initial surface contents are void; there is no content. + * wl_surface.attach assigns the given wl_buffer as the pending + * wl_buffer. wl_surface.commit makes the pending wl_buffer the new + * surface contents, and the size of the surface becomes the size + * calculated from the wl_buffer, as described above. After commit, + * there is no pending buffer until the next attach. + * + * Committing a pending wl_buffer allows the compositor to read the + * pixels in the wl_buffer. The compositor may access the pixels at + * any time after the wl_surface.commit request. When the compositor + * will not access the pixels anymore, it will send the + * wl_buffer.release event. Only after receiving wl_buffer.release, + * the client may reuse the wl_buffer. A wl_buffer that has been + * attached and then replaced by another attach instead of committed + * will not receive a release event, and is not used by the + * compositor. + * + * If a pending wl_buffer has been committed to more than one wl_surface, + * the delivery of wl_buffer.release events becomes undefined. A well + * behaved client should not rely on wl_buffer.release events in this + * case. Instead, clients hitting this case should use + * wl_surface.get_release or use a protocol extension providing per-commit + * release notifications (if none of these options are available, a + * fallback can be implemented by creating multiple wl_buffer objects from + * the same backing storage). + * + * Destroying the wl_buffer after wl_buffer.release does not change + * the surface contents. Destroying the wl_buffer before wl_buffer.release + * is allowed as long as the underlying buffer storage isn't re-used (this + * can happen e.g. on client process termination). However, if the client + * destroys the wl_buffer before receiving the wl_buffer.release event and + * mutates the underlying buffer storage, the surface contents become + * undefined immediately. + * + * If wl_surface.attach is sent with a NULL wl_buffer, the + * following wl_surface.commit will remove the surface content. + * + * If a pending wl_buffer has been destroyed, the result is not specified. + * Many compositors are known to remove the surface content on the following + * wl_surface.commit, but this behaviour is not universal. Clients seeking to + * maximise compatibility should not destroy pending buffers and should + * ensure that they explicitly remove content from surfaces, even after + * destroying buffers. + */ +static inline void +wl_surface_attach(struct wl_surface *wl_surface, struct wl_buffer *buffer, int32_t x, int32_t y) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, + WL_SURFACE_ATTACH, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, buffer, x, y); +} + +/** + * @ingroup iface_wl_surface + * + * This request is used to describe the regions where the pending + * buffer is different from the current surface contents, and where + * the surface therefore needs to be repainted. The compositor + * ignores the parts of the damage that fall outside of the surface. + * + * Damage is double-buffered state, see wl_surface.commit. + * + * The damage rectangle is specified in surface-local coordinates, + * where x and y specify the upper left corner of the damage rectangle. + * + * The initial value for pending damage is empty: no damage. + * wl_surface.damage adds pending damage: the new pending damage + * is the union of old pending damage and the given rectangle. + * + * wl_surface.commit assigns pending damage as the current damage, + * and clears pending damage. The server will clear the current + * damage as it repaints the surface. + * + * Note! New clients should not use this request. Instead damage can be + * posted with wl_surface.damage_buffer which uses buffer coordinates + * instead of surface coordinates. + */ +static inline void +wl_surface_damage(struct wl_surface *wl_surface, int32_t x, int32_t y, int32_t width, int32_t height) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, + WL_SURFACE_DAMAGE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, x, y, width, height); +} + +/** + * @ingroup iface_wl_surface + * + * Request a notification when it is a good time to start drawing a new + * frame, by creating a frame callback. This is useful for throttling + * redrawing operations, and driving animations. + * + * When a client is animating on a wl_surface, it can use the 'frame' + * request to get notified when it is a good time to draw and commit the + * next frame of animation. If the client commits an update earlier than + * that, it is likely that some updates will not make it to the display, + * and the client is wasting resources by drawing too often. + * + * The frame request will take effect on the next wl_surface.commit. + * The notification will only be posted for one frame unless + * requested again. For a wl_surface, the notifications are posted in + * the order the frame requests were committed. + * + * The server must send the notifications so that a client + * will not send excessive updates, while still allowing + * the highest possible update rate for clients that wait for the reply + * before drawing again. The server should give some time for the client + * to draw and commit after sending the frame callback events to let it + * hit the next output refresh. + * + * A server should avoid signaling the frame callbacks if the + * surface is not visible in any way, e.g. the surface is off-screen, + * or completely obscured by other opaque surfaces. + * + * The object returned by this request will be destroyed by the + * compositor after the callback is fired and as such the client must not + * attempt to use it after that point. + * + * The callback_data passed in the callback is the current time, in + * milliseconds, with an undefined base. + */ +static inline struct wl_callback * +wl_surface_frame(struct wl_surface *wl_surface) +{ + struct wl_proxy *callback; + + callback = wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, + WL_SURFACE_FRAME, &wl_callback_interface, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, NULL); + + return (struct wl_callback *) callback; +} + +/** + * @ingroup iface_wl_surface + * + * This request sets the region of the surface that contains + * opaque content. + * + * The opaque region is an optimization hint for the compositor + * that lets it optimize the redrawing of content behind opaque + * regions. Setting an opaque region is not required for correct + * behaviour, but marking transparent content as opaque will result + * in repaint artifacts. + * + * The opaque region is specified in surface-local coordinates. + * + * The compositor ignores the parts of the opaque region that fall + * outside of the surface. + * + * Opaque region is double-buffered state, see wl_surface.commit. + * + * wl_surface.set_opaque_region changes the pending opaque region. + * wl_surface.commit copies the pending region to the current region. + * Otherwise, the pending and current regions are never changed. + * + * The initial value for an opaque region is empty. Setting the pending + * opaque region has copy semantics, and the wl_region object can be + * destroyed immediately. A NULL wl_region causes the pending opaque + * region to be set to empty. + */ +static inline void +wl_surface_set_opaque_region(struct wl_surface *wl_surface, struct wl_region *region) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, + WL_SURFACE_SET_OPAQUE_REGION, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, region); +} + +/** + * @ingroup iface_wl_surface + * + * This request sets the region of the surface that can receive + * pointer and touch events. + * + * Input events happening outside of this region will try the next + * surface in the server surface stack. The compositor ignores the + * parts of the input region that fall outside of the surface. + * + * The input region is specified in surface-local coordinates. + * + * Input region is double-buffered state, see wl_surface.commit. + * + * wl_surface.set_input_region changes the pending input region. + * wl_surface.commit copies the pending region to the current region. + * Otherwise the pending and current regions are never changed, + * except cursor and icon surfaces are special cases, see + * wl_pointer.set_cursor and wl_data_device.start_drag. + * + * The initial value for an input region is infinite. That means the + * whole surface will accept input. Setting the pending input region + * has copy semantics, and the wl_region object can be destroyed + * immediately. A NULL wl_region causes the input region to be set + * to infinite. + */ +static inline void +wl_surface_set_input_region(struct wl_surface *wl_surface, struct wl_region *region) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, + WL_SURFACE_SET_INPUT_REGION, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, region); +} + +/** + * @ingroup iface_wl_surface + * + * Surface state (input, opaque, and damage regions, attached buffers, + * etc.) is double-buffered. Protocol requests modify the pending state, + * as opposed to the active state in use by the compositor. + * + * All requests that need a commit to become effective are documented + * to affect double-buffered state. + * + * Other interfaces may add further double-buffered surface state. + * + * A commit request atomically creates a Content Update (CU) from the + * pending state, even if the pending state has not been touched. The + * content update is placed at the end of a per-surface queue until it + * becomes active. After commit, the new pending state is as documented for + * each related request. + * + * A CU is either a Desync Content Update (DCU) or a Sync Content Update + * (SCU). If the surface is effectively synchronized at the commit request, + * it is a SCU, otherwise a DCU. + * + * When a surface transitions from effectively synchronized to effectively + * desynchronized, all SCUs in its queue which are not reachable by any + * DCU become DCUs and dependency edges from outside the queue to these CUs + * are removed. + * + * See wl_subsurface for the definition of 'effectively synchronized' and + * 'effectively desynchronized'. + * + * When a CU is placed in the queue, the CU has a dependency on the CU in + * front of it and to the SCU at end of the queue of every direct child + * surface if that SCU exists and does not have another dependent. This can + * form a directed acyclic graph of CUs with dependencies as edges. + * + * In addition to surface state, the CU can have constraints that must be + * satisfied before it can be applied. Other interfaces may add CU + * constraints. + * + * All DCUs which do not have a SCU in front of themselves in their queue, + * are candidates. If the graph that's reachable by a candidate does not + * have any unsatisfied constraints, the entire graph must be applied + * atomically. + * + * When a CU is applied, the wl_buffer is applied before all other state. + * This means that all coordinates in double-buffered state are relative to + * the newly attached wl_buffers, except for wl_surface.attach itself. If + * there is no newly attached wl_buffer, the coordinates are relative to + * the previous content update. + */ +static inline void +wl_surface_commit(struct wl_surface *wl_surface) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, + WL_SURFACE_COMMIT, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0); +} + +/** + * @ingroup iface_wl_surface + * + * This request sets the transformation that the client has already applied + * to the content of the buffer. The accepted values for the transform + * parameter are the values for wl_output.transform. + * + * The compositor applies the inverse of this transformation whenever it + * uses the buffer contents. + * + * Buffer transform is double-buffered state, see wl_surface.commit. + * + * A newly created surface has its buffer transformation set to normal. + * + * wl_surface.set_buffer_transform changes the pending buffer + * transformation. wl_surface.commit copies the pending buffer + * transformation to the current one. Otherwise, the pending and current + * values are never changed. + * + * The purpose of this request is to allow clients to render content + * according to the output transform, thus permitting the compositor to + * use certain optimizations even if the display is rotated. Using + * hardware overlays and scanning out a client buffer for fullscreen + * surfaces are examples of such optimizations. Those optimizations are + * highly dependent on the compositor implementation, so the use of this + * request should be considered on a case-by-case basis. + * + * Note that if the transform value includes 90 or 270 degree rotation, + * the width of the buffer will become the surface height and the height + * of the buffer will become the surface width. + * + * If transform is not one of the values from the + * wl_output.transform enum the invalid_transform protocol error + * is raised. + */ +static inline void +wl_surface_set_buffer_transform(struct wl_surface *wl_surface, int32_t transform) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, + WL_SURFACE_SET_BUFFER_TRANSFORM, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, transform); +} + +/** + * @ingroup iface_wl_surface + * + * This request sets an optional scaling factor on how the compositor + * interprets the contents of the buffer attached to the window. + * + * Buffer scale is double-buffered state, see wl_surface.commit. + * + * A newly created surface has its buffer scale set to 1. + * + * wl_surface.set_buffer_scale changes the pending buffer scale. + * wl_surface.commit copies the pending buffer scale to the current one. + * Otherwise, the pending and current values are never changed. + * + * The purpose of this request is to allow clients to supply higher + * resolution buffer data for use on high resolution outputs. It is + * intended that you pick the same buffer scale as the scale of the + * output that the surface is displayed on. This means the compositor + * can avoid scaling when rendering the surface on that output. + * + * Note that if the scale is larger than 1, then you have to attach + * a buffer that is larger (by a factor of scale in each dimension) + * than the desired surface size. + * + * If scale is not greater than 0 the invalid_scale protocol error is + * raised. + */ +static inline void +wl_surface_set_buffer_scale(struct wl_surface *wl_surface, int32_t scale) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, + WL_SURFACE_SET_BUFFER_SCALE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, scale); +} + +/** + * @ingroup iface_wl_surface + * + * This request is used to describe the regions where the pending + * buffer is different from the current surface contents, and where + * the surface therefore needs to be repainted. The compositor + * ignores the parts of the damage that fall outside of the surface. + * + * Damage is double-buffered state, see wl_surface.commit. + * + * The damage rectangle is specified in buffer coordinates, + * where x and y specify the upper left corner of the damage rectangle. + * + * The initial value for pending damage is empty: no damage. + * wl_surface.damage_buffer adds pending damage: the new pending + * damage is the union of old pending damage and the given rectangle. + * + * wl_surface.commit assigns pending damage as the current damage, + * and clears pending damage. The server will clear the current + * damage as it repaints the surface. + * + * This request differs from wl_surface.damage in only one way - it + * takes damage in buffer coordinates instead of surface-local + * coordinates. While this generally is more intuitive than surface + * coordinates, it is especially desirable when using wp_viewport + * or when a drawing library (like EGL) is unaware of buffer scale + * and buffer transform. + * + * Note: Because buffer transformation changes and damage requests may + * be interleaved in the protocol stream, it is impossible to determine + * the actual mapping between surface and buffer damage until + * wl_surface.commit time. Therefore, compositors wishing to take both + * kinds of damage into account will have to accumulate damage from the + * two requests separately and only transform from one to the other + * after receiving the wl_surface.commit. + */ +static inline void +wl_surface_damage_buffer(struct wl_surface *wl_surface, int32_t x, int32_t y, int32_t width, int32_t height) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, + WL_SURFACE_DAMAGE_BUFFER, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, x, y, width, height); +} + +/** + * @ingroup iface_wl_surface + * + * The x and y arguments specify the location of the new pending + * buffer's upper left corner, relative to the current buffer's upper + * left corner, in surface-local coordinates. In other words, the + * x and y, combined with the new surface size define in which + * directions the surface's size changes. + * + * The exact semantics of wl_surface.offset are role-specific. Refer to + * the documentation of specific roles for more information. + * + * Surface location offset is double-buffered state, see + * wl_surface.commit. + * + * This request is semantically equivalent to and the replaces the x and y + * arguments in the wl_surface.attach request in wl_surface versions prior + * to 5. See wl_surface.attach for details. + */ +static inline void +wl_surface_offset(struct wl_surface *wl_surface, int32_t x, int32_t y) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, + WL_SURFACE_OFFSET, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, x, y); +} + +/** + * @ingroup iface_wl_surface + * + * Create a callback for the release of the buffer attached by the client + * with wl_surface.attach. + * + * The compositor will release the buffer when it has finished its usage of + * the underlying storage for the relevant commit. Once the client receives + * this event, and assuming the associated buffer is not pending release + * from other wl_surface.commit requests, the client can safely re-use the + * buffer. + * + * Release callbacks are double-buffered state, and will be associated + * with the pending buffer at wl_surface.commit time. + * + * The callback_data passed in the wl_callback.done event is unused and + * is always zero. + * + * Sending this request without attaching a non-null buffer in the same + * content update is a protocol error. The compositor will send the + * no_buffer error in this case. + */ +static inline struct wl_callback * +wl_surface_get_release(struct wl_surface *wl_surface) +{ + struct wl_proxy *callback; + + callback = wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, + WL_SURFACE_GET_RELEASE, &wl_callback_interface, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, NULL); + + return (struct wl_callback *) callback; +} + +#ifndef WL_SEAT_CAPABILITY_ENUM +#define WL_SEAT_CAPABILITY_ENUM +/** + * @ingroup iface_wl_seat + * seat capability bitmask + * + * This is a bitmask of capabilities this seat has; if a member is + * set, then it is present on the seat. + */ +enum wl_seat_capability { + /** + * the seat has pointer devices + */ + WL_SEAT_CAPABILITY_POINTER = 1, + /** + * the seat has one or more keyboards + */ + WL_SEAT_CAPABILITY_KEYBOARD = 2, + /** + * the seat has touch devices + */ + WL_SEAT_CAPABILITY_TOUCH = 4, +}; +#endif /* WL_SEAT_CAPABILITY_ENUM */ + +#ifndef WL_SEAT_ERROR_ENUM +#define WL_SEAT_ERROR_ENUM +/** + * @ingroup iface_wl_seat + * wl_seat error values + * + * These errors can be emitted in response to wl_seat requests. + */ +enum wl_seat_error { + /** + * get_pointer, get_keyboard or get_touch called on seat without the matching capability + */ + WL_SEAT_ERROR_MISSING_CAPABILITY = 0, +}; +#endif /* WL_SEAT_ERROR_ENUM */ + +/** + * @ingroup iface_wl_seat + * @struct wl_seat_listener + */ +struct wl_seat_listener { + /** + * seat capabilities changed + * + * This is sent on binding to the seat global or whenever a seat + * gains or loses the pointer, keyboard or touch capabilities. The + * argument is a capability enum containing the complete set of + * capabilities this seat has. + * + * When the pointer capability is added, a client may create a + * wl_pointer object using the wl_seat.get_pointer request. This + * object will receive pointer events until the capability is + * removed in the future. + * + * When the pointer capability is removed, a client should destroy + * the wl_pointer objects associated with the seat where the + * capability was removed, using the wl_pointer.release request. No + * further pointer events will be received on these objects. + * + * In some compositors, if a seat regains the pointer capability + * and a client has a previously obtained wl_pointer object of + * version 4 or less, that object may start sending pointer events + * again. This behavior is considered a misinterpretation of the + * intended behavior and must not be relied upon by the client. + * wl_pointer objects of version 5 or later must not send events if + * created before the most recent event notifying the client of an + * added pointer capability. + * + * The above behavior also applies to wl_keyboard and wl_touch with + * the keyboard and touch capabilities, respectively. + * @param capabilities capabilities of the seat + */ + void (*capabilities)(void *data, + struct wl_seat *wl_seat, + uint32_t capabilities); + /** + * unique identifier for this seat + * + * In a multi-seat configuration the seat name can be used by + * clients to help identify which physical devices the seat + * represents. + * + * The seat name is a UTF-8 string with no convention defined for + * its contents. Each name is unique among all wl_seat globals. The + * name is only guaranteed to be unique for the current compositor + * instance. + * + * The same seat names are used for all clients. Thus, the name can + * be shared across processes to refer to a specific wl_seat + * global. + * + * The name event is sent after binding to the seat global, and + * should be sent before announcing capabilities. This event is + * only sent once per seat object, and the name does not change + * over the lifetime of the wl_seat global. + * + * Compositors may re-use the same seat name if the wl_seat global + * is destroyed and re-created later. + * @param name seat identifier + * @since 2 + */ + void (*name)(void *data, + struct wl_seat *wl_seat, + const char *name); +}; + +/** + * @ingroup iface_wl_seat + */ +static inline int +wl_seat_add_listener(struct wl_seat *wl_seat, + const struct wl_seat_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) wl_seat, + (void (**)(void)) listener, data); +} + +#define WL_SEAT_GET_POINTER 0 +#define WL_SEAT_GET_KEYBOARD 1 +#define WL_SEAT_GET_TOUCH 2 +#define WL_SEAT_RELEASE 3 + +/** + * @ingroup iface_wl_seat + */ +#define WL_SEAT_CAPABILITIES_SINCE_VERSION 1 +/** + * @ingroup iface_wl_seat + */ +#define WL_SEAT_NAME_SINCE_VERSION 2 + +/** + * @ingroup iface_wl_seat + */ +#define WL_SEAT_GET_POINTER_SINCE_VERSION 1 +/** + * @ingroup iface_wl_seat + */ +#define WL_SEAT_GET_KEYBOARD_SINCE_VERSION 1 +/** + * @ingroup iface_wl_seat + */ +#define WL_SEAT_GET_TOUCH_SINCE_VERSION 1 +/** + * @ingroup iface_wl_seat + */ +#define WL_SEAT_RELEASE_SINCE_VERSION 5 + +/** @ingroup iface_wl_seat */ +static inline void +wl_seat_set_user_data(struct wl_seat *wl_seat, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) wl_seat, user_data); +} + +/** @ingroup iface_wl_seat */ +static inline void * +wl_seat_get_user_data(struct wl_seat *wl_seat) +{ + return wl_proxy_get_user_data((struct wl_proxy *) wl_seat); +} + +static inline uint32_t +wl_seat_get_version(struct wl_seat *wl_seat) +{ + return wl_proxy_get_version((struct wl_proxy *) wl_seat); +} + +/** @ingroup iface_wl_seat */ +static inline void +wl_seat_destroy(struct wl_seat *wl_seat) +{ + wl_proxy_destroy((struct wl_proxy *) wl_seat); +} + +/** + * @ingroup iface_wl_seat + * + * The ID provided will be initialized to the wl_pointer interface + * for this seat. + * + * This request only takes effect if the seat has the pointer + * capability, or has had the pointer capability in the past. + * It is a protocol violation to issue this request on a seat that has + * never had the pointer capability. The missing_capability error will + * be sent in this case. + */ +static inline struct wl_pointer * +wl_seat_get_pointer(struct wl_seat *wl_seat) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) wl_seat, + WL_SEAT_GET_POINTER, &wl_pointer_interface, wl_proxy_get_version((struct wl_proxy *) wl_seat), 0, NULL); + + return (struct wl_pointer *) id; +} + +/** + * @ingroup iface_wl_seat + * + * The ID provided will be initialized to the wl_keyboard interface + * for this seat. + * + * This request only takes effect if the seat has the keyboard + * capability, or has had the keyboard capability in the past. + * It is a protocol violation to issue this request on a seat that has + * never had the keyboard capability. The missing_capability error will + * be sent in this case. + */ +static inline struct wl_keyboard * +wl_seat_get_keyboard(struct wl_seat *wl_seat) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) wl_seat, + WL_SEAT_GET_KEYBOARD, &wl_keyboard_interface, wl_proxy_get_version((struct wl_proxy *) wl_seat), 0, NULL); + + return (struct wl_keyboard *) id; +} + +/** + * @ingroup iface_wl_seat + * + * The ID provided will be initialized to the wl_touch interface + * for this seat. + * + * This request only takes effect if the seat has the touch + * capability, or has had the touch capability in the past. + * It is a protocol violation to issue this request on a seat that has + * never had the touch capability. The missing_capability error will + * be sent in this case. + */ +static inline struct wl_touch * +wl_seat_get_touch(struct wl_seat *wl_seat) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) wl_seat, + WL_SEAT_GET_TOUCH, &wl_touch_interface, wl_proxy_get_version((struct wl_proxy *) wl_seat), 0, NULL); + + return (struct wl_touch *) id; +} + +/** + * @ingroup iface_wl_seat + * + * Using this request a client can tell the server that it is not going to + * use the seat object anymore. + */ +static inline void +wl_seat_release(struct wl_seat *wl_seat) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_seat, + WL_SEAT_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_seat), WL_MARSHAL_FLAG_DESTROY); +} + +#ifndef WL_POINTER_ERROR_ENUM +#define WL_POINTER_ERROR_ENUM +enum wl_pointer_error { + /** + * given wl_surface has another role + */ + WL_POINTER_ERROR_ROLE = 0, +}; +#endif /* WL_POINTER_ERROR_ENUM */ + +#ifndef WL_POINTER_BUTTON_STATE_ENUM +#define WL_POINTER_BUTTON_STATE_ENUM +/** + * @ingroup iface_wl_pointer + * physical button state + * + * Describes the physical state of a button that produced the button + * event. + */ +enum wl_pointer_button_state { + /** + * the button is not pressed + */ + WL_POINTER_BUTTON_STATE_RELEASED = 0, + /** + * the button is pressed + */ + WL_POINTER_BUTTON_STATE_PRESSED = 1, +}; +#endif /* WL_POINTER_BUTTON_STATE_ENUM */ + +#ifndef WL_POINTER_AXIS_ENUM +#define WL_POINTER_AXIS_ENUM +/** + * @ingroup iface_wl_pointer + * axis types + * + * Describes the axis types of scroll events. + */ +enum wl_pointer_axis { + /** + * vertical axis + */ + WL_POINTER_AXIS_VERTICAL_SCROLL = 0, + /** + * horizontal axis + */ + WL_POINTER_AXIS_HORIZONTAL_SCROLL = 1, +}; +#endif /* WL_POINTER_AXIS_ENUM */ + +#ifndef WL_POINTER_AXIS_SOURCE_ENUM +#define WL_POINTER_AXIS_SOURCE_ENUM +/** + * @ingroup iface_wl_pointer + * axis source types + * + * Describes the source types for axis events. This indicates to the + * client how an axis event was physically generated; a client may + * adjust the user interface accordingly. For example, scroll events + * from a "finger" source may be in a smooth coordinate space with + * kinetic scrolling whereas a "wheel" source may be in discrete steps + * of a number of lines. + * + * The "continuous" axis source is a device generating events in a + * continuous coordinate space, but using something other than a + * finger. One example for this source is button-based scrolling where + * the vertical motion of a device is converted to scroll events while + * a button is held down. + * + * The "wheel tilt" axis source indicates that the actual device is a + * wheel but the scroll event is not caused by a rotation but a + * (usually sideways) tilt of the wheel. + */ +enum wl_pointer_axis_source { + /** + * a physical wheel rotation + */ + WL_POINTER_AXIS_SOURCE_WHEEL = 0, + /** + * finger on a touch surface + */ + WL_POINTER_AXIS_SOURCE_FINGER = 1, + /** + * continuous coordinate space + */ + WL_POINTER_AXIS_SOURCE_CONTINUOUS = 2, + /** + * a physical wheel tilt + * @since 6 + */ + WL_POINTER_AXIS_SOURCE_WHEEL_TILT = 3, +}; +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_AXIS_SOURCE_WHEEL_TILT_SINCE_VERSION 6 +#endif /* WL_POINTER_AXIS_SOURCE_ENUM */ + +#ifndef WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM +#define WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM +/** + * @ingroup iface_wl_pointer + * axis relative direction + * + * This specifies the direction of the physical motion that caused a + * wl_pointer.axis event, relative to the wl_pointer.axis direction. + */ +enum wl_pointer_axis_relative_direction { + /** + * physical motion matches axis direction + */ + WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL = 0, + /** + * physical motion is the inverse of the axis direction + */ + WL_POINTER_AXIS_RELATIVE_DIRECTION_INVERTED = 1, +}; +#endif /* WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM */ + +/** + * @ingroup iface_wl_pointer + * @struct wl_pointer_listener + */ +struct wl_pointer_listener { + /** + * enter event + * + * Notification that this seat's pointer is focused on a certain + * surface. + * + * When a seat's focus enters a surface, the pointer image is + * undefined and a client should respond to this event by setting + * an appropriate pointer image with the set_cursor request. + * @param serial serial number of the enter event + * @param surface surface entered by the pointer + * @param surface_x surface-local x coordinate + * @param surface_y surface-local y coordinate + */ + void (*enter)(void *data, + struct wl_pointer *wl_pointer, + uint32_t serial, + struct wl_surface *surface, + wl_fixed_t surface_x, + wl_fixed_t surface_y); + /** + * leave event + * + * Notification that this seat's pointer is no longer focused on + * a certain surface. + * + * The leave notification is sent before the enter notification for + * the new focus. + * @param serial serial number of the leave event + * @param surface surface left by the pointer + */ + void (*leave)(void *data, + struct wl_pointer *wl_pointer, + uint32_t serial, + struct wl_surface *surface); + /** + * pointer motion event + * + * Notification of pointer location change. The arguments + * surface_x and surface_y are the location relative to the focused + * surface. + * @param time timestamp with millisecond granularity + * @param surface_x surface-local x coordinate + * @param surface_y surface-local y coordinate + */ + void (*motion)(void *data, + struct wl_pointer *wl_pointer, + uint32_t time, + wl_fixed_t surface_x, + wl_fixed_t surface_y); + /** + * pointer button event + * + * Mouse button click and release notifications. + * + * The location of the click is given by the last motion, warp or + * enter event. The time argument is a timestamp with millisecond + * granularity, with an undefined base. + * + * The button is a button code as defined in the Linux kernel's + * linux/input-event-codes.h header file, e.g. BTN_LEFT. + * + * Any 16-bit button code value is reserved for future additions to + * the kernel's event code list. All other button codes above + * 0xFFFF are currently undefined but may be used in future + * versions of this protocol. + * @param serial serial number of the button event + * @param time timestamp with millisecond granularity + * @param button button that produced the event + * @param state physical state of the button + */ + void (*button)(void *data, + struct wl_pointer *wl_pointer, + uint32_t serial, + uint32_t time, + uint32_t button, + uint32_t state); + /** + * axis event + * + * Scroll and other axis notifications. + * + * For scroll events (vertical and horizontal scroll axes), the + * value parameter is the length of a vector along the specified + * axis in a coordinate space identical to those of motion events, + * representing a relative movement along the specified axis. + * + * For devices that support movements non-parallel to axes multiple + * axis events will be emitted. + * + * When applicable, for example for touch pads, the server can + * choose to emit scroll events where the motion vector is + * equivalent to a motion event vector. + * + * When applicable, a client can transform its content relative to + * the scroll distance. + * @param time timestamp with millisecond granularity + * @param axis axis type + * @param value length of vector in surface-local coordinate space + */ + void (*axis)(void *data, + struct wl_pointer *wl_pointer, + uint32_t time, + uint32_t axis, + wl_fixed_t value); + /** + * end of a pointer event sequence + * + * Indicates the end of a set of events that logically belong + * together. A client is expected to accumulate the data in all + * events within the frame before proceeding. + * + * All wl_pointer events before a wl_pointer.frame event belong + * logically together. For example, in a diagonal scroll motion the + * compositor will send an optional wl_pointer.axis_source event, + * two wl_pointer.axis events (horizontal and vertical) and finally + * a wl_pointer.frame event. The client may use this information to + * calculate a diagonal vector for scrolling. + * + * When multiple wl_pointer.axis events occur within the same + * frame, the motion vector is the combined motion of all events. + * When a wl_pointer.axis and a wl_pointer.axis_stop event occur + * within the same frame, this indicates that axis movement in one + * axis has stopped but continues in the other axis. When multiple + * wl_pointer.axis_stop events occur within the same frame, this + * indicates that these axes stopped in the same instance. + * + * A wl_pointer.frame event is sent for every logical event group, + * even if the group only contains a single wl_pointer event. + * Specifically, a client may get a sequence: motion, frame, + * button, frame, axis, frame, axis_stop, frame. + * + * The wl_pointer.enter and wl_pointer.leave events are logical + * events generated by the compositor and not the hardware. These + * events are also grouped by a wl_pointer.frame. When a pointer + * moves from one surface to another, a compositor should group the + * wl_pointer.leave event within the same wl_pointer.frame. + * However, a client must not rely on wl_pointer.leave and + * wl_pointer.enter being in the same wl_pointer.frame. + * Compositor-specific policies may require the wl_pointer.leave + * and wl_pointer.enter event being split across multiple + * wl_pointer.frame groups. + * @since 5 + */ + void (*frame)(void *data, + struct wl_pointer *wl_pointer); + /** + * axis source event + * + * Source information for scroll and other axes. + * + * This event does not occur on its own. It is sent before a + * wl_pointer.frame event and carries the source information for + * all events within that frame. + * + * The source specifies how this event was generated. If the source + * is wl_pointer.axis_source.finger, a wl_pointer.axis_stop event + * will be sent when the user lifts the finger off the device. + * + * If the source is wl_pointer.axis_source.wheel, + * wl_pointer.axis_source.wheel_tilt or + * wl_pointer.axis_source.continuous, a wl_pointer.axis_stop event + * may or may not be sent. Whether a compositor sends an axis_stop + * event for these sources is hardware-specific and + * implementation-dependent; clients must not rely on receiving an + * axis_stop event for these scroll sources and should treat scroll + * sequences from these scroll sources as unterminated by default. + * + * This event is optional. If the source is unknown for a + * particular axis event sequence, no event is sent. Only one + * wl_pointer.axis_source event is permitted per frame. + * + * The order of wl_pointer.axis_discrete and wl_pointer.axis_source + * is not guaranteed. + * @param axis_source source of the axis event + * @since 5 + */ + void (*axis_source)(void *data, + struct wl_pointer *wl_pointer, + uint32_t axis_source); + /** + * axis stop event + * + * Stop notification for scroll and other axes. + * + * For some wl_pointer.axis_source types, a wl_pointer.axis_stop + * event is sent to notify a client that the axis sequence has + * terminated. This enables the client to implement kinetic + * scrolling. See the wl_pointer.axis_source documentation for + * information on when this event may be generated. + * + * Any wl_pointer.axis events with the same axis_source after this + * event should be considered as the start of a new axis motion. + * + * The timestamp is to be interpreted identical to the timestamp in + * the wl_pointer.axis event. The timestamp value may be the same + * as a preceding wl_pointer.axis event. + * @param time timestamp with millisecond granularity + * @param axis the axis stopped with this event + * @since 5 + */ + void (*axis_stop)(void *data, + struct wl_pointer *wl_pointer, + uint32_t time, + uint32_t axis); + /** + * axis click event + * + * Discrete step information for scroll and other axes. + * + * This event carries the axis value of the wl_pointer.axis event + * in discrete steps (e.g. mouse wheel clicks). + * + * This event is deprecated with wl_pointer version 8 - this event + * is not sent to clients supporting version 8 or later. + * + * This event does not occur on its own, it is coupled with a + * wl_pointer.axis event that represents this axis value on a + * continuous scale. The protocol guarantees that each + * axis_discrete event is always followed by exactly one axis event + * with the same axis number within the same wl_pointer.frame. Note + * that the protocol allows for other events to occur between the + * axis_discrete and its coupled axis event, including other + * axis_discrete or axis events. A wl_pointer.frame must not + * contain more than one axis_discrete event per axis type. + * + * This event is optional; continuous scrolling devices like + * two-finger scrolling on touchpads do not have discrete steps and + * do not generate this event. + * + * The discrete value carries the directional information. e.g. a + * value of -2 is two steps towards the negative direction of this + * axis. + * + * The axis number is identical to the axis number in the + * associated axis event. + * + * The order of wl_pointer.axis_discrete and wl_pointer.axis_source + * is not guaranteed. + * @param axis axis type + * @param discrete number of steps + * @since 5 + * @deprecated Deprecated since version 8 + */ + void (*axis_discrete)(void *data, + struct wl_pointer *wl_pointer, + uint32_t axis, + int32_t discrete); + /** + * axis high-resolution scroll event + * + * Discrete high-resolution scroll information. + * + * This event carries high-resolution wheel scroll information, + * with each multiple of 120 representing one logical scroll step + * (a wheel detent). For example, an axis_value120 of 30 is one + * quarter of a logical scroll step in the positive direction, a + * value120 of -240 are two logical scroll steps in the negative + * direction within the same hardware event. Clients that rely on + * discrete scrolling should accumulate the value120 to multiples + * of 120 before processing the event. + * + * The value120 must not be zero. + * + * This event replaces the wl_pointer.axis_discrete event in + * clients supporting wl_pointer version 8 or later. + * + * Where a wl_pointer.axis_source event occurs in the same + * wl_pointer.frame, the axis source applies to this event. + * + * The order of wl_pointer.axis_value120 and wl_pointer.axis_source + * is not guaranteed. + * @param axis axis type + * @param value120 scroll distance as fraction of 120 + * @since 8 + */ + void (*axis_value120)(void *data, + struct wl_pointer *wl_pointer, + uint32_t axis, + int32_t value120); + /** + * axis relative physical direction event + * + * Relative directional information of the entity causing the + * axis motion. + * + * For a wl_pointer.axis event, the + * wl_pointer.axis_relative_direction event specifies the movement + * direction of the entity causing the wl_pointer.axis event. For + * example: - if a user's fingers on a touchpad move down and this + * causes a wl_pointer.axis vertical_scroll down event, the + * physical direction is 'identical' - if a user's fingers on a + * touchpad move down and this causes a wl_pointer.axis + * vertical_scroll up scroll up event ('natural scrolling'), the + * physical direction is 'inverted'. + * + * A client may use this information to adjust scroll motion of + * components. Specifically, enabling natural scrolling causes the + * content to change direction compared to traditional scrolling. + * Some widgets like volume control sliders should usually match + * the physical direction regardless of whether natural scrolling + * is active. This event enables clients to match the scroll + * direction of a widget to the physical direction. + * + * This event does not occur on its own, it is coupled with a + * wl_pointer.axis event that represents this axis value. The + * protocol guarantees that each axis_relative_direction event is + * always followed by exactly one axis event with the same axis + * number within the same wl_pointer.frame. Note that the protocol + * allows for other events to occur between the + * axis_relative_direction and its coupled axis event. + * + * The axis number is identical to the axis number in the + * associated axis event. + * + * The order of wl_pointer.axis_relative_direction, + * wl_pointer.axis_discrete and wl_pointer.axis_source is not + * guaranteed. + * @param axis axis type + * @param direction physical direction relative to axis motion + * @since 9 + */ + void (*axis_relative_direction)(void *data, + struct wl_pointer *wl_pointer, + uint32_t axis, + uint32_t direction); + /** + * pointer warp event + * + * Notification of pointer location change within a surface. + * + * This location change is not due to events on the input device, + * but because either the surface under the pointer was moved and + * thus the relative position of the pointer changed, or because + * the compositor changed the pointer position in response to an + * event like pointer confinement being exited. + * + * The arguments surface_x and surface_y are the location relative + * to the focused surface. + * + * This event must not occur in the same wl_pointer.frame as a + * wl_pointer.enter or wl_pointer.motion event. + * @param surface_x surface-local x coordinate + * @param surface_y surface-local y coordinate + * @since 11 + */ + void (*warp)(void *data, + struct wl_pointer *wl_pointer, + wl_fixed_t surface_x, + wl_fixed_t surface_y); +}; + +/** + * @ingroup iface_wl_pointer + */ +static inline int +wl_pointer_add_listener(struct wl_pointer *wl_pointer, + const struct wl_pointer_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) wl_pointer, + (void (**)(void)) listener, data); +} + +#define WL_POINTER_SET_CURSOR 0 +#define WL_POINTER_RELEASE 1 + +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_ENTER_SINCE_VERSION 1 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_LEAVE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_MOTION_SINCE_VERSION 1 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_BUTTON_SINCE_VERSION 1 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_AXIS_SINCE_VERSION 1 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_FRAME_SINCE_VERSION 5 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_AXIS_SOURCE_SINCE_VERSION 5 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_AXIS_STOP_SINCE_VERSION 5 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_AXIS_DISCRETE_SINCE_VERSION 5 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_AXIS_VALUE120_SINCE_VERSION 8 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_AXIS_RELATIVE_DIRECTION_SINCE_VERSION 9 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_WARP_SINCE_VERSION 11 + +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_SET_CURSOR_SINCE_VERSION 1 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_RELEASE_SINCE_VERSION 3 + +/** @ingroup iface_wl_pointer */ +static inline void +wl_pointer_set_user_data(struct wl_pointer *wl_pointer, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) wl_pointer, user_data); +} + +/** @ingroup iface_wl_pointer */ +static inline void * +wl_pointer_get_user_data(struct wl_pointer *wl_pointer) +{ + return wl_proxy_get_user_data((struct wl_proxy *) wl_pointer); +} + +static inline uint32_t +wl_pointer_get_version(struct wl_pointer *wl_pointer) +{ + return wl_proxy_get_version((struct wl_proxy *) wl_pointer); +} + +/** @ingroup iface_wl_pointer */ +static inline void +wl_pointer_destroy(struct wl_pointer *wl_pointer) +{ + wl_proxy_destroy((struct wl_proxy *) wl_pointer); +} + +/** + * @ingroup iface_wl_pointer + * + * Set the pointer surface, i.e., the surface that contains the + * pointer image (cursor). This request gives the surface the role + * of a cursor. If the surface already has another role, it raises + * a protocol error. + * + * The cursor actually changes only if the pointer + * focus for this device is one of the requesting client's surfaces + * or the surface parameter is the current pointer surface. If + * there was a previous surface set with this request it is + * replaced. If surface is NULL, the pointer image is hidden. + * + * The parameters hotspot_x and hotspot_y define the position of + * the pointer surface relative to the pointer location. Its + * top-left corner is always at (x, y) - (hotspot_x, hotspot_y), + * where (x, y) are the coordinates of the pointer location, in + * surface-local coordinates. + * + * On wl_surface.offset requests to the pointer surface, hotspot_x + * and hotspot_y are decremented by the x and y parameters + * passed to the request. The offset must be applied by + * wl_surface.commit as usual. + * + * The hotspot can also be updated by passing the currently set + * pointer surface to this request with new values for hotspot_x + * and hotspot_y. + * + * The input region is ignored for wl_surfaces with the role of + * a cursor. When the use as a cursor ends, the wl_surface is + * unmapped. + * + * The serial parameter must match the latest wl_pointer.enter + * serial number sent to the client. Otherwise the request will be + * ignored. + */ +static inline void +wl_pointer_set_cursor(struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, int32_t hotspot_x, int32_t hotspot_y) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_pointer, + WL_POINTER_SET_CURSOR, NULL, wl_proxy_get_version((struct wl_proxy *) wl_pointer), 0, serial, surface, hotspot_x, hotspot_y); +} + +/** + * @ingroup iface_wl_pointer + * + * Using this request a client can tell the server that it is not going to + * use the pointer object anymore. + * + * This request destroys the pointer proxy object, so clients must not call + * wl_pointer_destroy() after using this request. + */ +static inline void +wl_pointer_release(struct wl_pointer *wl_pointer) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_pointer, + WL_POINTER_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_pointer), WL_MARSHAL_FLAG_DESTROY); +} + +#ifndef WL_KEYBOARD_KEYMAP_FORMAT_ENUM +#define WL_KEYBOARD_KEYMAP_FORMAT_ENUM +/** + * @ingroup iface_wl_keyboard + * keyboard mapping format + * + * This specifies the format of the keymap provided to the + * client with the wl_keyboard.keymap event. + */ +enum wl_keyboard_keymap_format { + /** + * no keymap; client must understand how to interpret the raw keycode + */ + WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP = 0, + /** + * libxkbcommon compatible, null-terminated string; to determine the xkb keycode, clients must add 8 to the key event keycode + */ + WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 = 1, +}; +#endif /* WL_KEYBOARD_KEYMAP_FORMAT_ENUM */ + +#ifndef WL_KEYBOARD_KEY_STATE_ENUM +#define WL_KEYBOARD_KEY_STATE_ENUM +/** + * @ingroup iface_wl_keyboard + * physical key state + * + * Describes the physical state of a key that produced the key event. + * + * Since version 10, the key can be in a "repeated" pseudo-state which + * means the same as "pressed", but is used to signal repetition in the + * key event. + * + * The key may only enter the repeated state after entering the pressed + * state and before entering the released state. This event may be + * generated multiple times while the key is down. + */ +enum wl_keyboard_key_state { + /** + * key is not pressed + */ + WL_KEYBOARD_KEY_STATE_RELEASED = 0, + /** + * key is pressed + */ + WL_KEYBOARD_KEY_STATE_PRESSED = 1, + /** + * key was repeated + * @since 10 + */ + WL_KEYBOARD_KEY_STATE_REPEATED = 2, +}; +/** + * @ingroup iface_wl_keyboard + */ +#define WL_KEYBOARD_KEY_STATE_REPEATED_SINCE_VERSION 10 +#endif /* WL_KEYBOARD_KEY_STATE_ENUM */ + +/** + * @ingroup iface_wl_keyboard + * @struct wl_keyboard_listener + */ +struct wl_keyboard_listener { + /** + * keyboard mapping + * + * This event provides a file descriptor to the client which can + * be memory-mapped in read-only mode to provide a keyboard mapping + * description. + * + * From version 7 onwards, the fd must be mapped with MAP_PRIVATE + * by the recipient, as MAP_SHARED may fail. + * @param format keymap format + * @param fd keymap file descriptor + * @param size keymap size, in bytes + */ + void (*keymap)(void *data, + struct wl_keyboard *wl_keyboard, + uint32_t format, + int32_t fd, + uint32_t size); + /** + * enter event + * + * Notification that this seat's keyboard focus is on a certain + * surface. + * + * The compositor must send the wl_keyboard.modifiers event after + * this event. + * + * In the wl_keyboard logical state, this event sets the active + * surface to the surface argument and the keys currently logically + * down to the keys in the keys argument. The compositor must not + * send this event if the wl_keyboard already had an active surface + * immediately before this event. + * + * Clients should not use the list of pressed keys to emulate + * key-press events. The order of keys in the list is unspecified. + * @param serial serial number of the enter event + * @param surface surface gaining keyboard focus + * @param keys the keys currently logically down + */ + void (*enter)(void *data, + struct wl_keyboard *wl_keyboard, + uint32_t serial, + struct wl_surface *surface, + struct wl_array *keys); + /** + * leave event + * + * Notification that this seat's keyboard focus is no longer on a + * certain surface. + * + * The leave notification is sent before the enter notification for + * the new focus. + * + * In the wl_keyboard logical state, this event resets all values + * to their defaults. The compositor must not send this event if + * the active surface of the wl_keyboard was not equal to the + * surface argument immediately before this event. + * @param serial serial number of the leave event + * @param surface surface that lost keyboard focus + */ + void (*leave)(void *data, + struct wl_keyboard *wl_keyboard, + uint32_t serial, + struct wl_surface *surface); + /** + * key event + * + * A key was pressed or released. The time argument is a + * timestamp with millisecond granularity, with an undefined base. + * + * The key is a platform-specific key code that can be interpreted + * by feeding it to the keyboard mapping (see the keymap event). + * + * If this event produces a change in modifiers, then the resulting + * wl_keyboard.modifiers event must be sent after this event. + * + * In the wl_keyboard logical state, this event adds the key to the + * keys currently logically down (if the state argument is pressed) + * or removes the key from the keys currently logically down (if + * the state argument is released). The compositor must not send + * this event if the wl_keyboard did not have an active surface + * immediately before this event. The compositor must not send this + * event if state is pressed (resp. released) and the key was + * already logically down (resp. was not logically down) + * immediately before this event. + * + * Since version 10, compositors may send key events with the + * "repeated" key state when a wl_keyboard.repeat_info event with a + * rate argument of 0 has been received. This allows the compositor + * to take over the responsibility of key repetition. + * @param serial serial number of the key event + * @param time timestamp with millisecond granularity + * @param key key that produced the event + * @param state physical state of the key + */ + void (*key)(void *data, + struct wl_keyboard *wl_keyboard, + uint32_t serial, + uint32_t time, + uint32_t key, + uint32_t state); + /** + * modifier and group state + * + * Notifies clients that the modifier and/or group state has + * changed, and it should update its local state. + * + * The compositor may send this event without a surface of the + * client having keyboard focus, for example to tie modifier + * information to pointer focus instead. If a modifier event with + * pressed modifiers is sent without a prior enter event, the + * client can assume the modifier state is valid until it receives + * the next wl_keyboard.modifiers event. In order to reset the + * modifier state again, the compositor can send a + * wl_keyboard.modifiers event with no pressed modifiers. + * + * In the wl_keyboard logical state, this event updates the + * modifiers and group. + * @param serial serial number of the modifiers event + * @param mods_depressed depressed modifiers + * @param mods_latched latched modifiers + * @param mods_locked locked modifiers + * @param group keyboard layout + */ + void (*modifiers)(void *data, + struct wl_keyboard *wl_keyboard, + uint32_t serial, + uint32_t mods_depressed, + uint32_t mods_latched, + uint32_t mods_locked, + uint32_t group); + /** + * repeat rate and delay + * + * Informs the client about the keyboard's repeat rate and delay. + * + * This event is sent as soon as the wl_keyboard object has been + * created, and is guaranteed to be received by the client before + * any key press event. + * + * Negative values for either rate or delay are illegal. A rate of + * zero will disable any repeating (regardless of the value of + * delay). + * + * This event can be sent later on as well with a new value if + * necessary, so clients should continue listening for the event + * past the creation of wl_keyboard. + * @param rate the rate of repeating keys in characters per second + * @param delay delay in milliseconds since key down until repeating starts + * @since 4 + */ + void (*repeat_info)(void *data, + struct wl_keyboard *wl_keyboard, + int32_t rate, + int32_t delay); +}; + +/** + * @ingroup iface_wl_keyboard + */ +static inline int +wl_keyboard_add_listener(struct wl_keyboard *wl_keyboard, + const struct wl_keyboard_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) wl_keyboard, + (void (**)(void)) listener, data); +} + +#define WL_KEYBOARD_RELEASE 0 + +/** + * @ingroup iface_wl_keyboard + */ +#define WL_KEYBOARD_KEYMAP_SINCE_VERSION 1 +/** + * @ingroup iface_wl_keyboard + */ +#define WL_KEYBOARD_ENTER_SINCE_VERSION 1 +/** + * @ingroup iface_wl_keyboard + */ +#define WL_KEYBOARD_LEAVE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_keyboard + */ +#define WL_KEYBOARD_KEY_SINCE_VERSION 1 +/** + * @ingroup iface_wl_keyboard + */ +#define WL_KEYBOARD_MODIFIERS_SINCE_VERSION 1 +/** + * @ingroup iface_wl_keyboard + */ +#define WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION 4 + +/** + * @ingroup iface_wl_keyboard + */ +#define WL_KEYBOARD_RELEASE_SINCE_VERSION 3 + +/** @ingroup iface_wl_keyboard */ +static inline void +wl_keyboard_set_user_data(struct wl_keyboard *wl_keyboard, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) wl_keyboard, user_data); +} + +/** @ingroup iface_wl_keyboard */ +static inline void * +wl_keyboard_get_user_data(struct wl_keyboard *wl_keyboard) +{ + return wl_proxy_get_user_data((struct wl_proxy *) wl_keyboard); +} + +static inline uint32_t +wl_keyboard_get_version(struct wl_keyboard *wl_keyboard) +{ + return wl_proxy_get_version((struct wl_proxy *) wl_keyboard); +} + +/** @ingroup iface_wl_keyboard */ +static inline void +wl_keyboard_destroy(struct wl_keyboard *wl_keyboard) +{ + wl_proxy_destroy((struct wl_proxy *) wl_keyboard); +} + +/** + * @ingroup iface_wl_keyboard + */ +static inline void +wl_keyboard_release(struct wl_keyboard *wl_keyboard) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_keyboard, + WL_KEYBOARD_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_keyboard), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_wl_touch + * @struct wl_touch_listener + */ +struct wl_touch_listener { + /** + * touch down event and beginning of a touch sequence + * + * A new touch point has appeared on the surface. This touch + * point is assigned a unique ID. Future events from this touch + * point reference this ID. The ID ceases to be valid after a touch + * up event and may be reused in the future. + * @param serial serial number of the touch down event + * @param time timestamp with millisecond granularity + * @param surface surface touched + * @param id the unique ID of this touch point + * @param x surface-local x coordinate + * @param y surface-local y coordinate + */ + void (*down)(void *data, + struct wl_touch *wl_touch, + uint32_t serial, + uint32_t time, + struct wl_surface *surface, + int32_t id, + wl_fixed_t x, + wl_fixed_t y); + /** + * end of a touch event sequence + * + * The touch point has disappeared. No further events will be + * sent for this touch point and the touch point's ID is released + * and may be reused in a future touch down event. + * @param serial serial number of the touch up event + * @param time timestamp with millisecond granularity + * @param id the unique ID of this touch point + */ + void (*up)(void *data, + struct wl_touch *wl_touch, + uint32_t serial, + uint32_t time, + int32_t id); + /** + * update of touch point coordinates + * + * A touch point has changed coordinates. + * @param time timestamp with millisecond granularity + * @param id the unique ID of this touch point + * @param x surface-local x coordinate + * @param y surface-local y coordinate + */ + void (*motion)(void *data, + struct wl_touch *wl_touch, + uint32_t time, + int32_t id, + wl_fixed_t x, + wl_fixed_t y); + /** + * end of touch frame event + * + * Indicates the end of a set of events that logically belong + * together. A client is expected to accumulate the data in all + * events within the frame before proceeding. + * + * A wl_touch.frame terminates at least one event but otherwise no + * guarantee is provided about the set of events within a frame. A + * client must assume that any state not updated in a frame is + * unchanged from the previously known state. + */ + void (*frame)(void *data, + struct wl_touch *wl_touch); + /** + * touch session cancelled + * + * Sent if the compositor decides the touch stream is a global + * gesture. No further events are sent to the clients from that + * particular gesture. Touch cancellation applies to all touch + * points currently active on this client's surface. The client is + * responsible for finalizing the touch points, future touch points + * on this surface may reuse the touch point ID. + * + * No frame event is required after the cancel event. + */ + void (*cancel)(void *data, + struct wl_touch *wl_touch); + /** + * update shape of touch point + * + * Sent when a touchpoint has changed its shape. + * + * This event does not occur on its own. It is sent before a + * wl_touch.frame event and carries the new shape information for + * any previously reported, or new touch points of that frame. + * + * Other events describing the touch point such as wl_touch.down, + * wl_touch.motion or wl_touch.orientation may be sent within the + * same wl_touch.frame. A client should treat these events as a + * single logical touch point update. The order of wl_touch.shape, + * wl_touch.orientation and wl_touch.motion is not guaranteed. A + * wl_touch.down event is guaranteed to occur before the first + * wl_touch.shape event for this touch ID but both events may occur + * within the same wl_touch.frame. + * + * A touchpoint shape is approximated by an ellipse through the + * major and minor axis length. The major axis length describes the + * longer diameter of the ellipse, while the minor axis length + * describes the shorter diameter. Major and minor are orthogonal + * and both are specified in surface-local coordinates. The center + * of the ellipse is always at the touchpoint location as reported + * by wl_touch.down or wl_touch.motion. + * + * This event is only sent by the compositor if the touch device + * supports shape reports. The client has to make reasonable + * assumptions about the shape if it did not receive this event. + * @param id the unique ID of this touch point + * @param major length of the major axis in surface-local coordinates + * @param minor length of the minor axis in surface-local coordinates + * @since 6 + */ + void (*shape)(void *data, + struct wl_touch *wl_touch, + int32_t id, + wl_fixed_t major, + wl_fixed_t minor); + /** + * update orientation of touch point + * + * Sent when a touchpoint has changed its orientation. + * + * This event does not occur on its own. It is sent before a + * wl_touch.frame event and carries the new shape information for + * any previously reported, or new touch points of that frame. + * + * Other events describing the touch point such as wl_touch.down, + * wl_touch.motion or wl_touch.shape may be sent within the same + * wl_touch.frame. A client should treat these events as a single + * logical touch point update. The order of wl_touch.shape, + * wl_touch.orientation and wl_touch.motion is not guaranteed. A + * wl_touch.down event is guaranteed to occur before the first + * wl_touch.orientation event for this touch ID but both events may + * occur within the same wl_touch.frame. + * + * The orientation describes the clockwise angle of a touchpoint's + * major axis to the positive surface y-axis and is normalized to + * the -180 to +180 degree range. The granularity of orientation + * depends on the touch device, some devices only support binary + * rotation values between 0 and 90 degrees. + * + * This event is only sent by the compositor if the touch device + * supports orientation reports. + * @param id the unique ID of this touch point + * @param orientation angle between major axis and positive surface y-axis in degrees + * @since 6 + */ + void (*orientation)(void *data, + struct wl_touch *wl_touch, + int32_t id, + wl_fixed_t orientation); +}; + +/** + * @ingroup iface_wl_touch + */ +static inline int +wl_touch_add_listener(struct wl_touch *wl_touch, + const struct wl_touch_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) wl_touch, + (void (**)(void)) listener, data); +} + +#define WL_TOUCH_RELEASE 0 + +/** + * @ingroup iface_wl_touch + */ +#define WL_TOUCH_DOWN_SINCE_VERSION 1 +/** + * @ingroup iface_wl_touch + */ +#define WL_TOUCH_UP_SINCE_VERSION 1 +/** + * @ingroup iface_wl_touch + */ +#define WL_TOUCH_MOTION_SINCE_VERSION 1 +/** + * @ingroup iface_wl_touch + */ +#define WL_TOUCH_FRAME_SINCE_VERSION 1 +/** + * @ingroup iface_wl_touch + */ +#define WL_TOUCH_CANCEL_SINCE_VERSION 1 +/** + * @ingroup iface_wl_touch + */ +#define WL_TOUCH_SHAPE_SINCE_VERSION 6 +/** + * @ingroup iface_wl_touch + */ +#define WL_TOUCH_ORIENTATION_SINCE_VERSION 6 + +/** + * @ingroup iface_wl_touch + */ +#define WL_TOUCH_RELEASE_SINCE_VERSION 3 + +/** @ingroup iface_wl_touch */ +static inline void +wl_touch_set_user_data(struct wl_touch *wl_touch, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) wl_touch, user_data); +} + +/** @ingroup iface_wl_touch */ +static inline void * +wl_touch_get_user_data(struct wl_touch *wl_touch) +{ + return wl_proxy_get_user_data((struct wl_proxy *) wl_touch); +} + +static inline uint32_t +wl_touch_get_version(struct wl_touch *wl_touch) +{ + return wl_proxy_get_version((struct wl_proxy *) wl_touch); +} + +/** @ingroup iface_wl_touch */ +static inline void +wl_touch_destroy(struct wl_touch *wl_touch) +{ + wl_proxy_destroy((struct wl_proxy *) wl_touch); +} + +/** + * @ingroup iface_wl_touch + */ +static inline void +wl_touch_release(struct wl_touch *wl_touch) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_touch, + WL_TOUCH_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_touch), WL_MARSHAL_FLAG_DESTROY); +} + +#ifndef WL_OUTPUT_SUBPIXEL_ENUM +#define WL_OUTPUT_SUBPIXEL_ENUM +/** + * @ingroup iface_wl_output + * subpixel geometry information + * + * This enumeration describes how the physical + * pixels on an output are laid out. + */ +enum wl_output_subpixel { + /** + * unknown geometry + */ + WL_OUTPUT_SUBPIXEL_UNKNOWN = 0, + /** + * no geometry + */ + WL_OUTPUT_SUBPIXEL_NONE = 1, + /** + * horizontal RGB + */ + WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB = 2, + /** + * horizontal BGR + */ + WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR = 3, + /** + * vertical RGB + */ + WL_OUTPUT_SUBPIXEL_VERTICAL_RGB = 4, + /** + * vertical BGR + */ + WL_OUTPUT_SUBPIXEL_VERTICAL_BGR = 5, +}; +#endif /* WL_OUTPUT_SUBPIXEL_ENUM */ + +#ifndef WL_OUTPUT_TRANSFORM_ENUM +#define WL_OUTPUT_TRANSFORM_ENUM +/** + * @ingroup iface_wl_output + * transformation applied to buffer contents + * + * This describes transformations that clients and compositors apply to + * buffer contents. + * + * The flipped values correspond to an initial flip around a + * vertical axis followed by rotation. + * + * The purpose is mainly to allow clients to render accordingly and + * tell the compositor, so that for fullscreen surfaces, the + * compositor will still be able to scan out directly from client + * surfaces. + */ +enum wl_output_transform { + /** + * no transform + */ + WL_OUTPUT_TRANSFORM_NORMAL = 0, + /** + * 90 degrees counter-clockwise + */ + WL_OUTPUT_TRANSFORM_90 = 1, + /** + * 180 degrees counter-clockwise + */ + WL_OUTPUT_TRANSFORM_180 = 2, + /** + * 270 degrees counter-clockwise + */ + WL_OUTPUT_TRANSFORM_270 = 3, + /** + * 180 degree flip around a vertical axis + */ + WL_OUTPUT_TRANSFORM_FLIPPED = 4, + /** + * flip and rotate 90 degrees counter-clockwise + */ + WL_OUTPUT_TRANSFORM_FLIPPED_90 = 5, + /** + * flip and rotate 180 degrees counter-clockwise + */ + WL_OUTPUT_TRANSFORM_FLIPPED_180 = 6, + /** + * flip and rotate 270 degrees counter-clockwise + */ + WL_OUTPUT_TRANSFORM_FLIPPED_270 = 7, +}; +#endif /* WL_OUTPUT_TRANSFORM_ENUM */ + +#ifndef WL_OUTPUT_MODE_ENUM +#define WL_OUTPUT_MODE_ENUM +/** + * @ingroup iface_wl_output + * mode information + * + * These flags describe properties of an output mode. + * They are used in the flags bitfield of the mode event. + */ +enum wl_output_mode { + /** + * indicates this is the current mode + */ + WL_OUTPUT_MODE_CURRENT = 0x1, + /** + * indicates this is the preferred mode + */ + WL_OUTPUT_MODE_PREFERRED = 0x2, +}; +#endif /* WL_OUTPUT_MODE_ENUM */ + +/** + * @ingroup iface_wl_output + * @struct wl_output_listener + */ +struct wl_output_listener { + /** + * properties of the output + * + * The geometry event describes geometric properties of the + * output. The event is sent when binding to the output object and + * whenever any of the properties change. + * + * The physical size can be set to zero if it doesn't make sense + * for this output (e.g. for projectors or virtual outputs). + * + * The geometry event will be followed by a done event (starting + * from version 2). + * + * Clients should use wl_surface.preferred_buffer_transform instead + * of the transform advertised by this event to find the preferred + * buffer transform to use for a surface. + * + * Note: wl_output only advertises partial information about the + * output position and identification. Some compositors, for + * instance those not implementing a desktop-style output layout or + * those exposing virtual outputs, might fake this information. + * Instead of using x and y, clients should use + * xdg_output.logical_position. Instead of using make and model, + * clients should use name and description. + * @param x x position within the global compositor space + * @param y y position within the global compositor space + * @param physical_width width in millimeters of the output + * @param physical_height height in millimeters of the output + * @param subpixel subpixel orientation of the output + * @param make textual description of the manufacturer + * @param model textual description of the model + * @param transform additional transformation applied to buffer contents during presentation + */ + void (*geometry)(void *data, + struct wl_output *wl_output, + int32_t x, + int32_t y, + int32_t physical_width, + int32_t physical_height, + int32_t subpixel, + const char *make, + const char *model, + int32_t transform); + /** + * advertise available modes for the output + * + * The mode event describes an available mode for the output. + * + * The event is sent when binding to the output object and there + * will always be one mode, the current mode. The event is sent + * again if an output changes mode, for the mode that is now + * current. In other words, the current mode is always the last + * mode that was received with the current flag set. + * + * Non-current modes are deprecated. A compositor can decide to + * only advertise the current mode and never send other modes. + * Clients should not rely on non-current modes. + * + * The size of a mode is given in physical hardware units of the + * output device. This is not necessarily the same as the output + * size in the global compositor space. For instance, the output + * may be scaled, as described in wl_output.scale, or transformed, + * as described in wl_output.transform. Clients willing to retrieve + * the output size in the global compositor space should use + * xdg_output.logical_size instead. + * + * The vertical refresh rate can be set to zero if it doesn't make + * sense for this output (e.g. for virtual outputs). + * + * The mode event will be followed by a done event (starting from + * version 2). + * + * Clients should not use the refresh rate to schedule frames. + * Instead, they should use the wl_surface.frame event or the + * presentation-time protocol. + * + * Note: this information is not always meaningful for all outputs. + * Some compositors, such as those exposing virtual outputs, might + * fake the refresh rate or the size. + * @param flags bitfield of mode flags + * @param width width of the mode in hardware units + * @param height height of the mode in hardware units + * @param refresh vertical refresh rate in mHz + */ + void (*mode)(void *data, + struct wl_output *wl_output, + uint32_t flags, + int32_t width, + int32_t height, + int32_t refresh); + /** + * sent all information about output + * + * This event is sent after all other properties have been sent + * after binding to the output object and after any other property + * changes done after that. This allows changes to the output + * properties to be seen as atomic, even if they happen via + * multiple events. + * @since 2 + */ + void (*done)(void *data, + struct wl_output *wl_output); + /** + * output scaling properties + * + * This event contains scaling geometry information that is not + * in the geometry event. It may be sent after binding the output + * object or if the output scale changes later. The compositor will + * emit a non-zero, positive value for scale. If it is not sent, + * the client should assume a scale of 1. + * + * A scale larger than 1 means that the compositor will + * automatically scale surface buffers by this amount when + * rendering. This is used for very high resolution displays where + * applications rendering at the native resolution would be too + * small to be legible. + * + * Clients should use wl_surface.preferred_buffer_scale instead of + * this event to find the preferred buffer scale to use for a + * surface. + * + * The scale event will be followed by a done event. + * @param factor scaling factor of output + * @since 2 + */ + void (*scale)(void *data, + struct wl_output *wl_output, + int32_t factor); + /** + * name of this output + * + * Many compositors will assign user-friendly names to their + * outputs, show them to the user, allow the user to refer to an + * output, etc. The client may wish to know this name as well to + * offer the user similar behaviors. + * + * The name is a UTF-8 string with no convention defined for its + * contents. Each name is unique among all wl_output globals. The + * name is only guaranteed to be unique for the compositor + * instance. + * + * The same output name is used for all clients for a given + * wl_output global. Thus, the name can be shared across processes + * to refer to a specific wl_output global. + * + * The name is not guaranteed to be persistent across sessions, + * thus cannot be used to reliably identify an output in e.g. + * configuration files. + * + * Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. + * However, do not assume that the name is a reflection of an + * underlying DRM connector, X11 connection, etc. + * + * The name event is sent after binding the output object. This + * event is only sent once per output object, and the name does not + * change over the lifetime of the wl_output global. + * + * Compositors may re-use the same output name if the wl_output + * global is destroyed and re-created later. Compositors should + * avoid re-using the same name if possible. + * + * The name event will be followed by a done event. + * @param name output name + * @since 4 + */ + void (*name)(void *data, + struct wl_output *wl_output, + const char *name); + /** + * human-readable description of this output + * + * Many compositors can produce human-readable descriptions of + * their outputs. The client may wish to know this description as + * well, e.g. for output selection purposes. + * + * The description is a UTF-8 string with no convention defined for + * its contents. The description is not guaranteed to be unique + * among all wl_output globals. Examples might include 'Foocorp 11" + * Display' or 'Virtual X11 output via :1'. + * + * The description event is sent after binding the output object + * and whenever the description changes. The description is + * optional, and may not be sent at all. + * + * The description event will be followed by a done event. + * @param description output description + * @since 4 + */ + void (*description)(void *data, + struct wl_output *wl_output, + const char *description); +}; + +/** + * @ingroup iface_wl_output + */ +static inline int +wl_output_add_listener(struct wl_output *wl_output, + const struct wl_output_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) wl_output, + (void (**)(void)) listener, data); +} + +#define WL_OUTPUT_RELEASE 0 + +/** + * @ingroup iface_wl_output + */ +#define WL_OUTPUT_GEOMETRY_SINCE_VERSION 1 +/** + * @ingroup iface_wl_output + */ +#define WL_OUTPUT_MODE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_output + */ +#define WL_OUTPUT_DONE_SINCE_VERSION 2 +/** + * @ingroup iface_wl_output + */ +#define WL_OUTPUT_SCALE_SINCE_VERSION 2 +/** + * @ingroup iface_wl_output + */ +#define WL_OUTPUT_NAME_SINCE_VERSION 4 +/** + * @ingroup iface_wl_output + */ +#define WL_OUTPUT_DESCRIPTION_SINCE_VERSION 4 + +/** + * @ingroup iface_wl_output + */ +#define WL_OUTPUT_RELEASE_SINCE_VERSION 3 + +/** @ingroup iface_wl_output */ +static inline void +wl_output_set_user_data(struct wl_output *wl_output, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) wl_output, user_data); +} + +/** @ingroup iface_wl_output */ +static inline void * +wl_output_get_user_data(struct wl_output *wl_output) +{ + return wl_proxy_get_user_data((struct wl_proxy *) wl_output); +} + +static inline uint32_t +wl_output_get_version(struct wl_output *wl_output) +{ + return wl_proxy_get_version((struct wl_proxy *) wl_output); +} + +/** @ingroup iface_wl_output */ +static inline void +wl_output_destroy(struct wl_output *wl_output) +{ + wl_proxy_destroy((struct wl_proxy *) wl_output); +} + +/** + * @ingroup iface_wl_output + * + * Using this request a client can tell the server that it is not going to + * use the output object anymore. + */ +static inline void +wl_output_release(struct wl_output *wl_output) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_output, + WL_OUTPUT_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_output), WL_MARSHAL_FLAG_DESTROY); +} + +#define WL_REGION_DESTROY 0 +#define WL_REGION_ADD 1 +#define WL_REGION_SUBTRACT 2 + + +/** + * @ingroup iface_wl_region + */ +#define WL_REGION_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wl_region + */ +#define WL_REGION_ADD_SINCE_VERSION 1 +/** + * @ingroup iface_wl_region + */ +#define WL_REGION_SUBTRACT_SINCE_VERSION 1 + +/** @ingroup iface_wl_region */ +static inline void +wl_region_set_user_data(struct wl_region *wl_region, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) wl_region, user_data); +} + +/** @ingroup iface_wl_region */ +static inline void * +wl_region_get_user_data(struct wl_region *wl_region) +{ + return wl_proxy_get_user_data((struct wl_proxy *) wl_region); +} + +static inline uint32_t +wl_region_get_version(struct wl_region *wl_region) +{ + return wl_proxy_get_version((struct wl_proxy *) wl_region); +} + +/** + * @ingroup iface_wl_region + * + * Destroy the region. This will invalidate the object ID. + */ +static inline void +wl_region_destroy(struct wl_region *wl_region) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_region, + WL_REGION_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_region), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_wl_region + * + * Add the specified rectangle to the region. + */ +static inline void +wl_region_add(struct wl_region *wl_region, int32_t x, int32_t y, int32_t width, int32_t height) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_region, + WL_REGION_ADD, NULL, wl_proxy_get_version((struct wl_proxy *) wl_region), 0, x, y, width, height); +} + +/** + * @ingroup iface_wl_region + * + * Subtract the specified rectangle from the region. + */ +static inline void +wl_region_subtract(struct wl_region *wl_region, int32_t x, int32_t y, int32_t width, int32_t height) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_region, + WL_REGION_SUBTRACT, NULL, wl_proxy_get_version((struct wl_proxy *) wl_region), 0, x, y, width, height); +} + +#ifndef WL_SUBCOMPOSITOR_ERROR_ENUM +#define WL_SUBCOMPOSITOR_ERROR_ENUM +enum wl_subcompositor_error { + /** + * the to-be sub-surface is invalid + */ + WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE = 0, + /** + * the to-be sub-surface parent is invalid + */ + WL_SUBCOMPOSITOR_ERROR_BAD_PARENT = 1, +}; +#endif /* WL_SUBCOMPOSITOR_ERROR_ENUM */ + +#define WL_SUBCOMPOSITOR_DESTROY 0 +#define WL_SUBCOMPOSITOR_GET_SUBSURFACE 1 + + +/** + * @ingroup iface_wl_subcompositor + */ +#define WL_SUBCOMPOSITOR_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wl_subcompositor + */ +#define WL_SUBCOMPOSITOR_GET_SUBSURFACE_SINCE_VERSION 1 + +/** @ingroup iface_wl_subcompositor */ +static inline void +wl_subcompositor_set_user_data(struct wl_subcompositor *wl_subcompositor, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) wl_subcompositor, user_data); +} + +/** @ingroup iface_wl_subcompositor */ +static inline void * +wl_subcompositor_get_user_data(struct wl_subcompositor *wl_subcompositor) +{ + return wl_proxy_get_user_data((struct wl_proxy *) wl_subcompositor); +} + +static inline uint32_t +wl_subcompositor_get_version(struct wl_subcompositor *wl_subcompositor) +{ + return wl_proxy_get_version((struct wl_proxy *) wl_subcompositor); +} + +/** + * @ingroup iface_wl_subcompositor + * + * Informs the server that the client will not be using this + * protocol object anymore. This does not affect any other + * objects, wl_subsurface objects included. + */ +static inline void +wl_subcompositor_destroy(struct wl_subcompositor *wl_subcompositor) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_subcompositor, + WL_SUBCOMPOSITOR_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_subcompositor), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_wl_subcompositor + * + * Create a sub-surface interface for the given surface, and + * associate it with the given parent surface. This turns a + * plain wl_surface into a sub-surface. + * + * The to-be sub-surface must not already have another role, and it + * must not have an existing wl_subsurface object. Otherwise the + * bad_surface protocol error is raised. + * + * Adding sub-surfaces to a parent is a double-buffered operation on the + * parent (see wl_surface.commit). The effect of adding a sub-surface + * becomes visible on the next time the state of the parent surface is + * applied. + * + * The parent surface must not be one of the child surface's descendants, + * and the parent must be different from the child surface, otherwise the + * bad_parent protocol error is raised. + * + * This request modifies the behaviour of wl_surface.commit request on + * the sub-surface, see the documentation on wl_subsurface interface. + */ +static inline struct wl_subsurface * +wl_subcompositor_get_subsurface(struct wl_subcompositor *wl_subcompositor, struct wl_surface *surface, struct wl_surface *parent) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) wl_subcompositor, + WL_SUBCOMPOSITOR_GET_SUBSURFACE, &wl_subsurface_interface, wl_proxy_get_version((struct wl_proxy *) wl_subcompositor), 0, NULL, surface, parent); + + return (struct wl_subsurface *) id; +} + +#ifndef WL_SUBSURFACE_ERROR_ENUM +#define WL_SUBSURFACE_ERROR_ENUM +enum wl_subsurface_error { + /** + * wl_surface is not a sibling or the parent + */ + WL_SUBSURFACE_ERROR_BAD_SURFACE = 0, +}; +#endif /* WL_SUBSURFACE_ERROR_ENUM */ + +#define WL_SUBSURFACE_DESTROY 0 +#define WL_SUBSURFACE_SET_POSITION 1 +#define WL_SUBSURFACE_PLACE_ABOVE 2 +#define WL_SUBSURFACE_PLACE_BELOW 3 +#define WL_SUBSURFACE_SET_SYNC 4 +#define WL_SUBSURFACE_SET_DESYNC 5 + + +/** + * @ingroup iface_wl_subsurface + */ +#define WL_SUBSURFACE_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wl_subsurface + */ +#define WL_SUBSURFACE_SET_POSITION_SINCE_VERSION 1 +/** + * @ingroup iface_wl_subsurface + */ +#define WL_SUBSURFACE_PLACE_ABOVE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_subsurface + */ +#define WL_SUBSURFACE_PLACE_BELOW_SINCE_VERSION 1 +/** + * @ingroup iface_wl_subsurface + */ +#define WL_SUBSURFACE_SET_SYNC_SINCE_VERSION 1 +/** + * @ingroup iface_wl_subsurface + */ +#define WL_SUBSURFACE_SET_DESYNC_SINCE_VERSION 1 + +/** @ingroup iface_wl_subsurface */ +static inline void +wl_subsurface_set_user_data(struct wl_subsurface *wl_subsurface, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) wl_subsurface, user_data); +} + +/** @ingroup iface_wl_subsurface */ +static inline void * +wl_subsurface_get_user_data(struct wl_subsurface *wl_subsurface) +{ + return wl_proxy_get_user_data((struct wl_proxy *) wl_subsurface); +} + +static inline uint32_t +wl_subsurface_get_version(struct wl_subsurface *wl_subsurface) +{ + return wl_proxy_get_version((struct wl_proxy *) wl_subsurface); +} + +/** + * @ingroup iface_wl_subsurface + * + * The sub-surface interface is removed from the wl_surface object + * that was turned into a sub-surface with a + * wl_subcompositor.get_subsurface request. The wl_surface's association + * to the parent is deleted. The wl_surface is unmapped immediately. + */ +static inline void +wl_subsurface_destroy(struct wl_subsurface *wl_subsurface) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_subsurface, + WL_SUBSURFACE_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_subsurface), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_wl_subsurface + * + * This sets the position of the sub-surface, relative to the parent + * surface. + * + * The sub-surface will be moved so that its origin (top left + * corner pixel) will be at the location x, y of the parent surface + * coordinate system. The coordinates are not restricted to the parent + * surface area. Negative values are allowed. + * + * The initial position is 0, 0. + * + * Position is double-buffered state on the parent surface, see + * wl_subsurface and wl_surface.commit for more information. + */ +static inline void +wl_subsurface_set_position(struct wl_subsurface *wl_subsurface, int32_t x, int32_t y) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_subsurface, + WL_SUBSURFACE_SET_POSITION, NULL, wl_proxy_get_version((struct wl_proxy *) wl_subsurface), 0, x, y); +} + +/** + * @ingroup iface_wl_subsurface + * + * This sub-surface is taken from the stack, and put back just + * above the reference surface, changing the z-order of the sub-surfaces. + * The reference surface must be one of the sibling surfaces, or the + * parent surface. Using any other surface, including this sub-surface, + * will cause a protocol error. + * + * A new sub-surface is initially added as the top-most in the stack + * of its siblings and parent. + * + * Z-order is double-buffered state on the parent surface, see + * wl_subsurface and wl_surface.commit for more information. + */ +static inline void +wl_subsurface_place_above(struct wl_subsurface *wl_subsurface, struct wl_surface *sibling) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_subsurface, + WL_SUBSURFACE_PLACE_ABOVE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_subsurface), 0, sibling); +} + +/** + * @ingroup iface_wl_subsurface + * + * The sub-surface is placed just below the reference surface. + * + * See wl_subsurface.place_above. + */ +static inline void +wl_subsurface_place_below(struct wl_subsurface *wl_subsurface, struct wl_surface *sibling) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_subsurface, + WL_SUBSURFACE_PLACE_BELOW, NULL, wl_proxy_get_version((struct wl_proxy *) wl_subsurface), 0, sibling); +} + +/** + * @ingroup iface_wl_subsurface + * + * Change the commit behaviour of the sub-surface to synchronized + * mode. + * + * See wl_subsurface and wl_surface.commit for more information. + */ +static inline void +wl_subsurface_set_sync(struct wl_subsurface *wl_subsurface) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_subsurface, + WL_SUBSURFACE_SET_SYNC, NULL, wl_proxy_get_version((struct wl_proxy *) wl_subsurface), 0); +} + +/** + * @ingroup iface_wl_subsurface + * + * Change the commit behaviour of the sub-surface to desynchronized + * mode. + * + * See wl_subsurface and wl_surface.commit for more information. + */ +static inline void +wl_subsurface_set_desync(struct wl_subsurface *wl_subsurface) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_subsurface, + WL_SUBSURFACE_SET_DESYNC, NULL, wl_proxy_get_version((struct wl_proxy *) wl_subsurface), 0); +} + +#ifndef WL_FIXES_ERROR_ENUM +#define WL_FIXES_ERROR_ENUM +/** + * @ingroup iface_wl_fixes + * wl_fixes error values + * + * These errors can be emitted in response to wl_fixes requests. + */ +enum wl_fixes_error { + /** + * unknown global or the global is not removed + */ + WL_FIXES_ERROR_INVALID_ACK_REMOVE = 0, +}; +#endif /* WL_FIXES_ERROR_ENUM */ + +#define WL_FIXES_DESTROY 0 +#define WL_FIXES_DESTROY_REGISTRY 1 +#define WL_FIXES_ACK_GLOBAL_REMOVE 2 + + +/** + * @ingroup iface_wl_fixes + */ +#define WL_FIXES_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wl_fixes + */ +#define WL_FIXES_DESTROY_REGISTRY_SINCE_VERSION 1 +/** + * @ingroup iface_wl_fixes + */ +#define WL_FIXES_ACK_GLOBAL_REMOVE_SINCE_VERSION 2 + +/** @ingroup iface_wl_fixes */ +static inline void +wl_fixes_set_user_data(struct wl_fixes *wl_fixes, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) wl_fixes, user_data); +} + +/** @ingroup iface_wl_fixes */ +static inline void * +wl_fixes_get_user_data(struct wl_fixes *wl_fixes) +{ + return wl_proxy_get_user_data((struct wl_proxy *) wl_fixes); +} + +static inline uint32_t +wl_fixes_get_version(struct wl_fixes *wl_fixes) +{ + return wl_proxy_get_version((struct wl_proxy *) wl_fixes); +} + +/** + * @ingroup iface_wl_fixes + */ +static inline void +wl_fixes_destroy(struct wl_fixes *wl_fixes) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_fixes, + WL_FIXES_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_fixes), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_wl_fixes + * + * This request destroys a wl_registry object. + * + * The client should no longer use the wl_registry after making this + * request. + * + * The compositor will emit a wl_display.delete_id event with the object ID + * of the registry and will no longer emit any events on the registry. The + * client should re-use the object ID once it receives the + * wl_display.delete_id event. + */ +static inline void +wl_fixes_destroy_registry(struct wl_fixes *wl_fixes, struct wl_registry *registry) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_fixes, + WL_FIXES_DESTROY_REGISTRY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_fixes), 0, registry); +} + +/** + * @ingroup iface_wl_fixes + * + * Acknowledge the removal of the specified global. + * + * If no global with the specified name exists or the global is not removed, + * the wl_fixes.invalid_ack_remove protocol error will be posted. + * + * Due to the Wayland protocol being asynchronous, the wl_global objects + * cannot be destroyed immediately. For example, if a wl_global is removed + * and a client attempts to bind that global around same time, it can + * result in a protocol error due to an unknown global name in the bind + * request. + * + * In order to avoid crashing clients, the compositor should remove the + * wl_global once it is guaranteed that no more bind requests will come. + * + * The wl_fixes.ack_global_remove() request is used to signal to the + * compositor that the client will not bind the given global anymore. After + * all clients acknowledge the removal of the global, the compositor can + * safely destroy it. + * + * The client must call the wl_fixes.ack_global_remove() request in + * response to a wl_registry.global_remove() event even if it did not bind + * the corresponding global. + */ +static inline void +wl_fixes_ack_global_remove(struct wl_fixes *wl_fixes, struct wl_registry *registry, uint32_t name) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wl_fixes, + WL_FIXES_ACK_GLOBAL_REMOVE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_fixes), 0, registry, name); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/vr/third_party/wayland/wayland-client.h b/tools/vr/third_party/wayland/wayland-client.h new file mode 100644 index 00000000..9f70fa3f --- /dev/null +++ b/tools/vr/third_party/wayland/wayland-client.h @@ -0,0 +1,42 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** \file + * + * \brief Include the client API and protocol C API. + * + * \warning Use of this header file is discouraged. Prefer including + * wayland-client-core.h instead, which does not include the + * client protocol header and as such only defines the library + * API. + */ + +#ifndef WAYLAND_CLIENT_H +#define WAYLAND_CLIENT_H + +#include "wayland-client-core.h" +#include "wayland-client-protocol.h" + +#endif diff --git a/tools/vr/third_party/wayland/wayland-util.h b/tools/vr/third_party/wayland/wayland-util.h new file mode 100644 index 00000000..98c72fde --- /dev/null +++ b/tools/vr/third_party/wayland/wayland-util.h @@ -0,0 +1,768 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** \file wayland-util.h + * + * \brief Utility classes, functions, and macros. + */ + +#ifndef WAYLAND_UTIL_H +#define WAYLAND_UTIL_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Visibility attribute */ +#if defined(__GNUC__) && __GNUC__ >= 4 +#define WL_EXPORT __attribute__ ((visibility("default"))) +#else +#define WL_EXPORT +#endif + +/** Deprecated attribute */ +#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) || (defined(__cplusplus) && __cplusplus >= 201402L) +#define WL_DEPRECATED [[deprecated]] +#elif defined(__GNUC__) && __GNUC__ >= 4 +#define WL_DEPRECATED __attribute__ ((deprecated)) +#else +#define WL_DEPRECATED +#endif + +/** + * Printf-style argument attribute + * + * \param x Ordinality of the format string argument + * \param y Ordinality of the argument to check against the format string + * + * \sa https://gcc.gnu.org/onlinedocs/gcc-3.2.1/gcc/Function-Attributes.html + */ +#if defined(__GNUC__) && __GNUC__ >= 4 +#define WL_PRINTF(x, y) __attribute__((__format__(__printf__, x, y))) +#else +#define WL_PRINTF(x, y) +#endif + +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L +#define WL_TYPEOF(expr) typeof(expr) +#else +#define WL_TYPEOF(expr) __typeof__(expr) +#endif + +/** \class wl_object + * + * \brief A protocol object. + * + * A `wl_object` is an opaque struct identifying the protocol object + * underlying a `wl_proxy` or `wl_resource`. + * + * \note Functions accessing a `wl_object` are not normally used by client code. + * Clients should normally use the higher level interface generated by the + * scanner to interact with compositor objects. + * + */ +struct wl_object; + +/** + * The maximum size of a protocol message. + * + * If a message size exceeds this value, the connection will be dropped. + * Servers will send an invalid_method error before disconnecting. + */ +#define WL_MAX_MESSAGE_SIZE 4096 + +/** + * Protocol message signature + * + * A wl_message describes the signature of an actual protocol message, such as a + * request or event, that adheres to the Wayland protocol wire format. The + * protocol implementation uses a wl_message within its demarshal machinery for + * decoding messages between a compositor and its clients. In a sense, a + * wl_message is to a protocol message like a class is to an object. + * + * The `name` of a wl_message is the name of the corresponding protocol message. + * + * The `signature` is an ordered list of symbols representing the data types + * of message arguments and, optionally, a protocol version and indicators for + * nullability. A leading integer in the `signature` indicates the _since_ + * version of the protocol message. A `?` preceding a data type symbol indicates + * that the following argument type is nullable. While it is a protocol violation + * to send messages with non-nullable arguments set to `NULL`, event handlers in + * clients might still get called with non-nullable object arguments set to + * `NULL`. This can happen when the client destroyed the object being used as + * argument on its side and an event referencing that object was sent before the + * server knew about its destruction. As this race cannot be prevented, clients + * should - as a general rule - program their event handlers such that they can + * handle object arguments declared non-nullable being `NULL` gracefully. + * + * When no arguments accompany a message, `signature` is an empty string. + * + * Symbols: + * + * * `i`: int + * * `u`: uint + * * `f`: fixed + * * `s`: string + * * `o`: object + * * `n`: new_id + * * `a`: array + * * `h`: fd + * * `?`: following argument (`o` or `s`) is nullable + * + * While demarshaling primitive arguments is straightforward, when demarshaling + * messages containing `object` or `new_id` arguments, the protocol + * implementation often must determine the type of the object. The `types` of a + * wl_message is an array of wl_interface references that correspond to `o` and + * `n` arguments in `signature`, with `NULL` placeholders for arguments with + * non-object types. + * + * Consider the protocol event wl_display `delete_id` that has a single `uint` + * argument. The wl_message is: + * + * \code + * { "delete_id", "u", [NULL] } + * \endcode + * + * Here, the message `name` is `"delete_id"`, the `signature` is `"u"`, and the + * argument `types` is `[NULL]`, indicating that the `uint` argument has no + * corresponding wl_interface since it is a primitive argument. + * + * In contrast, consider a `wl_foo` interface supporting protocol request `bar` + * that has existed since version 2, and has two arguments: a `uint` and an + * object of type `wl_baz_interface` that may be `NULL`. Such a `wl_message` + * might be: + * + * \code + * { "bar", "2u?o", [NULL, &wl_baz_interface] } + * \endcode + * + * Here, the message `name` is `"bar"`, and the `signature` is `"2u?o"`. Notice + * how the `2` indicates the protocol version, the `u` indicates the first + * argument type is `uint`, and the `?o` indicates that the second argument + * is an object that may be `NULL`. Lastly, the argument `types` array indicates + * that no wl_interface corresponds to the first argument, while the type + * `wl_baz_interface` corresponds to the second argument. + * + * \sa wl_argument + * \sa wl_interface + * \sa Wire Format + */ +struct wl_message { + /** Message name */ + const char *name; + /** Message signature */ + const char *signature; + /** Object argument interfaces */ + const struct wl_interface **types; +}; + +/** + * Protocol object interface + * + * A wl_interface describes the API of a protocol object defined in the Wayland + * protocol specification. The protocol implementation uses a wl_interface + * within its marshalling machinery for encoding client requests. + * + * The `name` of a wl_interface is the name of the corresponding protocol + * interface, and `version` represents the version of the interface. The members + * `method_count` and `event_count` represent the number of `methods` (requests) + * and `events` in the respective wl_message members. + * + * For example, consider a protocol interface `foo`, marked as version `1`, with + * two requests and one event. + * + * \code{.xml} + * + * + * + * + * + * \endcode + * + * Given two wl_message arrays `foo_requests` and `foo_events`, a wl_interface + * for `foo` might be: + * + * \code + * struct wl_interface foo_interface = { + * "foo", 1, + * 2, foo_requests, + * 1, foo_events + * }; + * \endcode + * + * \note The server side of the protocol may define interface implementation + * types that incorporate the term `interface` in their name. Take + * care to not confuse these server-side `struct`s with a wl_interface + * variable whose name also ends in `interface`. For example, while the + * server may define a type `struct wl_foo_interface`, the client may + * define a `struct wl_interface wl_foo_interface`. + * + * \sa wl_message + * \sa wl_proxy + * \sa Interfaces + * \sa Versioning + */ +struct wl_interface { + /** Interface name */ + const char *name; + /** Interface version */ + int version; + /** Number of methods (requests) */ + int method_count; + /** Method (request) signatures */ + const struct wl_message *methods; + /** Number of events */ + int event_count; + /** Event signatures */ + const struct wl_message *events; +}; + +/** \class wl_list + * + * \brief Doubly-linked list + * + * On its own, an instance of `struct wl_list` represents the sentinel head of + * a doubly-linked list, and must be initialized using wl_list_init(). + * When empty, the list head's `next` and `prev` members point to the list head + * itself, otherwise `next` references the first element in the list, and `prev` + * refers to the last element in the list. + * + * Use the `struct wl_list` type to represent both the list head and the links + * between elements within the list. Use wl_list_empty() to determine if the + * list is empty in O(1). + * + * All elements in the list must be of the same type. The element type must have + * a `struct wl_list` member, often named `link` by convention. Prior to + * insertion, there is no need to initialize an element's `link` - invoking + * wl_list_init() on an individual list element's `struct wl_list` member is + * unnecessary if the very next operation is wl_list_insert(). However, a + * common idiom is to initialize an element's `link` prior to removal - ensure + * safety by invoking wl_list_init() before wl_list_remove(). + * + * Consider a list reference `struct wl_list foo_list`, an element type as + * `struct element`, and an element's link member as `struct wl_list link`. + * + * The following code initializes a list and adds three elements to it. + * + * \code + * struct wl_list foo_list; + * + * struct element { + * int foo; + * struct wl_list link; + * }; + * struct element e1, e2, e3; + * + * wl_list_init(&foo_list); + * wl_list_insert(&foo_list, &e1.link); // e1 is the first element + * wl_list_insert(&foo_list, &e2.link); // e2 is now the first element + * wl_list_insert(&e2.link, &e3.link); // insert e3 after e2 + * \endcode + * + * The list now looks like [e2, e3, e1]. + * + * The `wl_list` API provides some iterator macros. For example, to iterate + * a list in ascending order: + * + * \code + * struct element *e; + * wl_list_for_each(e, foo_list, link) { + * do_something_with_element(e); + * } + * \endcode + * + * See the documentation of each iterator for details. + * \sa http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/linux/list.h + */ +struct wl_list { + /** Previous list element */ + struct wl_list *prev; + /** Next list element */ + struct wl_list *next; +}; + +/** + * Initializes the list. + * + * \param list List to initialize + * + * \memberof wl_list + */ +void +wl_list_init(struct wl_list *list); + +/** + * Inserts an element into the list, after the element represented by \p list. + * When \p list is a reference to the list itself (the head), set the containing + * struct of \p elm as the first element in the list. + * + * \note If \p elm is already part of a list, inserting it again will lead to + * list corruption. + * + * \param list List element after which the new element is inserted + * \param elm Link of the containing struct to insert into the list + * + * \memberof wl_list + */ +void +wl_list_insert(struct wl_list *list, struct wl_list *elm); + +/** + * Removes an element from the list. + * + * \note This operation leaves \p elm in an invalid state. + * + * \param elm Link of the containing struct to remove from the list + * + * \memberof wl_list + */ +void +wl_list_remove(struct wl_list *elm); + +/** + * Determines the length of the list. + * + * \note This is an O(n) operation. + * + * \param list List whose length is to be determined + * + * \return Number of elements in the list + * + * \memberof wl_list + */ +int +wl_list_length(const struct wl_list *list); + +/** + * Determines if the list is empty. + * + * \param list List whose emptiness is to be determined + * + * \return 1 if empty, or 0 if not empty + * + * \memberof wl_list + */ +int +wl_list_empty(const struct wl_list *list); + +/** + * Inserts all of the elements of one list into another, after the element + * represented by \p list. + * + * \note This leaves \p other in an invalid state. + * + * \param list List element after which the other list elements will be inserted + * \param other List of elements to insert + * + * \memberof wl_list + */ +void +wl_list_insert_list(struct wl_list *list, struct wl_list *other); + +/** + * Retrieves a pointer to a containing struct, given a member name. + * + * This macro allows "conversion" from a pointer to a member to its containing + * struct. This is useful if you have a contained item like a wl_list, + * wl_listener, or wl_signal, provided via a callback or other means, and would + * like to retrieve the struct that contains it. + * + * To demonstrate, the following example retrieves a pointer to + * `example_container` given only its `destroy_listener` member: + * + * \code + * struct example_container { + * struct wl_listener destroy_listener; + * // other members... + * }; + * + * void example_container_destroy(struct wl_listener *listener, void *data) + * { + * struct example_container *ctr; + * + * ctr = wl_container_of(listener, ctr, destroy_listener); + * // destroy ctr... + * } + * \endcode + * + * \note `sample` need not be a valid pointer. A null or uninitialised pointer + * is sufficient. + * + * \param ptr Valid pointer to the contained member + * \param sample Pointer to a struct whose type contains \p ptr + * \param member Named location of \p ptr within the \p sample type + * + * \return The container for the specified pointer + */ +#define wl_container_of(ptr, sample, member) \ + (WL_TYPEOF(sample))((char *)(ptr) - \ + offsetof(WL_TYPEOF(*sample), member)) + +/** + * Iterates over a list. + * + * This macro expresses a for-each iterator for wl_list. Given a list and + * wl_list link member name (often named `link` by convention), this macro + * assigns each element in the list to \p pos, which can then be referenced in + * a trailing code block. For example, given a wl_list of `struct message` + * elements: + * + * \code + * struct message { + * char *contents; + * wl_list link; + * }; + * + * struct wl_list *message_list; + * // Assume message_list now "contains" many messages + * + * struct message *m; + * wl_list_for_each(m, message_list, link) { + * do_something_with_message(m); + * } + * \endcode + * + * \param pos Cursor that each list element will be assigned to + * \param head Head of the list to iterate over + * \param member Name of the link member within the element struct + * + * \relates wl_list + */ +#define wl_list_for_each(pos, head, member) \ + for (pos = wl_container_of((head)->next, pos, member); \ + &pos->member != (head); \ + pos = wl_container_of(pos->member.next, pos, member)) + +/** + * Iterates over a list, safe against removal of the list element. + * + * \note Only removal of the current element, \p pos, is safe. Removing + * any other element during traversal may lead to a loop malfunction. + * + * \sa wl_list_for_each() + * + * \param pos Cursor that each list element will be assigned to + * \param tmp Temporary pointer of the same type as \p pos + * \param head Head of the list to iterate over + * \param member Name of the link member within the element struct + * + * \relates wl_list + */ +#define wl_list_for_each_safe(pos, tmp, head, member) \ + for (pos = wl_container_of((head)->next, pos, member), \ + tmp = wl_container_of((pos)->member.next, tmp, member); \ + &pos->member != (head); \ + pos = tmp, \ + tmp = wl_container_of(pos->member.next, tmp, member)) + +/** + * Iterates backwards over a list. + * + * \sa wl_list_for_each() + * + * \param pos Cursor that each list element will be assigned to + * \param head Head of the list to iterate over + * \param member Name of the link member within the element struct + * + * \relates wl_list + */ +#define wl_list_for_each_reverse(pos, head, member) \ + for (pos = wl_container_of((head)->prev, pos, member); \ + &pos->member != (head); \ + pos = wl_container_of(pos->member.prev, pos, member)) + +/** + * Iterates backwards over a list, safe against removal of the list element. + * + * \note Only removal of the current element, \p pos, is safe. Removing + * any other element during traversal may lead to a loop malfunction. + * + * \sa wl_list_for_each() + * + * \param pos Cursor that each list element will be assigned to + * \param tmp Temporary pointer of the same type as \p pos + * \param head Head of the list to iterate over + * \param member Name of the link member within the element struct + * + * \relates wl_list + */ +#define wl_list_for_each_reverse_safe(pos, tmp, head, member) \ + for (pos = wl_container_of((head)->prev, pos, member), \ + tmp = wl_container_of((pos)->member.prev, tmp, member); \ + &pos->member != (head); \ + pos = tmp, \ + tmp = wl_container_of(pos->member.prev, tmp, member)) + +/** + * \class wl_array + * + * Dynamic array + * + * A wl_array is a dynamic array that can only grow until released. It is + * intended for relatively small allocations whose size is variable or not known + * in advance. While construction of a wl_array does not require all elements to + * be of the same size, wl_array_for_each() does require all elements to have + * the same type and size. + * + */ +struct wl_array { + /** Array size */ + size_t size; + /** Allocated space */ + size_t alloc; + /** Array data */ + void *data; +}; + +/** + * Initializes the array. + * + * \param array Array to initialize + * + * \memberof wl_array + */ +void +wl_array_init(struct wl_array *array); + +/** + * Releases the array data. + * + * \note Leaves the array in an invalid state. + * + * \param array Array whose data is to be released + * + * \memberof wl_array + */ +void +wl_array_release(struct wl_array *array); + +/** + * Increases the size of the array by \p size bytes. + * + * \param array Array whose size is to be increased + * \param size Number of bytes to increase the size of the array by + * + * \return A pointer to the beginning of the newly appended space, or NULL when + * resizing fails. + * + * \memberof wl_array + */ +void * +wl_array_add(struct wl_array *array, size_t size); + +/** + * Copies the contents of \p source to \p array. + * + * \param array Destination array to copy to + * \param source Source array to copy from + * + * \return 0 on success, or -1 on failure + * + * \memberof wl_array + */ +int +wl_array_copy(struct wl_array *array, struct wl_array *source); + +/** + * Iterates over an array. + * + * This macro expresses a for-each iterator for wl_array. It assigns each + * element in the array to \p pos, which can then be referenced in a trailing + * code block. \p pos must be a pointer to the array element type, and all + * array elements must be of the same type and size. + * + * \param pos Cursor that each array element will be assigned to + * \param array Array to iterate over + * + * \relates wl_array + * \sa wl_list_for_each() + */ +#define wl_array_for_each(pos, array) \ + for (pos = (array)->data; \ + (array)->size != 0 && \ + (const char *) pos < ((const char *) (array)->data + (array)->size); \ + (pos)++) + +/** + * Fixed-point number + * + * A `wl_fixed_t` is a 24.8 signed fixed-point number with a sign bit, 23 bits + * of integer precision and 8 bits of decimal precision. Consider `wl_fixed_t` + * as an opaque struct with methods that facilitate conversion to and from + * `double` and `int` types. + */ +typedef int32_t wl_fixed_t; + +/** + * Converts a fixed-point number to a floating-point number. + * + * \param f Fixed-point number to convert + * + * \return Floating-point representation of the fixed-point argument + */ +static inline double +wl_fixed_to_double(wl_fixed_t f) +{ + return f / 256.0; +} + +/** + * Converts a floating-point number to a fixed-point number. + * + * \param d Floating-point number to convert + * + * \return Fixed-point representation of the floating-point argument + */ +static inline wl_fixed_t +wl_fixed_from_double(double d) +{ + return (wl_fixed_t) (round(d * 256.0)); +} + +/** + * Converts a fixed-point number to an integer. + * + * \param f Fixed-point number to convert + * + * \return Integer component of the fixed-point argument + */ +static inline int +wl_fixed_to_int(wl_fixed_t f) +{ + return f / 256; +} + +/** + * Converts an integer to a fixed-point number. + * + * \param i Integer to convert + * + * \return Fixed-point representation of the integer argument + */ +static inline wl_fixed_t +wl_fixed_from_int(int i) +{ + return i * 256; +} + +/** + * Protocol message argument data types + * + * This union represents all of the argument types in the Wayland protocol wire + * format. The protocol implementation uses wl_argument within its marshalling + * machinery for dispatching messages between a client and a compositor. + * + * \sa wl_message + * \sa wl_interface + * \sa Wire Format + */ +union wl_argument { + int32_t i; /**< `int` */ + uint32_t u; /**< `uint` */ + wl_fixed_t f; /**< `fixed` */ + const char *s; /**< `string` */ + struct wl_object *o; /**< `object` */ + uint32_t n; /**< `new_id` */ + struct wl_array *a; /**< `array` */ + int32_t h; /**< `fd` */ +}; + +/** + * Dispatcher function type alias + * + * A dispatcher is a function that handles the emitting of callbacks in client + * code. For programs directly using the C library, this is done by using + * libffi to call function pointers. When binding to languages other than C, + * dispatchers provide a way to abstract the function calling process to be + * friendlier to other function calling systems. + * + * A dispatcher takes five arguments: The first is the dispatcher-specific + * implementation associated with the target object. The second is the object + * upon which the callback is being invoked (either wl_proxy or wl_resource). + * The third and fourth arguments are the opcode and the wl_message + * corresponding to the callback. The final argument is an array of arguments + * received from the other process via the wire protocol. + * + * \param user_data Dispatcher-specific implementation data + * \param target Callback invocation target (wl_proxy or `wl_resource`) + * \param opcode Callback opcode + * \param msg Callback message signature + * \param args Array of received arguments + * + * \return 0 on success, or -1 on failure + */ +typedef int (*wl_dispatcher_func_t)(const void *user_data, void *target, + uint32_t opcode, const struct wl_message *msg, + union wl_argument *args); + +/** + * Log function type alias + * + * The C implementation of the Wayland protocol abstracts the details of + * logging. Users may customize the logging behavior, with a function conforming + * to the `wl_log_func_t` type, via `wl_log_set_handler_client` and + * `wl_log_set_handler_server`. + * + * A `wl_log_func_t` must conform to the expectations of `vprintf`, and + * expects two arguments: a string to write and a corresponding variable + * argument list. While the string to write may contain format specifiers and + * use values in the variable argument list, the behavior of any `wl_log_func_t` + * depends on the implementation. + * + * \note Take care to not confuse this with `wl_protocol_logger_func_t`, which + * is a specific server-side logger for requests and events. + * + * \param fmt String to write to the log, containing optional format + * specifiers + * \param args Variable argument list + * + * \sa wl_log_set_handler_client + * \sa wl_log_set_handler_server + */ +typedef void (*wl_log_func_t)(const char *fmt, va_list args) WL_PRINTF(1, 0); + +/** + * Return value of an iterator function + * + * \sa wl_client_for_each_resource_iterator_func_t + * \sa wl_client_for_each_resource + */ +enum wl_iterator_result { + /** Stop the iteration */ + WL_ITERATOR_STOP, + /** Continue the iteration */ + WL_ITERATOR_CONTINUE +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/vr/third_party/wayland/wayland-version.h b/tools/vr/third_party/wayland/wayland-version.h new file mode 100644 index 00000000..c7e04c2c --- /dev/null +++ b/tools/vr/third_party/wayland/wayland-version.h @@ -0,0 +1,34 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WAYLAND_VERSION_H +#define WAYLAND_VERSION_H + +#define WAYLAND_VERSION_MAJOR 1 +#define WAYLAND_VERSION_MINOR 26 +#define WAYLAND_VERSION_MICRO 0 +#define WAYLAND_VERSION "1.26.0" + +#endif