Skip to content

File plugin.cpp

File List > gldemo > plugin.cpp

Go to the documentation of this file

#if defined(_WIN32) || defined(_WIN64)
#    include 
#endif

#ifdef __ANDROID__
#    include 
#else
// clang-format off
#  include  // GLEW has to be loaded before other GL libraries
// clang-format on
#endif

#include "illixr/error_util.hpp"
#include "illixr/global_module_defs.hpp"
#include "illixr/math_util.hpp"
#include "plugin.hpp"

#include 
#include 
#include 
#ifdef __ANDROID__
#    include 
#else
#    include 
#endif
#include 
#ifndef __ANDROID__
#    include 
#endif
#include 

using namespace ILLIXR;
using namespace ILLIXR::data_format;

// Wake up 1 ms after vsync instead of exactly at vsync to account for scheduling uncertainty
static constexpr std::chrono::milliseconds VSYNC_SAFETY_DELAY{1};

[[maybe_unused]] gldemo::gldemo(const std::string& name, phonebook* pb)
    : threadloop{name, pb}
#ifdef __ANDROID__
    , ext_window_{phonebook_->lookup_impl<xlib_gl_extended_window>()}
#else
    , ext_window_{new xlib_gl_extended_window{1, 1, phonebook_->lookup_impl<xlib_gl_extended_window>()->context_}}
#endif
    , switchboard_{phonebook_->lookup_impl<switchboard>()}
    , pose_prediction_{phonebook_->lookup_impl<pose_prediction>()}
#ifdef __ANDROID__
    , lock_{phonebook_->lookup_impl<common_lock>()}
#endif
    , clock_{phonebook_->lookup_impl<relative_clock>()}
    , vsync_{switchboard_->get_reader<switchboard::event_wrapper<time_point>>("vsync_estimate")}
    , image_handle_{switchboard_->get_writer<image_handle>("image_handle")}
    , eye_buffer_{switchboard_->get_writer<rendered_frame>("eyebuffer")} {
    spdlogger(switchboard_->get_env_char("GLDEMO_LOG_LEVEL"));
}

// Essentially, a crude equivalent of XRWaitFrame.
void gldemo::wait_vsync() {
    switchboard::ptr<const switchboard::event_wrapper<time_point>> next_vsync = vsync_.get_ro_nullable();
    time_point                                                     now        = clock_->now();
    time_point                                                     wait_time{};

    if (next_vsync == nullptr) {
        // If no vsync data available, just sleep for roughly a vsync period.
        // We'll get synced back up later.
        std::this_thread::sleep_for(display_params::period);
        return;
    }

#ifndef NDEBUG
    if (log_count_ > LOG_PERIOD) {
        double vsync_in = duration_to_double<std::milli>(**next_vsync - now);
        spdlog::get(name_)->debug("First vsync is in {} ms", vsync_in);
    }
#endif

    bool has_rendered_this_interval = (now - last_time_) < display_params::period;

    // If less than one frame interval has passed since we last rendered...
    if (has_rendered_this_interval) {
        // We'll wait until the next vsync, plus a small delay time.
        // Delay time helps with some inaccuracies in scheduling.
        wait_time = **next_vsync + VSYNC_SAFETY_DELAY;

        // If our sleep target is in the past, bump it forward
        // by a vsync period, so it's always in the future.
        while (wait_time < now) {
            wait_time += display_params::period;
        }

#ifndef NDEBUG
        if (log_count_ > LOG_PERIOD) {
            double wait_in = duration_to_double<std::milli>(wait_time - now);
            spdlog::get(name_)->debug("Waiting until next vsync, in {} ms", wait_in);
        }
#endif
        // Perform the sleep.
        // TODO: Consider using Monado-style sleeping, where we nanosleep for
        // most of the wait, and then spin-wait for the rest?
        std::this_thread::sleep_for(wait_time - now);
    } else {
#ifndef NDEBUG
        if (log_count_ > LOG_PERIOD) {
            spdlog::get(name_)->debug("We haven't rendered yet, rendering immediately");
        }
#endif
    }
}

