OpenXR: xrSuggestInteractionProfileBindings returns XR_ERROR_VALIDATION_FAILURE

Started by Richtea, May 20, 2023, 14:33:51

Previous topic - Next topic

Richtea

I am (having previously upgraded it to OpenVR http://forum.lwjgl.org/index.php?topic=7198.0) now planning on upgrading JMonkeyengine to OpenXR; we've got a fair way (The XR session boots and displays something somewhat sensible to the headset) but at the action set phase I'm getting an error I don't understand.

I've been following the example code at https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#_action_overview and I'm getting error code -1. Which I believe is XR_ERROR_VALIDATION_FAILURE, The function usage was invalid in some way. This happens when I call xrSuggestInteractionProfileBindings.

The Khronis example uses a XrActionSuggestedBinding but I'm using a XrActionSuggestedBinding.Buffer, but that seems to be what the LWJGL XrInteractionProfileSuggestedBinding wants me to do

This is the suggest actions code I'm running. Is there anything obvious I'm doing wrong?

    public void registerActions(){

        XrActionSetCreateInfo actionSetCreate = XrActionSetCreateInfo.create();
        actionSetCreate.actionSetName(stringToByte("test"));
        actionSetCreate.localizedActionSetName(stringToByte("testTranslation"));
        actionSetCreate.priority(0);

        PointerBuffer actionSetPointer = BufferUtils.createPointerBuffer(1);
        withResponseCodeLogging("Create action set", XR10.xrCreateActionSet(xrInstance, actionSetCreate, actionSetPointer));
        actionSetCreate.close();

        XrActionSet actionSet = new XrActionSet(actionSetPointer.get(), xrInstance);
        activeActionSet = actionSet;

        XrActionCreateInfo xrActionCreateInfo = XrActionCreateInfo.create();
        xrActionCreateInfo.actionName(stringToByte("teleport"));
        xrActionCreateInfo.actionType(ActionType.BOOLEAN.getOpenXrOption());
        xrActionCreateInfo.localizedActionName(stringToByte("teleportTranslated"));
        PointerBuffer actionPointer = BufferUtils.createPointerBuffer(1);
        withResponseCodeLogging("xrStringToPath", XR10.xrCreateAction(actionSet, xrActionCreateInfo, actionPointer));
        XrAction action = new XrAction(actionPointer.get(), actionSet);
        teleportAction = action;

        xrActionCreateInfo.close();

        LongBuffer oculusProfilePath = BufferUtils.createLongBuffer(1);
        withResponseCodeLogging("xrStringToPath", XR10.xrStringToPath(xrInstance, "/interaction_profiles/oculus/touch_controller", oculusProfilePath));

        //XrcAtionSuggestedBinding suggestedBinding = XrActionSuggestedBinding.create();
        LongBuffer xClickPathBuffer = BufferUtils.createLongBuffer(1);
        withResponseCodeLogging("xrStringToPath",XR10.xrStringToPath(xrInstance,"/user/hand/left/input/x/click", xClickPathBuffer));

        XrActionSuggestedBinding.Buffer suggestedBindingsBuffer = new XrActionSuggestedBinding.Buffer(BufferUtils.createByteBuffer(XrActionSuggestedBinding.SIZEOF));

        suggestedBindingsBuffer.action(action);
        suggestedBindingsBuffer.binding(xClickPathBuffer.get());

        XrInteractionProfileSuggestedBinding xrInteractionProfileSuggestedBinding = XrInteractionProfileSuggestedBinding.create();
        xrInteractionProfileSuggestedBinding.suggestedBindings(suggestedBindingsBuffer);
        xrInteractionProfileSuggestedBinding.interactionProfile(oculusProfilePath.get());

        //the below is the line returning the error code
        withResponseCodeLogging("xrSuggestInteractionProfileBindings", XR10.xrSuggestInteractionProfileBindings(xrInstance, xrInteractionProfileSuggestedBinding));

        actionSetPointer.rewind();
        XrSessionActionSetsAttachInfo actionSetsAttachInfo = XrSessionActionSetsAttachInfo.create();
        actionSetsAttachInfo.actionSets(actionSetPointer);

        withResponseCodeLogging("xrAttachSessionActionSets", XR10.xrAttachSessionActionSets(xrSession, actionSetsAttachInfo));
    }

    private static ByteBuffer stringToByte(String str){
        byte[] strBytes = str.getBytes(StandardCharsets.UTF_8);
        byte[] nullTerminatedBytes = new byte[strBytes.length + 1];
        System.arraycopy(strBytes, 0, nullTerminatedBytes, 0, strBytes.length);
        nullTerminatedBytes[nullTerminatedBytes.length - 1] = 0;  // add null terminator
        ByteBuffer buffer = BufferUtils.createByteBuffer(nullTerminatedBytes.length);
        buffer.put(nullTerminatedBytes);
        buffer.rewind();
        return buffer;
    }

    private static void withResponseCodeLogging(String eventText, int errorCode){
        //error code 0 is ultra common and means all is well. Don't flood the logs with it
        if (errorCode != 0){
            CallResponseCode fullErrorDetails = CallResponseCode.getResponseCode(errorCode);
            if (fullErrorDetails.isAnErrorCondition()){
                logger.warning(fullErrorDetails.getFormattedErrorMessage() + " Occurred during  " + eventText);
            }else{
                if (logger.isLoggable(Level.FINE)){
                    logger.info(fullErrorDetails.getFormattedErrorMessage() + " Occurred during " + eventText);
                }
            }
        }
    }

Richtea

I asked chatgpt and it suggested I change it to this

XrActionSuggestedBinding.Buffer suggestedBindingsBuffer = XrActionSuggestedBinding.calloc(1)
        .action(action.address())
        .binding(xClickPathBuffer.get());

XrInteractionProfileSuggestedBinding xrInteractionProfileSuggestedBinding = XrInteractionProfileSuggestedBinding.calloc()
        .type(XR10.XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING)
        .next(NULL)
        .interactionProfile(oculusProfilePath.get())
        .suggestedBindings(suggestedBindingsBuffer);

withResponseCodeLogging("xrSuggestInteractionProfileBindings", XR10.xrSuggestInteractionProfileBindings(xrInstance, xrInteractionProfileSuggestedBinding));


Which works! Which freaks me out slightly. I think the actual change that was needed (which was a part of the changes chatpt suggested) was that I shouldn't be doing

XrActionSuggestedBinding.Buffer suggestedBindingsBuffer = new XrActionSuggestedBinding.Buffer(BufferUtils.createByteBuffer(XrActionSuggestedBinding.SIZEOF));


But should be doing

XrActionSuggestedBinding.Buffer suggestedBindingsBuffer = XrActionSuggestedBinding.calloc(1)


It seems like quite a useful explanation so here is why chatgpt said the working version is better

QuoteIn Java, when working with LWJGL bindings, it's usually safer and more convenient to use the calloc methods provided by the LWJGL data structures. This method allocates and initializes the buffer for you, which can help avoid issues with uninitialized fields or incorrectly-sized buffers.

In contrast, BufferUtils.createByteBuffer just allocates a block of memory and gives you a ByteBuffer pointing to it. When you pass this to new XrActionSuggestedBinding.Buffer, it creates a view of this memory as XrActionSuggestedBinding structures, but it doesn't initialize them. This can lead to issues if the memory isn't correctly initialized.

By using XrActionSuggestedBinding.calloc(1), you're asking LWJGL to allocate memory for one XrActionSuggestedBinding and initialize it to zero. This is typically what you want when creating new structures to pass to LWJGL functions.

spasi

Hey Richtea,

Both ways to initialize XrActionSuggestedBinding are equivalent. The allocated memory is zero-initialized either way, so ChatGPT is not helpful here. The difference is that the first way uses a GC-managed ByteBuffer as the backing memory (created with ByteBuffer.allocateDirect), whereas the second way uses memory allocated with calloc, which must be explicitly freed to avoid a memory leak. Recommended reading: Memory management in LWJGL

Afaict, the second way works because you added .type(XR10.XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING). It is missing from the original code.

Richtea

Hmm, you're right. I was convinced I had introduced the suggested lines one by one and that was the point it started working but I must have made a mistake. Thanks.

Just for my understanding, most OpenXR LWJGL stuff is created with a
Thing.create();
but the XrActionSuggestedBinding.Buffer is created with a
new Thing.Buffer(BufferUtils.createByteBuffer(Thing.SIZEOF));
. Why the difference? Is it because XrActionSuggestedBinding.Buffer is an array?

spasi

No, new Thing.Buffer(BufferUtils.createByteBuffer(Thing.SIZEOF)) is not the recommended way to do it. It works, but that constructor is there in case you already have some memory around and you need to "cast" it to a specific struct type.

Typically, you'd do:

Thing.Buffer myThings = Thing.create(10); // myThings is now a pointer to an array of 10 Thing structs


You can replace create with malloc/calloc for explicit memory management.

Richtea

Ah, I had not noticed that while XrActionSuggestedBinding.create() returns a XrActionSuggestedBinding that XrActionSuggestedBinding.create(number) returns a Buffer. I was looking for a XrActionSuggestedBinding.Buffer.create(number) not finding it, and ending up creating something weird.

That simplifies things a lot. Thanks