package electrosphere.renderer.framebuffer; import java.awt.image.BufferedImage; import electrosphere.engine.Globals; import electrosphere.logger.LoggerInterface; import electrosphere.renderer.OpenGLState; import electrosphere.renderer.RenderingEngine; import electrosphere.renderer.texture.Texture; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import org.lwjgl.opengl.GL40; import org.lwjgl.system.MemoryUtil; import static org.lwjgl.opengl.GL45.glCheckNamedFramebufferStatus; /** * Framebuffer object */ public class Framebuffer { /** * The default framebuffer's pointer */ public static final int DEFAULT_FRAMEBUFFER_POINTER = 0; //the pointer to the framebuffer int framebufferPointer; //the mipmap level int mipMap = -1; //the map of attachment point to texture object Map attachTextureMap = new HashMap(); //attached texture Texture texture; //the depth texture for the framebuffer Texture depthTexture; /** * Creates a framebuffer */ public Framebuffer(){ framebufferPointer = GL40.glGenFramebuffers(); Globals.renderingEngine.checkError(); } /** * Creates a framebuffer with a predefined id * @param framebufferId The predefined framebuffer id */ public Framebuffer(int framebufferId){ this.framebufferPointer = framebufferId; } /** * Sets the texture attached to the framebuffer * @param texture The texture attached to the framebuffer */ public void setTexture(Texture texture){ this.texture = texture; } /** * Gets the texture attached to this framebuffer * @return The texture */ public Texture getTexture(){ return texture; } /** * Gets the depth texture attached to this framebuffer * @return The depth texture */ public Texture getDepthTexture(){ return depthTexture; } /** * Binds the framebuffer * @param openGLState The opengl state */ public void bind(OpenGLState openGLState){ openGLState.glBindFramebuffer(GL40.GL_FRAMEBUFFER, framebufferPointer); } /** * Checks if the framebuffer compiled correctly * @return true if compiled correctly, false otherwise */ public boolean isComplete(){ return glCheckNamedFramebufferStatus(framebufferPointer,GL40.GL_FRAMEBUFFER) == GL40.GL_FRAMEBUFFER_COMPLETE; } /** * Checks if the framebuffer has an error * @return true if error, false otherwise */ public boolean isError(){ return glCheckNamedFramebufferStatus(framebufferPointer,GL40.GL_FRAMEBUFFER) == 0; } public void checkStatus(){ if(isError()){ LoggerInterface.loggerRenderer.WARNING("Framebuffer [error] - " + RenderingEngine.getErrorInEnglish(Globals.renderingEngine.getError())); } else if(!isComplete()){ LoggerInterface.loggerRenderer.WARNING("Framebuffer [glError] - " + RenderingEngine.getErrorInEnglish(Globals.renderingEngine.getError())); LoggerInterface.loggerRenderer.WARNING("Framebuffer [status] - " + getStatus()); LoggerInterface.loggerRenderer.ERROR("Failed to build framebuffer", new IllegalStateException("Framebuffer failed to build.")); } } /** * Gets the framebuffer's pointer * @return The framebuffer's pointer */ public int getFramebufferPointer(){ return framebufferPointer; } /** * Frees the framebuffer */ public void free(){ GL40.glDeleteFramebuffers(framebufferPointer); } /** * Blocks the thread until the framebuffer has compiled */ public void blockUntilCompiled(){ while(glCheckNamedFramebufferStatus(framebufferPointer,GL40.GL_FRAMEBUFFER) != GL40.GL_FRAMEBUFFER_UNDEFINED){ try { TimeUnit.MILLISECONDS.sleep(1); } catch (InterruptedException ex) { LoggerInterface.loggerEngine.ERROR("Failed to sleep in framebuffer blocker", ex); } } } /** * Gets the status of the framebuffer * @return The status */ public String getStatus(){ switch(glCheckNamedFramebufferStatus(framebufferPointer,GL40.GL_FRAMEBUFFER)){ case GL40.GL_FRAMEBUFFER_UNDEFINED: { return "The specified framebuffer is the default read or draw framebuffer, but the default framebuffer does not exist."; } case GL40.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: { return "Any of the framebuffer attachment points are framebuffer incomplete."; } case GL40.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: { return "The framebuffer does not have at least one image attached to it."; } case GL40.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: { return "The value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE for any color attachment point(s) named by GL_DRAW_BUFFERi."; } case GL40.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: { return "GL_READ_BUFFER is not GL_NONE and the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE for the color attachment point named by GL_READ_BUFFER."; } case GL40.GL_FRAMEBUFFER_UNSUPPORTED: { return "The combination of internal formats of the attached images violates an implementation-dependent set of restrictions."; } case GL40.GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: { return "The value of GL_RENDERBUFFER_SAMPLES is not the same for all attached renderbuffers; if the value of GL_TEXTURE_SAMPLES is the not same for all attached textures; or, if the attached images are a mix of renderbuffers and textures, the value of GL_RENDERBUFFER_SAMPLES does not match the value of GL_TEXTURE_SAMPLES."; } case 0: { return RenderingEngine.getErrorInEnglish(Globals.renderingEngine.getError()); } } return "Unknown framebuffer status"; } /** * Sets the mipmap level * @param mipMap The mipmap level */ public void setMipMapLevel(int mipMap){ this.mipMap = mipMap; } /** * Attaches a color texture to the default texture unit * @param openGLState The opengl state * @param texture The texture */ public void attachTexture(OpenGLState openGLState, Texture texture){ attachTexture(openGLState, texture, 0); } /** * Attaches a texture to the framebuffer * @param openGLState The opengl state * @param texture The texture * @param attachmentNum The texture unit to attach to */ public void attachTexture(OpenGLState openGLState, Texture texture, int attachmentNum){ this.attachTextureMap.put(attachmentNum,texture); this.texture = texture; if(this.mipMap < 0){ LoggerInterface.loggerRenderer.ERROR(new IllegalStateException("Trying to attach a texture to a framebuffer where mipmap hasn't been set.")); } if(this.framebufferPointer == Framebuffer.DEFAULT_FRAMEBUFFER_POINTER){ throw new IllegalStateException("Trying to attach image to default frame buffer!"); } if(texture.getTexturePointer() == Texture.UNINITIALIZED_TEXTURE){ throw new IllegalStateException("Trying to attach uninitialized image to frame buffer!"); } openGLState.glBindFramebuffer(GL40.GL_FRAMEBUFFER, this.framebufferPointer); texture.bind(openGLState); //texture must be bound at least once so that the object is created prior to attaching to framebuffer GL40.glFramebufferTexture2D(GL40.GL_FRAMEBUFFER, GL40.GL_COLOR_ATTACHMENT0 + attachmentNum, GL40.GL_TEXTURE_2D, texture.getTexturePointer(), 0); Globals.renderingEngine.checkError(); openGLState.glBindFramebuffer(GL40.GL_FRAMEBUFFER, 0); } /** * Sets the depth attachment for the framebuffer * @param openGLState The opengl state * @param texturePointer The depth attachment's pointer */ public void setDepthAttachment(OpenGLState openGLState, Texture depthTexture){ openGLState.glBindFramebuffer(GL40.GL_FRAMEBUFFER, this.framebufferPointer); this.depthTexture = depthTexture; depthTexture.bind(openGLState); //texture must be bound at least once so that the object is created prior to attaching to framebuffer GL40.glFramebufferTexture2D(GL40.GL_FRAMEBUFFER, GL40.GL_DEPTH_ATTACHMENT, GL40.GL_TEXTURE_2D, depthTexture.getTexturePointer(), 0); Globals.renderingEngine.checkError(); openGLState.glBindFramebuffer(GL40.GL_FRAMEBUFFER, 0); } /** * Gets the pixels currently stored in this framebuffer */ public BufferedImage getPixels(OpenGLState openGLState){ BufferedImage rVal = null; int offsetX = 0; int offsetY = 0; int width = openGLState.getViewport().x; int height = openGLState.getViewport().y; int pixelFormat = GL40.GL_RGBA; int type = GL40.GL_UNSIGNED_INT; bind(openGLState); GL40.glReadBuffer(GL40.GL_COLOR_ATTACHMENT0); if(this.framebufferPointer == 0){ //this is the default framebuffer, read from backbuffer because it is default GL40.glReadBuffer(GL40.GL_BACK); } else if(attachTextureMap.containsKey(0)){ Texture texture = attachTextureMap.get(0); pixelFormat = texture.getFormat(); type = texture.getDataType(); width = texture.getWidth(); height = texture.getHeight(); } else { LoggerInterface.loggerRenderer.ERROR(new IllegalStateException("Tried to get pixels from a framebuffer that does not have a texture attached to attachment point 0.")); } //get pixel data try { int bytesPerPixel = pixelFormatToBytes(pixelFormat,type); int bufferSize = width * height * bytesPerPixel; ByteBuffer buffer = MemoryUtil.memAlloc(bufferSize); openGLState.glViewport(width, height); GL40.glReadPixels(offsetX, offsetY, width, height, pixelFormat, type, buffer); Globals.renderingEngine.checkError(); //convert to a buffered images rVal = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB); for(int x = 0; x < width; x++){ for(int y = 0; y < height; y++){ int i = (x + (width * y)) * bytesPerPixel; int red = buffer.get(i) & 0xFF; int green = buffer.get(i + 1) & 0xFF; int blue = buffer.get(i + 2) & 0xFF; int alpha = 255; if(pixelFormat == GL40.GL_RGBA){ alpha = buffer.get(i + 3) & 0xFF; } rVal.setRGB(x, height - (y + 1), (alpha << 24) | (red << 16) | (green << 8) | blue); } } //memory management MemoryUtil.memFree(buffer); } catch (OutOfMemoryError e){ LoggerInterface.loggerRenderer.ERROR(new IllegalStateException(e.getMessage())); } return rVal; } /** * Gets the number of bytes per pixel based on the format and type of pixel * @param format The format of the pixels * @param type The datatype of as single component of the pixel * @return The number of bytes */ private static int pixelFormatToBytes(int format, int type){ int multiplier = 1; switch(format){ case GL40.GL_RGBA: { multiplier = 4; } break; case GL40.GL_RGB: { multiplier = 3; } break; default: { LoggerInterface.loggerRenderer.WARNING("Trying to export framebuffer that has image of unsupported pixel format"); } break; } int bytesPerComponent = 1; switch(type){ case GL40.GL_UNSIGNED_INT: { bytesPerComponent = 4; } break; case GL40.GL_UNSIGNED_BYTE: { bytesPerComponent = 1; } break; default: { LoggerInterface.loggerRenderer.WARNING("Trying to export framebuffer that has image of unsupported datatype"); } break; } return multiplier * bytesPerComponent; } }