void gldemo::_p_thread_setup() {
    last_time_ = clock_->now();
#ifndef __ANDROID__
    // Note: glXMakeContextCurrent must be called from the thread which will be using it.
#    if defined(_WIN32) || defined(_WIN64)
    HGLRC ctx = wglGetCurrentContext();
    HDC   dcx = wglGetCurrentDC();
#    endif
    [[maybe_unused]] int gl_result =
#    if defined(_WIN32) || defined(_WIN64)
        wglMakeCurrent(ext_window_->hdc_, ext_window_->context_);
    DWORD error = GetLastError();
#    else
        static_cast<bool>(glXMakeCurrent(ext_window_->display_, ext_window_->window_, ext_window_->context_));
#    endif
    assert(gl_result && "glXMakeCurrent should not fail");
#endif
}

void gldemo::_p_one_iteration() {
#ifdef __ANDROID__
    auto start = std::chrono::high_resolution_clock::now();
#endif
    // Essentially, XRWaitFrame.
    wait_vsync();
#ifdef __ANDROID__
    lock_->get_lock();
    [[maybe_unused]] const bool gl_result = static_cast<bool>(
        eglMakeCurrent(ext_window_->display_, ext_window_->surface_, ext_window_->surface_, ext_window_->context_));
    assert(gl_result && "eglMakeCurrent should not fail");
#endif
    glUseProgram(demo_shader_program_);
    glBindFramebuffer(GL_FRAMEBUFFER, eye_texture_FBO_);

    glUseProgram(demo_shader_program_);
    glBindVertexArray(demo_vao_);
    glViewport(0, 0, display_params::width_pixels, display_params::height_pixels);

    glEnable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);

#ifdef __ANDROID__
    glClearDepthf(1);
#else
    glClearDepth(1);
#endif

    Eigen::Matrix4f model_matrix = Eigen::Matrix4f::Identity();

    const pose::fast_head_pose_type fast_pose = pose_prediction_->get_fast_pose();
    pose::head_pose_type            pose      = fast_pose.pose;

    Eigen::Matrix3f head_rotation_matrix = pose.orientation.toRotationMatrix();

    // Excessive? Maybe.
    constexpr int LEFT_EYE = 0;

    for (auto eye_idx = 0; eye_idx < 2; eye_idx++) {
        // Offset of eyeball from pose
        auto eyeball = Eigen::Vector3f((eye_idx == LEFT_EYE ? -display_params::ipd / 2.0f : display_params::ipd / 2.0f), 0, 0);

        // Apply head rotation to eyeball offset vector
        eyeball = head_rotation_matrix * eyeball;

        // Apply head position to eyeball
        eyeball += pose.position;

        // Build our eye matrix from the pose's position + orientation.
        Eigen::Matrix4f eye_matrix   = Eigen::Matrix4f::Identity();
        eye_matrix.block<3, 1>(0, 3) = eyeball; // Set position to eyeball's position
        eye_matrix.block<3, 3>(0, 0) = pose.orientation.toRotationMatrix();

        // Objects' "view matrix" is inverse of eye matrix.
        auto view_matrix = eye_matrix.inverse();

        // We'll calculate this model view matrix
        // using fresh pose data, if we have any.
        Eigen::Matrix4f model_view_matrix = view_matrix * model_matrix;
        glUniformMatrix4fv(static_cast<GLint>(model_view_), 1, GL_FALSE, (GLfloat*) (model_view_matrix.data()));
        glUniformMatrix4fv(static_cast<GLint>(projection_), 1, GL_FALSE, (GLfloat*) (basic_projection_.data()));

        glBindTexture(GL_TEXTURE_2D, eye_textures_[eye_idx]);
#ifdef __ANDROID__
        glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, eye_textures_[eye_idx], 0);
#else
        glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, eye_textures_[eye_idx], 0);
