diff --git a/build.gradle b/build.gradle index b32e59231..9fe9cb17e 100644 --- a/build.gradle +++ b/build.gradle @@ -22,10 +22,8 @@ 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 + // The following line declares the mojmap mappings 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") } @@ -34,6 +32,13 @@ subprojects { 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}") + implementation("org.lwjgl:lwjgl-openxr:${rootProject.lwjgl_version}:natives-linux") + implementation("org.lwjgl:lwjgl-openxr:${rootProject.lwjgl_version}:natives-windows") + // for OSC tracker support implementation("com.illposed.osc:javaosc-core:0.9") } @@ -155,17 +160,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 6d0c4d7ab..a2060f954 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_vr/VRData.java b/common/src/main/java/org/vivecraft/client_vr/VRData.java index 12135538d..6aefe54a6 100644 --- a/common/src/main/java/org/vivecraft/client_vr/VRData.java +++ b/common/src/main/java/org/vivecraft/client_vr/VRData.java @@ -74,7 +74,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 c5fa9212a..1757c06db 100644 --- a/common/src/main/java/org/vivecraft/client_vr/VRState.java +++ b/common/src/main/java/org/vivecraft/client_vr/VRState.java @@ -12,6 +12,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; @@ -51,11 +52,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(); + dh.vr = switch (dh.vrSettings.stereoProviderPluginID) { + case OPENVR -> new MCOpenVR(instance, dh); + case OPENXR -> new MCOpenXR(instance, dh); + default -> 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())); @@ -66,7 +69,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 f551b8417..5b99d1586 100644 --- a/common/src/main/java/org/vivecraft/client_vr/VRTextureTarget.java +++ b/common/src/main/java/org/vivecraft/client_vr/VRTextureTarget.java @@ -1,7 +1,10 @@ package org.vivecraft.client_vr; import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.platform.TextureUtil; import com.mojang.blaze3d.systems.RenderSystem; +import org.lwjgl.opengl.GL30; import org.vivecraft.client.Xplat; import org.vivecraft.client.extensions.RenderTargetExtension; @@ -36,6 +39,31 @@ public VRTextureTarget( this.setClearColor(0, 0, 0, 0); } + public VRTextureTarget(String name, int width, int height, int colorId, int index) { + super(false); + this.name = name; + RenderSystem.assertOnRenderThreadOrInit(); + this.resize(width, height); + + // free the old one when setting a new one + if (this.colorTextureId != -1) { + TextureUtil.releaseTextureId(this.colorTextureId); + } + this.colorTextureId = colorId; + + GlStateManager._glBindFramebuffer(GL30.GL_FRAMEBUFFER, this.frameBufferId); + // unset the old GL_COLOR_ATTACHMENT0 + GlStateManager._glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL30.GL_TEXTURE_2D, 0, + 0); + GL30.glFramebufferTextureLayer(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, colorId, 0, index); + + // unbind the framebuffer + this.unbindRead(); + this.unbindWrite(); + + 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 013b68721..12591e9e1 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 @@ -548,7 +548,7 @@ public static Vec3 applyGUIModelView(RenderPass currentPass, Matrix4f poseMatrix 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 294b83ddf..f4965b978 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 36281af82..a2803c9db 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.ScrollWheelHandler; @@ -26,9 +27,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.AutoCalibration; @@ -71,11 +74,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(); @@ -131,6 +136,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; protected static final Vector3fc[] FBT_REFERENCE_POSITIONS = new Vector3fc[]{ new Vector3f(0F, 0.875F, 0F), // waist @@ -153,6 +161,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 @@ -327,12 +336,11 @@ 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()); @@ -350,18 +358,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; - } } /** @@ -633,6 +634,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); @@ -1376,7 +1380,138 @@ public List> getTrackers() { /** * 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); + /** {@link org.lwjgl.openvr.VR.k_ulInvalidInputValueHandle} and {@link org.lwjgl.system.MemoryUtil.NULL} are both 0 */ + if (action.isEnabled() && action.getLastOrigin() != 0L) { + 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); + + /** {@link org.lwjgl.openvr.VR.k_ulInvalidInputValueHandle} and {@link org.lwjgl.system.MemoryUtil.NULL} are both 0 */ + if (action.isEnabled() && action.getLastOrigin() != 0L) { + 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 @@ -1385,7 +1520,12 @@ public List> getTrackers() { protected abstract ControllerType findActiveBindingControllerType(KeyMapping keyMapping); /** - * polls VR events, and fetches new device poses and inputs + * polls and processes VR events + */ + public abstract void handleEvents(); + + /** + * fetches new device poses and inputs * * @param frameIndex index of the current VR frame. Some VR runtimes need that */ @@ -1451,6 +1591,12 @@ public boolean hasExtendedFBT() { */ 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 cc3df1c66..835b86aa0 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 @@ -47,15 +47,14 @@ 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; + protected boolean eyeFramebuffersCreated = false; + public RenderTarget framebufferMR; public RenderTarget framebufferUndistorted; public RenderTarget framebufferVrRender; @@ -144,6 +143,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 * @@ -497,7 +505,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(); @@ -644,34 +652,16 @@ public void setupRenderConfiguration() throws RenderConfigException, IOException destroyBuffers(); - if (this.LeftEyeTextureId == -1) { + if (!this.eyeFramebuffersCreated) { + VRSettings.LOGGER.info("Vivecraft: VR Provider supplied texture resolution: {} x {}", eyew, eyeh); this.createRenderTexture(eyew, eyeh); - if (this.LeftEyeTextureId == -1) { + if (!this.getLastError().isEmpty()) { 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.eyeFramebuffersCreated = true; } float resolutionScale = @@ -935,18 +925,6 @@ protected void 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; - } - if (this.mirrorFramebuffer != null) { this.mirrorFramebuffer.destroyBuffers(); this.mirrorFramebuffer = null; 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 76% 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 c3ff80a0d..b391ebe8a 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; @@ -65,14 +65,13 @@ 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 7117e3733..9c924a8ea 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; @@ -213,17 +212,18 @@ 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 c12dd18b5..b923123b8 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/nullvr/NullVR.java b/common/src/main/java/org/vivecraft/client_vr/provider/nullvr/NullVR.java index 1248914bc..572e9ca38 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 @@ -16,7 +16,7 @@ import org.vivecraft.client_vr.provider.DeviceSource; 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.render.MirrorNotification; import org.vivecraft.client_vr.settings.VRSettings; import org.vivecraft.common.network.FBTMode; @@ -108,7 +108,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(); @@ -120,6 +123,9 @@ public boolean init() { return this.initialized; } + @Override + public void handleEvents() {} + @Override public void poll(long frameIndex) { if (this.initialized) { @@ -249,6 +255,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 5806ba49b..90e0107ca 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; @@ -7,12 +8,20 @@ import net.minecraft.util.Tuple; import org.joml.Matrix4f; import org.lwjgl.opengl.GL11; +import org.vivecraft.client_vr.VRTextureTarget; import org.vivecraft.client_vr.provider.MCVR; import org.vivecraft.client_vr.provider.VRRenderer; +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 framebufferEyeLeft; + public RenderTarget framebufferEyeRight; + public NullVRStereoRenderer(MCVR vr) { super(vr); } @@ -35,25 +44,44 @@ protected Matrix4f getProjectionMatrix(int eyeType, float nearClip, float farCli } @Override - public void createRenderTexture(int lwidth, int lheight) { + public void createRenderTexture(int width, int height) { + int boundTextureId = GlStateManager._getInteger(GL11.GL_TEXTURE_BINDING_2D); + this.LeftEyeTextureId = GlStateManager._genTexture(); - int i = GlStateManager._getInteger(GL11.GL_TEXTURE_BINDING_2D); RenderSystem.bindTexture(this.LeftEyeTextureId); RenderSystem.texParameter(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); RenderSystem.texParameter(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); - GlStateManager._texImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, lwidth, lheight, 0, GL11.GL_RGBA, GL11.GL_INT, + GlStateManager._texImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, width, height, 0, GL11.GL_RGBA, GL11.GL_INT, null); - RenderSystem.bindTexture(i); this.RightEyeTextureId = GlStateManager._genTexture(); - i = GlStateManager._getInteger(GL11.GL_TEXTURE_BINDING_2D); RenderSystem.bindTexture(this.RightEyeTextureId); RenderSystem.texParameter(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); RenderSystem.texParameter(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); - GlStateManager._texImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, lwidth, lheight, 0, GL11.GL_RGBA, GL11.GL_INT, + GlStateManager._texImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, width, height, 0, GL11.GL_RGBA, GL11.GL_INT, null); - RenderSystem.bindTexture(i); + this.lastError = RenderHelper.checkGLError("create VR textures"); + + this.framebufferEyeLeft = new VRTextureTarget("L Eye", width, height, false, this.LeftEyeTextureId, true, false, + false); + + VRSettings.LOGGER.info("Vivecraft: {}", this.framebufferEyeLeft); + + String leftError = RenderHelper.checkGLError("Left Eye framebuffer setup"); + + this.framebufferEyeRight = new VRTextureTarget("R Eye", width, height, false, this.RightEyeTextureId, true, false, + false); + + VRSettings.LOGGER.info("Vivecraft: {}", this.framebufferEyeRight); + + String rightError = RenderHelper.checkGLError("Right Eye framebuffer setup"); + + if (this.lastError.isEmpty()) { + this.lastError = !leftError.isEmpty() ? leftError : rightError; + } + + RenderSystem.bindTexture(boundTextureId); } @Override @@ -64,14 +92,42 @@ public boolean providesStencilMask() { return false; } + @Override + public RenderTarget getLeftEyeTarget() { + return this.framebufferEyeLeft; + } + + @Override + public RenderTarget getRightEyeTarget() { + return this.framebufferEyeRight; + } + + + @Override + public float[] getStencilMask(RenderPass eye) { + return null; + } + @Override public String getName() { return "NullVR"; } @Override - protected void destroyBuffers() { + public void destroy() { super.destroyBuffers(); + super.destroy(); + + if (this.framebufferEyeLeft != null) { + this.framebufferEyeLeft.destroyBuffers(); + this.framebufferEyeLeft = null; + } + + if (this.framebufferEyeRight != null) { + this.framebufferEyeRight.destroyBuffers(); + this.framebufferEyeRight = 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 364678f66..fdca4b509 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 @@ -3,7 +3,6 @@ import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import com.mojang.blaze3d.platform.InputConstants; import com.sun.jna.NativeLibrary; import net.minecraft.Util; import net.minecraft.client.KeyMapping; @@ -17,7 +16,6 @@ import org.apache.commons.lang3.tuple.Triple; import org.joml.*; import org.lwjgl.Version; -import org.lwjgl.glfw.GLFW; import org.lwjgl.openvr.*; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryUtil; @@ -27,12 +25,12 @@ import org.vivecraft.client.utils.FileUtils; import org.vivecraft.client_vr.ClientDataHolderVR; import org.vivecraft.client_vr.VRState; -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.*; -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; @@ -104,7 +102,6 @@ public class MCOpenVR extends MCVR { // holds the handle to the devices other than the headset private final long[] deviceHandle = new long[MCVR.TRACKABLE_DEVICE_COUNT]; - private boolean inputInitialized; private final InputOriginInfo originInfo; private final InputPoseActionData poseData; private final InputDigitalActionData digital; @@ -112,7 +109,6 @@ public class MCOpenVR extends MCVR { private boolean paused = false; - private final Map trackpadSwipeSamplers = new HashMap<>(); private boolean triedToInit; private final Queue vrEvents = new LinkedList<>(); @@ -424,15 +420,21 @@ private void initializeOpenVR() throws RuntimeException { } @Override - public void poll(long frameIndex) { - if (!this.initialized) return; - - this.paused = VRSystem_ShouldApplicationPause(); + public void handleEvents() { Profiler.get().push("pollEvents"); this.pollVREvents(); Profiler.get().popPush("processEvents"); this.processVREvents(); - Profiler.get().popPush("updatePose/Vsync"); + Profiler.get().pop(); + } + + @Override + public void poll(long frameIndex) { + if (!this.initialized) return; + + this.paused = VRSystem_ShouldApplicationPause(); + + Profiler.get().push("updatePose/Vsync"); this.updatePose(); if (!this.dh.vrSettings.seated) { @@ -451,36 +453,6 @@ public void poll(long frameIndex) { Profiler.get().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 */ @@ -1192,109 +1164,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 */ @@ -1524,6 +1393,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) { Profiler.get().push("updateActionState"); @@ -1607,11 +1480,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 b5cff6da2..1aa30a1ba 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,10 +27,14 @@ 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 framebufferEyeLeft; + public RenderTarget framebufferEyeRight; - 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(); @@ -93,6 +99,7 @@ protected Matrix4f getProjectionMatrix(int eyeType, float nearClip, float farCli @Override public void createRenderTexture(int width, int height) { int boundTextureId = GlStateManager._getInteger(GL11C.GL_TEXTURE_BINDING_2D); + // generate left eye texture this.LeftEyeTextureId = GlStateManager._genTexture(); RenderSystem.bindTexture(this.LeftEyeTextureId); @@ -115,8 +122,31 @@ public void createRenderTexture(int width, int height) { this.openvr.texType1.eColorSpace(VR.EColorSpace_ColorSpace_Gamma); this.openvr.texType1.eType(VR.ETextureType_TextureType_OpenGL); - RenderSystem.bindTexture(boundTextureId); + VRSettings.LOGGER.info("Vivecraft: VR Provider supplied render texture IDs: {}, {}", this.LeftEyeTextureId, + this.RightEyeTextureId); + this.lastError = RenderHelper.checkGLError("create VR textures"); + + this.framebufferEyeLeft = new VRTextureTarget("L Eye", width, height, false, this.LeftEyeTextureId, true, false, + false); + + VRSettings.LOGGER.info("Vivecraft: {}", this.framebufferEyeLeft); + + String leftError = RenderHelper.checkGLError("Left Eye framebuffer setup"); + + this.framebufferEyeRight = new VRTextureTarget("R Eye", width, height, false, this.RightEyeTextureId, true, + false, + false); + + VRSettings.LOGGER.info("Vivecraft: {}", this.framebufferEyeRight); + + String rightError = RenderHelper.checkGLError("Right Eye framebuffer setup"); + + if (this.lastError.isEmpty()) { + this.lastError = !leftError.isEmpty() ? leftError : rightError; + } + + RenderSystem.bindTexture(boundTextureId); } @Override @@ -163,13 +193,43 @@ public boolean providesStencilMask() { } @Override + public RenderTarget getLeftEyeTarget() { + return this.framebufferEyeLeft; + } + + @Override + public RenderTarget getRightEyeTarget() { + return this.framebufferEyeRight; + } + + 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"; } @Override - protected void destroyBuffers() { - super.destroyBuffers(); + public void destroy() { + super.destroy(); + + this.hiddenMeshes[0].free(); + this.hiddenMeshes[1].free(); + + if (this.framebufferEyeLeft != null) { + this.framebufferEyeLeft.destroyBuffers(); + this.framebufferEyeLeft = null; + } + + if (this.framebufferEyeRight != null) { + this.framebufferEyeRight.destroyBuffers(); + this.framebufferEyeRight = null; + } if (this.LeftEyeTextureId > -1) { TextureUtil.releaseTextureId(this.LeftEyeTextureId); this.LeftEyeTextureId = -1; @@ -180,11 +240,4 @@ protected void destroyBuffers() { this.RightEyeTextureId = -1; } } - - @Override - public void destroy() { - super.destroy(); - this.hiddenMeshes[0].free(); - this.hiddenMeshes[1].free(); - } } 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..d763519f5 --- /dev/null +++ b/common/src/main/java/org/vivecraft/client_vr/provider/openxr/DeviceCompat.java @@ -0,0 +1,171 @@ +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(); + return NULL; + } + + @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; + return null; + } + + @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; + return null; + } + + @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; + return null; + } + } +} 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..f0ca6df34 --- /dev/null +++ b/common/src/main/java/org/vivecraft/client_vr/provider/openxr/MCOpenXR.java @@ -0,0 +1,1317 @@ +package org.vivecraft.client_vr.provider.openxr; + +import net.minecraft.client.KeyMapping; +import net.minecraft.client.Minecraft; +import net.minecraft.util.profiling.Profiler; +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.lwjgl.system.MemoryUtil; +import org.vivecraft.client.VivecraftVRMod; +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.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.FloatBuffer; +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 OpenXRHapticScheduler(); + } + + @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 : this.actionSetHandles.values()) { + error = XR10.xrDestroyActionSet(new XrActionSet(inputActionSet, this.instance)); + logError(error, "xrDestroyActionSet", ""); + } + if (this.swapchain != null) { + error = XR10.xrDestroySwapchain(this.swapchain); + logError(error, "xrDestroySwapchain", ""); + } + if (this.viewBuffer != null) { + this.viewBuffer.close(); + } + if (this.xrAppSpace != null) { + error = XR10.xrDestroySpace(this.xrAppSpace); + logError(error, "xrDestroySpace", "xrAppSpace"); + } + if (this.xrViewSpace != null) { + error = XR10.xrDestroySpace(this.xrViewSpace); + logError(error, "xrDestroySpace", "xrViewSpace"); + } + if (this.session != null) { + error = XR10.xrDestroySession(this.session); + logError(error, "xrDestroySession", ""); + } + if (this.instance != null) { + error = XR10.xrDestroyInstance(this.instance); + logError(error, "xrDestroyInstance", ""); + } + this.eventDataBuffer.close(); + } + + @Override + protected ControllerType findActiveBindingControllerType(KeyMapping keyMapping) { + if (!this.inputInitialized) { + return null; + } else { + long path = this.getInputAction(keyMapping).getLastOrigin(); + try (MemoryStack stack = MemoryStack.stackPush()) { + IntBuffer buf = stack.callocInt(1); + int error = XR10.xrPathToString(this.instance, path, buf, null); + logError(error, "xrPathToString", "get string length for", keyMapping.getName()); + + int size = buf.get(); + if (size <= 0) { + return null; + } + + buf = stack.callocInt(size); + ByteBuffer byteBuffer = stack.calloc(size); + error = XR10.xrPathToString(this.instance, path, buf, byteBuffer); + logError(error, "xrPathToString", "get string for", keyMapping.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 handleEvents() { + Profiler.get().push("events"); + this.pollVREvents(); + Profiler.get().pop(); + } + + @Override + public void poll(long frameIndex) { + if (this.initialized) { + + if (!this.dh.vrSettings.seated) { + Profiler.get().push("controllers"); + Profiler.get().push("gui"); + + if (this.mc.screen == null && this.dh.vrSettings.vrTouchHotbar) { + + if (this.dh.vrSettings.vrHudLockMode != VRSettings.HUDLock.HEAD && this.hudPopup) { + this.processHotbar(); + } + } + + Profiler.get().pop(); + } + Profiler.get().popPush("updatePose/Vsync"); + this.updatePose(); + Profiler.get().popPush("processInputs"); + this.processInputs(); + Profiler.get().popPush("hmdSampling"); + this.hmdSampling(); + Profiler.get().pop(); + } + } + + private void updatePose() { + if (this.mc == null) { + return; + } + + try (MemoryStack stack = MemoryStack.stackPush()) { + XrFrameState frameState = XrFrameState.calloc(stack).type(XR10.XR_TYPE_FRAME_STATE); + + int error = XR10.xrWaitFrame( + this.session, + XrFrameWaitInfo.calloc(stack).type(XR10.XR_TYPE_FRAME_WAIT_INFO), + frameState); + logError(error, "xrWaitFrame", ""); + + this.time = frameState.predictedDisplayTime(); + this.shouldRender = frameState.shouldRender(); + + error = XR10.xrBeginFrame( + this.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(), + this.xrAppSpace + ); + + error = XR10.xrLocateViews(this.session, viewLocateInfo, viewState, intBuf, this.viewBuffer); + logError(error, "xrLocateViews", ""); + + XrSpaceLocation space_location = XrSpaceLocation.calloc(stack).type(XR10.XR_TYPE_SPACE_LOCATION); + + // HMD pose + error = XR10.xrLocateSpace(this.xrViewSpace, this.xrAppSpace, this.time, space_location); + logError(error, "xrLocateSpace", "xrViewSpace"); + if (error >= 0) { + OpenXRUtil.openXRPoseToMarix(space_location.pose(), this.hmdPose); + this.headIsTracking = true; + } else { + this.headIsTracking = false; + this.hmdPose.identity(); + this.hmdPose.m31(1.6F); + } + + // Eye positions + OpenXRUtil.openXRPoseToMarix(this.viewBuffer.get(0).pose(), this.hmdPoseLeftEye); + OpenXRUtil.openXRPoseToMarix(this.viewBuffer.get(1).pose(), this.hmdPoseRightEye); + + if (this.inputInitialized) { + Profiler.get().push("updateActionState"); + + if (this.updateActiveActionSets()) { + XrActionsSyncInfo syncInfo = XrActionsSyncInfo.calloc(stack) + .type(XR10.XR_TYPE_ACTIONS_SYNC_INFO) + .activeActionSets(this.activeActionSetsBuffer); + error = XR10.xrSyncActions(this.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), + this.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); + + Profiler.get().pop(); + + // reverse + if (this.dh.vrSettings.reverseHands) { + XrSpace temp = this.gripSpace[RIGHT_CONTROLLER]; + this.gripSpace[RIGHT_CONTROLLER] = this.gripSpace[LEFT_CONTROLLER]; + this.gripSpace[LEFT_CONTROLLER] = temp; + temp = this.aimSpace[RIGHT_CONTROLLER]; + this.aimSpace[RIGHT_CONTROLLER] = this.aimSpace[LEFT_CONTROLLER]; + this.aimSpace[LEFT_CONTROLLER] = temp; + } + + // Controller aim and grip poses + error = XR10.xrLocateSpace(this.gripSpace[RIGHT_CONTROLLER], this.xrAppSpace, this.time, + space_location); + logError(error, "xrLocateSpace", "gripSpace[0]"); + if (error >= 0) { + OpenXRUtil.openXRPoseToMarix(space_location.pose().orientation(), + this.handRotation[RIGHT_CONTROLLER]); + } + + error = XR10.xrLocateSpace(this.gripSpace[LEFT_CONTROLLER], this.xrAppSpace, this.time, space_location); + logError(error, "xrLocateSpace", "gripSpace[1]"); + if (error >= 0) { + OpenXRUtil.openXRPoseToMarix(space_location.pose().orientation(), + this.handRotation[LEFT_CONTROLLER]); + } + + error = XR10.xrLocateSpace(this.aimSpace[RIGHT_CONTROLLER], this.xrAppSpace, this.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(this.aimSpace[LEFT_CONTROLLER], this.xrAppSpace, this.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(this.actionSetHandles.get(action.actionSet), this.instance))); + XrActionStateBoolean state = XrActionStateBoolean.calloc(stack).type(XR10.XR_TYPE_ACTION_STATE_BOOLEAN); + int error = XR10.xrGetActionStateBoolean(this.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(this.actionSetHandles.get(action.actionSet), this.instance))); + XrActionStateFloat state = XrActionStateFloat.calloc(stack).type(XR10.XR_TYPE_ACTION_STATE_FLOAT); + int error = XR10.xrGetActionStateFloat(this.session, info, state); + logError(error, "xrGetActionStateFloat", action.name); + + action.analogData[i].deltaX = state.currentState() - action.analogData[i].x; + 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(this.actionSetHandles.get(action.actionSet), this.instance))); + XrActionStateVector2f state = XrActionStateVector2f.calloc(stack).type(XR10.XR_TYPE_ACTION_STATE_VECTOR2F); + int error = XR10.xrGetActionStateVector2f(this.session, info, state); + logError(error, "xrGetActionStateVector2f", action.name); + + action.analogData[i].deltaX = state.currentState().x() - action.analogData[i].x; + action.analogData[i].deltaY = state.currentState().y() - action.analogData[i].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(this.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) { + this.activeActionSetsBuffer = XrActiveActionSet.calloc(arraylist.size()); + } else if (this.activeActionSetsBuffer.capacity() != arraylist.size()) { + this.activeActionSetsBuffer.close(); + this.activeActionSetsBuffer = XrActiveActionSet.calloc(arraylist.size()); + } + + for (int i = 0; i < arraylist.size(); ++i) { + VRInputActionSet vrinputactionset = arraylist.get(i); + this.activeActionSetsBuffer.get(i) + .set(new XrActionSet(this.getActionSetHandle(vrinputactionset), this.instance), NULL); + } + + return !arraylist.isEmpty(); + } + + long getActionSetHandle(VRInputActionSet actionSet) { + return this.actionSetHandles.get(actionSet); + } + + private void pollVREvents() { + while (true) { + this.eventDataBuffer.clear(); + this.eventDataBuffer.type(XR10.XR_TYPE_EVENT_DATA_BUFFER); + int error = XR10.xrPollEvent(this.instance, this.eventDataBuffer); + logError(error, "xrPollEvent", ""); + if (error != XR10.XR_SUCCESS) { + break; + } + XrEventDataBaseHeader event = XrEventDataBaseHeader.create(this.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(this.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(this.session); + logError(error, "xrEndSession", "XR_SESSION_STATE_STOPPING"); + + if (ClientDataHolderVR.getInstance().vrSettings.closeWithRuntime) { + VRSettings.LOGGER.info("Vivecraft: OpenXR stopped, closing the game with it"); + this.mc.stop(); + } else { + VRSettings.LOGGER.info("Vivecraft: OpenXR stopped, disabling VR"); + VRState.VR_ENABLED = !VRState.VR_ENABLED; + ClientDataHolderVR.getInstance().vrSettings.vrEnabled = VRState.VR_ENABLED; + ClientDataHolderVR.getInstance().vrSettings.saveOptions(); + } + } + 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(this.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 { + this.tried = true; + this.mc = Minecraft.getInstance(); + try { + this.initializeOpenXRInstance(); + this.initializeOpenXRSession(); + this.initializeOpenXRSpace(); + this.initializeOpenXRSwapChain(); + this.initInputAndApplication(); + } catch (Exception e) { + VRSettings.LOGGER.error("Vivecraft: OpenXR init failed", e); + this.initSuccess = false; + this.initStatus = e.getLocalizedMessage(); + return false; + } + + // TODO Seated when no controllers + + VRSettings.LOGGER.info("Vivecraft: 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()) { + this.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 = this.device.getGraphicsExtension(); + boolean missingGraphics = true; + PointerBuffer extensions = stack.callocPointer(5); + 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 (extensionName.equals( + BDControllerInteraction.XR_BD_CONTROLLER_INTERACTION_EXTENSION_NAME)) + { + extensions.put(memAddress(stackUTF8( + BDControllerInteraction.XR_BD_CONTROLLER_INTERACTION_EXTENSION_NAME))); + } + if (extensionName.equals( + FBDisplayRefreshRate.XR_FB_DISPLAY_REFRESH_RATE_EXTENSION_NAME)) + { + extensions.put(memAddress(stackUTF8( + FBDisplayRefreshRate.XR_FB_DISPLAY_REFRESH_RATE_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(this.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); + } + this.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(this.instance, system, longBuffer); + logError(error, "xrGetSystem", ""); + this.systemID = longBuffer.get(0); + + if (this.systemID == 0) { + throw new RuntimeException("No compatible headset detected"); + } + + XrSystemProperties systemProperties = XrSystemProperties.calloc(stack).type(XR10.XR_TYPE_SYSTEM_PROPERTIES); + error = XR10.xrGetSystemProperties(this.instance, this.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: {}", this.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(this.device.checkGraphics(stack, this.instance, this.systemID).address()); + info.createFlags(0); + info.systemId(this.systemID); + + PointerBuffer sessionPtr = stack.callocPointer(1); + error = XR10.xrCreateSession(this.instance, info, sessionPtr); + logError(error, "xrCreateSession", ""); + + this.session = new XrSession(sessionPtr.get(0), this.instance); + + while (!this.isActive) { + VRSettings.LOGGER.info("Vivecraft: waiting for OpenXR session to start"); + 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(this.session, referenceSpaceCreateInfo, pp); + this.xrAppSpace = new XrSpace(pp.get(0), this.session); + logError(error, "xrCreateReferenceSpace", "XR_REFERENCE_SPACE_TYPE_STAGE"); + + referenceSpaceCreateInfo.referenceSpaceType(XR10.XR_REFERENCE_SPACE_TYPE_VIEW); + error = XR10.xrCreateReferenceSpace(this.session, referenceSpaceCreateInfo, pp); + logError(error, "xrCreateReferenceSpace", "XR_REFERENCE_SPACE_TYPE_VIEW"); + this.xrViewSpace = new XrSpace(pp.get(0), this.session); + } + } + + private void initializeOpenXRSwapChain() { + try (MemoryStack stack = stackPush()) { + // Check amount of views + IntBuffer intBuf = stack.callocInt(1); + int error = XR10.xrEnumerateViewConfigurationViews(this.instance, this.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(this.instance, this.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(this.session, intBuf, null); + logError(error, "xrEnumerateSwapchainFormats", "get count"); + + // Get swapchain formats + LongBuffer swapchainFormats = stack.callocLong(intBuf.get(0)); + error = XR10.xrEnumerateSwapchainFormats(this.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(this.session, swapchainCreateInfo, handlePointer); + logError(error, "xrCreateSwapchain", "format: " + chosenFormat); + this.swapchain = new XrSwapchain(handlePointer.get(0), this.session); + this.width = swapchainCreateInfo.width(); + this.height = swapchainCreateInfo.height(); + } + } + + private void initDisplayRefreshRate() { + if (this.session.getCapabilities().XR_FB_display_refresh_rate) { + try (MemoryStack stack = MemoryStack.stackPush()) { + IntBuffer refreshRateCount = stack.callocInt(1); + FBDisplayRefreshRate.xrEnumerateDisplayRefreshRatesFB(this.session, refreshRateCount, null); + FloatBuffer refreshRateBuffer = stack.callocFloat(refreshRateCount.get(0)); + FBDisplayRefreshRate.xrEnumerateDisplayRefreshRatesFB(this.session, refreshRateCount, refreshRateBuffer); + refreshRateBuffer.rewind(); + FBDisplayRefreshRate.xrRequestDisplayRefreshRateFB(this.session, refreshRateBuffer.get(refreshRateCount.get(0) -1)); + } + } + } + + /** + * Creates an array of XrStructs with their types preset to {@code 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; + this.initDisplayRefreshRate(); + } + + @Override + public Matrix4f getControllerComponentTransform(int controllerIndex, String componentName) { + return new Matrix4f(); + } + + @Override + public boolean hasCameraTracker() { + return false; + } + + @Override + public List getOrigins(VRInputAction action) { + 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(action.handle, + new XrActionSet(this.actionSetHandles.get(action.actionSet), this.instance))); + IntBuffer buf = stack.callocInt(1); + int error = XR10.xrEnumerateBoundSourcesForAction(this.session, info, buf, null); + logError(error, "xrEnumerateBoundSourcesForAction", action.name); + + int size = buf.get(); + if (size <= 0) { + return List.of(0L); + } + + buf = stack.callocInt(size); + LongBuffer longbuf = stack.callocLong(size); + error = XR10.xrEnumerateBoundSourcesForAction(this.session, info, buf, longbuf); + logError(error, "xrEnumerateBoundSourcesForAction", action.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 origin) { + 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(origin); + info.whichComponents(XR10.XR_INPUT_SOURCE_LOCALIZED_NAME_COMPONENT_BIT); + + IntBuffer buf = stack.callocInt(1); + int error = XR10.xrGetInputSourceLocalizedName(this.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(this.session, info, buf, byteBuffer); + logError(error, "xrGetInputSourceLocalizedName", "get String"); + return MemoryUtil.memUTF8(MemoryUtil.memAddress(buf)); + } + } + + @Override + public VRRenderer createVRRenderer() { + return new OpenXRStereoRenderer(this); + } + + @Override + public boolean isActive() { + return this.isActive; + } + + @Override + public ControllerType getOriginControllerType(long inputValueHandle) { + if (inputValueHandle == this.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(this.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), this.instance), BOTH_HANDS); + vrinputaction.setHandle(action); + } + + setupControllers(); + + XrActionSet actionSet = new XrActionSet(this.actionSetHandles.get(VRInputActionSet.GLOBAL), this.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), this.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(this.actionSetHandles.get(binding.actionSet), this.instance)), + getPath(pair.getRight()) + ); + } + + //TODO make this also changeable? + XrActionSet actionSet = new XrActionSet(this.actionSetHandles.get(VRInputActionSet.GLOBAL), + this.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(this.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(this.actionSetHandles.values().stream().mapToLong(value -> value).toArray())); + + error = XR10.xrAttachSessionActionSets(this.session, attach_info); + logError(error, "xrAttachSessionActionSets", ""); + + XrActionSet actionSet = new XrActionSet(this.actionSetHandles.get(VRInputActionSet.GLOBAL), this.instance); + XrActionSpaceCreateInfo actionSpace = XrActionSpaceCreateInfo.calloc(stack); + actionSpace.type(XR10.XR_TYPE_ACTION_SPACE_CREATE_INFO); + actionSpace.next(NULL); + actionSpace.action(new XrAction(this.grip[RIGHT_CONTROLLER], actionSet)); + actionSpace.subactionPath(getPath("/user/hand/right")); + actionSpace.poseInActionSpace(POSE_IDENTITY); + PointerBuffer pp = stackCallocPointer(1); + error = XR10.xrCreateActionSpace(this.session, actionSpace, pp); + logError(error, "xrCreateActionSpace", "grip: /user/hand/right"); + this.gripSpace[RIGHT_CONTROLLER] = new XrSpace(pp.get(0), this.session); + + actionSpace.action(new XrAction(this.grip[LEFT_CONTROLLER], actionSet)); + actionSpace.subactionPath(getPath("/user/hand/left")); + error = XR10.xrCreateActionSpace(this.session, actionSpace, pp); + logError(error, "xrCreateActionSpace", "grip: /user/hand/left"); + this.gripSpace[LEFT_CONTROLLER] = new XrSpace(pp.get(0), this.session); + + actionSpace.action(new XrAction(this.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), this.session); + + actionSpace.action(new XrAction(this.aim[LEFT_CONTROLLER], actionSet)); + actionSpace.subactionPath(getPath("/user/hand/left")); + error = XR10.xrCreateActionSpace(this.session, actionSpace, pp); + logError(error, "xrCreateActionSpace", "aim: /user/hand/left"); + this.aimSpace[LEFT_CONTROLLER] = new XrSpace(pp.get(0), this.session); + } + } + + public long getPath(String pathString) { + return this.paths.computeIfAbsent(pathString, s -> { + try (MemoryStack ignored = stackPush()) { + LongBuffer buf = stackCallocLong(1); + int error = XR10.xrStringToPath(this.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(this.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/OpenXRHapticScheduler.java b/common/src/main/java/org/vivecraft/client_vr/provider/openxr/OpenXRHapticScheduler.java new file mode 100644 index 000000000..a0022a671 --- /dev/null +++ b/common/src/main/java/org/vivecraft/client_vr/provider/openxr/OpenXRHapticScheduler.java @@ -0,0 +1,49 @@ +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 OpenXRHapticScheduler 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..13b89a698 --- /dev/null +++ b/common/src/main/java/org/vivecraft/client_vr/provider/openxr/OpenXRStereoRenderer.java @@ -0,0 +1,206 @@ +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.openxr.*; +import org.lwjgl.system.MemoryStack; +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 boolean recalculateProjectionMatrix = true; + + + public OpenXRStereoRenderer(MCOpenXR vr) { + super(vr); + this.openxr = vr; + } + + @Override + public void createRenderTexture(int width, int height) { + 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(this.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(this.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); + this.leftFramebuffers[i] = new VRTextureTarget("L Eye " + i, width, height, openxrImage.image(), 0); + String leftError = RenderHelper.checkGLError("Left Eye " + i + " framebuffer setup"); + this.rightFramebuffers[i] = new VRTextureTarget("R Eye " + i, width, height, openxrImage.image(), 1); + String rightError = RenderHelper.checkGLError("Right Eye " + i + " framebuffer setup"); + + if (this.lastError.isEmpty()) { + this.lastError = !leftError.isEmpty() ? leftError : rightError; + } + } + } + } + + @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( + this.openxr.swapchain, + XrSwapchainImageAcquireInfo.calloc(stack).type(XR10.XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO), + intBuf2); + this.openxr.logError(error, "xrAcquireSwapchainImage", ""); + + error = XR10.xrWaitSwapchainImage(this.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++) { + XrSwapchainSubImage subImage = this.projectionLayerViews.get(viewIndex) + .type(XR10.XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW) + .pose(this.openxr.viewBuffer.get(viewIndex).pose()) + .fov(this.openxr.viewBuffer.get(viewIndex).fov()) + .subImage(); + subImage.swapchain(this.openxr.swapchain); + subImage.imageRect().offset().set(0, 0); + subImage.imageRect().extent().set(this.openxr.width, this.openxr.height); + subImage.imageArrayIndex(viewIndex); + } + this.recalculateProjectionMatrix = true; + } + } + + /** + * no caching for openxr + * the projection matrix may change every frame, so recalculate it once per frame for up to date info + */ + @Override + public Matrix4f getCachedProjectionMatrix(int eyeType, float nearClip, float farClip) { + if (this.recalculateProjectionMatrix) { + this.eyeProj[0] = this.getProjectionMatrix(0, nearClip, farClip); + this.eyeProj[1] = this.getProjectionMatrix(1, nearClip, farClip); + this.recalculateProjectionMatrix = false; + } + return this.eyeProj[eyeType]; + } + + @Override + public Matrix4f getProjectionMatrix(int eyeType, float nearClip, float farClip) { + XrFovf fov = this.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 { + try (MemoryStack stack = MemoryStack.stackPush()) { + PointerBuffer layers = stack.callocPointer(1); + int error; + + error = XR10.xrReleaseSwapchainImage( + this.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(this.openxr.xrAppSpace) + .views(this.projectionLayerViews); + + layers.put(compositionLayerProjection); + + layers.flip(); + + error = XR10.xrEndFrame( + this.openxr.session, + XrFrameEndInfo.calloc(stack) + .type(XR10.XR_TYPE_FRAME_END_INFO) + .displayTime(this.openxr.time) + .environmentBlendMode(XR10.XR_ENVIRONMENT_BLEND_MODE_OPAQUE) + .layers(layers)); + this.openxr.logError(error, "xrEndFrame", ""); + + this.projectionLayerViews.close(); + } + } + + @Override + public boolean providesStencilMask() { + return false; + } + + @Override + public RenderTarget getLeftEyeTarget() { + return this.leftFramebuffers[this.swapIndex]; + } + + @Override + public RenderTarget getRightEyeTarget() { + return this.rightFramebuffers[this.swapIndex]; + } + + @Override + public String getName() { + return "OpenXR"; + } + + @Override + public Tuple getRenderTextureSizes() { + return new Tuple<>(this.openxr.width, this.openxr.height); + } + + @Override + public void destroy() { + super.destroy(); + + if (this.leftFramebuffers != null) { + for (VRTextureTarget leftFramebuffer : this.leftFramebuffers) { + leftFramebuffer.destroyBuffers(); + } + this.leftFramebuffers = null; + } + + if (this.rightFramebuffers != null) { + for (VRTextureTarget rightFramebuffer : this.rightFramebuffers) { + rightFramebuffer.destroyBuffers(); + } + this.rightFramebuffers = null; + } + } +} 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..eaa865b2a --- /dev/null +++ b/common/src/main/java/org/vivecraft/client_vr/provider/openxr/OpenXRUtil.java @@ -0,0 +1,20 @@ +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..76ce8a5f6 --- /dev/null +++ b/common/src/main/java/org/vivecraft/client_vr/provider/openxr/XRBindings.java @@ -0,0 +1,281 @@ +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().session.getCapabilities().XR_HTC_vive_cosmos_controller_interaction) { + set.add("/interaction_profiles/htc/vive_cosmos_controller"); + } + + if (MCOpenXR.get().session.getCapabilities().XR_BD_controller_interaction) { + set.add("/interaction_profiles/bytedance/pico4_controller"); + set.add("/interaction_profiles/bytedance/pico_neo3_controller"); + } + + set.add("/interaction_profiles/khr/simple_controller"); + set.add("/interaction_profiles/oculus/touch_controller"); + set.add("/interaction_profiles/htc/vive_controller"); + set.add("/interaction_profiles/valve/index_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/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")); + + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.vrInteract", "/user/hand/left/input/squeeze")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.vrInteract", "/user/hand/right/input/squeeze")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.vrInteract", "/user/hand/left/input/trigger")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.vrInteract", "/user/hand/right/input/trigger")); + + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.climbeyGrab", "/user/hand/left/input/squeeze")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.climbeyGrab", "/user/hand/right/input/squeeze")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.climbeyGrab", "/user/hand/left/input/trigger")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.climbeyGrab", "/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/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")); + + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.vrInteract", "/user/hand/left/input/squeeze/click")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.vrInteract", "/user/hand/right/input/squeeze/click")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.vrInteract", "/user/hand/left/input/trigger/click")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.vrInteract", "/user/hand/right/input/trigger/click")); + + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.climbeyGrab", "/user/hand/left/input/squeeze/click")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.climbeyGrab", "/user/hand/right/input/squeeze/click")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.climbeyGrab", "/user/hand/left/input/trigger/click")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.climbeyGrab", "/user/hand/right/input/trigger/click")); + + 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/key.inventory", "/user/hand/left/input/x/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/a/click")); + set.add(new MutablePair<>("/actions/gui/in/vivecraft.key.guiScrollAxis", "/user/hand/right/input/thumbstick")); + + 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/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/thumbstick")); + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.rotateAxis", "/user/hand/right/input/thumbstick")); + 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/shoulder/click")); + set.add(new MutablePair<>("/actions/ingame/in/key.sneak", "/user/hand/right/input/shoulder/click")); + + 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/contextual/in/vivecraft.key.vrInteract", "/user/hand/left/input/squeeze/click")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.vrInteract", "/user/hand/right/input/squeeze/click")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.vrInteract", "/user/hand/left/input/trigger/click")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.vrInteract", "/user/hand/right/input/trigger/click")); + + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.climbeyGrab", "/user/hand/left/input/squeeze/click")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.climbeyGrab", "/user/hand/right/input/squeeze/click")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.climbeyGrab", "/user/hand/left/input/trigger/click")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.climbeyGrab", "/user/hand/right/input/trigger/click")); + return set; + } + + private static HashSet> picoBindings() { + 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/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/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/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/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.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/click")); + set.add(new MutablePair<>("/actions/keyboard/in/vivecraft.key.keyboardClick", "/user/hand/right/input/trigger/click")); + + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.vrInteract", "/user/hand/left/input/squeeze")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.vrInteract", "/user/hand/right/input/squeeze")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.vrInteract", "/user/hand/left/input/trigger/click")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.vrInteract", "/user/hand/right/input/trigger/click")); + + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.climbeyGrab", "/user/hand/left/input/squeeze")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.climbeyGrab", "/user/hand/right/input/squeeze")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.climbeyGrab", "/user/hand/left/input/trigger/click")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.climbeyGrab", "/user/hand/right/input/trigger/click")); + return set; + } + + private static HashSet> indexBindings() { + HashSet> set = new HashSet<>(); + + set.add(new MutablePair<>("/actions/global/in/vivecraft.key.ingameMenuButton", "/user/hand/left/input/b/click")); + set.add(new MutablePair<>("/actions/global/in/key.inventory", "/user/hand/left/input/a/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/click")); + set.add(new MutablePair<>("/actions/gui/in/vivecraft.key.guiRightClick", "/user/hand/right/input/trackpad")); + set.add(new MutablePair<>("/actions/gui/in/vivecraft.key.guiScrollAxis", "/user/hand/right/input/thumbstick/y")); + 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")); + 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/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/trackpad")); + 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/a/click")); + 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/click")); + set.add(new MutablePair<>("/actions/keyboard/in/vivecraft.key.keyboardClick", "/user/hand/right/input/trigger/click")); + + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.vrInteract", "/user/hand/left/input/squeeze")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.vrInteract", "/user/hand/right/input/squeeze")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.vrInteract", "/user/hand/left/input/trigger/click")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.vrInteract", "/user/hand/right/input/trigger/click")); + + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.climbeyGrab", "/user/hand/left/input/squeeze")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.climbeyGrab", "/user/hand/right/input/squeeze")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.climbeyGrab", "/user/hand/left/input/trigger/click")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.climbeyGrab", "/user/hand/right/input/trigger/click")); + return set; + } + + private static HashSet> defaultBindings() { + 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/gui/in/vivecraft.key.guiLeftClick", "/user/hand/right/input/select/click")); + + set.add(new MutablePair<>("/actions/ingame/in/key.attack", "/user/hand/right/input/select/click")); + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.teleport", "/user/hand/left/input/select/click")); + set.add(new MutablePair<>("/actions/ingame/in/vivecraft.key.teleportFallback", "/user/hand/left/input/select/click")); + + set.add(new MutablePair<>("/actions/keyboard/in/vivecraft.key.keyboardClick", "/user/hand/left/input/select/click")); + set.add(new MutablePair<>("/actions/keyboard/in/vivecraft.key.keyboardClick", "/user/hand/right/input/select/click")); + + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.vrInteract", "/user/hand/left/input/select/click")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.vrInteract", "/user/hand/right/input/select/click")); + + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.climbeyGrab", "/user/hand/left/input/select/click")); + set.add(new MutablePair<>("/actions/contextual/in/vivecraft.key.climbeyGrab", "/user/hand/right/input/select/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/valve/index_controller" -> { + return indexBindings(); + } + case "/interaction_profiles/oculus/touch_controller" -> { + return quest2Bindings(); + } + case "/interaction_profiles/bytedance/pico4_controller", + "/interaction_profiles/bytedance/pico_neo3_controller" -> { + return picoBindings(); + } + default -> { + return defaultBindings(); + } + } + } +} diff --git a/common/src/main/java/org/vivecraft/client_vr/render/VRShaders.java b/common/src/main/java/org/vivecraft/client_vr/render/VRShaders.java index 79cf11e3b..98c5af94f 100644 --- a/common/src/main/java/org/vivecraft/client_vr/render/VRShaders.java +++ b/common/src/main/java/org/vivecraft/client_vr/render/VRShaders.java @@ -44,11 +44,6 @@ public class VRShaders { public static AbstractUniform POST_PROCESSING_OVERLAY_BLACK_ALPHA_UNIFORM; public static AbstractUniform POST_PROCESSING_OVERLAY_EYE_UNIFORM; - // blit shader - public static ShaderProgram BLIT_VR_SHADER = new ShaderProgram( - ResourceLocation.fromNamespaceAndPath("vivecraft", "core/blit_vr"), - DefaultVertexFormat.POSITION_TEX, ShaderDefines.EMPTY); - // end portal shaders public static ShaderProgram RENDERTYPE_END_PORTAL_VR_SHADER = new ShaderProgram( ResourceLocation.fromNamespaceAndPath("vivecraft", "core/rendertype_end_portal_vr"), 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 a19060f88..253c81978 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 @@ -1,6 +1,7 @@ package org.vivecraft.client_vr.render.helpers; import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.*; import net.minecraft.Util; @@ -16,6 +17,8 @@ import org.jetbrains.annotations.NotNull; import org.joml.Matrix4f; import org.joml.Vector3f; +import org.lwjgl.opengl.GL11C; +import org.lwjgl.opengl.GL30C; import org.lwjgl.opengl.GL43; import org.vivecraft.client_vr.ClientDataHolderVR; import org.vivecraft.client_vr.extensions.GameRendererExtension; @@ -88,7 +91,9 @@ private static void drawFullscreenQuad(VertexFormat format) { throw new IllegalStateException("Unexpected vertex format " + format); } + RenderSystem.disableDepthTest(); BufferUploader.draw(builder.buildOrThrow()); + RenderSystem.enableDepthTest(); } /** @@ -255,25 +260,25 @@ 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 = MC.mainRenderTarget.width / 2; int screenHeight = MC.mainRenderTarget.height; if (leftEye != null) { - ShaderHelper.blitToScreen(leftEye, 0, screenWidth, screenHeight, 0, 0.0F, 0.0F, false); + blitFramebuffer(leftEye, 0, 0, screenWidth, screenHeight); } if (rightEye != null) { - ShaderHelper.blitToScreen(rightEye, screenWidth, screenWidth, screenHeight, 0, 0.0F, 0.0F, false); + blitFramebuffer(rightEye, screenWidth, 0, MC.mainRenderTarget.width, screenHeight); } } else { // general single buffer case 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()) @@ -290,11 +295,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; @@ -305,9 +310,7 @@ public static void drawMirror() { // source = DataHolder.getInstance().vrRenderer.telescopeFramebufferR; // if (source != null) { - ShaderHelper.blitToScreen(source, - 0, MC.mainRenderTarget.width, - MC.mainRenderTarget.height, 0, + blitFramebufferCrop(source, 0, 0, MC.mainRenderTarget.width, MC.mainRenderTarget.height, xCrop, yCrop, keepAspect); } } @@ -362,6 +365,12 @@ public static void doMixedRealityMirror() { mixedRealityShader.bindSampler("thirdPersonDepth", DATA_HOLDER.vrRenderer.framebufferMR.getDepthTextureId()); + mixedRealityShader.apply(); + + drawFullscreenQuad(VRShaders.MIXED_REALITY_SHADER.vertexFormat()); + + mixedRealityShader.clear(); + if (DATA_HOLDER.vrSettings.mixedRealityUnityLike) { RenderTarget source; if (DATA_HOLDER.vrSettings.displayMirrorUseScreenshotCamera && DATA_HOLDER.cameraTracker.isVisible()) { @@ -370,19 +379,14 @@ 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(); } } - mixedRealityShader.bindSampler("firstPersonColor", source.getColorTextureId()); + blitFramebuffer(source, MC.mainRenderTarget.width / 2, 0, + MC.mainRenderTarget.width, MC.mainRenderTarget.height / 2); } - - mixedRealityShader.apply(); - - drawFullscreenQuad(VRShaders.MIXED_REALITY_SHADER.vertexFormat()); - - mixedRealityShader.clear(); } /** @@ -436,71 +440,66 @@ public static void doFSAA(RenderTarget source, RenderTarget firstPass, RenderTar } /** - * blits the given {@code source} RenderTarget to the screen/bound buffer
+ * blits the given {@code source} RenderTarget to the bound framebuffer
* the {@code source} is drawn to the rectangle at {@code left},{@code top} with a size of {@code width},{@code height}
* if {@code xCropFactor} or {@code yCropFactor} are non 0 the {@code source} gets zoomed in - * - * @param source RenderTarget to draw to the screen - * @param left left edge of the target area - * @param width width of the target area - * @param height height of the target area - * @param top top edge of the target area + * @param source RenderTarget to draw to the screen + * @param left left edge of the target area + * @param top top edge of the target area + * @param right right edge width of the target area + * @param bottom bottom edge of the target area * @param xCropFactor vertical crop factor for the {@code source} * @param yCropFactor horizontal crop factor for the {@code source} - * @param keepAspect keeps the aspect ratio in takt when cropping the buffer + * @param keepAspect keeps the aspect ratio in takt when cropping the buffer */ - public static void blitToScreen( - RenderTarget source, int left, int width, int height, int top, float xCropFactor, float yCropFactor, - boolean keepAspect) + private static void blitFramebufferCrop( + RenderTarget source, int left, int top, int right, int bottom, + float xCropFactor, float yCropFactor, boolean keepAspect) { - RenderSystem.assertOnRenderThread(); - RenderSystem.colorMask(true, true, true, false); - RenderSystem.disableDepthTest(); - RenderSystem.depthMask(false); - RenderSystem.viewport(left, top, width, height); - RenderSystem.disableBlend(); - - float drawAspect = (float) width / (float) height; - float bufferAspect = (float) source.viewWidth / (float) source.viewHeight; - - float xMin = xCropFactor; - float yMin = yCropFactor; - float xMax = 1.0F - xCropFactor; - float yMax = 1.0F - yCropFactor; - if (keepAspect) { + float drawAspect = (float) MC.mainRenderTarget.width / (float) MC.mainRenderTarget.height; + float bufferAspect = (float) source.viewWidth / (float) source.viewHeight; if (drawAspect > bufferAspect) { // destination is wider than the buffer float heightAspect = (bufferAspect / drawAspect) * (0.5F - yCropFactor); - yMin = 0.5F - heightAspect; - yMax = 0.5F + heightAspect; + yCropFactor = 0.5F - heightAspect; } else { // destination is taller than the buffer float widthAspect = (drawAspect / bufferAspect) * (0.5F - xCropFactor); - xMin = 0.5F - widthAspect; - xMax = 0.5F + widthAspect; + xCropFactor = 0.5F - widthAspect; } } - CompiledShaderProgram blitShader = Objects.requireNonNull( - RenderSystem.setShader(VRShaders.BLIT_VR_SHADER), "Vivecraft blit shader not loaded"); - blitShader.bindSampler("DiffuseSampler", source.getColorTextureId()); - - blitShader.apply(); - - BufferBuilder bufferbuilder = RenderSystem.renderThreadTesselator() - .begin(VertexFormat.Mode.QUADS, VRShaders.BLIT_VR_SHADER.vertexFormat()); - - bufferbuilder.addVertex(-1.0F, -1.0F, 0.0F).setUv(xMin, yMin); - bufferbuilder.addVertex(1.0F, -1.0F, 0.0F).setUv(xMax, yMin); - bufferbuilder.addVertex(1.0F, 1.0F, 0.0F).setUv(xMax, yMax); - bufferbuilder.addVertex(-1.0F, 1.0F, 0.0F).setUv(xMin, yMax); - BufferUploader.draw(bufferbuilder.buildOrThrow()); - blitShader.clear(); - - RenderSystem.depthMask(true); - RenderSystem.colorMask(true, true, true, true); + int xMin = (int) (xCropFactor * source.width); + int yMin = (int) (yCropFactor * source.height); + int xMax = source.width - xMin; + int yMax = source.height - yMin; + + GlStateManager._glBindFramebuffer(GL30C.GL_READ_FRAMEBUFFER, source.frameBufferId); + GlStateManager._glBlitFrameBuffer( + xMin, yMin, xMax, yMax, + left, top, right, bottom, + GL11C.GL_COLOR_BUFFER_BIT, GL11C.GL_LINEAR); + GlStateManager._glBindFramebuffer(GL30C.GL_READ_FRAMEBUFFER, 0); + } + /** + * blits the given {@code source} RenderTarget to the bound framebuffer + * @param source RenderTarget to draw to the screen + * @param left left edge of the target area + * @param top top edge of the target area + * @param right right edge width of the target area + * @param bottom bottom edge of the target area + */ + private static void blitFramebuffer( + RenderTarget source, int left, int top, int right, int bottom) + { + GlStateManager._glBindFramebuffer(GL30C.GL_READ_FRAMEBUFFER, source.frameBufferId); + GlStateManager._glBlitFrameBuffer( + 0, 0, source.width, source.height, + left, top, right, bottom, + GL11C.GL_COLOR_BUFFER_BIT, GL11C.GL_LINEAR); + GlStateManager._glBindFramebuffer(GL30C.GL_READ_FRAMEBUFFER, 0); } } 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 bfccdbaf2..95bbc90a2 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 @@ -63,9 +63,9 @@ public static void renderSingleView(RenderPass eye, DeltaTracker.Timer deltaTrac } 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 @@ -248,7 +248,15 @@ public static void renderAndSubmit(boolean renderLevel, DeltaTracker.Timer delta Profiler.get().pop(); DATA_HOLDER.vrPlayer.postRender(deltaTracker.getGameTimeDeltaPartialTick(true)); - Profiler.get().push("Display/Reproject"); + + Profiler.get().push("vrMirror"); + // use the vanilla target for the mirror + RenderPassManager.setMirrorRenderPass(); + MC.mainRenderTarget.bindWrite(true); + ShaderHelper.drawMirror(); + RenderHelper.checkGLError("post-mirror"); + + Profiler.get().popPush("Display/Reproject"); try { DATA_HOLDER.vrRenderer.endFrame(); 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 2662248d8..79c06d068 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 @@ -144,6 +144,7 @@ public enum ShaderGUIRender implements OptionEnum { public enum VRProvider implements OptionEnum { OPENVR, + OPENXR, NULLVR } @@ -1828,7 +1829,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 3676e3b5f..1952f065e 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 @@ -52,7 +52,6 @@ public abstract class RenderTargetMixin implements RenderTargetExtension { this.vivecraft$texId = texId; } - @Override @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 ade136f65..3f3107c7a 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 @@ -19,11 +19,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.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; import org.vivecraft.common.utils.MathUtils; @Mixin(KeyboardInput.class) @@ -50,7 +50,7 @@ public class KeyboardInputVRMixin extends ClientInput { @Unique private float vivecraft$getAxisValue(KeyMapping keyBinding) { - return Math.abs(MCVR.get().getInputAction(keyBinding).getAxis1DUseTracked()); + return Math.abs(ClientDataHolderVR.getInstance().vr.getInputAction(keyBinding).getAxis1DUseTracked()); } @WrapOperation(method = "tick", at = @At(value = "NEW", target = "net/minecraft/world/entity/player/Input")) 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 756a7a5ad..f5b3c5ad4 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 @@ -69,12 +69,11 @@ import org.vivecraft.client_vr.menuworlds.MenuWorldDownloader; import org.vivecraft.client_vr.menuworlds.MenuWorldExporter; import org.vivecraft.client_vr.provider.MCVR; -import org.vivecraft.client_vr.provider.openvr_lwjgl.VRInputAction; +import org.vivecraft.client_vr.provider.control.VRInputAction; import org.vivecraft.client_vr.render.MirrorNotification; import org.vivecraft.client_vr.render.RenderConfigException; import org.vivecraft.client_vr.render.VRFirstPersonArmSwing; import org.vivecraft.client_vr.render.helpers.RenderHelper; -import org.vivecraft.client_vr.render.helpers.ShaderHelper; import org.vivecraft.client_vr.settings.VRHotkeys; import org.vivecraft.client_vr.settings.VRSettings; import org.vivecraft.client_xr.render_pass.RenderPassManager; @@ -246,6 +245,10 @@ public abstract class MinecraftVRMixin implements MinecraftExtension { if (!VRState.VR_INITIALIZED) { return; } + + // handle vr events, regardless of VR active state + ClientDataHolderVR.getInstance().vr.handleEvents(); + boolean vrActive = !ClientDataHolderVR.getInstance().vrSettings.vrHotswitchingEnabled || ClientDataHolderVR.getInstance().vr.isActive(); if (VRState.VR_RUNNING != vrActive && (ClientNetworking.SERVER_ALLOWS_VR_SWITCHING || this.player == null)) { @@ -314,7 +317,7 @@ public abstract class MinecraftVRMixin implements MinecraftExtension { try { Profiler.get().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 @@ -352,17 +355,6 @@ public abstract class MinecraftVRMixin implements MinecraftExtension { } } - @Inject(method = "runTick", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/pipeline/RenderTarget;unbindWrite()V")) - private void vivecraft$blitMirror(CallbackInfo ci) { - if (VRState.VR_RUNNING) { - Profiler.get().popPush("vrMirror"); - RenderPassManager.setMirrorRenderPass(); - this.mainRenderTarget.bindWrite(true); - ShaderHelper.drawMirror(); - RenderHelper.checkGLError("post-mirror"); - } - } - @Inject(method = "setCameraEntity", at = @At("HEAD")) private void vivecraft$rideEntity(Entity entity, CallbackInfo ci) { if (VRState.VR_INITIALIZED) { 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 834a5b926..3c31702fa 100644 --- a/common/src/main/resources/assets/vivecraft/lang/en_us.json +++ b/common/src/main/resources/assets/vivecraft/lang/en_us.json @@ -198,6 +198,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).", @@ -371,6 +372,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/common/src/main/resources/assets/vivecraft/shaders/core/blit_vr.fsh b/common/src/main/resources/assets/vivecraft/shaders/core/blit_vr.fsh deleted file mode 100644 index d961ccfc5..000000000 --- a/common/src/main/resources/assets/vivecraft/shaders/core/blit_vr.fsh +++ /dev/null @@ -1,11 +0,0 @@ -#version 150 core - -uniform sampler2D DiffuseSampler; - -in vec2 texCoordinates; - -out vec4 fragColor; - -void main(){ - fragColor = texture(DiffuseSampler, texCoordinates.st); -} diff --git a/common/src/main/resources/assets/vivecraft/shaders/core/blit_vr.json b/common/src/main/resources/assets/vivecraft/shaders/core/blit_vr.json deleted file mode 100644 index 0f3041b2a..000000000 --- a/common/src/main/resources/assets/vivecraft/shaders/core/blit_vr.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "vertex": "vivecraft:core/passthrough_vr", - "fragment": "vivecraft:core/blit_vr", - "samplers": [ - { - "name": "DiffuseSampler" - } - ], - "uniforms": [] -} diff --git a/common/src/main/resources/assets/vivecraft/shaders/core/mixedreality_vr.fsh b/common/src/main/resources/assets/vivecraft/shaders/core/mixedreality_vr.fsh index 346fab2b2..44d5465b1 100644 --- a/common/src/main/resources/assets/vivecraft/shaders/core/mixedreality_vr.fsh +++ b/common/src/main/resources/assets/vivecraft/shaders/core/mixedreality_vr.fsh @@ -1,7 +1,5 @@ #version 330 core -uniform sampler2D firstPersonColor; - uniform sampler2D thirdPersonColor; uniform sampler2D thirdPersonDepth; @@ -63,9 +61,6 @@ void main(void) { out_Color.rgb = vec3(1.0); } } - } else if (texCoordinates.x >= 0.5 && texCoordinates.y < 0.5){ - // first person - out_Color.rgb = texture(firstPersonColor, sampleTexcCoord).rgb; } } else { // side by side diff --git a/fabric/build.gradle b/fabric/build.gradle index f016fb166..711a531fc 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -68,6 +68,13 @@ 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}")) + include(implementation("org.lwjgl:lwjgl-openxr:${rootProject.lwjgl_version}:natives-windows")) + include(implementation("org.lwjgl:lwjgl-openxr:${rootProject.lwjgl_version}:natives-linux")) + include(implementation("com.illposed.osc:javaosc-core:0.9")) } diff --git a/forge/build.gradle b/forge/build.gradle index 46f001850..af6cc7270 100644 --- a/forge/build.gradle +++ b/forge/build.gradle @@ -61,12 +61,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 9f24ac5f0..dc07963e9 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}"))