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