Implement ODS rendering via Iris Shader for MC 1.17
This commit is contained in:
@@ -352,6 +352,10 @@ dependencies {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mcVersion >= 11600) {
|
||||||
|
modCompileOnly 'com.github.IrisShaders:Iris:1.0.0'
|
||||||
|
}
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.11'
|
testImplementation 'junit:junit:4.11'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
//#if MC>=11600
|
||||||
|
package com.replaymod.render.capturer;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.platform.GlStateManager;
|
||||||
|
import com.replaymod.render.RenderSettings;
|
||||||
|
import com.replaymod.render.frame.CubicOpenGlFrame;
|
||||||
|
import com.replaymod.render.frame.ODSOpenGlFrame;
|
||||||
|
import com.replaymod.render.frame.OpenGlFrame;
|
||||||
|
import com.replaymod.render.rendering.Channel;
|
||||||
|
import com.replaymod.render.rendering.FrameCapturer;
|
||||||
|
import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension;
|
||||||
|
import net.coderbot.iris.Iris;
|
||||||
|
import net.coderbot.iris.config.IrisConfig;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static com.replaymod.core.versions.MCVer.popMatrix;
|
||||||
|
import static com.replaymod.core.versions.MCVer.pushMatrix;
|
||||||
|
import static com.replaymod.core.versions.MCVer.resizeMainWindow;
|
||||||
|
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
|
||||||
|
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
|
||||||
|
|
||||||
|
public class IrisODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
|
||||||
|
|
||||||
|
public static final String SHADER_PACK_NAME = "assets/replaymod/iris/ods";
|
||||||
|
public static IrisODSFrameCapturer INSTANCE;
|
||||||
|
private final CubicPboOpenGlFrameCapturer left, right;
|
||||||
|
private final String prevShaderPack;
|
||||||
|
private int direction;
|
||||||
|
private boolean isLeftEye;
|
||||||
|
|
||||||
|
public IrisODSFrameCapturer(WorldRenderer worldRenderer, final RenderInfo renderInfo, int frameSize) {
|
||||||
|
RenderInfo fakeInfo = new RenderInfo() {
|
||||||
|
private int call;
|
||||||
|
private float partialTicks;
|
||||||
|
@Override
|
||||||
|
public ReadableDimension getFrameSize() {
|
||||||
|
return renderInfo.getFrameSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFramesDone() {
|
||||||
|
return renderInfo.getFramesDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTotalFrames() {
|
||||||
|
return renderInfo.getTotalFrames();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float updateForNextFrame() {
|
||||||
|
if (call++ % 2 == 0) {
|
||||||
|
partialTicks = renderInfo.updateForNextFrame();
|
||||||
|
}
|
||||||
|
return partialTicks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RenderSettings getRenderSettings() {
|
||||||
|
return renderInfo.getRenderSettings();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
left = new CubicStereoFrameCapturer(worldRenderer, fakeInfo, frameSize);
|
||||||
|
right = new CubicStereoFrameCapturer(worldRenderer, fakeInfo, frameSize);
|
||||||
|
|
||||||
|
INSTANCE = this;
|
||||||
|
prevShaderPack = Iris.getIrisConfig().getShaderPackName().orElse(null);
|
||||||
|
setShaderPack(SHADER_PACK_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setShaderPack(String name) {
|
||||||
|
IrisConfig irisConfig = Iris.getIrisConfig();
|
||||||
|
irisConfig.setShaderPackName(name);
|
||||||
|
try {
|
||||||
|
irisConfig.save();
|
||||||
|
Iris.reload();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDirection() {
|
||||||
|
return direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLeftEye() {
|
||||||
|
return isLeftEye;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDone() {
|
||||||
|
return left.isDone() && right.isDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<Channel, ODSOpenGlFrame> process() {
|
||||||
|
isLeftEye = true;
|
||||||
|
Map<Channel, CubicOpenGlFrame> leftChannels = left.process();
|
||||||
|
isLeftEye = false;
|
||||||
|
Map<Channel, CubicOpenGlFrame> rightChannels = right.process();
|
||||||
|
|
||||||
|
if (leftChannels != null && rightChannels != null) {
|
||||||
|
Map<Channel, ODSOpenGlFrame> result = new HashMap<>();
|
||||||
|
for (Channel channel : Channel.values()) {
|
||||||
|
CubicOpenGlFrame leftFrame = leftChannels.get(channel);
|
||||||
|
CubicOpenGlFrame rightFrame = rightChannels.get(channel);
|
||||||
|
if (leftFrame != null && rightFrame != null) {
|
||||||
|
result.put(channel, new ODSOpenGlFrame(leftFrame, rightFrame));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
left.close();
|
||||||
|
right.close();
|
||||||
|
INSTANCE = null;
|
||||||
|
setShaderPack(prevShaderPack);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CubicStereoFrameCapturer extends CubicPboOpenGlFrameCapturer {
|
||||||
|
public CubicStereoFrameCapturer(WorldRenderer worldRenderer, RenderInfo renderInfo, int frameSize) {
|
||||||
|
super(worldRenderer, renderInfo, frameSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected OpenGlFrame renderFrame(int frameId, float partialTicks, CubicOpenGlFrameCapturer.Data captureData) {
|
||||||
|
resizeMainWindow(mc, getFrameWidth(), getFrameHeight());
|
||||||
|
|
||||||
|
pushMatrix();
|
||||||
|
frameBuffer().beginWrite(true);
|
||||||
|
|
||||||
|
GlStateManager.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT
|
||||||
|
//#if MC>=11400
|
||||||
|
, false
|
||||||
|
//#endif
|
||||||
|
);
|
||||||
|
GlStateManager.enableTexture();
|
||||||
|
|
||||||
|
direction = captureData.ordinal();
|
||||||
|
worldRenderer.renderWorld(partialTicks, null);
|
||||||
|
|
||||||
|
frameBuffer().endWrite();
|
||||||
|
popMatrix();
|
||||||
|
|
||||||
|
return captureFrame(frameId, captureData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#endif
|
||||||
@@ -348,7 +348,7 @@ public class GuiRenderQueue extends AbstractGuiPopup<GuiRenderQueue> implements
|
|||||||
renderButton.setEnabled(jobs.size() > 0);
|
renderButton.setEnabled(jobs.size() > 0);
|
||||||
renderButton.setI18nLabel("replaymod.gui.renderqueue.render" + (selected > 0 ? "selected" : "all"));
|
renderButton.setI18nLabel("replaymod.gui.renderqueue.render" + (selected > 0 ? "selected" : "all"));
|
||||||
|
|
||||||
String[] compatError = VideoRenderer.checkCompat();
|
String[] compatError = VideoRenderer.checkCompat(jobs.stream().map(RenderJob::getSettings));
|
||||||
if (compatError != null) {
|
if (compatError != null) {
|
||||||
renderButton.setDisabled().setTooltip(new GuiTooltip().setText(compatError));
|
renderButton.setDisabled().setTooltip(new GuiTooltip().setText(compatError));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -363,7 +363,7 @@ public class GuiRenderSettings extends AbstractGuiPopup<GuiRenderSettings> {
|
|||||||
videoHeight.setTextColor(Colors.RED);
|
videoHeight.setTextColor(Colors.RED);
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] compatError = VideoRenderer.checkCompat();
|
String[] compatError = VideoRenderer.checkCompat(save(false));
|
||||||
if (resolutionError != null) {
|
if (resolutionError != null) {
|
||||||
renderButton.setDisabled().setTooltip(new GuiTooltip().setI18nText(resolutionError));
|
renderButton.setDisabled().setTooltip(new GuiTooltip().setI18nText(resolutionError));
|
||||||
} else if (compatError != null) {
|
} else if (compatError != null) {
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
//#if MC>=11600
|
||||||
|
package com.replaymod.render.mixin;
|
||||||
|
|
||||||
|
import com.replaymod.render.capturer.IrisODSFrameCapturer;
|
||||||
|
import net.coderbot.iris.gl.uniform.UniformHolder;
|
||||||
|
import net.coderbot.iris.gl.uniform.UniformUpdateFrequency;
|
||||||
|
import net.coderbot.iris.uniforms.CommonUniforms;
|
||||||
|
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.ModifyVariable;
|
||||||
|
|
||||||
|
@Pseudo
|
||||||
|
@Mixin(value = CommonUniforms.class, remap = false)
|
||||||
|
public class Mixin_AddIrisOdsShaderUniforms {
|
||||||
|
// Using ModifyVariable, so we only depend on a single argument. Hoping that reduces the chance of breaking changes.
|
||||||
|
@ModifyVariable(method = "generalCommonUniforms", at = @At("HEAD"), argsOnly = true)
|
||||||
|
private static UniformHolder addReplayModOdsUniforms(UniformHolder uniforms) {
|
||||||
|
IrisODSFrameCapturer ods = IrisODSFrameCapturer.INSTANCE;
|
||||||
|
if (ods != null) {
|
||||||
|
uniforms.uniform1b(UniformUpdateFrequency.PER_FRAME, "leftEye", ods::isLeftEye);
|
||||||
|
uniforms.uniform1i(UniformUpdateFrequency.PER_FRAME, "direction", ods::getDirection);
|
||||||
|
}
|
||||||
|
return uniforms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#endif
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
//#if MC>=11600
|
||||||
|
package com.replaymod.render.mixin;
|
||||||
|
|
||||||
|
import com.replaymod.render.capturer.IrisODSFrameCapturer;
|
||||||
|
import net.coderbot.iris.Iris;
|
||||||
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
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.Redirect;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
@Pseudo
|
||||||
|
@Mixin(value = Iris.class, remap = false)
|
||||||
|
public class Mixin_LoadIrisOdsShaderPack {
|
||||||
|
@Redirect(method = "loadExternalShaderpack", at = @At(value = "FIELD", opcode = Opcodes.GETSTATIC, target = "Lnet/coderbot/iris/Iris;SHADERPACKS_DIRECTORY:Ljava/nio/file/Path;"))
|
||||||
|
private static Path loadReplayModOdsPack(String name) {
|
||||||
|
if (IrisODSFrameCapturer.INSTANCE != null && IrisODSFrameCapturer.SHADER_PACK_NAME.equals(name)) {
|
||||||
|
return FabricLoader.getInstance().getModContainer("replaymod")
|
||||||
|
.orElseThrow(() -> new RuntimeException("Failed to get mod container for ReplayMod"))
|
||||||
|
.getRootPath();
|
||||||
|
} else {
|
||||||
|
return Iris.SHADERPACKS_DIRECTORY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#endif
|
||||||
@@ -105,8 +105,14 @@ public class Pipelines {
|
|||||||
ODSToBitmapProcessor processor = new ODSToBitmapProcessor(settings.getVideoWidth(),
|
ODSToBitmapProcessor processor = new ODSToBitmapProcessor(settings.getVideoWidth(),
|
||||||
settings.getVideoHeight(), settings.getSphericalFovX());
|
settings.getVideoHeight(), settings.getSphericalFovX());
|
||||||
|
|
||||||
FrameCapturer<ODSOpenGlFrame> capturer =
|
//#if MC>=11600
|
||||||
new ODSFrameCapturer(worldRenderer, renderInfo, processor.getFrameSize());
|
boolean iris = net.fabricmc.loader.api.FabricLoader.getInstance().isModLoaded("iris");
|
||||||
|
FrameCapturer<ODSOpenGlFrame> capturer = iris
|
||||||
|
? new com.replaymod.render.capturer.IrisODSFrameCapturer(worldRenderer, renderInfo, processor.getFrameSize())
|
||||||
|
: new ODSFrameCapturer(worldRenderer, renderInfo, processor.getFrameSize());
|
||||||
|
//#else
|
||||||
|
//$$ FrameCapturer<ODSOpenGlFrame> capturer = new ODSFrameCapturer(worldRenderer, renderInfo, processor.getFrameSize());
|
||||||
|
//#endif
|
||||||
return new Pipeline<>(worldRenderer, capturer, processor, consumer);
|
return new Pipeline<>(worldRenderer, capturer, processor, consumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,10 +74,12 @@ import java.io.IOException;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.FutureTask;
|
import java.util.concurrent.FutureTask;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static com.google.common.collect.Iterables.getLast;
|
import static com.google.common.collect.Iterables.getLast;
|
||||||
import static com.replaymod.core.versions.MCVer.*;
|
import static com.replaymod.core.versions.MCVer.*;
|
||||||
@@ -699,7 +701,11 @@ public class VideoRenderer implements RenderInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String[] checkCompat() {
|
public static String[] checkCompat(Stream<RenderSettings> settings) {
|
||||||
|
return settings.map(VideoRenderer::checkCompat).filter(Objects::nonNull).findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String[] checkCompat(RenderSettings settings) {
|
||||||
//#if FABRIC>=1
|
//#if FABRIC>=1
|
||||||
if (net.fabricmc.loader.api.FabricLoader.getInstance().isModLoaded("sodium")) {
|
if (net.fabricmc.loader.api.FabricLoader.getInstance().isModLoaded("sodium")) {
|
||||||
return new String[] {
|
return new String[] {
|
||||||
@@ -708,6 +714,17 @@ public class VideoRenderer implements RenderInfo {
|
|||||||
"For now, you need to uninstall Sodium before rendering!"
|
"For now, you need to uninstall Sodium before rendering!"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
//#if MC>=11700
|
||||||
|
//$$ if (settings.getRenderMethod() == RenderSettings.RenderMethod.ODS
|
||||||
|
//$$ && !net.fabricmc.loader.api.FabricLoader.getInstance().isModLoaded("iris")) {
|
||||||
|
//$$ return new String[] {
|
||||||
|
//$$ "ODS export requires Iris to be installed for Minecraft 1.17 and above.",
|
||||||
|
//$$ "Note that it is nevertheless incompatible with other shaders and will simply replace them.",
|
||||||
|
//$$ "Get it from: https://irisshaders.net/",
|
||||||
|
//$$ "Use the Standalone version! Sodium is not currently compatible with rendering!",
|
||||||
|
//$$ };
|
||||||
|
//$$ }
|
||||||
|
//#endif
|
||||||
//#endif
|
//#endif
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ public class GuiReplayViewer extends GuiScreen {
|
|||||||
loadButton.setTooltip(new GuiTooltip().setText(tooltipLines));
|
loadButton.setTooltip(new GuiTooltip().setText(tooltipLines));
|
||||||
loadButton.setEnabled(!jobs.isEmpty());
|
loadButton.setEnabled(!jobs.isEmpty());
|
||||||
|
|
||||||
String[] compatError = VideoRenderer.checkCompat();
|
String[] compatError = VideoRenderer.checkCompat(jobs.stream().map(RenderJob::getSettings));
|
||||||
if (compatError != null) {
|
if (compatError != null) {
|
||||||
loadButton.setDisabled().setTooltip(new GuiTooltip().setText(compatError));
|
loadButton.setDisabled().setTooltip(new GuiTooltip().setText(compatError));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#version 120
|
||||||
|
|
||||||
|
uniform sampler2D texture;
|
||||||
|
uniform sampler2D lightmap;
|
||||||
|
|
||||||
|
varying vec4 color;
|
||||||
|
varying vec2 texcoord;
|
||||||
|
varying vec2 lmcoord;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 lightmap = texture2D(lightmap, lmcoord);
|
||||||
|
vec4 color = texture2D(texture, texcoord) * color;
|
||||||
|
color *= lightmap;
|
||||||
|
gl_FragColor = color;
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
#version 120
|
||||||
|
|
||||||
|
varying vec4 color;
|
||||||
|
varying vec2 texcoord;
|
||||||
|
varying vec2 lmcoord;
|
||||||
|
|
||||||
|
uniform int leftEye;
|
||||||
|
uniform int direction;
|
||||||
|
|
||||||
|
const float eyeDistance = 0.14;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Transform to view space
|
||||||
|
vec4 position = gl_ModelViewMatrix * gl_Vertex;
|
||||||
|
|
||||||
|
// Distort for ODS
|
||||||
|
// O := The origin
|
||||||
|
// P := The current vertex/point
|
||||||
|
// T := Point of tangency with the tangent going through P
|
||||||
|
// Distance between P and O
|
||||||
|
float distPO = sqrt(position.x * position.x + position.z * position.z);
|
||||||
|
float distTO = eyeDistance * 0.5;
|
||||||
|
// Angle between PO and PT
|
||||||
|
float angP = acos(distTO / distPO);
|
||||||
|
// Angle between PO and TO (angle at the origin)
|
||||||
|
float angO = 90.0 - angP;
|
||||||
|
if (leftEye == 0) {
|
||||||
|
angO = -angO;
|
||||||
|
}
|
||||||
|
// The angel of OP within the circle, that is between OP and O(0,1)
|
||||||
|
float angOP = atan(position.x, position.z);
|
||||||
|
// The angle of OT within the circle, that is between OT and O(0,1)
|
||||||
|
float angOT = angO + angOP;
|
||||||
|
// Calculate the vector between O and T and finally move the vertex by that vector
|
||||||
|
position -= vec4(distTO * sin(angOT), 0, distTO * cos(angOT), 0);
|
||||||
|
|
||||||
|
// Rotate for different cubic views
|
||||||
|
float z;
|
||||||
|
if (direction == 0) { // LEFT
|
||||||
|
z = position.z;
|
||||||
|
position.z = position.x;
|
||||||
|
position.x = -z;
|
||||||
|
} else if (direction == 1) { // RIGHT
|
||||||
|
z = position.z;
|
||||||
|
position.z = -position.x;
|
||||||
|
position.x = z;
|
||||||
|
} else if (direction == 2) { // FRONT
|
||||||
|
// No changes required
|
||||||
|
} else if (direction == 3) { // BACK
|
||||||
|
position.x = -position.x;
|
||||||
|
position.z = -position.z;
|
||||||
|
} else if (direction == 4) { // TOP
|
||||||
|
z = position.z;
|
||||||
|
position.z = -position.y;
|
||||||
|
position.y = z;
|
||||||
|
} else if (direction == 5) { // BOTTOM
|
||||||
|
z = position.z;
|
||||||
|
position.z = position.y;
|
||||||
|
position.y = -z;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform to screen space
|
||||||
|
gl_Position = gl_ProjectionMatrix * position;
|
||||||
|
|
||||||
|
// Misc.
|
||||||
|
color = gl_Color;
|
||||||
|
texcoord = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy;
|
||||||
|
lmcoord = (gl_TextureMatrix[1] * gl_MultiTexCoord1).xy;
|
||||||
|
}
|
||||||
@@ -20,6 +20,10 @@
|
|||||||
"Mixin_StabilizeCamera",
|
"Mixin_StabilizeCamera",
|
||||||
"Mixin_Stereoscopic_Camera",
|
"Mixin_Stereoscopic_Camera",
|
||||||
"Mixin_Stereoscopic_HandRenderPass",
|
"Mixin_Stereoscopic_HandRenderPass",
|
||||||
|
//#if MC>=11600
|
||||||
|
"Mixin_AddIrisOdsShaderUniforms",
|
||||||
|
"Mixin_LoadIrisOdsShaderPack",
|
||||||
|
//#endif
|
||||||
//#if MC>=10800
|
//#if MC>=10800
|
||||||
//#if MC>=11500
|
//#if MC>=11500
|
||||||
"Mixin_BlockOnChunkRebuilds",
|
"Mixin_BlockOnChunkRebuilds",
|
||||||
|
|||||||
Reference in New Issue
Block a user