Skip to content

File plugin.cpp

File List > controller > plugin.cpp

Go to the documentation of this file

#include "plugin.hpp"

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

namespace {

// Input thresholds are used only to derive convenient pressed/moved states;
// the original analog values are still published on the switchboard.
constexpr float         kTriggerPressedThreshold    = 0.75F;
constexpr float         kSqueezePressedThreshold    = 0.85F;
constexpr float         kThumbstickMoveThreshold    = 0.15F;
constexpr int           kWindowSize                 = 64;
constexpr float         kPanelDistanceMeters        = 1.1F;
constexpr float         kPanelWidthMeters           = 1.2F;
constexpr float         kPanelYOffsetMeters         = 0.0F;
constexpr float         kNearZ                      = 0.02F;
constexpr float         kFarZ                       = 100.0F;
constexpr std::uint32_t kOverlayCommandStrideFloats = 14;

constexpr const char* kSimpleControllerProfile = "/interaction_profiles/khr/simple_controller";
constexpr const char* kOculusTouchProfile      = "/interaction_profiles/oculus/touch_controller";
constexpr const char* kHtcViveProfile          = "/interaction_profiles/htc/vive_controller";
constexpr const char* kValveIndexProfile       = "/interaction_profiles/valve/index_controller";
constexpr const char* kMicrosoftMotionProfile  = "/interaction_profiles/microsoft/motion_controller";

template<typename T>
T make_xr_struct(XrStructureType type) {
    T value{};
    value.type = type;
    return value;
}

bool has_extension(const std::vector<XrExtensionProperties>& extensions, const char* name) {
    return std::any_of(extensions.begin(), extensions.end(), [name](const XrExtensionProperties& extension) {
        return std::strcmp(extension.extensionName, name) == 0;
    });
}

struct mat4 {
    float values[16]{};
};

// Minimal column-major transform helpers used by the optional mono/head-locked
// panel modes. The normal stereo path submits Boba's original render poses.
mat4 identity_matrix() {
    mat4 result{};
    result.values[0]  = 1.0F;
    result.values[5]  = 1.0F;
    result.values[10] = 1.0F;
    result.values[15] = 1.0F;
    return result;
}

mat4 multiply(const mat4& left, const mat4& right) {
    mat4 result{};
    for (int column = 0; column < 4; ++column) {
        for (int row = 0; row < 4; ++row) {
            for (int index = 0; index < 4; ++index) {
                result.values[column * 4 + row] += left.values[index * 4 + row] * right.values[column * 4 + index];
            }
        }
    }
    return result;
}

mat4 translation_matrix(float x, float y, float z) {
    mat4 result       = identity_matrix();
    result.values[12] = x;
    result.values[13] = y;
    result.values[14] = z;
    return result;
}

mat4 scale_matrix(float x, float y, float z) {
    mat4 result{};
    result.values[0]  = x;
    result.values[5]  = y;
    result.values[10] = z;
    result.values[15] = 1.0F;
    return result;
}

mat4 pose_matrix(const XrPosef& pose) {
    const float x  = pose.orientation.x;
    const float y  = pose.orientation.y;
    const float z  = pose.orientation.z;
    const float w  = pose.orientation.w;
    const float xx = x * x;
    const float yy = y * y;
    const float zz = z * z;
    const float xy = x * y;
    const float xz = x * z;
    const float yz = y * z;
    const float wx = w * x;
    const float wy = w * y;
    const float wz = w * z;

    mat4 result       = identity_matrix();
    result.values[0]  = 1.0F - 2.0F * (yy + zz);
    result.values[1]  = 2.0F * (xy + wz);
    result.values[2]  = 2.0F * (xz - wy);
    result.values[4]  = 2.0F * (xy - wz);
    result.values[5]  = 1.0F - 2.0F * (xx + zz);
    result.values[6]  = 2.0F * (yz + wx);
    result.values[8]  = 2.0F * (xz + wy);
    result.values[9]  = 2.0F * (yz - wx);
    result.values[10] = 1.0F - 2.0F * (xx + yy);
    result.values[12] = pose.position.x;
    result.values[13] = pose.position.y;
    result.values[14] = pose.position.z;
    return result;
}

mat4 inverse_rigid_transform(const mat4& matrix) {
    mat4 result       = identity_matrix();
    result.values[0]  = matrix.values[0];
    result.values[1]  = matrix.values[4];
    result.values[2]  = matrix.values[8];
    result.values[4]  = matrix.values[1];
    result.values[5]  = matrix.values[5];
    result.values[6]  = matrix.values[9];
    result.values[8]  = matrix.values[2];
    result.values[9]  = matrix.values[6];
    result.values[10] = matrix.values[10];

    const float x     = matrix.values[12];
    const float y     = matrix.values[13];
    const float z     = matrix.values[14];
    result.values[12] = -(result.values[0] * x + result.values[4] * y + result.values[8] * z);
    result.values[13] = -(result.values[1] * x + result.values[5] * y + result.values[9] * z);
    result.values[14] = -(result.values[2] * x + result.values[6] * y + result.values[10] * z);
    return result;
}

mat4 projection_matrix(const XrFovf& fov, float near_z, float far_z) {
    const float tangent_left   = std::tan(fov.angleLeft);
    const float tangent_right  = std::tan(fov.angleRight);
    const float tangent_down   = std::tan(fov.angleDown);
    const float tangent_up     = std::tan(fov.angleUp);
    const float tangent_width  = tangent_right - tangent_left;
    const float tangent_height = tangent_up - tangent_down;

    mat4 result{};
    result.values[0]  = 2.0F / tangent_width;
    result.values[5]  = 2.0F / tangent_height;
    result.values[8]  = (tangent_right + tangent_left) / tangent_width;
    result.values[9]  = (tangent_up + tangent_down) / tangent_height;
    result.values[10] = -(far_z + near_z) / (far_z - near_z);
    result.values[11] = -1.0F;
    result.values[14] = -(2.0F * far_z * near_z) / (far_z - near_z);
    return result;
}

GLuint compile_shader(GLenum type, const char* source) {
    const GLuint shader = glCreateShader(type);
    glShaderSource(shader, 1, &source, nullptr);
    glCompileShader(shader);
    GLint status = GL_FALSE;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
    if (status == GL_TRUE) {
        return shader;
    }
    GLint log_length = 0;
    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
    std::vector<GLchar> log(static_cast<std::size_t>(std::max(1, log_length)), '\0');
    glGetShaderInfoLog(shader, log_length, nullptr, log.data());
    std::cerr << "OpenXR Quest shader compilation failed: " << log.data() << '\n';
    glDeleteShader(shader);
    return 0;
}

// The compatibility plugin uses small local shaders rather than depending on
// ILLIXR's normal display backend, because this plugin owns the OpenXR session.
GLuint link_program(const char* vertex_source, const char* fragment_source) {
    const GLuint vertex_shader   = compile_shader(GL_VERTEX_SHADER, vertex_source);
    const GLuint fragment_shader = compile_shader(GL_FRAGMENT_SHADER, fragment_source);
    if (vertex_shader == 0 || fragment_shader == 0) {
        if (vertex_shader != 0) {
            glDeleteShader(vertex_shader);
        }
        if (fragment_shader != 0) {
            glDeleteShader(fragment_shader);
        }
        return 0;
    }
    const GLuint program = glCreateProgram();
    glAttachShader(program, vertex_shader);
    glAttachShader(program, fragment_shader);
    glLinkProgram(program);
    glDeleteShader(vertex_shader);
    glDeleteShader(fragment_shader);
    GLint status = GL_FALSE;
    glGetProgramiv(program, GL_LINK_STATUS, &status);
    if (status == GL_TRUE) {
        return program;
    }
    GLint log_length = 0;
    glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
    std::vector<GLchar> log(static_cast<std::size_t>(std::max(1, log_length)), '\0');
    glGetProgramInfoLog(program, log_length, nullptr, log.data());
    std::cerr << "OpenXR Quest shader program link failed: " << log.data() << '\n';
    glDeleteProgram(program);
    return 0;
}

GLuint create_panel_program() {
    constexpr const char* vertex_source   = R"GLSL(
        #version 330 core
        out vec2 uv;
        uniform mat4 uMvp;
        const vec3 positions[6] = vec3[6](
            vec3(-0.5, -0.5, 0.0), vec3( 0.5, -0.5, 0.0),
            vec3( 0.5,  0.5, 0.0), vec3(-0.5, -0.5, 0.0),
            vec3( 0.5,  0.5, 0.0), vec3(-0.5,  0.5, 0.0)
        );
        const vec2 texcoords[6] = vec2[6](
            vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(1.0, 1.0),
            vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 1.0)
        );
        void main() {
            gl_Position = uMvp * vec4(positions[gl_VertexID], 1.0);
            uv = texcoords[gl_VertexID];
        }
    )GLSL";
    constexpr const char* fragment_source = R"GLSL(
        #version 330 core
        in vec2 uv;
        out vec4 frag;
        uniform sampler2D uSource;
        uniform bool uFlipY;
        void main() {
            frag = texture(uSource, vec2(uv.x, uFlipY ? 1.0 - uv.y : uv.y));
        }
    )GLSL";
    return link_program(vertex_source, fragment_source);
}

GLuint create_overlay_program() {
    constexpr const char* vertex_source   = R"GLSL(
        #version 330 core
        layout(location = 0) in vec2 aPos;
        layout(location = 1) in vec4 aColor;
        uniform vec2 uSourceSize;
        out vec4 vColor;
        void main() {
            vec2 ndc = vec2(
                (aPos.x / max(uSourceSize.x, 1.0)) * 2.0 - 1.0,
                1.0 - (aPos.y / max(uSourceSize.y, 1.0)) * 2.0
            );
            gl_Position = vec4(ndc, 0.0, 1.0);
            vColor = aColor;
        }
    )GLSL";
    constexpr const char* fragment_source = R"GLSL(
        #version 330 core
        in vec4 vColor;
        out vec4 frag;
        void main() { frag = vColor; }
    )GLSL";
    return link_program(vertex_source, fragment_source);
}

GLuint create_modal_program() {
    constexpr const char* vertex_source   = R"GLSL(
        #version 330 core
        layout(location = 0) in vec2 aPos;
        layout(location = 1) in vec2 aUv;
        uniform vec2 uSourceSize;
        out vec2 vUv;
        void main() {
            vec2 ndc = vec2(
                (aPos.x / max(uSourceSize.x, 1.0)) * 2.0 - 1.0,
                1.0 - (aPos.y / max(uSourceSize.y, 1.0)) * 2.0
            );
            gl_Position = vec4(ndc, 0.0, 1.0);
            vUv = aUv;
        }
    )GLSL";
    constexpr const char* fragment_source = R"GLSL(
        #version 330 core
        in vec2 vUv;
        out vec4 frag;
        uniform sampler2D uModal;
        void main() { frag = texture(uModal, vUv); }
    )GLSL";
    return link_program(vertex_source, fragment_source);
}

void append_overlay_vertex(std::vector<float>* vertices, float x, float y, float red, float green, float blue, float alpha) {
    vertices->insert(vertices->end(),
                     {x, y, std::clamp(red / 255.0F, 0.0F, 1.0F), std::clamp(green / 255.0F, 0.0F, 1.0F),
                      std::clamp(blue / 255.0F, 0.0F, 1.0F), std::clamp(alpha, 0.0F, 1.0F)});
}

void append_overlay_triangle(std::vector<float>* vertices, float x0, float y0, float x1, float y1, float x2, float y2,
                             float red, float green, float blue, float alpha) {
    append_overlay_vertex(vertices, x0, y0, red, green, blue, alpha);
    append_overlay_vertex(vertices, x1, y1, red, green, blue, alpha);
    append_overlay_vertex(vertices, x2, y2, red, green, blue, alpha);
}

