Implement ODS rendering via Iris Shader for MC 1.17

This commit is contained in:
Jonas Herzig
2021-06-29 17:57:11 +02:00
parent f462a5b391
commit 7ab2edd738
12 changed files with 333 additions and 6 deletions

View File

@@ -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

View File

@@ -348,7 +348,7 @@ public class GuiRenderQueue extends AbstractGuiPopup<GuiRenderQueue> implements
renderButton.setEnabled(jobs.size() > 0);
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) {
renderButton.setDisabled().setTooltip(new GuiTooltip().setText(compatError));
}

View File

@@ -363,7 +363,7 @@ public class GuiRenderSettings extends AbstractGuiPopup<GuiRenderSettings> {
videoHeight.setTextColor(Colors.RED);
}
String[] compatError = VideoRenderer.checkCompat();
String[] compatError = VideoRenderer.checkCompat(save(false));
if (resolutionError != null) {
renderButton.setDisabled().setTooltip(new GuiTooltip().setI18nText(resolutionError));
} else if (compatError != null) {

View File

@@ -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

View File

@@ -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

View File

@@ -105,8 +105,14 @@ public class Pipelines {
ODSToBitmapProcessor processor = new ODSToBitmapProcessor(settings.getVideoWidth(),
settings.getVideoHeight(), settings.getSphericalFovX());
FrameCapturer<ODSOpenGlFrame> capturer =
new ODSFrameCapturer(worldRenderer, renderInfo, processor.getFrameSize());
//#if MC>=11600
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);
}

View File

@@ -74,10 +74,12 @@ import java.io.IOException;
import java.util.Collection;
import java.util.EnumMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.stream.Stream;
import static com.google.common.collect.Iterables.getLast;
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 (net.fabricmc.loader.api.FabricLoader.getInstance().isModLoaded("sodium")) {
return new String[] {
@@ -708,6 +714,17 @@ public class VideoRenderer implements RenderInfo {
"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
return null;
}

View File

@@ -260,7 +260,7 @@ public class GuiReplayViewer extends GuiScreen {
loadButton.setTooltip(new GuiTooltip().setText(tooltipLines));
loadButton.setEnabled(!jobs.isEmpty());
String[] compatError = VideoRenderer.checkCompat();
String[] compatError = VideoRenderer.checkCompat(jobs.stream().map(RenderJob::getSettings));
if (compatError != null) {
loadButton.setDisabled().setTooltip(new GuiTooltip().setText(compatError));
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -20,6 +20,10 @@
"Mixin_StabilizeCamera",
"Mixin_Stereoscopic_Camera",
"Mixin_Stereoscopic_HandRenderPass",
//#if MC>=11600
"Mixin_AddIrisOdsShaderUniforms",
"Mixin_LoadIrisOdsShaderPack",
//#endif
//#if MC>=10800
//#if MC>=11500
"Mixin_BlockOnChunkRebuilds",