diff --git a/build.gradle b/build.gradle index feb995596..9ff0573f2 100644 --- a/build.gradle +++ b/build.gradle @@ -22,10 +22,7 @@ subprojects { dependencies { minecraft "com.mojang:minecraft:${rootProject.minecraft_version}" mappings loom.layered() { - // The following line declares the mojmap mappings, you may use other mappings as well officialMojangMappings() - // The following line declares the yarn mappings you may select this one as well. - // "net.fabricmc:yarn:1.18.2+build.4:v2" // parchment mappings as backup parchment("org.parchmentmc.data:parchment-${rootProject.minecraft_version}:${rootProject.parchment_version}@zip") } @@ -33,6 +30,11 @@ subprojects { implementation("org.lwjgl:lwjgl-openvr:${rootProject.lwjgl_version}:natives-linux") implementation("org.lwjgl:lwjgl-openvr:${rootProject.lwjgl_version}:natives-macos") implementation("org.lwjgl:lwjgl-openvr:${rootProject.lwjgl_version}:natives-windows") + + // Use custom OpenXR lib for Android and GLES bindings + implementation("QuestCraftPlusPlus:lwjgl3:${rootProject.lwjgl_version}:lwjgl-openxr-${rootProject.lwjgl_version}") + implementation("org.lwjgl:lwjgl-openxr:${rootProject.lwjgl_version}:natives-linux") + implementation("org.lwjgl:lwjgl-openxr:${rootProject.lwjgl_version}:natives-windows") } tasks.withType(JavaCompile).configureEach { @@ -152,17 +154,16 @@ allprojects { exclusiveContent { forRepository { ivy { - name = "Discord" - url = "https://cdn.discordapp.com/attachments/" + name = "GitHub" + url = "https://github.com/" patternLayout { - artifact '/[organisation]/[module]/[revision].[ext]' + artifact '/[organisation]/[module]/releases/download/[revision]/[classifier].jar' } metadataSources { artifact() } } } filter { - // discords are always just numbers - includeGroupByRegex "^\\d*\$" + } } } diff --git a/common/build.gradle b/common/build.gradle index 38c50d11a..a6acd8df6 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -45,7 +45,7 @@ dependencies { compileOnly('com.electronwill.night-config:toml:3.6.6') //LaunchPopup - implementation 'com.github.Vivecraft:LaunchPopup:1.1.1' + implementation 'Vivecraft:LaunchPopup:1.1.1:LaunchPopup-1.1.1' } // extract the LaunchPopup classes jar { diff --git a/common/src/main/java/org/vivecraft/client/extensions/RenderTargetExtension.java b/common/src/main/java/org/vivecraft/client/extensions/RenderTargetExtension.java index cc751cfcf..3258c56c9 100644 --- a/common/src/main/java/org/vivecraft/client/extensions/RenderTargetExtension.java +++ b/common/src/main/java/org/vivecraft/client/extensions/RenderTargetExtension.java @@ -35,4 +35,10 @@ public interface RenderTargetExtension { * @return if the RenderTarget is set to use mipmaps */ boolean vivecraft$hasMipmaps(); + + /** + * Sets the color id + * @param colorid the color id to set + */ + void vivecraft$setColorid(int colorid); } diff --git a/common/src/main/java/org/vivecraft/client_vr/VRData.java b/common/src/main/java/org/vivecraft/client_vr/VRData.java index f90b37a08..bae767182 100644 --- a/common/src/main/java/org/vivecraft/client_vr/VRData.java +++ b/common/src/main/java/org/vivecraft/client_vr/VRData.java @@ -58,7 +58,7 @@ public VRData(Vec3 origin, float walkMul, float worldScale, float rotation) { Vector3f scaleOffset = new Vector3f(scaledPos.x - hmd_raw.x, 0.0F, scaledPos.z - hmd_raw.z); // headset - this.hmd = new VRDevicePose(this, mcVR.hmdRotation, scaledPos, mcVR.getHmdVector()); + this.hmd = new VRDevicePose(this, mcVR.getEyeRotation(RenderPass.CENTER), scaledPos, mcVR.getHmdVector()); this.eye0 = new VRDevicePose(this, mcVR.getEyeRotation(RenderPass.LEFT), diff --git a/common/src/main/java/org/vivecraft/client_vr/VRState.java b/common/src/main/java/org/vivecraft/client_vr/VRState.java index bdf1df0bb..afbe92aac 100644 --- a/common/src/main/java/org/vivecraft/client_vr/VRState.java +++ b/common/src/main/java/org/vivecraft/client_vr/VRState.java @@ -11,6 +11,7 @@ import org.vivecraft.client_vr.menuworlds.MenuWorldRenderer; import org.vivecraft.client_vr.provider.nullvr.NullVR; import org.vivecraft.client_vr.provider.openvr_lwjgl.MCOpenVR; +import org.vivecraft.client_vr.provider.openxr.MCOpenXR; import org.vivecraft.client_vr.render.RenderConfigException; import org.vivecraft.client_vr.settings.VRSettings; import org.vivecraft.client_xr.render_pass.RenderPassManager; @@ -50,11 +51,13 @@ public static void initializeVR() { } ClientDataHolderVR dh = ClientDataHolderVR.getInstance(); - if (dh.vrSettings.stereoProviderPluginID == VRSettings.VRProvider.OPENVR) { - dh.vr = new MCOpenVR(Minecraft.getInstance(), dh); - } else { - dh.vr = new NullVR(Minecraft.getInstance(), dh); + Minecraft instance = Minecraft.getInstance(); + switch (dh.vrSettings.stereoProviderPluginID) { + case OPENVR -> dh.vr = new MCOpenVR(instance, dh); + case OPENXR -> dh.vr = new MCOpenXR(instance, dh); + default -> dh.vr = new NullVR(instance, dh); } + if (!dh.vr.init()) { throw new RenderConfigException(Component.translatable("vivecraft.messages.vriniterror"), Component.translatable("vivecraft.messages.rendersetupfailed", dh.vr.initStatus, dh.vr.getName())); @@ -65,7 +68,7 @@ public static void initializeVR() { // everything related to VR is created now VR_INITIALIZED = true; - dh.vrRenderer.setupRenderConfiguration(); + dh.vrRenderer.setupRenderConfiguration(false); //For openXR, setup but don't render yet RenderPassManager.setVanillaRenderPass(); dh.vrPlayer = new VRPlayer(); diff --git a/common/src/main/java/org/vivecraft/client_vr/VRTextureTarget.java b/common/src/main/java/org/vivecraft/client_vr/VRTextureTarget.java index 37d9d5f2c..782f8ce1e 100644 --- a/common/src/main/java/org/vivecraft/client_vr/VRTextureTarget.java +++ b/common/src/main/java/org/vivecraft/client_vr/VRTextureTarget.java @@ -1,8 +1,10 @@ package org.vivecraft.client_vr; import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.client.Minecraft; +import org.lwjgl.opengl.GL30; import org.vivecraft.client.Xplat; import org.vivecraft.client.extensions.RenderTargetExtension; @@ -34,6 +36,17 @@ public VRTextureTarget(String name, int width, int height, boolean useDepth, int this.setClearColor(0, 0, 0, 0); } + public VRTextureTarget(String name, int width, int height, int colorid, int index) { + super(true); + this.name = name; + RenderSystem.assertOnGameThreadOrInit(); + this.resize(width, height, Minecraft.ON_OSX); + ((RenderTargetExtension) this).vivecraft$setColorid(colorid); + GlStateManager._glBindFramebuffer(GL30.GL_FRAMEBUFFER, frameBufferId); + GL30.glFramebufferTextureLayer(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, colorid, 0, index); + this.setClearColor(0, 0, 0, 0); + } + @Override public String toString() { return """ diff --git a/common/src/main/java/org/vivecraft/client_vr/gameplay/screenhandlers/GuiHandler.java b/common/src/main/java/org/vivecraft/client_vr/gameplay/screenhandlers/GuiHandler.java index 040d288d6..4cecb936a 100644 --- a/common/src/main/java/org/vivecraft/client_vr/gameplay/screenhandlers/GuiHandler.java +++ b/common/src/main/java/org/vivecraft/client_vr/gameplay/screenhandlers/GuiHandler.java @@ -510,7 +510,7 @@ public static Vec3 applyGUIModelView(RenderPass currentPass, PoseStack poseStack direction = DH.vrPlayer.vrdata_world_render.getController(0).getDirection(); guirot = guirot.mul(DH.vr.getAimRotation(0), guirot); } else { - guirot = guirot.mul(DH.vr.hmdRotation, guirot); + guirot = guirot.mul(DH.vr.getEyeRotation(RenderPass.CENTER), guirot); } guipos = new Vec3( diff --git a/common/src/main/java/org/vivecraft/client_vr/gui/GuiRadial.java b/common/src/main/java/org/vivecraft/client_vr/gui/GuiRadial.java index 646ca6b70..808fc2b3b 100644 --- a/common/src/main/java/org/vivecraft/client_vr/gui/GuiRadial.java +++ b/common/src/main/java/org/vivecraft/client_vr/gui/GuiRadial.java @@ -7,7 +7,7 @@ import net.minecraft.network.chat.Component; import org.vivecraft.client.gui.framework.TwoHandedScreen; import org.vivecraft.client_vr.provider.MCVR; -import org.vivecraft.client_vr.provider.openvr_lwjgl.VRInputAction; +import org.vivecraft.client_vr.provider.control.VRInputAction; public class GuiRadial extends TwoHandedScreen { private boolean isShift = false; diff --git a/common/src/main/java/org/vivecraft/client_vr/provider/ActionParams.java b/common/src/main/java/org/vivecraft/client_vr/provider/ActionParams.java index 1c6b91386..4e99173d3 100644 --- a/common/src/main/java/org/vivecraft/client_vr/provider/ActionParams.java +++ b/common/src/main/java/org/vivecraft/client_vr/provider/ActionParams.java @@ -1,6 +1,6 @@ package org.vivecraft.client_vr.provider; -import org.vivecraft.client_vr.provider.openvr_lwjgl.control.VRInputActionSet; +import org.vivecraft.client_vr.provider.control.VRInputActionSet; /** * holds the parameters for a VR action key diff --git a/common/src/main/java/org/vivecraft/client_vr/provider/MCVR.java b/common/src/main/java/org/vivecraft/client_vr/provider/MCVR.java index 8a0c34df9..adf408564 100644 --- a/common/src/main/java/org/vivecraft/client_vr/provider/MCVR.java +++ b/common/src/main/java/org/vivecraft/client_vr/provider/MCVR.java @@ -1,5 +1,6 @@ package org.vivecraft.client_vr.provider; +import com.mojang.blaze3d.platform.InputConstants; import net.minecraft.client.KeyMapping; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.ChatScreen; @@ -24,9 +25,11 @@ import org.vivecraft.client_vr.gameplay.screenhandlers.GuiHandler; import org.vivecraft.client_vr.gameplay.screenhandlers.KeyboardHandler; import org.vivecraft.client_vr.gameplay.screenhandlers.RadialHandler; +import org.vivecraft.client_vr.provider.control.TrackpadSwipeSampler; +import org.vivecraft.client_vr.provider.control.VRInputAction; +import org.vivecraft.client_vr.provider.control.VRInputActionSet; +import org.vivecraft.client_vr.provider.openxr.DeviceCompat; import org.vivecraft.client_vr.gameplay.trackers.ClimbTracker; -import org.vivecraft.client_vr.provider.openvr_lwjgl.VRInputAction; -import org.vivecraft.client_vr.provider.openvr_lwjgl.control.VRInputActionSet; import org.vivecraft.client_vr.render.RenderConfigException; import org.vivecraft.client_vr.render.RenderPass; import org.vivecraft.client_vr.settings.VRHotkeys; @@ -42,6 +45,9 @@ import java.util.stream.Stream; public abstract class MCVR { + public static final int LEFT_CONTROLLER = 1; + public static final int RIGHT_CONTROLLER = 0; + public static final int CAMERA_TRACKER = 2; protected Minecraft mc; protected ClientDataHolderVR dh; protected static MCVR ME; @@ -49,11 +55,13 @@ public abstract class MCVR { protected HardwareType detectedHardware = HardwareType.VIVE; - // position/orientation of headset and eye offsets - protected Matrix4f hmdPose = new Matrix4f(); - public Matrix4f hmdRotation = new Matrix4f(); - protected Matrix4f hmdPoseLeftEye = new Matrix4f(); - protected Matrix4f hmdPoseRightEye = new Matrix4f(); + // position/orientation of headset and eyes + protected final Matrix4f hmdPose = new Matrix4f(); + protected final Matrix4f hmdRotation = new Matrix4f(); + protected final Matrix4f hmdPoseLeftEye = new Matrix4f(); + protected final Matrix4f hmdPoseRightEye = new Matrix4f(); + protected final Matrix4f hmdRotationLeftEye = new Matrix4f(); + protected final Matrix4f hmdRotationRightEye = new Matrix4f(); public Vector3fHistory hmdHistory = new Vector3fHistory(); public Vector3fHistory hmdPivotHistory = new Vector3fHistory(); @@ -103,6 +111,9 @@ public abstract class MCVR { protected int quickTorchPreviousSlot; protected Map inputActions = new HashMap<>(); protected Map inputActionsByKeyBinding = new HashMap<>(); + protected final Map trackpadSwipeSamplers = new HashMap<>(); + protected boolean inputInitialized; + public final DeviceCompat device; /** * creates the MCVR instance @@ -114,6 +125,7 @@ public MCVR(Minecraft mc, ClientDataHolderVR dh, VivecraftVRMod vrMod) { this.mc = mc; this.dh = dh; MOD = vrMod; + this.device = DeviceCompat.detectDevice(); ME = this; // initialize all controller/tracker fields @@ -275,11 +287,10 @@ public Vector3f getHandVector(int controller) { * @return position of the given eye, in room space */ public Vector3f getEyePosition(RenderPass eye) { - Matrix4f pose = new Matrix4f(this.hmdPose); - switch (eye) { - case LEFT -> pose.mul(this.hmdPoseLeftEye); - case RIGHT -> pose.mul(this.hmdPoseRightEye); - default -> {} + Matrix4fc pose = switch (eye) { + case LEFT -> this.hmdPoseLeftEye; + case RIGHT -> this.hmdPoseRightEye; + default -> this.hmdPose; }; Vector3f pos = pose.getTranslation(new Vector3f()); @@ -298,18 +309,11 @@ public Vector3f getEyePosition(RenderPass eye) { * @return rotation of the given eye, in room space */ public Matrix4fc getEyeRotation(RenderPass eye) { - Matrix4f hmdToEye = switch (eye) { - case LEFT -> this.hmdPoseLeftEye; - case RIGHT -> this.hmdPoseRightEye; - default -> null; + return switch (eye) { + case LEFT -> this.hmdRotationLeftEye; + case RIGHT -> this.hmdRotationRightEye; + default -> this.hmdRotation; }; - - if (hmdToEye != null) { - Matrix4f eyeRot = new Matrix4f().set3x3(hmdToEye); - return this.hmdRotation.mul(eyeRot, eyeRot); - } else { - return this.hmdRotation; - } } /** @@ -584,6 +588,9 @@ protected void updateAim() { this.hmdRotation.identity(); this.hmdRotation.set3x3(this.hmdPose); + this.hmdRotationLeftEye.set3x3(this.hmdPoseLeftEye); + this.hmdRotationRightEye.set3x3(this.hmdPoseRightEye); + Vector3fc eye = this.getEyePosition(RenderPass.CENTER); this.hmdHistory.add(eye); @@ -1163,7 +1170,131 @@ public boolean handleKeyboardInputs(int key, int scanCode, int action, int modif /** * processes the fetched inputs from the VR runtime, and maps them to the ingame keys */ - public abstract void processInputs(); + public void processInputs() { + if (this.dh.vrSettings.seated || ClientDataHolderVR.VIEW_ONLY || !this.inputInitialized) return; + + for (VRInputAction action : this.inputActions.values()) { + if (action.isHanded()) { + for (ControllerType controllertype : ControllerType.values()) { + action.setCurrentHand(controllertype); + this.processInputAction(action); + } + } else { + this.processInputAction(action); + } + } + + this.processScrollInput(GuiHandler.KEY_SCROLL_AXIS, + () -> InputSimulator.scrollMouse(0.0D, 1.0D), + () -> InputSimulator.scrollMouse(0.0D, -1.0D)); + this.processScrollInput(VivecraftVRMod.INSTANCE.keyHotbarScroll, + () -> this.changeHotbar(-1), + () -> this.changeHotbar(1)); + this.processSwipeInput(VivecraftVRMod.INSTANCE.keyHotbarSwipeX, + () -> this.changeHotbar(1), + () -> this.changeHotbar(-1), null, null); + this.processSwipeInput(VivecraftVRMod.INSTANCE.keyHotbarSwipeY, null, null, + () -> this.changeHotbar(-1), + () -> this.changeHotbar(1)); + this.ignorePressesNextFrame = false; + } + + /** + * updates the KeyMapping state that is linked to the given VRInputAction + * @param action VRInputAction to process + */ + private void processInputAction(VRInputAction action) { + if (action.isActive() && action.isEnabledRaw() && + // try to prevent double left clicks + (!ClientDataHolderVR.getInstance().vrSettings.ingameBindingsInGui || + !(action.actionSet == VRInputActionSet.INGAME && + action.keyBinding.key.getType() == InputConstants.Type.MOUSE && + action.keyBinding.key.getValue() == GLFW.GLFW_MOUSE_BUTTON_LEFT && this.mc.screen != null + ) + )) + { + if (action.isButtonChanged()) { + if (action.isButtonPressed() && action.isEnabled()) { + // We do this, so shit like closing a GUI by clicking a button won't + // also click in the world immediately after. + if (!this.ignorePressesNextFrame) { + action.pressBinding(); + } + } else { + action.unpressBinding(); + } + } + } else { + action.unpressBinding(); + } + } + + /** + * checks the axis input of the VRInputAction linked to {@code keyMapping} and runs the callbacks when it's non 0 + * @param keyMapping KeyMapping to check + * @param upCallback action to do when the axis input is positive + * @param downCallback action to do when the axis input is negative + */ + private void processScrollInput(KeyMapping keyMapping, Runnable upCallback, Runnable downCallback) { + VRInputAction action = this.getInputAction(keyMapping); + + if (action.isEnabled() && action.getLastOrigin() != 0L) { /** {@link org.lwjgl.openvr.VR.k_ulInvalidInputValueHandle} and {@link org.lwjgl.system.MemoryUtil.NULL} are both 0 */ + float value = action.getAxis2D(false).y(); + if (value != 0.0F) { + if (value > 0.0F) { + upCallback.run(); + } else if (value < 0.0F) { + downCallback.run(); + } + } + } + } + + /** + * checks the trackpad input of the controller the {@code keyMapping} is on + * @param keyMapping KeyMapping to check + * @param leftCallback action to do when swiped to the left + * @param rightCallback action to do when swiped to the right + * @param upCallback action to do when swiped to the up + * @param downCallback action to do when swiped to the down + */ + private void processSwipeInput(KeyMapping keyMapping, Runnable leftCallback, Runnable rightCallback, Runnable upCallback, Runnable downCallback) { + VRInputAction action = this.getInputAction(keyMapping); + + if (action.isEnabled() && action.getLastOrigin() != 0L) { /** {@link org.lwjgl.openvr.VR.k_ulInvalidInputValueHandle} and {@link org.lwjgl.system.MemoryUtil.NULL} are both 0 */ + ControllerType controller = this.findActiveBindingControllerType(keyMapping); + + if (controller != null) { + // if that keyMapping is not tracked yet, create a new sampler + if (!this.trackpadSwipeSamplers.containsKey(keyMapping.getName())) { + this.trackpadSwipeSamplers.put(keyMapping.getName(), new TrackpadSwipeSampler()); + } + + TrackpadSwipeSampler trackpadswipesampler = this.trackpadSwipeSamplers.get(keyMapping.getName()); + trackpadswipesampler.update(controller, action.getAxis2D(false)); + + if (trackpadswipesampler.isSwipedUp() && upCallback != null) { + this.triggerHapticPulse(controller, 0.001F, 400.0F, 0.5F); + upCallback.run(); + } + + if (trackpadswipesampler.isSwipedDown() && downCallback != null) { + this.triggerHapticPulse(controller, 0.001F, 400.0F, 0.5F); + downCallback.run(); + } + + if (trackpadswipesampler.isSwipedLeft() && leftCallback != null) { + this.triggerHapticPulse(controller, 0.001F, 400.0F, 0.5F); + leftCallback.run(); + } + + if (trackpadswipesampler.isSwipedRight() && rightCallback != null) { + this.triggerHapticPulse(controller, 0.001F, 400.0F, 0.5F); + rightCallback.run(); + } + } + } + } /** * @param keyMapping KeyMapping to check where it is bound at @@ -1216,6 +1347,12 @@ public boolean handleKeyboardInputs(int key, int scanCode, int action, int modif */ public abstract boolean isActive(); + /** + * @param inputValueHandle inputHandle to check + * @return what controller the inputHandle is on, {@code null} if the handle or device is invalid + */ + public abstract ControllerType getOriginControllerType(long inputValueHandle); + /** * determines if the vanilla framecap should still be applied, * by default this returns false, since the VR runtime should handle any frame caps diff --git a/common/src/main/java/org/vivecraft/client_vr/provider/VRRenderer.java b/common/src/main/java/org/vivecraft/client_vr/provider/VRRenderer.java index f9f75b5d8..29bb0f4b7 100644 --- a/common/src/main/java/org/vivecraft/client_vr/provider/VRRenderer.java +++ b/common/src/main/java/org/vivecraft/client_vr/provider/VRRenderer.java @@ -26,6 +26,7 @@ import org.vivecraft.client.utils.TextUtils; import org.vivecraft.client_vr.ClientDataHolderVR; import org.vivecraft.client_vr.VRTextureTarget; +import org.vivecraft.client_vr.extensions.GameRendererExtension; import org.vivecraft.client_vr.extensions.WindowExtension; import org.vivecraft.client_vr.gameplay.screenhandlers.GuiHandler; import org.vivecraft.client_vr.gameplay.screenhandlers.KeyboardHandler; @@ -48,13 +49,12 @@ import java.util.stream.Collectors; public abstract class VRRenderer { + // projection matrices public Matrix4f[] eyeProj = new Matrix4f[2]; private float lastFarClip = 0F; // render buffers - public RenderTarget framebufferEye0; - public RenderTarget framebufferEye1; protected int LeftEyeTextureId = -1; protected int RightEyeTextureId = -1; public RenderTarget framebufferMR; @@ -100,7 +100,7 @@ public VRRenderer(MCVR vr) { * @param width width of the texture * @param height height of the texture */ - public abstract void createRenderTexture(int width, int height); + public abstract void createRenderTexture(int width, int height) throws RenderConfigException; /** * gets the cached projection matrix if the farClip distance matches with the last, else gets a new one from the VR runtime @@ -141,6 +141,15 @@ public Matrix4f getCachedProjectionMatrix(int eyeType, float nearClip, float far */ public abstract boolean providesStencilMask(); + /** + * @return the left eye rendertarget + */ + public abstract RenderTarget getLeftEyeTarget(); + + /** + * @return the right eye rendertarget + */ + public abstract RenderTarget getRightEyeTarget(); /** * gets an array with the vertex info of the stencil mesh, if there is one provided by this renderer * @param eye which eye the stencil should be for @@ -469,7 +478,7 @@ public void resizeFrameBuffers(String cause) { * @throws RenderConfigException in case something failed to initialize or the gpu vendor is unsupported * @throws IOException can be thrown by the WorldRenderPass init when trying to load the shaders */ - public void setupRenderConfiguration() throws RenderConfigException, IOException { + public void setupRenderConfiguration(boolean render) throws RenderConfigException, IOException { Minecraft minecraft = Minecraft.getInstance(); ClientDataHolderVR dataholder = ClientDataHolderVR.getInstance(); @@ -563,6 +572,10 @@ public void setupRenderConfiguration() throws RenderConfigException, IOException } } + //for OPENXR, it needs to reinit + this.eyeProj[0] = this.getProjectionMatrix(0, ((GameRendererExtension) minecraft.gameRenderer).vivecraft$getMinClipDistance(), lastFarClip); + this.eyeProj[1] = this.getProjectionMatrix(1, ((GameRendererExtension) minecraft.gameRenderer).vivecraft$getMinClipDistance(), lastFarClip); + if (this.reinitFrameBuffers) { RenderHelper.checkGLError("Start Init"); @@ -604,32 +617,7 @@ public void setupRenderConfiguration() throws RenderConfigException, IOException destroyBuffers(); - if (this.LeftEyeTextureId == -1) { - this.createRenderTexture(eyew, eyeh); - - if (this.LeftEyeTextureId == -1) { - throw new RenderConfigException( - Component.translatable("vivecraft.messages.renderiniterror", this.getName()), - Component.literal(this.getLastError())); - } - - VRSettings.LOGGER.info("Vivecraft: VR Provider supplied render texture IDs: {}, {}", this.LeftEyeTextureId, this.RightEyeTextureId); - VRSettings.LOGGER.info("Vivecraft: VR Provider supplied texture resolution: {} x {}", eyew, eyeh); - } - - RenderHelper.checkGLError("Render Texture setup"); - - if (this.framebufferEye0 == null) { - this.framebufferEye0 = new VRTextureTarget("L Eye", eyew, eyeh, false, this.LeftEyeTextureId, true, false, false); - VRSettings.LOGGER.info("Vivecraft: {}", this.framebufferEye0); - RenderHelper.checkGLError("Left Eye framebuffer setup"); - } - - if (this.framebufferEye1 == null) { - this.framebufferEye1 = new VRTextureTarget("R Eye", eyew, eyeh, false, this.RightEyeTextureId, true, false, false); - VRSettings.LOGGER.info("Vivecraft: {}", this.framebufferEye1); - RenderHelper.checkGLError("Right Eye framebuffer setup"); - } + this.createRenderTexture(eyew, eyeh); float resolutionScale = ResolutionControlHelper.isLoaded() ? ResolutionControlHelper.getCurrentScaleFactor() : 1.0F; @@ -873,18 +861,6 @@ protected void destroyBuffers() { this.fsaaLastPassResultFBO.destroyBuffers(); this.fsaaLastPassResultFBO = null; } - - if (this.framebufferEye0 != null) { - this.framebufferEye0.destroyBuffers(); - this.framebufferEye0 = null; - this.LeftEyeTextureId = -1; - } - - if (this.framebufferEye1 != null) { - this.framebufferEye1.destroyBuffers(); - this.framebufferEye1 = null; - this.RightEyeTextureId = -1; - } } /** diff --git a/common/src/main/java/org/vivecraft/client_vr/provider/openvr_lwjgl/control/HapticMusicPlayer.java b/common/src/main/java/org/vivecraft/client_vr/provider/control/HapticMusicPlayer.java similarity index 78% rename from common/src/main/java/org/vivecraft/client_vr/provider/openvr_lwjgl/control/HapticMusicPlayer.java rename to common/src/main/java/org/vivecraft/client_vr/provider/control/HapticMusicPlayer.java index 244f14852..3adb77642 100644 --- a/common/src/main/java/org/vivecraft/client_vr/provider/openvr_lwjgl/control/HapticMusicPlayer.java +++ b/common/src/main/java/org/vivecraft/client_vr/provider/control/HapticMusicPlayer.java @@ -1,7 +1,7 @@ -package org.vivecraft.client_vr.provider.openvr_lwjgl.control; +package org.vivecraft.client_vr.provider.control; +import org.vivecraft.client_vr.ClientDataHolderVR; import org.vivecraft.client_vr.provider.ControllerType; -import org.vivecraft.client_vr.provider.openvr_lwjgl.MCOpenVR; import javax.annotation.Nullable; import java.util.HashMap; @@ -63,10 +63,10 @@ public void play() { for (Object object : this.data) { if (object instanceof Note note) { if (note.controller != null) { - MCOpenVR.get().triggerHapticPulse(note.controller, note.durationSeconds, note.frequency, note.amplitude, delayAccum); + ClientDataHolderVR.getInstance().vr.triggerHapticPulse(note.controller, note.durationSeconds, note.frequency, note.amplitude, delayAccum); } else { - MCOpenVR.get().triggerHapticPulse(ControllerType.RIGHT, note.durationSeconds, note.frequency, note.amplitude, delayAccum); - MCOpenVR.get().triggerHapticPulse(ControllerType.LEFT, note.durationSeconds, note.frequency, note.amplitude, delayAccum); + ClientDataHolderVR.getInstance().vr.triggerHapticPulse(ControllerType.RIGHT, note.durationSeconds, note.frequency, note.amplitude, delayAccum); + ClientDataHolderVR.getInstance().vr.triggerHapticPulse(ControllerType.LEFT, note.durationSeconds, note.frequency, note.amplitude, delayAccum); } } else if (object instanceof Delay delay) { delayAccum += delay.durationSeconds; diff --git a/common/src/main/java/org/vivecraft/client_vr/provider/openvr_lwjgl/control/TrackpadSwipeSampler.java b/common/src/main/java/org/vivecraft/client_vr/provider/control/TrackpadSwipeSampler.java similarity index 95% rename from common/src/main/java/org/vivecraft/client_vr/provider/openvr_lwjgl/control/TrackpadSwipeSampler.java rename to common/src/main/java/org/vivecraft/client_vr/provider/control/TrackpadSwipeSampler.java index 40cbd3f02..5dfaa3b8f 100644 --- a/common/src/main/java/org/vivecraft/client_vr/provider/openvr_lwjgl/control/TrackpadSwipeSampler.java +++ b/common/src/main/java/org/vivecraft/client_vr/provider/control/TrackpadSwipeSampler.java @@ -1,11 +1,11 @@ -package org.vivecraft.client_vr.provider.openvr_lwjgl.control; +package org.vivecraft.client_vr.provider.control; import org.joml.Vector2f; import org.joml.Vector2fc; import org.vivecraft.client.VivecraftVRMod; +import org.vivecraft.client_vr.ClientDataHolderVR; import org.vivecraft.client_vr.provider.ControllerType; import org.vivecraft.client_vr.provider.openvr_lwjgl.MCOpenVR; -import org.vivecraft.client_vr.provider.openvr_lwjgl.VRInputAction; public class TrackpadSwipeSampler { private static final int UP = 0; diff --git a/common/src/main/java/org/vivecraft/client_vr/provider/openvr_lwjgl/VRInputAction.java b/common/src/main/java/org/vivecraft/client_vr/provider/control/VRInputAction.java similarity index 96% rename from common/src/main/java/org/vivecraft/client_vr/provider/openvr_lwjgl/VRInputAction.java rename to common/src/main/java/org/vivecraft/client_vr/provider/control/VRInputAction.java index 5868dcfc3..b664d57db 100644 --- a/common/src/main/java/org/vivecraft/client_vr/provider/openvr_lwjgl/VRInputAction.java +++ b/common/src/main/java/org/vivecraft/client_vr/provider/control/VRInputAction.java @@ -1,4 +1,4 @@ -package org.vivecraft.client_vr.provider.openvr_lwjgl; +package org.vivecraft.client_vr.provider.control; import com.mojang.blaze3d.platform.InputConstants; import net.minecraft.client.KeyMapping; @@ -8,11 +8,10 @@ import org.joml.Vector3fc; import org.vivecraft.client.VivecraftVRMod; import org.vivecraft.client.Xplat; +import org.vivecraft.client_vr.ClientDataHolderVR; import org.vivecraft.client_vr.provider.ControllerType; import org.vivecraft.client_vr.provider.HandedKeyBinding; import org.vivecraft.client_vr.provider.InputSimulator; -import org.vivecraft.client_vr.provider.MCVR; -import org.vivecraft.client_vr.provider.openvr_lwjgl.control.VRInputActionSet; import javax.annotation.Nullable; import java.util.ArrayList; @@ -215,17 +214,17 @@ public VRInputAction setPriority(int priority) { */ public boolean isEnabled() { if (!this.isEnabledRaw(this.currentHand)) return false; - if (MCOpenVR.get() == null) return false; + if (ClientDataHolderVR.getInstance().vr == null) return false; long lastOrigin = this.getLastOrigin(); - ControllerType hand = MCOpenVR.get().getOriginControllerType(lastOrigin); + ControllerType hand = ClientDataHolderVR.getInstance().vr.getOriginControllerType(lastOrigin); if (hand == null && this.isHanded()) return false; // iterate over all actions, and check if another action has a higher priority - for (VRInputAction action : MCOpenVR.get().getInputActions()) { + for (VRInputAction action : ClientDataHolderVR.getInstance().vr.getInputActions()) { if (action != this && action.isEnabledRaw(hand) && action.isActive() && - action.getPriority() > this.getPriority() && MCVR.get().getOrigins(action).contains(lastOrigin)) + action.getPriority() > this.getPriority() && ClientDataHolderVR.getInstance().vr.getOrigins(action).contains(lastOrigin)) { if (action.isHanded()) { return !((HandedKeyBinding) action.keyBinding).isPriorityOnController(hand); diff --git a/common/src/main/java/org/vivecraft/client_vr/provider/openvr_lwjgl/control/VRInputActionSet.java b/common/src/main/java/org/vivecraft/client_vr/provider/control/VRInputActionSet.java similarity index 97% rename from common/src/main/java/org/vivecraft/client_vr/provider/openvr_lwjgl/control/VRInputActionSet.java rename to common/src/main/java/org/vivecraft/client_vr/provider/control/VRInputActionSet.java index cdbf24741..041330bef 100644 --- a/common/src/main/java/org/vivecraft/client_vr/provider/openvr_lwjgl/control/VRInputActionSet.java +++ b/common/src/main/java/org/vivecraft/client_vr/provider/control/VRInputActionSet.java @@ -1,4 +1,4 @@ -package org.vivecraft.client_vr.provider.openvr_lwjgl.control; +package org.vivecraft.client_vr.provider.control; import net.minecraft.client.KeyMapping; import org.vivecraft.client.VivecraftVRMod; diff --git a/common/src/main/java/org/vivecraft/client_vr/provider/control/VivecraftMovementInput.java b/common/src/main/java/org/vivecraft/client_vr/provider/control/VivecraftMovementInput.java new file mode 100644 index 000000000..79de67246 --- /dev/null +++ b/common/src/main/java/org/vivecraft/client_vr/provider/control/VivecraftMovementInput.java @@ -0,0 +1,11 @@ +package org.vivecraft.client_vr.provider.control; + +import net.minecraft.client.KeyMapping; +import org.vivecraft.client_vr.ClientDataHolderVR; + +public class VivecraftMovementInput { + public static float getMovementAxisValue(KeyMapping keyBinding) { + VRInputAction vrinputaction = ClientDataHolderVR.getInstance().vr.getInputAction(keyBinding); + return Math.abs(vrinputaction.getAxis1DUseTracked()); + } +} diff --git a/common/src/main/java/org/vivecraft/client_vr/provider/nullvr/NullVR.java b/common/src/main/java/org/vivecraft/client_vr/provider/nullvr/NullVR.java index 0c9065ef2..84d2c5b9c 100644 --- a/common/src/main/java/org/vivecraft/client_vr/provider/nullvr/NullVR.java +++ b/common/src/main/java/org/vivecraft/client_vr/provider/nullvr/NullVR.java @@ -11,7 +11,7 @@ import org.vivecraft.client_vr.provider.ControllerType; import org.vivecraft.client_vr.provider.MCVR; import org.vivecraft.client_vr.provider.VRRenderer; -import org.vivecraft.client_vr.provider.openvr_lwjgl.VRInputAction; +import org.vivecraft.client_vr.provider.control.VRInputAction; import org.vivecraft.client_vr.settings.VRSettings; import java.util.List; @@ -65,7 +65,10 @@ public boolean init() { this.hmdPose.m31(1.62F); // eye offset, 10cm total distance + this.hmdPoseLeftEye.set(this.hmdPose); this.hmdPoseLeftEye.m30(-IPD * 0.5F); + + this.hmdPoseRightEye.set(this.hmdPose); this.hmdPoseRightEye.m30(IPD * 0.5F); this.populateInputActions(); @@ -159,6 +162,11 @@ public boolean isActive() { return this.vrActive; } + @Override + public ControllerType getOriginControllerType(long i) { + return ControllerType.LEFT; + } + @Override public boolean capFPS() { return true; diff --git a/common/src/main/java/org/vivecraft/client_vr/provider/nullvr/NullVRStereoRenderer.java b/common/src/main/java/org/vivecraft/client_vr/provider/nullvr/NullVRStereoRenderer.java index 6bd5dfb8b..a1d975844 100644 --- a/common/src/main/java/org/vivecraft/client_vr/provider/nullvr/NullVRStereoRenderer.java +++ b/common/src/main/java/org/vivecraft/client_vr/provider/nullvr/NullVRStereoRenderer.java @@ -1,5 +1,6 @@ package org.vivecraft.client_vr.provider.nullvr; +import com.mojang.blaze3d.pipeline.RenderTarget; import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.platform.TextureUtil; import com.mojang.blaze3d.systems.RenderSystem; @@ -9,10 +10,18 @@ import org.lwjgl.opengl.GL11; import org.vivecraft.client_vr.provider.MCVR; import org.vivecraft.client_vr.provider.VRRenderer; +import org.vivecraft.client_vr.render.RenderConfigException; +import org.vivecraft.client_vr.render.RenderPass; import org.vivecraft.client_vr.render.helpers.RenderHelper; import org.vivecraft.client_vr.settings.VRSettings; public class NullVRStereoRenderer extends VRRenderer { + + protected int LeftEyeTextureId = -1; + protected int RightEyeTextureId = -1; + public RenderTarget framebufferEye0; + public RenderTarget framebufferEye1; + public NullVRStereoRenderer(MCVR vr) { super(vr); } @@ -34,7 +43,7 @@ protected Matrix4f getProjectionMatrix(int eyeType, float nearClip, float farCli } @Override - public void createRenderTexture(int lwidth, int lheight) { + public void createRenderTexture(int lwidth, int lheight) throws RenderConfigException { this.LeftEyeTextureId = GlStateManager._genTexture(); int i = GlStateManager._getInteger(GL11.GL_TEXTURE_BINDING_2D); RenderSystem.bindTexture(this.LeftEyeTextureId); @@ -61,6 +70,22 @@ public boolean providesStencilMask() { return false; } + @Override + public RenderTarget getLeftEyeTarget() { + return framebufferEye0; + } + + @Override + public RenderTarget getRightEyeTarget() { + return framebufferEye1; + } + + + @Override + public float[] getStencilMask(RenderPass eye) { + return null; + } + @Override public String getName() { return "NullVR"; @@ -69,6 +94,17 @@ public String getName() { @Override protected void destroyBuffers() { super.destroyBuffers(); + + if (this.framebufferEye0 != null) { + this.framebufferEye0.destroyBuffers(); + this.framebufferEye0 = null; + } + + if (this.framebufferEye1 != null) { + this.framebufferEye1.destroyBuffers(); + this.framebufferEye1 = null; + } + if (this.LeftEyeTextureId > -1) { TextureUtil.releaseTextureId(this.LeftEyeTextureId); this.LeftEyeTextureId = -1; diff --git a/common/src/main/java/org/vivecraft/client_vr/provider/openvr_lwjgl/MCOpenVR.java b/common/src/main/java/org/vivecraft/client_vr/provider/openvr_lwjgl/MCOpenVR.java index 2327e806a..bf8ee4fdc 100644 --- a/common/src/main/java/org/vivecraft/client_vr/provider/openvr_lwjgl/MCOpenVR.java +++ b/common/src/main/java/org/vivecraft/client_vr/provider/openvr_lwjgl/MCOpenVR.java @@ -29,8 +29,9 @@ import org.vivecraft.client_vr.gameplay.screenhandlers.KeyboardHandler; import org.vivecraft.client_vr.gameplay.screenhandlers.RadialHandler; import org.vivecraft.client_vr.provider.*; -import org.vivecraft.client_vr.provider.openvr_lwjgl.control.TrackpadSwipeSampler; -import org.vivecraft.client_vr.provider.openvr_lwjgl.control.VRInputActionSet; +import org.vivecraft.client_vr.provider.control.TrackpadSwipeSampler; +import org.vivecraft.client_vr.provider.control.VRInputAction; +import org.vivecraft.client_vr.provider.control.VRInputActionSet; import org.vivecraft.client_vr.render.RenderConfigException; import org.vivecraft.client_vr.settings.VRSettings; import org.vivecraft.client_vr.utils.external.jinfinadeck; @@ -62,10 +63,6 @@ * MCVR implementation to communicate with OpenVR/SteamVR */ public class MCOpenVR extends MCVR { - public static final int LEFT_CONTROLLER = 1; - public static final int RIGHT_CONTROLLER = 0; - public static final int CAMERA_TRACKER = 2; - protected static MCOpenVR OME; // action paths @@ -90,10 +87,10 @@ public class MCOpenVR extends MCVR { private final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); - private final TrackedDevicePose.Buffer hmdTrackedDevicePoses; - private final int[] controllerDeviceIndex = new int[3]; + private final TrackedDevicePose.Buffer hmdTrackedDevicePoses; + private long leftControllerHandle; private long leftHapticHandle; private long leftPoseHandle; @@ -101,7 +98,6 @@ public class MCOpenVR extends MCVR { private long rightControllerHandle; private long rightHapticHandle; private long rightPoseHandle; - private long externalCameraPoseHandle; private boolean inputInitialized; @@ -446,36 +442,6 @@ public void poll(long frameIndex) { this.mc.getProfiler().pop(); } - @Override - public void processInputs() { - if (this.dh.vrSettings.seated || ClientDataHolderVR.VIEW_ONLY || !this.inputInitialized) return; - - for (VRInputAction action : this.inputActions.values()) { - if (action.isHanded()) { - for (ControllerType controllertype : ControllerType.values()) { - action.setCurrentHand(controllertype); - this.processInputAction(action); - } - } else { - this.processInputAction(action); - } - } - - this.processScrollInput(GuiHandler.KEY_SCROLL_AXIS, - () -> InputSimulator.scrollMouse(0.0D, 1.0D), - () -> InputSimulator.scrollMouse(0.0D, -1.0D)); - this.processScrollInput(VivecraftVRMod.INSTANCE.keyHotbarScroll, - () -> this.changeHotbar(-1), - () -> this.changeHotbar(1)); - this.processSwipeInput(VivecraftVRMod.INSTANCE.keyHotbarSwipeX, - () -> this.changeHotbar(1), - () -> this.changeHotbar(-1), null, null); - this.processSwipeInput(VivecraftVRMod.INSTANCE.keyHotbarSwipeY, null, null, - () -> this.changeHotbar(-1), - () -> this.changeHotbar(1)); - this.ignorePressesNextFrame = false; - } - /** * @return if the error buffer contains any error */ @@ -1106,105 +1072,6 @@ private void loadActionManifest() throws RenderConfigException { } } - /** - * updates the KeyMapping state that is linked to the given VRInputAction - * @param action VRInputAction to process - */ - private void processInputAction(VRInputAction action) { - if (action.isActive() && action.isEnabledRaw() && - // try to prevent double left clicks - (!ClientDataHolderVR.getInstance().vrSettings.ingameBindingsInGui || - !(action.actionSet == VRInputActionSet.INGAME && - action.keyBinding.key.getType() == InputConstants.Type.MOUSE && - action.keyBinding.key.getValue() == GLFW.GLFW_MOUSE_BUTTON_LEFT && this.mc.screen != null - ) - )) - { - if (action.isButtonChanged()) { - if (action.isButtonPressed() && action.isEnabled()) { - // We do this, so shit like closing a GUI by clicking a button won't - // also click in the world immediately after. - if (!this.ignorePressesNextFrame) { - action.pressBinding(); - } - } else { - action.unpressBinding(); - } - } - } else { - action.unpressBinding(); - } - } - - /** - * checks the axis input of the VRInputAction linked to {@code keyMapping} and runs the callbacks when it's non 0 - * @param keyMapping KeyMapping to check - * @param upCallback action to do when the axis input is positive - * @param downCallback action to do when the axis input is negative - */ - private void processScrollInput(KeyMapping keyMapping, Runnable upCallback, Runnable downCallback) { - VRInputAction action = this.getInputAction(keyMapping); - - if (action.isEnabled() && action.getLastOrigin() != k_ulInvalidInputValueHandle) { - float value = action.getAxis2D(false).y(); - if (value != 0.0F) { - if (value > 0.0F) { - upCallback.run(); - } else if (value < 0.0F) { - downCallback.run(); - } - } - } - } - - /** - * checks the trackpad input of the controller the {@code keyMapping} is on - * @param keyMapping KeyMapping to check - * @param leftCallback action to do when swiped to the left - * @param rightCallback action to do when swiped to the right - * @param upCallback action to do when swiped to the up - * @param downCallback action to do when swiped to the down - */ - private void processSwipeInput(KeyMapping keyMapping, Runnable leftCallback, Runnable rightCallback, Runnable upCallback, Runnable downCallback) { - VRInputAction action = this.getInputAction(keyMapping); - - if (action.isEnabled() && action.getLastOrigin() != k_ulInvalidInputValueHandle) { - ControllerType controller = this.findActiveBindingControllerType(keyMapping); - - if (controller != null) { - // if that keyMapping is not tracked yet, create a new sampler - if (!this.trackpadSwipeSamplers.containsKey(keyMapping.getName())) { - this.trackpadSwipeSamplers.put(keyMapping.getName(), new TrackpadSwipeSampler()); - } - - TrackpadSwipeSampler trackpadswipesampler = this.trackpadSwipeSamplers.get(keyMapping.getName()); - trackpadswipesampler.update(controller, action.getAxis2D(false)); - - if (trackpadswipesampler.isSwipedUp() && upCallback != null) { - this.triggerHapticPulse(controller, 0.001F, 400.0F, 0.5F); - upCallback.run(); - } - - if (trackpadswipesampler.isSwipedDown() && downCallback != null) { - this.triggerHapticPulse(controller, 0.001F, 400.0F, 0.5F); - downCallback.run(); - } - - if (trackpadswipesampler.isSwipedLeft() && leftCallback != null) { - this.triggerHapticPulse(controller, 0.001F, 400.0F, 0.5F); - leftCallback.run(); - } - - if (trackpadswipesampler.isSwipedRight() && rightCallback != null) { - this.triggerHapticPulse(controller, 0.001F, 400.0F, 0.5F); - rightCallback.run(); - } - - - } - } - } - /** * fetches all available VREvents from OpenVR */ @@ -1374,6 +1241,10 @@ private void updatePose() { this.hmdPose.m31(1.62F); } + // ret the complete room eye transforms + this.hmdPose.mul(this.hmdPoseLeftEye, this.hmdPoseLeftEye); + this.hmdPose.mul(this.hmdPoseRightEye, this.hmdPoseRightEye); + // Gotta do this here so we can get the poses if (this.inputInitialized) { this.mc.getProfiler().push("updateActionState"); @@ -1445,11 +1316,8 @@ private long getInputSourceHandle(String path) { } } - /** - * @param inputValueHandle inputHandle to check - * @return what controller the inputHandle is on, {@code null} if the handle or device is invalid - */ - protected ControllerType getOriginControllerType(long inputValueHandle) { + @Override + public ControllerType getOriginControllerType(long inputValueHandle) { if (inputValueHandle != k_ulInvalidInputValueHandle) { this.readOriginInfo(inputValueHandle); diff --git a/common/src/main/java/org/vivecraft/client_vr/provider/openvr_lwjgl/OpenVRStereoRenderer.java b/common/src/main/java/org/vivecraft/client_vr/provider/openvr_lwjgl/OpenVRStereoRenderer.java index 165a6abf0..6db87596b 100644 --- a/common/src/main/java/org/vivecraft/client_vr/provider/openvr_lwjgl/OpenVRStereoRenderer.java +++ b/common/src/main/java/org/vivecraft/client_vr/provider/openvr_lwjgl/OpenVRStereoRenderer.java @@ -1,5 +1,6 @@ package org.vivecraft.client_vr.provider.openvr_lwjgl; +import com.mojang.blaze3d.pipeline.RenderTarget; import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.platform.TextureUtil; import com.mojang.blaze3d.systems.RenderSystem; @@ -12,9 +13,10 @@ import org.lwjgl.openvr.VR; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryUtil; -import org.vivecraft.client_vr.provider.MCVR; +import org.vivecraft.client_vr.VRTextureTarget; import org.vivecraft.client_vr.provider.VRRenderer; import org.vivecraft.client_vr.render.RenderConfigException; +import org.vivecraft.client_vr.render.RenderPass; import org.vivecraft.client_vr.render.helpers.RenderHelper; import org.vivecraft.client_vr.settings.VRSettings; @@ -25,14 +27,17 @@ public class OpenVRStereoRenderer extends VRRenderer { private final HiddenAreaMesh[] hiddenMeshes = new HiddenAreaMesh[2]; private final MCOpenVR openvr; + protected int LeftEyeTextureId = -1; + protected int RightEyeTextureId = -1; + public RenderTarget framebufferEye0; + public RenderTarget framebufferEye1; - public OpenVRStereoRenderer(MCVR vr) { + public OpenVRStereoRenderer(MCOpenVR vr) { super(vr); - this.openvr = (MCOpenVR) vr; + this.openvr = vr; - // allocate meshes, they are freed in destroy() - this.hiddenMeshes[0] = HiddenAreaMesh.calloc(); - this.hiddenMeshes[1] = HiddenAreaMesh.calloc(); + hiddenMeshes[0] = HiddenAreaMesh.calloc(); + hiddenMeshes[1] = HiddenAreaMesh.calloc(); } @Override @@ -88,7 +93,7 @@ protected Matrix4f getProjectionMatrix(int eyeType, float nearClip, float farCli } @Override - public void createRenderTexture(int width, int height) { + public void createRenderTexture(int width, int height) throws RenderConfigException { int boundTextureId = GlStateManager._getInteger(GL11C.GL_TEXTURE_BINDING_2D); // generate left eye texture this.LeftEyeTextureId = GlStateManager._genTexture(); @@ -110,6 +115,28 @@ public void createRenderTexture(int width, int height) { this.openvr.texType1.eColorSpace(VR.EColorSpace_ColorSpace_Gamma); this.openvr.texType1.eType(VR.ETextureType_TextureType_OpenGL); + if (this.LeftEyeTextureId == -1) { + throw new RenderConfigException( + Component.translatable("vivecraft.messages.renderiniterror", this.getName()), + Component.literal(this.getLastError())); + } + + VRSettings.LOGGER.info("Vivecraft: VR Provider supplied render texture IDs: {}, {}", this.LeftEyeTextureId, this.RightEyeTextureId); + VRSettings.LOGGER.info("Vivecraft: VR Provider supplied texture resolution: {} x {}", width, height); + + RenderHelper.checkGLError("Render Texture setup"); + + if (this.framebufferEye0 == null) { + this.framebufferEye0 = new VRTextureTarget("L Eye", width, height, false, this.LeftEyeTextureId, false, true, false); + VRSettings.LOGGER.info("Vivecraft: {}", this.framebufferEye0); + RenderHelper.checkGLError("Left Eye framebuffer setup"); + } + + if (this.framebufferEye1 == null) { + this.framebufferEye1 = new VRTextureTarget("R Eye", width, height, false, this.RightEyeTextureId, false, true, false); + VRSettings.LOGGER.info("Vivecraft: {}", this.framebufferEye1); + RenderHelper.checkGLError("Right Eye framebuffer setup"); + } RenderSystem.bindTexture(boundTextureId); this.lastError = RenderHelper.checkGLError("create VR textures"); } @@ -156,6 +183,23 @@ public boolean providesStencilMask() { } @Override + public RenderTarget getLeftEyeTarget() { + return framebufferEye0; + } + + @Override + public RenderTarget getRightEyeTarget() { + return framebufferEye1; + } + + public float[] getStencilMask(RenderPass eye) { + if (this.hiddenMeshVertices != null && (eye == RenderPass.LEFT || eye == RenderPass.RIGHT)) { + return eye == RenderPass.LEFT ? this.hiddenMeshVertices[0] : this.hiddenMeshVertices[1]; + } else { + return null; + } + } + public String getName() { return "OpenVR"; } @@ -163,6 +207,15 @@ public String getName() { @Override protected void destroyBuffers() { super.destroyBuffers(); + if (this.framebufferEye0 != null) { + this.framebufferEye0.destroyBuffers(); + this.framebufferEye0 = null; + } + + if (this.framebufferEye1 != null) { + this.framebufferEye1.destroyBuffers(); + this.framebufferEye1 = null; + } if (this.LeftEyeTextureId > -1) { TextureUtil.releaseTextureId(this.LeftEyeTextureId); this.LeftEyeTextureId = -1; diff --git a/common/src/main/java/org/vivecraft/client_vr/provider/openxr/DeviceCompat.java b/common/src/main/java/org/vivecraft/client_vr/provider/openxr/DeviceCompat.java new file mode 100644 index 000000000..c3cff1fc4 --- /dev/null +++ b/common/src/main/java/org/vivecraft/client_vr/provider/openxr/DeviceCompat.java @@ -0,0 +1,160 @@ +package org.vivecraft.client_vr.provider.openxr; + +import com.mojang.blaze3d.platform.Window; +import com.sun.jna.Platform; +import net.minecraft.client.Minecraft; +import org.lwjgl.PointerBuffer; +import org.lwjgl.glfw.GLFWNativeGLX; +import org.lwjgl.glfw.GLFWNativeWGL; +import org.lwjgl.glfw.GLFWNativeWin32; +import org.lwjgl.glfw.GLFWNativeX11; +import org.lwjgl.openxr.*; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.Struct; +import org.lwjgl.system.linux.X11; +import org.lwjgl.system.windows.User32; +import org.vivecraft.client_vr.settings.VRSettings; +import org.vivecraft.util.VLoader; + +import java.util.Objects; + +import static org.lwjgl.opengl.GLX13.*; +import static org.lwjgl.system.MemoryStack.stackInts; +import static org.lwjgl.system.MemoryUtil.NULL; + +//TODO: VulkanMod Support +public interface DeviceCompat { + long getPlatformInfo(MemoryStack stack); + void initOpenXRLoader(MemoryStack stack); + String getGraphicsExtension(); + XrSwapchainImageOpenGLKHR.Buffer createImageBuffers(int imageCount, MemoryStack stack); + Struct checkGraphics(MemoryStack stack, XrInstance instance, long systemID); + static DeviceCompat detectDevice() { + return System.getProperty("os.version").contains("Android") ? new Mobile() : new Desktop(); + } + + class Desktop implements DeviceCompat { + @Override + public long getPlatformInfo(MemoryStack stack) { + return NULL; + } + + @Override + public void initOpenXRLoader(MemoryStack stack) { + VRSettings.LOGGER.info("Platform: {}", System.getProperty("os.version")); + } + + @Override + public String getGraphicsExtension() { + return KHROpenGLEnable.XR_KHR_OPENGL_ENABLE_EXTENSION_NAME; + } + + @Override + public XrSwapchainImageOpenGLKHR.Buffer createImageBuffers(int imageCount, MemoryStack stack) { + XrSwapchainImageOpenGLKHR.Buffer swapchainImageBuffer = XrSwapchainImageOpenGLKHR.calloc(imageCount, stack); + for (XrSwapchainImageOpenGLKHR image : swapchainImageBuffer) { + image.type(KHROpenGLEnable.XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR); + } + + return swapchainImageBuffer; + } + + @Override + public Struct checkGraphics(MemoryStack stack, XrInstance instance, long systemID) { + XrGraphicsRequirementsOpenGLKHR graphicsRequirements = XrGraphicsRequirementsOpenGLKHR.calloc(stack).type(KHROpenGLEnable.XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR); + KHROpenGLEnable.xrGetOpenGLGraphicsRequirementsKHR(instance, systemID, graphicsRequirements); + //Bind the OpenGL context to the OpenXR instance and create the session + Window window = Minecraft.getInstance().getWindow(); + long windowHandle = window.getWindow(); + if (Platform.getOSType() == Platform.WINDOWS) { + return XrGraphicsBindingOpenGLWin32KHR.calloc(stack).set( + KHROpenGLEnable.XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR, + NULL, + User32.GetDC(GLFWNativeWin32.glfwGetWin32Window(windowHandle)), + GLFWNativeWGL.glfwGetWGLContext(windowHandle) + ); + } else if (Platform.getOSType() == Platform.LINUX) { + long xDisplay = GLFWNativeX11.glfwGetX11Display(); + + long glXContext = GLFWNativeGLX.glfwGetGLXContext(windowHandle); + long glXWindowHandle = GLFWNativeGLX.glfwGetGLXWindow(windowHandle); + + int fbXID = glXQueryDrawable(xDisplay, glXWindowHandle, GLX_FBCONFIG_ID); + PointerBuffer fbConfigBuf = glXChooseFBConfig(xDisplay, X11.XDefaultScreen(xDisplay), stackInts(GLX_FBCONFIG_ID, fbXID, 0)); + if (fbConfigBuf == null) { + throw new IllegalStateException("Your framebuffer config was null, make a github issue"); + } + long fbConfig = fbConfigBuf.get(); + + return XrGraphicsBindingOpenGLXlibKHR.calloc(stack).set( + KHROpenGLEnable.XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR, + NULL, + xDisplay, + (int) Objects.requireNonNull(glXGetVisualFromFBConfig(xDisplay, fbConfig)).visualid(), + fbConfig, + glXWindowHandle, + glXContext + ); + } else { + throw new IllegalStateException("Macos not supported"); + } + } + } + + class Mobile implements DeviceCompat { + @Override + public long getPlatformInfo(MemoryStack stack) { + return XrInstanceCreateInfoAndroidKHR.calloc(stack).set( + KHRAndroidCreateInstance.XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR, + NULL, + VLoader.getDalvikVM(), + VLoader.getDalvikActivity() + ).address(); + } + + @Override + public void initOpenXRLoader(MemoryStack stack) { + VRSettings.LOGGER.info("Platform: {}", System.getProperty("os.version")); + VLoader.setupAndroid(); + XrLoaderInitInfoAndroidKHR initInfo = XrLoaderInitInfoAndroidKHR.calloc(stack).set( + KHRLoaderInitAndroid.XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR, + NULL, + VLoader.getDalvikVM(), + VLoader.getDalvikActivity() + ); + + KHRLoaderInit.xrInitializeLoaderKHR(XrLoaderInitInfoBaseHeaderKHR.create(initInfo.address())); + } + + @Override + public String getGraphicsExtension() { + return KHROpenGLESEnable.XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME; + } + + @Override + public XrSwapchainImageOpenGLKHR.Buffer createImageBuffers(int imageCount, MemoryStack stack) { + XrSwapchainImageOpenGLKHR.Buffer swapchainImageBuffer = XrSwapchainImageOpenGLKHR.calloc(imageCount, stack); + for (XrSwapchainImageOpenGLKHR image : swapchainImageBuffer) { + image.type(KHROpenGLESEnable.XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR); + } + + return swapchainImageBuffer; + } + + @Override + public Struct checkGraphics(MemoryStack stack, XrInstance instance, long systemID) { + XrGraphicsRequirementsOpenGLESKHR graphicsRequirements = XrGraphicsRequirementsOpenGLESKHR.calloc(stack).type(KHROpenGLESEnable.XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR); + KHROpenGLESEnable.xrGetOpenGLESGraphicsRequirementsKHR(instance, systemID, graphicsRequirements); + XrGraphicsBindingOpenGLESAndroidKHR graphicsBinding = XrGraphicsBindingOpenGLESAndroidKHR.calloc(stack); + graphicsBinding.set( + KHROpenGLESEnable.XR_TYPE_GRAPHICS_BINDING_OPENGL_ES_ANDROID_KHR, + NULL, + VLoader.getEGLDisplay(), + VLoader.getEGLConfig(), + VLoader.getEGLContext() + ); + + return graphicsBinding; + } + } +} diff --git a/common/src/main/java/org/vivecraft/client_vr/provider/openxr/MCOpenXR.java b/common/src/main/java/org/vivecraft/client_vr/provider/openxr/MCOpenXR.java new file mode 100644 index 000000000..7df540539 --- /dev/null +++ b/common/src/main/java/org/vivecraft/client_vr/provider/openxr/MCOpenXR.java @@ -0,0 +1,1240 @@ +package org.vivecraft.client_vr.provider.openxr; + +import net.minecraft.client.KeyMapping; +import net.minecraft.client.Minecraft; +import org.apache.commons.lang3.tuple.Pair; +import org.joml.Matrix4f; +import org.joml.Vector2f; +import org.joml.Vector3f; +import org.lwjgl.PointerBuffer; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL21; +import org.lwjgl.opengl.GL30; +import org.lwjgl.opengl.GL31; +import org.lwjgl.openxr.*; +import org.lwjgl.system.MemoryStack; +import org.vivecraft.client.VivecraftVRMod; +import org.vivecraft.client_vr.ClientDataHolderVR; +import org.vivecraft.client_vr.gameplay.screenhandlers.KeyboardHandler; +import org.vivecraft.client_vr.gameplay.screenhandlers.RadialHandler; +import org.vivecraft.client_vr.provider.ControllerType; +import org.vivecraft.client_vr.provider.MCVR; +import org.vivecraft.client_vr.provider.VRRenderer; +import org.vivecraft.client_vr.provider.control.VRInputAction; +import org.vivecraft.client_vr.provider.control.VRInputActionSet; +import org.vivecraft.client_vr.render.RenderPass; +import org.vivecraft.client_vr.settings.VRSettings; + +import javax.annotation.Nullable; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.LongBuffer; +import java.util.*; + +import static org.lwjgl.system.MemoryStack.*; +import static org.lwjgl.system.MemoryUtil.*; + +public class MCOpenXR extends MCVR { + + private static MCOpenXR OME; + public XrInstance instance; + public XrSession session; + public XrSpace xrAppSpace; + public XrSpace xrViewSpace; + public XrSwapchain swapchain; + public final XrEventDataBuffer eventDataBuffer = XrEventDataBuffer.calloc(); + public long time; + private boolean tried; + private long systemID; + public XrView.Buffer viewBuffer; + public int width; + public int height; + //TODO either move to MCVR, Or make special for OpenXR holding the instance itself. + private final Map actionSetHandles = new EnumMap<>(VRInputActionSet.class); + //TODO Move to MCVR + private XrActiveActionSet.Buffer activeActionSetsBuffer; + private boolean isActive; + private final HashMap paths = new HashMap<>(); + private final long[] grip = new long[2]; + private final long[] aim = new long[2]; + private final XrSpace[] gripSpace = new XrSpace[2]; + private final XrSpace[] aimSpace = new XrSpace[2]; + public static final XrPosef POSE_IDENTITY = XrPosef.calloc().set( + XrQuaternionf.calloc().set(0, 0, 0, 1), + XrVector3f.calloc() + ); + public boolean shouldRender = true; + public long[] haptics = new long[2]; + public String systemName; + + + public MCOpenXR(Minecraft mc, ClientDataHolderVR dh) { + super(mc, dh, VivecraftVRMod.INSTANCE); + OME = this; + this.hapticScheduler = new OpenXRHapticSchedular(); + } + + @Override + public String getName() { + return "OpenXR"; + } + + @Override + public void destroy() { + int error; + //Not sure if we need the action sets one here, as we are shutting down + for (Long inputActionSet : actionSetHandles.values()){ + error = XR10.xrDestroyActionSet(new XrActionSet(inputActionSet, instance)); + logError(error, "xrDestroyActionSet", ""); + } + if (swapchain != null) { + error = XR10.xrDestroySwapchain(swapchain); + logError(error, "xrDestroySwapchain", ""); + } + if (viewBuffer != null) { + viewBuffer.close(); + } + if (xrAppSpace != null) { + error = XR10.xrDestroySpace(xrAppSpace); + logError(error, "xrDestroySpace", "xrAppSpace"); + } + if (xrViewSpace != null) { + error = XR10.xrDestroySpace(xrViewSpace); + logError(error, "xrDestroySpace", "xrViewSpace"); + } + if (session != null){ + error = XR10.xrDestroySession(session); + logError(error, "xrDestroySession", ""); + } + if (instance != null){ + error = XR10.xrDestroyInstance(instance); + logError(error, "xrDestroyInstance", ""); + } + eventDataBuffer.close(); + } + + @Override + protected ControllerType findActiveBindingControllerType(KeyMapping binding) { + if (!this.inputInitialized) { + return null; + } else { + long path = this.getInputAction(binding).getLastOrigin(); + try (MemoryStack stack = MemoryStack.stackPush()) { + IntBuffer buf = stack.callocInt(1); + int error = XR10.xrPathToString(instance, path, buf, null); + logError(error, "xrPathToString", "get string length for", binding.getName()); + + int size = buf.get(); + if (size <= 0) { + return null; + } + + buf = stack.callocInt(size); + ByteBuffer byteBuffer = stack.calloc(size); + error = XR10.xrPathToString(instance, path, buf, byteBuffer); + logError(error, "xrPathToString", "get string for", binding.getName()); + byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.get(bytes); + String name = new String(bytes); + if (name.contains("right")) { + return ControllerType.RIGHT; + } + return ControllerType.LEFT; + } + } + } + + @Override + public void poll(long var1) { + if (this.initialized) { + this.mc.getProfiler().push("events"); + this.pollVREvents(); + + if (!this.dh.vrSettings.seated) { + this.mc.getProfiler().popPush("controllers"); + this.mc.getProfiler().push("gui"); + + if (this.mc.screen == null && this.dh.vrSettings.vrTouchHotbar) { + + if (this.dh.vrSettings.vrHudLockMode != VRSettings.HUDLock.HEAD && this.hudPopup) { + this.processHotbar(); + } + } + + this.mc.getProfiler().pop(); + } + this.mc.getProfiler().popPush("updatePose/Vsync"); + this.updatePose(); + this.mc.getProfiler().popPush("processInputs"); + this.processInputs(); + this.mc.getProfiler().popPush("hmdSampling"); + this.hmdSampling(); + this.mc.getProfiler().pop(); + } + } + + private void updatePose() { + if (mc == null) { + return; + } + + try (MemoryStack stack = MemoryStack.stackPush()) { + XrFrameState frameState = XrFrameState.calloc(stack).type(XR10.XR_TYPE_FRAME_STATE); + + int error = XR10.xrWaitFrame( + session, + XrFrameWaitInfo.calloc(stack).type(XR10.XR_TYPE_FRAME_WAIT_INFO), + frameState); + logError(error, "xrWaitFrame", ""); + + time = frameState.predictedDisplayTime(); + this.shouldRender = frameState.shouldRender(); + + error = XR10.xrBeginFrame( + session, + XrFrameBeginInfo.calloc(stack).type(XR10.XR_TYPE_FRAME_BEGIN_INFO)); + logError(error, "xrBeginFrame", ""); + + + XrViewState viewState = XrViewState.calloc(stack).type(XR10.XR_TYPE_VIEW_STATE); + IntBuffer intBuf = stack.callocInt(1); + + XrViewLocateInfo viewLocateInfo = XrViewLocateInfo.calloc(stack); + viewLocateInfo.set(XR10.XR_TYPE_VIEW_LOCATE_INFO, + 0, + XR10.XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, + frameState.predictedDisplayTime(), + xrAppSpace + ); + + error = XR10.xrLocateViews(session, viewLocateInfo, viewState, intBuf, viewBuffer); + logError(error, "xrLocateViews", ""); + + XrSpaceLocation space_location = XrSpaceLocation.calloc(stack).type(XR10.XR_TYPE_SPACE_LOCATION); + + //HMD pose + error = XR10.xrLocateSpace(xrViewSpace, xrAppSpace, time, space_location); + logError(error, "xrLocateSpace", "xrViewSpace"); + if (error >= 0) { + OpenXRUtil.openXRPoseToMarix(space_location.pose(), this.hmdPose); + headIsTracking = true; + } else { + headIsTracking = false; + this.hmdPose.identity(); + this.hmdPose.m31(1.6F); + } + + //Eye positions + OpenXRUtil.openXRPoseToMarix(viewBuffer.get(0).pose(), this.hmdPoseLeftEye); + OpenXRUtil.openXRPoseToMarix(viewBuffer.get(1).pose(), this.hmdPoseRightEye); + + if (this.inputInitialized) { + this.mc.getProfiler().push("updateActionState"); + + if (this.updateActiveActionSets()) { + XrActionsSyncInfo syncInfo = XrActionsSyncInfo.calloc(stack) + .type(XR10.XR_TYPE_ACTIONS_SYNC_INFO) + .activeActionSets(activeActionSetsBuffer); + error = XR10.xrSyncActions(session, syncInfo); + logError(error, "xrSyncActions", ""); + } + + this.inputActions.values().forEach(this::readNewData); + + //TODO Not needed it seems? Poses come from the action space + XrActionSet actionSet = new XrActionSet(this.actionSetHandles.get(VRInputActionSet.GLOBAL), instance); + this.readPoseData(this.grip[RIGHT_CONTROLLER], actionSet); + this.readPoseData(this.grip[LEFT_CONTROLLER], actionSet); + this.readPoseData(this.aim[RIGHT_CONTROLLER], actionSet); + this.readPoseData(this.aim[LEFT_CONTROLLER], actionSet); + + this.mc.getProfiler().pop(); + + //reverse + if (this.dh.vrSettings.reverseHands) { + XrSpace temp = gripSpace[RIGHT_CONTROLLER]; + gripSpace[RIGHT_CONTROLLER] = gripSpace[LEFT_CONTROLLER]; + gripSpace[LEFT_CONTROLLER] = temp; + temp = aimSpace[RIGHT_CONTROLLER]; + aimSpace[RIGHT_CONTROLLER] = aimSpace[LEFT_CONTROLLER]; + aimSpace[LEFT_CONTROLLER] = temp; + } + + //Controller aim and grip poses + error = XR10.xrLocateSpace(gripSpace[RIGHT_CONTROLLER], xrAppSpace, time, space_location); + logError(error, "xrLocateSpace", "gripSpace[0]"); + if (error >= 0) { + OpenXRUtil.openXRPoseToMarix(space_location.pose().orientation(), this.handRotation[RIGHT_CONTROLLER]); + } + + error = XR10.xrLocateSpace(gripSpace[LEFT_CONTROLLER], xrAppSpace, time, space_location); + logError(error, "xrLocateSpace", "gripSpace[1]"); + if (error >= 0) { + OpenXRUtil.openXRPoseToMarix(space_location.pose().orientation(), this.handRotation[LEFT_CONTROLLER]); + } + + error = XR10.xrLocateSpace(aimSpace[RIGHT_CONTROLLER], xrAppSpace, time, space_location); + logError(error, "xrLocateSpace", "aimSpace[0]"); + if (error >= 0) { + OpenXRUtil.openXRPoseToMarix(space_location.pose(), this.controllerPose[RIGHT_CONTROLLER]); + OpenXRUtil.openXRPoseToMarix(space_location.pose().orientation(), this.controllerRotation[RIGHT_CONTROLLER]); + this.controllerTracking[RIGHT_CONTROLLER] = true; + } else { + this.controllerTracking[RIGHT_CONTROLLER] = false; + } + + error = XR10.xrLocateSpace(aimSpace[LEFT_CONTROLLER], xrAppSpace, time, space_location); + logError(error, "xrLocateSpace", "aimSpace[1]"); + if (error >= 0) { + OpenXRUtil.openXRPoseToMarix(space_location.pose(), this.controllerPose[LEFT_CONTROLLER]); + OpenXRUtil.openXRPoseToMarix(space_location.pose().orientation(), this.controllerRotation[LEFT_CONTROLLER]); + this.controllerTracking[LEFT_CONTROLLER] = true; + } else { + this.controllerTracking[LEFT_CONTROLLER] = false; + } + } + + this.updateAim(); + } + } + + public void readNewData(VRInputAction action) { + switch (action.type) { + case "boolean" -> { + if (action.isHanded()) { + for (ControllerType controllertype1 : ControllerType.values()) { + this.readBoolean(action, controllertype1); + } + } else { + this.readBoolean(action, null); + } + } + + case "vector1" -> { + if (action.isHanded()) { + for (ControllerType controllertype : ControllerType.values()) { + this.readFloat(action, controllertype); + } + } else { + this.readFloat(action, null); + } + } + + case "vector2" -> { + if (action.isHanded()) { + for (ControllerType controllertype : ControllerType.values()) { + this.readVecData(action, controllertype); + } + } else { + this.readVecData(action, null); + } + } + + } + } + + private void readBoolean(VRInputAction action, ControllerType hand) { + int i = 0; + + if (hand != null) { + i = hand.ordinal(); + } + try (MemoryStack stack = MemoryStack.stackPush()){ + XrActionStateGetInfo info = XrActionStateGetInfo.calloc(stack); + info.type(XR10.XR_TYPE_ACTION_STATE_GET_INFO); + info.action(new XrAction(action.handle, new XrActionSet(actionSetHandles.get(action.actionSet), instance))); + XrActionStateBoolean state = XrActionStateBoolean.calloc(stack).type(XR10.XR_TYPE_ACTION_STATE_BOOLEAN); + int error = XR10.xrGetActionStateBoolean(session, info, state); + logError(error, "xrGetActionStateBoolean", action.name); + + action.digitalData[i].state = state.currentState(); + action.digitalData[i].isActive = state.isActive(); + action.digitalData[i].isChanged = state.changedSinceLastSync(); + action.digitalData[i].activeOrigin = getOrigins(action).get(0); + } + } + + private void readFloat(VRInputAction action, ControllerType hand) { + int i = 0; + + if (hand != null) { + i = hand.ordinal(); + } + try (MemoryStack stack = MemoryStack.stackPush()){ + XrActionStateGetInfo info = XrActionStateGetInfo.calloc(stack); + info.type(XR10.XR_TYPE_ACTION_STATE_GET_INFO); + info.action(new XrAction(action.handle, new XrActionSet(actionSetHandles.get(action.actionSet), instance))); + XrActionStateFloat state = XrActionStateFloat.calloc(stack).type(XR10.XR_TYPE_ACTION_STATE_FLOAT); + int error = XR10.xrGetActionStateFloat(session, info, state); + logError(error, "xrGetActionStateFloat", action.name); + + action.analogData[i].deltaX = action.analogData[i].x - state.currentState(); + action.analogData[i].x = state.currentState(); + action.analogData[i].activeOrigin = getOrigins(action).get(0); + action.analogData[i].isActive = state.isActive(); + action.analogData[i].isChanged = state.changedSinceLastSync(); + } + } + + private void readVecData(VRInputAction action, ControllerType hand) { + int i = 0; + + if (hand != null) { + i = hand.ordinal(); + } + try (MemoryStack stack = MemoryStack.stackPush()){ + XrActionStateGetInfo info = XrActionStateGetInfo.calloc(stack); + info.type(XR10.XR_TYPE_ACTION_STATE_GET_INFO); + info.action(new XrAction(action.handle, new XrActionSet(actionSetHandles.get(action.actionSet), instance))); + XrActionStateVector2f state = XrActionStateVector2f.calloc(stack).type(XR10.XR_TYPE_ACTION_STATE_VECTOR2F); + int error = XR10.xrGetActionStateVector2f(session, info, state); + logError(error, "xrGetActionStateVector2f", action.name); + + action.analogData[i].deltaX = action.analogData[i].x - state.currentState().x(); + action.analogData[i].deltaY = action.analogData[i].y - state.currentState().y(); + action.analogData[i].x = state.currentState().x(); + action.analogData[i].y = state.currentState().y(); + action.analogData[i].activeOrigin = getOrigins(action).get(0); + action.analogData[i].isActive = state.isActive(); + action.analogData[i].isChanged = state.changedSinceLastSync(); + } + } + + private void readPoseData(Long action, XrActionSet set) { + try (MemoryStack stack = MemoryStack.stackPush()){ + XrActionStateGetInfo info = XrActionStateGetInfo.calloc(stack); + info.type(XR10.XR_TYPE_ACTION_STATE_GET_INFO); + info.action(new XrAction(action, set)); + XrActionStatePose state = XrActionStatePose.calloc(stack).type(XR10.XR_TYPE_ACTION_STATE_POSE); + int error = XR10.xrGetActionStatePose(session, info, state); + logError(error, "xrGetActionStatePose", ""); + } + } + + private boolean updateActiveActionSets() { + ArrayList arraylist = new ArrayList<>(); + arraylist.add(VRInputActionSet.GLOBAL); + + // we are always modded + arraylist.add(VRInputActionSet.MOD); + + arraylist.add(VRInputActionSet.MIXED_REALITY); + arraylist.add(VRInputActionSet.TECHNICAL); + + if (this.mc.screen == null) { + arraylist.add(VRInputActionSet.INGAME); + arraylist.add(VRInputActionSet.CONTEXTUAL); + } else { + arraylist.add(VRInputActionSet.GUI); + if (ClientDataHolderVR.getInstance().vrSettings.ingameBindingsInGui) { + arraylist.add(VRInputActionSet.INGAME); + } + } + + if (KeyboardHandler.SHOWING || RadialHandler.isShowing()) { + arraylist.add(VRInputActionSet.KEYBOARD); + } + + if (this.activeActionSetsBuffer == null) { + activeActionSetsBuffer = XrActiveActionSet.calloc(arraylist.size()); + } else if (activeActionSetsBuffer.capacity() != arraylist.size()) { + activeActionSetsBuffer.close(); + activeActionSetsBuffer = XrActiveActionSet.calloc(arraylist.size()); + } + + for (int i = 0; i < arraylist.size(); ++i) { + VRInputActionSet vrinputactionset = arraylist.get(i); + activeActionSetsBuffer.get(i).set(new XrActionSet(this.getActionSetHandle(vrinputactionset), instance), NULL); + } + + return !arraylist.isEmpty(); + } + + long getActionSetHandle(VRInputActionSet actionSet) { + return this.actionSetHandles.get(actionSet); + } + + private void pollVREvents() { + while (true) { + eventDataBuffer.clear(); + eventDataBuffer.type(XR10.XR_TYPE_EVENT_DATA_BUFFER); + int error = XR10.xrPollEvent(instance, eventDataBuffer); + logError(error, "xrPollEvent", ""); + if (error != XR10.XR_SUCCESS) { + break; + } + XrEventDataBaseHeader event = XrEventDataBaseHeader.create(eventDataBuffer.address()); + + switch (event.type()) { + case XR10.XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING -> { + XrEventDataInstanceLossPending instanceLossPending = XrEventDataInstanceLossPending.create(event.address()); + } + case XR10.XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED -> { + this.sessionChanged(XrEventDataSessionStateChanged.create(event.address())); + } + case XR10.XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED -> { + } + case XR10.XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING -> { + } + default -> { + } + } + } + } + + private void sessionChanged(XrEventDataSessionStateChanged xrEventDataSessionStateChanged) { + int state = xrEventDataSessionStateChanged.state(); + + switch (state) { + case XR10.XR_SESSION_STATE_READY: { + try (MemoryStack stack = MemoryStack.stackPush()){ + XrSessionBeginInfo sessionBeginInfo = XrSessionBeginInfo.calloc(stack); + sessionBeginInfo.type(XR10.XR_TYPE_SESSION_BEGIN_INFO); + sessionBeginInfo.next(NULL); + sessionBeginInfo.primaryViewConfigurationType(XR10.XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO); + + int error = XR10.xrBeginSession(session, sessionBeginInfo); + logError(error, "xrBeginSession", "XR_SESSION_STATE_READY"); + } + this.isActive = true; + break; + } + case XR10.XR_SESSION_STATE_STOPPING: { + this.isActive = false; + int error = XR10.xrEndSession(session); + logError(error, "xrEndSession", "XR_SESSION_STATE_STOPPING"); + } + case XR10.XR_SESSION_STATE_VISIBLE, XR10.XR_SESSION_STATE_FOCUSED: { + this.isActive = true; + break; + } + case XR10.XR_SESSION_STATE_EXITING, XR10.XR_SESSION_STATE_IDLE, XR10.XR_SESSION_STATE_SYNCHRONIZED: { + this.isActive = false; + break; + } + case XR10.XR_SESSION_STATE_LOSS_PENDING: { + break; + } + default: + break; + } + } + + @Override + public Vector2f getPlayAreaSize() { + try (MemoryStack stack = MemoryStack.stackPush()) { + XrExtent2Df vec = XrExtent2Df.calloc(stack); + int error = XR10.xrGetReferenceSpaceBoundsRect(session, XR10.XR_REFERENCE_SPACE_TYPE_STAGE, vec); + logError(error, "xrGetReferenceSpaceBoundsRect", ""); + return new Vector2f(vec.width(), vec.height()); + } + } + + @Override + public boolean init() { + if (this.initialized) { + return true; + } else if (this.tried) { + return this.initialized; + } else { + tried = true; + this.mc = Minecraft.getInstance(); + try { + this.initializeOpenXRInstance(); + this.initializeOpenXRSession(); + this.initializeOpenXRSpace(); + this.initializeOpenXRSwapChain(); + this.initInputAndApplication(); + } catch (Exception e) { + e.printStackTrace(); + this.initSuccess = false; + this.initStatus = e.getLocalizedMessage(); + return false; + } + + //TODO Seated when no controllers + + System.out.println("OpenXR initialized & VR connected."); + this.deviceVelocity = new Vector3f[64]; + + for (int i = 0; i < this.poseMatrices.length; ++i) { + this.poseMatrices[i] = new Matrix4f(); + this.deviceVelocity[i] = new Vector3f(); + } + + this.initialized = true; + return true; + } + } + + private void initializeOpenXRInstance() { + try (MemoryStack stack = MemoryStack.stackPush()) { + device.initOpenXRLoader(stack); + + //Check extensions + IntBuffer numExtensions = stack.callocInt(1); + int error = XR10.xrEnumerateInstanceExtensionProperties((ByteBuffer) null, numExtensions, null); + logError(error, "xrEnumerateInstanceExtensionProperties", "get count"); + + XrExtensionProperties.Buffer properties = new XrExtensionProperties.Buffer( + bufferStack(numExtensions.get(0), XrExtensionProperties.SIZEOF, XR10.XR_TYPE_EXTENSION_PROPERTIES) + ); + + //Load extensions + error = XR10.xrEnumerateInstanceExtensionProperties((ByteBuffer) null, numExtensions, properties); + logError(error, "xrEnumerateInstanceExtensionProperties", "get extensions"); + + //get needed extensions + String graphicsExtension = device.getGraphicsExtension(); + boolean missingGraphics = true; + PointerBuffer extensions = stack.callocPointer(3); + while (properties.hasRemaining()) { + XrExtensionProperties prop = properties.get(); + String extensionName = prop.extensionNameString(); + if (extensionName.equals(graphicsExtension)) { + missingGraphics = false; + extensions.put(memAddress(stackUTF8(graphicsExtension))); + } + if (extensionName.equals(EXTHPMixedRealityController.XR_EXT_HP_MIXED_REALITY_CONTROLLER_EXTENSION_NAME)) { + extensions.put(memAddress(stackUTF8(EXTHPMixedRealityController.XR_EXT_HP_MIXED_REALITY_CONTROLLER_EXTENSION_NAME))); + } + if (extensionName.equals(HTCViveCosmosControllerInteraction.XR_HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME)) { + extensions.put(memAddress(stackUTF8(HTCViveCosmosControllerInteraction.XR_HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME))); + } + } + + if (missingGraphics) { + throw new RuntimeException("OpenXR runtime is missing a supported graphics extension."); + } + + //Create APP info + XrApplicationInfo applicationInfo = XrApplicationInfo.calloc(stack); + applicationInfo.apiVersion(XR10.XR_MAKE_VERSION(1, 0, 40)); + applicationInfo.applicationName(stack.UTF8("Vivecraft")); + applicationInfo.applicationVersion(1); + + //Create instance info + XrInstanceCreateInfo createInfo = XrInstanceCreateInfo.calloc(stack); + createInfo.type(XR10.XR_TYPE_INSTANCE_CREATE_INFO); + createInfo.next(device.getPlatformInfo(stack)); + createInfo.createFlags(0); + createInfo.applicationInfo(applicationInfo); + createInfo.enabledApiLayerNames(null); + createInfo.enabledExtensionNames(extensions.flip()); + + //Create XR instance + PointerBuffer instancePtr = stack.callocPointer(1); + int xrResult = XR10.xrCreateInstance(createInfo, instancePtr); + if (xrResult == XR10.XR_ERROR_RUNTIME_FAILURE) { + throw new RuntimeException("Failed to create xrInstance, are you sure your headset is plugged in?"); + } else if (xrResult == XR10.XR_ERROR_INSTANCE_LOST) { + throw new RuntimeException("Failed to create xrInstance due to runtime updating"); + } else if (xrResult < 0) { + throw new RuntimeException("XR method returned " + xrResult); + } + instance = new XrInstance(instancePtr.get(0), createInfo); + + this.poseMatrices = new Matrix4f[64]; + + for (int i = 0; i < this.poseMatrices.length; ++i) { + this.poseMatrices[i] = new Matrix4f(); + } + + this.initSuccess = true; + } + } + + public static MCOpenXR get() { + return OME; + } + + private void initializeOpenXRSession() { + try (MemoryStack stack = MemoryStack.stackPush()) { + //Create system + XrSystemGetInfo system = XrSystemGetInfo.calloc(stack); + system.type(XR10.XR_TYPE_SYSTEM_GET_INFO); + system.next(NULL); + system.formFactor(XR10.XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY); + + LongBuffer longBuffer = stack.callocLong(1); + int error = XR10.xrGetSystem(instance, system, longBuffer); + logError(error, "xrGetSystem", ""); + this.systemID = longBuffer.get(0); + + if (systemID == 0) { + throw new RuntimeException("No compatible headset detected"); + } + + XrSystemProperties systemProperties = XrSystemProperties.calloc(stack).type(XR10.XR_TYPE_SYSTEM_PROPERTIES); + error = XR10.xrGetSystemProperties(instance, systemID, systemProperties); + MCOpenXR.get().logError(error, "xrGetSystemProperties", ""); + XrSystemTrackingProperties trackingProperties = systemProperties.trackingProperties(); + XrSystemGraphicsProperties graphicsProperties = systemProperties.graphicsProperties(); + + MCOpenXR.get().systemName = memUTF8(memAddress(systemProperties.systemName())); + int vendor = systemProperties.vendorId(); + boolean orientationTracking = trackingProperties.orientationTracking(); + boolean positionTracking = trackingProperties.positionTracking(); + int maxWidth = graphicsProperties.maxSwapchainImageWidth(); + int maxHeight = graphicsProperties.maxSwapchainImageHeight(); + int maxLayerCount = graphicsProperties.maxLayerCount(); + + VRSettings.LOGGER.info("Found device with id: {}", systemID); + VRSettings.LOGGER.info("Headset Name: {}, Vendor: {}", MCOpenXR.get().systemName, vendor); + VRSettings.LOGGER.info("Headset Orientation Tracking: {}, Position Tracking: {}", orientationTracking, positionTracking); + VRSettings.LOGGER.info("Headset Max Width: {}, Max Height: {}, Max Layer Count: {}", maxWidth, maxHeight, maxLayerCount); + + //Create session + XrSessionCreateInfo info = XrSessionCreateInfo.calloc(stack); + info.type(XR10.XR_TYPE_SESSION_CREATE_INFO); + info.next(device.checkGraphics(stack, instance, systemID).address()); + info.createFlags(0); + info.systemId(systemID); + + PointerBuffer sessionPtr = stack.callocPointer(1); + error = XR10.xrCreateSession(instance, info, sessionPtr); + logError(error, "xrCreateSession", ""); + + session = new XrSession(sessionPtr.get(0), instance); + + while (!this.isActive) { + System.out.println("waiting"); + pollVREvents(); + } + + } + } + + private void initializeOpenXRSpace() { + try (MemoryStack stack = MemoryStack.stackPush()){ + XrPosef identityPose = XrPosef.calloc(stack); + identityPose.set( + XrQuaternionf.calloc(stack).set(0, 0, 0, 1), + XrVector3f.calloc(stack) + ); + + XrReferenceSpaceCreateInfo referenceSpaceCreateInfo = XrReferenceSpaceCreateInfo.calloc(stack); + referenceSpaceCreateInfo.type(XR10.XR_TYPE_REFERENCE_SPACE_CREATE_INFO); + referenceSpaceCreateInfo.next(NULL); + referenceSpaceCreateInfo.referenceSpaceType(XR10.XR_REFERENCE_SPACE_TYPE_STAGE); + referenceSpaceCreateInfo.poseInReferenceSpace(identityPose); + + PointerBuffer pp = stack.callocPointer(1); + int error = XR10.xrCreateReferenceSpace(session, referenceSpaceCreateInfo, pp); + xrAppSpace = new XrSpace(pp.get(0), session); + logError(error, "xrCreateReferenceSpace", "XR_REFERENCE_SPACE_TYPE_STAGE"); + + referenceSpaceCreateInfo.referenceSpaceType(XR10.XR_REFERENCE_SPACE_TYPE_VIEW); + error = XR10.xrCreateReferenceSpace(session, referenceSpaceCreateInfo, pp); + logError(error, "xrCreateReferenceSpace", "XR_REFERENCE_SPACE_TYPE_VIEW"); + xrViewSpace = new XrSpace(pp.get(0), session); + } + } + + private void initializeOpenXRSwapChain() { + try (MemoryStack stack = stackPush()) { + //Check amount of views + IntBuffer intBuf = stack.callocInt(1); + int error = XR10.xrEnumerateViewConfigurationViews(instance, systemID, XR10.XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, intBuf, null); + logError(error, "xrEnumerateViewConfigurationViews", "get count"); + + //Get all views + ByteBuffer viewConfBuffer = bufferStack(intBuf.get(0), XrViewConfigurationView.SIZEOF, XR10.XR_TYPE_VIEW_CONFIGURATION_VIEW); + XrViewConfigurationView.Buffer views = new XrViewConfigurationView.Buffer(viewConfBuffer); + error = XR10.xrEnumerateViewConfigurationViews(instance, systemID, XR10.XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, intBuf, views); + logError(error, "xrEnumerateViewConfigurationViews", "get views"); + int viewCountNumber = intBuf.get(0); + + this.viewBuffer = new XrView.Buffer( + bufferHeap(viewCountNumber, XrView.SIZEOF, XR10.XR_TYPE_VIEW) + ); + //Check swapchain formats + error = XR10.xrEnumerateSwapchainFormats(session, intBuf, null); + logError(error, "xrEnumerateSwapchainFormats", "get count"); + + //Get swapchain formats + LongBuffer swapchainFormats = stack.callocLong(intBuf.get(0)); + error = XR10.xrEnumerateSwapchainFormats(session, intBuf, swapchainFormats); + logError(error, "xrEnumerateSwapchainFormats", "get formats"); + + long[] desiredSwapchainFormats = { + //SRGB formats + GL21.GL_SRGB8_ALPHA8, + GL21.GL_SRGB8, + //others + GL11.GL_RGB10_A2, + GL30.GL_RGBA16F, + GL30.GL_RGB16F, + + // The two below should only be used as a fallback, as they are linear color formats without enough bits for color + // depth, thus leading to banding. + GL11.GL_RGBA8, + GL31.GL_RGBA8_SNORM, + }; + + //Choose format + long chosenFormat = 0; + for (long glFormatIter : desiredSwapchainFormats) { + swapchainFormats.rewind(); + while (swapchainFormats.hasRemaining()) { + if (glFormatIter == swapchainFormats.get()) { + chosenFormat = glFormatIter; + break; + } + } + if (chosenFormat != 0) { + break; + } + } + + if (chosenFormat == 0) { + var formats = new ArrayList(); + swapchainFormats.rewind(); + while (swapchainFormats.hasRemaining()) { + formats.add(swapchainFormats.get()); + } + throw new RuntimeException("No compatible swapchain / framebuffer format available: " + formats); + } + + //Make swapchain + XrViewConfigurationView viewConfig = views.get(0); + XrSwapchainCreateInfo swapchainCreateInfo = XrSwapchainCreateInfo.calloc(stack); + swapchainCreateInfo.type(XR10.XR_TYPE_SWAPCHAIN_CREATE_INFO); + swapchainCreateInfo.next(NULL); + swapchainCreateInfo.createFlags(0); + swapchainCreateInfo.usageFlags(XR10.XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT); + swapchainCreateInfo.format(chosenFormat); + swapchainCreateInfo.sampleCount(1); + swapchainCreateInfo.width(viewConfig.recommendedImageRectWidth()); + swapchainCreateInfo.height(viewConfig.recommendedImageRectHeight()); + swapchainCreateInfo.faceCount(1); + swapchainCreateInfo.arraySize(2); + swapchainCreateInfo.mipCount(1); + + PointerBuffer handlePointer = stack.callocPointer(1); + error = XR10.xrCreateSwapchain(session, swapchainCreateInfo, handlePointer); + logError(error, "xrCreateSwapchain", "format: " + chosenFormat); + swapchain = new XrSwapchain(handlePointer.get(0), session); + this.width = swapchainCreateInfo.width(); + this.height = swapchainCreateInfo.height(); + } + } + + /** + * Creates an array of XrStructs with their types pre set to @param type + */ + static ByteBuffer bufferStack(int capacity, int sizeof, int type) { + ByteBuffer b = stackCalloc(capacity * sizeof); + + for (int i = 0; i < capacity; i++) { + b.position(i * sizeof); + b.putInt(type); + } + b.rewind(); + return b; + } + + private void initInputAndApplication() { + this.populateInputActions(); + + //this.generateActionManifest(); + //this.loadActionManifest(); + this.loadActionHandles(); + this.loadDefaultBindings(); + //this.installApplicationManifest(false); + this.inputInitialized = true; + + } + + @Override + public Matrix4f getControllerComponentTransform(int var1, String var2) { + return new Matrix4f(); + } + + @Override + public boolean hasCameraTracker() { + return false; + } + + @Override + public List getOrigins(VRInputAction var1) { + try (MemoryStack stack = MemoryStack.stackPush()){ + XrBoundSourcesForActionEnumerateInfo info = XrBoundSourcesForActionEnumerateInfo.calloc(stack); + info.type(XR10.XR_TYPE_BOUND_SOURCES_FOR_ACTION_ENUMERATE_INFO); + info.next(NULL); + info.action(new XrAction(var1.handle, new XrActionSet(actionSetHandles.get(var1.actionSet), instance))); + IntBuffer buf = stack.callocInt(1); + int error = XR10.xrEnumerateBoundSourcesForAction(session, info, buf, null); + logError(error, "xrEnumerateBoundSourcesForAction", var1.name); + + int size = buf.get(); + if (size <= 0) { + return List.of(0L); + } + + buf = stack.callocInt(size); + LongBuffer longbuf = stack.callocLong(size); + error = XR10.xrEnumerateBoundSourcesForAction(session, info, buf, longbuf); + logError(error, "xrEnumerateBoundSourcesForAction", var1.name); + long[] array; + if (longbuf.hasArray()) { //TODO really? + array = longbuf.array(); + } else { + longbuf.rewind(); + array = new long[longbuf.remaining()]; + int index = 0; + while (longbuf.hasRemaining()) { + array[index++] = longbuf.get(); + } + } + return Arrays.stream(array).boxed().toList(); + } + } + + @Override + public String getOriginName(long l) { + try (MemoryStack stack = MemoryStack.stackPush()){ + XrInputSourceLocalizedNameGetInfo info = XrInputSourceLocalizedNameGetInfo.calloc(stack); + info.type(XR10.XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO); + info.next(0); + info.sourcePath(l); + info.whichComponents(XR10.XR_INPUT_SOURCE_LOCALIZED_NAME_COMPONENT_BIT); + + IntBuffer buf = stack.callocInt(1); + int error = XR10.xrGetInputSourceLocalizedName(session, info, buf, null); + logError(error, "xrGetInputSourceLocalizedName", "get length"); + + int size = buf.get(); + if (size <= 0) { + return ""; + } + + buf = stack.callocInt(size); + ByteBuffer byteBuffer = stack.calloc(size); + error = XR10.xrGetInputSourceLocalizedName(session, info, buf, byteBuffer); + logError(error, "xrGetInputSourceLocalizedName", "get String"); + return new String(byteBuffer.array()); + } + } + + @Override + public VRRenderer createVRRenderer() { + return new OpenXRStereoRenderer(this); + } + + @Override + public boolean isActive() { + this.pollVREvents(); + return isActive; + } + + @Override + public ControllerType getOriginControllerType(long i) { + if (i == aim[RIGHT_CONTROLLER]) { + return ControllerType.RIGHT; + } + return ControllerType.LEFT; + } + + @Override + public float getIPD() { + return this.getEyePosition(RenderPass.RIGHT).x - this.getEyePosition(RenderPass.LEFT).x; + } + + @Override + public String getRuntimeName() { + return "OpenXR"; + } + + private static final String[] BOTH_HANDS = new String[] {"/user/hand/left", "/user/hand/right"}; + + //TODO Collect and register all actions + private void loadActionHandles() { + for (VRInputActionSet vrinputactionset : VRInputActionSet.values()) { + long actionSet = makeActionSet(instance, vrinputactionset.name, vrinputactionset.localizedName, 0); + this.actionSetHandles.put(vrinputactionset, actionSet); + } + + for (VRInputAction vrinputaction : this.inputActions.values()) { + long action = createAction(vrinputaction.name, vrinputaction.name, vrinputaction.type, new XrActionSet(this.actionSetHandles.get(vrinputaction.actionSet), instance), BOTH_HANDS); + vrinputaction.setHandle(action); + } + + setupControllers(); + + XrActionSet actionSet = new XrActionSet(this.actionSetHandles.get(VRInputActionSet.GLOBAL), instance); + this.haptics[RIGHT_CONTROLLER] = createAction("/actions/global/out/righthaptic", "/actions/global/out/righthaptic", "haptic", actionSet, BOTH_HANDS); + this.haptics[LEFT_CONTROLLER] = createAction("/actions/global/out/lefthaptic", "/actions/global/out/lefthaptic", "haptic", actionSet, BOTH_HANDS); + + } + + private void setupControllers() { + XrActionSet actionSet = new XrActionSet(this.actionSetHandles.get(VRInputActionSet.GLOBAL), instance); + this.grip[RIGHT_CONTROLLER] = createAction("/actions/global/in/righthand", "/actions/global/in/righthand", "pose", actionSet, BOTH_HANDS); + this.grip[LEFT_CONTROLLER] = createAction("/actions/global/in/lefthand", "/actions/global/in/lefthand", "pose", actionSet, BOTH_HANDS); + this.aim[RIGHT_CONTROLLER] = createAction("/actions/global/in/righthandaim", "/actions/global/in/righthandaim", "pose", actionSet, BOTH_HANDS); + this.aim[LEFT_CONTROLLER] = createAction("/actions/global/in/lefthandaim", "/actions/global/in/lefthandaim", "pose", actionSet, BOTH_HANDS); + } + + private void loadDefaultBindings() { + try (MemoryStack stack = MemoryStack.stackPush()) { + int error; + for (String headset: XRBindings.supportedHeadsets()) { + VRSettings.LOGGER.info("loading defaults for {}", headset); + Pair[] defaultBindings = XRBindings.getBinding(headset).toArray(new Pair[0]); + XrActionSuggestedBinding.Buffer bindings = XrActionSuggestedBinding.calloc(defaultBindings.length + 6, stack); //TODO different way of adding controller poses + + for (int i = 0; i < defaultBindings.length; i++) { + Pair pair = defaultBindings[i]; + VRInputAction binding = this.getInputActionByName(pair.getLeft()); + if (binding.handle == 0L) { + VRSettings.LOGGER.error("Handle for '{}'/'{}' is null", pair.getLeft(), pair.getRight()); + continue; + } + bindings.get(i).set( + new XrAction(binding.handle, new XrActionSet(actionSetHandles.get(binding.actionSet), instance)), + getPath(pair.getRight()) + ); + } + + //TODO make this also changeable? + XrActionSet actionSet = new XrActionSet(actionSetHandles.get(VRInputActionSet.GLOBAL), instance); + bindings.get(defaultBindings.length).set( + new XrAction(this.grip[RIGHT_CONTROLLER], actionSet), + getPath("/user/hand/right/input/grip/pose") + ); + bindings.get(defaultBindings.length + 1).set( + new XrAction(this.grip[LEFT_CONTROLLER], actionSet), + getPath("/user/hand/left/input/grip/pose") + ); + bindings.get(defaultBindings.length + 2).set( + new XrAction(this.aim[RIGHT_CONTROLLER], actionSet), + getPath("/user/hand/right/input/aim/pose") + ); + bindings.get(defaultBindings.length + 3).set( + new XrAction(this.aim[LEFT_CONTROLLER], actionSet), + getPath("/user/hand/left/input/aim/pose") + ); + + bindings.get(defaultBindings.length + 4).set( + new XrAction(this.haptics[RIGHT_CONTROLLER], actionSet), + getPath("/user/hand/right/output/haptic") + ); + + bindings.get(defaultBindings.length + 5).set( + new XrAction(this.haptics[LEFT_CONTROLLER], actionSet), + getPath("/user/hand/left/output/haptic") + ); + + XrInteractionProfileSuggestedBinding suggested_binds = XrInteractionProfileSuggestedBinding.calloc(stack); + suggested_binds.type(XR10.XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING); + suggested_binds.next(NULL); + suggested_binds.interactionProfile(getPath(headset)); + suggested_binds.suggestedBindings(bindings); + + error = XR10.xrSuggestInteractionProfileBindings(instance, suggested_binds); + logError(error, "xrSuggestInteractionProfileBindings", headset); + } + + + XrSessionActionSetsAttachInfo attach_info = XrSessionActionSetsAttachInfo.calloc(stack); + attach_info.type(XR10.XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO); + attach_info.next(NULL); + attach_info.actionSets(stackPointers(actionSetHandles.values().stream().mapToLong(value -> value).toArray())); + + error = XR10.xrAttachSessionActionSets(session, attach_info); + logError(error, "xrAttachSessionActionSets", ""); + + XrActionSet actionSet = new XrActionSet(this.actionSetHandles.get(VRInputActionSet.GLOBAL), instance); + XrActionSpaceCreateInfo actionSpace = XrActionSpaceCreateInfo.calloc(stack); + actionSpace.type(XR10.XR_TYPE_ACTION_SPACE_CREATE_INFO); + actionSpace.next(NULL); + actionSpace.action(new XrAction(grip[RIGHT_CONTROLLER], actionSet)); + actionSpace.subactionPath(getPath("/user/hand/right")); + actionSpace.poseInActionSpace(POSE_IDENTITY); + PointerBuffer pp = stackCallocPointer(1); + error = XR10.xrCreateActionSpace(session, actionSpace, pp); + logError(error, "xrCreateActionSpace", "grip: /user/hand/right"); + this.gripSpace[RIGHT_CONTROLLER] = new XrSpace(pp.get(0), session); + + actionSpace.action(new XrAction(grip[LEFT_CONTROLLER], actionSet)); + actionSpace.subactionPath(getPath("/user/hand/left")); + error = XR10.xrCreateActionSpace(session, actionSpace, pp); + logError(error, "xrCreateActionSpace", "grip: /user/hand/left"); + this.gripSpace[LEFT_CONTROLLER] = new XrSpace(pp.get(0), session); + + actionSpace.action(new XrAction(aim[RIGHT_CONTROLLER], actionSet)); + actionSpace.subactionPath(getPath("/user/hand/right")); + error = XR10.xrCreateActionSpace(session, actionSpace, pp); + logError(error, "xrCreateActionSpace", "aim: /user/hand/right"); + this.aimSpace[RIGHT_CONTROLLER] = new XrSpace(pp.get(0), session); + + actionSpace.action(new XrAction(aim[LEFT_CONTROLLER], actionSet)); + actionSpace.subactionPath(getPath("/user/hand/left")); + error = XR10.xrCreateActionSpace(session, actionSpace, pp); + logError(error, "xrCreateActionSpace", "aim: /user/hand/left"); + this.aimSpace[LEFT_CONTROLLER] = new XrSpace(pp.get(0), session); + + } + } + + public long getPath(String pathString) { + return this.paths.computeIfAbsent(pathString, s -> { + try (MemoryStack ignored = stackPush()) { + LongBuffer buf = stackCallocLong(1); + int error = XR10.xrStringToPath(instance, pathString, buf); + logError(error, "getPath", pathString); + return buf.get(); + } + }); + } + + private long createAction(String name, String localisedName, String type, XrActionSet actionSet, @Nullable String[] subactionPaths) { + try (MemoryStack stack = MemoryStack.stackPush()){ + String s = name.split("/")[name.split("/").length -1].toLowerCase(); + XrActionCreateInfo hands = XrActionCreateInfo.calloc(stack); + hands.type(XR10.XR_TYPE_ACTION_CREATE_INFO); + hands.next(NULL); + hands.actionName(memUTF8(s)); + switch (type) { + case "boolean" -> hands.actionType(XR10.XR_ACTION_TYPE_BOOLEAN_INPUT); + case "vector1" -> hands.actionType(XR10.XR_ACTION_TYPE_FLOAT_INPUT); + case "vector2" -> hands.actionType(XR10.XR_ACTION_TYPE_VECTOR2F_INPUT); + case "pose" -> hands.actionType(XR10.XR_ACTION_TYPE_POSE_INPUT); + case "haptic" -> hands.actionType(XR10.XR_ACTION_TYPE_VIBRATION_OUTPUT); + } + if(subactionPaths != null) { + LongBuffer buffer = stackCallocLong(subactionPaths.length); + for(String path : subactionPaths) { + buffer.put(getPath(path)); + } + hands.countSubactionPaths(subactionPaths.length); + hands.subactionPaths(buffer.rewind()); + } else { + hands.countSubactionPaths(0); + hands.subactionPaths(null); + } + hands.localizedActionName(memUTF8(s)); + PointerBuffer buffer = stackCallocPointer(1); + + int error = XR10.xrCreateAction(actionSet, hands, buffer); + logError(error, "xrCreateAction", "name:", name, "type:", type); + return buffer.get(0); + } + } + + private long makeActionSet(XrInstance instance, String name, String localisedName, int priority) { + try (MemoryStack stack = MemoryStack.stackPush()){ + XrActionSetCreateInfo info = XrActionSetCreateInfo.calloc(stack); + info.type(XR10.XR_TYPE_ACTION_SET_CREATE_INFO); + info.next(NULL); + info.actionSetName(memUTF8(localisedName.toLowerCase())); + info.localizedActionSetName(memUTF8(localisedName.toLowerCase())); + info.priority(priority); + PointerBuffer buffer = stack.callocPointer(1); + + int error = XR10.xrCreateActionSet(instance, info, buffer); + logError(error, "makeActionSet", localisedName.toLowerCase()); + return buffer.get(0); + } + } + + static ByteBuffer bufferHeap(int capacity, int sizeof, int type) { + ByteBuffer b = memCalloc(capacity * sizeof); + + for (int i = 0; i < capacity; i++) { + b.position(i * sizeof); + b.putInt(type); + } + b.rewind(); + return b; + } + + /** + * gets the String for the given xrResult + */ + private String getResultName(int xrResult) { + String resultString = switch (xrResult) { + case 1 -> "XR_TIMEOUT_EXPIRED"; + case 3 -> "XR_SESSION_LOSS_PENDING"; + case 4 -> "XR_EVENT_UNAVAILABLE"; + case 7 -> "XR_SPACE_BOUNDS_UNAVAILABLE"; + case 8 -> "XR_SESSION_NOT_FOCUSED"; + case 9 -> "XR_FRAME_DISCARDED"; + case -1 -> "XR_ERROR_VALIDATION_FAILURE"; + case -2 -> "XR_ERROR_RUNTIME_FAILURE"; + case -3 -> "XR_ERROR_OUT_OF_MEMORY"; + case -4 -> "XR_ERROR_API_VERSION_UNSUPPORTED"; + case -6 -> "XR_ERROR_INITIALIZATION_FAILED"; + case -7 -> "XR_ERROR_FUNCTION_UNSUPPORTED"; + case -8 -> "XR_ERROR_FEATURE_UNSUPPORTED"; + case -9 -> "XR_ERROR_EXTENSION_NOT_PRESENT"; + case -10 -> "XR_ERROR_LIMIT_REACHED"; + case -11 -> "XR_ERROR_SIZE_INSUFFICIENT"; + case -12 -> "XR_ERROR_HANDLE_INVALID"; + case -13 -> "XR_ERROR_INSTANCE_LOST"; + case -14 -> "XR_ERROR_SESSION_RUNNING"; + case -16 -> "XR_ERROR_SESSION_NOT_RUNNING"; + case -17 -> "XR_ERROR_SESSION_LOST"; + case -18 -> "XR_ERROR_SYSTEM_INVALID"; + case -19 -> "XR_ERROR_PATH_INVALID"; + case -20 -> "XR_ERROR_PATH_COUNT_EXCEEDED"; + case -21 -> "XR_ERROR_PATH_FORMAT_INVALID"; + case -22 -> "XR_ERROR_PATH_UNSUPPORTED"; + case -23 -> "XR_ERROR_LAYER_INVALID"; + case -24 -> "XR_ERROR_LAYER_LIMIT_EXCEEDED"; + case -25 -> "XR_ERROR_SWAPCHAIN_RECT_INVALID"; + case -26 -> "XR_ERROR_SWAPCHAIN_FORMAT_UNSUPPORTED"; + case -27 -> "XR_ERROR_ACTION_TYPE_MISMATCH"; + case -28 -> "XR_ERROR_SESSION_NOT_READY"; + case -29 -> "XR_ERROR_SESSION_NOT_STOPPING"; + case -30 -> "XR_ERROR_TIME_INVALID"; + case -31 -> "XR_ERROR_REFERENCE_SPACE_UNSUPPORTED"; + case -32 -> "XR_ERROR_FILE_ACCESS_ERROR"; + case -33 -> "XR_ERROR_FILE_CONTENTS_INVALID"; + case -34 -> "XR_ERROR_FORM_FACTOR_UNSUPPORTED"; + case -35 -> "XR_ERROR_FORM_FACTOR_UNAVAILABLE"; + case -36 -> "XR_ERROR_API_LAYER_NOT_PRESENT"; + case -37 -> "XR_ERROR_CALL_ORDER_INVALID"; + case -38 -> "XR_ERROR_GRAPHICS_DEVICE_INVALID"; + case -39 -> "XR_ERROR_POSE_INVALID"; + case -40 -> "XR_ERROR_INDEX_OUT_OF_RANGE"; + case -41 -> "XR_ERROR_VIEW_CONFIGURATION_TYPE_UNSUPPORTED"; + case -42 -> "XR_ERROR_ENVIRONMENT_BLEND_MODE_UNSUPPORTED"; + case -44 -> "XR_ERROR_NAME_DUPLICATED"; + case -45 -> "XR_ERROR_NAME_INVALID"; + case -46 -> "XR_ERROR_ACTIONSET_NOT_ATTACHED"; + case -47 -> "XR_ERROR_ACTIONSETS_ALREADY_ATTACHED"; + case -48 -> "XR_ERROR_LOCALIZED_NAME_DUPLICATED"; + case -49 -> "XR_ERROR_LOCALIZED_NAME_INVALID"; + case -50 -> "XR_ERROR_GRAPHICS_REQUIREMENTS_CALL_MISSING"; + case -51 -> "XR_ERROR_RUNTIME_UNAVAILABLE"; + default -> null; + }; + if (resultString == null) { + // ask the runtime for the xrResult name + try (MemoryStack stack = MemoryStack.stackPush()) { + ByteBuffer str = stack.calloc(XR10.XR_MAX_RESULT_STRING_SIZE); + + if (XR10.xrResultToString(instance, xrResult, str) == XR10.XR_SUCCESS) { + resultString = (memUTF8(memAddress(str))); + } else { + resultString = "Unknown Error: " + xrResult; + } + } + } + return resultString; + } + + /** + * logs only errors + * @param xrResult result to check + * @param caller where the xrResult came from + * @param args arguments may be helpful in locating the error + */ + protected void logError(int xrResult, String caller, String... args) { + if (xrResult < 0) { + VRSettings.LOGGER.error("{} for {} errored: {}", caller, String.join(" ", args), getResultName(xrResult)); + } + } +} diff --git a/common/src/main/java/org/vivecraft/client_vr/provider/openxr/OpenXRHapticSchedular.java b/common/src/main/java/org/vivecraft/client_vr/provider/openxr/OpenXRHapticSchedular.java new file mode 100644 index 000000000..819a355e8 --- /dev/null +++ b/common/src/main/java/org/vivecraft/client_vr/provider/openxr/OpenXRHapticSchedular.java @@ -0,0 +1,47 @@ +package org.vivecraft.client_vr.provider.openxr; + +import org.lwjgl.openxr.*; +import org.lwjgl.system.MemoryStack; +import org.vivecraft.client_vr.ClientDataHolderVR; +import org.vivecraft.client_vr.provider.ControllerType; +import org.vivecraft.client_vr.provider.HapticScheduler; +import org.vivecraft.client_vr.provider.control.VRInputActionSet; + +import java.util.concurrent.TimeUnit; + +import static java.sql.Types.NULL; + +public class OpenXRHapticSchedular extends HapticScheduler { + + private void triggerHapticPulse(ControllerType controller, float durationSeconds, float frequency, float amplitude) { + try (MemoryStack stack = MemoryStack.stackPush()){ + int i = controller == ControllerType.RIGHT ? 0 : 1; + if (ClientDataHolderVR.getInstance().vrSettings.reverseHands) { + i = controller == ControllerType.RIGHT ? 1 : 0; + } + XrActionSet actionSet = new XrActionSet(MCOpenXR.get().getActionSetHandle(VRInputActionSet.GLOBAL), MCOpenXR.get().instance); + XrHapticActionInfo info = XrHapticActionInfo.calloc(stack); + info.type(XR10.XR_TYPE_HAPTIC_ACTION_INFO); + info.next(NULL); + info.action(new XrAction(MCOpenXR.get().haptics[i], actionSet)); + + XrHapticVibration vibration = XrHapticVibration.calloc(stack); + vibration.type(XR10.XR_TYPE_HAPTIC_VIBRATION); + vibration.next(NULL); + vibration.duration((long) (durationSeconds * 1_000_000_000)); + vibration.frequency(frequency); + vibration.amplitude(amplitude); + + int error = XR10.xrApplyHapticFeedback(MCOpenXR.get().session, info, XrHapticBaseHeader.create(vibration)); + MCOpenXR.get().logError(error, "xrApplyHapticFeedback", ""); + } + } + + @Override + public void queueHapticPulse(ControllerType controller, float durationSeconds, float frequency, float amplitude, float delaySeconds) { + this.executor.schedule(() -> + { + this.triggerHapticPulse(controller, durationSeconds, frequency, amplitude); + }, (long) (delaySeconds * 1000000.0F), TimeUnit.MICROSECONDS); + } +} diff --git a/common/src/main/java/org/vivecraft/client_vr/provider/openxr/OpenXRStereoRenderer.java b/common/src/main/java/org/vivecraft/client_vr/provider/openxr/OpenXRStereoRenderer.java new file mode 100644 index 000000000..3cebe96cc --- /dev/null +++ b/common/src/main/java/org/vivecraft/client_vr/provider/openxr/OpenXRStereoRenderer.java @@ -0,0 +1,184 @@ +package org.vivecraft.client_vr.provider.openxr; + +import com.mojang.blaze3d.pipeline.RenderTarget; +import net.minecraft.util.Tuple; +import org.joml.Matrix4f; +import org.lwjgl.PointerBuffer; +import org.lwjgl.opengl.GL31; +import org.lwjgl.openxr.*; +import org.lwjgl.system.MemoryStack; +import org.vivecraft.client_vr.ClientDataHolderVR; +import org.vivecraft.client_vr.VRTextureTarget; +import org.vivecraft.client_vr.provider.VRRenderer; +import org.vivecraft.client_vr.render.RenderConfigException; +import org.vivecraft.client_vr.render.helpers.RenderHelper; + +import java.io.IOException; +import java.nio.IntBuffer; + +public class OpenXRStereoRenderer extends VRRenderer { + private final MCOpenXR openxr; + private int swapIndex; + private VRTextureTarget[] leftFramebuffers; + private VRTextureTarget[] rightFramebuffers; + private boolean render; + private XrCompositionLayerProjectionView.Buffer projectionLayerViews; + private VRTextureTarget rightFramebuffer; + private VRTextureTarget leftFramebuffer; + + + public OpenXRStereoRenderer(MCOpenXR vr) { + super(vr); + this.openxr = vr; + } + + @Override + public void createRenderTexture(int width, int height) throws RenderConfigException{ + try (MemoryStack stack = MemoryStack.stackPush()) { + + //Get amount of views in the swapchain + IntBuffer intBuffer = stack.ints(0); //Set value to 0 + int error = XR10.xrEnumerateSwapchainImages(openxr.swapchain, intBuffer, null); + this.openxr.logError(error, "xrEnumerateSwapchainImages", "get count"); + + //Now we know the amount, create the image buffer + int imageCount = intBuffer.get(0); + XrSwapchainImageOpenGLKHR.Buffer swapchainImageBuffer = this.openxr.device.createImageBuffers(imageCount, stack); + + error = XR10.xrEnumerateSwapchainImages(openxr.swapchain, intBuffer, XrSwapchainImageBaseHeader.create(swapchainImageBuffer.address(), swapchainImageBuffer.capacity())); + this.openxr.logError(error, "xrEnumerateSwapchainImages", "get images"); + + this.leftFramebuffers = new VRTextureTarget[imageCount]; + this.rightFramebuffers = new VRTextureTarget[imageCount]; + + for (int i = 0; i < imageCount; i++) { + XrSwapchainImageOpenGLKHR openxrImage = swapchainImageBuffer.get(i); + leftFramebuffers[i] = new VRTextureTarget("L Eye " + i, width, height, openxrImage.image(), 0); + RenderHelper.checkGLError("Left Eye framebuffer setup"); + rightFramebuffers[i] = new VRTextureTarget("R Eye " + i, width, height, openxrImage.image(), 1); + RenderHelper.checkGLError("Right Eye framebuffer setup"); + } + + this.rightFramebuffer = new VRTextureTarget("R Eye mirror", width, height, true, -1, true, true, ClientDataHolderVR.getInstance().vrSettings.vrUseStencil); + this.leftFramebuffer = new VRTextureTarget("L Eye mirror", width, height, true, -1, true, true, ClientDataHolderVR.getInstance().vrSettings.vrUseStencil); + } + } + + @Override + public void setupRenderConfiguration(boolean render) throws IOException, RenderConfigException { + super.setupRenderConfiguration(render); + + if (!render) { + return; + } + this.projectionLayerViews = XrCompositionLayerProjectionView.calloc(2); + try (MemoryStack stack = MemoryStack.stackPush()){ + + IntBuffer intBuf2 = stack.callocInt(1); + + int error = XR10.xrAcquireSwapchainImage( + openxr.swapchain, + XrSwapchainImageAcquireInfo.calloc(stack).type(XR10.XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO), + intBuf2); + this.openxr.logError(error, "xrAcquireSwapchainImage", ""); + + error = XR10.xrWaitSwapchainImage(openxr.swapchain, + XrSwapchainImageWaitInfo.calloc(stack) + .type(XR10.XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO) + .timeout(XR10.XR_INFINITE_DURATION)); + this.openxr.logError(error, "xrWaitSwapchainImage", ""); + + this.swapIndex = intBuf2.get(0); + + // Render view to the appropriate part of the swapchain image. + for (int viewIndex = 0; viewIndex < 2; viewIndex++) { + + var subImage = projectionLayerViews.get(viewIndex) + .type(XR10.XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW) + .pose(openxr.viewBuffer.get(viewIndex).pose()) + .fov(openxr.viewBuffer.get(viewIndex).fov()) + .subImage(); + subImage.swapchain(openxr.swapchain); + subImage.imageRect().offset().set(0, 0); + subImage.imageRect().extent().set(openxr.width, openxr.height); + subImage.imageArrayIndex(viewIndex); + + } + } + } + + @Override + public Matrix4f getProjectionMatrix(int eyeType, float nearClip, float farClip) { + XrFovf fov = openxr.viewBuffer.get(eyeType).fov(); + return new Matrix4f().setPerspectiveOffCenterFov(fov.angleLeft(), fov.angleRight(), fov.angleDown(), fov.angleUp(), nearClip, farClip); + } + + @Override + public void endFrame() throws RenderConfigException { + + GL31.glBindFramebuffer(GL31.GL_READ_FRAMEBUFFER, getLeftEyeTarget().frameBufferId); + GL31.glBindFramebuffer(GL31.GL_DRAW_FRAMEBUFFER, leftFramebuffers[swapIndex].frameBufferId); + GL31.glBlitFramebuffer(0,0, getLeftEyeTarget().viewWidth, getLeftEyeTarget().viewHeight, 0,0, leftFramebuffers[swapIndex].viewWidth, leftFramebuffers[swapIndex].viewHeight, GL31.GL_STENCIL_BUFFER_BIT | GL31.GL_COLOR_BUFFER_BIT, GL31.GL_NEAREST); + + GL31.glBindFramebuffer(GL31.GL_READ_FRAMEBUFFER, getRightEyeTarget().frameBufferId); + GL31.glBindFramebuffer(GL31.GL_DRAW_FRAMEBUFFER, rightFramebuffers[swapIndex].frameBufferId); + GL31.glBlitFramebuffer(0,0, getRightEyeTarget().viewWidth, getRightEyeTarget().viewHeight, 0,0, rightFramebuffers[swapIndex].viewWidth, rightFramebuffers[swapIndex].viewHeight, GL31.GL_STENCIL_BUFFER_BIT | GL31.GL_COLOR_BUFFER_BIT, GL31.GL_NEAREST); + + + try (MemoryStack stack = MemoryStack.stackPush()){ + PointerBuffer layers = stack.callocPointer(1); + int error; + + error = XR10.xrReleaseSwapchainImage( + openxr.swapchain, + XrSwapchainImageReleaseInfo.calloc(stack) + .type(XR10.XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO)); + this.openxr.logError(error, "xrReleaseSwapchainImage", ""); + + XrCompositionLayerProjection compositionLayerProjection = XrCompositionLayerProjection.calloc(stack) + .type(XR10.XR_TYPE_COMPOSITION_LAYER_PROJECTION) + .space(openxr.xrAppSpace) + .views(projectionLayerViews); + + layers.put(compositionLayerProjection); + + layers.flip(); + + error = XR10.xrEndFrame( + openxr.session, + XrFrameEndInfo.calloc(stack) + .type(XR10.XR_TYPE_FRAME_END_INFO) + .displayTime(openxr.time) + .environmentBlendMode(XR10.XR_ENVIRONMENT_BLEND_MODE_OPAQUE) + .layers(layers)); + this.openxr.logError(error, "xrEndFrame", ""); + + projectionLayerViews.close(); + } + } + + @Override + public boolean providesStencilMask() { + return false; + } + + @Override + public RenderTarget getLeftEyeTarget() { + return leftFramebuffer; + } + + @Override + public RenderTarget getRightEyeTarget() { + return rightFramebuffer; + } + + @Override + public String getName() { + return "OpenXR"; + } + + @Override + public Tuple getRenderTextureSizes() { + return new Tuple<>(openxr.width, openxr.height); + } +} diff --git a/common/src/main/java/org/vivecraft/client_vr/provider/openxr/OpenXRUtil.java b/common/src/main/java/org/vivecraft/client_vr/provider/openxr/OpenXRUtil.java new file mode 100644 index 000000000..ebc94576f --- /dev/null +++ b/common/src/main/java/org/vivecraft/client_vr/provider/openxr/OpenXRUtil.java @@ -0,0 +1,19 @@ +package org.vivecraft.client_vr.provider.openxr; + +import org.joml.Matrix4f; +import org.joml.Quaternionf; +import org.lwjgl.openxr.XrPosef; +import org.lwjgl.openxr.XrQuaternionf; + +public class OpenXRUtil { + + public static void openXRPoseToMarix(XrPosef pose, Matrix4f mat) { + mat.set(new Quaternionf(pose.orientation().x(), pose.orientation().y(), pose.orientation().z(), pose.orientation().w())) + .setTranslation(pose.position$().x(), pose.position$().y(), pose.position$().z()) + .m33(1); + } + + public static void openXRPoseToMarix(XrQuaternionf quat, Matrix4f mat) { + mat.set(new Quaternionf(quat.x(), quat.y(), quat.z(), quat.w())); + } +} diff --git a/common/src/main/java/org/vivecraft/client_vr/provider/openxr/XRBindings.java b/common/src/main/java/org/vivecraft/client_vr/provider/openxr/XRBindings.java new file mode 100644 index 000000000..8a3a684a6 --- /dev/null +++ b/common/src/main/java/org/vivecraft/client_vr/provider/openxr/XRBindings.java @@ -0,0 +1,137 @@ +package org.vivecraft.client_vr.provider.openxr; + +import org.apache.commons.lang3.tuple.MutablePair; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.HashSet; + +public class XRBindings { + + public static HashSet supportedHeadsets() { + HashSet set = new HashSet<>(); + if (MCOpenXR.get().systemName.toLowerCase().contains("oculus") || MCOpenXR.get().systemName.toLowerCase().contains("meta")) { + set.add("/interaction_profiles/oculus/touch_controller"); + return set; + } + if (MCOpenXR.get().session.getCapabilities().XR_HTC_vive_cosmos_controller_interaction) { + set.add("/interaction_profiles/htc/vive_cosmos_controller"); + } + set.add("/interaction_profiles/htc/vive_controller"); + return set; + } + + private static HashSet> quest2Bindings() { + HashSet> set = new HashSet<>(); + + set.add(new MutablePair<>("/actions/global/in/vivecraft.key.ingameMenuButton", "/user/hand/left/input/y/click")); + set.add(new MutablePair<>("/actions/global/in/vivecraft.key.toggleKeyboard", "/user/hand/left/input/squeeze")); + set.add(new MutablePair<>("/actions/global/in/key.inventory", "/user/hand/left/input/x/click")); + + set.add(new MutablePair<>("/actions/gui/in/vivecraft.key.guiShift", "/user/hand/left/input/squeeze")); + set.add(new MutablePair<>("/actions/gui/in/vivecraft.key.guiMiddleClick", "/user/hand/right/input/squeeze")); + set.add(new MutablePair<>("/actions/gui/in/vivecraft.key.guiLeftClick", "/user/hand/right/input/trigger")); + set.add(new MutablePair<>("/actions/gui/in/vivecraft.key.guiRightClick", "/user/hand/right/input/a/click")); + set.add(new MutablePair<>("/actions/gui/in/vivecraft.key.guiScrollAxis", "/user/hand/right/input/thumbstick/y")); + + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.hotbarPrev", "/user/hand/left/input/squeeze")); + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.hotbarNext", "/user/hand/right/input/squeeze")); + set.add(new MutablePair<>("/actions/ingame/in/key.attack", "/user/hand/right/input/trigger")); + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.teleport", "/user/hand/left/input/trigger")); + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.radialMenu", "/user/hand/right/input/b/click")); + set.add(new MutablePair<>("/actions/ingame/in/key.use", "/user/hand/right/input/a/click")); + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.teleportFallback", "/user/hand/left/input/trigger/value")); + set.add(new MutablePair<>("/actions/ingame/in/key.jump", "/user/hand/left/input/thumbstick")); + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.freeMoveStrafe", "/user/hand/left/input/thumbstick")); + set.add(new MutablePair<>("/actions/ingame/in/key.sneak", "/user/hand/right/input/thumbstick")); + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.rotateAxis", "/user/hand/right/input/thumbstick")); + + set.add(new MutablePair<>("/actions/keyboard/in/vivecraft.key.keyboardShift", "/user/hand/left/input/squeeze")); + set.add(new MutablePair<>("/actions/keyboard/in/vivecraft.key.keyboardShift", "/user/hand/right/input/squeeze")); + set.add(new MutablePair<>("/actions/keyboard/in/vivecraft.key.keyboardClick", "/user/hand/left/input/trigger")); + set.add(new MutablePair<>("/actions/keyboard/in/vivecraft.key.keyboardClick", "/user/hand/right/input/trigger")); + return set; + } + + private static HashSet> viveBindings() { + HashSet> set = new HashSet<>(); + + set.add(new MutablePair<>("/actions/global/in/vivecraft.key.ingameMenuButton", "/user/hand/left/input/menu/click")); + set.add(new MutablePair<>("/actions/global/in/vivecraft.key.toggleKeyboard", "/user/hand/left/input/squeeze/click")); + set.add(new MutablePair<>("/actions/global/in/key.inventory", "/user/hand/right/input/trackpad/click")); + + set.add(new MutablePair<>("/actions/gui/in/vivecraft.key.guiShift", "/user/hand/left/input/squeeze/click")); + set.add(new MutablePair<>("/actions/gui/in/vivecraft.key.guiMiddleClick", "/user/hand/right/input/squeeze/click")); + set.add(new MutablePair<>("/actions/gui/in/vivecraft.key.guiLeftClick", "/user/hand/right/input/trigger/click")); + set.add(new MutablePair<>("/actions/gui/in/vivecraft.key.guiRightClick", "/user/hand/right/input/trackpad/click")); + set.add(new MutablePair<>("/actions/gui/in/vivecraft.key.guiScrollAxis", "/user/hand/right/input/trackpad/y")); + + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.hotbarPrev", "/user/hand/left/input/squeeze/click")); + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.hotbarNext", "/user/hand/right/input/squeeze/click")); + set.add(new MutablePair<>("/actions/ingame/in/key.attack", "/user/hand/right/input/trigger/click")); + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.teleport", "/user/hand/left/input/trigger/click")); + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.radialMenu", "/user/hand/right/input/menu/click")); + set.add(new MutablePair<>("/actions/ingame/in/key.use", "/user/hand/right/input/trackpad/click")); + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.teleportFallback", "/user/hand/left/input/trigger/value")); + set.add(new MutablePair<>("/actions/ingame/in/key.jump", "/user/hand/left/input/trackpad/x")); + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.freeMoveStrafe", "/user/hand/left/input/trackpad")); + set.add(new MutablePair<>("/actions/ingame/in/key.sneak", "/user/hand/left/input/trackpad/y")); + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.rotateAxis", "/user/hand/right/input/trackpad")); + + set.add(new MutablePair<>("/actions/keyboard/in/vivecraft.key.keyboardShift", "/user/hand/left/input/squeeze/click")); + set.add(new MutablePair<>("/actions/keyboard/in/vivecraft.key.keyboardClick", "/user/hand/left/input/trigger/click")); + set.add(new MutablePair<>("/actions/keyboard/in/vivecraft.key.keyboardClick", "/user/hand/right/input/trigger/click")); + + set.add(new MutablePair<>("/actions/technical/in/vivecraft.key.trackpadTouch", "/user/hand/left/input/trackpad/click")); + set.add(new MutablePair<>("/actions/technical/in/vivecraft.key.trackpadTouch", "/user/hand/right/input/trackpad/touch")); + + return set; + } + + private static HashSet> cosmosBindings() { + HashSet> set = new HashSet<>(); + + set.add(new MutablePair<>("/actions/global/in/vivecraft.key.ingameMenuButton", "/user/hand/left/input/y/click")); + set.add(new MutablePair<>("/actions/global/in/vivecraft.key.toggleKeyboard", "/user/hand/left/input/y/long")); + set.add(new MutablePair<>("/actions/global/in/key.inventory", "/user/hand/left/input/x/click")); + + set.add(new MutablePair<>("/actions/gui/in/vivecraft.key.guiShift", "/user/hand/left/input/grip/click")); + set.add(new MutablePair<>("/actions/gui/in/vivecraft.key.guiMiddleClick", "/user/hand/right/input/grip/click")); + set.add(new MutablePair<>("/actions/gui/in/vivecraft.key.guiLeftClick", "/user/hand/right/input/trigger/click")); + set.add(new MutablePair<>("/actions/gui/in/vivecraft.key.guiRightClick", "/user/hand/right/input/a/click")); + set.add(new MutablePair<>("/actions/gui/in/vivecraft.key.guiScrollAxis", "/user/hand/right/input/joystick/scroll")); + + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.hotbarPrev", "/user/hand/left/input/grip/click")); + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.hotbarNext", "/user/hand/right/input/grip/click")); + set.add(new MutablePair<>("/actions/ingame/in/key.attack", "/user/hand/right/input/trigger/click")); + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.teleport", "/user/hand/left/input/trigger/click")); + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.radialMenu", "/user/hand/right/input/b/click")); + set.add(new MutablePair<>("/actions/ingame/in/key.use", "/user/hand/right/input/a/click")); + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.freeMoveStrafe", "/user/hand/left/input/joystick/position")); + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.rotateAxis", "/user/hand/right/input/joystick/position")); + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.teleportFallback", "/user/hand/left/input/trigger/pull")); + set.add(new MutablePair<>("/actions/ingame/in/key.jump", "/user/hand/left/input/bumper/click")); + set.add(new MutablePair<>("/actions/ingame/in/key.sneak", "/user/hand/right/input/bumper/click")); + + set.add(new MutablePair<>("/actions/keyboard/in/vivecraft.key.keyboardShift", "/user/hand/left/input/grip/click")); + set.add(new MutablePair<>("/actions/keyboard/in/vivecraft.key.keyboardClick", "/user/hand/left/input/trigger/click")); + set.add(new MutablePair<>("/actions/keyboard/in/vivecraft.key.keyboardClick", "/user/hand/right/input/trigger/click")); + return set; + } + + public static HashSet> getBinding(String Headset){ + switch (Headset) { + case "/interaction_profiles/htc/vive_cosmos_controller" -> { + return cosmosBindings(); + } + case "/interaction_profiles/htc/vive_controller" -> { + return viveBindings(); + } + case "/interaction_profiles/oculus/touch_controller" -> { + return quest2Bindings(); + } + default -> { + return viveBindings(); + } + } + } +} diff --git a/common/src/main/java/org/vivecraft/client_vr/render/helpers/ShaderHelper.java b/common/src/main/java/org/vivecraft/client_vr/render/helpers/ShaderHelper.java index 4a3030b74..601cd132a 100644 --- a/common/src/main/java/org/vivecraft/client_vr/render/helpers/ShaderHelper.java +++ b/common/src/main/java/org/vivecraft/client_vr/render/helpers/ShaderHelper.java @@ -253,8 +253,8 @@ public static void drawMirror() { )) { // show both eyes side by side - RenderTarget leftEye = DATA_HOLDER.vrRenderer.framebufferEye0; - RenderTarget rightEye = DATA_HOLDER.vrRenderer.framebufferEye1; + RenderTarget leftEye = DATA_HOLDER.vrRenderer.getLeftEyeTarget(); + RenderTarget rightEye = DATA_HOLDER.vrRenderer.getRightEyeTarget(); int screenWidth = ((WindowExtension) (Object) MC.getWindow()).vivecraft$getActualScreenWidth() / 2; int screenHeight = ((WindowExtension) (Object) MC.getWindow()).vivecraft$getActualScreenHeight(); @@ -271,7 +271,7 @@ public static void drawMirror() { float xCrop = 0.0F; float yCrop = 0.0F; boolean keepAspect = false; - RenderTarget source = DATA_HOLDER.vrRenderer.framebufferEye0; + RenderTarget source = DATA_HOLDER.vrRenderer.getLeftEyeTarget(); if (DATA_HOLDER.vrSettings.displayMirrorUseScreenshotCamera && DATA_HOLDER.cameraTracker.isVisible()) @@ -288,11 +288,11 @@ public static void drawMirror() { DATA_HOLDER.vrSettings.displayMirrorMode == VRSettings.MirrorMode.OFF) { if (!DATA_HOLDER.vrSettings.displayMirrorLeftEye) { - source = DATA_HOLDER.vrRenderer.framebufferEye1; + source = DATA_HOLDER.vrRenderer.getRightEyeTarget(); } } else if (DATA_HOLDER.vrSettings.displayMirrorMode == VRSettings.MirrorMode.CROPPED) { if (!DATA_HOLDER.vrSettings.displayMirrorLeftEye) { - source = DATA_HOLDER.vrRenderer.framebufferEye1; + source = DATA_HOLDER.vrRenderer.getRightEyeTarget(); } xCrop = DATA_HOLDER.vrSettings.mirrorCrop; @@ -363,9 +363,9 @@ public static void doMixedRealityMirror() { source = DATA_HOLDER.vrRenderer.framebufferUndistorted; } else { if (DATA_HOLDER.vrSettings.displayMirrorLeftEye) { - source = DATA_HOLDER.vrRenderer.framebufferEye0; + source = DATA_HOLDER.vrRenderer.getLeftEyeTarget(); } else { - source = DATA_HOLDER.vrRenderer.framebufferEye1; + source = DATA_HOLDER.vrRenderer.getRightEyeTarget(); } } VRShaders.MIXED_REALITY_SHADER.setSampler("firstPersonColor", source.getColorTextureId()); diff --git a/common/src/main/java/org/vivecraft/client_vr/render/helpers/VRPassHelper.java b/common/src/main/java/org/vivecraft/client_vr/render/helpers/VRPassHelper.java index 2f39077ee..a173f1ac2 100644 --- a/common/src/main/java/org/vivecraft/client_vr/render/helpers/VRPassHelper.java +++ b/common/src/main/java/org/vivecraft/client_vr/render/helpers/VRPassHelper.java @@ -62,9 +62,9 @@ public static void renderSingleView(RenderPass eye, float partialTick, long nano } if (eye == RenderPass.LEFT) { - DATA_HOLDER.vrRenderer.framebufferEye0.bindWrite(true); + DATA_HOLDER.vrRenderer.getLeftEyeTarget().bindWrite(true); } else { - DATA_HOLDER.vrRenderer.framebufferEye1.bindWrite(true); + DATA_HOLDER.vrRenderer.getRightEyeTarget().bindWrite(true); } // do post-processing diff --git a/common/src/main/java/org/vivecraft/client_vr/settings/VRSettings.java b/common/src/main/java/org/vivecraft/client_vr/settings/VRSettings.java index 99da37d5a..373247e9b 100644 --- a/common/src/main/java/org/vivecraft/client_vr/settings/VRSettings.java +++ b/common/src/main/java/org/vivecraft/client_vr/settings/VRSettings.java @@ -142,6 +142,7 @@ public enum ShaderGUIRender implements OptionEnum { public enum VRProvider implements OptionEnum { OPENVR, + OPENXR, NULLVR } @@ -1728,7 +1729,7 @@ void onOptionChange() { @Override String getDisplayString(String prefix, Object value) { if (VRState.VR_ENABLED) { - RenderTarget eye0 = ClientDataHolderVR.getInstance().vrRenderer.framebufferEye0; + RenderTarget eye0 = ClientDataHolderVR.getInstance().vrRenderer.getLeftEyeTarget(); return prefix + Math.round((float) value * 100) + "% (" + (int) Math.ceil(eye0.viewWidth * Math.sqrt((float) value)) + "x" + (int) Math.ceil(eye0.viewHeight * Math.sqrt((float) value)) + ")"; diff --git a/common/src/main/java/org/vivecraft/mixin/client/blaze3d/RenderTargetMixin.java b/common/src/main/java/org/vivecraft/mixin/client/blaze3d/RenderTargetMixin.java index c1513d234..e72594dbc 100644 --- a/common/src/main/java/org/vivecraft/mixin/client/blaze3d/RenderTargetMixin.java +++ b/common/src/main/java/org/vivecraft/mixin/client/blaze3d/RenderTargetMixin.java @@ -23,6 +23,8 @@ public abstract class RenderTargetMixin implements RenderTargetExtension { public int width; @Shadow public int height; + @Shadow + protected int colorTextureId; @Unique private int vivecraft$texId = -1; @Unique @@ -53,6 +55,10 @@ public abstract class RenderTargetMixin implements RenderTargetExtension { } @Override + public void vivecraft$setColorid(int colorid) { + this.colorTextureId = colorid; + } + @Unique public void vivecraft$setLinearFilter(boolean linearFilter) { this.vivecraft$linearFilter = linearFilter; diff --git a/common/src/main/java/org/vivecraft/mixin/client_vr/KeyboardInputVRMixin.java b/common/src/main/java/org/vivecraft/mixin/client_vr/KeyboardInputVRMixin.java index a82ceb635..0cd53437e 100644 --- a/common/src/main/java/org/vivecraft/mixin/client_vr/KeyboardInputVRMixin.java +++ b/common/src/main/java/org/vivecraft/mixin/client_vr/KeyboardInputVRMixin.java @@ -16,12 +16,11 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.vivecraft.client.VivecraftVRMod; +import org.vivecraft.client_vr.provider.control.VRInputAction; import org.vivecraft.common.utils.MathUtils; import org.vivecraft.client_vr.ClientDataHolderVR; import org.vivecraft.client_vr.VRState; import org.vivecraft.client_vr.gameplay.screenhandlers.KeyboardHandler; -import org.vivecraft.client_vr.provider.MCVR; -import org.vivecraft.client_vr.provider.openvr_lwjgl.VRInputAction; @Mixin(KeyboardInput.class) public class KeyboardInputVRMixin extends Input { @@ -47,7 +46,7 @@ public class KeyboardInputVRMixin extends Input { @Unique private float vivecraft$getAxisValue(KeyMapping keyBinding) { - return Math.abs(MCVR.get().getInputAction(keyBinding).getAxis1DUseTracked()); + return Math.abs(ClientDataHolderVR.getInstance().vr.getInputAction(keyBinding).getAxis1DUseTracked()); } @Inject(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/KeyboardInput;calculateImpulse(ZZ)F", ordinal = 0)) diff --git a/common/src/main/java/org/vivecraft/mixin/client_vr/MinecraftVRMixin.java b/common/src/main/java/org/vivecraft/mixin/client_vr/MinecraftVRMixin.java index e65d9a9a6..7ab155cc5 100644 --- a/common/src/main/java/org/vivecraft/mixin/client_vr/MinecraftVRMixin.java +++ b/common/src/main/java/org/vivecraft/mixin/client_vr/MinecraftVRMixin.java @@ -68,7 +68,7 @@ import org.vivecraft.client_vr.gameplay.trackers.TelescopeTracker; import org.vivecraft.client_vr.menuworlds.MenuWorldDownloader; import org.vivecraft.client_vr.menuworlds.MenuWorldExporter; -import org.vivecraft.client_vr.provider.openvr_lwjgl.VRInputAction; +import org.vivecraft.client_vr.provider.control.VRInputAction; import org.vivecraft.client_vr.render.RenderConfigException; import org.vivecraft.client_vr.render.VRFirstPersonArmSwing; import org.vivecraft.client_vr.render.MirrorNotification; @@ -349,7 +349,7 @@ public abstract class MinecraftVRMixin implements MinecraftExtension { try { this.profiler.push("setupRenderConfiguration"); RenderHelper.checkGLError("pre render setup"); - ClientDataHolderVR.getInstance().vrRenderer.setupRenderConfiguration(); + ClientDataHolderVR.getInstance().vrRenderer.setupRenderConfiguration(true); RenderHelper.checkGLError("post render setup"); } catch (Exception e) { // something went wrong, disable VR diff --git a/common/src/main/java/org/vivecraft/util/VLoader.java b/common/src/main/java/org/vivecraft/util/VLoader.java new file mode 100644 index 000000000..cecdd9a10 --- /dev/null +++ b/common/src/main/java/org/vivecraft/util/VLoader.java @@ -0,0 +1,14 @@ +package org.vivecraft.util; + +public class VLoader { + static { + System.loadLibrary("vloader"); + } + + public static native long getEGLContext(); + public static native long getEGLConfig(); + public static native long getEGLDisplay(); + public static native long getDalvikVM(); + public static native long getDalvikActivity(); + public static native void setupAndroid(); +} diff --git a/common/src/main/resources/assets/vivecraft/lang/en_us.json b/common/src/main/resources/assets/vivecraft/lang/en_us.json index 25afb4062..f84ef2339 100644 --- a/common/src/main/resources/assets/vivecraft/lang/en_us.json +++ b/common/src/main/resources/assets/vivecraft/lang/en_us.json @@ -195,6 +195,7 @@ "vivecraft.options.MIXED_REALITY_RENDER_CAMERA_MODEL": "Show Camera Model", "vivecraft.options.PHYSICAL_KEYBOARD_THEME": "Keyboard Theme", "vivecraft.options.KEYBOARD_PRESS_BINDS": "Keyboard Presses Bindings", + "vivecraft.options.STEREOPLUGIN": "Stereo Plugin", "_comment6": "Option tooltips", "vivecraft.options.HUD_SCALE.tooltip": "Relative size HUD takes up in field-of-view.\nThe units are just relative, not in degrees or a fraction of FOV or anything.", "vivecraft.options.HUD_DISTANCE.tooltip": "Distance the floating HUD is drawn in front of your body.\nThe relative size of the HUD is unchanged by this.\nDistance is in meters (though isn't obstructed by blocks).", @@ -365,6 +366,9 @@ "vivecraft.options.keyboardtheme.aesthetic": "§bA§de§bs§dt§bh§de§bt§di§bc", "vivecraft.options.keyboardtheme.dose": "Medicine", "vivecraft.options.keyboardtheme.custom": "Custom", + "vivecraft.options.vrprovider.openvr": "OpenVR", + "vivecraft.options.vrprovider.openxr": "OpenXR", + "vivecraft.options.vrprovider.nullvr": "NullVR", "_comment8": "Button text", "vivecraft.gui.ok": "OK", "vivecraft.gui.clear": "Clear", diff --git a/fabric/build.gradle b/fabric/build.gradle index 97e03c728..7e08316ee 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -68,6 +68,10 @@ dependencies { include(implementation("org.lwjgl:lwjgl-openvr:${rootProject.lwjgl_version}:natives-macos")) include(implementation("org.lwjgl:lwjgl-openvr:${rootProject.lwjgl_version}:natives-windows")) + // Use custom OpenXR lib for Android and GLES bindings + include(implementation("QuestCraftPlusPlus:lwjgl3:${rootProject.lwjgl_version}:lwjgl-openxr-${rootProject.lwjgl_version}")) + include(implementation("org.lwjgl:lwjgl-openxr:${rootProject.lwjgl_version}:natives-windows")) + include(implementation("org.lwjgl:lwjgl-openxr:${rootProject.lwjgl_version}:natives-linux")) } processResources { diff --git a/forge/build.gradle b/forge/build.gradle index fb4dc5865..d5c4a7303 100644 --- a/forge/build.gradle +++ b/forge/build.gradle @@ -59,12 +59,21 @@ dependencies { forgeRuntimeLibrary("org.lwjgl:lwjgl-openvr:${rootProject.lwjgl_version}:natives-macos") forgeRuntimeLibrary("org.lwjgl:lwjgl-openvr:${rootProject.lwjgl_version}:natives-windows") + // Use custom OpenXR lib for Android and GLES bindings + forgeRuntimeLibrary("QuestCraftPlusPlus:lwjgl3:${rootProject.lwjgl_version}:lwjgl-openxr-${rootProject.lwjgl_version}") + forgeRuntimeLibrary("org.lwjgl:lwjgl-openxr:${rootProject.lwjgl_version}:natives-linux") + forgeRuntimeLibrary("org.lwjgl:lwjgl-openxr:${rootProject.lwjgl_version}:natives-windows") + // shadow the natives bundle("org.lwjgl:lwjgl-openvr:${rootProject.lwjgl_version}") { transitive = false } bundle("org.lwjgl:lwjgl-openvr:${rootProject.lwjgl_version}:natives-linux") { transitive = false } bundle("org.lwjgl:lwjgl-openvr:${rootProject.lwjgl_version}:natives-macos") { transitive = false } bundle("org.lwjgl:lwjgl-openvr:${rootProject.lwjgl_version}:natives-windows") { transitive = false } + bundle("QuestCraftPlusPlus:lwjgl3:${rootProject.lwjgl_version}:lwjgl-openxr-${rootProject.lwjgl_version}") { transitive = false } + bundle("org.lwjgl:lwjgl-openxr:${rootProject.lwjgl_version}:natives-linux") { transitive = false } + bundle("org.lwjgl:lwjgl-openxr:${rootProject.lwjgl_version}:natives-windows") { transitive = false } + compileOnly(annotationProcessor("io.github.llamalad7:mixinextras-common:${rootProject.mixin_extras_version}")) implementation(include("io.github.llamalad7:mixinextras-forge:${rootProject.mixin_extras_version}")) } diff --git a/neoforge/build.gradle b/neoforge/build.gradle index 271043ec5..3fec03e29 100644 --- a/neoforge/build.gradle +++ b/neoforge/build.gradle @@ -35,12 +35,21 @@ dependencies { forgeRuntimeLibrary("org.lwjgl:lwjgl-openvr:${rootProject.lwjgl_version}:natives-macos") forgeRuntimeLibrary("org.lwjgl:lwjgl-openvr:${rootProject.lwjgl_version}:natives-windows") + // Use custom OpenXR lib for Android and GLES bindings + forgeRuntimeLibrary("QuestCraftPlusPlus:lwjgl3:${rootProject.lwjgl_version}:lwjgl-openxr-${rootProject.lwjgl_version}") + forgeRuntimeLibrary("org.lwjgl:lwjgl-openxr:${rootProject.lwjgl_version}:natives-linux") + forgeRuntimeLibrary("org.lwjgl:lwjgl-openxr:${rootProject.lwjgl_version}:natives-windows") + // shadow the natives bundle("org.lwjgl:lwjgl-openvr:${rootProject.lwjgl_version}") { transitive = false } bundle("org.lwjgl:lwjgl-openvr:${rootProject.lwjgl_version}:natives-linux") { transitive = false } bundle("org.lwjgl:lwjgl-openvr:${rootProject.lwjgl_version}:natives-macos") { transitive = false } bundle("org.lwjgl:lwjgl-openvr:${rootProject.lwjgl_version}:natives-windows") { transitive = false } + bundle("QuestCraftPlusPlus:lwjgl3:${rootProject.lwjgl_version}:lwjgl-openxr-${rootProject.lwjgl_version}") { transitive = false } + bundle("org.lwjgl:lwjgl-openxr:${rootProject.lwjgl_version}:natives-linux") { transitive = false } + bundle("org.lwjgl:lwjgl-openxr:${rootProject.lwjgl_version}:natives-windows") { transitive = false } + compileOnly(annotationProcessor("io.github.llamalad7:mixinextras-common:${rootProject.mixin_extras_version}")) implementation(include("io.github.llamalad7:mixinextras-neoforge:${rootProject.mixin_extras_version}")) }