// Convert Boba's compact line/rectangle commands into transient GL triangles.
// Coordinates remain in source-image pixels so overlays track the rendered scene.
void append_overlay_command(std::vector<float>* vertices, const float* command) {
    const int   command_type = static_cast<int>(std::round(command[0]));
    const float radius       = std::max(command_type == 0 ? 0.5F : 1.0F, command[5]);
    const float alpha        = command[6];
    const float red          = command[7];
    const float green        = command[8];
    const float blue         = command[9];
    if (command_type == 1) {
        const float x0 = command[1] - radius;
        const float y0 = command[2] - radius;
        const float x1 = command[1] + radius;
        const float y1 = command[2] + radius;
        append_overlay_triangle(vertices, x0, y0, x1, y0, x1, y1, red, green, blue, alpha);
        append_overlay_triangle(vertices, x0, y0, x1, y1, x0, y1, red, green, blue, alpha);
        return;
    }
    if (command_type != 0) {
        return;
    }
    const float delta_x = command[3] - command[1];
    const float delta_y = command[4] - command[2];
    const float length  = std::sqrt(delta_x * delta_x + delta_y * delta_y);
    if (length <= 1.0e-4F) {
        return;
    }
    const float normal_x = -delta_y / length * radius;
    const float normal_y = delta_x / length * radius;
    append_overlay_triangle(vertices, command[1] + normal_x, command[2] + normal_y, command[3] + normal_x,
                            command[4] + normal_y, command[3] - normal_x, command[4] - normal_y, red, green, blue, alpha);
    append_overlay_triangle(vertices, command[1] + normal_x, command[2] + normal_y, command[3] - normal_x,
                            command[4] - normal_y, command[1] - normal_x, command[2] - normal_y, red, green, blue, alpha);
}

void draw_overlay_commands(const std::vector<float>& commands, GLuint program, GLuint vao, GLuint vbo,
                           GLint source_size_location, std::uint32_t source_width, std::uint32_t source_height) {
    if (commands.empty() || program == 0 || vao == 0 || vbo == 0) {
        return;
    }
    std::vector<float> vertices;
    const std::size_t  command_count = commands.size() / kOverlayCommandStrideFloats;
    vertices.reserve(command_count * 36);
    for (std::size_t command_index = 0; command_index < command_count; ++command_index) {
        append_overlay_command(&vertices, commands.data() + command_index * kOverlayCommandStrideFloats);
    }
    if (vertices.empty()) {
        return;
    }
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glUseProgram(program);
    glUniform2f(source_size_location, static_cast<float>(source_width), static_cast<float>(source_height));
    glBindVertexArray(vao);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(vertices.size() * sizeof(float)), vertices.data(), GL_STREAM_DRAW);
    glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(vertices.size() / 6));
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
    glUseProgram(0);
    glDisable(GL_BLEND);
}

void draw_modal_overlay(const ILLIXR::data_format::stereo_modal_overlay& modal, GLuint texture, GLuint program, GLuint vao,
                        GLuint vbo, GLint source_size_location, GLint texture_location, std::size_t eye_index,
                        std::uint32_t source_width, std::uint32_t source_height) {
    const bool eye_valid = eye_index == 0 ? modal.left_valid : modal.right_valid;
    if (!modal.visible || !eye_valid || modal.width == 0 || modal.height == 0 || texture == 0 || program == 0 || vao == 0 ||
        vbo == 0) {
        return;
    }
    const auto& quad       = eye_index == 0 ? modal.left_quad_pixels : modal.right_quad_pixels;
    const float vertices[] = {
        quad[0].x(), quad[0].y(), 0.0F, 0.0F, quad[1].x(), quad[1].y(), 1.0F, 0.0F, quad[2].x(), quad[2].y(), 1.0F, 1.0F,
        quad[0].x(), quad[0].y(), 0.0F, 0.0F, quad[2].x(), quad[2].y(), 1.0F, 1.0F, quad[3].x(), quad[3].y(), 0.0F, 1.0F,
    };
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glUseProgram(program);
    glUniform2f(source_size_location, static_cast<float>(source_width), static_cast<float>(source_height));
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture);
    glUniform1i(texture_location, 0);
    glBindVertexArray(vao);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STREAM_DRAW);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
    glBindTexture(GL_TEXTURE_2D, 0);
    glUseProgram(0);
    glDisable(GL_BLEND);
}

bool byte_range_valid(std::size_t mapping_size, std::uint64_t offset, std::uint64_t byte_count) {
    return offset <= mapping_size && byte_count <= mapping_size - static_cast<std::size_t>(offset);
}

bool read_generation(const std::uint8_t* data, std::size_t size, std::uint64_t offset, std::uint64_t* generation) {
    if (!byte_range_valid(size, offset, sizeof(*generation))) {
        return false;
    }
    std::memcpy(generation, data + offset, sizeof(*generation));
    return true;
}

} // namespace

namespace ILLIXR {

// ---- Shared-memory and plugin lifecycle -----------------------------------

openxr_quest_controller::mapped_file::~mapped_file() {
    reset();
}

void openxr_quest_controller::mapped_file::reset() {
    if (data != nullptr) {
        munmap(const_cast<std::uint8_t*>(data), size);
        data = nullptr;
        size = 0;
    }
    if (fd >= 0) {
        close(fd);
        fd = -1;
    }
    path.clear();
}

openxr_quest_controller::openxr_quest_controller(const std::string& name, phonebook* pb)
    : plugin{name, pb}
    , switchboard_{pb->lookup_impl<switchboard>()}
    , clock_{pb->lookup_impl<relative_clock>()}
    , stoplight_{pb->lookup_impl<stoplight>()}
    , controller_writer_{switchboard_->get_writer<controller_input>("quest_controller")}
    , view_writer_{switchboard_->get_writer<view_frame>("openxr_view")}
    , stereo_reader_{switchboard_->get_reader<stereo_frame>("stereo_frame")} {
    spdlogger(switchboard_->get_env_char("OPENXR_QUEST_CONTROLLER_LOG_LEVEL", "info"));
    log_input_ = switchboard_->get_env_bool("OPENXR_QUEST_CONTROLLER_LOG_INPUT", "true");
}

openxr_quest_controller::~openxr_quest_controller() {
    stop_requested_.store(true);
    if (worker_.joinable()) {
        worker_.join();
    }
    destroy_graphics();
}

void openxr_quest_controller::start() {
    initialize_graphics();
    plugin::start();

    stop_requested_.store(false);
    worker_ = std::thread([this]() {
        run();
    });
}

void openxr_quest_controller::stop() {
    stop_requested_.store(true);
    if (worker_.joinable()) {
        worker_.join();
    }
    destroy_graphics();
    plugin::stop();
}

void openxr_quest_controller::initialize_graphics() {
    std::string display_mode = switchboard_->get_env("ILLIXR_DISPLAY_MODE", "glfw");
    std::transform(display_mode.begin(), display_mode.end(), display_mode.begin(), [](unsigned char value) {
        return static_cast<char>(std::tolower(value));
    });
    if (display_mode != "none") {
        throw std::runtime_error{
            "openxr_quest_controller requires ILLIXR_DISPLAY_MODE=none because it owns the MVP OpenXR graphics session"};
    }

    if (XInitThreads() == 0) {
        plugin_logger_->warn("XInitThreads reported that Xlib thread support could not be initialized");
    }

    if (glfwInit() != GLFW_TRUE) {
        const char* description = nullptr;
        glfwGetError(&description);
        throw std::runtime_error{std::string{"glfwInit failed"} + (description ? ": " + std::string{description} : "")};
    }
    glfw_initialized_ = true;

    // A tiny hidden window supplies valid GLX handles; all visible output goes
    // to OpenXR swapchains rather than this desktop surface.
    glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
    glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    window_ = glfwCreateWindow(kWindowSize, kWindowSize, "ILLIXR Quest Controller", nullptr, nullptr);
    if (window_ == nullptr) {
        const char* description = nullptr;
        glfwGetError(&description);
        destroy_graphics();
        throw std::runtime_error{std::string{"glfwCreateWindow failed"} + (description ? ": " + std::string{description} : "")};
    }

    glfwMakeContextCurrent(window_);
    glfwSwapInterval(0);
    if (!resolve_glx_binding()) {
        glfwMakeContextCurrent(nullptr);
        destroy_graphics();
        throw std::runtime_error{"Failed to resolve the hidden OpenGL window's GLX handles"};
    }

    const GLubyte* gl_version = glGetString(GL_VERSION);
    plugin_logger_->info("Created hidden OpenGL context: {}",
                         gl_version ? reinterpret_cast<const char*>(gl_version) : "unknown version");
    glfwMakeContextCurrent(nullptr);
}

void openxr_quest_controller::destroy_graphics() {
    if (window_ != nullptr) {
        glfwDestroyWindow(window_);
        window_ = nullptr;
    }
    if (glfw_initialized_) {
        glfwTerminate();
        glfw_initialized_ = false;
    }

    x_display_     = nullptr;
    glx_fb_config_ = nullptr;
    glx_drawable_  = 0;
    glx_context_   = nullptr;
    visual_id_     = 0;
}

bool openxr_quest_controller::resolve_glx_binding() {
    x_display_    = glfwGetX11Display();
    glx_drawable_ = glfwGetGLXWindow(window_);
    glx_context_  = glfwGetGLXContext(window_);
    if (x_display_ == nullptr || glx_drawable_ == 0 || glx_context_ == nullptr) {
        plugin_logger_->error("Failed to get GLFW's native X11/GLX handles");
        return false;
    }

    unsigned int fb_config_id = 0;
    glXQueryDrawable(x_display_, glx_drawable_, GLX_FBCONFIG_ID, &fb_config_id);
    if (fb_config_id == 0) {
        plugin_logger_->error("GLX_FBCONFIG_ID query returned zero");
        return false;
    }

    const int    attribs[]    = {GLX_FBCONFIG_ID, static_cast<int>(fb_config_id), None};
    int          config_count = 0;
    GLXFBConfig* configs      = glXChooseFBConfig(x_display_, DefaultScreen(x_display_), attribs, &config_count);
    if (configs == nullptr || config_count < 1) {
        plugin_logger_->error("glXChooseFBConfig failed for GLX_FBCONFIG_ID={}", fb_config_id);
        return false;
    }

    glx_fb_config_           = configs[0];
    XVisualInfo* visual_info = glXGetVisualFromFBConfig(x_display_, glx_fb_config_);
    if (visual_info == nullptr) {
        XFree(configs);
        plugin_logger_->error("glXGetVisualFromFBConfig failed");
        return false;
    }

    visual_id_ = static_cast<std::uint32_t>(visual_info->visualid);
    XFree(visual_info);
    XFree(configs);
    return true;
}

// ---- OpenXR session lifecycle ---------------------------------------------

void openxr_quest_controller::run() {
    stoplight_->wait_for_ready();
    glfwMakeContextCurrent(window_);

    try {
        if (initialize_openxr()) {
            while (!stop_requested_.load() && !stoplight_->check_should_stop() && !exit_requested_) {
                if (!pump_events()) {
                    break;
                }
                if (!session_running_) {
                    std::this_thread::sleep_for(std::chrono::milliseconds{10});
                    continue;
                }
                if (!process_frame()) {
                    break;
                }
            }
        }
    } catch (const std::exception& error) {
        plugin_logger_->error("OpenXR controller thread failed: {}", error.what());
    }

    destroy_openxr();
    glfwMakeContextCurrent(nullptr);
}

bool openxr_quest_controller::initialize_openxr() {
    const char* runtime_json = std::getenv("XR_RUNTIME_JSON");
    plugin_logger_->info("Starting Quest controller input through the OpenXR loader (XR_RUNTIME_JSON={})",
                         runtime_json ? runtime_json : "loader default");

    std::uint32_t extension_count = 0;
    if (!check_xr(xrEnumerateInstanceExtensionProperties(nullptr, 0, &extension_count, nullptr),
                  "xrEnumerateInstanceExtensionProperties(count)")) {
        return false;
    }

    std::vector<XrExtensionProperties> extensions(extension_count);
    for (XrExtensionProperties& extension : extensions) {
        extension = make_xr_struct<XrExtensionProperties>(XR_TYPE_EXTENSION_PROPERTIES);
    }
    if (!check_xr(xrEnumerateInstanceExtensionProperties(nullptr, extension_count, &extension_count, extensions.data()),
                  "xrEnumerateInstanceExtensionProperties(list)")) {
        return false;
    }
    if (!has_extension(extensions, XR_KHR_OPENGL_ENABLE_EXTENSION_NAME)) {
        plugin_logger_->error("The active OpenXR runtime does not expose {}", XR_KHR_OPENGL_ENABLE_EXTENSION_NAME);
        return false;
    }

    const char*          enabled_extensions[] = {XR_KHR_OPENGL_ENABLE_EXTENSION_NAME};
    XrInstanceCreateInfo instance_info        = make_xr_struct<XrInstanceCreateInfo>(XR_TYPE_INSTANCE_CREATE_INFO);
    std::strncpy(instance_info.applicationInfo.applicationName, "ILLIXR Quest Controller", XR_MAX_APPLICATION_NAME_SIZE - 1);
    std::strncpy(instance_info.applicationInfo.engineName, "ILLIXR", XR_MAX_ENGINE_NAME_SIZE - 1);
    instance_info.applicationInfo.applicationVersion = 1;
    instance_info.applicationInfo.engineVersion      = 1;
    instance_info.applicationInfo.apiVersion         = XR_CURRENT_API_VERSION;
    instance_info.enabledExtensionCount              = 1;
    instance_info.enabledExtensionNames              = enabled_extensions;
    if (!check_xr(xrCreateInstance(&instance_info, &instance_), "xrCreateInstance")) {
        return false;
    }

    XrInstanceProperties instance_properties = make_xr_struct<XrInstanceProperties>(XR_TYPE_INSTANCE_PROPERTIES);
    if (check_xr(xrGetInstanceProperties(instance_, &instance_properties), "xrGetInstanceProperties")) {
        plugin_logger_->info("OpenXR runtime: {} {}.{}.{}", instance_properties.runtimeName,
                             XR_VERSION_MAJOR(instance_properties.runtimeVersion),
                             XR_VERSION_MINOR(instance_properties.runtimeVersion),
                             XR_VERSION_PATCH(instance_properties.runtimeVersion));
    }

    XrSystemGetInfo system_info = make_xr_struct<XrSystemGetInfo>(XR_TYPE_SYSTEM_GET_INFO);
    system_info.formFactor      = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
    if (!check_xr(xrGetSystem(instance_, &system_info, &system_id_), "xrGetSystem(HMD)")) {
        plugin_logger_->error("SteamVR did not expose a running HMD; connect ALVR and the Quest before launching ILLIXR");
        return false;
    }

    XrSystemProperties system_properties = make_xr_struct<XrSystemProperties>(XR_TYPE_SYSTEM_PROPERTIES);
    if (check_xr(xrGetSystemProperties(instance_, system_id_, &system_properties), "xrGetSystemProperties")) {
        plugin_logger_->info("OpenXR HMD: {}", system_properties.systemName);
    }

    if (!enumerate_view_configuration()) {
        return false;
    }

    PFN_xrGetOpenGLGraphicsRequirementsKHR get_graphics_requirements = nullptr;
    if (!check_xr(xrGetInstanceProcAddr(instance_, "xrGetOpenGLGraphicsRequirementsKHR",
                                        reinterpret_cast<PFN_xrVoidFunction*>(&get_graphics_requirements)),
                  "xrGetInstanceProcAddr(xrGetOpenGLGraphicsRequirementsKHR)")) {
        return false;
    }
    XrGraphicsRequirementsOpenGLKHR graphics_requirements =
        make_xr_struct<XrGraphicsRequirementsOpenGLKHR>(XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR);
    if (!check_xr(get_graphics_requirements(instance_, system_id_, &graphics_requirements),
                  "xrGetOpenGLGraphicsRequirementsKHR")) {
        return false;
    }

    XrGraphicsBindingOpenGLXlibKHR graphics_binding =
        make_xr_struct<XrGraphicsBindingOpenGLXlibKHR>(XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR);
    graphics_binding.xDisplay    = x_display_;
    graphics_binding.visualid    = visual_id_;
    graphics_binding.glxFBConfig = glx_fb_config_;
    graphics_binding.glxDrawable = glx_drawable_;
    graphics_binding.glxContext  = glx_context_;

    XrSessionCreateInfo session_info = make_xr_struct<XrSessionCreateInfo>(XR_TYPE_SESSION_CREATE_INFO);
    session_info.next                = &graphics_binding;
    session_info.systemId            = system_id_;
    if (!check_xr(xrCreateSession(instance_, &session_info, &session_), "xrCreateSession")) {
        return false;
    }

    std::uint32_t blend_mode_count = 0;
    if (check_xr(
            xrEnumerateEnvironmentBlendModes(instance_, system_id_, view_configuration_type_, 0, &blend_mode_count, nullptr),
            "xrEnumerateEnvironmentBlendModes(count)") &&
        blend_mode_count > 0) {
        std::vector<XrEnvironmentBlendMode> blend_modes(blend_mode_count);
        if (check_xr(xrEnumerateEnvironmentBlendModes(instance_, system_id_, view_configuration_type_, blend_mode_count,
                                                      &blend_mode_count, blend_modes.data()),
                     "xrEnumerateEnvironmentBlendModes(list)")) {
            blend_mode_       = blend_modes.front();
            const auto opaque = std::find(blend_modes.begin(), blend_modes.end(), XR_ENVIRONMENT_BLEND_MODE_OPAQUE);
            if (opaque != blend_modes.end()) {
                blend_mode_ = *opaque;
            }
        }
    }

    // Actions must be attached before the session begins, while swapchains and
    // GL renderer resources are created once for the lifetime of this session.
    if (!create_actions()) {
        return false;
    }

    XrReferenceSpaceCreateInfo local_space_info =
        make_xr_struct<XrReferenceSpaceCreateInfo>(XR_TYPE_REFERENCE_SPACE_CREATE_INFO);
    local_space_info.referenceSpaceType                 = XR_REFERENCE_SPACE_TYPE_LOCAL;
    local_space_info.poseInReferenceSpace.orientation.w = 1.0F;
    if (!check_xr(xrCreateReferenceSpace(session_, &local_space_info, &local_space_), "xrCreateReferenceSpace(LOCAL)")) {
        return false;
    }

    if (!create_swapchains() || !initialize_stereo_renderer()) {
        return false;
    }

    plugin_logger_->info(
        "OpenXR Quest input/display session initialized; waiting for SteamVR session focus and stereo_frame data");
    return true;
}

// ---- Controller actions and interaction profiles --------------------------

bool openxr_quest_controller::create_actions() {
    XrActionSetCreateInfo action_set_info = make_xr_struct<XrActionSetCreateInfo>(XR_TYPE_ACTION_SET_CREATE_INFO);
    std::strncpy(action_set_info.actionSetName, "quest_controller", XR_MAX_ACTION_SET_NAME_SIZE - 1);
    std::strncpy(action_set_info.localizedActionSetName, "Quest Controller Input", XR_MAX_LOCALIZED_ACTION_SET_NAME_SIZE - 1);
    if (!check_xr(xrCreateActionSet(instance_, &action_set_info, &action_set_), "xrCreateActionSet")) {
        return false;
    }

    if (!check_xr(xrStringToPath(instance_, "/user/hand/left", &hand_paths_[0]), "xrStringToPath(left hand)") ||
        !check_xr(xrStringToPath(instance_, "/user/hand/right", &hand_paths_[1]), "xrStringToPath(right hand)")) {
        return false;
    }

    if (!create_action(XR_ACTION_TYPE_POSE_INPUT, "grip_pose", "Grip Pose", &grip_pose_action_) ||
        !create_action(XR_ACTION_TYPE_POSE_INPUT, "aim_pose", "Aim Pose", &aim_pose_action_) ||
        !create_action(XR_ACTION_TYPE_BOOLEAN_INPUT, "trigger_click", "Trigger Click", &trigger_click_action_) ||
        !create_action(XR_ACTION_TYPE_FLOAT_INPUT, "trigger_value", "Trigger Value", &trigger_value_action_) ||
        !create_action(XR_ACTION_TYPE_BOOLEAN_INPUT, "primary_click", "Primary Button", &primary_click_action_) ||
        !create_action(XR_ACTION_TYPE_BOOLEAN_INPUT, "secondary_click", "Secondary Button", &secondary_click_action_) ||
        !create_action(XR_ACTION_TYPE_BOOLEAN_INPUT, "thumbstick_click", "Thumbstick Click", &thumbstick_click_action_) ||
        !create_action(XR_ACTION_TYPE_VECTOR2F_INPUT, "thumbstick_axis", "Thumbstick Axis", &thumbstick_axis_action_) ||
        !create_action(XR_ACTION_TYPE_FLOAT_INPUT, "squeeze_value", "Squeeze Value", &squeeze_value_action_)) {
        return false;
    }

    if (!suggest_bindings()) {
        return false;
    }

    XrSessionActionSetsAttachInfo attach_info =
        make_xr_struct<XrSessionActionSetsAttachInfo>(XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO);
    attach_info.countActionSets = 1;
    attach_info.actionSets      = &action_set_;
    if (!check_xr(xrAttachSessionActionSets(session_, &attach_info), "xrAttachSessionActionSets")) {
        return false;
    }

    for (std::size_t hand_index = 0; hand_index < hand_paths_.size(); ++hand_index) {
        const char* grip_label = hand_index == 0 ? "xrCreateActionSpace(grip left)" : "xrCreateActionSpace(grip right)";
        const char* aim_label  = hand_index == 0 ? "xrCreateActionSpace(aim left)" : "xrCreateActionSpace(aim right)";
        if (!create_action_space(grip_pose_action_, hand_paths_[hand_index], &grip_spaces_[hand_index], grip_label) ||
            !create_action_space(aim_pose_action_, hand_paths_[hand_index], &aim_spaces_[hand_index], aim_label)) {
            return false;
        }
    }
    return true;
}

bool openxr_quest_controller::create_action(XrActionType type, const char* name, const char* localized_name, XrAction* action) {
    XrActionCreateInfo action_info = make_xr_struct<XrActionCreateInfo>(XR_TYPE_ACTION_CREATE_INFO);
    std::strncpy(action_info.actionName, name, XR_MAX_ACTION_NAME_SIZE - 1);
    std::strncpy(action_info.localizedActionName, localized_name, XR_MAX_LOCALIZED_ACTION_NAME_SIZE - 1);
    action_info.actionType          = type;
    action_info.countSubactionPaths = static_cast<std::uint32_t>(hand_paths_.size());
    action_info.subactionPaths      = hand_paths_.data();
    return check_xr(xrCreateAction(action_set_, &action_info, action), "xrCreateAction");
}

bool openxr_quest_controller::suggest_bindings() {
    // Suggest a portable core for all known profiles, then add profile-specific
    // analog controls and the Oculus Touch face-button/thumbstick layout.
    const char* profiles[] = {kSimpleControllerProfile, kOculusTouchProfile, kHtcViveProfile, kValveIndexProfile,
                              kMicrosoftMotionProfile};

    for (const char* profile : profiles) {
        std::vector<XrActionSuggestedBinding> bindings;
        if (!add_binding(&bindings, grip_pose_action_, "/user/hand/left/input/grip/pose") ||
            !add_binding(&bindings, grip_pose_action_, "/user/hand/right/input/grip/pose") ||
            !add_binding(&bindings, aim_pose_action_, "/user/hand/left/input/aim/pose") ||
            !add_binding(&bindings, aim_pose_action_, "/user/hand/right/input/aim/pose")) {
            return false;
        }

        if (std::strcmp(profile, kSimpleControllerProfile) == 0) {
            if (!add_binding(&bindings, trigger_click_action_, "/user/hand/left/input/select/click") ||
                !add_binding(&bindings, trigger_click_action_, "/user/hand/right/input/select/click")) {
                return false;
            }
        } else {
            if (!add_binding(&bindings, trigger_click_action_, "/user/hand/left/input/trigger/value") ||
                !add_binding(&bindings, trigger_click_action_, "/user/hand/right/input/trigger/value") ||
                !add_binding(&bindings, trigger_value_action_, "/user/hand/left/input/trigger/value") ||
                !add_binding(&bindings, trigger_value_action_, "/user/hand/right/input/trigger/value") ||
                !add_binding(&bindings, squeeze_value_action_, "/user/hand/left/input/squeeze/value") ||
                !add_binding(&bindings, squeeze_value_action_, "/user/hand/right/input/squeeze/value")) {
                return false;
            }
        }

        if (std::strcmp(profile, kOculusTouchProfile) == 0) {
            if (!add_binding(&bindings, primary_click_action_, "/user/hand/left/input/x/click") ||
                !add_binding(&bindings, primary_click_action_, "/user/hand/right/input/a/click") ||
                !add_binding(&bindings, secondary_click_action_, "/user/hand/left/input/y/click") ||
                !add_binding(&bindings, secondary_click_action_, "/user/hand/right/input/b/click") ||
                !add_binding(&bindings, thumbstick_click_action_, "/user/hand/left/input/thumbstick/click") ||
                !add_binding(&bindings, thumbstick_click_action_, "/user/hand/right/input/thumbstick/click") ||
                !add_binding(&bindings, thumbstick_axis_action_, "/user/hand/left/input/thumbstick") ||
                !add_binding(&bindings, thumbstick_axis_action_, "/user/hand/right/input/thumbstick")) {
                return false;
            }
        }

        if (!suggest_profile_bindings(profile, bindings)) {
            return false;
        }
    }
    return true;
}

bool openxr_quest_controller::add_binding(std::vector<XrActionSuggestedBinding>* bindings, XrAction action,
                                          const char* path_string_value) {
    XrPath path = XR_NULL_PATH;
    if (!check_xr(xrStringToPath(instance_, path_string_value, &path), path_string_value)) {
        return false;
    }
    bindings->push_back({action, path});
    return true;
}

bool openxr_quest_controller::suggest_profile_bindings(const char*                                  profile_string,
                                                       const std::vector<XrActionSuggestedBinding>& bindings) {
    XrPath profile_path = XR_NULL_PATH;
    if (!check_xr(xrStringToPath(instance_, profile_string, &profile_path), profile_string)) {
        return false;
    }

    XrInteractionProfileSuggestedBinding suggested =
        make_xr_struct<XrInteractionProfileSuggestedBinding>(XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING);
    suggested.interactionProfile     = profile_path;
    suggested.countSuggestedBindings = static_cast<std::uint32_t>(bindings.size());
    suggested.suggestedBindings      = bindings.data();

    const XrResult result = xrSuggestInteractionProfileBindings(instance_, &suggested);
    if (result == XR_ERROR_PATH_UNSUPPORTED || result == XR_ERROR_PATH_INVALID) {
        plugin_logger_->debug("OpenXR runtime does not support interaction profile {}", profile_string);
        return true;
    }
    return check_xr(result, "xrSuggestInteractionProfileBindings");
}

bool openxr_quest_controller::create_action_space(XrAction action, XrPath hand_path, XrSpace* space, const char* label) {
    XrActionSpaceCreateInfo space_info         = make_xr_struct<XrActionSpaceCreateInfo>(XR_TYPE_ACTION_SPACE_CREATE_INFO);
    space_info.action                          = action;
    space_info.subactionPath                   = hand_path;
    space_info.poseInActionSpace.orientation.w = 1.0F;
    return check_xr(xrCreateActionSpace(session_, &space_info, space), label);
}

bool openxr_quest_controller::pump_events() {
    XrEventDataBuffer event = make_xr_struct<XrEventDataBuffer>(XR_TYPE_EVENT_DATA_BUFFER);
    for (;;) {
        const XrResult result = xrPollEvent(instance_, &event);
        if (result == XR_EVENT_UNAVAILABLE) {
            return true;
        }
        if (!check_xr(result, "xrPollEvent")) {
            return false;
        }

        switch (event.type) {
        case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: {
            const auto* changed = reinterpret_cast<const XrEventDataSessionStateChanged*>(&event);
            session_state_      = changed->state;
            plugin_logger_->info("OpenXR session state -> {}", session_state_label(session_state_));

            if (session_state_ == XR_SESSION_STATE_READY && !session_running_) {
                XrSessionBeginInfo begin_info           = make_xr_struct<XrSessionBeginInfo>(XR_TYPE_SESSION_BEGIN_INFO);
                begin_info.primaryViewConfigurationType = view_configuration_type_;
                if (!check_xr(xrBeginSession(session_, &begin_info), "xrBeginSession")) {
                    return false;
                }
                session_running_            = true;
                interaction_profiles_dirty_ = true;
            } else if (session_state_ == XR_SESSION_STATE_STOPPING && session_running_) {
                if (!check_xr(xrEndSession(session_), "xrEndSession")) {
                    return false;
                }
                session_running_ = false;
            } else if (session_state_ == XR_SESSION_STATE_EXITING || session_state_ == XR_SESSION_STATE_LOSS_PENDING) {
                exit_requested_ = true;
            }
            break;
        }
        case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED:
            interaction_profiles_dirty_ = true;
            break;
        case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING:
            plugin_logger_->warn("OpenXR runtime reported instance loss pending");
            exit_requested_ = true;
            break;
        case XR_TYPE_EVENT_DATA_EVENTS_LOST: {
            const auto* lost = reinterpret_cast<const XrEventDataEventsLost*>(&event);
            plugin_logger_->warn("OpenXR runtime reported {} lost events", lost->lostEventCount);
            break;
        }
        default:
            break;
        }

        event = make_xr_struct<XrEventDataBuffer>(XR_TYPE_EVENT_DATA_BUFFER);
    }
}

// ---- Per-frame input sampling and compositor submission -------------------

bool openxr_quest_controller::process_frame() {
    XrFrameWaitInfo wait_info   = make_xr_struct<XrFrameWaitInfo>(XR_TYPE_FRAME_WAIT_INFO);
    XrFrameState    frame_state = make_xr_struct<XrFrameState>(XR_TYPE_FRAME_STATE);
    if (!check_xr(xrWaitFrame(session_, &wait_info, &frame_state), "xrWaitFrame")) {
        return false;
    }

    XrFrameBeginInfo begin_info = make_xr_struct<XrFrameBeginInfo>(XR_TYPE_FRAME_BEGIN_INFO);
    if (!check_xr(xrBeginFrame(session_, &begin_info), "xrBeginFrame")) {
        return false;
    }

    bool              sample_ok = true;
    XrActiveActionSet active_action_set{action_set_, XR_NULL_PATH};
    XrActionsSyncInfo sync_info     = make_xr_struct<XrActionsSyncInfo>(XR_TYPE_ACTIONS_SYNC_INFO);
    sync_info.countActiveActionSets = 1;
    sync_info.activeActionSets      = &active_action_set;
    if (!check_xr(xrSyncActions(session_, &sync_info), "xrSyncActions")) {
        sample_ok = false;
    }

    if (sample_ok && interaction_profiles_dirty_) {
        refresh_interaction_profiles();
    }

    // Controller and eye events intentionally share one sequence number and
    // predicted display time so Boba never has to correlate independent topics.
    controller_input input;
    input.sequence       = sequence_;
    input.sample_time    = clock_->now();
    input.xr_sample_time = static_cast<std::int64_t>(frame_state.predictedDisplayTime);
    if (sample_ok &&
        (!query_hand(0, frame_state.predictedDisplayTime, &input.left) ||
         !query_hand(1, frame_state.predictedDisplayTime, &input.right))) {
        sample_ok = false;
    }

    view_frame views;
    views.sequence                    = sequence_;
    views.sample_time                 = input.sample_time;
    views.xr_sample_time              = static_cast<std::int64_t>(frame_state.predictedDisplayTime);
    views.xr_predicted_display_period = static_cast<std::int64_t>(frame_state.predictedDisplayPeriod);
    views.should_render               = frame_state.shouldRender == XR_TRUE;
    if (sample_ok && !query_views(frame_state.predictedDisplayTime, &views)) {
        sample_ok = false;
    }

    // Input continues to publish even before Boba produces its first image. In
    // that case xrEndFrame submits zero layers, which is valid OpenXR behavior.
    update_stereo_frame();
    XrCompositionLayerProjection                    projection_layer{};
    std::array<XrCompositionLayerProjectionView, 2> projection_views{};
    const XrCompositionLayerBaseHeader*             submitted_layer = nullptr;
    if (sample_ok && views.should_render && active_stereo_frame_valid_ && located_views_valid_) {
        if (render_projection_layer(&projection_layer, &projection_views)) {
            submitted_layer = reinterpret_cast<const XrCompositionLayerBaseHeader*>(&projection_layer);
        } else {
            sample_ok = false;
        }
    }

    XrFrameEndInfo end_info       = make_xr_struct<XrFrameEndInfo>(XR_TYPE_FRAME_END_INFO);
    end_info.displayTime          = frame_state.predictedDisplayTime;
    end_info.environmentBlendMode = blend_mode_;
    end_info.layerCount           = submitted_layer == nullptr ? 0U : 1U;
    end_info.layers               = submitted_layer == nullptr ? nullptr : &submitted_layer;
    if (!check_xr(xrEndFrame(session_, &end_info), "xrEndFrame")) {
        return false;
    }
    if (!sample_ok) {
        return false;
    }

    log_input_changes(input);
    controller_writer_.put(controller_writer_.allocate(input));
    view_writer_.put(view_writer_.allocate(views));
    ++sequence_;
    return true;
}

bool openxr_quest_controller::enumerate_view_configuration() {
    std::uint32_t view_count = 0;
    if (!check_xr(xrEnumerateViewConfigurationViews(instance_, system_id_, view_configuration_type_, 0, &view_count, nullptr),
                  "xrEnumerateViewConfigurationViews(count)")) {
        return false;
    }
    if (view_count != view_configuration_views_.size()) {
        plugin_logger_->error("PRIMARY_STEREO reported {} views; this MVP requires exactly two", view_count);
        return false;
    }
    for (XrViewConfigurationView& view : view_configuration_views_) {
        view = make_xr_struct<XrViewConfigurationView>(XR_TYPE_VIEW_CONFIGURATION_VIEW);
    }
    if (!check_xr(xrEnumerateViewConfigurationViews(instance_, system_id_, view_configuration_type_, view_count, &view_count,
                                                    view_configuration_views_.data()),
                  "xrEnumerateViewConfigurationViews(list)")) {
        return false;
    }
    plugin_logger_->info(
        "OpenXR recommended eye sizes: left={}x{}, right={}x{}", view_configuration_views_[0].recommendedImageRectWidth,
        view_configuration_views_[0].recommendedImageRectHeight, view_configuration_views_[1].recommendedImageRectWidth,
        view_configuration_views_[1].recommendedImageRectHeight);
    return true;
}

bool openxr_quest_controller::create_swapchains() {
    std::uint32_t format_count = 0;
    if (!check_xr(xrEnumerateSwapchainFormats(session_, 0, &format_count, nullptr), "xrEnumerateSwapchainFormats(count)")) {
        return false;
    }
    std::vector<std::int64_t> formats(format_count);
    if (!check_xr(xrEnumerateSwapchainFormats(session_, format_count, &format_count, formats.data()),
                  "xrEnumerateSwapchainFormats(list)")) {
        return false;
    }
    const std::int64_t preferred_formats[] = {
        static_cast<std::int64_t>(GL_SRGB8_ALPHA8),
        static_cast<std::int64_t>(GL_RGBA8),
    };
    swapchain_format_ = 0;
    for (const std::int64_t candidate : preferred_formats) {
        if (std::find(formats.begin(), formats.end(), candidate) != formats.end()) {
            swapchain_format_ = candidate;
            break;
        }
    }
    if (swapchain_format_ == 0 && !formats.empty()) {
        swapchain_format_ = formats.front();
    }
    if (swapchain_format_ == 0) {
        plugin_logger_->error("The OpenXR runtime did not expose a usable OpenGL swapchain format");
        return false;
    }

    for (std::size_t eye_index = 0; eye_index < swapchain_views_.size(); ++eye_index) {
        swapchain_view&                destination   = swapchain_views_[eye_index];
        const XrViewConfigurationView& configuration = view_configuration_views_[eye_index];
        destination.width                            = configuration.recommendedImageRectWidth;
        destination.height                           = configuration.recommendedImageRectHeight;

        XrSwapchainCreateInfo create_info = make_xr_struct<XrSwapchainCreateInfo>(XR_TYPE_SWAPCHAIN_CREATE_INFO);
        create_info.usageFlags            = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_SAMPLED_BIT;
        create_info.format                = swapchain_format_;
        create_info.sampleCount           = 1;
        create_info.width                 = destination.width;
        create_info.height                = destination.height;
        create_info.faceCount             = 1;
        create_info.arraySize             = 1;
        create_info.mipCount              = 1;
        if (!check_xr(xrCreateSwapchain(session_, &create_info, &destination.handle), "xrCreateSwapchain(eye)")) {
            return false;
        }

        std::uint32_t image_count = 0;
        if (!check_xr(xrEnumerateSwapchainImages(destination.handle, 0, &image_count, nullptr),
                      "xrEnumerateSwapchainImages(count)")) {
            return false;
        }
        destination.images.resize(image_count);
        for (XrSwapchainImageOpenGLKHR& image : destination.images) {
            image = make_xr_struct<XrSwapchainImageOpenGLKHR>(XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR);
        }
        if (!check_xr(xrEnumerateSwapchainImages(destination.handle, image_count, &image_count,
                                                 reinterpret_cast<XrSwapchainImageBaseHeader*>(destination.images.data())),
                      "xrEnumerateSwapchainImages(list)")) {
            return false;
        }
        for (const XrSwapchainImageOpenGLKHR& image : destination.images) {
            glBindTexture(GL_TEXTURE_2D, image.image);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        }
    }
    glBindTexture(GL_TEXTURE_2D, 0);
    plugin_logger_->info("Created OpenXR eye swapchains: left={}x{}, right={}x{}, format={}", swapchain_views_[0].width,
                         swapchain_views_[0].height, swapchain_views_[1].width, swapchain_views_[1].height, swapchain_format_);
    return true;
}

// ---- Boba shared-ring upload and OpenGL drawing ----------------------------

void openxr_quest_controller::destroy_swapchains() {
    for (swapchain_view& view : swapchain_views_) {
        if (view.handle != XR_NULL_HANDLE) {
            xrDestroySwapchain(view.handle);
            view.handle = XR_NULL_HANDLE;
        }
        view.images.clear();
        view.width  = 0;
        view.height = 0;
    }
    swapchain_format_ = 0;
}

bool openxr_quest_controller::initialize_stereo_renderer() {
    panel_program_   = create_panel_program();
    overlay_program_ = create_overlay_program();
    modal_program_   = create_modal_program();
    if (panel_program_ == 0 || overlay_program_ == 0 || modal_program_ == 0) {
        plugin_logger_->error("Failed to create the OpenGL programs used for Quest stereo submission");
        return false;
    }

    panel_source_location_        = glGetUniformLocation(panel_program_, "uSource");
    panel_mvp_location_           = glGetUniformLocation(panel_program_, "uMvp");
    panel_flip_y_location_        = glGetUniformLocation(panel_program_, "uFlipY");
    overlay_source_size_location_ = glGetUniformLocation(overlay_program_, "uSourceSize");
    modal_source_size_location_   = glGetUniformLocation(modal_program_, "uSourceSize");
    modal_texture_location_       = glGetUniformLocation(modal_program_, "uModal");

    glGenFramebuffers(1, &framebuffer_);
    glGenVertexArrays(1, &panel_vao_);

    glGenVertexArrays(1, &overlay_vao_);
    glGenBuffers(1, &overlay_vbo_);
    glBindVertexArray(overlay_vao_);
    glBindBuffer(GL_ARRAY_BUFFER, overlay_vbo_);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, static_cast<GLsizei>(6 * sizeof(float)), reinterpret_cast<void*>(0));
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, static_cast<GLsizei>(6 * sizeof(float)),
                          reinterpret_cast<void*>(2 * sizeof(float)));

    glGenVertexArrays(1, &modal_vao_);
    glGenBuffers(1, &modal_vbo_);
    glBindVertexArray(modal_vao_);
    glBindBuffer(GL_ARRAY_BUFFER, modal_vbo_);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, static_cast<GLsizei>(4 * sizeof(float)), reinterpret_cast<void*>(0));
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, static_cast<GLsizei>(4 * sizeof(float)),
                          reinterpret_cast<void*>(2 * sizeof(float)));
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    glGenTextures(static_cast<GLsizei>(modal_textures_.size()), modal_textures_.data());
    for (const GLuint texture : modal_textures_) {
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    }
    glBindTexture(GL_TEXTURE_2D, 0);

    if (framebuffer_ == 0 || panel_vao_ == 0 || overlay_vao_ == 0 || overlay_vbo_ == 0 || modal_vao_ == 0 || modal_vbo_ == 0 ||
        modal_textures_[0] == 0 || modal_textures_[1] == 0) {
        plugin_logger_->error("Failed to allocate OpenGL resources for Quest stereo submission");
        return false;
    }
    return true;
}

void openxr_quest_controller::destroy_stereo_renderer() {
    for (std::array<GLuint, 2>& texture_set : source_textures_) {
        glDeleteTextures(static_cast<GLsizei>(texture_set.size()), texture_set.data());
        texture_set = {};
    }
    glDeleteTextures(static_cast<GLsizei>(modal_textures_.size()), modal_textures_.data());
    modal_textures_ = {};
    if (overlay_vbo_ != 0) {
        glDeleteBuffers(1, &overlay_vbo_);
        overlay_vbo_ = 0;
    }
    if (modal_vbo_ != 0) {
        glDeleteBuffers(1, &modal_vbo_);
        modal_vbo_ = 0;
    }
    if (panel_vao_ != 0) {
        glDeleteVertexArrays(1, &panel_vao_);
        panel_vao_ = 0;
    }
    if (overlay_vao_ != 0) {
        glDeleteVertexArrays(1, &overlay_vao_);
        overlay_vao_ = 0;
    }
    if (modal_vao_ != 0) {
        glDeleteVertexArrays(1, &modal_vao_);
        modal_vao_ = 0;
    }
    if (framebuffer_ != 0) {
        glDeleteFramebuffers(1, &framebuffer_);
        framebuffer_ = 0;
    }
    if (panel_program_ != 0) {
        glDeleteProgram(panel_program_);
        panel_program_ = 0;
    }
    if (overlay_program_ != 0) {
        glDeleteProgram(overlay_program_);
        overlay_program_ = 0;
    }
    if (modal_program_ != 0) {
        glDeleteProgram(modal_program_);
        modal_program_ = 0;
    }

    pixel_mapping_.reset();
    overlay_mapping_.reset();
    modal_mapping_.reset();
    active_texture_set_        = -1;
    source_width_              = 0;
    source_height_             = 0;
    active_stereo_frame_id_    = 0;
    active_stereo_frame_valid_ = false;
    active_overlay_commands_   = {};
    active_modal_              = {};
    world_panel_model_         = {};
    world_panel_initialized_   = false;
}

