Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OpenXR extension wrappers for fb_scene, fb_spatial_entity, fb_spatial_entity_query, fb_spatial_entity_container #66

Merged
merged 5 commits into from
Jan 18, 2024

Conversation

maunvz
Copy link
Contributor

@maunvz maunvz commented Dec 9, 2023

As title, following the pattern from the fb_scene_capture extension wrapper, I've added wrappers for the 4 extensions mentioned. They don't yet expose useful functionality to godot projects (beyond getting the singletons and checking if the extensions are supported), but provide most of the openxr related boilterplate so a later change can add an "XR Scene" abstraction that expresses a physical room's layout and surfaces to the a Godot scene.

@maunvz maunvz force-pushed the scene_ext branch 3 times, most recently from c7cbe0a to 970d1aa Compare December 11, 2023 00:56
@m4gr3d m4gr3d added this to the 2.1.0 milestone Dec 21, 2023
Copy link
Collaborator

@m4gr3d m4gr3d left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall as a first step to add support for these extensions!

@BastiaanOlij @Malcolmnixon As a follow-up to this PR, we'll need to evaluate how we want to expose this functionality on the Godot side in a manner that's consistent.

Comment on lines 10 to 11
/* Original contributed implementation: */
/* Copyright (c) 2022-2023 MattaKis Consulting Kft. (Migeran) */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated all headers to match that pattern (also switched to #pragma once)

bool initialize_fb_spatial_entity_extension(const XrInstance instance);

std::map<godot::String, bool *> request_extensions;
std::map<XrAsyncRequestIdFB, SetSpaceComponentStatusCallback_t> set_status_callbacks;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: formatting issue?


bool initialize_fb_spatial_entity_query_extension(const XrInstance instance);
void on_space_query_results(const XrEventDataSpaceQueryResultsAvailableFB* event);
void on_space_query_complete(const XrEventDataSpaceQueryCompleteFB* event);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: here and below, formatting issues?

Comment on lines 102 to 103
XrSpaceContainerFB spaceContainer = {XR_TYPE_SPACE_CONTAINER_FB};
xrGetSpaceContainerFB(SESSION, space, &spaceContainer);
std::vector<XrUuidEXT> uuids(spaceContainer.uuidCountOutput);
spaceContainer.uuidCapacityInput = uuids.size();
spaceContainer.uuids = uuids.data();
xrGetSpaceContainerFB(SESSION, space, &spaceContainer);
return uuids;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the indents used in this method are different from the ones used in the previous methods.

@BastiaanOlij
Copy link
Member

BastiaanOlij commented Dec 21, 2023

@BastiaanOlij @Malcolmnixon As a follow-up to this PR, we'll need to evaluate how we want to expose this functionality on the Godot side in a manner that's consistent.

In Godot 3 we introduced Anchors based on how ARKit solved this, most of this logic is still in place but not all.
Whether detected by plane detection or otherwise detected, any location that was tracked in space was created as an XRPositionalTracker with the type set to XRServer::TRACKER_ANCHOR. While this location was mostly static, ARKit would update locations as it learned more about its surroundings. It would even merge or split anchors when needed.

So that part of the solution I think is sound and we should apply here too. I don't know the details of the flow Meta currently has but if any spatial location is detected, we should create a positional tracker and register it with the XRServer by calling XRServer::add_tracker. The XRServer already emits signals so the game can react to the creation of a new tracker and make any additions to the scene. This entails adding an XRAnchor3D node to the scene that is bound to the newly created tracker.

Using XRAnchor3D also ensures that the location is adjusted when we recenter the player.

What is missing is what to do after this, I wasn't at all happy with how we did things in Godot 3 so I left this out in the conversion to Godot 4. We need a mechanism to expose further meta data about the anchor and provide a collision shape and/or occlusion mesh.

These could become OpenXRCollisionShape and OpenXRMesh classes where you specify the tracker, the problem is that this would suggest this would also apply for other trackers such as controllers.
This is how we did it in Godot 3 where OpenVR also allowed us to provide render models in this fashion however with OpenXR this is no longer sufficient. With OpenXR the vendor render model APIs are all GLTF based and it looks like this is a trend. We could paint ourselves in a corner if this trend continues into a more generic AR solution.

@maunvz
Copy link
Contributor Author

maunvz commented Dec 22, 2023

addressed the formatting nits (now using tabs throughout, turned on whitespace rendering in my editor to catch these sooner lol). Is there / should there be a linter for this project?

@maunvz maunvz requested a review from m4gr3d December 22, 2023 20:43
@BastiaanOlij
Copy link
Member

addressed the formatting nits (now using tabs throughout, turned on whitespace rendering in my editor to catch these sooner lol). Is there / should there be a linter for this project?

We use clang-format in most of our projects but doesn't look like we set that up here. Probably worth doing.

@maunvz
Copy link
Contributor Author

maunvz commented Dec 25, 2023

Added an export option to request the USE_SCENE permission as per https://developer.oculus.com/documentation/native/native-spatial-data-perm/

From a discussion in #68 the permission is already auto-requested by GodotActivity's existing logic

Copy link
Collaborator

@m4gr3d m4gr3d left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

}

