Path: blob/master/modules/openxr/extensions/openxr_frame_synthesis_extension.cpp
21155 views
/**************************************************************************/1/* openxr_frame_synthesis_extension.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "openxr_frame_synthesis_extension.h"3132#include "core/config/project_settings.h"33#include "servers/rendering/rendering_server.h"34#include "servers/xr/xr_server.h"3536#define GL_RGBA16F 0x881A37#define GL_DEPTH24_STENCIL8 0x88F03839#define VK_FORMAT_R16G16B16A16_SFLOAT 9740#define VK_FORMAT_D24_UNORM_S8_UINT 1294142OpenXRFrameSynthesisExtension *OpenXRFrameSynthesisExtension::singleton = nullptr;4344OpenXRFrameSynthesisExtension *OpenXRFrameSynthesisExtension::get_singleton() {45return singleton;46}4748void OpenXRFrameSynthesisExtension::_bind_methods() {49ClassDB::bind_method(D_METHOD("is_available"), &OpenXRFrameSynthesisExtension::is_available);5051ClassDB::bind_method(D_METHOD("is_enabled"), &OpenXRFrameSynthesisExtension::is_enabled);52ClassDB::bind_method(D_METHOD("set_enabled", "enable"), &OpenXRFrameSynthesisExtension::set_enabled);53ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");5455ClassDB::bind_method(D_METHOD("get_relax_frame_interval"), &OpenXRFrameSynthesisExtension::get_relax_frame_interval);56ClassDB::bind_method(D_METHOD("set_relax_frame_interval", "relax_frame_interval"), &OpenXRFrameSynthesisExtension::set_relax_frame_interval);57ADD_PROPERTY(PropertyInfo(Variant::BOOL, "relax_frame_interval"), "set_relax_frame_interval", "get_relax_frame_interval");5859ClassDB::bind_method(D_METHOD("skip_next_frame"), &OpenXRFrameSynthesisExtension::skip_next_frame);60}6162OpenXRFrameSynthesisExtension::OpenXRFrameSynthesisExtension() {63singleton = this;64}6566OpenXRFrameSynthesisExtension::~OpenXRFrameSynthesisExtension() {67singleton = nullptr;68}6970HashMap<String, bool *> OpenXRFrameSynthesisExtension::get_requested_extensions(XrVersion p_version) {71HashMap<String, bool *> request_extensions;7273if (GLOBAL_GET("xr/openxr/extensions/frame_synthesis")) {74request_extensions[XR_EXT_FRAME_SYNTHESIS_EXTENSION_NAME] = &frame_synthesis_ext;75}7677return request_extensions;78}7980void OpenXRFrameSynthesisExtension::on_instance_created(const XrInstance p_instance) {81// Register this as a projection view extension82if (frame_synthesis_ext) {83OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();84ERR_FAIL_NULL(openxr_api);85openxr_api->register_projection_views_extension(this);86}8788// Enable this if our extension was successfully enabled89enabled = frame_synthesis_ext;90render_state.enabled = frame_synthesis_ext;91}9293void OpenXRFrameSynthesisExtension::on_instance_destroyed() {94// Unregister this as a projection view extension.95if (frame_synthesis_ext) {96OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();97ERR_FAIL_NULL(openxr_api);98openxr_api->unregister_projection_views_extension(this);99}100101frame_synthesis_ext = false;102enabled = false;103render_state.enabled = false;104}105106void OpenXRFrameSynthesisExtension::prepare_view_configuration(uint32_t p_view_count) {107if (!frame_synthesis_ext) {108return;109}110111// Called during initialization, we can safely change this.112render_state.config_views.resize(p_view_count);113114for (XrFrameSynthesisConfigViewEXT &config_view : render_state.config_views) {115config_view.type = XR_TYPE_FRAME_SYNTHESIS_CONFIG_VIEW_EXT;116config_view.next = nullptr;117118// These will be set by xrEnumerateViewConfigurationViews.119config_view.recommendedMotionVectorImageRectWidth = 0;120config_view.recommendedMotionVectorImageRectHeight = 0;121}122}123124void *OpenXRFrameSynthesisExtension::set_view_configuration_and_get_next_pointer(uint32_t p_view, void *p_next_pointer) {125if (!frame_synthesis_ext) {126return nullptr;127}128129// Called during initialization, we can safely access this.130ERR_FAIL_UNSIGNED_INDEX_V(p_view, render_state.config_views.size(), nullptr);131132XrFrameSynthesisConfigViewEXT &config_view = render_state.config_views[p_view];133config_view.next = p_next_pointer;134135return &config_view;136}137138void OpenXRFrameSynthesisExtension::print_view_configuration_info(uint32_t p_view) const {139if (!frame_synthesis_ext) {140return;141}142143// Called during initialization, we can safely access this.144if (p_view < render_state.config_views.size()) {145const XrFrameSynthesisConfigViewEXT &config_view = render_state.config_views[p_view];146147print_line(" - motion vector width: ", itos(config_view.recommendedMotionVectorImageRectWidth));148print_line(" - motion vector height: ", itos(config_view.recommendedMotionVectorImageRectHeight));149}150}151152void OpenXRFrameSynthesisExtension::on_session_destroyed() {153if (!frame_synthesis_ext) {154return;155}156157// Free our swapchains.158free_swapchains();159}160161void OpenXRFrameSynthesisExtension::on_main_swapchains_created() {162if (!frame_synthesis_ext) {163return;164}165166// It is possible that our swapchain information gets resized,167// and that our motion vector and depth resolution changes with this.168// So (re)create our swapchains here as well.169// Note that we do this even if motion vectors aren't enabled yet.170171OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();172ERR_FAIL_NULL(openxr_api);173174RenderingServer *rendering_server = RenderingServer::get_singleton();175ERR_FAIL_NULL(rendering_server);176177// Out with the old.178free_swapchains();179180// We only support stereo.181size_t view_count = render_state.config_views.size();182ERR_FAIL_COND(view_count != 2);183184// Determine specific values for each renderer.185int swapchain_format = 0;186int depth_swapchain_format = 0;187String rendering_driver_name = rendering_server->get_current_rendering_driver_name();188if (rendering_driver_name.contains("opengl")) {189swapchain_format = GL_RGBA16F;190depth_swapchain_format = GL_DEPTH24_STENCIL8;191} else if (rendering_driver_name == "vulkan") {192String rendering_method = rendering_server->get_current_rendering_method();193if (rendering_method == "mobile") {194swapchain_format = VK_FORMAT_R16G16B16A16_SFLOAT;195depth_swapchain_format = VK_FORMAT_D24_UNORM_S8_UINT;196} else {197WARN_PRINT("OpenXR: Frame synthesis not supported for this rendering method!");198frame_synthesis_ext = false;199openxr_api->unregister_projection_views_extension(this);200return;201}202} else {203WARN_PRINT("OpenXR: Frame synthesis not supported for this rendering driver!");204frame_synthesis_ext = false;205openxr_api->unregister_projection_views_extension(this);206return;207}208209// We assume the size for each eye is the same, it should be.210uint32_t width = render_state.config_views[0].recommendedMotionVectorImageRectWidth;211uint32_t height = render_state.config_views[0].recommendedMotionVectorImageRectHeight;212213// Create swapchains for motion vectors and depth.214render_state.swapchains[SWAPCHAIN_MOTION_VECTOR].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, swapchain_format, width, height, 1, view_count);215render_state.swapchains[SWAPCHAIN_DEPTH].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, depth_swapchain_format, width, height, 1, view_count);216217// Set up our frame synthesis info.218render_state.frame_synthesis_info.resize(view_count);219220uint32_t index = 0;221for (XrFrameSynthesisInfoEXT &frame_synthesis_info : render_state.frame_synthesis_info) {222frame_synthesis_info.type = XR_TYPE_FRAME_SYNTHESIS_INFO_EXT;223frame_synthesis_info.next = nullptr;224frame_synthesis_info.layerFlags = 0;225226// Set up motion vector.227frame_synthesis_info.motionVectorSubImage.swapchain = render_state.swapchains[SWAPCHAIN_MOTION_VECTOR].get_swapchain();228frame_synthesis_info.motionVectorSubImage.imageArrayIndex = index;229frame_synthesis_info.motionVectorSubImage.imageRect.offset.x = 0;230frame_synthesis_info.motionVectorSubImage.imageRect.offset.y = 0;231frame_synthesis_info.motionVectorSubImage.imageRect.extent.width = width;232frame_synthesis_info.motionVectorSubImage.imageRect.extent.height = height;233234// Q: this should be 1.0, -1.0, 1.0. We output OpenGL NDC, frame synthesis expects Vulkan NDC, but might be a problem on runtime I'm testing.235frame_synthesis_info.motionVectorScale = { 1.0, 1.0, 1.0, 0.0 };236frame_synthesis_info.motionVectorOffset = { 0.0, 0.0, 0.0, 0.0 };237frame_synthesis_info.appSpaceDeltaPose = { { 0.0, 0.0, 0.0, 1.0 }, { 0.0, 0.0, 0.0 } };238239// Set up depth image.240frame_synthesis_info.depthSubImage.swapchain = render_state.swapchains[SWAPCHAIN_DEPTH].get_swapchain();241frame_synthesis_info.depthSubImage.imageArrayIndex = index;242frame_synthesis_info.depthSubImage.imageRect.offset.x = 0;243frame_synthesis_info.depthSubImage.imageRect.offset.y = 0;244frame_synthesis_info.depthSubImage.imageRect.extent.width = width;245frame_synthesis_info.depthSubImage.imageRect.extent.height = height;246247frame_synthesis_info.minDepth = 0.0;248frame_synthesis_info.maxDepth = 1.0;249250// Note: reverse-Z, these are just defaults for now.251frame_synthesis_info.nearZ = 100.0;252frame_synthesis_info.farZ = 0.01;253254index++;255}256}257258void OpenXRFrameSynthesisExtension::on_pre_render() {259if (!frame_synthesis_ext) {260return;261}262263OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();264ERR_FAIL_NULL(openxr_api);265266size_t view_count = render_state.config_views.size();267if (!enabled || view_count != 2 || render_state.skip_next_frame) {268// Unset these just in case.269openxr_api->set_velocity_texture(RID());270openxr_api->set_velocity_depth_texture(RID());271272// Remember our transform just in case we (re)start frame synthesis later on.273render_state.previous_transform = XRServer::get_singleton()->get_world_origin();274275return;276}277278// Acquire our swapchains.279for (int i = 0; i < SWAPCHAIN_MAX; i++) {280bool should_render = true;281render_state.swapchains[i].acquire(should_render);282}283284// Set our images.285openxr_api->set_velocity_texture(render_state.swapchains[SWAPCHAIN_MOTION_VECTOR].get_image());286openxr_api->set_velocity_depth_texture(render_state.swapchains[SWAPCHAIN_DEPTH].get_image());287288// Set our size.289uint32_t width = render_state.config_views[0].recommendedMotionVectorImageRectWidth;290uint32_t height = render_state.config_views[0].recommendedMotionVectorImageRectHeight;291openxr_api->set_velocity_target_size(Size2i(width, height));292293// Get our head motion294Transform3D world_transform = XRServer::get_singleton()->get_world_origin();295Transform3D delta_transform = render_state.previous_transform.affine_inverse() * world_transform;296Quaternion delta_quat = delta_transform.basis.get_quaternion();297Vector3 delta_origin = delta_transform.origin;298299// Z near/far can change per frame, so make sure we update this.300for (XrFrameSynthesisInfoEXT &frame_synthesis_info : render_state.frame_synthesis_info) {301frame_synthesis_info.layerFlags = render_state.relax_frame_interval ? XR_FRAME_SYNTHESIS_INFO_REQUEST_RELAXED_FRAME_INTERVAL_BIT_EXT : 0;302303frame_synthesis_info.appSpaceDeltaPose = {304{ (float)delta_quat.x, (float)delta_quat.y, (float)delta_quat.z, (float)delta_quat.w },305{ (float)delta_origin.x, (float)delta_origin.y, (float)delta_origin.z }306};307308// Note: reverse-Z.309frame_synthesis_info.nearZ = openxr_api->get_render_state_z_far();310frame_synthesis_info.farZ = openxr_api->get_render_state_z_near();311}312313// Remember our transform.314render_state.previous_transform = world_transform;315}316317void OpenXRFrameSynthesisExtension::on_post_draw_viewport(RID p_render_target) {318// Check if our extension is supported and enabled.319if (!frame_synthesis_ext || !enabled || render_state.config_views.size() != 2 || render_state.skip_next_frame) {320return;321}322323// Release our swapchains.324for (int i = 0; i < SWAPCHAIN_MAX; i++) {325render_state.swapchains[i].release();326}327}328329void *OpenXRFrameSynthesisExtension::set_projection_views_and_get_next_pointer(int p_view_index, void *p_next_pointer) {330// Check if our extension is supported and enabled.331if (!frame_synthesis_ext || !enabled || render_state.config_views.size() != 2) {332return nullptr;333}334335// Did we skip this frame?336if (render_state.skip_next_frame) {337// Only unset when we've handled both eyes.338if (p_view_index == 1) {339render_state.skip_next_frame = false;340}341return nullptr;342}343344// Check if we can run frame synthesis.345size_t view_count = render_state.config_views.size();346if (enabled && view_count == 2) {347render_state.frame_synthesis_info[p_view_index].next = p_next_pointer;348return &render_state.frame_synthesis_info[p_view_index];349}350351return nullptr;352}353354bool OpenXRFrameSynthesisExtension::is_available() const {355return frame_synthesis_ext;356}357358bool OpenXRFrameSynthesisExtension::is_enabled() const {359return frame_synthesis_ext && enabled;360}361362void OpenXRFrameSynthesisExtension::set_enabled(bool p_enabled) {363if (enabled == p_enabled) {364return;365}366ERR_FAIL_COND(!frame_synthesis_ext && p_enabled);367368enabled = p_enabled;369370RenderingServer *rendering_server = RenderingServer::get_singleton();371ERR_FAIL_NULL(rendering_server);372rendering_server->call_on_render_thread(callable_mp(this, &OpenXRFrameSynthesisExtension::_set_render_state_enabled_rt).bind(enabled));373}374375bool OpenXRFrameSynthesisExtension::get_relax_frame_interval() const {376return relax_frame_interval;377}378379void OpenXRFrameSynthesisExtension::set_relax_frame_interval(bool p_relax_frame_interval) {380if (relax_frame_interval == p_relax_frame_interval) {381return;382}383relax_frame_interval = p_relax_frame_interval;384385RenderingServer *rendering_server = RenderingServer::get_singleton();386ERR_FAIL_NULL(rendering_server);387rendering_server->call_on_render_thread(callable_mp(this, &OpenXRFrameSynthesisExtension::_set_relax_frame_interval_rt).bind(relax_frame_interval));388}389390void OpenXRFrameSynthesisExtension::_set_render_state_enabled_rt(bool p_enabled) {391render_state.enabled = p_enabled;392}393394void OpenXRFrameSynthesisExtension::_set_relax_frame_interval_rt(bool p_relax_frame_interval) {395render_state.relax_frame_interval = p_relax_frame_interval;396}397398void OpenXRFrameSynthesisExtension::free_swapchains() {399for (int i = 0; i < SWAPCHAIN_MAX; i++) {400render_state.swapchains[i].queue_free();401}402}403404void OpenXRFrameSynthesisExtension::skip_next_frame() {405RenderingServer *rendering_server = RenderingServer::get_singleton();406ERR_FAIL_NULL(rendering_server);407rendering_server->call_on_render_thread(callable_mp(this, &OpenXRFrameSynthesisExtension::_set_skip_next_frame_rt));408}409410void OpenXRFrameSynthesisExtension::_set_skip_next_frame_rt() {411render_state.skip_next_frame = true;412}413414415