From fde56b54278f6b611ed0d4bf6936b473d2308f85 Mon Sep 17 00:00:00 2001 From: Weever <109693401+Weever1337@users.noreply.github.com> Date: Sun, 4 Oct 2026 01:41:24 +0300 Subject: [PATCH] new DH and voxy compatibilites; mellow shader support; --- .../visor/compatibility/LodPassHelper.java | 38 +++++ .../compatibility/dh/DhCompatHelper.java | 32 +--- .../dh/mixin/DhCameraZoomVRMixin.java | 44 ++++++ .../compatibility/shaders/ShaderPatcher.java | 34 +++- .../compatibility/voxy/VoxyCompatHelper.java | 36 +++++ .../voxy/VoxyIrisPipelineBinder.java | 149 ++++++++++++++++++ .../mixin/VoxyIrisPipelineDataVRMixin.java | 27 ++++ .../mixin/VoxyIrisRenderPipelineVRMixin.java | 27 ++++ .../voxy/mixin/VoxyRenderSystemVRMixin.java | 36 +++++ .../mixin/VoxyViewportSelectorVRMixin.java | 51 ++++++ .../resources/assets/visor/lang/en_us.json | 4 +- .../resources/assets/visor/lang/fr_fr.json | 4 +- .../resources/assets/visor/lang/hu_hu.json | 4 +- .../resources/assets/visor/lang/ru_ru.json | 4 +- .../resources/assets/visor/lang/uk_ua.json | 4 +- .../src/main/resources/visor.dh.mixins.json | 3 +- .../shaders/ShaderPatcherTest.java | 37 +++++ .../src/main/resources/fabric.mod.json | 1 + .../src/main/resources/visor.voxy.mixins.json | 14 ++ 19 files changed, 511 insertions(+), 38 deletions(-) create mode 100644 visor-core/src/main/java/org/vmstudio/visor/compatibility/LodPassHelper.java create mode 100644 visor-core/src/main/java/org/vmstudio/visor/compatibility/dh/mixin/DhCameraZoomVRMixin.java create mode 100644 visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/VoxyCompatHelper.java create mode 100644 visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/VoxyIrisPipelineBinder.java create mode 100644 visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/mixin/VoxyIrisPipelineDataVRMixin.java create mode 100644 visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/mixin/VoxyIrisRenderPipelineVRMixin.java create mode 100644 visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/mixin/VoxyRenderSystemVRMixin.java create mode 100644 visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/mixin/VoxyViewportSelectorVRMixin.java create mode 100644 visor-fabric/src/main/resources/visor.voxy.mixins.json diff --git a/visor-core/src/main/java/org/vmstudio/visor/compatibility/LodPassHelper.java b/visor-core/src/main/java/org/vmstudio/visor/compatibility/LodPassHelper.java new file mode 100644 index 00000000..56cde92f --- /dev/null +++ b/visor-core/src/main/java/org/vmstudio/visor/compatibility/LodPassHelper.java @@ -0,0 +1,38 @@ +package org.vmstudio.visor.compatibility; + +import org.jetbrains.annotations.Nullable; +import org.vmstudio.visor.api.client.render.VRRenderPass; +import org.vmstudio.visor.api.client.settings.VRClientSettings; +import org.vmstudio.visor.core.client.VisorState; +import org.vmstudio.visor.core.client.render.VRRenderState; + +public class LodPassHelper { + @Nullable + public static VRRenderPass currentWorldPass() { + if (VisorState.get().isNotActive() + || VRRenderState.getPhase().isNotVRWorld()) { + return null; + } + + VRRenderPass renderPass = VRRenderState.getRenderPass(); + return renderPass != null && renderPass.isWorld() ? renderPass : null; + } + + public static boolean isVrWorldPass() { + return currentWorldPass() != null; + } + + public static boolean isVrEyeWorldPass() { + VRRenderPass renderPass = currentWorldPass(); + return renderPass != null && renderPass.isEye(); + } + + public static boolean shouldSkipMirrorPass() { + VRRenderPass renderPass = currentWorldPass(); + if (renderPass == null || renderPass.isEye()) { + return false; + } + + return !VRClientSettings.isDhMirrorPasses(); + } +} diff --git a/visor-core/src/main/java/org/vmstudio/visor/compatibility/dh/DhCompatHelper.java b/visor-core/src/main/java/org/vmstudio/visor/compatibility/dh/DhCompatHelper.java index 00b722dd..85e06ee8 100644 --- a/visor-core/src/main/java/org/vmstudio/visor/compatibility/dh/DhCompatHelper.java +++ b/visor-core/src/main/java/org/vmstudio/visor/compatibility/dh/DhCompatHelper.java @@ -1,43 +1,23 @@ package org.vmstudio.visor.compatibility.dh; import org.vmstudio.visor.api.ModLoader; -import org.vmstudio.visor.api.client.render.VRRenderPass; -import org.vmstudio.visor.core.client.VisorState; -import org.vmstudio.visor.core.client.render.VRRenderState; -import org.vmstudio.visor.api.client.settings.VRClientSettings; +import org.vmstudio.visor.compatibility.LodPassHelper; public final class DhCompatHelper { public static final String MOD_ID = "distanthorizons"; - - private DhCompatHelper() { - throw new UnsupportedOperationException("Utility class"); - } - public static boolean isLoaded() { return ModLoader.get().isModLoaded(MOD_ID); } public static boolean shouldSkipDhRender() { - if (VisorState.get().isNotActive() - || VRRenderState.getPhase().isNotVRWorld()) { - return false; - } - - VRRenderPass renderPass = VRRenderState.getRenderPass(); - if (renderPass == null || !renderPass.isWorld() || renderPass.isEye()) { - return false; - } + return LodPassHelper.shouldSkipMirrorPass(); + } - return !VRClientSettings.isDhMirrorPasses(); + public static boolean isVrWorldPass() { + return LodPassHelper.isVrWorldPass(); } public static boolean isVrEyeWorldPass() { - if (VisorState.get().isNotActive() - || VRRenderState.getPhase().isNotVRWorld()) { - return false; - } - - VRRenderPass renderPass = VRRenderState.getRenderPass(); - return renderPass != null && renderPass.isEye(); + return LodPassHelper.isVrEyeWorldPass(); } } \ No newline at end of file diff --git a/visor-core/src/main/java/org/vmstudio/visor/compatibility/dh/mixin/DhCameraZoomVRMixin.java b/visor-core/src/main/java/org/vmstudio/visor/compatibility/dh/mixin/DhCameraZoomVRMixin.java new file mode 100644 index 00000000..d7f0719f --- /dev/null +++ b/visor-core/src/main/java/org/vmstudio/visor/compatibility/dh/mixin/DhCameraZoomVRMixin.java @@ -0,0 +1,44 @@ +package org.vmstudio.visor.compatibility.dh.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.vmstudio.visor.compatibility.MixinGate; +import org.vmstudio.visor.compatibility.dh.DhCompatHelper; + +@Pseudo +@MixinGate( + classes = "com.seibel.distanthorizons.core.render.CameraZoom", + methods = "update", + fields = {"magnification", "coneTanHalfAngle", "lookDirectionX", "lookDirectionZ"} +) +@Mixin(targets = "com.seibel.distanthorizons.core.render.CameraZoom", remap = false) +public class DhCameraZoomVRMixin { + @Shadow(remap = false) + public double magnification; + + @Shadow(remap = false) + public double coneTanHalfAngle; + + @Shadow(remap = false) + public double lookDirectionX; + + @Shadow(remap = false) + public double lookDirectionZ; + + @Inject(method = "update", at = @At("HEAD"), cancellable = true, require = 0, expect = 0, remap = false) + private void visor$noZoomInVr(CallbackInfo ci) { + if (!DhCompatHelper.isVrWorldPass()) { + return; + } + + this.magnification = 1.0; + this.coneTanHalfAngle = 0.0; + this.lookDirectionX = 0.0; + this.lookDirectionZ = 0.0; + ci.cancel(); + } +} diff --git a/visor-core/src/main/java/org/vmstudio/visor/compatibility/shaders/ShaderPatcher.java b/visor-core/src/main/java/org/vmstudio/visor/compatibility/shaders/ShaderPatcher.java index b76775f7..801893a4 100644 --- a/visor-core/src/main/java/org/vmstudio/visor/compatibility/shaders/ShaderPatcher.java +++ b/visor-core/src/main/java/org/vmstudio/visor/compatibility/shaders/ShaderPatcher.java @@ -87,6 +87,15 @@ public final class ShaderPatcher { .capture("view").glsl("),").same("view").glsl(".z*") .same("mat").glsl("[2].w)")); + // vec4 assembled row by row with fma() for the depth term, the [2].xy column is simply left out (Mellow) + private static final ShaderPatch FORWARD_FMA_ROWS = new ShaderPatch() + .hint("fma") + .rewrite("return ${mat} * vec4(${view}, 1.0);", + p -> p.glsl("return vec4(vec2(").capture("mat").glsl("[0].x,").same("mat") + .glsl("[1].y)*").capture("view").glsl(".xy,fma(").same("mat") + .glsl("[2].z,").same("view").glsl(".z,").same("mat").glsl("[3].z),") + .same("mat").glsl("[2].w*").same("view").glsl(".z);")); + // ---- screen -> view ---- // unproject via inverse diagonal plus [3] column, divided in a second statement (rre36 packs); @@ -145,6 +154,15 @@ public final class ShaderPatcher { .same("out").glsl("/=(").same("mat").glsl("[2].w*(") .capture("depth").glsl("*2.0-1.0)+").same("mat").glsl("[3].w);")); + // same row by row assembly for the inverse, this one drops the [3].xy column (Mellow) + private static final ShaderPatch INVERSE_FMA_ROWS = new ShaderPatch() + .hint("fma") + .rewrite("return ${mat} * vec4(${ndc}, 1.0);", + p -> p.glsl("return vec4(vec2(").capture("mat").glsl("[0].x,").same("mat") + .glsl("[1].y)*").capture("ndc").glsl(".xy,").same("mat") + .glsl("[3].z,fma(").same("mat").glsl("[2].w,").same("ndc") + .glsl(".z,").same("mat").glsl("[3].w));")); + // ---- view -> screen uv ---- // two-statement uv from the diagonal, xy only @@ -210,6 +228,17 @@ public final class ShaderPatcher { .same("mat").glsl("[3].xy;").capture("uv").glsl("=(") .same("clip").glsl("/-").same("view").glsl(".z)*0.5+0.5;")); + // previous-frame reprojection returned straight away, what a TAA resolve samples its history with (Mellow) + private static final ShaderPatch SCREEN_PREVIOUS_FRAME_RETURN = new ShaderPatch() + .hint("[0]") + .rewrite(""" + vec4 ${clip} = ${mat} * vec4(${view}, 1.0); + return (${clip}.xy / ${clip}.w) * 0.5 + 0.5;""", + p -> p.glsl("vec2").capture("clip").glsl("=vec2(").capture("mat").glsl("[0].x,") + .same("mat").glsl("[1].y)*").capture("view").glsl(".xy+") + .same("mat").glsl("[3].xy;return(").same("clip").glsl("/-") + .same("view").glsl(".z)*0.5+0.5;")); + private static final List PATCHES = List.of( // macro definitions first, then grouped by direction PROJMAD_MACRO, @@ -219,17 +248,20 @@ public final class ShaderPatcher { FORWARD_DIAG4_Z, FORWARD_MAT3_SCALE, FORWARD_PROJMAD, + FORWARD_FMA_ROWS, // screen -> view INVERSE_DIAG_DIVIDE, INVERSE_NDC_DIAG, INVERSE_PROJMAD, INVERSE_TEXCOORD_DIAG, + INVERSE_FMA_ROWS, // view -> screen uv SCREEN_XY_DIAG, SCREEN_XY_DIAG2, SCREEN_XYZ_DIAG, SCREEN_LINE_XY_DIAG, - SCREEN_PREVIOUS_FRAME); + SCREEN_PREVIOUS_FRAME, + SCREEN_PREVIOUS_FRAME_RETURN); private ShaderPatcher() { throw new UnsupportedOperationException("Utility class"); diff --git a/visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/VoxyCompatHelper.java b/visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/VoxyCompatHelper.java new file mode 100644 index 00000000..e6263262 --- /dev/null +++ b/visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/VoxyCompatHelper.java @@ -0,0 +1,36 @@ +package org.vmstudio.visor.compatibility.voxy; + +import net.minecraft.client.Minecraft; +import org.jetbrains.annotations.Nullable; +import org.vmstudio.visor.api.ModLoader; +import org.vmstudio.visor.api.client.render.VRRenderPass; +import org.vmstudio.visor.compatibility.LodPassHelper; +import org.vmstudio.visor.compatibility.ShaderCompatHelper; +import org.vmstudio.visor.core.client.VisorState; +import org.vmstudio.visor.extensions.client.render.GameRendererExtension; + +public final class VoxyCompatHelper { + public static final String MOD_ID = "voxy"; + public static boolean isLoaded() { + return ModLoader.get().isModLoaded(MOD_ID); + } + + @Nullable + public static VRRenderPass viewportPass() { + return LodPassHelper.currentWorldPass(); + } + + public static boolean shouldSkipVoxyRender() { + if (ShaderCompatHelper.isShaderActive()) { + return false; + } + return LodPassHelper.shouldSkipMirrorPass(); + } + + public static float vanillaNearPlane(float original) { + if (VisorState.get().isNotActive()) { + return original; + } + return ((GameRendererExtension) Minecraft.getInstance().gameRenderer).visor$getNearClipPlane(); + } +} diff --git a/visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/VoxyIrisPipelineBinder.java b/visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/VoxyIrisPipelineBinder.java new file mode 100644 index 00000000..52bc7d57 --- /dev/null +++ b/visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/VoxyIrisPipelineBinder.java @@ -0,0 +1,149 @@ +package org.vmstudio.visor.compatibility.voxy; + +import net.irisshaders.iris.Iris; +import org.lwjgl.opengl.GL30C; +import org.lwjgl.opengl.GL45C; +import org.vmstudio.visor.api.common.utils.LoggerUtils; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Comparator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public final class VoxyIrisPipelineBinder { + private static boolean initialized; + private static boolean ready; + + private static Class dataHolderInterface; + private static Method getPipelineData; + + private static Field pipelineData; + private static Field pipelineFb; + private static Field pipelineFbTranslucent; + private static Field dataThePipeline; + private static Field dataOpaqueDrawTargets; + private static Field dataTranslucentDrawTargets; + private static Field depthFramebufferFramebuffer; + private static Field glFramebufferId; + + public static void rebindVoxyToIrisPipeline(Object voxyPipeline) { + if (initialized && !ready) { + return; + } + try { + rebind(voxyPipeline, Iris.getPipelineManager().getPipelineNullable()); + } catch (Throwable t) { + ready = false; + LoggerUtils.getLogger().error( + "Visor: failed to rebind Voxy to the current Iris pipeline; " + + "Voxy LODs will be wrong with shaders in VR", t); + } + } + + private static void rebind(Object voxyPipeline, Object irisPipeline) throws Exception { + if (!init(voxyPipeline.getClass())) { + return; + } + if (!dataHolderInterface.isInstance(irisPipeline)) { + return; + } + Object current = getPipelineData.invoke(irisPipeline); + Object bound = pipelineData.get(voxyPipeline); + if (current == null || current == bound) { + return; + } + + dataThePipeline.set(bound, null); + dataThePipeline.set(current, voxyPipeline); + pipelineData.set(voxyPipeline, current); + + attachDrawTargets(framebufferId(pipelineFb.get(voxyPipeline)), + (int[]) dataOpaqueDrawTargets.get(current), ((int[]) dataOpaqueDrawTargets.get(bound)).length); + attachDrawTargets(framebufferId(pipelineFbTranslucent.get(voxyPipeline)), + (int[]) dataTranslucentDrawTargets.get(current), ((int[]) dataTranslucentDrawTargets.get(bound)).length); + } + + private static int framebufferId(Object depthFramebuffer) throws Exception { + return glFramebufferId.getInt(depthFramebufferFramebuffer.get(depthFramebuffer)); + } + + private static void attachDrawTargets(int framebuffer, int[] targets, int previousCount) { + int[] drawBuffers = new int[targets.length]; + for (int i = 0; i < targets.length; i++) { + drawBuffers[i] = GL30C.GL_COLOR_ATTACHMENT0 + i; + GL45C.glNamedFramebufferTexture(framebuffer, GL30C.GL_COLOR_ATTACHMENT0 + i, targets[i], 0); + } + for (int i = targets.length; i < previousCount; i++) { + GL45C.glNamedFramebufferTexture(framebuffer, GL30C.GL_COLOR_ATTACHMENT0 + i, 0, 0); + } + GL45C.glNamedFramebufferDrawBuffers(framebuffer, drawBuffers); + } + + private static boolean init(Class pipelineClass) throws Exception { + if (initialized) { + return ready; + } + initialized = true; + + dataHolderInterface = Class.forName("me.cortex.voxy.client.iris.IGetIrisVoxyPipelineData", + false, pipelineClass.getClassLoader()); + getPipelineData = dataHolderInterface.getMethod("voxy$getPipelineData"); + Class dataClass = getPipelineData.getReturnType(); + + pipelineData = pipelineClass.getDeclaredField("data"); + pipelineData.setAccessible(true); + pipelineFb = pipelineClass.getField("fb"); + pipelineFb.setAccessible(true); + pipelineFbTranslucent = pipelineClass.getField("fbTranslucent"); + pipelineFbTranslucent.setAccessible(true); + dataThePipeline = dataClass.getField("thePipeline"); + dataThePipeline.setAccessible(true); + dataOpaqueDrawTargets = dataClass.getField("opaqueDrawTargets"); + dataOpaqueDrawTargets.setAccessible(true); + dataTranslucentDrawTargets = dataClass.getField("translucentDrawTargets"); + dataTranslucentDrawTargets.setAccessible(true); + depthFramebufferFramebuffer = pipelineFb.getType().getField("framebuffer"); + depthFramebufferFramebuffer.setAccessible(true); + glFramebufferId = depthFramebufferFramebuffer.getType().getField("id"); + glFramebufferId.setAccessible(true); + + ready = true; + return true; + } + + + + private static Map, Method> NAME_GETTERS = new ConcurrentHashMap<>(); + public static Comparator UNIFORM_ORDER = Comparator.comparing(VoxyIrisPipelineBinder::uniformName); + + private static boolean loggedNameFailure; + + private static String uniformName(Object uniform) { + if (uniform == null) { + return ""; + } + try { + Method getter = NAME_GETTERS.computeIfAbsent(uniform.getClass(), VoxyIrisPipelineBinder::findNameGetter); + return String.valueOf(getter.invoke(uniform)); + } catch (Throwable t) { + if (!loggedNameFailure) { + loggedNameFailure = true; + LoggerUtils.getLogger().error( + "Visor: couldnt read the names of Voxy's shader uniforms; " + + "Voxy LODs may get garbage uniforms with shaders in VR", t); + } + return ""; + } + } + + private static Method findNameGetter(Class uniformClass) { + try { + Method method = uniformClass.getDeclaredMethod("name"); + method.setAccessible(true); + return method; + } catch (NoSuchMethodException e) { + throw new IllegalStateException("no name() in " + uniformClass.getName(), e); + } + } +} diff --git a/visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/mixin/VoxyIrisPipelineDataVRMixin.java b/visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/mixin/VoxyIrisPipelineDataVRMixin.java new file mode 100644 index 00000000..2345e52f --- /dev/null +++ b/visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/mixin/VoxyIrisPipelineDataVRMixin.java @@ -0,0 +1,27 @@ +package org.vmstudio.visor.compatibility.voxy.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.vmstudio.visor.compatibility.MixinGate; +import org.vmstudio.visor.compatibility.voxy.VoxyIrisPipelineBinder; + +import java.util.List; + +@Pseudo +@MixinGate( + classes = "me.cortex.voxy.client.iris.IrisVoxyRenderPipelineData", + methods = "createUniformSet" +) +@Mixin(targets = "me.cortex.voxy.client.iris.IrisVoxyRenderPipelineData", remap = false) +public class VoxyIrisPipelineDataVRMixin { + @SuppressWarnings({"unchecked", "rawtypes"}) + @Inject(method = "createUniformSet", at = @At("RETURN"), require = 0, expect = 0, remap = false) + private static void visor$sameUniformLayoutForEveryPipeline(CallbackInfoReturnable cir) { + if (cir.getReturnValue() instanceof List uniforms) { + uniforms.sort(VoxyIrisPipelineBinder.UNIFORM_ORDER); + } + } +} diff --git a/visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/mixin/VoxyIrisRenderPipelineVRMixin.java b/visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/mixin/VoxyIrisRenderPipelineVRMixin.java new file mode 100644 index 00000000..f4c6d146 --- /dev/null +++ b/visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/mixin/VoxyIrisRenderPipelineVRMixin.java @@ -0,0 +1,27 @@ +package org.vmstudio.visor.compatibility.voxy.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.vmstudio.visor.compatibility.MixinGate; +import org.vmstudio.visor.compatibility.voxy.VoxyIrisPipelineBinder; + +@Pseudo +@MixinGate( + classes = { + "me.cortex.voxy.client.core.IrisVoxyRenderPipeline", + "me.cortex.voxy.client.iris.IGetIrisVoxyPipelineData", + "net.irisshaders.iris.Iris" + }, + methods = "preSetup", + fields = {"data", "fbTranslucent"} +) +@Mixin(targets = "me.cortex.voxy.client.core.IrisVoxyRenderPipeline", remap = false) +public class VoxyIrisRenderPipelineVRMixin { + @Inject(method = "preSetup", at = @At("HEAD"), require = 0, expect = 0, remap = false) + private void visor$followIrisPipeline(CallbackInfo ci) { + VoxyIrisPipelineBinder.rebindVoxyToIrisPipeline(this); + } +} diff --git a/visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/mixin/VoxyRenderSystemVRMixin.java b/visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/mixin/VoxyRenderSystemVRMixin.java new file mode 100644 index 00000000..89c12d40 --- /dev/null +++ b/visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/mixin/VoxyRenderSystemVRMixin.java @@ -0,0 +1,36 @@ +package org.vmstudio.visor.compatibility.voxy.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyConstant; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.vmstudio.visor.compatibility.MixinGate; +import org.vmstudio.visor.compatibility.voxy.VoxyCompatHelper; + +@Pseudo +@MixinGate(classes = "me.cortex.voxy.client.core.VoxyRenderSystem") +@Mixin(targets = "me.cortex.voxy.client.core.VoxyRenderSystem", remap = false) +public class VoxyRenderSystemVRMixin { + @Inject(method = "setupViewport", at = @At("HEAD"), cancellable = true, require = 0, expect = 0, remap = false) + private void visor$noViewportForMirrorPass(CallbackInfoReturnable cir) { + if (VoxyCompatHelper.shouldSkipVoxyRender()) { + cir.setReturnValue(null); + } + } + + @Inject(method = "renderOpaque", at = @At("HEAD"), cancellable = true, require = 0, expect = 0, remap = false) + private void visor$skipLodsForMirrorPass(CallbackInfo ci) { + if (VoxyCompatHelper.shouldSkipVoxyRender()) { + ci.cancel(); + } + } + + @ModifyConstant(method = "computeProjectionMat", constant = @Constant(floatValue = 0.05F), require = 0, expect = 0, remap = false) + private static float visor$vrNearPlane(float original) { + return VoxyCompatHelper.vanillaNearPlane(original); + } +} diff --git a/visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/mixin/VoxyViewportSelectorVRMixin.java b/visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/mixin/VoxyViewportSelectorVRMixin.java new file mode 100644 index 00000000..25c18f46 --- /dev/null +++ b/visor-core/src/main/java/org/vmstudio/visor/compatibility/voxy/mixin/VoxyViewportSelectorVRMixin.java @@ -0,0 +1,51 @@ +package org.vmstudio.visor.compatibility.voxy.mixin; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.vmstudio.visor.api.client.render.VRRenderPass; +import org.vmstudio.visor.compatibility.MixinGate; +import org.vmstudio.visor.compatibility.voxy.VoxyCompatHelper; + +import java.util.Map; +import java.util.function.Supplier; + +@Pseudo +@MixinGate( + classes = "me.cortex.voxy.client.core.rendering.ViewportSelector", + methods = "getViewport", + fields = {"extraViewports", "creator"} +) +@Mixin(targets = "me.cortex.voxy.client.core.rendering.ViewportSelector", remap = false) +public class VoxyViewportSelectorVRMixin { + @SuppressWarnings("rawtypes") + @Shadow(remap = false) + @Final + private Map extraViewports; + + @SuppressWarnings("rawtypes") + @Shadow(remap = false) + @Final + private Supplier creator; + + @SuppressWarnings("unchecked") + @Inject(method = "getViewport", at = @At("RETURN"), cancellable = true, require = 0, expect = 0, remap = false) + private void visor$viewportPerPass(CallbackInfoReturnable cir) { + VRRenderPass renderPass = VoxyCompatHelper.viewportPass(); + if (renderPass == null) { + return; + } + + Object chosen = cir.getReturnValue(); + if (chosen == null || this.extraViewports.containsValue(chosen)) { + return; + } + + cir.setReturnValue(this.extraViewports.computeIfAbsent(renderPass, __ -> this.creator.get())); + } + +} diff --git a/visor-core/src/main/resources/assets/visor/lang/en_us.json b/visor-core/src/main/resources/assets/visor/lang/en_us.json index e96c5df5..c3639950 100644 --- a/visor-core/src/main/resources/assets/visor/lang/en_us.json +++ b/visor-core/src/main/resources/assets/visor/lang/en_us.json @@ -88,8 +88,8 @@ "visor.options.rendering.mirror_mode.tooltip": "§bDesktop Mirror§r\n\n§e•§r §eOFF§r — No mirror §7(best performance)§r\n§e•§r §eGUI§r — GUI only\n§e•§r §eCropped / Single§r — One eye view\n§e•§r §eDual§r — Both eyes side-by-side\n§e•§r §e1st Person§r — Undistorted first-person\n§e•§r §e3rd Person§r — Third-person camera\n§e•§r §eMixed Reality§r — Composite view", "visor.options.rendering.mirror_eye": "Mirror Eye", "visor.options.rendering.mirror_eye.tooltip": "§bMirror Eye§r\n\nChoose which eye to display on the desktop mirror window.\n\n§e•§r §eLeft§r or §eRight§r eye", - "visor.options.rendering.dh_mirror_passes": "DH in Mirror", - "visor.options.rendering.dh_mirror_passes.tooltip": "§bDistant Horizons in Mirror§r\n\nAlso renders Distant Horizons LODs in the desktop\nmirror camera (1st/3rd person view).\n\n§e•§r §eOFF§r — LODs only in the VR eyes §7(recommended)§r\n§e•§r §eON§r — LODs in the mirror too, but DH re-creates\nits render textures every frame §7(heavy stutter)§r", + "visor.options.rendering.dh_mirror_passes": "LODs in Mirror", + "visor.options.rendering.dh_mirror_passes.tooltip": "§bDistant Horizons / Voxy in Mirror§r\n\nAlso renders the LODs of Distant Horizons and Voxy\nin the desktop mirror camera (1st/3rd person view).\n\n§e•§r §eOFF§r — LODs only in the VR eyes §7(recommended)§r\n§e•§r §eON§r — LODs in the mirror too, but the LOD mod re-creates\nits render textures every frame §7(heavy stutter)§r\n\n§7Voxy with a shader pack always renders in the mirror.§r", "visor.options.rendering.graphics_api": "Graphics API", "visor.options.rendering.graphics_api.tooltip": "§bGraphics API§r\n\nHow frames are submitted to the VR runtime.\nThe game itself always renders with OpenGL\nRestart VR or game to apply changes\n\n§e•§r §eAUTO§r — picks the best one §7(recommended)§r\n§e•§r §eOPENGL§r — default§r\n§e•§r §eVULKAN§r — submit via Vulkan\n§7(Intel Arc GPUs and VR runtimes that don't like OpenGL)§r", "_comment.rendering.shaders": "----Shaders----", diff --git a/visor-core/src/main/resources/assets/visor/lang/fr_fr.json b/visor-core/src/main/resources/assets/visor/lang/fr_fr.json index ae76abcb..e4e03518 100644 --- a/visor-core/src/main/resources/assets/visor/lang/fr_fr.json +++ b/visor-core/src/main/resources/assets/visor/lang/fr_fr.json @@ -75,8 +75,8 @@ "visor.options.rendering.mirror_mode.tooltip": "§bMise En Miroir D'écran§r\n\n§e•§r §eDÉSACTIVɧr — Miroir désactivé §7(performance meilleure)§r\n§e•§r §eIU§r — Interface utilisateur uniquement\n§e•§r §eUnique§r — Miroir a un œil\n§e•§r §eDouble§r — Miroir a deux yeux côte à côte\n§e•§r §e1re Personne§r — Vue à la première personne\n§e•§r §e3e Personne§r — Vue à la troisième personne\n§e•§r §eRéalité mixte§r — Vue composite", "visor.options.rendering.mirror_eye": "Miroir a un œil", "visor.options.rendering.mirror_eye.tooltip": "§bMiroir a un œil§r\n\nChoisissez quel œil afficher dans la mise en miroir d'écran.\n\n§e•§r Œil §eGauche§r ou §eDroit§r", - "visor.options.rendering.dh_mirror_passes": "Mise en miroir d'écran avec DH", - "visor.options.rendering.dh_mirror_passes.tooltip": "§bMise en miroir d'écran avec Distant Horizons§r\n\nAffiche également les LOD de Distant Horizons dans\nla mise en miroir de l'écran (vue 1er et 3e personne).\n\n§e•§r §eDÉSACTIVɧr — Afficher les LOD uniquement dans le casque RV §7(recommandé)§r\n§e•§r §eACTIVɧr — Afficher les LOD également dans la\nmise en miroir de l'écran, mais DH recrée ses\ntextures de rendu à chaque trame §7(fortes saccades)§r", + "visor.options.rendering.dh_mirror_passes": "LOD dans le miroir", + "visor.options.rendering.dh_mirror_passes.tooltip": "§bDistant Horizons / Voxy dans la mise en miroir§r\n\nAffiche également les LOD de Distant Horizons et de Voxy\ndans la mise en miroir de l'écran (vue 1re et 3e personne).\n\n§e•§r §eDÉSACTIVɧr — Afficher les LOD uniquement dans le casque RV §7(recommandé)§r\n§e•§r §eACTIVɧr — Afficher les LOD également dans la\nmise en miroir de l'écran, mais le mod de LOD recrée ses\ntextures de rendu à chaque trame §7(fortes saccades)§r\n\n§7Avec un pack de shaders, Voxy est toujours affiché dans le miroir.§r", "_comment.rendering.shaders": "----Shaders----", "visor.options.rendering.shaders.button": "Shaders (RV)...", "visor.options.rendering.shaders.per_eye_pipelines": "Pipelines par œil", diff --git a/visor-core/src/main/resources/assets/visor/lang/hu_hu.json b/visor-core/src/main/resources/assets/visor/lang/hu_hu.json index 38317506..07956cc3 100644 --- a/visor-core/src/main/resources/assets/visor/lang/hu_hu.json +++ b/visor-core/src/main/resources/assets/visor/lang/hu_hu.json @@ -88,8 +88,8 @@ "visor.options.rendering.mirror_mode.tooltip": "§bAsztali tükrözés§r\n\n§e•§r §eKI§r - nincs tükrözés §7(a leggyorsabb)§r\n§e•§r §eGUI§r - csak a felület\n§e•§r §eVágott / Egy szem§r - az egyik szem képe\n§e•§r §eKét szem§r - mindkét szem egymás mellett\n§e•§r §eBelső nézet§r - torzításmentes belső nézet\n§e•§r §eKülső nézet§r - külső kamera\n§e•§r §eKevert valóság§r - összefűzött kép", "visor.options.rendering.mirror_eye": "Tükrözött szem", "visor.options.rendering.mirror_eye.tooltip": "§bTükrözött szem§r\n\nVálaszd ki, melyik szem képe jelenjen meg az asztali tükrözés ablakában.\n\n§e•§r §eBal§r vagy §eJobb§r szem", - "visor.options.rendering.dh_mirror_passes": "DH a tükrözésben", - "visor.options.rendering.dh_mirror_passes.tooltip": "§bDistant Horizons a tükrözésben§r\n\nA Distant Horizons LOD-jait az asztali tükrözés\nkamerájában is kirajzolja (belső/külső nézet).\n\n§e•§r §eKI§r - LOD-ok csak a VR szemekben §7(ajánlott)§r\n§e•§r §eBE§r - LOD-ok a tükrözésben is, de a DH minden\nképkockán újra létrehozza a textúráit §7(erős akadozás)§r", + "visor.options.rendering.dh_mirror_passes": "LOD-ok a tükrözésben", + "visor.options.rendering.dh_mirror_passes.tooltip": "§bDistant Horizons / Voxy a tükrözésben§r\n\nA Distant Horizons és a Voxy LOD-jait az asztali tükrözés\nkamerájában is kirajzolja (belső/külső nézet).\n\n§e•§r §eKI§r - LOD-ok csak a VR szemekben §7(ajánlott)§r\n§e•§r §eBE§r - LOD-ok a tükrözésben is, de a LOD mod minden\nképkockán újra létrehozza a textúráit §7(erős akadozás)§r\n\n§7Shader csomaggal a Voxy mindig megjelenik a tükrözésben.§r", "visor.options.rendering.graphics_api": "Grafikus API", "visor.options.rendering.graphics_api.tooltip": "§bGrafikus API§r\n\nAhogy a képkockák eljutnak a VR futtatókörnyezethez.\nMaga a játék mindig OpenGL-lel renderel.\nA módosítás életbe lépéséhez indítsd újra a VR-t vagy a játékot\n\n§e•§r §eAUTO§r - a legjobbat választja ki §7(ajánlott)§r\n§e•§r §eOPENGL§r - alapértelmezett§r\n§e•§r §eVULKAN§r - küldés Vulkanon keresztül\n§7(Intel Arc videokártyák és olyan VR futtatókörnyezetek, amelyek nem kedvelik az OpenGL-t)§r", "_comment.rendering.shaders": "----Shaderek----", diff --git a/visor-core/src/main/resources/assets/visor/lang/ru_ru.json b/visor-core/src/main/resources/assets/visor/lang/ru_ru.json index e0606587..0d19d10e 100644 --- a/visor-core/src/main/resources/assets/visor/lang/ru_ru.json +++ b/visor-core/src/main/resources/assets/visor/lang/ru_ru.json @@ -86,8 +86,8 @@ "visor.options.rendering.mirror_mode.tooltip": "§bОкно на мониторе§r\n\n§e•§r §eВЫКЛ§r - нет изображения на мониторе §7(макс. FPS)§r\n§e•§r §eGUI§r - только интерфейс\n§e•§r §eОдин глаз / Кроп§r - вид из одного глаза\n§e•§r §eОба глаза§r - стерео-вид (side-by-side)\n§e•§r §e1-е лицо§r - вид от первого лица без искажений\n§e•§r §e3-е лицо§r - камера со стороны\n§e•§r §eСмешанная реальность§r - комбинированный вид", "visor.options.rendering.mirror_eye": "Глаз для зеркала", "visor.options.rendering.mirror_eye.tooltip": "§bГлаз для зеркала§r\n\nВыберите, какой глаз отображать в окне на рабочем столе.\n\n§e•§r §eЛевый§r или §eПравый§r", - "visor.options.rendering.dh_mirror_passes": "DH в зеркале", - "visor.options.rendering.dh_mirror_passes.tooltip": "§bDistant Horizons в зеркале§r\n\nРендерить LOD'ы Distant Horizons также в камере\nзеркала на рабочем столе (вид от 1-го/3-го лица).\n\n§e•§r §eВЫКЛ§r — LOD'ы только в глазах VR §7(рекомендуется)§r\n§e•§r §eВКЛ§r — LOD'ы и в зеркале, но DH пересоздаёт\nсвои текстуры каждый кадр §7(сильные фризы)§r", + "visor.options.rendering.dh_mirror_passes": "LOD'ы в зеркале", + "visor.options.rendering.dh_mirror_passes.tooltip": "§bDistant Horizons / Voxy в зеркале§r\n\nРендерить LOD'ы Distant Horizons и Voxy также в камере\nзеркала на рабочем столе (вид от 1-го/3-го лица).\n\n§e•§r §eВЫКЛ§r — LOD'ы только в глазах VR §7(рекомендуется)§r\n§e•§r §eВКЛ§r — LOD'ы и в зеркале, но LOD-мод пересоздаёт\nсвои текстуры каждый кадр §7(сильные фризы)§r\n\n§7Voxy с шейдерпаком рендерится в зеркале всегда.§r", "visor.options.rendering.graphics_api": "Графический API", "visor.options.rendering.graphics_api.tooltip": "§bГрафический API§r\n\nКак кадры отправляются в VR-рантайм.\nСама игра всегда рендерится через OpenGL.\n\n§e•§r §eAUTO§r — выбирается лучший §7(рекомендуется)§r\n§e•§r §eOPENGL§r — только OpenGL §7(SteamVR)§r\n§e•§r §eVULKAN§r — отправка через Vulkan\n§7(VDXR / Virtual Desktop, видеокарты Intel Arc)§r\n\n§7Применяется после перезапуска VR.§r", "_comment.rendering.shaders": "----Шейдеры (VR)----", diff --git a/visor-core/src/main/resources/assets/visor/lang/uk_ua.json b/visor-core/src/main/resources/assets/visor/lang/uk_ua.json index 69b40733..ba241cac 100644 --- a/visor-core/src/main/resources/assets/visor/lang/uk_ua.json +++ b/visor-core/src/main/resources/assets/visor/lang/uk_ua.json @@ -86,8 +86,8 @@ "visor.options.rendering.mirror_mode.tooltip": "§bВікно на моніторі§r\n\n§e•§r §eВИМК§r - немає зображення на моніторі §7(макс. FPS)§r\n§e•§r §eGUI§r - тільки інтерфейс\n§e•§r §eОдне око / Кроп§r - вигляд з одного ока\n§e•§r §eДва ока§r - стерео-вигляд (side-by-side)\n§e•§r §e1-е обличчя§r - вигляд від першої особи без спотворень\n§e•§r §e3-є обличчя§r - камера збоку\n§e•§r §eЗмішана реальність§r - комбінований вигляд", "visor.options.rendering.mirror_eye": "Око для дзеркала", "visor.options.rendering.mirror_eye.tooltip": "§bОко для дзеркала§r\n\nВиберіть, яке око відображати у вікні на робочому столі.\n\n§e•§r §eЛіве§r або §eПраве§r", - "visor.options.rendering.dh_mirror_passes": "DH у дзеркалі", - "visor.options.rendering.dh_mirror_passes.tooltip": "§bDistant Horizons у дзеркалі§r\n\nРендерити LOD'и Distant Horizons також у камері\nдзеркала на робочому столі (вигляд від 1-ї/3-ї особи).\n\n§e•§r §eВИМК§r — LOD'и лише в очах VR §7(рекомендовано)§r\n§e•§r §eУВІМК§r — LOD'и й у дзеркалі, але DH перестворює\nсвої текстури щокадру §7(сильні фризи)§r", + "visor.options.rendering.dh_mirror_passes": "LOD'и у дзеркалі", + "visor.options.rendering.dh_mirror_passes.tooltip": "§bDistant Horizons / Voxy у дзеркалі§r\n\nРендерити LOD'и Distant Horizons і Voxy також у камері\nдзеркала на робочому столі (вигляд від 1-ї/3-ї особи).\n\n§e•§r §eВИМК§r — LOD'и лише в очах VR §7(рекомендовано)§r\n§e•§r §eУВІМК§r — LOD'и й у дзеркалі, але LOD-мод перестворює\nсвої текстури щокадру §7(сильні фризи)§r\n\n§7Voxy із шейдерпаком рендериться в дзеркалі завжди.§r", "visor.options.rendering.graphics_api": "Графічний API", "visor.options.rendering.graphics_api.tooltip": "§bГрафічний API§r\n\nЯк кадри надсилаються у VR-рантайм.\nСама гра завжди рендериться через OpenGL.\n\n§e•§r §eAUTO§r — обирається найкращий §7(рекомендовано)§r\n§e•§r §eOPENGL§r — лише OpenGL §7(SteamVR)§r\n§e•§r §eVULKAN§r — надсилання через Vulkan\n§7(VDXR / Virtual Desktop, відеокарти Intel Arc)§r\n\n§7Застосовується після перезапуску VR.§r", "_comment.rendering.shaders": "----Шейдери (VR)----", diff --git a/visor-core/src/main/resources/visor.dh.mixins.json b/visor-core/src/main/resources/visor.dh.mixins.json index 5564a269..3cf84b89 100644 --- a/visor-core/src/main/resources/visor.dh.mixins.json +++ b/visor-core/src/main/resources/visor.dh.mixins.json @@ -8,6 +8,7 @@ ], "minVersion": "0.8.4", "client": [ - "DhClientApiVRMixin" + "DhClientApiVRMixin", + "DhCameraZoomVRMixin" ] } diff --git a/visor-core/src/test/java/org/vmstudio/visor/compatibility/shaders/ShaderPatcherTest.java b/visor-core/src/test/java/org/vmstudio/visor/compatibility/shaders/ShaderPatcherTest.java index 83e0f92e..e26bbefe 100644 --- a/visor-core/src/test/java/org/vmstudio/visor/compatibility/shaders/ShaderPatcherTest.java +++ b/visor-core/src/test/java/org/vmstudio/visor/compatibility/shaders/ShaderPatcherTest.java @@ -103,6 +103,19 @@ void projMadWithWTerm() { "vec4 clipPos = vec4(applyMAD(cam2clip, eyePos), eyePos.z * cam2clip[2].w);"); } + @Test + void rowByRowClipWithFma() { + patchesTo( + "return cam2clip * vec4(eyePos, 1.0);", + """ + return vec4( + vec2(cam2clip[0].x, cam2clip[1].y) * eyePos.xy, + fma(cam2clip[2].z, eyePos.z, cam2clip[3].z), + cam2clip[2].w * eyePos.z + );"""); + unchanged("float clipZ = fma(cam2clip[2].z, eyePos.z, cam2clip[3].z);"); + } + // ---- screen -> view ---- @Test @@ -155,6 +168,19 @@ void unprojectTexcoordDepthPair() { eyePoint /= (clip2view[2].w * (sceneDepth * 2.0 - 1.0) + clip2view[3].w);"""); } + @Test + void rowByRowUnprojectWithFma() { + patchesTo( + "return clip2view * vec4(devPos, 1.0);", + """ + return vec4( + vec2(clip2view[0].x, clip2view[1].y) * devPos.xy, + clip2view[3].z, + fma(clip2view[2].w, devPos.z, clip2view[3].w) + );"""); + unchanged("return vec4(vec2(a[0].x, a[1].y) * devPos.xy, b[3].z, fma(a[2].w, devPos.z, a[3].w));"); + } + // ---- view -> screen uv ---- @Test @@ -223,6 +249,17 @@ void screenUvPreviousFrame() { histUV = (histClip / -prevEye.z) * 0.5 + 0.5;"""); } + @Test + void screenUvPreviousFrameReturned() { + patchesTo( + """ + vec4 histClip = prevCam2clip * vec4(prevEye, 1.0); + return (histClip.xy / histClip.w) * 0.5 + 0.5;""", + """ + vec2 histClip = vec2(prevCam2clip[0].x, prevCam2clip[1].y) * prevEye.xy + prevCam2clip[3].xy; + return (histClip / -prevEye.z) * 0.5 + 0.5;"""); + } + // ---- non-matches ---- @Test diff --git a/visor-fabric/src/main/resources/fabric.mod.json b/visor-fabric/src/main/resources/fabric.mod.json index fb747bda..3afc203f 100644 --- a/visor-fabric/src/main/resources/fabric.mod.json +++ b/visor-fabric/src/main/resources/fabric.mod.json @@ -30,6 +30,7 @@ "visor.dynamicfps.mixins.json", "visor.iris.mixins.json", "visor.nvidium.mixins.json", + "visor.voxy.mixins.json", "visor.blur.mixins.json", "visor.reacharound.mixins.json" ], diff --git a/visor-fabric/src/main/resources/visor.voxy.mixins.json b/visor-fabric/src/main/resources/visor.voxy.mixins.json new file mode 100644 index 00000000..089e8d7a --- /dev/null +++ b/visor-fabric/src/main/resources/visor.voxy.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "package": "org.vmstudio.visor.compatibility.voxy.mixin", + "plugin": "org.vmstudio.visor.MixinConfig", + "compatibilityLevel": "JAVA_${mixin_compat_level}", + "refmap": "${mod_id}.refmap.json", + "client": [ + "VoxyViewportSelectorVRMixin", + "VoxyRenderSystemVRMixin", + "VoxyIrisRenderPipelineVRMixin", + "VoxyIrisPipelineDataVRMixin" + ], + "minVersion": "0.8.4" +}