bool openxr_quest_controller::ensure_mapped(const std::string& path, mapped_file* mapping) {
    if (path.empty()) {
        mapping->reset();
        return false;
    }
    if (mapping->data != nullptr && mapping->path == path) {
        return true;
    }
    mapping->reset();
    const int fd = open(path.c_str(), O_RDONLY);
    if (fd < 0) {
        return false;
    }
    struct stat status{};
    if (fstat(fd, &status) != 0 || status.st_size <= 0) {
        close(fd);
        return false;
    }
    void* data = mmap(nullptr, static_cast<std::size_t>(status.st_size), PROT_READ, MAP_SHARED, fd, 0);
    if (data == MAP_FAILED) {
        close(fd);
        return false;
    }
    mapping->fd   = fd;
    mapping->data = static_cast<const std::uint8_t*>(data);
    mapping->size = static_cast<std::size_t>(status.st_size);
    mapping->path = path;
    return true;
}

bool openxr_quest_controller::ensure_source_textures(std::uint32_t width, std::uint32_t height) {
    if (width == 0 || height == 0 || width > 8192 || height > 8192) {
        return false;
    }
    if (source_width_ == width && source_height_ == height && source_textures_[0][0] != 0 && source_textures_[0][1] != 0 &&
        source_textures_[1][0] != 0 && source_textures_[1][1] != 0) {
        return true;
    }

    while (glGetError() != GL_NO_ERROR) { }
    for (std::array<GLuint, 2>& texture_set : source_textures_) {
        glDeleteTextures(static_cast<GLsizei>(texture_set.size()), texture_set.data());
        texture_set = {};
        glGenTextures(static_cast<GLsizei>(texture_set.size()), texture_set.data());
        for (const GLuint texture : texture_set) {
            glBindTexture(GL_TEXTURE_2D, texture);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, static_cast<GLsizei>(width), static_cast<GLsizei>(height), 0, GL_RGBA,
                         GL_UNSIGNED_BYTE, nullptr);
        }
    }
    glBindTexture(GL_TEXTURE_2D, 0);
    source_width_              = width;
    source_height_             = height;
    active_texture_set_        = -1;
    active_stereo_frame_valid_ = false;
    world_panel_initialized_   = false;
    return source_textures_[0][0] != 0 && source_textures_[0][1] != 0 && source_textures_[1][0] != 0 &&
        source_textures_[1][1] != 0 && glGetError() == GL_NO_ERROR;
}

