diff options
author | Peter Kim <pk15950@gmail.com> | 2021-10-12 10:18:05 +0300 |
---|---|---|
committer | Peter Kim <pk15950@gmail.com> | 2021-10-12 10:18:05 +0300 |
commit | 9dda65455b54336fe3efef91eba9e41866dac1c1 (patch) | |
tree | 2f8fd6edf8d190e018b63c6b6bdacb38ab7905bd | |
parent | cfa59b3fabe729e49a57154ce21c5a4b88aa5812 (diff) |
XR Controller Support Step 4: Controller Drawing
Addresses T77127 (Controller Drawing).
Adds VR controller visualization and custom drawing via draw
handlers. Add-ons can draw to the XR surface (headset display) and
mirror window by adding a View3D draw handler of region type 'XR' and
draw type 'POST_VIEW'. Controller drawing and custom overlays can be
toggled individually as XR session options, which will be added in a
future update to the VR Scene Inspection add-on.
For the actual drawing, the OpenXR XR_MSFT_controller_model extension
is used to load a glTF model provided by the XR runtime. The model's
vertex data is then used to create a GPUBatch in the XR session
state. Finally, this batch is drawn via the XR surface draw handler
mentioned above.
For runtimes that do not support the controller model extension, a
a simple fallback shape (sphere) is drawn instead.
Reviewed By: Severin, fclem
Differential Revision: https://developer.blender.org/D10948
25 files changed, 1319 insertions, 24 deletions
diff --git a/intern/ghost/CMakeLists.txt b/intern/ghost/CMakeLists.txt index 76cac1049fb..05423209c71 100644 --- a/intern/ghost/CMakeLists.txt +++ b/intern/ghost/CMakeLists.txt @@ -473,6 +473,7 @@ if(WITH_XR_OPENXR) intern/GHOST_Xr.cpp intern/GHOST_XrAction.cpp intern/GHOST_XrContext.cpp + intern/GHOST_XrControllerModel.cpp intern/GHOST_XrEvent.cpp intern/GHOST_XrGraphicsBinding.cpp intern/GHOST_XrSession.cpp @@ -482,13 +483,19 @@ if(WITH_XR_OPENXR) intern/GHOST_IXrGraphicsBinding.h intern/GHOST_XrAction.h intern/GHOST_XrContext.h + intern/GHOST_XrControllerModel.h intern/GHOST_XrException.h intern/GHOST_XrSession.h intern/GHOST_XrSwapchain.h intern/GHOST_Xr_intern.h intern/GHOST_Xr_openxr_includes.h ) + list(APPEND INC + ../../extern/json/include + ../../extern/tinygltf + ) list(APPEND INC_SYS + ${EIGEN3_INCLUDE_DIRS} ${XR_OPENXR_SDK_INCLUDE_DIR} ) list(APPEND LIB diff --git a/intern/ghost/GHOST_C-api.h b/intern/ghost/GHOST_C-api.h index b78aac6f5eb..784febe8581 100644 --- a/intern/ghost/GHOST_C-api.h +++ b/intern/ghost/GHOST_C-api.h @@ -1140,6 +1140,30 @@ void GHOST_XrGetActionCustomdataArray(GHOST_XrContextHandle xr_context, const char *action_set_name, void **r_customdata_array); +/* controller model */ +/** + * Load the OpenXR controller model. + */ +int GHOST_XrLoadControllerModel(GHOST_XrContextHandle xr_context, const char *subaction_path); + +/** + * Unload the OpenXR controller model. + */ +void GHOST_XrUnloadControllerModel(GHOST_XrContextHandle xr_context, const char *subaction_path); + +/** + * Update component transforms for the OpenXR controller model. + */ +int GHOST_XrUpdateControllerModelComponents(GHOST_XrContextHandle xr_context, + const char *subaction_path); + +/** + * Get vertex data for the OpenXR controller model. + */ +int GHOST_XrGetControllerModelData(GHOST_XrContextHandle xr_context, + const char *subaction_path, + GHOST_XrControllerModelData *r_data); + #endif /* WITH_XR_OPENXR */ #ifdef __cplusplus diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h index 898ee451baf..2c8014a08cc 100644 --- a/intern/ghost/GHOST_Types.h +++ b/intern/ghost/GHOST_Types.h @@ -754,8 +754,31 @@ typedef struct GHOST_XrActionProfileInfo { const char *profile_path; uint32_t count_subaction_paths; const char **subaction_paths; - /* Bindings for each subaction path. */ + /** Bindings for each subaction path. */ const GHOST_XrActionBindingInfo *bindings; } GHOST_XrActionProfileInfo; +typedef struct GHOST_XrControllerModelVertex { + float position[3]; + float normal[3]; +} GHOST_XrControllerModelVertex; + +typedef struct GHOST_XrControllerModelComponent { + /** World space transform. */ + float transform[4][4]; + uint32_t vertex_offset; + uint32_t vertex_count; + uint32_t index_offset; + uint32_t index_count; +} GHOST_XrControllerModelComponent; + +typedef struct GHOST_XrControllerModelData { + uint32_t count_vertices; + const GHOST_XrControllerModelVertex *vertices; + uint32_t count_indices; + const uint32_t *indices; + uint32_t count_components; + const GHOST_XrControllerModelComponent *components; +} GHOST_XrControllerModelData; + #endif /* WITH_XR_OPENXR */ diff --git a/intern/ghost/intern/GHOST_C-api.cpp b/intern/ghost/intern/GHOST_C-api.cpp index a2871b46222..a21c3a90c06 100644 --- a/intern/ghost/intern/GHOST_C-api.cpp +++ b/intern/ghost/intern/GHOST_C-api.cpp @@ -1069,4 +1069,39 @@ void GHOST_XrGetActionCustomdataArray(GHOST_XrContextHandle xr_contexthandle, xr_context); } +int GHOST_XrLoadControllerModel(GHOST_XrContextHandle xr_contexthandle, const char *subaction_path) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->loadControllerModel(subaction_path), xr_context); + return 0; +} + +void GHOST_XrUnloadControllerModel(GHOST_XrContextHandle xr_contexthandle, + const char *subaction_path) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL(xr_session->unloadControllerModel(subaction_path), xr_context); +} + +int GHOST_XrUpdateControllerModelComponents(GHOST_XrContextHandle xr_contexthandle, + const char *subaction_path) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->updateControllerModelComponents(subaction_path), xr_context); + return 0; +} + +int GHOST_XrGetControllerModelData(GHOST_XrContextHandle xr_contexthandle, + const char *subaction_path, + GHOST_XrControllerModelData *r_data) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->getControllerModelData(subaction_path, *r_data), xr_context); + return 0; +} + #endif /* WITH_XR_OPENXR */ diff --git a/intern/ghost/intern/GHOST_XrContext.cpp b/intern/ghost/intern/GHOST_XrContext.cpp index fe8fec052fe..15b40690d83 100644 --- a/intern/ghost/intern/GHOST_XrContext.cpp +++ b/intern/ghost/intern/GHOST_XrContext.cpp @@ -412,11 +412,14 @@ void GHOST_XrContext::getExtensionsToEnable( try_ext.push_back(XR_EXT_DEBUG_UTILS_EXTENSION_NAME); } - /* Try enabling interaction profile extensions. */ + /* Interaction profile extensions. */ try_ext.push_back(XR_EXT_HP_MIXED_REALITY_CONTROLLER_EXTENSION_NAME); try_ext.push_back(XR_HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME); try_ext.push_back(XR_HUAWEI_CONTROLLER_INTERACTION_EXTENSION_NAME); + /* Controller model extension. */ + try_ext.push_back(XR_MSFT_CONTROLLER_MODEL_EXTENSION_NAME); + /* Varjo quad view extension. */ try_ext.push_back(XR_VARJO_QUAD_VIEWS_EXTENSION_NAME); diff --git a/intern/ghost/intern/GHOST_XrControllerModel.cpp b/intern/ghost/intern/GHOST_XrControllerModel.cpp new file mode 100644 index 00000000000..ae15bf11aa0 --- /dev/null +++ b/intern/ghost/intern/GHOST_XrControllerModel.cpp @@ -0,0 +1,629 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup GHOST + */ + +#include <cassert> + +#include <Eigen/Core> +#include <Eigen/Geometry> + +#include "GHOST_Types.h" +#include "GHOST_XrException.h" +#include "GHOST_Xr_intern.h" + +#include "GHOST_XrControllerModel.h" + +#define TINYGLTF_IMPLEMENTATION +#define TINYGLTF_NO_STB_IMAGE +#define TINYGLTF_NO_STB_IMAGE_WRITE +#define STBIWDEF static inline +#include "tiny_gltf.h" + +struct GHOST_XrControllerModelNode { + int32_t parent_idx = -1; + int32_t component_idx = -1; + float local_transform[4][4]; +}; + +/* -------------------------------------------------------------------- */ +/** \name glTF Utilities + * + * Adapted from Microsoft OpenXR-Mixed Reality Samples (MIT License): + * https://github.com/microsoft/OpenXR-MixedReality + * \{ */ + +struct GHOST_XrPrimitive { + std::vector<GHOST_XrControllerModelVertex> vertices; + std::vector<uint32_t> indices; +}; + +/** + * Validate that an accessor does not go out of bounds of the buffer view that it references and + * that the buffer view does not exceed the bounds of the buffer that it references + */ +static void validate_accessor(const tinygltf::Accessor &accessor, + const tinygltf::BufferView &buffer_view, + const tinygltf::Buffer &buffer, + size_t byte_stride, + size_t element_size) +{ + /* Make sure the accessor does not go out of range of the buffer view. */ + if (accessor.byteOffset + (accessor.count - 1) * byte_stride + element_size > + buffer_view.byteLength) { + throw GHOST_XrException("glTF: Accessor goes out of range of bufferview."); + } + + /* Make sure the buffer view does not go out of range of the buffer. */ + if (buffer_view.byteOffset + buffer_view.byteLength > buffer.data.size()) { + throw GHOST_XrException("glTF: BufferView goes out of range of buffer."); + } +} + +template<float (GHOST_XrControllerModelVertex::*field)[3]> +static void read_vertices(const tinygltf::Accessor &accessor, + const tinygltf::BufferView &buffer_view, + const tinygltf::Buffer &buffer, + GHOST_XrPrimitive &primitive) +{ + if (accessor.type != TINYGLTF_TYPE_VEC3) { + throw GHOST_XrException( + "glTF: Accessor for primitive attribute has incorrect type (VEC3 expected)."); + } + + if (accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT) { + throw GHOST_XrException( + "glTF: Accessor for primitive attribute has incorrect component type (FLOAT expected)."); + } + + /* If stride is not specified, it is tightly packed. */ + constexpr size_t packed_size = sizeof(float) * 3; + const size_t stride = buffer_view.byteStride == 0 ? packed_size : buffer_view.byteStride; + validate_accessor(accessor, buffer_view, buffer, stride, packed_size); + + /* Resize the vertices vector, if necessary, to include room for the attribute data. + If there are multiple attributes for a primitive, the first one will resize, and the + subsequent will not need to. */ + primitive.vertices.resize(accessor.count); + + /* Copy the attribute value over from the glTF buffer into the appropriate vertex field. */ + const uint8_t *buffer_ptr = buffer.data.data() + buffer_view.byteOffset + accessor.byteOffset; + for (size_t i = 0; i < accessor.count; i++, buffer_ptr += stride) { + memcpy(primitive.vertices[i].*field, buffer_ptr, stride); + } +} + +static void load_attribute_accessor(const tinygltf::Model &gltf_model, + const std::string &attribute_name, + int accessor_id, + GHOST_XrPrimitive &primitive) +{ + const auto &accessor = gltf_model.accessors.at(accessor_id); + + if (accessor.bufferView == -1) { + throw GHOST_XrException("glTF: Accessor for primitive attribute specifies no bufferview."); + } + + const tinygltf::BufferView &buffer_view = gltf_model.bufferViews.at(accessor.bufferView); + if (buffer_view.target != TINYGLTF_TARGET_ARRAY_BUFFER && buffer_view.target != 0) { + throw GHOST_XrException( + "glTF: Accessor for primitive attribute uses bufferview with invalid 'target' type."); + } + + const tinygltf::Buffer &buffer = gltf_model.buffers.at(buffer_view.buffer); + + if (attribute_name.compare("POSITION") == 0) { + read_vertices<&GHOST_XrControllerModelVertex::position>( + accessor, buffer_view, buffer, primitive); + } + else if (attribute_name.compare("NORMAL") == 0) { + read_vertices<&GHOST_XrControllerModelVertex::normal>( + accessor, buffer_view, buffer, primitive); + } +} + +/** + * Reads index data from a glTF primitive into a GHOST_XrPrimitive. glTF indices may be 8bit, 16bit + * or 32bit integers. This will coalesce indices from the source type(s) into a 32bit integer. + */ +template<typename TSrcIndex> +static void read_indices(const tinygltf::Accessor &accessor, + const tinygltf::BufferView &buffer_view, + const tinygltf::Buffer &buffer, + GHOST_XrPrimitive &primitive) +{ + if (buffer_view.target != TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER && + buffer_view.target != 0) { /* Allow 0 (not specified) even though spec doesn't seem to allow + this (BoomBox GLB fails). */ + throw GHOST_XrException( + "glTF: Accessor for indices uses bufferview with invalid 'target' type."); + } + + constexpr size_t component_size_bytes = sizeof(TSrcIndex); + if (buffer_view.byteStride != 0 && + buffer_view.byteStride != + component_size_bytes) { /* Index buffer must be packed per glTF spec. */ + throw GHOST_XrException( + "glTF: Accessor for indices uses bufferview with invalid 'byteStride'."); + } + + validate_accessor(accessor, buffer_view, buffer, component_size_bytes, component_size_bytes); + + if ((accessor.count % 3) != 0) { /* Since only triangles are supported, enforce that the number + of indices is divisible by 3. */ + throw GHOST_XrException("glTF: Unexpected number of indices for triangle primitive"); + } + + const TSrcIndex *index_buffer = reinterpret_cast<const TSrcIndex *>( + buffer.data.data() + buffer_view.byteOffset + accessor.byteOffset); + for (uint32_t i = 0; i < accessor.count; i++) { + primitive.indices.push_back(*(index_buffer + i)); + } +} + +/** + * Reads index data from a glTF primitive into a GHOST_XrPrimitive. + */ +static void load_index_accessor(const tinygltf::Model &gltf_model, + const tinygltf::Accessor &accessor, + GHOST_XrPrimitive &primitive) +{ + if (accessor.type != TINYGLTF_TYPE_SCALAR) { + throw GHOST_XrException("glTF: Accessor for indices specifies invalid 'type'."); + } + + if (accessor.bufferView == -1) { + throw GHOST_XrException("glTF: Index accessor without bufferView is currently not supported."); + } + + const tinygltf::BufferView &buffer_view = gltf_model.bufferViews.at(accessor.bufferView); + const tinygltf::Buffer &buffer = gltf_model.buffers.at(buffer_view.buffer); + + if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { + read_indices<uint8_t>(accessor, buffer_view, buffer, primitive); + } + else if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { + read_indices<uint16_t>(accessor, buffer_view, buffer, primitive); + } + else if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { + read_indices<uint32_t>(accessor, buffer_view, buffer, primitive); + } + else { + throw GHOST_XrException("glTF: Accessor for indices specifies invalid 'componentType'."); + } +} + +static GHOST_XrPrimitive read_primitive(const tinygltf::Model &gltf_model, + const tinygltf::Primitive &gltf_primitive) +{ + if (gltf_primitive.mode != TINYGLTF_MODE_TRIANGLES) { + throw GHOST_XrException( + "glTF: Unsupported primitive mode. Only TINYGLTF_MODE_TRIANGLES is supported."); + } + + GHOST_XrPrimitive primitive; + + /* glTF vertex data is stored in an attribute dictionary.Loop through each attribute and insert + * it into the GHOST_XrPrimitive. */ + for (const auto &[attr_name, accessor_idx] : gltf_primitive.attributes) { + load_attribute_accessor(gltf_model, attr_name, accessor_idx, primitive); + } + + if (gltf_primitive.indices != -1) { + /* If indices are specified for the glTF primitive, read them into the GHOST_XrPrimitive. */ + load_index_accessor(gltf_model, gltf_model.accessors.at(gltf_primitive.indices), primitive); + } + + return primitive; +} + +/** + * Calculate node local and world transforms. + */ +static void calc_node_transforms(const tinygltf::Node &gltf_node, + const float parent_transform[4][4], + float r_local_transform[4][4], + float r_world_transform[4][4]) +{ + /* A node may specify either a 4x4 matrix or TRS (Translation - Rotation - Scale) values, but not + * both. */ + if (gltf_node.matrix.size() == 16) { + const std::vector<double> &dm = gltf_node.matrix; + float m[4][4] = {(float)dm[0], + (float)dm[1], + (float)dm[2], + (float)dm[3], + (float)dm[4], + (float)dm[5], + (float)dm[6], + (float)dm[7], + (float)dm[8], + (float)dm[9], + (float)dm[10], + (float)dm[11], + (float)dm[12], + (float)dm[13], + (float)dm[14], + (float)dm[15]}; + memcpy(r_local_transform, m, sizeof(float) * 16); + } + else { + /* No matrix is present, so construct a matrix from the TRS values (each one is optional). */ + std::vector<double> translation = gltf_node.translation; + std::vector<double> rotation = gltf_node.rotation; + std::vector<double> scale = gltf_node.scale; + Eigen::Matrix4f &m = *(Eigen::Matrix4f *)r_local_transform; + Eigen::Quaternionf q; + Eigen::Matrix3f scalemat; + + if (translation.size() != 3) { + translation.resize(3); + translation[0] = translation[1] = translation[2] = 0.0; + } + if (rotation.size() != 4) { + rotation.resize(4); + rotation[0] = rotation[1] = rotation[2] = 0.0; + rotation[3] = 1.0; + } + if (scale.size() != 3) { + scale.resize(3); + scale[0] = scale[1] = scale[2] = 1.0; + } + + q.w() = (float)rotation[3]; + q.x() = (float)rotation[0]; + q.y() = (float)rotation[1]; + q.z() = (float)rotation[2]; + q.normalize(); + + scalemat.setIdentity(); + scalemat(0, 0) = (float)scale[0]; + scalemat(1, 1) = (float)scale[1]; + scalemat(2, 2) = (float)scale[2]; + + m.setIdentity(); + m.block<3, 3>(0, 0) = q.toRotationMatrix() * scalemat; + m.block<3, 1>(0, 3) = Eigen::Vector3f( + (float)translation[0], (float)translation[1], (float)translation[2]); + } + + *(Eigen::Matrix4f *)r_world_transform = *(Eigen::Matrix4f *)parent_transform * + *(Eigen::Matrix4f *)r_local_transform; +} + +static void load_node(const tinygltf::Model &gltf_model, + int gltf_node_id, + int32_t parent_idx, + const float parent_transform[4][4], + const std::string &parent_name, + const std::vector<XrControllerModelNodePropertiesMSFT> &node_properties, + std::vector<GHOST_XrControllerModelVertex> &vertices, + std::vector<uint32_t> &indices, + std::vector<GHOST_XrControllerModelComponent> &components, + std::vector<GHOST_XrControllerModelNode> &nodes, + std::vector<int32_t> &node_state_indices) +{ + const tinygltf::Node &gltf_node = gltf_model.nodes.at(gltf_node_id); + float world_transform[4][4]; + + GHOST_XrControllerModelNode &node = nodes.emplace_back(); + const int32_t node_idx = (int32_t)(nodes.size() - 1); + node.parent_idx = parent_idx; + calc_node_transforms(gltf_node, parent_transform, node.local_transform, world_transform); + + for (size_t i = 0; i < node_properties.size(); ++i) { + if ((node_state_indices[i] < 0) && (parent_name == node_properties[i].parentNodeName) && + (gltf_node.name == node_properties[i].nodeName)) { + node_state_indices[i] = node_idx; + break; + } + } + + if (gltf_node.mesh != -1) { + const tinygltf::Mesh &gltf_mesh = gltf_model.meshes.at(gltf_node.mesh); + + GHOST_XrControllerModelComponent &component = components.emplace_back(); + node.component_idx = components.size() - 1; + memcpy(component.transform, world_transform, sizeof(component.transform)); + component.vertex_offset = vertices.size(); + component.index_offset = indices.size(); + + for (const tinygltf::Primitive &gltf_primitive : gltf_mesh.primitives) { + /* Read the primitive data from the glTF buffers. */ + const GHOST_XrPrimitive primitive = read_primitive(gltf_model, gltf_primitive); + + const size_t start_vertex = vertices.size(); + size_t offset = start_vertex; + size_t count = primitive.vertices.size(); + vertices.resize(offset + count); + memcpy(vertices.data() + offset, + primitive.vertices.data(), + count * sizeof(decltype(primitive.vertices)::value_type)); + + offset = indices.size(); + count = primitive.indices.size(); + indices.resize(offset + count); + for (size_t i = 0; i < count; i += 3) { + indices[offset + i + 0] = start_vertex + primitive.indices[i + 0]; + indices[offset + i + 1] = start_vertex + primitive.indices[i + 2]; + indices[offset + i + 2] = start_vertex + primitive.indices[i + 1]; + } + } + + component.vertex_count = vertices.size() - component.vertex_offset; + component.index_count = indices.size() - component.index_offset; + } + + /* Recursively load all children. */ + for (const int child_node_id : gltf_node.children) { + load_node(gltf_model, + child_node_id, + node_idx, + world_transform, + gltf_node.name, + node_properties, + vertices, + indices, + components, + nodes, + node_state_indices); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name OpenXR Extension Functions + * + * \{ */ + +static PFN_xrGetControllerModelKeyMSFT g_xrGetControllerModelKeyMSFT = nullptr; +static PFN_xrLoadControllerModelMSFT g_xrLoadControllerModelMSFT = nullptr; +static PFN_xrGetControllerModelPropertiesMSFT g_xrGetControllerModelPropertiesMSFT = nullptr; +static PFN_xrGetControllerModelStateMSFT g_xrGetControllerModelStateMSFT = nullptr; +static XrInstance g_instance = XR_NULL_HANDLE; + +#define INIT_EXTENSION_FUNCTION(name) \ + CHECK_XR( \ + xrGetInstanceProcAddr(instance, #name, reinterpret_cast<PFN_xrVoidFunction *>(&g_##name)), \ + "Failed to get pointer to extension function: " #name); + +static void init_controller_model_extension_functions(XrInstance instance) +{ + if (instance != g_instance) { + g_instance = instance; + g_xrGetControllerModelKeyMSFT = nullptr; + g_xrLoadControllerModelMSFT = nullptr; + g_xrGetControllerModelPropertiesMSFT = nullptr; + g_xrGetControllerModelStateMSFT = nullptr; + } + + if (g_xrGetControllerModelKeyMSFT == nullptr) { + INIT_EXTENSION_FUNCTION(xrGetControllerModelKeyMSFT); + } + if (g_xrLoadControllerModelMSFT == nullptr) { + INIT_EXTENSION_FUNCTION(xrLoadControllerModelMSFT); + } + if (g_xrGetControllerModelPropertiesMSFT == nullptr) { + INIT_EXTENSION_FUNCTION(xrGetControllerModelPropertiesMSFT); + } + if (g_xrGetControllerModelStateMSFT == nullptr) { + INIT_EXTENSION_FUNCTION(xrGetControllerModelStateMSFT); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name GHOST_XrControllerModel + * + * \{ */ + +GHOST_XrControllerModel::GHOST_XrControllerModel(XrInstance instance, + const char *subaction_path_str) +{ + init_controller_model_extension_functions(instance); + + CHECK_XR(xrStringToPath(instance, subaction_path_str, &m_subaction_path), + (std::string("Failed to get user path \"") + subaction_path_str + "\".").data()); +} + +GHOST_XrControllerModel::~GHOST_XrControllerModel() +{ + if (m_load_task.valid()) { + m_load_task.wait(); + } +} + +void GHOST_XrControllerModel::load(XrSession session) +{ + if (m_data_loaded || m_load_task.valid()) { + return; + } + + /* Get model key. */ + XrControllerModelKeyStateMSFT key_state{XR_TYPE_CONTROLLER_MODEL_KEY_STATE_MSFT}; + CHECK_XR(g_xrGetControllerModelKeyMSFT(session, m_subaction_path, &key_state), + "Failed to get controller model key state."); + + if (key_state.modelKey != XR_NULL_CONTROLLER_MODEL_KEY_MSFT) { + m_model_key = key_state.modelKey; + /* Load asynchronously. */ + m_load_task = std::async(std::launch::async, + [&, session = session]() { return loadControllerModel(session); }); + } +} + +void GHOST_XrControllerModel::loadControllerModel(XrSession session) +{ + /* Load binary buffers. */ + uint32_t buf_size = 0; + CHECK_XR(g_xrLoadControllerModelMSFT(session, m_model_key, 0, &buf_size, nullptr), + "Failed to get controller model buffer size."); + + std::vector<uint8_t> buf((size_t)buf_size); + CHECK_XR(g_xrLoadControllerModelMSFT(session, m_model_key, buf_size, &buf_size, buf.data()), + "Failed to load controller model binary buffers."); + + /* Convert to glTF model. */ + tinygltf::TinyGLTF gltf_loader; + tinygltf::Model gltf_model; + std::string err_msg; + { + /* Workaround for TINYGLTF_NO_STB_IMAGE define. Set custom image loader to prevent failure when + * parsing image data. */ + auto load_img_func = [](tinygltf::Image *img, + const int p0, + std::string *p1, + std::string *p2, + int p3, + int p4, + const unsigned char *p5, + int p6, + void *user_pointer) -> bool { + (void)img; + (void)p0; + (void)p1; + (void)p2; + (void)p3; + (void)p4; + (void)p5; + (void)p6; + (void)user_pointer; + return true; + }; + gltf_loader.SetImageLoader(load_img_func, nullptr); + } + + if (!gltf_loader.LoadBinaryFromMemory(&gltf_model, &err_msg, nullptr, buf.data(), buf_size)) { + throw GHOST_XrException(("Failed to load glTF controller model: " + err_msg).c_str()); + } + + /* Get node properties. */ + XrControllerModelPropertiesMSFT model_properties{XR_TYPE_CONTROLLER_MODEL_PROPERTIES_MSFT}; + model_properties.nodeCapacityInput = 0; + CHECK_XR(g_xrGetControllerModelPropertiesMSFT(session, m_model_key, &model_properties), + "Failed to get controller model node properties count."); + + std::vector<XrControllerModelNodePropertiesMSFT> node_properties( + model_properties.nodeCountOutput, {XR_TYPE_CONTROLLER_MODEL_NODE_PROPERTIES_MSFT}); + model_properties.nodeCapacityInput = (uint32_t)node_properties.size(); + model_properties.nodeProperties = node_properties.data(); + CHECK_XR(g_xrGetControllerModelPropertiesMSFT(session, m_model_key, &model_properties), + "Failed to get controller model node properties."); + + m_node_state_indices.resize(node_properties.size(), -1); + + /* Get mesh vertex data. */ + const tinygltf::Scene &default_scene = gltf_model.scenes.at( + (gltf_model.defaultScene == -1) ? 0 : gltf_model.defaultScene); + const int32_t root_idx = -1; + const std::string root_name = ""; + float root_transform[4][4] = {0}; + root_transform[0][0] = root_transform[1][1] = root_transform[2][2] = root_transform[3][3] = 1.0f; + + for (const int node_id : default_scene.nodes) { + load_node(gltf_model, + node_id, + root_idx, + root_transform, + root_name, + node_properties, + m_vertices, + m_indices, + m_components, + m_nodes, + m_node_state_indices); + } + + m_data_loaded = true; +} + +void GHOST_XrControllerModel::updateComponents(XrSession session) +{ + if (!m_data_loaded) { + return; + } + + /* Get node states. */ + XrControllerModelStateMSFT model_state{XR_TYPE_CONTROLLER_MODEL_STATE_MSFT}; + model_state.nodeCapacityInput = 0; + CHECK_XR(g_xrGetControllerModelStateMSFT(session, m_model_key, &model_state), + "Failed to get controller model node state count."); + + const uint32_t count = model_state.nodeCountOutput; + std::vector<XrControllerModelNodeStateMSFT> node_states( + count, {XR_TYPE_CONTROLLER_MODEL_NODE_STATE_MSFT}); + model_state.nodeCapacityInput = count; + model_state.nodeStates = node_states.data(); + CHECK_XR(g_xrGetControllerModelStateMSFT(session, m_model_key, &model_state), + "Failed to get controller model node states."); + + /* Update node local transforms. */ + assert(m_node_state_indices.size() == count); + + for (uint32_t state_idx = 0; state_idx < count; ++state_idx) { + const int32_t &node_idx = m_node_state_indices[state_idx]; + if (node_idx >= 0) { + const XrPosef &pose = node_states[state_idx].nodePose; + Eigen::Matrix4f &m = *(Eigen::Matrix4f *)m_nodes[node_idx].local_transform; + Eigen::Quaternionf q( + pose.orientation.w, pose.orientation.x, pose.orientation.y, pose.orientation.z); + m.setIdentity(); + m.block<3, 3>(0, 0) = q.toRotationMatrix(); + m.block<3, 1>(0, 3) = Eigen::Vector3f(pose.position.x, pose.position.y, pose.position.z); + } + } + + /* Calculate component transforms (in world space). */ + std::vector<Eigen::Matrix4f> world_transforms(m_nodes.size()); + uint32_t i = 0; + for (const GHOST_XrControllerModelNode &node : m_nodes) { + world_transforms[i] = (node.parent_idx >= 0) ? world_transforms[node.parent_idx] * + *(Eigen::Matrix4f *)node.local_transform : + *(Eigen::Matrix4f *)node.local_transform; + if (node.component_idx >= 0) { + memcpy(m_components[node.component_idx].transform, + world_transforms[i].data(), + sizeof(m_components[node.component_idx].transform)); + } + ++i; + } +} + +void GHOST_XrControllerModel::getData(GHOST_XrControllerModelData &r_data) +{ + if (m_data_loaded) { + r_data.count_vertices = (uint32_t)m_vertices.size(); + r_data.vertices = m_vertices.data(); + r_data.count_indices = (uint32_t)m_indices.size(); + r_data.indices = m_indices.data(); + r_data.count_components = (uint32_t)m_components.size(); + r_data.components = m_components.data(); + } + else { + r_data.count_vertices = 0; + r_data.vertices = nullptr; + r_data.count_indices = 0; + r_data.indices = nullptr; + r_data.count_components = 0; + r_data.components = nullptr; + } +} + +/** \} */ diff --git a/intern/ghost/intern/GHOST_XrControllerModel.h b/intern/ghost/intern/GHOST_XrControllerModel.h new file mode 100644 index 00000000000..5ff72957b24 --- /dev/null +++ b/intern/ghost/intern/GHOST_XrControllerModel.h @@ -0,0 +1,59 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup GHOST + */ + +/* Note: Requires OpenXR headers to be included before this one for OpenXR types (XrInstance, + * XrSession, etc.). */ + +#pragma once + +#include <atomic> +#include <future> +#include <vector> + +struct GHOST_XrControllerModelNode; + +/** + * OpenXR glTF controller model. + */ +class GHOST_XrControllerModel { + public: + GHOST_XrControllerModel(XrInstance instance, const char *subaction_path); + ~GHOST_XrControllerModel(); + + void load(XrSession session); + void updateComponents(XrSession session); + void getData(GHOST_XrControllerModelData &r_data); + + private: + XrPath m_subaction_path = XR_NULL_PATH; + XrControllerModelKeyMSFT m_model_key = XR_NULL_CONTROLLER_MODEL_KEY_MSFT; + + std::future<void> m_load_task; + std::atomic<bool> m_data_loaded = false; + + std::vector<GHOST_XrControllerModelVertex> m_vertices; + std::vector<uint32_t> m_indices; + std::vector<GHOST_XrControllerModelComponent> m_components; + std::vector<GHOST_XrControllerModelNode> m_nodes; + /** Maps node states to nodes. */ + std::vector<int32_t> m_node_state_indices; + + void loadControllerModel(XrSession session); +}; diff --git a/intern/ghost/intern/GHOST_XrSession.cpp b/intern/ghost/intern/GHOST_XrSession.cpp index cd930c8328b..808f3a26be7 100644 --- a/intern/ghost/intern/GHOST_XrSession.cpp +++ b/intern/ghost/intern/GHOST_XrSession.cpp @@ -30,6 +30,7 @@ #include "GHOST_IXrGraphicsBinding.h" #include "GHOST_XrAction.h" #include "GHOST_XrContext.h" +#include "GHOST_XrControllerModel.h" #include "GHOST_XrException.h" #include "GHOST_XrSwapchain.h" #include "GHOST_Xr_intern.h" @@ -52,6 +53,8 @@ struct OpenXRSessionData { std::vector<GHOST_XrSwapchain> swapchains; std::map<std::string, GHOST_XrActionSet> action_sets; + /* Controller models identified by subaction path. */ + std::map<std::string, GHOST_XrControllerModel> controller_models; }; struct GHOST_XrDrawInfo { @@ -916,3 +919,71 @@ void GHOST_XrSession::getActionCustomdataArray(const char *action_set_name, } /** \} */ /* Actions */ + +/* -------------------------------------------------------------------- */ +/** \name Controller Model + * + * \{ */ + +bool GHOST_XrSession::loadControllerModel(const char *subaction_path) +{ + if (!m_context->isExtensionEnabled(XR_MSFT_CONTROLLER_MODEL_EXTENSION_NAME)) { + return false; + } + + XrSession session = m_oxr->session; + std::map<std::string, GHOST_XrControllerModel> &controller_models = m_oxr->controller_models; + std::map<std::string, GHOST_XrControllerModel>::iterator it = controller_models.find( + subaction_path); + + if (it == controller_models.end()) { + XrInstance instance = m_context->getInstance(); + it = controller_models + .emplace(std::piecewise_construct, + std::make_tuple(subaction_path), + std::make_tuple(instance, subaction_path)) + .first; + } + + it->second.load(session); + + return true; +} + +void GHOST_XrSession::unloadControllerModel(const char *subaction_path) +{ + std::map<std::string, GHOST_XrControllerModel> &controller_models = m_oxr->controller_models; + if (controller_models.find(subaction_path) != controller_models.end()) { + controller_models.erase(subaction_path); + } +} + +bool GHOST_XrSession::updateControllerModelComponents(const char *subaction_path) +{ + XrSession session = m_oxr->session; + std::map<std::string, GHOST_XrControllerModel>::iterator it = m_oxr->controller_models.find( + subaction_path); + if (it == m_oxr->controller_models.end()) { + return false; + } + + it->second.updateComponents(session); + + return true; +} + +bool GHOST_XrSession::getControllerModelData(const char *subaction_path, + GHOST_XrControllerModelData &r_data) +{ + std::map<std::string, GHOST_XrControllerModel>::iterator it = m_oxr->controller_models.find( + subaction_path); + if (it == m_oxr->controller_models.end()) { + return false; + } + + it->second.getData(r_data); + + return true; +} + +/** \} */ /* Controller Model */ diff --git a/intern/ghost/intern/GHOST_XrSession.h b/intern/ghost/intern/GHOST_XrSession.h index a76e11aede1..83de44c8d8e 100644 --- a/intern/ghost/intern/GHOST_XrSession.h +++ b/intern/ghost/intern/GHOST_XrSession.h @@ -90,6 +90,12 @@ class GHOST_XrSession { uint32_t getActionCount(const char *action_set_name); void getActionCustomdataArray(const char *action_set_name, void **r_customdata_array); + /** Controller model functions. */ + bool loadControllerModel(const char *subaction_path); + void unloadControllerModel(const char *subaction_path); + bool updateControllerModelComponents(const char *subaction_path); + bool getControllerModelData(const char *subaction_path, GHOST_XrControllerModelData &r_data); + private: /** Pointer back to context managing this session. Would be nice to avoid, but needed to access * custom callbacks set before session start. */ diff --git a/source/blender/draw/intern/draw_manager.c b/source/blender/draw/intern/draw_manager.c index b8de92dea7f..c8900d64935 100644 --- a/source/blender/draw/intern/draw_manager.c +++ b/source/blender/draw/intern/draw_manager.c @@ -51,6 +51,7 @@ #include "BKE_pbvh.h" #include "BKE_pointcache.h" #include "BKE_pointcloud.h" +#include "BKE_screen.h" #include "BKE_volume.h" #include "DNA_camera_types.h" @@ -1418,6 +1419,27 @@ void DRW_draw_callbacks_post_scene(void) ED_region_draw_cb_draw(DST.draw_ctx.evil_C, DST.draw_ctx.region, REGION_DRAW_POST_VIEW); +#ifdef WITH_XR_OPENXR + /* XR callbacks (controllers, custom draw functions) for session mirror. */ + if ((v3d->flag & V3D_XR_SESSION_MIRROR) != 0) { + if ((v3d->flag2 & V3D_XR_SHOW_CONTROLLERS) != 0) { + ARegionType *art = WM_xr_surface_controller_region_type_get(); + if (art) { + ED_region_surface_draw_cb_draw(art, REGION_DRAW_POST_VIEW); + } + } + if ((v3d->flag2 & V3D_XR_SHOW_CUSTOM_OVERLAYS) != 0) { + SpaceType *st = BKE_spacetype_from_id(SPACE_VIEW3D); + if (st) { + ARegionType *art = BKE_regiontype_from_id(st, RGN_TYPE_XR); + if (art) { + ED_region_surface_draw_cb_draw(art, REGION_DRAW_POST_VIEW); + } + } + } + } +#endif + /* Callback can be nasty and do whatever they want with the state. * Don't trust them! */ DRW_state_reset(); @@ -1464,6 +1486,46 @@ void DRW_draw_callbacks_post_scene(void) ED_annotation_draw_view3d(DEG_get_input_scene(depsgraph), depsgraph, v3d, region, true); GPU_depth_test(GPU_DEPTH_LESS_EQUAL); } + +#ifdef WITH_XR_OPENXR + if ((v3d->flag & V3D_XR_SESSION_SURFACE) != 0) { + DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get(); + + DRW_state_reset(); + + GPU_framebuffer_bind(dfbl->overlay_fb); + + GPU_matrix_projection_set(rv3d->winmat); + GPU_matrix_set(rv3d->viewmat); + + /* XR callbacks (controllers, custom draw functions) for session surface. */ + if (((v3d->flag2 & V3D_XR_SHOW_CONTROLLERS) != 0) || + ((v3d->flag2 & V3D_XR_SHOW_CUSTOM_OVERLAYS) != 0)) { + GPU_depth_test(GPU_DEPTH_NONE); + GPU_apply_state(); + + if ((v3d->flag2 & V3D_XR_SHOW_CONTROLLERS) != 0) { + ARegionType *art = WM_xr_surface_controller_region_type_get(); + if (art) { + ED_region_surface_draw_cb_draw(art, REGION_DRAW_POST_VIEW); + } + } + if ((v3d->flag2 & V3D_XR_SHOW_CUSTOM_OVERLAYS) != 0) { + SpaceType *st = BKE_spacetype_from_id(SPACE_VIEW3D); + if (st) { + ARegionType *art = BKE_regiontype_from_id(st, RGN_TYPE_XR); + if (art) { + ED_region_surface_draw_cb_draw(art, REGION_DRAW_POST_VIEW); + } + } + } + + DRW_state_reset(); + } + + GPU_depth_test(GPU_DEPTH_LESS_EQUAL); + } +#endif } } diff --git a/source/blender/editors/include/ED_space_api.h b/source/blender/editors/include/ED_space_api.h index 1a3aa7e5496..958df8f7707 100644 --- a/source/blender/editors/include/ED_space_api.h +++ b/source/blender/editors/include/ED_space_api.h @@ -72,8 +72,9 @@ void *ED_region_draw_cb_activate(struct ARegionType *art, void (*draw)(const struct bContext *, struct ARegion *, void *), void *customdata, int type); -void ED_region_draw_cb_draw(const struct bContext *, struct ARegion *, int); -void ED_region_draw_cb_exit(struct ARegionType *, void *); +void ED_region_draw_cb_draw(const struct bContext *C, struct ARegion *region, int type); +void ED_region_surface_draw_cb_draw(struct ARegionType *art, int type); +void ED_region_draw_cb_exit(struct ARegionType *art, void *handle); void ED_region_draw_cb_remove_by_type(struct ARegionType *art, void *draw_fn, void (*free)(void *)); diff --git a/source/blender/editors/include/ED_view3d_offscreen.h b/source/blender/editors/include/ED_view3d_offscreen.h index c490e96031f..8b695e61a35 100644 --- a/source/blender/editors/include/ED_view3d_offscreen.h +++ b/source/blender/editors/include/ED_view3d_offscreen.h @@ -60,7 +60,7 @@ void ED_view3d_draw_offscreen(struct Depsgraph *depsgraph, void ED_view3d_draw_offscreen_simple(struct Depsgraph *depsgraph, struct Scene *scene, struct View3DShading *shading_override, - int drawtype, + eDrawType drawtype, int winx, int winy, unsigned int draw_flags, @@ -68,6 +68,7 @@ void ED_view3d_draw_offscreen_simple(struct Depsgraph *depsgraph, const float winmat[4][4], float clip_start, float clip_end, + bool is_xr_surface, bool is_image_render, bool draw_background, const char *viewname, diff --git a/source/blender/editors/space_api/spacetypes.c b/source/blender/editors/space_api/spacetypes.c index 3da283089c5..149067a94fe 100644 --- a/source/blender/editors/space_api/spacetypes.c +++ b/source/blender/editors/space_api/spacetypes.c @@ -264,9 +264,9 @@ void ED_region_draw_cb_exit(ARegionType *art, void *handle) } } -void ED_region_draw_cb_draw(const bContext *C, ARegion *region, int type) +static void ed_region_draw_cb_draw(const bContext *C, ARegion *region, ARegionType *art, int type) { - LISTBASE_FOREACH_MUTABLE (RegionDrawCB *, rdc, ®ion->type->drawcalls) { + LISTBASE_FOREACH_MUTABLE (RegionDrawCB *, rdc, &art->drawcalls) { if (rdc->type == type) { rdc->draw(C, region, rdc->customdata); @@ -276,6 +276,16 @@ void ED_region_draw_cb_draw(const bContext *C, ARegion *region, int type) } } +void ED_region_draw_cb_draw(const bContext *C, ARegion *region, int type) +{ + ed_region_draw_cb_draw(C, region, region->type, type); +} + +void ED_region_surface_draw_cb_draw(ARegionType *art, int type) +{ + ed_region_draw_cb_draw(NULL, NULL, art, type); +} + void ED_region_draw_cb_remove_by_type(ARegionType *art, void *draw_fn, void (*free)(void *)) { LISTBASE_FOREACH_MUTABLE (RegionDrawCB *, rdc, &art->drawcalls) { diff --git a/source/blender/editors/space_view3d/space_view3d.c b/source/blender/editors/space_view3d/space_view3d.c index f68a4d78a00..bedc24a6287 100644 --- a/source/blender/editors/space_view3d/space_view3d.c +++ b/source/blender/editors/space_view3d/space_view3d.c @@ -1798,5 +1798,10 @@ void ED_spacetype_view3d(void) art = ED_area_type_hud(st->spaceid); BLI_addhead(&st->regiontypes, art); + /* regions: xr */ + art = MEM_callocN(sizeof(ARegionType), "spacetype view3d xr region"); + art->regionid = RGN_TYPE_XR; + BLI_addhead(&st->regiontypes, art); + BKE_spacetype_register(st); } diff --git a/source/blender/editors/space_view3d/view3d_draw.c b/source/blender/editors/space_view3d/view3d_draw.c index 733ec7e81cd..4da24888e1d 100644 --- a/source/blender/editors/space_view3d/view3d_draw.c +++ b/source/blender/editors/space_view3d/view3d_draw.c @@ -339,7 +339,16 @@ static void view3d_xr_mirror_setup(const wmWindowManager *wm, } view3d_main_region_setup_view(depsgraph, scene, v3d, region, viewmat, NULL, rect); - /* Reset overridden View3D data */ + /* Set draw flags. */ + SET_FLAG_FROM_TEST(v3d->flag2, + (wm->xr.session_settings.draw_flags & V3D_OFSDRAW_XR_SHOW_CONTROLLERS) != 0, + V3D_XR_SHOW_CONTROLLERS); + SET_FLAG_FROM_TEST(v3d->flag2, + (wm->xr.session_settings.draw_flags & V3D_OFSDRAW_XR_SHOW_CUSTOM_OVERLAYS) != + 0, + V3D_XR_SHOW_CUSTOM_OVERLAYS); + + /* Reset overridden View3D data. */ v3d->lens = lens_old; } #endif /* WITH_XR_OPENXR */ @@ -1757,6 +1766,7 @@ void ED_view3d_draw_offscreen_simple(Depsgraph *depsgraph, const float winmat[4][4], float clip_start, float clip_end, + bool is_xr_surface, bool is_image_render, bool draw_background, const char *viewname, @@ -1786,23 +1796,37 @@ void ED_view3d_draw_offscreen_simple(Depsgraph *depsgraph, v3d.shading.flag = V3D_SHADING_SCENE_WORLD | V3D_SHADING_SCENE_LIGHTS; } - if (draw_flags & V3D_OFSDRAW_SHOW_ANNOTATION) { - v3d.flag2 |= V3D_SHOW_ANNOTATION; + if ((draw_flags & ~V3D_OFSDRAW_OVERRIDE_SCENE_SETTINGS) == V3D_OFSDRAW_NONE) { + v3d.flag2 = V3D_HIDE_OVERLAYS; } - if (draw_flags & V3D_OFSDRAW_SHOW_GRIDFLOOR) { - v3d.gridflag |= V3D_SHOW_FLOOR | V3D_SHOW_X | V3D_SHOW_Y; - v3d.grid = 1.0f; - v3d.gridlines = 16; - v3d.gridsubdiv = 10; - - /* Show grid, disable other overlays (set all available _HIDE_ flags). */ + else { + if (draw_flags & V3D_OFSDRAW_SHOW_ANNOTATION) { + v3d.flag2 |= V3D_SHOW_ANNOTATION; + } + if (draw_flags & V3D_OFSDRAW_SHOW_GRIDFLOOR) { + v3d.gridflag |= V3D_SHOW_FLOOR | V3D_SHOW_X | V3D_SHOW_Y; + v3d.grid = 1.0f; + v3d.gridlines = 16; + v3d.gridsubdiv = 10; + } + if (draw_flags & V3D_OFSDRAW_SHOW_SELECTION) { + v3d.flag |= V3D_SELECT_OUTLINE; + } + if (draw_flags & V3D_OFSDRAW_XR_SHOW_CONTROLLERS) { + v3d.flag2 |= V3D_XR_SHOW_CONTROLLERS; + } + if (draw_flags & V3D_OFSDRAW_XR_SHOW_CUSTOM_OVERLAYS) { + v3d.flag2 |= V3D_XR_SHOW_CUSTOM_OVERLAYS; + } + /* Disable other overlays (set all available _HIDE_ flags). */ v3d.overlay.flag |= V3D_OVERLAY_HIDE_CURSOR | V3D_OVERLAY_HIDE_TEXT | V3D_OVERLAY_HIDE_MOTION_PATHS | V3D_OVERLAY_HIDE_BONES | V3D_OVERLAY_HIDE_OBJECT_XTRAS | V3D_OVERLAY_HIDE_OBJECT_ORIGINS; v3d.flag |= V3D_HIDE_HELPLINES; } - else { - v3d.flag2 = V3D_HIDE_OVERLAYS; + + if (is_xr_surface) { + v3d.flag |= V3D_XR_SESSION_SURFACE; } rv3d.persp = RV3D_PERSP; diff --git a/source/blender/makesdna/DNA_screen_types.h b/source/blender/makesdna/DNA_screen_types.h index f557890e3e8..86fd6b9744a 100644 --- a/source/blender/makesdna/DNA_screen_types.h +++ b/source/blender/makesdna/DNA_screen_types.h @@ -666,8 +666,11 @@ typedef enum eRegion_Type { RGN_TYPE_EXECUTE = 10, RGN_TYPE_FOOTER = 11, RGN_TYPE_TOOL_HEADER = 12, + /* Region type used exclusively by internal code and add-ons to register draw callbacks to the XR + context (surface, mirror view). Does not represent any real region. */ + RGN_TYPE_XR = 13, -#define RGN_TYPE_LEN (RGN_TYPE_TOOL_HEADER + 1) +#define RGN_TYPE_LEN (RGN_TYPE_XR + 1) } eRegion_Type; /* use for function args */ diff --git a/source/blender/makesdna/DNA_view3d_enums.h b/source/blender/makesdna/DNA_view3d_enums.h index aec52da1bf9..59012a0cd8d 100644 --- a/source/blender/makesdna/DNA_view3d_enums.h +++ b/source/blender/makesdna/DNA_view3d_enums.h @@ -30,6 +30,9 @@ typedef enum eV3DOffscreenDrawFlag { V3D_OFSDRAW_SHOW_ANNOTATION = (1 << 0), V3D_OFSDRAW_OVERRIDE_SCENE_SETTINGS = (1 << 1), V3D_OFSDRAW_SHOW_GRIDFLOOR = (1 << 2), + V3D_OFSDRAW_SHOW_SELECTION = (1 << 3), + V3D_OFSDRAW_XR_SHOW_CONTROLLERS = (1 << 4), + V3D_OFSDRAW_XR_SHOW_CUSTOM_OVERLAYS = (1 << 5), } eV3DOffscreenDrawFlag; /** #View3DShading.light */ diff --git a/source/blender/makesdna/DNA_view3d_types.h b/source/blender/makesdna/DNA_view3d_types.h index 4d88f6f0c15..fafc470b95a 100644 --- a/source/blender/makesdna/DNA_view3d_types.h +++ b/source/blender/makesdna/DNA_view3d_types.h @@ -373,6 +373,7 @@ typedef struct View3D { #define V3D_HIDE_HELPLINES (1 << 2) #define V3D_FLAG_UNUSED_2 (1 << 3) /* cleared */ #define V3D_XR_SESSION_MIRROR (1 << 4) +#define V3D_XR_SESSION_SURFACE (1 << 5) #define V3D_FLAG_UNUSED_10 (1 << 10) /* cleared */ #define V3D_SELECT_OUTLINE (1 << 11) @@ -465,6 +466,8 @@ enum { #define V3D_FLAG2_UNUSED_13 (1 << 13) /* cleared */ #define V3D_FLAG2_UNUSED_14 (1 << 14) /* cleared */ #define V3D_FLAG2_UNUSED_15 (1 << 15) /* cleared */ +#define V3D_XR_SHOW_CONTROLLERS (1 << 16) +#define V3D_XR_SHOW_CUSTOM_OVERLAYS (1 << 17) /** #View3D.gp_flag (short) */ #define V3D_GP_FADE_OBJECTS (1 << 0) /* Fade all non GP objects */ diff --git a/source/blender/makesdna/DNA_xr_types.h b/source/blender/makesdna/DNA_xr_types.h index a9d427777f7..6f4f7e3e8ae 100644 --- a/source/blender/makesdna/DNA_xr_types.h +++ b/source/blender/makesdna/DNA_xr_types.h @@ -42,7 +42,9 @@ typedef struct XrSessionSettings { /** View3D draw flags (V3D_OFSDRAW_NONE, V3D_OFSDRAW_SHOW_ANNOTATION, ...). */ char draw_flags; - char _pad2[3]; + /** Draw style for controller visualization. */ + char controller_draw_style; + char _pad2[2]; /** Clipping distance. */ float clip_start, clip_end; @@ -61,6 +63,13 @@ typedef enum eXRSessionBasePoseType { XR_BASE_POSE_CUSTOM = 2, } eXRSessionBasePoseType; +typedef enum eXrSessionControllerDrawStyle { + XR_CONTROLLER_DRAW_DARK = 0, + XR_CONTROLLER_DRAW_LIGHT = 1, + XR_CONTROLLER_DRAW_DARK_RAY = 2, + XR_CONTROLLER_DRAW_LIGHT_RAY = 3, +} eXrSessionControllerDrawStyle; + /** XR action type. Enum values match those in GHOST_XrActionType enum for consistency. */ typedef enum eXrActionType { XR_BOOLEAN_INPUT = 1, diff --git a/source/blender/makesrna/intern/rna_screen.c b/source/blender/makesrna/intern/rna_screen.c index 912f47ba9aa..a8f28f6091c 100644 --- a/source/blender/makesrna/intern/rna_screen.c +++ b/source/blender/makesrna/intern/rna_screen.c @@ -46,6 +46,7 @@ const EnumPropertyItem rna_enum_region_type_items[] = { {RGN_TYPE_EXECUTE, "EXECUTE", 0, "Execute Buttons", ""}, {RGN_TYPE_FOOTER, "FOOTER", 0, "Footer", ""}, {RGN_TYPE_TOOL_HEADER, "TOOL_HEADER", 0, "Tool Header", ""}, + {RGN_TYPE_XR, "XR", 0, "XR", ""}, {0, NULL, 0, NULL, NULL}, }; diff --git a/source/blender/makesrna/intern/rna_xr.c b/source/blender/makesrna/intern/rna_xr.c index a433a93e403..3705284ca66 100644 --- a/source/blender/makesrna/intern/rna_xr.c +++ b/source/blender/makesrna/intern/rna_xr.c @@ -1559,6 +1559,22 @@ static void rna_def_xr_session_settings(BlenderRNA *brna) {0, NULL, 0, NULL, NULL}, }; + static const EnumPropertyItem controller_draw_styles[] = { + {XR_CONTROLLER_DRAW_DARK, "DARK", 0, "Dark", "Draw dark controller"}, + {XR_CONTROLLER_DRAW_LIGHT, "LIGHT", 0, "Light", "Draw light controller"}, + {XR_CONTROLLER_DRAW_DARK_RAY, + "DARK_RAY", + 0, + "Dark + Ray", + "Draw dark controller with aiming axis ray"}, + {XR_CONTROLLER_DRAW_LIGHT_RAY, + "LIGHT_RAY", + 0, + "Light + Ray", + "Draw light controller with aiming axis ray"}, + {0, NULL, 0, NULL, NULL}, + }; + srna = RNA_def_struct(brna, "XrSessionSettings", NULL); RNA_def_struct_ui_text(srna, "XR Session Settings", ""); @@ -1609,6 +1625,29 @@ static void rna_def_xr_session_settings(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Show Annotation", "Show annotations for this view"); RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL); + prop = RNA_def_property(srna, "show_selection", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "draw_flags", V3D_OFSDRAW_SHOW_SELECTION); + RNA_def_property_ui_text(prop, "Show Selection", "Show selection outlines"); + RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL); + + prop = RNA_def_property(srna, "show_controllers", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "draw_flags", V3D_OFSDRAW_XR_SHOW_CONTROLLERS); + RNA_def_property_ui_text( + prop, "Show Controllers", "Show VR controllers (requires VR actions for controller poses)"); + RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL); + + prop = RNA_def_property(srna, "show_custom_overlays", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "draw_flags", V3D_OFSDRAW_XR_SHOW_CUSTOM_OVERLAYS); + RNA_def_property_ui_text(prop, "Show Custom Overlays", "Show custom VR overlays"); + RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL); + + prop = RNA_def_property(srna, "controller_draw_style", PROP_ENUM, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_enum_items(prop, controller_draw_styles); + RNA_def_property_ui_text( + prop, "Controller Draw Style", "Style to use when drawing VR controllers"); + RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL); + prop = RNA_def_property(srna, "clip_start", PROP_FLOAT, PROP_DISTANCE); RNA_def_property_range(prop, 1e-6f, FLT_MAX); RNA_def_property_ui_range(prop, 0.001f, FLT_MAX, 10, 3); diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index 531c5aa19bf..68e2c6b38f0 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -1013,6 +1013,8 @@ bool WM_xr_session_state_controller_aim_rotation_get(const wmXrData *xr, unsigned int subaction_idx, float r_rotation[4]); +struct ARegionType *WM_xr_surface_controller_region_type_get(void); + /* wm_xr_actions.c */ /* XR action functions to be called pre-XR session start. * NOTE: The "destroy" functions can also be called post-session start. */ diff --git a/source/blender/windowmanager/xr/intern/wm_xr_draw.c b/source/blender/windowmanager/xr/intern/wm_xr_draw.c index bbb73fc2007..6b3756c8178 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_draw.c +++ b/source/blender/windowmanager/xr/intern/wm_xr_draw.c @@ -24,12 +24,17 @@ #include <string.h> +#include "BKE_context.h" + #include "BLI_listbase.h" #include "BLI_math.h" #include "ED_view3d_offscreen.h" #include "GHOST_C-api.h" +#include "GPU_batch_presets.h" +#include "GPU_immediate.h" +#include "GPU_matrix.h" #include "GPU_viewport.h" @@ -153,6 +158,7 @@ void wm_xr_draw_view(const GHOST_XrDrawViewInfo *draw_view, void *customdata) winmat, settings->clip_start, settings->clip_end, + true, false, true, NULL, @@ -172,3 +178,200 @@ void wm_xr_draw_view(const GHOST_XrDrawViewInfo *draw_view, void *customdata) wm_xr_draw_viewport_buffers_to_active_framebuffer(xr_data->runtime, surface_data, draw_view); } + +static GPUBatch *wm_xr_controller_model_batch_create(GHOST_XrContextHandle xr_context, + const char *subaction_path) +{ + GHOST_XrControllerModelData model_data; + + if (!GHOST_XrGetControllerModelData(xr_context, subaction_path, &model_data) || + model_data.count_vertices < 1) { + return NULL; + } + + GPUVertFormat format = {0}; + GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); + GPU_vertformat_attr_add(&format, "nor", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); + + GPUVertBuf *vbo = GPU_vertbuf_create_with_format(&format); + GPU_vertbuf_data_alloc(vbo, model_data.count_vertices); + void *vbo_data = GPU_vertbuf_get_data(vbo); + memcpy( + vbo_data, model_data.vertices, model_data.count_vertices * sizeof(model_data.vertices[0])); + + GPUIndexBuf *ibo = NULL; + if (model_data.count_indices > 0 && ((model_data.count_indices % 3) == 0)) { + GPUIndexBufBuilder ibo_builder; + const unsigned int prim_len = model_data.count_indices / 3; + GPU_indexbuf_init(&ibo_builder, GPU_PRIM_TRIS, prim_len, model_data.count_vertices); + for (unsigned int i = 0; i < prim_len; ++i) { + const uint32_t *idx = &model_data.indices[i * 3]; + GPU_indexbuf_add_tri_verts(&ibo_builder, idx[0], idx[1], idx[2]); + } + ibo = GPU_indexbuf_build(&ibo_builder); + } + + return GPU_batch_create_ex(GPU_PRIM_TRIS, vbo, ibo, GPU_BATCH_OWNS_VBO | GPU_BATCH_OWNS_INDEX); +} + +static void wm_xr_controller_model_draw(const XrSessionSettings *settings, + GHOST_XrContextHandle xr_context, + wmXrSessionState *state) +{ + GHOST_XrControllerModelData model_data; + + float color[4]; + switch (settings->controller_draw_style) { + case XR_CONTROLLER_DRAW_DARK: + case XR_CONTROLLER_DRAW_DARK_RAY: + color[0] = color[1] = color[2] = 0.0f, color[3] = 0.4f; + break; + case XR_CONTROLLER_DRAW_LIGHT: + case XR_CONTROLLER_DRAW_LIGHT_RAY: + color[0] = 0.422f, color[1] = 0.438f, color[2] = 0.446f, color[3] = 0.4f; + break; + } + + GPU_depth_test(GPU_DEPTH_NONE); + GPU_blend(GPU_BLEND_ALPHA); + + LISTBASE_FOREACH (wmXrController *, controller, &state->controllers) { + GPUBatch *model = controller->model; + if (!model) { + model = controller->model = wm_xr_controller_model_batch_create(xr_context, + controller->subaction_path); + } + + if (model && + GHOST_XrGetControllerModelData(xr_context, controller->subaction_path, &model_data) && + model_data.count_components > 0) { + GPU_batch_program_set_builtin(model, GPU_SHADER_3D_UNIFORM_COLOR); + GPU_batch_uniform_4fv(model, "color", color); + + GPU_matrix_push(); + GPU_matrix_mul(controller->grip_mat); + for (unsigned int component_idx = 0; component_idx < model_data.count_components; + ++component_idx) { + const GHOST_XrControllerModelComponent *component = &model_data.components[component_idx]; + GPU_matrix_push(); + GPU_matrix_mul(component->transform); + GPU_batch_draw_range(model, + model->elem ? component->index_offset : component->vertex_offset, + model->elem ? component->index_count : component->vertex_count); + GPU_matrix_pop(); + } + GPU_matrix_pop(); + } + else { + /* Fallback. */ + const float scale = 0.05f; + GPUBatch *sphere = GPU_batch_preset_sphere(2); + GPU_batch_program_set_builtin(sphere, GPU_SHADER_3D_UNIFORM_COLOR); + GPU_batch_uniform_4fv(sphere, "color", color); + + GPU_matrix_push(); + GPU_matrix_mul(controller->grip_mat); + GPU_matrix_scale_1f(scale); + GPU_batch_draw(sphere); + GPU_matrix_pop(); + } + } +} + +static void wm_xr_controller_aim_draw(const XrSessionSettings *settings, wmXrSessionState *state) +{ + bool draw_ray; + switch (settings->controller_draw_style) { + case XR_CONTROLLER_DRAW_DARK: + case XR_CONTROLLER_DRAW_LIGHT: + draw_ray = false; + break; + case XR_CONTROLLER_DRAW_DARK_RAY: + case XR_CONTROLLER_DRAW_LIGHT_RAY: + draw_ray = true; + break; + } + + GPUVertFormat *format = immVertexFormat(); + uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); + uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_U8, 4, GPU_FETCH_INT_TO_FLOAT_UNIT); + immBindBuiltinProgram(GPU_SHADER_3D_POLYLINE_FLAT_COLOR); + + float viewport[4]; + GPU_viewport_size_get_f(viewport); + immUniform2fv("viewportSize", &viewport[2]); + + immUniform1f("lineWidth", 3.0f * U.pixelsize); + + if (draw_ray) { + const uchar color[4] = {89, 89, 255, 127}; + const float scale = settings->clip_end; + float ray[3]; + + GPU_depth_test(GPU_DEPTH_LESS_EQUAL); + GPU_blend(GPU_BLEND_ALPHA); + + immBegin(GPU_PRIM_LINES, (uint)BLI_listbase_count(&state->controllers) * 2); + + LISTBASE_FOREACH (wmXrController *, controller, &state->controllers) { + const float(*mat)[4] = controller->aim_mat; + madd_v3_v3v3fl(ray, mat[3], mat[2], -scale); + + immAttrSkip(col); + immVertex3fv(pos, mat[3]); + immAttr4ubv(col, color); + immVertex3fv(pos, ray); + } + + immEnd(); + } + else { + const uchar r[4] = {255, 51, 82, 255}; + const uchar g[4] = {139, 220, 0, 255}; + const uchar b[4] = {40, 144, 255, 255}; + const float scale = 0.01f; + float x_axis[3], y_axis[3], z_axis[3]; + + GPU_depth_test(GPU_DEPTH_NONE); + GPU_blend(GPU_BLEND_NONE); + + immBegin(GPU_PRIM_LINES, (uint)BLI_listbase_count(&state->controllers) * 6); + + LISTBASE_FOREACH (wmXrController *, controller, &state->controllers) { + const float(*mat)[4] = controller->aim_mat; + madd_v3_v3v3fl(x_axis, mat[3], mat[0], scale); + madd_v3_v3v3fl(y_axis, mat[3], mat[1], scale); + madd_v3_v3v3fl(z_axis, mat[3], mat[2], scale); + + immAttrSkip(col); + immVertex3fv(pos, mat[3]); + immAttr4ubv(col, r); + immVertex3fv(pos, x_axis); + + immAttrSkip(col); + immVertex3fv(pos, mat[3]); + immAttr4ubv(col, g); + immVertex3fv(pos, y_axis); + + immAttrSkip(col); + immVertex3fv(pos, mat[3]); + immAttr4ubv(col, b); + immVertex3fv(pos, z_axis); + } + + immEnd(); + } + + immUnbindProgram(); +} + +void wm_xr_draw_controllers(const bContext *UNUSED(C), ARegion *UNUSED(region), void *customdata) +{ + wmXrData *xr = customdata; + const XrSessionSettings *settings = &xr->session_settings; + GHOST_XrContextHandle xr_context = xr->runtime->context; + wmXrSessionState *state = &xr->runtime->session_state; + + wm_xr_controller_model_draw(settings, xr_context, state); + wm_xr_controller_aim_draw(settings, state); +} diff --git a/source/blender/windowmanager/xr/intern/wm_xr_intern.h b/source/blender/windowmanager/xr/intern/wm_xr_intern.h index 8fb5424b8c2..2cd0ba5c056 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_intern.h +++ b/source/blender/windowmanager/xr/intern/wm_xr_intern.h @@ -88,6 +88,11 @@ typedef struct wmXrViewportPair { typedef struct { /** Off-screen buffers/viewports for each view. */ ListBase viewports; /* #wmXrViewportPair */ + + /** Dummy region type for controller draw callback. */ + struct ARegionType *controller_art; + /** Controller draw callback handle. */ + void *controller_draw_handle; } wmXrSurfaceData; typedef struct wmXrDrawData { @@ -114,12 +119,16 @@ typedef struct wmXrController { /input/trigger/value, interaction_path = /user/hand/left/input/trigger/value). */ char subaction_path[64]; - /* Pose (in world space) that represents the user's hand when holding the controller.*/ + + /** Pose (in world space) that represents the user's hand when holding the controller. */ GHOST_XrPose grip_pose; float grip_mat[4][4]; - /* Pose (in world space) that represents the controller's aiming source. */ + /** Pose (in world space) that represents the controller's aiming source. */ GHOST_XrPose aim_pose; float aim_mat[4][4]; + + /** Controller model. */ + struct GPUBatch *model; } wmXrController; typedef struct wmXrAction { @@ -207,3 +216,4 @@ void wm_xr_session_controller_data_clear(wmXrSessionState *state); void wm_xr_pose_to_mat(const GHOST_XrPose *pose, float r_mat[4][4]); void wm_xr_pose_to_imat(const GHOST_XrPose *pose, float r_imat[4][4]); void wm_xr_draw_view(const GHOST_XrDrawViewInfo *draw_view, void *customdata); +void wm_xr_draw_controllers(const struct bContext *C, struct ARegion *region, void *customdata); diff --git a/source/blender/windowmanager/xr/intern/wm_xr_session.c b/source/blender/windowmanager/xr/intern/wm_xr_session.c index 88bc5c45c91..9f53db1347c 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_session.c +++ b/source/blender/windowmanager/xr/intern/wm_xr_session.c @@ -24,6 +24,7 @@ #include "BKE_idprop.h" #include "BKE_main.h" #include "BKE_scene.h" +#include "BKE_screen.h" #include "BLI_listbase.h" #include "BLI_math.h" @@ -36,9 +37,11 @@ #include "DRW_engine.h" #include "ED_screen.h" +#include "ED_space_api.h" #include "GHOST_C-api.h" +#include "GPU_batch.h" #include "GPU_viewport.h" #include "MEM_guardedalloc.h" @@ -72,7 +75,15 @@ static void wm_xr_session_create_cb(void) static void wm_xr_session_controller_data_free(wmXrSessionState *state) { - BLI_freelistN(&state->controllers); + ListBase *lb = &state->controllers; + wmXrController *c; + + while ((c = BLI_pophead(lb))) { + if (c->model) { + GPU_batch_discard(c->model); + } + BLI_freelinkN(lb, c); + } } void wm_xr_session_data_free(wmXrSessionState *state) @@ -529,6 +540,7 @@ static void wm_xr_session_controller_pose_calc(const GHOST_XrPose *raw_pose, static void wm_xr_session_controller_data_update(const XrSessionSettings *settings, const wmXrAction *grip_action, const wmXrAction *aim_action, + GHOST_XrContextHandle xr_context, wmXrSessionState *state) { BLI_assert(grip_action->count_subaction_paths == aim_action->count_subaction_paths); @@ -560,6 +572,16 @@ static void wm_xr_session_controller_data_update(const XrSessionSettings *settin base_mat, &controller->aim_pose, controller->aim_mat); + + if (!controller->model) { + /* Notify GHOST to load/continue loading the controller model data. This can be called more + * than once since the model may not be available from the runtime yet. The batch itself will + * be created in wm_xr_draw_controllers(). */ + GHOST_XrLoadControllerModel(xr_context, controller->subaction_path); + } + else { + GHOST_XrUpdateControllerModelComponents(xr_context, controller->subaction_path); + } } } @@ -1089,6 +1111,7 @@ void wm_xr_session_actions_update(wmWindowManager *wm) wm_xr_session_controller_data_update(&xr->session_settings, active_action_set->controller_grip_action, active_action_set->controller_aim_action, + xr_context, state); } @@ -1125,11 +1148,33 @@ void wm_xr_session_controller_data_populate(const wmXrAction *grip_action, BLI_addtail(controllers, controller); } + + /* Activate draw callback. */ + if (g_xr_surface) { + wmXrSurfaceData *surface_data = g_xr_surface->customdata; + if (surface_data && !surface_data->controller_draw_handle) { + if (surface_data->controller_art) { + surface_data->controller_draw_handle = ED_region_draw_cb_activate( + surface_data->controller_art, wm_xr_draw_controllers, xr, REGION_DRAW_POST_VIEW); + } + } + } } void wm_xr_session_controller_data_clear(wmXrSessionState *state) { wm_xr_session_controller_data_free(state); + + /* Deactivate draw callback. */ + if (g_xr_surface) { + wmXrSurfaceData *surface_data = g_xr_surface->customdata; + if (surface_data && surface_data->controller_draw_handle) { + if (surface_data->controller_art) { + ED_region_draw_cb_exit(surface_data->controller_art, surface_data->controller_draw_handle); + } + surface_data->controller_draw_handle = NULL; + } + } } /** \} */ /* XR-Session Actions */ @@ -1255,6 +1300,11 @@ static void wm_xr_session_surface_free_data(wmSurface *surface) BLI_freelinkN(lb, vp); } + if (data->controller_art) { + BLI_freelistN(&data->controller_art->drawcalls); + MEM_freeN(data->controller_art); + } + MEM_freeN(surface->customdata); g_xr_surface = NULL; @@ -1269,6 +1319,7 @@ static wmSurface *wm_xr_session_surface_create(void) wmSurface *surface = MEM_callocN(sizeof(*surface), __func__); wmXrSurfaceData *data = MEM_callocN(sizeof(*data), "XrSurfaceData"); + data->controller_art = MEM_callocN(sizeof(*(data->controller_art)), "XrControllerRegionType"); surface->draw = wm_xr_session_surface_draw; surface->free_data = wm_xr_session_surface_free_data; @@ -1278,6 +1329,7 @@ static wmSurface *wm_xr_session_surface_create(void) surface->ghost_ctx = DRW_xr_opengl_context_get(); surface->gpu_ctx = DRW_xr_gpu_context_get(); + data->controller_art->regionid = RGN_TYPE_XR; surface->customdata = data; g_xr_surface = surface; @@ -1311,4 +1363,14 @@ void wm_xr_session_gpu_binding_context_destroy(GHOST_ContextHandle UNUSED(contex WM_main_add_notifier(NC_WM | ND_XR_DATA_CHANGED, NULL); } +ARegionType *WM_xr_surface_controller_region_type_get(void) +{ + if (g_xr_surface) { + wmXrSurfaceData *data = g_xr_surface->customdata; + return data->controller_art; + } + + return NULL; +} + /** \} */ /* XR-Session Surface */ |