/*
* Copyright 2013 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.rendering.opengl;
import com.google.common.collect.Maps;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.ARBHalfFloatPixel;
import org.lwjgl.opengl.ARBTextureFloat;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.EXTPackedDepthStencil;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL20;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.asset.Assets;
import org.terasology.config.Config;
import org.terasology.config.RenderingConfig;
import org.terasology.editor.EditorRange;
import org.terasology.monitoring.PerformanceMonitor;
import org.terasology.registry.CoreRegistry;
import org.terasology.engine.GameEngine;
import org.terasology.engine.paths.PathManager;
import org.terasology.math.TeraMath;
import org.terasology.rendering.assets.material.Material;
import org.terasology.rendering.oculusVr.OculusVrHelper;
import org.terasology.rendering.world.WorldRenderer;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Map;
import static org.lwjgl.opengl.EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT;
import static org.lwjgl.opengl.EXTFramebufferObject.GL_COLOR_ATTACHMENT1_EXT;
import static org.lwjgl.opengl.EXTFramebufferObject.GL_COLOR_ATTACHMENT2_EXT;
import static org.lwjgl.opengl.EXTFramebufferObject.GL_DEPTH_ATTACHMENT_EXT;
import static org.lwjgl.opengl.EXTFramebufferObject.GL_FRAMEBUFFER_COMPLETE_EXT;
import static org.lwjgl.opengl.EXTFramebufferObject.GL_FRAMEBUFFER_EXT;
import static org.lwjgl.opengl.EXTFramebufferObject.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT;
import static org.lwjgl.opengl.EXTFramebufferObject.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT;
import static org.lwjgl.opengl.EXTFramebufferObject.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT;
import static org.lwjgl.opengl.EXTFramebufferObject.GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT;
import static org.lwjgl.opengl.EXTFramebufferObject.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT;
import static org.lwjgl.opengl.EXTFramebufferObject.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT;
import static org.lwjgl.opengl.EXTFramebufferObject.GL_FRAMEBUFFER_UNSUPPORTED_EXT;
import static org.lwjgl.opengl.EXTFramebufferObject.GL_RENDERBUFFER_EXT;
import static org.lwjgl.opengl.EXTFramebufferObject.GL_STENCIL_ATTACHMENT_EXT;
import static org.lwjgl.opengl.EXTFramebufferObject.glBindFramebufferEXT;
import static org.lwjgl.opengl.EXTFramebufferObject.glBindRenderbufferEXT;
import static org.lwjgl.opengl.EXTFramebufferObject.glCheckFramebufferStatusEXT;
import static org.lwjgl.opengl.EXTFramebufferObject.glDeleteFramebuffersEXT;
import static org.lwjgl.opengl.EXTFramebufferObject.glDeleteRenderbuffersEXT;
import static org.lwjgl.opengl.EXTFramebufferObject.glFramebufferRenderbufferEXT;
import static org.lwjgl.opengl.EXTFramebufferObject.glFramebufferTexture2DEXT;
import static org.lwjgl.opengl.EXTFramebufferObject.glGenFramebuffersEXT;
import static org.lwjgl.opengl.EXTFramebufferObject.glGenRenderbuffersEXT;
import static org.lwjgl.opengl.EXTFramebufferObject.glRenderbufferStorageEXT;
import static org.lwjgl.opengl.EXTPixelBufferObject.GL_PIXEL_PACK_BUFFER_EXT;
import static org.lwjgl.opengl.EXTPixelBufferObject.GL_STREAM_READ_ARB;
import static org.lwjgl.opengl.EXTPixelBufferObject.glBindBufferARB;
import static org.lwjgl.opengl.EXTPixelBufferObject.glBufferDataARB;
import static org.lwjgl.opengl.EXTPixelBufferObject.glGenBuffersARB;
import static org.lwjgl.opengl.EXTPixelBufferObject.glMapBufferARB;
import static org.lwjgl.opengl.EXTPixelBufferObject.glUnmapBufferARB;
import static org.lwjgl.opengl.GL11.GL_ALWAYS;
import static org.lwjgl.opengl.GL11.GL_BACK;
import static org.lwjgl.opengl.GL11.GL_BLEND;
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_CULL_FACE;
import static org.lwjgl.opengl.GL11.GL_DECR;
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_DEPTH_TEST;
import static org.lwjgl.opengl.GL11.GL_FRONT;
import static org.lwjgl.opengl.GL11.GL_INCR;
import static org.lwjgl.opengl.GL11.GL_KEEP;
import static org.lwjgl.opengl.GL11.GL_MODELVIEW;
import static org.lwjgl.opengl.GL11.GL_NOTEQUAL;
import static org.lwjgl.opengl.GL11.GL_ONE;
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_PROJECTION;
import static org.lwjgl.opengl.GL11.GL_QUADS;
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_STENCIL_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_STENCIL_TEST;
import static org.lwjgl.opengl.GL11.glBegin;
import static org.lwjgl.opengl.GL11.glBlendFunc;
import static org.lwjgl.opengl.GL11.glCallList;
import static org.lwjgl.opengl.GL11.glClear;
import static org.lwjgl.opengl.GL11.glColor4f;
import static org.lwjgl.opengl.GL11.glCullFace;
import static org.lwjgl.opengl.GL11.glDepthMask;
import static org.lwjgl.opengl.GL11.glDisable;
import static org.lwjgl.opengl.GL11.glEnable;
import static org.lwjgl.opengl.GL11.glEnd;
import static org.lwjgl.opengl.GL11.glEndList;
import static org.lwjgl.opengl.GL11.glGenLists;
import static org.lwjgl.opengl.GL11.glGenTextures;
import static org.lwjgl.opengl.GL11.glLoadIdentity;
import static org.lwjgl.opengl.GL11.glMatrixMode;
import static org.lwjgl.opengl.GL11.glNewList;
import static org.lwjgl.opengl.GL11.glPopMatrix;
import static org.lwjgl.opengl.GL11.glPushMatrix;
import static org.lwjgl.opengl.GL11.glReadPixels;
import static org.lwjgl.opengl.GL11.glStencilFunc;
import static org.lwjgl.opengl.GL11.glTexCoord2d;
import static org.lwjgl.opengl.GL11.glVertex3i;
import static org.lwjgl.opengl.GL11.glViewport;
import static org.lwjgl.opengl.GL15.GL_READ_ONLY;
import static org.lwjgl.opengl.GL20.glStencilOpSeparate;
/**
* The Default Rendering Process class.
*
* @author Benjamin Glatzel <benjamin.glatzel@me.com>
*/
public class DefaultRenderingProcess {
private static final Logger logger = LoggerFactory.getLogger(DefaultRenderingProcess.class);
private static DefaultRenderingProcess instance;
/* PROPERTIES */
@EditorRange(min = 0.0f, max = 10.0f)
private float hdrExposureDefault = 2.5f;
@EditorRange(min = 0.0f, max = 10.0f)
private float hdrMaxExposure = 8.0f;
@EditorRange(min = 0.0f, max = 10.0f)
private float hdrMaxExposureNight = 8.0f;
@EditorRange(min = 0.0f, max = 10.0f)
private float hdrMinExposure = 1.0f;
@EditorRange(min = 0.0f, max = 4.0f)
private float hdrTargetLuminance = 1.0f;
@EditorRange(min = 0.0f, max = 0.5f)
private float hdrExposureAdjustmentSpeed = 0.05f;
@EditorRange(min = 0.0f, max = 5.0f)
private float bloomHighPassThreshold = 0.75f;
@EditorRange(min = 0.0f, max = 32.0f)
private float bloomBlurRadius = 12.0f;
@EditorRange(min = 0.0f, max = 16.0f)
private float overallBlurRadiusFactor = 0.8f;
/* HDR */
private float currentExposure = 2.0f;
private float currentSceneLuminance = 1.0f;
private PBO readBackPBOFront;
private PBO readBackPBOBack;
private PBO readBackPBOCurrent;
/* RTs */
private int rtFullWidth;
private int rtFullHeight;
private int rtWidth2;
private int rtHeight2;
private int rtWidth4;
private int rtHeight4;
private int rtWidth8;
private int rtHeight8;
private int rtWidth16;
private int rtHeight16;
private int rtWidth32;
private int rtHeight32;
private int overwriteRtWidth;
private int overwriteRtHeight;
private String currentlyBoundFboName = "";
private FBO currentlyBoundFbo;
//private int currentlyBoundTextureId = -1;
public enum FBOType {
DEFAULT,
HDR,
NO_COLOR
}
/* VARIOUS */
private boolean takeScreenshot;
private int displayListQuad = -1;
private Config config = CoreRegistry.get(Config.class);
public enum StereoRenderState {
MONO,
OCULUS_LEFT_EYE,
OCULUS_RIGHT_EYE
}
private Map<String, FBO> fboLookup = Maps.newHashMap();
public DefaultRenderingProcess() {
initialize();
}
/**
* Returns (and creates – if necessary) the static instance
* of this helper class.
*
* @return The instance
*/
public static DefaultRenderingProcess getInstance() {
if (instance == null) {
instance = new DefaultRenderingProcess();
}
return instance;
}
public void initialize() {
createOrUpdateFullscreenFbos();
createFBO("scene16", 16, 16, FBOType.DEFAULT, false, false);
createFBO("scene8", 8, 8, FBOType.DEFAULT, false, false);
createFBO("scene4", 4, 4, FBOType.DEFAULT, false, false);
createFBO("scene2", 2, 2, FBOType.DEFAULT, false, false);
createFBO("scene1", 1, 1, FBOType.DEFAULT, false, false);
readBackPBOFront = new PBO();
readBackPBOBack = new PBO();
readBackPBOFront.init(1, 1);
readBackPBOBack.init(1, 1);
readBackPBOCurrent = readBackPBOFront;
}
/**
* Creates the scene FBOs and updates them according to the size of the viewport. The current size
* provided by the display class is only used if the parameters overwriteRTWidth and overwriteRTHeight are set
* to zero.
*/
private void createOrUpdateFullscreenFbos() {
rtFullWidth = overwriteRtWidth;
rtFullHeight = overwriteRtHeight;
if (overwriteRtWidth == 0) {
rtFullWidth = org.lwjgl.opengl.Display.getWidth();
}
if (overwriteRtHeight == 0) {
rtFullHeight = org.lwjgl.opengl.Display.getHeight();
}
RenderingConfig renderingConfig = CoreRegistry.get(Config.class).getRendering();
rtFullWidth *= renderingConfig.getFboScale() / 100f;
rtFullHeight *= renderingConfig.getFboScale() / 100f;
if (renderingConfig.isOculusVrSupport()) {
if (overwriteRtWidth == 0) {
rtFullWidth *= OculusVrHelper.getScaleFactor();
}
if (overwriteRtHeight == 0) {
rtFullHeight *= OculusVrHelper.getScaleFactor();
}
}
rtWidth2 = rtFullWidth / 2;
rtHeight2 = rtFullHeight / 2;
rtWidth4 = rtWidth2 / 2;
rtHeight4 = rtHeight2 / 2;
rtWidth8 = rtWidth4 / 2;
rtHeight8 = rtHeight4 / 2;
rtWidth16 = rtHeight8 / 2;
rtHeight16 = rtWidth8 / 2;
rtWidth32 = rtHeight16 / 2;
rtHeight32 = rtWidth16 / 2;
FBO scene = fboLookup.get("sceneOpaque");
final boolean recreate = scene == null || (scene.width != rtFullWidth || scene.height != rtFullHeight);
if (!recreate) {
return;
}
/*
TODO: switch from createFBO() calls to
new FBOBuilder("title", width, height, FBOType.HDR)
.useDepthBuffer()
.useNormalBuffer()
.useLightBuffer()
.create()
to improve readability
*/
createFBO("sceneOpaque", rtFullWidth, rtFullHeight, FBOType.HDR, true, true, true, true);
createFBO("sceneOpaquePingPong", rtFullWidth, rtFullHeight, FBOType.HDR, true, true, true, true);
createFBO("sceneReflectiveRefractive", rtFullWidth, rtFullHeight, FBOType.HDR, false, true);
attachDepthBufferToFbo("sceneOpaque", "sceneReflectiveRefractive");
createFBO("sceneReflected", rtWidth2, rtHeight2, FBOType.DEFAULT, true);
createFBO("sceneShadowMap", config.getRendering().getShadowMapResolution(), config.getRendering().getShadowMapResolution(), FBOType.NO_COLOR, true, false);
createFBO("scenePrePost", rtFullWidth, rtFullHeight, FBOType.HDR);
createFBO("sceneToneMapped", rtFullWidth, rtFullHeight, FBOType.HDR);
createFBO("sceneFinal", rtFullWidth, rtFullHeight, FBOType.DEFAULT);
createFBO("sobel", rtFullWidth, rtFullHeight, FBOType.DEFAULT);
createFBO("ssao", rtFullWidth, rtFullHeight, FBOType.DEFAULT);
createFBO("ssaoBlurred", rtFullWidth, rtFullHeight, FBOType.DEFAULT);
createFBO("lightShafts", rtWidth2, rtHeight2, FBOType.DEFAULT);
createFBO("sceneHighPass", rtFullWidth, rtFullHeight, FBOType.DEFAULT);
createFBO("sceneBloom0", rtWidth2, rtHeight2, FBOType.DEFAULT);
createFBO("sceneBloom1", rtWidth4, rtHeight4, FBOType.DEFAULT);
createFBO("sceneBloom2", rtWidth8, rtHeight8, FBOType.DEFAULT);
createFBO("sceneBlur0", rtWidth2, rtHeight2, FBOType.DEFAULT);
createFBO("sceneBlur1", rtWidth2, rtHeight2, FBOType.DEFAULT);
createFBO("sceneSkyBand0", rtWidth16, rtHeight16, FBOType.DEFAULT);
createFBO("sceneSkyBand1", rtWidth32, rtHeight32, FBOType.DEFAULT);
}
public void deleteFBO(String title) {
if (fboLookup.containsKey(title)) {
FBO fbo = fboLookup.get(title);
glDeleteFramebuffersEXT(fbo.fboId);
glDeleteRenderbuffersEXT(fbo.depthStencilRboId);
GL11.glDeleteTextures(fbo.normalsTextureId);
GL11.glDeleteTextures(fbo.depthStencilTextureId);
GL11.glDeleteTextures(fbo.textureId);
}
}
public boolean attachDepthBufferToFbo(String sourceFboName, String targetFboName) {
FBO source = getFBO(sourceFboName);
FBO target = getFBO(targetFboName);
if (source == null || target == null) {
return false;
}
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, target.fboId);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, source.depthStencilRboId);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL11.GL_TEXTURE_2D, source.depthStencilTextureId, 0);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
return true;
}
public FBO createFBO(String title, int width, int height, FBOType type) {
return createFBO(title, width, height, type, false, false, false);
}
public FBO createFBO(String title, int width, int height, FBOType type, boolean depth) {
return createFBO(title, width, height, type, depth, false, false);
}
public FBO createFBO(String title, int width, int height, FBOType type, boolean depth, boolean normals) {
return createFBO(title, width, height, type, depth, normals, false);
}
public FBO createFBO(String title, int width, int height, FBOType type, boolean depth, boolean normals, boolean lightBuffer) {
return createFBO(title, width, height, type, depth, normals, lightBuffer, false);
}
public FBO createFBO(String title, int width, int height, FBOType type, boolean depthBuffer, boolean normalBuffer, boolean lightBuffer, boolean stencilBuffer) {
// Make sure to delete the existing FBO before creating a new one
deleteFBO(title);
// Create a new FBO object
FBO fbo = new FBO();
fbo.width = width;
fbo.height = height;
// Create the FBO
fbo.fboId = glGenFramebuffersEXT();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo.fboId);
if (type != FBOType.NO_COLOR) {
fbo.textureId = glGenTextures();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, fbo.textureId);
GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
if (type == FBOType.HDR) {
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, width, height, 0, GL11.GL_RGBA,
ARBHalfFloatPixel.GL_HALF_FLOAT_ARB, (ByteBuffer) null);
} else {
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, width, height, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
}
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL11.GL_TEXTURE_2D, fbo.textureId, 0);
}
if (normalBuffer) {
fbo.normalsTextureId = glGenTextures();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, fbo.normalsTextureId);
GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, width, height, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (java.nio.ByteBuffer) null);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL11.GL_TEXTURE_2D, fbo.normalsTextureId, 0);
}
if (lightBuffer) {
fbo.lightBufferTextureId = glGenTextures();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, fbo.lightBufferTextureId);
GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
if (type == FBOType.HDR) {
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, ARBTextureFloat.GL_RGBA16F_ARB, width, height, 0,
GL11.GL_RGBA, ARBHalfFloatPixel.GL_HALF_FLOAT_ARB, (ByteBuffer) null);
} else {
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, width, height, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (java.nio.ByteBuffer) null);
}
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT2_EXT, GL11.GL_TEXTURE_2D, fbo.lightBufferTextureId, 0);
}
if (depthBuffer) {
fbo.depthStencilTextureId = glGenTextures();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, fbo.depthStencilTextureId);
GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
if (!stencilBuffer) {
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL14.GL_DEPTH_COMPONENT24, width, height, 0, GL11.GL_DEPTH_COMPONENT, GL11.GL_UNSIGNED_INT, (ByteBuffer) null);
} else {
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, EXTPackedDepthStencil.GL_DEPTH24_STENCIL8_EXT, width, height, 0,
EXTPackedDepthStencil.GL_DEPTH_STENCIL_EXT, EXTPackedDepthStencil.GL_UNSIGNED_INT_24_8_EXT, (ByteBuffer) null);
}
fbo.depthStencilRboId = glGenRenderbuffersEXT();
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fbo.depthStencilRboId);
if (!stencilBuffer) {
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL14.GL_DEPTH_COMPONENT24, width, height);
} else {
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, EXTPackedDepthStencil.GL_DEPTH24_STENCIL8_EXT, width, height);
}
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo.depthStencilRboId);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL11.GL_TEXTURE_2D, fbo.depthStencilTextureId, 0);
if (stencilBuffer) {
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL11.GL_TEXTURE_2D, fbo.depthStencilTextureId, 0);
}
}
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
IntBuffer bufferIds = BufferUtils.createIntBuffer(3);
if (type != FBOType.NO_COLOR) {
bufferIds.put(GL_COLOR_ATTACHMENT0_EXT);
}
if (normalBuffer) {
bufferIds.put(GL_COLOR_ATTACHMENT1_EXT);
}
if (lightBuffer) {
bufferIds.put(GL_COLOR_ATTACHMENT2_EXT);
}
bufferIds.flip();
if (bufferIds.limit() == 0) {
GL11.glReadBuffer(GL11.GL_NONE);
GL20.glDrawBuffers(GL11.GL_NONE);
} else {
GL20.glDrawBuffers(bufferIds);
}
int checkFB = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
switch (checkFB) {
case GL_FRAMEBUFFER_COMPLETE_EXT:
break;
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
logger.error("FrameBuffer: " + title
+ ", has caused a GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT exception");
break;
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
logger.error("FrameBuffer: " + title
+ ", has caused a GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT exception");
break;
case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
logger.error("FrameBuffer: " + title
+ ", has caused a GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT exception");
break;
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
logger.error("FrameBuffer: " + title
+ ", has caused a GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT exception");
break;
case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
logger.error("FrameBuffer: " + title
+ ", has caused a GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT exception");
break;
case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
logger.error("FrameBuffer: " + title
+ ", has caused a GL_FRAMEBUFFER_UNSUPPORTED_EXT exception");
break;
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
logger.error("FrameBuffer: " + title
+ ", has caused a GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT exception");
/*
* On some graphics cards, FBOType.NO_COLOR can cause a GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT.
* Attempt to continue without this FBO.
*/
if (type == FBOType.NO_COLOR) {
logger.error("FrameBuffer: " + title
+ ", ...but the FBOType was NO_COLOR, ignoring this error and continuing without this FBO.");
return null;
}
break;
default:
logger.error("Unexpected reply from glCheckFramebufferStatusEXT: " + checkFB);
break;
}
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
fboLookup.put(title, fbo);
return fbo;
}
private void updateExposure() {
if (config.getRendering().isEyeAdaptation()) {
FBO scene = getFBO("scene1");
if (scene == null) {
return;
}
readBackPBOCurrent.copyFromFBO(scene.fboId, 1, 1, GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE);
if (readBackPBOCurrent == readBackPBOFront) {
readBackPBOCurrent = readBackPBOBack;
} else {
readBackPBOCurrent = readBackPBOFront;
}
ByteBuffer pixels = readBackPBOCurrent.readBackPixels();
if (pixels.limit() < 3) {
logger.error("Failed to auto-update the exposure value.");
return;
}
currentSceneLuminance = 0.2126f * (pixels.get(2) & 0xFF) / 255.f + 0.7152f * (pixels.get(1) & 0xFF) / 255.f + 0.0722f * (pixels.get(0) & 0xFF) / 255.f;
float targetExposure = hdrMaxExposure;
if (currentSceneLuminance > 0) {
targetExposure = hdrTargetLuminance / currentSceneLuminance;
}
float maxExposure = hdrMaxExposure;
if (CoreRegistry.get(WorldRenderer.class).getSkysphere().getDaylight() == 0.0) {
maxExposure = hdrMaxExposureNight;
}
if (targetExposure > maxExposure) {
targetExposure = maxExposure;
} else if (targetExposure < hdrMinExposure) {
targetExposure = hdrMinExposure;
}
currentExposure = (float) TeraMath.lerp(currentExposure, targetExposure, hdrExposureAdjustmentSpeed);
} else {
if (CoreRegistry.get(WorldRenderer.class).getSkysphere().getDaylight() == 0.0) {
currentExposure = hdrMaxExposureNight;
} else {
currentExposure = hdrExposureDefault;
}
}
}
public void clear() {
bindFbo("sceneOpaque");
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
unbindFbo("sceneOpaque");
bindFbo("sceneReflectiveRefractive");
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
unbindFbo("sceneReflectiveRefractive");
}
public void beginRenderSceneOpaque() {
bindFbo("sceneOpaque");
setRenderBufferMask(true, true, true);
}
public void endRenderSceneOpaque() {
setRenderBufferMask(true, true, true);
unbindFbo("sceneOpaque");
}
public void beginRenderLightGeometryStencilPass() {
bindFbo("sceneOpaque");
setRenderBufferMask(false, false, false);
glDepthMask(false);
glClear(GL_STENCIL_BUFFER_BIT);
glCullFace(GL_FRONT);
glDisable(GL_CULL_FACE);
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS, 0, 0);
glStencilOpSeparate(GL_BACK, GL_KEEP, GL_INCR, GL_KEEP);
glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_DECR, GL_KEEP);
}
public void endRenderLightGeometryStencilPass() {
setRenderBufferMask(true, true, true);
unbindFbo("sceneOpaque");
}
public void beginRenderLightGeometry() {
bindFbo("sceneOpaque");
// Only write to the light buffer
setRenderBufferMask(false, false, true);
glStencilFunc(GL_NOTEQUAL, 0, 0xFF);
glDepthMask(true);
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);
glEnable(GL_CULL_FACE);
glCullFace(GL_FRONT);
}
public void endRenderLightGeometry() {
glDisable(GL_STENCIL_TEST);
glCullFace(GL_BACK);
unbindFbo("sceneOpaque");
}
public void beginRenderDirectionalLights() {
bindFbo("sceneOpaque");
}
public void endRenderDirectionalLights() {
glDisable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_DEPTH_TEST);
setRenderBufferMask(true, true, true);
unbindFbo("sceneOpaque");
applyLightBufferPass("sceneOpaque");
}
public void setRenderBufferMask(boolean color, boolean normal, boolean lightBuffer) {
setRenderBufferMask(currentlyBoundFboName, color, normal, lightBuffer);
}
public void setRenderBufferMask(String fboTitle, boolean color, boolean normal, boolean lightBuffer) {
setRenderBufferMask(getFBO(fboTitle), color, normal, lightBuffer);
}
public void setRenderBufferMask(FBO fbo, boolean color, boolean normal, boolean lightBuffer) {
if (fbo == null) {
return;
}
int attachmentId = 0;
IntBuffer bufferIds = BufferUtils.createIntBuffer(3);
if (fbo.textureId != 0) {
if (color) {
bufferIds.put(GL_COLOR_ATTACHMENT0_EXT + attachmentId);
}
attachmentId++;
}
if (fbo.normalsTextureId != 0) {
if (normal) {
bufferIds.put(GL_COLOR_ATTACHMENT0_EXT + attachmentId);
}
attachmentId++;
}
if (fbo.lightBufferTextureId != 0) {
if (lightBuffer) {
bufferIds.put(GL_COLOR_ATTACHMENT0_EXT + attachmentId);
}
attachmentId++;
}
bufferIds.flip();
GL20.glDrawBuffers(bufferIds);
}
public void beginRenderSceneReflectiveRefractive() {
bindFbo("sceneReflectiveRefractive");
}
public void endRenderSceneReflectiveRefractive() {
unbindFbo("sceneReflectiveRefractive");
}
public void beginRenderReflectedScene() {
FBO reflected = getFBO("sceneReflected");
if (reflected == null) {
return;
}
reflected.bind();
glViewport(0, 0, reflected.width, reflected.height);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
public void endRenderReflectedScene() {
unbindFbo("sceneReflected");
glViewport(0, 0, rtFullWidth, rtFullHeight);
}
public void beginRenderSceneShadowMap() {
FBO shadowMap = getFBO("sceneShadowMap");
if (shadowMap == null) {
return;
}
shadowMap.bind();
glViewport(0, 0, shadowMap.width, shadowMap.height);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
public void endRenderSceneShadowMap() {
unbindFbo("sceneShadowMap");
glViewport(0, 0, rtFullWidth, rtFullHeight);
}
public void beginRenderSceneSky() {
setRenderBufferMask(true, false, false);
}
public void endRenderSceneSky() {
setRenderBufferMask(true, true, true);
if (config.getRendering().isInscattering()) {
generateSkyBand(0);
generateSkyBand(1);
}
bindFbo("sceneOpaque");
}
public void renderPreCombinedScene() {
createOrUpdateFullscreenFbos();
if (config.getRendering().isOutline()) {
generateSobel();
}
if (config.getRendering().isSsao()) {
generateSSAO();
generateBlurredSSAO();
}
generateCombinedScene();
}
public void renderPost(StereoRenderState stereoRenderState) {
if (config.getRendering().isLightShafts()) {
PerformanceMonitor.startActivity("Rendering light shafts");
generateLightShafts();
PerformanceMonitor.endActivity();
}
PerformanceMonitor.startActivity("Pre-post processing");
generatePrePost();
PerformanceMonitor.endActivity();
if (config.getRendering().isEyeAdaptation()) {
PerformanceMonitor.startActivity("Rendering eye adaption");
generateDownsampledScene();
PerformanceMonitor.endActivity();
}
PerformanceMonitor.startActivity("Updating exposure");
updateExposure();
PerformanceMonitor.endActivity();
PerformanceMonitor.startActivity("Tone mapping");
generateToneMappedScene();
PerformanceMonitor.endActivity();
if (config.getRendering().isBloom()) {
PerformanceMonitor.startActivity("Applying bloom");
generateHighPass();
for (int i = 0; i < 3; i++) {
generateBloom(i);
}
PerformanceMonitor.endActivity();
}
PerformanceMonitor.startActivity("Applying blur");
for (int i = 0; i < 2; i++) {
if (config.getRendering().getBlurIntensity() != 0) {
generateBlur(i);
}
}
PerformanceMonitor.endActivity();
PerformanceMonitor.startActivity("Rendering final scene");
if (stereoRenderState == StereoRenderState.OCULUS_LEFT_EYE
|| stereoRenderState == StereoRenderState.OCULUS_RIGHT_EYE
|| (stereoRenderState == StereoRenderState.MONO && takeScreenshot)) {
renderFinalSceneToRT(stereoRenderState);
if (takeScreenshot) {
saveScreenshot();
}
}
if (stereoRenderState == StereoRenderState.MONO
|| stereoRenderState == StereoRenderState.OCULUS_RIGHT_EYE) {
renderFinalScene();
}
PerformanceMonitor.endActivity();
}
private void renderFinalSceneToRT(StereoRenderState stereoRenderState) {
Material material;
if (config.getRendering().getDebug().isEnabled()) {
material = Assets.getMaterial("engine:prog.debug");
} else {
material = Assets.getMaterial("engine:prog.post");
}
material.enable();
bindFbo("sceneFinal");
if (stereoRenderState == StereoRenderState.MONO || stereoRenderState == StereoRenderState.OCULUS_LEFT_EYE) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
switch (stereoRenderState) {
case MONO:
renderFullscreenQuad(0, 0, rtFullWidth, rtFullHeight);
break;
case OCULUS_LEFT_EYE:
renderFullscreenQuad(0, 0, rtFullWidth / 2, rtFullHeight);
break;
case OCULUS_RIGHT_EYE:
renderFullscreenQuad(rtFullWidth / 2, 0, rtFullWidth / 2, rtFullHeight);
break;
}
unbindFbo("sceneFinal");
}
private void updateOcShaderParametersForVP(Material program, int vpX, int vpY, int vpWidth, int vpHeight, StereoRenderState stereoRenderState) {
float w = (float) vpWidth / rtFullWidth;
float h = (float) vpHeight / rtFullHeight;
float x = (float) vpX / rtFullWidth;
float y = (float) vpY / rtFullHeight;
float as = (float) vpWidth / vpHeight;
program.setFloat4("ocHmdWarpParam", OculusVrHelper.getDistortionParams()[0], OculusVrHelper.getDistortionParams()[1],
OculusVrHelper.getDistortionParams()[2], OculusVrHelper.getDistortionParams()[3], true);
float ocLensCenter = (stereoRenderState == StereoRenderState.OCULUS_RIGHT_EYE) ? -1.0f * OculusVrHelper.getLensViewportShift() : OculusVrHelper.getLensViewportShift();
program.setFloat2("ocLensCenter", x + (w + ocLensCenter * 0.5f) * 0.5f, y + h * 0.5f, true);
program.setFloat2("ocScreenCenter", x + w * 0.5f, y + h * 0.5f, true);
float scaleFactor = 1.0f / OculusVrHelper.getScaleFactor();
program.setFloat2("ocScale", (w / 2) * scaleFactor, (h / 2) * scaleFactor * as, true);
program.setFloat2("ocScaleIn", (2 / w), (2 / h) / as, true);
}
private void renderFinalScene() {
Material material;
if (config.getRendering().isOculusVrSupport()) {
material = Assets.getMaterial("engine:prog.ocDistortion");
material.enable();
updateOcShaderParametersForVP(material, 0, 0, rtFullWidth / 2, rtFullHeight, StereoRenderState.OCULUS_LEFT_EYE);
} else {
if (config.getRendering().getDebug().isEnabled()) {
material = Assets.getMaterial("engine:prog.debug");
} else {
material = Assets.getMaterial("engine:prog.post");
}
material.enable();
}
renderFullscreenQuad(0, 0, org.lwjgl.opengl.Display.getWidth(), org.lwjgl.opengl.Display.getHeight());
if (config.getRendering().isOculusVrSupport()) {
updateOcShaderParametersForVP(material, rtFullWidth / 2, 0, rtFullWidth / 2, rtFullHeight, StereoRenderState.OCULUS_RIGHT_EYE);
renderFullscreenQuad(0, 0, org.lwjgl.opengl.Display.getWidth(), Display.getHeight());
}
}
private void generateCombinedScene() {
Assets.getMaterial("engine:prog.combine").enable();
bindFbo("sceneOpaquePingPong");
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
renderFullscreenQuad();
unbindFbo("sceneOpaquePingPong");
flipPingPongFbo("sceneOpaque");
attachDepthBufferToFbo("sceneOpaque", "sceneReflectiveRefractive");
}
private void applyLightBufferPass(String target) {
Material program = Assets.getMaterial("engine:prog.lightBufferPass");
program.enable();
DefaultRenderingProcess.FBO targetFbo = getFBO(target);
int texId = 0;
if (targetFbo != null) {
GL13.glActiveTexture(GL13.GL_TEXTURE0 + texId);
targetFbo.bindTexture();
program.setInt("texSceneOpaque", texId++);
GL13.glActiveTexture(GL13.GL_TEXTURE0 + texId);
targetFbo.bindDepthTexture();
program.setInt("texSceneOpaqueDepth", texId++);
GL13.glActiveTexture(GL13.GL_TEXTURE0 + texId);
targetFbo.bindNormalsTexture();
program.setInt("texSceneOpaqueNormals", texId++);
GL13.glActiveTexture(GL13.GL_TEXTURE0 + texId);
targetFbo.bindLightBufferTexture();
program.setInt("texSceneOpaqueLightBuffer", texId++, true);
}
bindFbo(target + "PingPong");
setRenderBufferMask(true, true, true);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
renderFullscreenQuad();
unbindFbo(target + "PingPong");
flipPingPongFbo(target);
if (target.equals("sceneOpaque")) {
attachDepthBufferToFbo("sceneOpaque", "sceneReflectiveRefractive");
}
}
private void generateSkyBand(int id) {
FBO skyBand = getFBO("sceneSkyBand" + id);
if (skyBand == null) {
return;
}
skyBand.bind();
setRenderBufferMask(true, false, false);
Material material = Assets.getMaterial("engine:prog.blur");
material.enable();
material.setFloat("radius", 8.0f, true);
material.setFloat2("texelSize", 1.0f / skyBand.width, 1.0f / skyBand.height, true);
if (id == 0) {
bindFboTexture("sceneOpaque");
} else {
bindFboTexture("sceneSkyBand" + (id - 1));
}
glViewport(0, 0, skyBand.width, skyBand.height);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
renderFullscreenQuad();
skyBand.unbind();
glViewport(0, 0, rtFullWidth, rtFullHeight);
}
private void generateToneMappedScene() {
Assets.getMaterial("engine:prog.hdr").enable();
bindFbo("sceneToneMapped");
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
renderFullscreenQuad();
unbindFbo("sceneToneMapped");
}
private void generateLightShafts() {
Assets.getMaterial("engine:prog.lightshaft").enable();
FBO lightshaft = getFBO("lightShafts");
if (lightshaft == null) {
return;
}
lightshaft.bind();
glViewport(0, 0, lightshaft.width, lightshaft.height);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
renderFullscreenQuad();
lightshaft.unbind();
glViewport(0, 0, rtFullWidth, rtFullHeight);
}
private void generateSSAO() {
Material ssaoShader = Assets.getMaterial("engine:prog.ssao");
ssaoShader.enable();
FBO ssao = getFBO("ssao");
if (ssao == null) {
return;
}
ssaoShader.setFloat2("texelSize", 1.0f / ssao.width, 1.0f / ssao.height, true);
ssaoShader.setFloat2("noiseTexelSize", 1.0f / 4.0f, 1.0f / 4.0f, true);
ssao.bind();
glViewport(0, 0, ssao.width, ssao.height);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
renderFullscreenQuad();
ssao.unbind();
glViewport(0, 0, rtFullWidth, rtFullHeight);
}
private void generateSobel() {
Assets.getMaterial("engine:prog.sobel").enable();
FBO sobel = getFBO("sobel");
if (sobel == null) {
return;
}
sobel.bind();
glViewport(0, 0, sobel.width, sobel.height);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
renderFullscreenQuad();
sobel.unbind();
glViewport(0, 0, rtFullWidth, rtFullHeight);
}
private void generateBlurredSSAO() {
Material shader = Assets.getMaterial("engine:prog.ssaoBlur");
shader.enable();
FBO ssao = getFBO("ssaoBlurred");
if (ssao == null) {
return;
}
shader.setFloat2("texelSize", 1.0f / ssao.width, 1.0f / ssao.height, true);
ssao.bind();
glViewport(0, 0, ssao.width, ssao.height);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
bindFboTexture("ssao");
renderFullscreenQuad();
ssao.unbind();
glViewport(0, 0, rtFullWidth, rtFullHeight);
}
private void generatePrePost() {
Assets.getMaterial("engine:prog.prePost").enable();
bindFbo("scenePrePost");
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
renderFullscreenQuad();
unbindFbo("scenePrePost");
}
private void generateHighPass() {
Material program = Assets.getMaterial("engine:prog.highp");
program.setFloat("highPassThreshold", bloomHighPassThreshold, true);
program.enable();
FBO highPass = getFBO("sceneHighPass");
if (highPass == null) {
return;
}
highPass.bind();
FBO sceneOpaque = getFBO("sceneOpaque");
int texId = 0;
GL13.glActiveTexture(GL13.GL_TEXTURE0 + texId);
sceneOpaque.bindTexture();
program.setInt("tex", texId++);
// GL13.glActiveTexture(GL13.GL_TEXTURE0 + texId);
// sceneOpaque.bindDepthTexture();
// program.setInt("texDepth", texId++);
glViewport(0, 0, highPass.width, highPass.height);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
renderFullscreenQuad();
highPass.unbind();
glViewport(0, 0, rtFullWidth, rtFullHeight);
}
private void generateBlur(int id) {
Material material = Assets.getMaterial("engine:prog.blur");
material.enable();
material.setFloat("radius", overallBlurRadiusFactor * config.getRendering().getBlurRadius(), true);
FBO blur = getFBO("sceneBlur" + id);
if (blur == null) {
return;
}
material.setFloat2("texelSize", 1.0f / blur.width, 1.0f / blur.height, true);
blur.bind();
glViewport(0, 0, blur.width, blur.height);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (id == 0) {
bindFboTexture("sceneToneMapped");
} else {
bindFboTexture("sceneBlur" + (id - 1));
}
renderFullscreenQuad();
blur.unbind();
glViewport(0, 0, rtFullWidth, rtFullHeight);
}
private void generateBloom(int id) {
Material shader = Assets.getMaterial("engine:prog.blur");
shader.enable();
shader.setFloat("radius", bloomBlurRadius, true);
FBO bloom = getFBO("sceneBloom" + id);
if (bloom == null) {
return;
}
shader.setFloat2("texelSize", 1.0f / bloom.width, 1.0f / bloom.height, true);
bloom.bind();
glViewport(0, 0, bloom.width, bloom.height);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (id == 0) {
getFBO("sceneHighPass").bindTexture();
} else {
getFBO("sceneBloom" + (id - 1)).bindTexture();
}
renderFullscreenQuad();
bloom.unbind();
glViewport(0, 0, rtFullWidth, rtFullHeight);
}
private void generateDownsampledScene() {
Material shader = Assets.getMaterial("engine:prog.down");
shader.enable();
for (int i = 4; i >= 0; i--) {
int sizePrev = TeraMath.pow(2, i + 1);
int size = TeraMath.pow(2, i);
shader.setFloat("size", size, true);
bindFbo("scene" + size);
glViewport(0, 0, size, size);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (i == 4) {
bindFboTexture("scenePrePost");
} else {
bindFboTexture("scene" + sizePrev);
}
renderFullscreenQuad();
unbindFbo("scene" + size);
}
glViewport(0, 0, rtFullWidth, rtFullHeight);
}
public void renderFullscreenQuad() {
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
renderQuad();
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
}
public void renderFullscreenQuad(int x, int y, int viewportWidth, int viewportHeight) {
glViewport(x, y, viewportWidth, viewportHeight);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
renderQuad();
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
}
private void renderQuad() {
if (displayListQuad == -1) {
displayListQuad = glGenLists(1);
glNewList(displayListQuad, GL11.GL_COMPILE);
glBegin(GL_QUADS);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glTexCoord2d(0.0, 0.0);
glVertex3i(-1, -1, -1);
glTexCoord2d(1.0, 0.0);
glVertex3i(1, -1, -1);
glTexCoord2d(1.0, 1.0);
glVertex3i(1, 1, -1);
glTexCoord2d(0.0, 1.0);
glVertex3i(-1, 1, -1);
glEnd();
glEndList();
}
glCallList(displayListQuad);
}
public void takeScreenshot() {
takeScreenshot = true;
// TODO: Used to be huge for super screenies, shrunk down for performance until size is an in-game option
overwriteRtWidth = 1152;
overwriteRtHeight = 700;
createOrUpdateFullscreenFbos();
}
public void saveScreenshot() {
if (!takeScreenshot) {
return;
}
final FBO fboSceneFinal = getFBO("sceneFinal");
if (fboSceneFinal == null) {
return;
}
final ByteBuffer buffer = BufferUtils.createByteBuffer(fboSceneFinal.width * fboSceneFinal.height * 4);
fboSceneFinal.bindTexture();
GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer);
fboSceneFinal.unbindTexture();
Runnable r = new Runnable() {
@Override
public void run() {
Calendar cal = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss");
final String fileName = "Terasology-" + sdf.format(cal.getTime()) + "-" + fboSceneFinal.width + "x" + fboSceneFinal.height + ".png";
Path path = PathManager.getInstance().getScreenshotPath().resolve(fileName);
BufferedImage image = new BufferedImage(fboSceneFinal.width, fboSceneFinal.height, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < fboSceneFinal.width; x++) {
for (int y = 0; y < fboSceneFinal.height; y++) {
int i = (x + fboSceneFinal.width * y) * 4;
int r = buffer.get(i) & 0xFF;
int g = buffer.get(i + 1) & 0xFF;
int b = buffer.get(i + 2) & 0xFF;
image.setRGB(x, fboSceneFinal.height - (y + 1), (0xFF << 24) | (r << 16) | (g << 8) | b);
}
}
try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(path))) {
ImageIO.write(image, "png", out);
logger.info("Screenshot '" + fileName + "' saved! ");
} catch (IOException e) {
logger.warn("Failed to save screenshot!", e);
}
}
};
CoreRegistry.get(GameEngine.class).submitTask("Write screenshot", r);
takeScreenshot = false;
overwriteRtWidth = 0;
overwriteRtWidth = 0;
createOrUpdateFullscreenFbos();
}
public float getExposure() {
return currentExposure;
}
public FBO getFBO(String title) {
FBO fbo = fboLookup.get(title);
if (fbo == null) {
logger.error("Failed to retrieve FBO '" + title + "'!");
}
return fbo;
}
public boolean bindFbo(String title) {
FBO fbo = fboLookup.get(title);
if (fbo != null) {
fbo.bind();
currentlyBoundFboName = title;
return true;
}
logger.error("Failed to bind FBO since the requested FBO could not be found!");
return false;
}
public boolean unbindFbo(String title) {
FBO fbo = fboLookup.get(title);
if (fbo != null) {
fbo.unbind();
currentlyBoundFboName = "";
return true;
}
logger.error("Failed to unbind FBO since the requested FBO could not be found!");
return false;
}
public boolean bindFboTexture(String title) {
FBO fbo = fboLookup.get(title);
if (fbo != null) {
fbo.bindTexture();
return true;
}
logger.error("Failed to bind FBO texture since the requested FBO could not be found!");
return false;
}
public boolean bindFboDepthTexture(String title) {
FBO fbo = fboLookup.get(title);
if (fbo != null) {
fbo.bindDepthTexture();
return true;
}
logger.error("Failed to bind FBO depth texture since the requested FBO could not be found!");
return false;
}
public boolean bindFboNormalsTexture(String title) {
FBO fbo = fboLookup.get(title);
if (fbo != null) {
fbo.bindNormalsTexture();
return true;
}
logger.error("Failed to bind FBO normals texture since the requested FBO could not be found!");
return false;
}
public boolean bindFboLightBufferTexture(String title) {
FBO fbo = fboLookup.get(title);
if (fbo != null) {
fbo.bindLightBufferTexture();
return true;
}
logger.error("Failed to bind FBO texture since the requested FBO could not be found!");
return false;
}
public void flipPingPongFbo(String title) {
FBO fbo1 = getFBO(title);
FBO fbo2 = getFBO(title + "PingPong");
if (fbo1 == null || fbo2 == null) {
return;
}
fboLookup.put(title, fbo2);
fboLookup.put(title + "PingPong", fbo1);
}
public static class PBO {
public int pboId;
public int bufferWidth;
public int bufferHeight;
ByteBuffer cachedBuffer;
public PBO() {
pboId = glGenBuffersARB();
}
public void bind() {
glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, pboId);
}
public void unbind() {
glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, 0);
}
public void init(int width, int height) {
this.bufferWidth = width;
this.bufferHeight = height;
int byteSize = width * height * 4;
cachedBuffer = BufferUtils.createByteBuffer(byteSize);
bind();
glBufferDataARB(GL_PIXEL_PACK_BUFFER_EXT, byteSize, GL_STREAM_READ_ARB);
unbind();
}
public void copyFromFBO(int fboId, int width, int height, int format, int type) {
bind();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);
glReadPixels(0, 0, width, height, format, type, 0);
unbind();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
}
public ByteBuffer readBackPixels() {
bind();
cachedBuffer = glMapBufferARB(GL_PIXEL_PACK_BUFFER_EXT, GL_READ_ONLY, cachedBuffer);
// Maybe fix for the issues appearing on some platforms where accessing the "cachedBuffer" causes a JVM exception and therefore a crash...
ByteBuffer resultBuffer = BufferUtils.createByteBuffer(cachedBuffer.capacity());
resultBuffer.put(cachedBuffer);
cachedBuffer.rewind();
resultBuffer.flip();
glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_EXT);
unbind();
return resultBuffer;
}
}
public class FBO {
public int fboId;
public int textureId;
public int depthStencilTextureId;
public int depthStencilRboId;
public int normalsTextureId;
public int lightBufferTextureId;
public int width;
public int height;
public void bind() {
if (this != currentlyBoundFbo) {
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);
currentlyBoundFbo = this;
}
}
public void unbind() {
if (currentlyBoundFbo != null) {
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
currentlyBoundFbo = null;
}
}
public void bindDepthTexture() {
//if (currentlyBoundTextureId != depthStencilTextureId) {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, depthStencilTextureId);
//currentlyBoundTextureId = depthStencilTextureId;
//}
}
public void bindTexture() {
//if (currentlyBoundTextureId != textureId) {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureId);
//currentlyBoundTextureId = textureId;
//}
}
public void bindNormalsTexture() {
//if (currentlyBoundTextureId != normalsTextureId) {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, normalsTextureId);
//currentlyBoundTextureId = normalsTextureId;
//}
}
public void bindLightBufferTexture() {
//if (currentlyBoundTextureId != lightBufferTextureId) {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, lightBufferTextureId);
//currentlyBoundTextureId = lightBufferTextureId;
//}
}
public void unbindTexture() {
//if (currentlyBoundTextureId != 0) {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
//currentlyBoundTextureId = 0;
//}
}
}
}