bool openxr_quest_controller::update_stereo_frame() {
    const auto frame = stereo_reader_.get_ro_nullable();
    if (frame == nullptr || frame->source_frame_id == 0 || frame->source_frame_id <= active_stereo_frame_id_) {
        return false;
    }
    if (frame->format != data_format::stereo_pixel_format::rgba8_unorm || frame->left.width != frame->right.width ||
        frame->left.height != frame->right.height || frame->left.width == 0 || frame->left.height == 0 ||
        frame->left.width > 8192 || frame->left.height > 8192 || frame->left.row_stride_bytes < frame->left.width * 4U ||
        frame->right.row_stride_bytes < frame->right.width * 4U || frame->left.row_stride_bytes % 4U != 0 ||
        frame->right.row_stride_bytes % 4U != 0) {
        plugin_logger_->warn("Rejected malformed stereo_frame id={}", frame->source_frame_id);
        return false;
    }
    if (!ensure_mapped(frame->pixel_buffer_path, &pixel_mapping_)) {
        return false;
    }

    const auto image_required_bytes = [](const data_format::stereo_shared_image& image) -> std::uint64_t {
        return static_cast<std::uint64_t>(image.height - 1U) * image.row_stride_bytes +
            static_cast<std::uint64_t>(image.width) * 4U;
    };
    const std::uint64_t left_required_bytes  = image_required_bytes(frame->left);
    const std::uint64_t right_required_bytes = image_required_bytes(frame->right);
    if (frame->left.byte_count < left_required_bytes || frame->right.byte_count < right_required_bytes ||
        !byte_range_valid(pixel_mapping_.size, frame->left.byte_offset, left_required_bytes) ||
        !byte_range_valid(pixel_mapping_.size, frame->right.byte_offset, right_required_bytes)) {
        plugin_logger_->warn("Rejected out-of-range stereo_frame id={}", frame->source_frame_id);
        return false;
    }

    std::uint64_t pixel_generation = 0;
    if (!read_generation(pixel_mapping_.data, pixel_mapping_.size, frame->pixel_generation_offset, &pixel_generation) ||
        pixel_generation != frame->source_frame_id || !ensure_source_textures(frame->left.width, frame->left.height)) {
        return false;
    }

    // Upload into the inactive texture set. It becomes visible only after all
    // pixel and optional overlay/modal generations have been revalidated.
    const int candidate_texture_set = active_texture_set_ == 0 ? 1 : 0;
    while (glGetError() != GL_NO_ERROR) { }
    const data_format::stereo_shared_image* images[] = {&frame->left, &frame->right};
    for (std::size_t eye_index = 0; eye_index < 2; ++eye_index) {
        const data_format::stereo_shared_image& image = *images[eye_index];
        glBindTexture(GL_TEXTURE_2D, source_textures_[candidate_texture_set][eye_index]);
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(image.row_stride_bytes / 4U));
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, static_cast<GLsizei>(image.width), static_cast<GLsizei>(image.height), GL_RGBA,
                        GL_UNSIGNED_BYTE, pixel_mapping_.data + image.byte_offset);
    }
    glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
    glBindTexture(GL_TEXTURE_2D, 0);
    if (glGetError() != GL_NO_ERROR) {
        plugin_logger_->warn("OpenGL rejected stereo_frame upload id={}", frame->source_frame_id);
        return false;
    }

    std::array<std::vector<float>, 2> candidate_overlay_commands;
    bool                              overlay_generation_required = false;
    if (!frame->overlay_buffer_path.empty()) {
        if (!ensure_mapped(frame->overlay_buffer_path, &overlay_mapping_)) {
            return false;
        }
        std::uint64_t overlay_generation = 0;
        if (!read_generation(overlay_mapping_.data, overlay_mapping_.size, frame->overlay_generation_offset,
                             &overlay_generation) ||
            overlay_generation != frame->source_frame_id) {
            return false;
        }
        overlay_generation_required                               = true;
        const data_format::stereo_overlay_command_range* ranges[] = {
            &frame->left_overlay_commands,
            &frame->right_overlay_commands,
        };
        for (std::size_t eye_index = 0; eye_index < 2; ++eye_index) {
            const auto& range = *ranges[eye_index];
            if (range.command_stride_floats != kOverlayCommandStrideFloats && range.command_count != 0) {
                return false;
            }
            const std::uint64_t float_count = static_cast<std::uint64_t>(range.command_count) * range.command_stride_floats;
            const std::uint64_t byte_count  = float_count * sizeof(float);
            if (!byte_range_valid(overlay_mapping_.size, range.byte_offset, byte_count)) {
                return false;
            }
            candidate_overlay_commands[eye_index].resize(static_cast<std::size_t>(float_count));
            if (byte_count != 0) {
                std::memcpy(candidate_overlay_commands[eye_index].data(), overlay_mapping_.data + range.byte_offset,
                            static_cast<std::size_t>(byte_count));
            }
        }
    } else {
        overlay_mapping_.reset();
    }

    data_format::stereo_modal_overlay candidate_modal = frame->modal;
    std::vector<std::uint8_t>         candidate_modal_pixels;
    bool                              modal_generation_required = false;
    if (!frame->modal_buffer_path.empty()) {
        if (!ensure_mapped(frame->modal_buffer_path, &modal_mapping_)) {
            return false;
        }
        std::uint64_t modal_generation = 0;
        if (!read_generation(modal_mapping_.data, modal_mapping_.size, frame->modal_generation_offset, &modal_generation) ||
            modal_generation != frame->source_frame_id) {
            return false;
        }
        modal_generation_required = true;
        if (candidate_modal.visible) {
            if (candidate_modal.width == 0 || candidate_modal.height == 0 || candidate_modal.width > 8192 ||
                candidate_modal.height > 8192 || candidate_modal.source_row_stride_bytes < candidate_modal.width * 4U) {
                return false;
            }
            const std::uint64_t modal_required_bytes =
                static_cast<std::uint64_t>(candidate_modal.height - 1U) * candidate_modal.source_row_stride_bytes +
                static_cast<std::uint64_t>(candidate_modal.width) * 4U;
            if (!byte_range_valid(modal_mapping_.size, candidate_modal.byte_offset, modal_required_bytes)) {
                return false;
            }
            const std::size_t tight_row_bytes = static_cast<std::size_t>(candidate_modal.width) * 4U;
            candidate_modal_pixels.resize(tight_row_bytes * candidate_modal.height);
            for (std::uint32_t row = 0; row < candidate_modal.height; ++row) {
                std::memcpy(candidate_modal_pixels.data() + static_cast<std::size_t>(row) * tight_row_bytes,
                            modal_mapping_.data + candidate_modal.byte_offset +
                                static_cast<std::uint64_t>(row) * candidate_modal.source_row_stride_bytes,
                            tight_row_bytes);
            }
        }
    } else {
        modal_mapping_.reset();
        if (candidate_modal.visible) {
            return false;
        }
    }

    // This second generation read is the consumer half of the ring protocol:
    // if Boba reused any slot while data was copied/uploaded, retain the prior
    // complete texture set and try again on the next OpenXR frame.
    std::uint64_t confirmed_generation = 0;
    if (!read_generation(pixel_mapping_.data, pixel_mapping_.size, frame->pixel_generation_offset, &confirmed_generation) ||
        confirmed_generation != frame->source_frame_id) {
        return false;
    }
    if (overlay_generation_required &&
        (!read_generation(overlay_mapping_.data, overlay_mapping_.size, frame->overlay_generation_offset,
                          &confirmed_generation) ||
         confirmed_generation != frame->source_frame_id)) {
        return false;
    }
    if (modal_generation_required &&
        (!read_generation(modal_mapping_.data, modal_mapping_.size, frame->modal_generation_offset, &confirmed_generation) ||
         confirmed_generation != frame->source_frame_id)) {
        return false;
    }

    if (candidate_modal.visible) {
        glBindTexture(GL_TEXTURE_2D, modal_textures_[candidate_texture_set]);
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, static_cast<GLsizei>(candidate_modal.width),
                     static_cast<GLsizei>(candidate_modal.height), 0, GL_RGBA, GL_UNSIGNED_BYTE, candidate_modal_pixels.data());
        glBindTexture(GL_TEXTURE_2D, 0);
        if (glGetError() != GL_NO_ERROR) {
            return false;
        }
    }

    if (frame->presentation_mode == data_format::stereo_presentation_mode::mono_panel &&
        active_presentation_mode_ != data_format::stereo_presentation_mode::mono_panel) {
        world_panel_initialized_ = false;
    }
    active_texture_set_        = candidate_texture_set;
    active_stereo_frame_id_    = frame->source_frame_id;
    active_stereo_frame_valid_ = true;
    active_origin_             = frame->origin;
    active_presentation_mode_  = frame->presentation_mode;
    active_render_views_       = {frame->left_render_view, frame->right_render_view};
    active_overlay_commands_   = std::move(candidate_overlay_commands);
    active_modal_              = std::move(candidate_modal);
    if (active_stereo_frame_id_ == 1 || active_stereo_frame_id_ % 300 == 0) {
        plugin_logger_->info("Uploaded stereo_frame id={} size={}x{} for OpenXR submission", active_stereo_frame_id_,
                             source_width_, source_height_);
    }
    return true;
}

bool openxr_quest_controller::render_eye(std::size_t eye_index, GLuint swapchain_image) {
    if (!active_stereo_frame_valid_ || active_texture_set_ < 0 || eye_index >= 2) {
        return false;
    }
    while (glGetError() != GL_NO_ERROR) { }
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer_);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, swapchain_image, 0);
    glDrawBuffer(GL_COLOR_ATTACHMENT0);
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        plugin_logger_->error("OpenXR swapchain framebuffer is incomplete for eye {}", eye_index);
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        return false;
    }

    const bool  render_as_panel   = active_presentation_mode_ != data_format::stereo_presentation_mode::stereo_fullscreen;
    mat4        mvp               = scale_matrix(2.0F, 2.0F, 1.0F);
    std::size_t texture_eye_index = eye_index;
    if (render_as_panel) {
        // mono_panel captures the panel's initial world transform; head_locked
        // recomputes it from the current eye pose on every frame.
        texture_eye_index        = 0;
        const float panel_height = kPanelWidthMeters * static_cast<float>(source_height_) /
            static_cast<float>(std::max<std::uint32_t>(source_width_, 1));
        const mat4 eye_pose = pose_matrix(located_views_[eye_index].pose);
        mat4       panel_model{};
        if (active_presentation_mode_ == data_format::stereo_presentation_mode::mono_panel) {
            if (!world_panel_initialized_) {
                const mat4 initial_head_pose = pose_matrix(located_views_[0].pose);
                panel_model                  = multiply(initial_head_pose,
                                                        multiply(translation_matrix(0.0F, kPanelYOffsetMeters, -kPanelDistanceMeters),
                                                                 scale_matrix(kPanelWidthMeters, panel_height, 1.0F)));
                std::copy(std::begin(panel_model.values), std::end(panel_model.values), world_panel_model_.begin());
                world_panel_initialized_ = true;
            } else {
                std::copy(world_panel_model_.begin(), world_panel_model_.end(), std::begin(panel_model.values));
            }
        } else {
            panel_model = multiply(eye_pose,
                                   multiply(translation_matrix(0.0F, kPanelYOffsetMeters, -kPanelDistanceMeters),
                                            scale_matrix(kPanelWidthMeters, panel_height, 1.0F)));
        }
        const mat4 view       = inverse_rigid_transform(eye_pose);
        const mat4 projection = projection_matrix(located_views_[eye_index].fov, kNearZ, kFarZ);
        mvp                   = multiply(projection, multiply(view, panel_model));
    }

    glViewport(0, 0, static_cast<GLsizei>(swapchain_views_[eye_index].width),
               static_cast<GLsizei>(swapchain_views_[eye_index].height));
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);
    glDisable(GL_SCISSOR_TEST);
    glClearColor(0.0F, 0.0F, 0.0F, 1.0F);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(panel_program_);
    glBindVertexArray(panel_vao_);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, source_textures_[active_texture_set_][texture_eye_index]);
    glUniform1i(panel_source_location_, 0);
    glUniform1i(panel_flip_y_location_, active_origin_ == data_format::stereo_image_origin::upper_left ? 1 : 0);
    glUniformMatrix4fv(panel_mvp_location_, 1, GL_FALSE, mvp.values);
    glDrawArrays(GL_TRIANGLES, 0, 6);

    if (!render_as_panel) {
        draw_overlay_commands(active_overlay_commands_[eye_index], overlay_program_, overlay_vao_, overlay_vbo_,
                              overlay_source_size_location_, source_width_, source_height_);
        draw_modal_overlay(active_modal_, modal_textures_[active_texture_set_], modal_program_, modal_vao_, modal_vbo_,
                           modal_source_size_location_, modal_texture_location_, eye_index, source_width_, source_height_);
    }
    glBindTexture(GL_TEXTURE_2D, 0);
    glBindVertexArray(0);
    glUseProgram(0);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glFlush();
    return glGetError() == GL_NO_ERROR;
}