#endif
        glBindTexture(GL_TEXTURE_2D, 0);
        glClearColor(0.9f, 0.9f, 0.9f, 1.0f);

        RAC_ERRNO_MSG("gldemo before glClear");
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        RAC_ERRNO_MSG("gldemo after glClear");

        demo_scene_.Draw();
    }

    glFinish();

#ifndef NDEBUG
    const double frame_duration_s = duration_to_double(clock_->now() - last_time_);
    const double fps              = 1.0 / frame_duration_s;

    if (log_count_ > LOG_PERIOD) {
        spdlog::get(name_)->debug("Submitting frame to buffer {}, frametime: {}, FPS: {}", which_buffer_, frame_duration_s,
                                  fps);
    }
#endif
    last_time_ = clock_->now();

    eye_buffer_.put(eye_buffer_.allocate<rendered_frame>(rendered_frame{
        // Somehow, C++ won't let me construct this object if I remove the `rendered_frame{` and `}`.
        // `allocate(...)` _should_ forward the arguments to rendered_frame's constructor, but I guess
        // not.
        std::array<GLuint, 2>{0, 0}, std::array<GLuint, 2>{which_buffer_, which_buffer_}, fast_pose,
        fast_pose.predict_computed_time, last_time_}));

    which_buffer_ = !which_buffer_;

#ifdef __ANDROID__
    [[maybe_unused]] const bool gl_result_1 =
        static_cast<bool>(eglMakeCurrent(ext_window_->display_, nullptr, nullptr, nullptr));
    assert(gl_result_1 && "glXMakeCurrent should not fail");
    lock_->release_lock();
    auto stop     = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(stop - start);
    spdlog::get("illixr")->debug("duration: %f", duration_to_double(duration));
#endif
#ifndef NDEBUG
    if (log_count_ > LOG_PERIOD) {
        log_count_ = 0;
    } else {
        log_count_++;
    }
#endif
}

// We override start() to control our own lifecycle
void gldemo::start() {
#ifdef __ANDROID__
    lock_->get_lock();
#endif
    [[maybe_unused]] const bool gl_result_0 =
#if defined(_WIN32) || defined(_WIN64)
        static_cast<bool>(wglMakeCurrent(ext_window_->hdc_, ext_window_->context_));
#elif defined(__ANDROID__)
        static_cast<bool>(
            eglMakeCurrent(ext_window_->display_, ext_window_->surface_, ext_window_->surface_, ext_window_->context_));
#else
        static_cast<bool>(glXMakeCurrent(ext_window_->display_, ext_window_->window_, ext_window_->context_));
#endif

    assert(gl_result_0 && "glXMakeCurrent should not fail");

    // Init and verify GLEW
#ifndef __ANDROID__
    const GLenum glew_err = glewInit();
    if (glew_err != GLEW_OK) {
        spdlog::get(name_)->error("GLEW Error: {}", (void*) glewGetErrorString(glew_err));
        ILLIXR::abort("Failed to initialize GLEW");
    }

    glEnable(GL_DEBUG_OUTPUT);
    glDebugMessageCallback(message_callback, nullptr);
#endif

    // Create two shared textures, one for each eye.
    create_shared_eyebuffer(&(eye_textures_[0]));
    image_handle_.put(image_handle_.allocate<image_handle>(image_handle{eye_textures_[0], 1, swapchain_usage::LEFT_SWAPCHAIN}));
    create_shared_eyebuffer(&(eye_textures_[1]));
    image_handle_.put(
        image_handle_.allocate<image_handle>(image_handle{eye_textures_[1], 1, swapchain_usage::RIGHT_SWAPCHAIN}));

    // Initialize FBO and depth targets, attaching to the frame handle
    create_FBO(&(eye_textures_[0]), &eye_texture_FBO_, &eye_texture_depth_target_);

    // Create and bind global VAO object
    glGenVertexArrays(1, &demo_vao_);
    glBindVertexArray(demo_vao_);

    demo_shader_program_ = init_and_link(demo_vertex_shader, demo_fragment_shader);
#ifndef NDEBUG
    spdlog::get(name_)->debug("Demo app shader program is program {}", demo_shader_program_);
#endif

#ifndef __ANDROID__
    vertex_position_ = glGetAttribLocation(demo_shader_program_, "vertexPosition");
    vertex_normal_   = glGetAttribLocation(demo_shader_program_, "vertexNormal");
#endif
    model_view_ = glGetUniformLocation(demo_shader_program_, "u_modelview");
    projection_ = glGetUniformLocation(demo_shader_program_, "u_projection");
#ifdef __ANDROID__
    color_uniform_ = glGetUniformLocation(demo_shader_program_, "u_color");
#endif

    // Load/initialize the demo scene
    const char* obj_dir = switchboard_->get_env_char("ILLIXR_DEMO_DATA");
    if (obj_dir == nullptr) {
        ILLIXR::abort("Please define ILLIXR_DEMO_DATA.");
    }

    demo_scene_ = ObjScene(std::string(obj_dir), "scene.obj");

    // Construct perspective projection matrix
    math_util::projection_fov(&basic_projection_, display_params::fov_x / 2.0f, display_params::fov_x / 2.0f,
                              display_params::fov_y / 2.0f, display_params::fov_y / 2.0f, rendering_params::near_z,
                              rendering_params::far_z);

    [[maybe_unused]] const bool gl_result_1 =
#if defined(_WIN32) || defined(_WIN64)
        static_cast<bool>(wglMakeCurrent(ext_window_->hdc_, ext_window_->context_));
#elif defined(__ANDROID__)
        static_cast<bool>(eglMakeCurrent(ext_window_->display_, nullptr, nullptr, nullptr));
#else
        static_cast<bool>(glXMakeCurrent(ext_window_->display_, None, nullptr));
#endif

    assert(gl_result_1 && "glXMakeCurrent should not fail");
#ifdef __ANDROID__
    lock_->release_lock();
#endif

    // Effectively, last vsync was at zero.
    // Try to run gldemo right away.
    threadloop::start();
}

