Add ODS support

This commit is contained in:
johni0702
2016-08-22 17:07:23 +02:00
parent a6efbe6a83
commit cd9592cfcb
20 changed files with 491 additions and 18 deletions

View File

@@ -9,7 +9,7 @@ import java.io.File;
@Data
public class RenderSettings {
public enum RenderMethod {
DEFAULT, STEREOSCOPIC, CUBIC, EQUIRECTANGULAR;
DEFAULT, STEREOSCOPIC, CUBIC, EQUIRECTANGULAR, ODS;
@Override
public String toString() {

View File

@@ -11,6 +11,7 @@ public class CubicOpenGlFrameCapturer extends OpenGlFrameCapturer<CubicOpenGlFra
public CubicOpenGlFrameCapturer(WorldRenderer worldRenderer, RenderInfo renderInfo, int frameSize) {
super(worldRenderer, renderInfo);
this.frameSize = frameSize;
worldRenderer.setOmnidirectional(true);
}
@Override

View File

@@ -10,6 +10,7 @@ public class CubicPboOpenGlFrameCapturer extends
public CubicPboOpenGlFrameCapturer(WorldRenderer worldRenderer, RenderInfo renderInfo, int frameSize) {
super(worldRenderer, renderInfo, CubicOpenGlFrameCapturer.Data.class, frameSize * frameSize);
this.frameSize = frameSize;
worldRenderer.setOmnidirectional(true);
}
@Override

View File

@@ -0,0 +1,161 @@
package com.replaymod.render.capturer;
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.FrameCapturer;
import com.replaymod.render.shader.Program;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.crash.CrashReport;
import net.minecraft.util.ReportedException;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.util.Dimension;
import org.lwjgl.util.ReadableDimension;
import java.io.IOException;
import static net.minecraft.client.renderer.GlStateManager.*;
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
public class ODSFrameCapturer implements FrameCapturer<ODSOpenGlFrame> {
private static final ResourceLocation vertexResource = new ResourceLocation("replaymod", "shader/ods.vert");
private static final ResourceLocation fragmentResource = new ResourceLocation("replaymod", "shader/ods.frag");
private final CubicPboOpenGlFrameCapturer left, right;
private final Program shaderProgram;
private final Program.Uniform directionVariable;
private final Program.Uniform leftEyeVariable;
private final BooleanState[] previousStates = new BooleanState[3];
private final BooleanState previousFogState;
public ODSFrameCapturer(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 getTotalFrames() {
return renderInfo.getTotalFrames();
}
@Override
public float updateForNextFrame() {
if (call++ % 2 == 0) {
shaderProgram.stopUsing();
partialTicks = renderInfo.updateForNextFrame();
shaderProgram.use();
}
return partialTicks;
}
@Override
public RenderSettings getRenderSettings() {
return renderInfo.getRenderSettings();
}
};
left = new CubicStereoFrameCapturer(worldRenderer, fakeInfo, frameSize);
right = new CubicStereoFrameCapturer(worldRenderer, fakeInfo, frameSize);
try {
shaderProgram = new Program(vertexResource, fragmentResource);
shaderProgram.use();
leftEyeVariable = shaderProgram.getUniformVariable("leftEye");
directionVariable = shaderProgram.getUniformVariable("direction");
setTexture("texture", 0);
setTexture("lightMap", 1);
linkState(0, "textureEnabled");
linkState(1, "lightMapEnabled");
linkState(2, "hurtTextureEnabled");
final Program.Uniform uniform = shaderProgram.getUniformVariable("fogEnabled");
previousFogState = GlStateManager.fogState.field_179049_a;
uniform.set(previousFogState.currentState);
GlStateManager.fogState.field_179049_a = new BooleanState(previousFogState.capability) {
@Override
public void setState(boolean state) {
super.setState(state);
uniform.set(state);
}
};
shaderProgram.stopUsing();
} catch (Exception e) {
throw new ReportedException(CrashReport.makeCrashReport(e, "Creating ODS shaders"));
}
}
private void setTexture(String texture, int i) {
shaderProgram.getUniformVariable(texture).set(i);
}
private void linkState(int id, String var) {
final Program.Uniform uniform = shaderProgram.getUniformVariable(var);
previousStates[id] = GlStateManager.textureState[id].texture2DState;
uniform.set(previousStates[id].currentState);
GlStateManager.textureState[id].texture2DState = new BooleanState(previousStates[id].capability) {
@Override
public void setState(boolean state) {
super.setState(state);
uniform.set(state);
}
};
}
@Override
public boolean isDone() {
return left.isDone() && right.isDone();
}
@Override
public ODSOpenGlFrame process() {
shaderProgram.use();
leftEyeVariable.set(true);
CubicOpenGlFrame leftFrame = left.process();
leftEyeVariable.set(false);
CubicOpenGlFrame rightFrame = right.process();
shaderProgram.stopUsing();
if (leftFrame != null && rightFrame != null) {
return new ODSOpenGlFrame(leftFrame, rightFrame);
}
return null;
}
@Override
public void close() throws IOException {
left.close();
right.close();
shaderProgram.delete();
for (int i = 0; i < 3; i++) {
GlStateManager.textureState[i].texture2DState = previousStates[i];
}
GlStateManager.fogState.field_179049_a = previousFogState;
}
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) {
pushMatrix();
frameBuffer().bindFramebuffer(true);
clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
enableTexture2D();
directionVariable.set(captureData.ordinal());
worldRenderer.renderWorld(new Dimension(getFrameWidth(), getFrameHeight()), partialTicks, null);
frameBuffer().unbindFramebuffer();
popMatrix();
return captureFrame(frameId, captureData);
}
}
}