bool openxr_quest_controller::render_projection_layer(XrCompositionLayerProjection*                    layer,
                                                      std::array<XrCompositionLayerProjectionView, 2>* projection_views) {
    if (!active_stereo_frame_valid_ || !located_views_valid_) {
        return false;
    }
    *layer           = make_xr_struct<XrCompositionLayerProjection>(XR_TYPE_COMPOSITION_LAYER_PROJECTION);
    layer->space     = local_space_;
    layer->viewCount = static_cast<std::uint32_t>(projection_views->size());
    layer->views     = projection_views->data();

    for (std::size_t eye_index = 0; eye_index < projection_views->size(); ++eye_index) {
        XrCompositionLayerProjectionView& projection_view = (*projection_views)[eye_index];
        projection_view      = make_xr_struct<XrCompositionLayerProjectionView>(XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW);
        projection_view.pose = located_views_[eye_index].pose;
        projection_view.fov  = located_views_[eye_index].fov;
        // Fullscreen stereo is reprojected from the poses/FOV Boba rendered
        // against. Panel modes use the current eye views for normal 3D geometry.
        if (active_presentation_mode_ == data_format::stereo_presentation_mode::stereo_fullscreen &&
            active_render_views_[eye_index].valid) {
            const auto& source   = active_render_views_[eye_index];
            projection_view.pose = source;
            projection_view.fov  = {source.angle_left, source.angle_right, source.angle_up, source.angle_down};
        }

        swapchain_view&             swapchain = swapchain_views_[eye_index];
        XrSwapchainImageAcquireInfo acquire_info =
            make_xr_struct<XrSwapchainImageAcquireInfo>(XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO);
        std::uint32_t image_index = 0;
        if (!check_xr(xrAcquireSwapchainImage(swapchain.handle, &acquire_info, &image_index), "xrAcquireSwapchainImage")) {
            return false;
        }
        XrSwapchainImageWaitInfo wait_info = make_xr_struct<XrSwapchainImageWaitInfo>(XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO);
        wait_info.timeout                  = XR_INFINITE_DURATION;
        if (!check_xr(xrWaitSwapchainImage(swapchain.handle, &wait_info), "xrWaitSwapchainImage")) {
            return false;
        }
        if (image_index >= swapchain.images.size() || !render_eye(eye_index, swapchain.images[image_index].image)) {
            XrSwapchainImageReleaseInfo release_info =
                make_xr_struct<XrSwapchainImageReleaseInfo>(XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO);
            xrReleaseSwapchainImage(swapchain.handle, &release_info);
            return false;
        }
        XrSwapchainImageReleaseInfo release_info =
            make_xr_struct<XrSwapchainImageReleaseInfo>(XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO);
        if (!check_xr(xrReleaseSwapchainImage(swapchain.handle, &release_info), "xrReleaseSwapchainImage")) {
            return false;
        }

        projection_view.subImage.swapchain        = swapchain.handle;
        projection_view.subImage.imageRect.offset = {0, 0};
        projection_view.subImage.imageRect.extent = {static_cast<std::int32_t>(swapchain.width),
                                                     static_cast<std::int32_t>(swapchain.height)};
        projection_view.subImage.imageArrayIndex  = 0;
    }
    return true;
}

// ---- OpenXR view and controller-state extraction --------------------------

bool openxr_quest_controller::query_views(XrTime sample_time, view_frame* frame) {
    for (XrView& view : located_views_) {
        view = make_xr_struct<XrView>(XR_TYPE_VIEW);
    }

    XrViewLocateInfo locate_info      = make_xr_struct<XrViewLocateInfo>(XR_TYPE_VIEW_LOCATE_INFO);
    locate_info.viewConfigurationType = view_configuration_type_;
    locate_info.displayTime           = sample_time;
    locate_info.space                 = local_space_;

    XrViewState   view_state = make_xr_struct<XrViewState>(XR_TYPE_VIEW_STATE);
    std::uint32_t view_count = 0;
    if (!check_xr(xrLocateViews(session_, &locate_info, &view_state, static_cast<std::uint32_t>(located_views_.size()),
                                &view_count, located_views_.data()),
                  "xrLocateViews")) {
        located_views_valid_ = false;
        return false;
    }
    if (view_count != located_views_.size()) {
        plugin_logger_->error("xrLocateViews returned {} views; expected two", view_count);
        located_views_valid_ = false;
        return false;
    }

    const bool pose_valid = (view_state.viewStateFlags & XR_VIEW_STATE_POSITION_VALID_BIT) != 0 &&
        (view_state.viewStateFlags & XR_VIEW_STATE_ORIENTATION_VALID_BIT) != 0;
    const bool pose_tracked = (view_state.viewStateFlags & XR_VIEW_STATE_POSITION_TRACKED_BIT) != 0 &&
        (view_state.viewStateFlags & XR_VIEW_STATE_ORIENTATION_TRACKED_BIT) != 0;
    located_views_valid_ = pose_valid;

    const auto copy_view = [pose_valid, pose_tracked](const XrView& source, const XrViewConfigurationView& configuration,
                                                      eye_view* destination) {
        destination->valid              = pose_valid;
        destination->pose_tracked       = pose_tracked;
        destination->recommended_width  = configuration.recommendedImageRectWidth;
        destination->recommended_height = configuration.recommendedImageRectHeight;
        destination->angle_left         = source.fov.angleLeft;
        destination->angle_right        = source.fov.angleRight;
        destination->angle_up           = source.fov.angleUp;
        destination->angle_down         = source.fov.angleDown;
        if (pose_valid) {
            destination->update(source.pose);
        }
    };
    copy_view(located_views_[0], view_configuration_views_[0], &frame->left);
    copy_view(located_views_[1], view_configuration_views_[1], &frame->right);
    return true;
}

bool openxr_quest_controller::query_hand(std::size_t hand_index, XrTime sample_time, hand_controller* hand) {
    *hand                     = hand_controller{};
    hand->interaction_profile = interaction_profiles_[hand_index];

    if (!query_pose(grip_pose_action_, grip_spaces_[hand_index], hand_paths_[hand_index], sample_time, &hand->grip_pose) ||
        !query_pose(aim_pose_action_, aim_spaces_[hand_index], hand_paths_[hand_index], sample_time, &hand->aim_pose)) {
        return false;
    }

    controller_button trigger_click;
    controller_button trigger_value;
    if (!query_boolean(trigger_click_action_, hand_paths_[hand_index], &trigger_click) ||
        !query_float(trigger_value_action_, hand_paths_[hand_index], kTriggerPressedThreshold, &trigger_value) ||
        !query_float(squeeze_value_action_, hand_paths_[hand_index], kSqueezePressedThreshold, &hand->squeeze) ||
        !query_boolean(primary_click_action_, hand_paths_[hand_index], &hand->primary) ||
        !query_boolean(secondary_click_action_, hand_paths_[hand_index], &hand->secondary) ||
        !query_boolean(thumbstick_click_action_, hand_paths_[hand_index], &hand->thumbstick_click) ||
        !query_axis2d(thumbstick_axis_action_, hand_paths_[hand_index], &hand->thumbstick)) {
        return false;
    }
    // Runtimes differ on whether trigger is exposed as boolean, float, or both.
    // Merging preserves click transitions while retaining the analog value.
    hand->trigger = trigger_click;
    merge_button(&hand->trigger, trigger_value);

    hand->available = hand->grip_pose.active || hand->aim_pose.active || hand->trigger.active || hand->squeeze.active ||
        hand->primary.active || hand->secondary.active || hand->thumbstick_click.active || hand->thumbstick.active;
    return true;
}

