Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit
327 lines
12 KiB
Java
327 lines
12 KiB
Java
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<Integer,Texture> attachTextureMap = new HashMap<Integer,Texture>();
|
|
//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;
|
|
}
|
|
|
|
}
|