View File

@@ -6,4 +6,5 @@ import java.io.Closeable;
public interface WorldRenderer extends Closeable {
void renderWorld(ReadableDimension displaySize, float partialTicks, CaptureData data);
void setOmnidirectional(boolean omnidirectional);
}

View File

@@ -0,0 +1,21 @@
package com.replaymod.render.frame;
import com.replaymod.render.rendering.Frame;
import lombok.Getter;
import org.apache.commons.lang3.Validate;
public class ODSOpenGlFrame implements Frame {
@Getter
private final CubicOpenGlFrame left, right;
public ODSOpenGlFrame(CubicOpenGlFrame left, CubicOpenGlFrame right) {
Validate.isTrue(left.getFrameId() == right.getFrameId(), "Frame ids do not match.");
this.left = left;
this.right = right;
}
@Override
public int getFrameId() {
return left.getFrameId();
}
}

View File

@@ -286,6 +286,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
switch (renderMethodDropdown.getSelectedValue()) {
case CUBIC:
case EQUIRECTANGULAR:
case ODS:
stabilizePanel.forEach(IGuiCheckbox.class).setEnabled();
break;
default:
@@ -294,7 +295,8 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
// Enable/Disable inject metadata checkbox
if (encodingPresetDropdown.getSelectedValue().getFileExtension().equals("mp4")
&& renderMethodDropdown.getSelectedValue() == RenderSettings.RenderMethod.EQUIRECTANGULAR) {
&& (renderMethodDropdown.getSelectedValue() == RenderSettings.RenderMethod.EQUIRECTANGULAR
|| renderMethodDropdown.getSelectedValue() == RenderSettings.RenderMethod.ODS)) {
inject360Metadata.setEnabled().setTooltip(null);
} else {
inject360Metadata.setDisabled().setTooltip(new GuiTooltip().setColor(Colors.RED)
@@ -325,6 +327,10 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
&& (videoWidth / 2 != videoHeight || videoWidth % 2 != 0)) {
return "replaymod.gui.rendersettings.customresolution.warning.equirectangular";
}
if (method == RenderSettings.RenderMethod.ODS
&& videoWidth != videoHeight) {
return "replaymod.gui.rendersettings.customresolution.warning.ods";
}
return null;
}

View File

@@ -20,6 +20,8 @@ public class EntityRendererHandler implements WorldRenderer {
public CaptureData data;
public boolean omnidirectional;
public EntityRendererHandler(RenderSettings settings) {
this.settings = settings;
@@ -64,6 +66,11 @@ public class EntityRendererHandler implements WorldRenderer {
((IEntityRenderer) mc.entityRenderer).replayModRender_setHandler(null);
}
@Override
public void setOmnidirectional(boolean omnidirectional) {
this.omnidirectional = omnidirectional;
}
public static final class NoCullingClippingHelper extends ClippingHelper {
@Override
public boolean isBoxInFrustum(double p_78553_1_, double p_78553_3_, double p_78553_5_, double p_78553_7_, double p_78553_9_, double p_78553_11_) {

View File

@@ -29,9 +29,12 @@ public class MetadataInjector {
"<GSpherical:Stitched>true</GSpherical:Stitched> " +
"<GSpherical:StitchingSoftware>"+STITCHING_SOFTWARE+"</GSpherical:StitchingSoftware> " +
"<GSpherical:ProjectionType>equirectangular</GSpherical:ProjectionType> ";
private static final String STEREO_XML_CONTENTS = "<GSpherical:StereoMode>top-bottom</GSpherical:StereoMode>";
private static final String SPHERICAL_XML_FOOTER = "</rdf:SphericalVideo>";
private static final String XML_360_METADATA = SPHERICAL_XML_HEADER + SPHERICAL_XML_CONTENTS + SPHERICAL_XML_FOOTER;
private static final String XML_ODS_METADATA = SPHERICAL_XML_HEADER + SPHERICAL_XML_CONTENTS
+ STEREO_XML_CONTENTS + SPHERICAL_XML_FOOTER;
/**
* These bytes are taken from the variable 'spherical_uuid_id'
@@ -45,11 +48,16 @@ public class MetadataInjector {
};
private static final byte[] BYTES_360_METADATA = Bytes.concat(UUID_BYTES, XML_360_METADATA.getBytes());
private static final byte[] BYTES_ODS_METADATA = Bytes.concat(UUID_BYTES, XML_ODS_METADATA.getBytes());
public static void inject360Metadata(File videoFile) {
writeMetadata(videoFile, BYTES_360_METADATA);
}
public static void injectODSMetadata(File videoFile) {
writeMetadata(videoFile, BYTES_ODS_METADATA);
}
private static void writeMetadata(File videoFile, byte[] metadata) {
File tempFile = null;
FileOutputStream videoFileOutputStream = null;

View File

@@ -1,6 +1,5 @@
package com.replaymod.render.mixin;
import com.replaymod.render.capturer.CubicOpenGlFrameCapturer;
import com.replaymod.render.hooks.EntityRendererHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.client.particle.EffectRenderer;
@@ -28,7 +27,7 @@ public abstract class MixinEffectRenderer {
private void renderParticle(EntityFX fx, WorldRenderer worldRenderer, Entity view, float partialTicks,
float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler();
if (handler != null && handler.data instanceof CubicOpenGlFrameCapturer.Data) {
if (handler != null && handler.omnidirectional) {
// Align all particles towards the camera
double dx = fx.prevPosX + (fx.posX - fx.prevPosX) * partialTicks - view.posX;
double dy = fx.prevPosY + (fx.posY - fx.prevPosY) * partialTicks - view.posY;

View File

@@ -109,7 +109,7 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti
@Inject(method = "renderHand", at = @At("HEAD"), cancellable = true)
private void replayModRender_renderSpectatorHand(float partialTicks, int renderPass, CallbackInfo ci) {
if (replayModRender_handler != null) {
if (replayModRender_handler.data instanceof CubicOpenGlFrameCapturer.Data) {
if (replayModRender_handler.omnidirectional) {
ci.cancel();
return; // No spectator hands during 360° view, we wouldn't even know where to put it
}
@@ -204,7 +204,7 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti
@Override
public void replayModRender_gluPerspective(float fovY, float aspect, float zNear, float zFar) {
if (replayModRender_getHandler() != null && replayModRender_getHandler().data instanceof CubicOpenGlFrameCapturer.Data) {
if (replayModRender_getHandler() != null && replayModRender_getHandler().omnidirectional) {
fovY = 90;
aspect = 1;
}
@@ -234,7 +234,8 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti
GlStateManager.rotate(-90, 1.0F, 0.0F, 0.0F);
break;
}
}
if (replayModRender_getHandler() != null && replayModRender_getHandler().omnidirectional) {
// Minecraft goes back a little so we have to revert that
GlStateManager.translate(0.0F, 0.0F, 0.1F);
}

View File

@@ -1,6 +1,5 @@
package com.replaymod.render.mixin;
import com.replaymod.render.capturer.CubicOpenGlFrameCapturer;
import com.replaymod.render.hooks.EntityRendererHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.entity.RenderManager;
@@ -22,7 +21,7 @@ public abstract class MixinRenderManager {
@Inject(method = "doRenderEntity", at = @At("HEAD"))
private void replayModRender_reorientForCubicRendering(Entity entity, double dx, double dy, double dz, float iDoNotKnow, float partialTicks, boolean iDoNotCare, CallbackInfoReturnable ci) {
EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler();
if (handler != null && handler.data instanceof CubicOpenGlFrameCapturer.Data) {
if (handler != null && handler.omnidirectional) {
double pitch = -Math.atan2(dy, Math.sqrt(dx * dx + dz * dz));
double yaw = -Math.atan2(dx, dz);
playerViewX = (float) Math.toDegrees(pitch);

View File

@@ -0,0 +1,37 @@
package com.replaymod.render.processor;
import com.replaymod.render.frame.ODSOpenGlFrame;
import com.replaymod.render.frame.RGBFrame;
import com.replaymod.render.utils.ByteBufferPool;
import org.lwjgl.util.Dimension;
import org.lwjgl.util.ReadableDimension;
import java.io.IOException;
import java.nio.ByteBuffer;
public class ODSToRGBProcessor extends AbstractFrameProcessor<ODSOpenGlFrame, RGBFrame> {
private final EquirectangularToRGBProcessor processor;
public ODSToRGBProcessor(int frameSize) {
processor = new EquirectangularToRGBProcessor(frameSize);
}
@Override
public RGBFrame process(ODSOpenGlFrame rawFrame) {
RGBFrame leftFrame = processor.process(rawFrame.getLeft());
RGBFrame rightFrame = processor.process(rawFrame.getRight());
ReadableDimension size = new Dimension(leftFrame.getSize().getWidth(), leftFrame.getSize().getHeight() * 2);
ByteBuffer result = ByteBufferPool.allocate(size.getWidth() * size.getHeight() * 3);
result.put(leftFrame.getByteBuffer());
result.put(rightFrame.getByteBuffer());
result.rewind();
ByteBufferPool.release(leftFrame.getByteBuffer());
ByteBufferPool.release(rightFrame.getByteBuffer());
return new RGBFrame(rawFrame.getFrameId(), size, result);
}
@Override
public void close() throws IOException {
processor.close();
}
}

View File

@@ -2,15 +2,9 @@ package com.replaymod.render.rendering;
import com.replaymod.render.RenderSettings;
import com.replaymod.render.capturer.*;
import com.replaymod.render.frame.CubicOpenGlFrame;
import com.replaymod.render.frame.OpenGlFrame;
import com.replaymod.render.frame.RGBFrame;
import com.replaymod.render.frame.StereoscopicOpenGlFrame;
import com.replaymod.render.frame.*;
import com.replaymod.render.hooks.EntityRendererHandler;
import com.replaymod.render.processor.CubicToRGBProcessor;
import com.replaymod.render.processor.EquirectangularToRGBProcessor;
import com.replaymod.render.processor.OpenGlToRGBProcessor;
import com.replaymod.render.processor.StereoscopicToRGBProcessor;
import com.replaymod.render.processor.*;
import com.replaymod.render.utils.PixelBufferObject;
import lombok.experimental.UtilityClass;
@@ -26,6 +20,8 @@ public class Pipelines {
return newCubicPipeline(renderInfo, consumer);
case EQUIRECTANGULAR:
return newEquirectangularPipeline(renderInfo, consumer);
case ODS:
return newODSPipeline(renderInfo, consumer);
}
throw new UnsupportedOperationException("Unknown method: " + method);
}
@@ -73,4 +69,11 @@ public class Pipelines {
}
return new Pipeline<>(capturer, new EquirectangularToRGBProcessor(settings.getVideoWidth() / 4), consumer);
}
public static Pipeline<ODSOpenGlFrame, RGBFrame> newODSPipeline(RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
RenderSettings settings = renderInfo.getRenderSettings();
FrameCapturer<ODSOpenGlFrame> capturer =
new ODSFrameCapturer(new EntityRendererHandler(settings), renderInfo, settings.getVideoWidth() / 4);
return new Pipeline<>(capturer, new ODSToRGBProcessor(settings.getVideoWidth() / 4), consumer);
}
}

View File

@@ -113,7 +113,11 @@ public class VideoRenderer implements RenderInfo {
renderingPipeline.run();
if (settings.isInject360Metadata()) {
MetadataInjector.inject360Metadata(settings.getOutputFile());
if (settings.getRenderMethod() == RenderSettings.RenderMethod.ODS) {
MetadataInjector.injectODSMetadata(settings.getOutputFile());
} else {
MetadataInjector.inject360Metadata(settings.getOutputFile());
}
}
finish();

View File

@@ -0,0 +1,102 @@
package com.replaymod.render.shader;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.IResource;
import net.minecraft.util.ResourceLocation;
import org.apache.commons.io.IOUtils;
import org.lwjgl.opengl.ARBFragmentShader;
import org.lwjgl.opengl.ARBShaderObjects;
import org.lwjgl.opengl.ARBVertexShader;
import org.lwjgl.opengl.GL11;
import java.io.InputStream;
import static org.lwjgl.opengl.ARBShaderObjects.*;
public class Program {
private final int program;
public Program(ResourceLocation vertexShader, ResourceLocation fragmentShader) throws Exception {
int vertShader = createShader(vertexShader, ARBVertexShader.GL_VERTEX_SHADER_ARB);
int fragShader = createShader(fragmentShader, ARBFragmentShader.GL_FRAGMENT_SHADER_ARB);
program = glCreateProgramObjectARB();
if (program == 0) {
throw new Exception("glCreateProgramObjectARB failed");
}
glAttachObjectARB(program, vertShader);
glAttachObjectARB(program, fragShader);
glLinkProgramARB(program);
if (glGetObjectParameteriARB(program, GL_OBJECT_LINK_STATUS_ARB) == GL11.GL_FALSE) {
throw new Exception("Error linking: " + getLogInfo(program));
}
glValidateProgramARB(program);
if (glGetObjectParameteriARB(program, GL_OBJECT_VALIDATE_STATUS_ARB) == GL11.GL_FALSE) {
throw new Exception("Error validating: " + getLogInfo(program));
}
}
private int createShader(ResourceLocation resourceLocation, int shaderType) throws Exception {
int shader = 0;
try {
shader = glCreateShaderObjectARB(shaderType);
if(shader == 0)
throw new Exception("glCreateShaderObjectARB failed");
IResource resource = Minecraft.getMinecraft().getResourceManager().getResource(resourceLocation);
try (InputStream is = resource.getInputStream()) {
glShaderSourceARB(shader, IOUtils.toString(is));
}
glCompileShaderARB(shader);
if (glGetObjectParameteriARB(shader, GL_OBJECT_COMPILE_STATUS_ARB) == GL11.GL_FALSE)
throw new RuntimeException("Error creating shader: " + getLogInfo(shader));
return shader;
}
catch(Exception exc) {
glDeleteObjectARB(shader);
throw exc;
}
}
private static String getLogInfo(int obj) {
return glGetInfoLogARB(obj, glGetObjectParameteriARB(obj, GL_OBJECT_INFO_LOG_LENGTH_ARB));
}
public void use() {
ARBShaderObjects.glUseProgramObjectARB(program);
}
public void stopUsing() {
ARBShaderObjects.glUseProgramObjectARB(0);
}
public void delete() {
ARBShaderObjects.glDeleteObjectARB(program);
}
public Uniform getUniformVariable(String name) {
return new Uniform(ARBShaderObjects.glGetUniformLocationARB(program, name));
}
public class Uniform {
private final int location;
public Uniform(int location) {
this.location = location;
}
public void set(boolean bool) {
ARBShaderObjects.glUniform1iARB(location, bool ? GL11.GL_TRUE : GL11.GL_FALSE);
}
public void set(int integer) {
ARBShaderObjects.glUniform1iARB(location, integer);
}
}
}