bool openxr_quest_controller::query_pose(XrAction action, XrSpace action_space, XrPath hand_path, XrTime sample_time,
                                         controller_pose* pose) {
    *pose                         = controller_pose{};
    XrActionStateGetInfo get_info = make_xr_struct<XrActionStateGetInfo>(XR_TYPE_ACTION_STATE_GET_INFO);
    get_info.action               = action;
    get_info.subactionPath        = hand_path;

    XrActionStatePose state = make_xr_struct<XrActionStatePose>(XR_TYPE_ACTION_STATE_POSE);
    if (!check_xr(xrGetActionStatePose(session_, &get_info, &state), "xrGetActionStatePose")) {
        return false;
    }
    pose->active = state.isActive == XR_TRUE;
    if (!pose->active) {
        return true;
    }

    XrSpaceLocation location = make_xr_struct<XrSpaceLocation>(XR_TYPE_SPACE_LOCATION);
    if (!check_xr(xrLocateSpace(action_space, local_space_, sample_time, &location), "xrLocateSpace(controller)")) {
        return false;
    }

    pose->position_valid      = (location.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) != 0;
    pose->orientation_valid   = (location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0;
    pose->position_tracked    = (location.locationFlags & XR_SPACE_LOCATION_POSITION_TRACKED_BIT) != 0;
    pose->orientation_tracked = (location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT) != 0;

    if (pose->position_valid) {
        pose->position = location.pose.position;
    }
    if (pose->orientation_valid) {
        pose->orientation = location.pose.orientation;
    }
    return true;
}

bool openxr_quest_controller::query_boolean(XrAction action, XrPath hand_path, controller_button* button) {
    *button                       = controller_button{};
    XrActionStateGetInfo get_info = make_xr_struct<XrActionStateGetInfo>(XR_TYPE_ACTION_STATE_GET_INFO);
    get_info.action               = action;
    get_info.subactionPath        = hand_path;

    XrActionStateBoolean state = make_xr_struct<XrActionStateBoolean>(XR_TYPE_ACTION_STATE_BOOLEAN);
    if (!check_xr(xrGetActionStateBoolean(session_, &get_info, &state), "xrGetActionStateBoolean")) {
        return false;
    }
    button->active                  = state.isActive == XR_TRUE;
    button->pressed                 = button->active && state.currentState == XR_TRUE;
    button->changed_since_last_sync = state.changedSinceLastSync == XR_TRUE;
    button->value                   = button->pressed ? 1.0F : 0.0F;
    button->last_change_time        = static_cast<std::int64_t>(state.lastChangeTime);
    return true;
}

bool openxr_quest_controller::query_float(XrAction action, XrPath hand_path, float pressed_threshold,
                                          controller_button* button) {
    *button                       = controller_button{};
    XrActionStateGetInfo get_info = make_xr_struct<XrActionStateGetInfo>(XR_TYPE_ACTION_STATE_GET_INFO);
    get_info.action               = action;
    get_info.subactionPath        = hand_path;

    XrActionStateFloat state = make_xr_struct<XrActionStateFloat>(XR_TYPE_ACTION_STATE_FLOAT);
    if (!check_xr(xrGetActionStateFloat(session_, &get_info, &state), "xrGetActionStateFloat")) {
        return false;
    }
    button->active                  = state.isActive == XR_TRUE;
    button->value                   = button->active ? state.currentState : 0.0F;
    button->pressed                 = button->active && state.currentState >= pressed_threshold;
    button->changed_since_last_sync = state.changedSinceLastSync == XR_TRUE;
    button->last_change_time        = static_cast<std::int64_t>(state.lastChangeTime);
    return true;
}

bool openxr_quest_controller::query_axis2d(XrAction action, XrPath hand_path, controller_axis2d* axis) {
    *axis                         = controller_axis2d{};
    XrActionStateGetInfo get_info = make_xr_struct<XrActionStateGetInfo>(XR_TYPE_ACTION_STATE_GET_INFO);
    get_info.action               = action;
    get_info.subactionPath        = hand_path;

    XrActionStateVector2f state = make_xr_struct<XrActionStateVector2f>(XR_TYPE_ACTION_STATE_VECTOR2F);
    if (!check_xr(xrGetActionStateVector2f(session_, &get_info, &state), "xrGetActionStateVector2f")) {
        return false;
    }
    axis->active                  = state.isActive == XR_TRUE;
    axis->changed_since_last_sync = state.changedSinceLastSync == XR_TRUE;
    axis->last_change_time        = static_cast<std::int64_t>(state.lastChangeTime);
    if (axis->active) {
        axis->value = {state.currentState.x, state.currentState.y};
    }
    return true;
}

bool openxr_quest_controller::refresh_interaction_profiles() {
    bool success = true;
    for (std::size_t hand_index = 0; hand_index < hand_paths_.size(); ++hand_index) {
        XrInteractionProfileState state  = make_xr_struct<XrInteractionProfileState>(XR_TYPE_INTERACTION_PROFILE_STATE);
        const XrResult            result = xrGetCurrentInteractionProfile(session_, hand_paths_[hand_index], &state);
        if (XR_FAILED(result)) {
            plugin_logger_->warn("xrGetCurrentInteractionProfile({}) failed: {}", hand_index == 0 ? "left" : "right",
                                 xr_result_string(result));
            success = false;
            continue;
        }

        const std::string        raw_profile = path_string(state.interactionProfile);
        const controller_profile profile     = profile_from_path(raw_profile);
        if (profile != interaction_profiles_[hand_index]) {
            interaction_profiles_[hand_index] = profile;
            plugin_logger_->info("{} interaction profile -> {}", hand_index == 0 ? "left" : "right",
                                 raw_profile.empty() ? "none" : raw_profile);
        }
    }
    interaction_profiles_dirty_ = false;
    return success;
}

// ---- Transition logging, teardown, and OpenXR utility helpers --------------

void openxr_quest_controller::log_input_changes(const controller_input& input) {
    if (log_input_) {
        const controller_input  empty_previous;
        const controller_input& previous = have_previous_input_ ? previous_input_ : empty_previous;
        log_hand_changes("left", true, input.left, previous.left, !have_previous_input_);
        log_hand_changes("right", false, input.right, previous.right, !have_previous_input_);
    }
    previous_input_      = input;
    have_previous_input_ = true;
}

void openxr_quest_controller::log_hand_changes(const char* hand_name, bool left, const hand_controller& current,
                                               const hand_controller& previous, bool first_sample) {
    if ((!first_sample && current.available != previous.available) || (first_sample && current.available)) {
        plugin_logger_->info("{} controller {}", hand_name, current.available ? "AVAILABLE" : "UNAVAILABLE");
    }
    if ((!first_sample && current.interaction_profile != previous.interaction_profile) ||
        (first_sample && current.interaction_profile != controller_profile::none)) {
        plugin_logger_->info("{} controller profile {}", hand_name, profile_label(current.interaction_profile));
    }

    const bool tracked            = current.tracked();
    const bool previously_tracked = previous.tracked();
    if ((!first_sample && tracked != previously_tracked) || (first_sample && tracked)) {
        plugin_logger_->info("{} controller tracking {}", hand_name, tracked ? "ACQUIRED" : "LOST");
    }

    log_button_change(hand_name, "trigger", current.trigger, previous.trigger, first_sample);
    log_button_change(hand_name, "squeeze", current.squeeze, previous.squeeze, first_sample);
    log_button_change(hand_name, left ? "primary (X)" : "primary (A)", current.primary, previous.primary, first_sample);
    log_button_change(hand_name, left ? "secondary (Y)" : "secondary (B)", current.secondary, previous.secondary, first_sample);
    log_button_change(hand_name, "thumbstick click", current.thumbstick_click, previous.thumbstick_click, first_sample);

    const bool thumbstick_moved = current.thumbstick.active && current.thumbstick.value.norm() >= kThumbstickMoveThreshold;
    const bool previous_moved   = previous.thumbstick.active && previous.thumbstick.value.norm() >= kThumbstickMoveThreshold;
    if ((!first_sample && thumbstick_moved != previous_moved) || (first_sample && thumbstick_moved)) {
        if (thumbstick_moved) {
            plugin_logger_->info("{} thumbstick MOVED x={:.2f} y={:.2f}", hand_name, current.thumbstick.value.x(),
                                 current.thumbstick.value.y());
        } else {
            plugin_logger_->info("{} thumbstick CENTERED", hand_name);
        }
    }
}

void openxr_quest_controller::log_button_change(const char* hand_name, const char* button_name,
                                                const controller_button& current, const controller_button& previous,
                                                bool first_sample) {
    if ((!first_sample && current.pressed != previous.pressed) || (first_sample && current.pressed)) {
        plugin_logger_->info("{} {} {} value={:.2f}", hand_name, button_name, current.pressed ? "PRESSED" : "RELEASED",
                             current.value);
    }
}

void openxr_quest_controller::destroy_openxr() {
    if (session_ != XR_NULL_HANDLE && session_running_) {
        const XrResult result = xrRequestExitSession(session_);
        if (XR_FAILED(result) && result != XR_ERROR_SESSION_NOT_RUNNING) {
            plugin_logger_->debug("xrRequestExitSession during shutdown returned {}", xr_result_string(result));
        }
    }

    destroy_stereo_renderer();
    destroy_swapchains();

    if (local_space_ != XR_NULL_HANDLE) {
        xrDestroySpace(local_space_);
        local_space_ = XR_NULL_HANDLE;
    }
    for (XrSpace& space : aim_spaces_) {
        if (space != XR_NULL_HANDLE) {
            xrDestroySpace(space);
            space = XR_NULL_HANDLE;
        }
    }
    for (XrSpace& space : grip_spaces_) {
        if (space != XR_NULL_HANDLE) {
            xrDestroySpace(space);
            space = XR_NULL_HANDLE;
        }
    }
    if (session_ != XR_NULL_HANDLE) {
        xrDestroySession(session_);
        session_ = XR_NULL_HANDLE;
    }
    if (action_set_ != XR_NULL_HANDLE) {
        xrDestroyActionSet(action_set_);
        action_set_ = XR_NULL_HANDLE;
    }
    if (instance_ != XR_NULL_HANDLE) {
        xrDestroyInstance(instance_);
        instance_ = XR_NULL_HANDLE;
    }

    system_id_                  = XR_NULL_SYSTEM_ID;
    session_running_            = false;
    session_state_              = XR_SESSION_STATE_UNKNOWN;
    interaction_profiles_dirty_ = true;
    interaction_profiles_       = {controller_profile::none, controller_profile::none};
    located_views_valid_        = false;
}

bool openxr_quest_controller::check_xr(XrResult result, const char* operation) const {
    if (XR_SUCCEEDED(result)) {
        return true;
    }
    plugin_logger_->error("{} failed: {}", operation, xr_result_string(result));
    return false;
}

std::string openxr_quest_controller::xr_result_string(XrResult result) const {
    if (instance_ != XR_NULL_HANDLE) {
        char result_buffer[XR_MAX_RESULT_STRING_SIZE]{};
        if (XR_SUCCEEDED(xrResultToString(instance_, result, result_buffer))) {
            return result_buffer;
        }
    }
    return std::to_string(result);
}

std::string openxr_quest_controller::path_string(XrPath path) const {
    if (path == XR_NULL_PATH || instance_ == XR_NULL_HANDLE) {
        return {};
    }

    std::uint32_t required_size = 0;
    if (XR_FAILED(xrPathToString(instance_, path, 0, &required_size, nullptr)) || required_size == 0) {
        return {};
    }
    std::vector<char> buffer(required_size);
    if (XR_FAILED(xrPathToString(instance_, path, required_size, &required_size, buffer.data()))) {
        return {};
    }
    return buffer.data();
}

openxr_quest_controller::controller_profile openxr_quest_controller::profile_from_path(const std::string& path) {
    if (path.empty()) {
        return controller_profile::none;
    }
    if (path == kSimpleControllerProfile) {
        return controller_profile::simple_controller;
    }
    if (path == kOculusTouchProfile) {
        return controller_profile::oculus_touch;
    }
    if (path == kHtcViveProfile) {
        return controller_profile::htc_vive;
    }
    if (path == kValveIndexProfile) {
        return controller_profile::valve_index;
    }
    if (path == kMicrosoftMotionProfile) {
        return controller_profile::microsoft_motion;
    }
    return controller_profile::unknown;
}

const char* openxr_quest_controller::profile_label(controller_profile profile) {
    switch (profile) {
    case controller_profile::none:
        return "none";
    case controller_profile::simple_controller:
        return "simple_controller";
    case controller_profile::oculus_touch:
        return "oculus_touch";
    case controller_profile::htc_vive:
        return "htc_vive";
    case controller_profile::valve_index:
        return "valve_index";
    case controller_profile::microsoft_motion:
        return "microsoft_motion";
    case controller_profile::unknown:
        return "unknown";
    }
    return "unknown";
}

const char* openxr_quest_controller::session_state_label(XrSessionState state) {
    switch (state) {
    case XR_SESSION_STATE_UNKNOWN:
        return "UNKNOWN";
    case XR_SESSION_STATE_IDLE:
        return "IDLE";
    case XR_SESSION_STATE_READY:
        return "READY";
    case XR_SESSION_STATE_SYNCHRONIZED:
        return "SYNCHRONIZED";
    case XR_SESSION_STATE_VISIBLE:
        return "VISIBLE";
    case XR_SESSION_STATE_FOCUSED:
        return "FOCUSED";
    case XR_SESSION_STATE_STOPPING:
        return "STOPPING";
    case XR_SESSION_STATE_LOSS_PENDING:
        return "LOSS_PENDING";
    case XR_SESSION_STATE_EXITING:
        return "EXITING";
    case XR_SESSION_STATE_MAX_ENUM:
        return "MAX_ENUM";
    }
    return "UNKNOWN";
}

void openxr_quest_controller::merge_button(controller_button* destination, const controller_button& source) {
    destination->active                  = destination->active || source.active;
    destination->pressed                 = destination->pressed || source.pressed;
    destination->changed_since_last_sync = destination->changed_since_last_sync || source.changed_since_last_sync;
    destination->value                   = std::max(destination->value, source.value);
    destination->last_change_time        = std::max(destination->last_change_time, source.last_change_time);
}

} // namespace ILLIXR

using namespace ILLIXR;

PLUGIN_MAIN(openxr_quest_controller)