File plugin.cpp
File List > plugins > timewarp_gl > plugin.cpp
Go to the documentation of this file
#define GL_GLEXT_PROTOTYPES
#ifdef __ANDROID__
# define EGL_EGLEXT_PROTOTYPES 1
# include
# include
# include
# include
# include
#else
# if defined(_WIN32) || defined(_WIN64)
# include
# endif
// clang-format off
# include // GLEW has to be loaded before other GL libraries
# if !defined(_WIN32) && !defined(_WIN64)
# include
# endif
// clang-format on
#endif
#include "plugin.hpp"
#ifdef __ANDROID__
# include "illixr/extended_window.hpp"
#endif
#include "illixr/error_util.hpp"
#include "illixr/global_module_defs.hpp"
#include "illixr/math_util.hpp"
#include "illixr/shader_util.hpp"
#include "shaders/timewarp_shader.hpp"
#include
#include
#include
#ifdef __ANDROID__
# include
#else
# include
# include
# include
#endif
#include
using namespace ILLIXR;
using namespace ILLIXR::data_format;
#if defined(_WIN32) || defined(_WIN64)
typedef BOOL(WINAPI* wglSwapIntervalEXTProc)(int interval);
#elif !defined(__ANDROID__)
typedef void (*glXSwapIntervalEXTProc)(Display* display_, GLXDrawable drawable, int interval);
#endif
const record_header timewarp_gpu_record{"timewarp_gpu",
{
{"iteration_no", typeid(std::size_t)},
{"wall_time_start", typeid(time_point)},
{"wall_time_stop", typeid(time_point)},
{"gpu_time_duration", typeid(std::chrono::nanoseconds)},
}};
const record_header mtp_record{"mtp_record",
{
{"iteration_no", typeid(std::size_t)},
{"vsync", typeid(time_point)},
{"imu_to_display", typeid(std::chrono::nanoseconds)},
{"predict_to_display", typeid(std::chrono::nanoseconds)},
{"render_to_display", typeid(std::chrono::nanoseconds)},
}};
timewarp_gl::timewarp_gl(const std::string& name, phonebook* pb)
: timewarp_type{name, pb}
, 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>()}
#ifndef ENABLE_MONADO
, eyebuffer_{switchboard_->get_reader<rendered_frame>("eyebuffer")}
, vsync_estimate_{switchboard_->get_writer<switchboard::event_wrapper<time_point>>("vsync_estimate")}
, offload_data_{switchboard_->get_writer<pose::texture_pose>("texture_pose")}
, mtp_logger_{record_logger_}
// TODO: Use #198 to configure this.
// This is useful for experiments which seek to evaluate the end-effect of timewarp vs no-timewarp.
// Timewarp poses a "second channel" by which pose data can correct the video stream,
// which results in a "multipath" between the pose and the video stream.
// In production systems, this is certainly a good thing, but it makes the system harder to analyze.
, disable_warp_{switchboard_->get_env_bool("ILLIXR_TIMEWARP_DISABLE", "False")}
, enable_offload_{switchboard_->get_env_bool("ILLIXR_OFFLOAD_ENABLE", "False")}
#else
, signal_quad_{switchboard_->get_writer<signal_to_quad>("signal_quad")}
#endif
, timewarp_gpu_logger_{record_logger_}
, hologram_{switchboard_->get_writer<hologram_input>("hologram_in")} {
spdlogger(switchboard_->get_env_char("TIMEWARP_GL_LOG_LEVEL"));
#ifndef ENABLE_MONADO
const std::shared_ptr<xlib_gl_extended_window> x_win = phonebook_->lookup_impl<xlib_gl_extended_window>();
# if defined(_WIN32) || defined(_WIN64)
hwnd_ = x_win->hwnd_;
hdc_ = x_win->hdc_;
# else
display_ = x_win->display_;
root_window_ = x_win->window_;
# ifdef __ANDROID__
surface_ = x_win->surface;
# endif
# endif
context_ = x_win->context_;
#else
// If we use Monado, timewarp_gl must create its own GL context because the extended window isn't used
# ifdef __ANDROID__
display_ = eglGetDisplay(EGL_DEFAULT_DISPLAY);
EGLint major_version, minor_version;
eglInitialize(display_, &major_version, &minor_version);
const EGLint attribs[] = {// EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8,
EGL_ALPHA_SIZE, 0,
// EGL_DEPTH_SIZE, 24,
EGL_NONE};
// EGLint w, h, format;
EGLint numConfigs;
EGLConfig config = nullptr;
eglChooseConfig(display_, attribs, &config, 1, &numConfigs);
std::unique_ptr<EGLConfig[]> supportedConfigs(new EGLConfig[numConfigs]);
assert(supportedConfigs);
eglChooseConfig(display_, attribs, supportedConfigs.get(), numConfigs, &numConfigs);
assert(numConfigs);
auto i = 0;
for (; i < numConfigs; i++) {
auto& cfg = supportedConfigs[i];
EGLint r, g, b, d;
if (eglGetConfigAttrib(display_, cfg, EGL_RED_SIZE, &r) && eglGetConfigAttrib(display_, cfg, EGL_GREEN_SIZE, &g) &&
eglGetConfigAttrib(display_, cfg, EGL_BLUE_SIZE, &b) && eglGetConfigAttrib(display_, cfg, EGL_DEPTH_SIZE, &d) &&
r == 8 && g == 8 && b == 8 && d == 24) {
config = supportedConfigs[i];
break;
}
}
if (i == numConfigs) {
config = supportedConfigs[0];
}
if (config == nullptr) {
return;
}
EGLint ctxattrb[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
context_ = eglCreateContext(display_, config, EGL_NO_CONTEXT, ctxattrb);
surface_ = EGL_NO_SURFACE;
// std::cout << "Timewarp creating GL Context" << std::endl;
// GLint attr[] = {GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None};
// XVisualInfo* vi;
// if (!(display_ = XOpenDisplay(NULL))) {
// fprintf(stderr, "cannot connect to X server\n\n");
// exit(1);
// }
//
// /* get root window */
// root = DefaultRootWindow(display_);
//
// /* get visual matching attr */
// if (!(vi = glXChooseVisual(display_, 0, attr))) {
// fprintf(stderr, "no appropriate visual found\n\n");
// exit(1);
// }
//
// /* create a context using the root window */
// if (!(context_ = glXCreateContext(display_, vi, NULL, GL_TRUE))) {
// fprintf(stderr, "failed to create context\n\n");
// exit(1);
// }
# else
std::cout << "Timewarp creating GL Context" << std::endl;
GLint attr[] = {GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None};
XVisualInfo* vi;
/* open display */
if (!(display_ = XOpenDisplay(NULL))) {
fprintf(stderr, "cannot connect to X server\n\n");
exit(1);
}
/* get root_window_ window */
root_window_ = DefaultRootWindow(display_);
/* get visual matching attr */
if (!(vi = glXChooseVisual(display_, 0, attr))) {
fprintf(stderr, "no appropriate visual found\n\n");
exit(1);
}
/* create a context using the root_window_ window */
if (!(context_ = glXCreateContext(display_, vi, NULL, GL_TRUE))) {
fprintf(stderr, "failed to create context\n\n");
exit(1);
}
# endif
#endif
client_backend_ = graphics_api::TBD;
rendering_ready_ = false;
image_handles_ready_ = false;
switchboard_->schedule<image_handle>(id_, "image_handle", [this](switchboard::ptr<const image_handle> handle, std::size_t) {
// only 2 swapchains (for the left and right eye) are supported for now.
#ifdef ENABLE_MONADO
static bool left_output_ready = false, right_output_ready = false;
#else
static bool left_output_ready = true, right_output_ready = true;
#endif
switch (handle->usage) {
case swapchain_usage::LEFT_SWAPCHAIN: {
this->eye_image_handles_[0].push_back(*handle);
this->eye_swapchains_size_[0] = handle->num_images;
break;
}
case swapchain_usage::RIGHT_SWAPCHAIN: {
this->eye_image_handles_[1].push_back(*handle);
this->eye_swapchains_size_[1] = handle->num_images;
break;
}
#ifdef ENABLE_MONADO
case swapchain_usage::LEFT_RENDER: {
this->eye_output_handles_[0] = *handle;
left_output_ready = true;
break;
}
case swapchain_usage::RIGHT_RENDER: {
this->eye_output_handles_[1] = *handle;
right_output_ready = true;
break;
}
#endif
default: {
spdlog::get(name_)->warn("Invalid swapchain usage provided");
break;
}
}
if (client_backend_ == graphics_api::TBD) {
client_backend_ = handle->type;
} else {
assert(client_backend_ == handle->type);
}
if (this->eye_image_handles_[0].size() == this->eye_swapchains_size_[0] &&
this->eye_image_handles_[1].size() == this->eye_swapchains_size_[1] && left_output_ready && right_output_ready) {
image_handles_ready_ = true;
}
});
#ifndef __ANDROID__
this->_setup();
#endif
#ifdef ENABLE_MONADO
switchboard_->schedule<rendered_frame>(id_, "eyebuffer", [this](switchboard::ptr<const rendered_frame> datum, std::size_t) {
this->warp(datum);
});
#endif
}
GLubyte* timewarp_gl::read_texture_image() {
const unsigned mem_size = display_params::width_pixels * display_params::height_pixels * 3;
auto* pixels = new GLubyte[mem_size];
// Start timer
time_point start_get_tex_time = clock_->now();
// Read the contents of the default framebuffer to the PBO
glBindBuffer(GL_PIXEL_PACK_BUFFER, PBO_buffer_);
#ifdef __ANDROID__
// Transfer texture image from GPU to Pinned Memory(CPU)
GLubyte* ptr = nullptr;
// Copy texture to CPU memory
memcpy(pixels, ptr, mem_size);
#else
glReadPixels(0, 0, display_params::width_pixels, display_params::height_pixels, GL_RGB, GL_UNSIGNED_BYTE, pixels);
#endif
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
// Record the image collection time
offload_duration_ = clock_->now() - start_get_tex_time;
#ifndef NDEBUG
double time = duration_to_double<std::milli>(offload_duration_);
spdlog::get(name_)->debug("Texture image collecting time: {} ms", time);
#endif
return pixels;
}
GLuint timewarp_gl::convert_vk_format_to_gl(int64_t vk_format
#ifdef __ANDROID__
,
GLint swizzle_mask[]
#endif
) {
switch (vk_format) {
case VK_FORMAT_R8G8B8A8_UNORM:
return GL_RGBA8;
case VK_FORMAT_B8G8R8A8_SRGB:
#ifdef __ANDROID__
{
swizzle_mask[0] = GL_BLUE;
swizzle_mask[2] = GL_RED;
}
#endif
case VK_FORMAT_R8G8B8A8_SRGB:
return GL_SRGB8_ALPHA8;
default:
return 0;
}
}
#ifndef __ANDROID__
void timewarp_gl::import_vulkan_image(const vk_image_handle& vk_handle, swapchain_usage usage) {
# if defined(_WIN32) || defined(_WIN64)
const bool gl_result = static_cast<bool>(wglMakeCurrent(hdc_, context_));
# else
[[maybe_unused]] const bool gl_result = static_cast<bool>(glXMakeCurrent(display_, root_window_, context_));
# endif
assert(gl_result && "glXMakeCurrent should not fail");
assert(GLEW_EXT_memory_object_fd && "[timewarp_gl] Missing object memory extensions for Vulkan-GL interop");
// first get the memory handle of the vulkan object
GLuint memory_handle;
GLint dedicated = GL_TRUE;
glCreateMemoryObjectsEXT(1, &memory_handle);
glMemoryObjectParameterivEXT(memory_handle, GL_DEDICATED_MEMORY_OBJECT_EXT, &dedicated);
glImportMemoryFdEXT(memory_handle, vk_handle.allocation_size, GL_HANDLE_TYPE_OPAQUE_FD_EXT, vk_handle.file_descriptor);
// then use the imported memory as the opengl texture.
// since we're writing to an intermediate texture that's the same memory format as Monado's layer renderer,
// there's no need to reformat anything.
GLuint format = convert_vk_format_to_GL(vk_handle.format);
assert(format != 0 && "Given Vulkan format not handled!");
GLuint image_handle;
glGenTextures(1, &image_handle);
glBindTexture(GL_TEXTURE_2D, image_handle);
glTextureStorageMem2DEXT(image_handle, 1, format, static_cast<GLsizei>(vk_handle.width),
static_cast<GLsizei>(vk_handle.height), memory_handle, 0);
float color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
switch (usage) {
case swapchain_usage::LEFT_SWAPCHAIN: {
eye_swapchains_[0].push_back(image_handle);
break;
}
case swapchain_usage::RIGHT_SWAPCHAIN: {
eye_swapchains_[1].push_back(image_handle);
break;
}
case swapchain_usage::LEFT_RENDER: {
eye_output_textures_[0] = image_handle;
break;
}
case swapchain_usage::RIGHT_RENDER: {
eye_output_textures_[1] = image_handle;
break;
}
default: {
assert(false && "Invalid swapchain usage");
// break;
}
}
}
#endif
void timewarp_gl::build_timewarp(HMD::hmd_info_t& hmd_info) {
// Calculate the number of vertices+indices in the distortion mesh.
num_distortion_vertices_ = (hmd_info.eye_tiles_high + 1) * (hmd_info.eye_tiles_wide + 1);
num_distortion_indices_ = hmd_info.eye_tiles_high * hmd_info.eye_tiles_wide * 6;
// Allocate memory for the elements/indices array.
distortion_indices_.resize(num_distortion_indices_);
// This is just a simple grid/plane index array, nothing fancy.
// Same for both eye distortions, too!
for (int y = 0; y < hmd_info.eye_tiles_high; y++) {
for (int x = 0; x < hmd_info.eye_tiles_wide; x++) {
const int offset = (y * hmd_info.eye_tiles_wide + x) * 6;
// How are the indices figured out?
distortion_indices_[offset + 0] = (GLuint) ((y + 0) * (hmd_info.eye_tiles_wide + 1) + (x + 0));
distortion_indices_[offset + 1] = (GLuint) ((y + 1) * (hmd_info.eye_tiles_wide + 1) + (x + 0));
distortion_indices_[offset + 2] = (GLuint) ((y + 0) * (hmd_info.eye_tiles_wide + 1) + (x + 1));
distortion_indices_[offset + 3] = (GLuint) ((y + 0) * (hmd_info.eye_tiles_wide + 1) + (x + 1));
distortion_indices_[offset + 4] = (GLuint) ((y + 1) * (hmd_info.eye_tiles_wide + 1) + (x + 0));
distortion_indices_[offset + 5] = (GLuint) ((y + 1) * (hmd_info.eye_tiles_wide + 1) + (x + 1));
}
}
// There are `num_distortion_vertices_` distortion coordinates for each color channel (3) of each eye (2).
// These are NOT the coordinates of the distorted vertices. They are *coefficients* that will be used to
// offset the UV coordinates of the distortion mesh.
std::array<std::array<std::vector<HMD::mesh_coord2d_t>, HMD::NUM_COLOR_CHANNELS>, HMD::NUM_EYES> distort_coords;
for (auto& eye_coords : distort_coords) {
for (auto& channel_coords : eye_coords) {
channel_coords.resize(num_distortion_vertices_);
}
}
HMD::build_distortion_meshes(distort_coords, hmd_info);
// Allocate memory for position and UV CPU buffers.
const std::size_t num_elems_pos_uv = HMD::NUM_EYES * num_distortion_vertices_;
distortion_positions_.resize(num_elems_pos_uv);
distortion_uv0_.resize(num_elems_pos_uv);
distortion_uv1_.resize(num_elems_pos_uv);
distortion_uv2_.resize(num_elems_pos_uv);
for (int eye = 0; eye < HMD::NUM_EYES; eye++) {
for (int y = 0; y <= hmd_info.eye_tiles_high; y++) {
for (int x = 0; x <= hmd_info.eye_tiles_wide; x++) {
const int index = y * (hmd_info.eye_tiles_wide + 1) + x;
// Set the physical distortion mesh coordinates. These are rectangular/grid-like, not distorted.
// The distortion is handled by the UVs, not the actual mesh coordinates!
distortion_positions_[eye * num_distortion_vertices_ + index].x =
(-1.0f + 2.0f * (static_cast<float>(x) / static_cast<float>(hmd_info.eye_tiles_wide)));
distortion_positions_[eye * num_distortion_vertices_ + index].y =
(-1.0f +
2.0f *
((static_cast<float>(hmd_info.eye_tiles_high) - static_cast<float>(y)) /
static_cast<float>(hmd_info.eye_tiles_high)) *
(static_cast<float>(hmd_info.eye_tiles_high * hmd_info.tile_pixels_high) /
static_cast<float>(hmd_info.display_pixels_high)));
distortion_positions_[eye * num_distortion_vertices_ + index].z = 0.0f;
// Use the previously-calculated distort_coords to set the UVs on the distortion mesh
distortion_uv0_[eye * num_distortion_vertices_ + index].u = distort_coords[eye][0][index].x;
distortion_uv0_[eye * num_distortion_vertices_ + index].v = distort_coords[eye][0][index].y;
distortion_uv1_[eye * num_distortion_vertices_ + index].u = distort_coords[eye][1][index].x;
distortion_uv1_[eye * num_distortion_vertices_ + index].v = distort_coords[eye][1][index].y;
distortion_uv2_[eye * num_distortion_vertices_ + index].u = distort_coords[eye][2][index].x;
distortion_uv2_[eye * num_distortion_vertices_ + index].v = distort_coords[eye][2][index].y;
}
}
}
// 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);
}
/* Calculate timewarp transform from projection matrix, view matrix, etc */
void timewarp_gl::calculate_time_warp_transform(Eigen::Matrix4f& transform, const Eigen::Matrix4f& render_projection_matrix,
const Eigen::Matrix4f& render_view_matrix,
const Eigen::Matrix4f& new_view_matrix) {
// Eigen stores matrices internally in column-major order.
// However, the (i,j) accessors are row-major (i.e, the first argument
// is which row, and the second argument is which column.)
Eigen::Matrix4f tex_coord_projection;
tex_coord_projection << 0.5f * render_projection_matrix(0, 0), 0.0f, 0.5f * render_projection_matrix(0, 2) - 0.5f, 0.0f,
0.0f, 0.5f * render_projection_matrix(1, 1), 0.5f * render_projection_matrix(1, 2) - 0.5f, 0.0f, 0.0f, 0.0f, -1.0f,
0.0f, 0.0f, 0.0f, 0.0f, 1.0f;
// Calculate the delta between the view matrix used for rendering and
// a more recent or predicted view matrix based on new sensor input.
Eigen::Matrix4f inverse_render_view_matrix = render_view_matrix.inverse();
Eigen::Matrix4f delta_view_matrix = inverse_render_view_matrix * new_view_matrix;
delta_view_matrix(0, 3) = 0.0f;
delta_view_matrix(1, 3) = 0.0f;
delta_view_matrix(2, 3) = 0.0f;
// Accumulate the transforms.
transform = tex_coord_projection * delta_view_matrix;
}
#ifndef ENABLE_MONADO
// Get the estimated time of the next swap/next Vsync.
// This is an estimate, used to wait until *just* before vsync.
[[nodiscard]] time_point timewarp_gl::get_next_swap_time_estimate() const {
return time_last_swap_ + display_params::period;
}
// Get the estimated amount of time to put the CPU thread to sleep,
// given a specified percentage of the total Vsync period to delay.
[[maybe_unused]] [[nodiscard]] duration timewarp_gl::estimate_time_to_sleep(const double frame_percentage) const {
return std::chrono::duration_cast<duration>((get_next_swap_time_estimate() - clock_->now()) * frame_percentage);
}
#endif
void timewarp_gl::_setup() {
#ifdef __ANDROID__
// Wait a vsync for gldemo to go first.
// This first time_last_swap will be "out of phase" with actual vsync.
// The second one should be on the dot, since we don't exit the first until actual vsync.
time_last_swap_ = clock_->now() + display_params::period;
#endif
// Generate reference HMD and physical body dimensions
HMD::get_default_hmd_info(display_params::width_pixels, display_params::height_pixels, display_params::width_meters,
display_params::height_meters, display_params::lens_separation,
display_params::meters_per_tan_angle, display_params::aberration, hmd_info_);
// Construct timewarp meshes and other data
build_timewarp(hmd_info_);
// includes setting swap interval
#ifdef __ANDROID__
# ifdef ENABLE_MONADO
sem_wait(&lock_->sem_monado);
# else
lock_->get_lock();
# endif
#endif
[[maybe_unused]] const bool gl_result_0 =
#if defined(_WIN32) || defined(_WIN64)
static_cast<bool>(wglMakeCurrent(hdc_, context_));
#elif defined(__ANDROID__)
static_cast<bool>(eglMakeCurrent(display_, surface_, surface_, context_));
#else
static_cast<bool>(glXMakeCurrent(display_, root_window_, context_));
#endif
assert(gl_result_0 && "glXMakeCurrent should not fail");
// set swap interval for 1
// TODO do we still need this if timewarp is not doing the presenting?
#if defined(_WIN32) || defined(_WIN64)
auto swap_interval_ext = (wglSwapIntervalEXTProc) wglGetProcAddress("wglSwapIntervalEXT");
swap_interval_ext(1);
#elif defined(__ANDROID__)
eglSwapInterval(display_, 1);
#else
auto glx_swap_interval_ext = (glXSwapIntervalEXTProc) glXGetProcAddressARB((const GLubyte*) "glx_swap_interval_ext");
glx_swap_interval_ext(display_, root_window_, 1);
#endif
// Init and verify GLEW
#ifndef __ANDROID__
glewExperimental = GL_TRUE;
const GLenum glew_err = glewInit();
if (glew_err != GLEW_OK) {
spdlog::get(name_)->error("[timewarp_gl] GLEW Error: {}", reinterpret_cast<const char*>(glewGetErrorString(glew_err)));
ILLIXR::abort("[timewarp_gl] Failed to initialize GLEW");
}
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(message_callback, nullptr);
#endif
// Create and bind global VAO object
glGenVertexArrays(1, &tw_vao);
glBindVertexArray(tw_vao);
#ifdef USE_ALT_EYE_FORMAT
timewarp_shader_program_ =
init_and_link(time_warp_chromatic_vertex_program_GLSL, time_warp_chromatic_fragment_program_GLSL_alternative);
#else
timewarp_shader_program_ =
init_and_link(time_warp_chromatic_vertex_program_GLSL, time_warp_chromatic_fragment_program_GLSL);
#endif
// Acquire attribute and uniform locations from the compiled and linked shader program
distortion_pos_attr_ = glGetAttribLocation(timewarp_shader_program_, "vertexPosition");
distortion_uv0_attr_ = glGetAttribLocation(timewarp_shader_program_, "vertexUv0");
distortion_uv1_attr_ = glGetAttribLocation(timewarp_shader_program_, "vertexUv1");
distortion_uv2_attr_ = glGetAttribLocation(timewarp_shader_program_, "vertexUv2");
tw_start_transform_uniform_ = glGetUniformLocation(timewarp_shader_program_, "TimeWarpStartTransform");
tw_end_transform_uniform_ = glGetUniformLocation(timewarp_shader_program_, "TimeWarpEndTransform");
tw_eye_index_uniform_ = glGetUniformLocation(timewarp_shader_program_, "ArrayLayer");
eye_sampler_0_ = glGetUniformLocation(timewarp_shader_program_, "Texture[0]");
eye_sampler_1_ = glGetUniformLocation(timewarp_shader_program_, "Texture[1]");
flip_y_uniform_ = glGetUniformLocation(timewarp_shader_program_, "flipY");
// Config distortion mesh position vbo
glGenBuffers(1, &distortion_positions_vbo_);
glBindBuffer(GL_ARRAY_BUFFER, distortion_positions_vbo_);
const std::size_t num_elems_pos_uv = HMD::NUM_EYES * num_distortion_vertices_;
HMD::mesh_coord3d_t* const distortion_positions_data = distortion_positions_.data();
assert(distortion_positions_data != nullptr && "Timewarp allocation should not fail");
glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(num_elems_pos_uv * sizeof(HMD::mesh_coord3d_t)),
distortion_positions_data, GL_STATIC_DRAW);
glVertexAttribPointer(distortion_pos_attr_, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
// glEnableVertexAttribArray(distortion_pos_attr_);
// Config distortion uv0 vbo
glGenBuffers(1, &distortion_uv0_vbo_);
glBindBuffer(GL_ARRAY_BUFFER, distortion_uv0_vbo_);
HMD::uv_coord_t* const distortion_uv0_data = distortion_uv0_.data();
assert(distortion_uv0_data != nullptr && "Timewarp allocation should not fail");
glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(num_elems_pos_uv * sizeof(HMD::uv_coord_t)), distortion_uv0_data,
GL_STATIC_DRAW);
glVertexAttribPointer(distortion_uv0_attr_, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
// glEnableVertexAttribArray(distortion_uv0_attr_);
// Config distortion uv1 vbo
glGenBuffers(1, &distortion_uv1_vbo_);
glBindBuffer(GL_ARRAY_BUFFER, distortion_uv1_vbo_);
HMD::uv_coord_t* const distortion_uv1_data = distortion_uv1_.data();
assert(distortion_uv1_data != nullptr && "Timewarp allocation should not fail");
glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(num_elems_pos_uv * sizeof(HMD::uv_coord_t)), distortion_uv1_data,
GL_STATIC_DRAW);
glVertexAttribPointer(distortion_uv1_attr_, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
// glEnableVertexAttribArray(distortion_uv1_attr_);
// Config distortion uv2 vbo
glGenBuffers(1, &distortion_uv2_vbo_);
glBindBuffer(GL_ARRAY_BUFFER, distortion_uv2_vbo_);
HMD::uv_coord_t* const distortion_uv2_data = distortion_uv2_.data();
assert(distortion_uv2_data != nullptr && "Timewarp allocation should not fail");
glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(num_elems_pos_uv * sizeof(HMD::uv_coord_t)), distortion_uv2_data,
GL_STATIC_DRAW);
glVertexAttribPointer(distortion_uv2_attr_, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
// glEnableVertexAttribArray(distortion_uv2_attr_);
// Config distortion mesh indices vbo
glGenBuffers(1, &distortion_indices_vbo_);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, distortion_indices_vbo_);
GLuint* const distortion_indices_data = distortion_indices_.data();
assert(distortion_indices_data != nullptr && "Timewarp allocation should not fail");
glBufferData(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLsizeiptr>(num_distortion_indices_ * sizeof(GLuint)),
distortion_indices_data, GL_STATIC_DRAW);
if (enable_offload_) {
// Config PBO for texture image collection
glGenBuffers(1, &PBO_buffer_);
glBindBuffer(GL_PIXEL_PACK_BUFFER, PBO_buffer_);
glBufferData(GL_PIXEL_PACK_BUFFER, display_params::width_pixels * display_params::height_pixels * 3, nullptr,
GL_STREAM_DRAW);
}
[[maybe_unused]] const bool gl_result_1 =
#if defined(_WIN32) || defined(_WIN64)
static_cast<bool>(wglMakeCurrent(nullptr, nullptr));
#elif defined(__ANDROID__)
static_cast<bool>(eglMakeCurrent(display_, nullptr, nullptr, nullptr));
# ifdef ENABLE_MONADO
sem_post(&lock_->sem_illixr);
# else
lock_->release_lock();
# endif
#else
static_cast<bool>(glXMakeCurrent(display_, None, nullptr));
#endif
assert(gl_result_1 && "glXMakeCurrent should not fail");
}
void timewarp_gl::_prepare_rendering() {
#ifndef __ANDROID__
[[maybe_unused]] const bool gl_result =
# if defined(_WIN32) || defined(_WIN64)
static_cast<bool>(wglMakeCurrent(hdc_, context_));
# else
static_cast<bool>(glXMakeCurrent(display_, root_window_, context_));
# endif
assert(gl_result && "glXMakeCurrent should not fail");
#endif
if (!rendering_ready_) {
#ifdef __ANDROID__
while (!image_handles_ready_)
;
#endif
assert(image_handles_ready_);
for (int eye = 0; eye < 2; eye++) {
uint32_t num_images = eye_image_handles_[eye][0].num_images;
for (uint32_t image_index = 0; image_index < num_images; image_index++) {
image_handle image = eye_image_handles_[eye][image_index];
if (client_backend_ == graphics_api::OPENGL) {
eye_swapchains_[eye].push_back(image.gl_handle);
} else {
#ifdef __ANDROID__
vulkanGL_interop_buffer(image.vk_buffer_handle, image.usage);
#else
import_vulkan_image(image.vk_handle, image.usage);
#endif
}
}
}
// If we're using Monado, we need to import the eye output textures to render to.
// Otherwise, with native, we can directly create the textures.
for (int eye = 0; eye < 2; eye++) {
#ifdef ENABLE_MONADO
image_handle image = eye_output_handles_[eye];
# ifdef __ANDROID__
vulkanGL_interop_buffer(image.vk_buffer_handle, image.usage);
# else
import_vulkan_image(image.vk_handle, image.usage);
# endif
#else
GLuint eye_output_texture;
glGenTextures(1, &eye_output_texture);
eye_output_textures_[eye] = eye_output_texture;
glBindTexture(GL_TEXTURE_2D, eye_output_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, display_params::width_pixels * 0.5f, display_params::height_pixels, 0,
GL_RGB, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
#endif
// Once the eye output textures are created, we bind them to the framebuffer
GLuint framebuffer;
glGenFramebuffers(1, &framebuffer);
eye_framebuffers_[eye] = framebuffer;
glBindFramebuffer(GL_FRAMEBUFFER, eye_framebuffers_[eye]);
glBindTexture(GL_TEXTURE_2D, eye_output_textures_[eye]);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, eye_output_textures_[eye], 0);
uint32_t attachment = GL_COLOR_ATTACHMENT0;
glDrawBuffers(1, &attachment);
}
rendering_ready_ = true;
}
}
void timewarp_gl::warp(const switchboard::ptr<const rendered_frame>& most_recent_frame) {
#ifdef __ANDROID__
# ifdef ENABLE_MONADO
sem_wait(&lock_->sem_monado);
# else
lock_->get_lock();
# endif
[[maybe_unused]] const bool gl_result = static_cast<bool>(eglMakeCurrent(display_, surface_, surface_, context_));
assert(gl_result && "eglMakeCurrent should not fail");
#endif
if (!rendering_ready_)
_prepare_rendering();
assert(this->image_handles_ready_ && rendering_ready_);
// Use the timewarp program
glUseProgram(timewarp_shader_program_);
// Generate "starting" view matrix, from the pose sampled at the time of rendering the frame
Eigen::Matrix4f view_matrix = Eigen::Matrix4f::Identity();
view_matrix.block(0, 0, 3, 3) = most_recent_frame->render_pose.pose.orientation.toRotationMatrix();
// We simulate two asynchronous view matrices, one at the beginning of
// display refresh, and one at the end of display refresh. The
// distortion shader will leap between these two predictive view
// transformations as it renders across the horizontal view,
// compensating for display panel refresh delay (wow!)
Eigen::Matrix4f view_matrix_begin = Eigen::Matrix4f::Identity();
Eigen::Matrix4f view_matrix_end = Eigen::Matrix4f::Identity();
const pose::fast_head_pose_type latest_pose =
disable_warp_ ? most_recent_frame->render_pose : pose_prediction_->get_fast_pose();
view_matrix_begin.block(0, 0, 3, 3) = latest_pose.pose.orientation.toRotationMatrix();
// TODO: We set the "end" pose to the same as the beginning pose, but this really should be the pose for
// `display_period` later
view_matrix_end = view_matrix_begin;
// Calculate the timewarp transformation matrices. These are a product
// of the last-known-good view matrix and the predictive transforms.
Eigen::Matrix4f time_warp_start_transform4x4;
Eigen::Matrix4f time_warp_end_transform4x4;
// Calculate timewarp transforms using predictive view transforms
calculate_time_warp_transform(time_warp_start_transform4x4, basic_projection_, view_matrix, view_matrix_begin);
calculate_time_warp_transform(time_warp_end_transform4x4, basic_projection_, view_matrix, view_matrix_end);
glUniformMatrix4fv(static_cast<GLint>(tw_start_transform_uniform_), 1, GL_FALSE,
(GLfloat*) (time_warp_start_transform4x4.data()));
glUniformMatrix4fv(static_cast<GLint>(tw_end_transform_uniform_), 1, GL_FALSE,
(GLfloat*) (time_warp_end_transform4x4.data()));
// Flip the Y axis if the client is using a Vulkan backend
glUniform1i(static_cast<GLint>(flip_y_uniform_), false);
// Debugging aid, toggle switch for rendering in the fragment shader
glUniform1i(glGetUniformLocation(timewarp_shader_program_, "ArrayIndex"), 0);
glUniform1i(static_cast<GLint>(eye_sampler_0_), 0);
#if defined(__ANDROID__) && !defined(USE_ALT_EYE_FORMAT)
// Bind the shared texture handle
glBindTexture(GL_TEXTURE_2D_ARRAY, most_recent_frame->texture_handle);
#endif
glBindVertexArray(tw_vao);
auto gpu_start_wall_time = clock_->now();
#ifndef __ANDROID__
GLuint query = 0;
#endif
GLuint64 elapsed_time = 0;
#ifndef __ANDROID__
glGenQueries(1, &query);
glBeginQuery(GL_TIME_ELAPSED, query);
#endif
// Loop over each eye
for (int eye = 0; eye < HMD::NUM_EYES; eye++) {
// Choose the appropriate texture to render to
glBindFramebuffer(GL_FRAMEBUFFER, eye_framebuffers_[eye]);
glViewport(0, 0, display_params::width_pixels * 0.5, display_params::height_pixels);
#ifdef __ANDROID__
glClearColor(1.0, 0.0, 1.0, 1.0);
#else
glClearColor(1.0, 1.0, 1.0, 1.0);
#endif
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glDepthFunc(GL_LEQUAL);
#if !defined(__ANDROID__) || defined(USE_ALT_EYE_FORMAT) // If we're using Monado-style buffers we need to rebind eyebuffers.
[[maybe_unused]] const bool is_texture =
static_cast<bool>(glIsTexture(eye_swapchains_[eye][most_recent_frame->swapchain_indices[eye]]));
assert(is_texture && "The requested image is not a texture!");
glBindTexture(GL_TEXTURE_2D, eye_swapchains_[eye][most_recent_frame->swapchain_indices[eye]]);
#endif
// The distortion_positions_vbo_ GPU buffer already contains
// the distortion mesh for both eyes! They are contiguously
// laid out in GPU memory. Therefore, on each eye render,
// we set the attribute pointer to be offset by the full
// eye's distortion mesh size, rendering the correct eye mesh
// to that region of the screen. This prevents re-uploading
// GPU data for each eye.
glBindBuffer(GL_ARRAY_BUFFER, distortion_positions_vbo_);
glVertexAttribPointer(distortion_pos_attr_, 3, GL_FLOAT, GL_FALSE, 0,
(void*) (eye * num_distortion_vertices_ * sizeof(HMD::mesh_coord3d_t)));
glEnableVertexAttribArray(distortion_pos_attr_);
// We do the exact same thing for the UV GPU memory.
glBindBuffer(GL_ARRAY_BUFFER, distortion_uv0_vbo_);
glVertexAttribPointer(distortion_uv0_attr_, 2, GL_FLOAT, GL_FALSE, 0,
(void*) (eye * num_distortion_vertices_ * sizeof(HMD::mesh_coord2d_t)));
glEnableVertexAttribArray(distortion_uv0_attr_);
// We do the exact same thing for the UV GPU memory.
glBindBuffer(GL_ARRAY_BUFFER, distortion_uv1_vbo_);
glVertexAttribPointer(distortion_uv1_attr_, 2, GL_FLOAT, GL_FALSE, 0,
(void*) (eye * num_distortion_vertices_ * sizeof(HMD::mesh_coord2d_t)));
glEnableVertexAttribArray(distortion_uv1_attr_);
// We do the exact same thing for the UV GPU memory.
glBindBuffer(GL_ARRAY_BUFFER, distortion_uv2_vbo_);
glVertexAttribPointer(distortion_uv2_attr_, 2, GL_FLOAT, GL_FALSE, 0,
(void*) (eye * num_distortion_vertices_ * sizeof(HMD::mesh_coord2d_t)));
glEnableVertexAttribArray(distortion_uv2_attr_);
#if defined(__ANDROID__) && !defined(USE_ALT_EYE_FORMAT) // If we are using normal ILLIXR-format eyebuffers
// Specify which layer of the eye texture we're going to be using.
// Each eye has its own layer.
glUniform1i(tw_eye_index_uniform_, eye);
#endif
// Interestingly, the element index buffer is identical for both eyes, and is
// reused for both eyes. Therefore, glDrawElements can be immediately called,
// with the UV and position buffers correctly offset.
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(num_distortion_indices_), GL_UNSIGNED_INT, (void*) nullptr);
}
glFinish();
#ifndef __ANDROID__
glEndQuery(GL_TIME_ELAPSED);
#endif
#ifdef ENABLE_MONADO
// signal quad layer in Monado
signal_quad_.put(signal_quad_.allocate(++signal_quad_seq_));
#else
// If we're not using Monado, we want to composite the left and right buffers into one
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, display_params::width_pixels, display_params::height_pixels);
// Blit the left and right color buffers onto the default color buffer
glBindFramebuffer(GL_READ_FRAMEBUFFER, eye_framebuffers_[0]);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0, 0, display_params::width_pixels * 0.5, display_params::height_pixels, 0, 0,
display_params::width_pixels * 0.5, display_params::height_pixels, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, eye_framebuffers_[1]);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0, 0, display_params::width_pixels * 0.5, display_params::height_pixels,
display_params::width_pixels * 0.5, 0, display_params::width_pixels, display_params::height_pixels,
GL_COLOR_BUFFER_BIT, GL_NEAREST);
// Call swap buffers; when vsync is enabled, this will return to the
// CPU thread once the buffers have been successfully swapped.
[[maybe_unused]] time_point time_before_swap = clock_->now();
# if defined(_WIN32) || defined(_WIN64)
SwapBuffers(hdc_);
# elif defined(__ANDROID__)
eglSwapBuffers(display_, surface_);
# else
glXSwapBuffers(display_, root_window_);
# endif
// The swap time needs to be obtained and published as soon as possible
time_last_swap_ = clock_->now();
[[maybe_unused]] time_point time_after_swap = time_last_swap_;
// Now that we have the most recent swap time, we can publish the new estimate.
vsync_estimate_.put(vsync_estimate_.allocate<switchboard::event_wrapper<time_point>>(
switchboard::event_wrapper<time_point>(get_next_swap_time_estimate())));
std::chrono::nanoseconds imu_to_display = time_last_swap_ - latest_pose.pose.sensor_time;
std::chrono::nanoseconds predict_to_display = time_last_swap_ - latest_pose.predict_computed_time;
std::chrono::nanoseconds render_to_display = time_last_swap_ - most_recent_frame->render_time;
mtp_logger_.log(record{mtp_record,
{
{iteration_no},
{time_last_swap_},
{imu_to_display},
{predict_to_display},
{render_to_display},
}});
# ifndef NDEBUG // Timewarp only has vsync estimates if we're running with native-gl
if (log_count_ > LOG_PERIOD_) {
const double time_swap = duration_to_double<std::milli>(time_after_swap - time_before_swap);
const double latency_mtd = duration_to_double<std::milli>(imu_to_display);
const double latency_ptd = duration_to_double<std::milli>(predict_to_display);
const double latency_rtd = duration_to_double<std::milli>(render_to_display);
const time_point time_next_swap = get_next_swap_time_estimate();
const double timewarp_estimate = duration_to_double<std::milli>(time_next_swap - time_last_swap_);
spdlog::get(name_)->debug("Swap time: {} ms", time_swap);
spdlog::get(name_)->debug("Motion-to-display latency: {} ms", latency_mtd);
spdlog::get(name_)->debug("Prediction-to-display latency: {} ms", latency_ptd);
spdlog::get(name_)->debug("Render-to-display latency: {} ms", latency_rtd);
spdlog::get(name_)->debug("Next swap in: {} ms in the future", timewarp_estimate);
}
# endif
// For now, it only makes sense to enable offloading in native mode
// because running timewarp with Monado will not produce a single texture.
if (enable_offload_) {
// Read texture image from texture buffer
GLubyte* image = read_texture_image();
// Publish image and pose
offload_data_.put(offload_data_.allocate<pose::texture_pose>(
pose::texture_pose{offload_duration_, image, time_last_swap_, latest_pose.pose.position,
latest_pose.pose.orientation, most_recent_frame->render_pose.pose.orientation}));
}
#endif
// retrieving the recorded elapsed time
// wait until the query result is available
#ifndef __ANDROID__
int done = 0;
glGetQueryObjectiv(query, GL_QUERY_RESULT_AVAILABLE, &done);
while (!done) {
std::this_thread::yield();
glGetQueryObjectiv(query, GL_QUERY_RESULT_AVAILABLE, &done);
}
// get the query result
glGetQueryObjectui64v(query, GL_QUERY_RESULT, &elapsed_time);
#else
[[maybe_unused]] const bool gl_result_1 = static_cast<bool>(eglMakeCurrent(display_, nullptr, nullptr, nullptr));
assert(gl_result_1 && "eglMakeCurrent should not fail");
# ifdef ENABLE_MONADO
sem_post(&lock_->sem_illixr);
# else
lock_->release_lock();
# endif
#endif
timewarp_gpu_logger_.log(record{timewarp_gpu_record,
{
{iteration_no},
{gpu_start_wall_time},
{clock_->now()},
{std::chrono::nanoseconds(elapsed_time)},
}});
#ifdef ENABLE_MONADO
// Manually increment the iteration number if timewarp is running as a plugin
++iteration_no;
#endif
// Call Hologram
hologram_.put(hologram_.allocate<hologram_input>(hologram_input(++hologram_seq_)));
#ifndef NDEBUG
if (log_count_ > LOG_PERIOD_) {
log_count_ = 0;
} else {
log_count_++;
}
#endif
}
#ifndef ENABLE_MONADO
threadloop::skip_option timewarp_gl::_p_should_skip() {
using namespace std::chrono_literals;
// Sleep for approximately 90% of the time until the next vsync.
// Scheduling granularity can't be assumed to be super accurate here,
// so don't push your luck (i.e. don't wait too long....) Tradeoff with
// MTP here. More you wait, closer to the display sync you sample the pose.
std::this_thread::sleep_for(estimate_time_to_sleep(DELAY_FRACTION));
if (image_handles_ready_.load() && eyebuffer_.get_ro_nullable() != nullptr) {
return skip_option::run;
} else {
// Null means system is nothing has been pushed yet
// because not all components are initialized yet
return skip_option::skip_and_yield;
}
}
void timewarp_gl::_p_thread_setup() {
_setup();
}
void timewarp_gl::_p_one_iteration() {
switchboard::ptr<const rendered_frame> most_recent_frame = eyebuffer_.get_ro();
warp(most_recent_frame);
}
#endif
#ifdef __ANDROID__
void timewarp_gl::vulkanGL_interop_buffer(const vk_buffer_handle& vk_buffer_handle, swapchain_usage usage) {
[[maybe_unused]] const bool gl_result = static_cast<bool>(eglMakeCurrent(display_, surface_, surface_, context_));
assert(gl_result && "glXMakeCurrent should not fail");
EGLClientBuffer native_buffer = NULL;
native_buffer = eglGetNativeClientBufferANDROID(vk_buffer_handle.ahardware_buffer);
AHardwareBuffer_Desc desc;
AHardwareBuffer_describe(vk_buffer_handle.ahardware_buffer, &desc);
EGLint attrs[] = {
EGL_IMAGE_PRESERVED_KHR,
EGL_TRUE,
(desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT) ? EGL_TRUE : EGL_FALSE,
EGL_NONE,
EGL_NONE,
EGL_NONE,
};
EGLenum source = EGL_NATIVE_BUFFER_ANDROID;
EGLImageKHR image = eglCreateImageKHR(display_, EGL_NO_CONTEXT, source, native_buffer, attrs);
GLint swizzle_mask[4] = {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA};
GLuint format = convert_vk_format_to_gl(vk_buffer_handle.format, swizzle_mask);
assert(format != 0 && "Given VK format not handled!");
spdlog::get("illixr")->debug("Format : {}", format);
GLuint image_handle;
glGenTextures(1, &image_handle);
glBindTexture(GL_TEXTURE_2D, image_handle);
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) eglGetProcAddress("glEGLImageTargetTexture2DOES");
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES) image); // Try the EXT alternative
GLenum err;
err = glGetError();
if (err != GL_NO_ERROR)
spdlog::get("illixr")->error("error {}", err);
spdlog::get("illixr")->debug("NO ERROR");
// Alternate GL_TEXTURE_2D
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, swizzle_mask[0]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, swizzle_mask[1]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, swizzle_mask[2]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, swizzle_mask[3]);
spdlog::get("illixr")->debug("SWITCH");
switch (usage) {
case data_format::swapchain_usage::LEFT_SWAPCHAIN: {
spdlog::get("illixr")->debug("Pushed LEFT_SWAPCHAIN");
eye_swapchains_[0].push_back(image_handle);
break;
}
case data_format::swapchain_usage::RIGHT_SWAPCHAIN: {
spdlog::get("illixr")->debug("Pushed RIGHT_SWAPCHAIN");
eye_swapchains_[1].push_back(image_handle);
break;
}
case data_format::swapchain_usage::LEFT_RENDER: {
spdlog::get("illixr")->debug("Pushed LEFT_RENDER");
eye_output_textures_[0] = image_handle;
break;
}
case data_format::swapchain_usage::RIGHT_RENDER: {
spdlog::get("illixr")->debug("Pushed RIGHT_RENDER");
eye_output_textures_[1] = image_handle;
break;
}
default: {
assert(false && "Invalid swapchain usage");
break;
}
}
}
# ifdef ENABLE_MONADO
void timewarp_gl::import_vulkan_semaphore(const semaphore_handle& vk_handle) {
[[maybe_unused]] const bool gl_result = static_cast<bool>(eglMakeCurrent(display_, surface_, surface_, context_));
assert(gl_result && "glXMakeCurrent should not fail");
}
# endif
#endif
PLUGIN_MAIN(timewarp_gl)