void gldemo::create_shared_eyebuffer(GLuint* texture_handle) {
    // Create the shared eye texture handle
    glGenTextures(1, texture_handle);
    glBindTexture(GL_TEXTURE_2D, *texture_handle);

    // Set the texture parameters for the texture that the FBO will be mapped into
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
#ifdef __ANDROID__
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
#else
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
#endif
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, display_params::width_pixels, display_params::height_pixels, 0, GL_RGB,
                 GL_UNSIGNED_BYTE, nullptr);

    // Unbind texture
    glBindTexture(GL_TEXTURE_2D, 0);
}

void gldemo::create_FBO(const GLuint* texture_handle, GLuint* fbo, GLuint* depth_target) {
    // Create a framebuffer to draw some things to the eye texture
    glGenFramebuffers(1, fbo);

    // Bind the FBO as the active framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, *fbo);
    glGenRenderbuffers(1, depth_target);
    glBindRenderbuffer(GL_RENDERBUFFER, *depth_target);
#ifdef __ANDROID__
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, display_params::width_pixels, display_params::height_pixels);
#else
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, display_params::width_pixels, display_params::height_pixels);
#endif
    // glRenderbufferStorageMultisample(GL_RENDERBUFFER, fboSampleCount, GL_DEPTH_COMPONENT, display_params::width_pixels,
    // display_params::height_pixels);

    glBindRenderbuffer(GL_RENDERBUFFER, 0);

    // Bind eyebuffer texture
    spdlog::get(name_)->info("About to bind eyebuffer texture, texture handle: {}", *texture_handle);

    glBindTexture(GL_TEXTURE_2D, *texture_handle);
#ifdef __ANDROID__
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, *texture_handle, 0);
#else
    glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, *texture_handle, 0);
#endif
    glBindTexture(GL_TEXTURE_2D, 0);

    // attach a renderbuffer to depth attachment point
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, *depth_target);

    // Unbind FBO
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

PLUGIN_MAIN(gldemo)