void OpenXRFbSceneExtensionWrapper::_bind_methods() {
ClassDB::bind_static_method("OpenXRFbSceneExtensionWrapper", D_METHOD("get_singleton"), &OpenXRFbSceneExtensionWrapper::get_singleton);
Copy link
Collaborator

@dsnopek dsnopek Jan 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@m4gr3d @BastiaanOlij I know this is a pre-existing pattern from OpenXRFbSceneCaptureExtensionWrapper which is already merged, but I'm curious how this pattern of registering a static get_singleton() method came to be? Because this isn't how singletons are usually bound in Godot.

Usually, you'd register a singleton in register_types.cpp via:

Engine::get_singleton()->register_singleton("OpenXRFbSceneExtensionWrapper", OpenXRFbSceneExtensionWrapper::get_singleton());

The pattern used here certainly works, but it leads to an unusual API from GDScript. If there isn't a compelling reason for this, I think we should switch to the usual way. I can open a separate issue about that, though! I just happened to notice it when reviewing this PR.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't aware of the reigster_singleton(...) approach; let's use that instead and update OpenXRFbSceneCaptureExtensionWrapper accordingly!

Copy link
Collaborator

@m4gr3d m4gr3d Jan 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dsnopek What's the earliest that Engine::get_singleton()->register_singleton(...) can be used?

I tried invoking it during MODULE_INITIALIZATION_LEVEL_CORE and I kept getting crash errors because the Engine singleton was not available.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, doing Engine.get_singleton(...) does not retrieve the singleton instance.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a draft PR for reference: #72

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm getting segfaults when registering using this approach. Did a bit of digging, and I think it's due to Engine::get_singleton() not being available in a gdextension

ERROR: Failed to retrieve non-existent singleton 'Engine'.
   at: get_singleton_object (core/config/engine.cpp:270)
ERROR: Parameter "singleton_obj" is null.
   at: get_singleton (thirdparty/godot-cpp/gen/src/classes/engine.cpp:49)

Digging into the code, godot-cpp auto-generates bindings that eventually call into Engine::get_singleton_object(const StringName &p_name) and passes the class name (in this case "Engine"), but godot never actually registers Engine itself as a singleton in this form, within the godot/ source tree it's just a raw pointer, and since it doesn't extend Object it's not trivial to register like all the other Singleton instances. From godot/core/config/engine.cpp

Engine *Engine::singleton = nullptr;

Engine *Engine::get_singleton() {
	return singleton;
}

I think we'd need to address that in the engine before we can use Engine::get_singleton() from an extension? In contrast, main.cpp registers Performance as follows: engine->add_singleton(Engine::Singleton("Performance", performance));

Not sure if you encountered something similar in #73 @dsnopek

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maunvz There was some logic missing in register_types.cpp that @dsnopek fixed in https://github.com/GodotVR/godot_openxr_vendors/pull/73/files#diff-aaccbe3504fdb5822961fde8149e985f5539985f9e60a4a31d804f3bf5feb5e4.

In the updated register_types.cpp, the singleton registration is initialized at the MODULE_INITIALIZATION_LEVEL_SCENE level and the minimum initialization level was also updated to MODULE_INITIALIZATION_LEVEL_SCENE.
With those changes in place, registration via the engine works as expected.

struct XrSceneObjectInternal {
XrUuidEXT uuid;
XrSpace space;
std::optional<std::string> label;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use Godots implementations whenever possible to prevent needless conversions of data between internal data and Godot.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This collection of OpenXR extensions use XrSpace and XrUuidEXT across multiple API calls to request different metadata and poses (for example, once you've queried for all available anchors, you can locate that anchor or ask about it's semantic labels / bounding volumes, all with uuid / space). The intent here is to store in the original OpenXR formats to facilitate later calls associated with the same anchor, and translate to a Godot type in some higher level "XrSceneManager" that gets used by gdscript (rather than using these wrappers directly)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maunvz The comment was referring to the use of the std::string type, you can use godot::String instead here and throughout this PR.

@maunvz
Copy link
Contributor Author

maunvz commented Jan 12, 2024

updated to remove unused constants, request the scene permission manually, based on #71, and use reigster_singleton(...) in the new additions.


// Vertices and lines on a plane, probably use this for a floor / ceiling as they are irregularly shaped
// We store a std::vector instead of XrBoundary2DFB to own the vertex memory
std::optional<std::vector<XrVector2f>> boundary2D;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, you can replace std::vector with godot::Vector throughout this PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

related: If I have "using namespace godot;" at the top, should I omit the godot:: prefix throughout the rest of the file (including in headers and variable types)?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, you can omit the godot:: prefix.

Comment on lines 99 to 100
"TABLE,COUCH,FLOOR,CEILING,WALL_FACE,WINDOW_FRAME,DOOR_FRAME,STORAGE,BED,SCREEN,LAMP,PLANT,WALL_ART,INVISIBLE_WALL_FACE,OTHER";
const XrSemanticLabelsSupportInfoFB semanticLabelsSupportInfo = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this list also include GLOBAL_MESH as mentioned in the documentation?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maunvz ping on this ^^

Comment on lines 78 to 79
std::optional<std::string> get_semantic_labels(const XrSpace space);
void get_shapes(const XrSpace space, XrSceneObjectInternal& object);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and throughout the PR, we should use a const XrSpace &space argument instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm does that actually make a difference? the XrSpace, like many other XR handles, is just a pointer / uint64_t, not a full object. See openxr.h:

#if !defined(XR_DEFINE_HANDLE)
#if (XR_PTR_SIZE == 8)
    #define XR_DEFINE_HANDLE(object) typedef struct object##_T* object;
#else
    #define XR_DEFINE_HANDLE(object) typedef uint64_t object;
#endif
#endif

...

XR_DEFINE_HANDLE(XrSpace)

Will update to match the style of the rest of the codebase

@maunvz
Copy link
Contributor Author

maunvz commented Jan 18, 2024

updated XrSpace usage, removed unnecessary godot:: prefixes, switched to Godot types throughout, and switched to new singleton registration

return std::nullopt;
}

static const std::string recognizedLabels =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another usage of std:string

// First call.
xrGetSpaceSemanticLabelsFB(SESSION, space, &labels);
// Second call
std::vector<char> labelData(labels.bufferCountOutput);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another usage of std::vector

bool OpenXRFbSpatialEntityExtensionWrapper::is_component_supported(const XrSpace& space, XrSpaceComponentTypeFB type) {
uint32_t numComponents = 0;
xrEnumerateSpaceSupportedComponentsFB(space, 0, &numComponents, nullptr);
std::vector<XrSpaceComponentTypeFB> components(numComponents);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another usage of std::vector


std::map<XrAsyncRequestIdFB, Vector<XrSpaceQueryResultFB>> query_results;
std::map<String, bool *> request_extensions;
std::map<XrAsyncRequestIdFB, SpaceQueryCompleteCallback_t> query_complete_callbacks;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to use godot::HashMap here instead

Copy link
Collaborator

@m4gr3d m4gr3d left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall!

Just some minor nits regarding types to address before merging.

@maunvz
Copy link
Contributor Author

maunvz commented Jan 18, 2024

addressed final type nits, added missing SCENE_MESH semantic label

@m4gr3d m4gr3d merged commit 2b31718 into GodotVR:master Jan 18, 2024
5 checks passed
@m4gr3d
Copy link
Collaborator

m4gr3d commented Jan 18, 2024

Thanks for the contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants