Renderer/src/main/java/electrosphere/renderer/framebuffer/Framebuffer.java
austin 1873b8abad
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit
always run debug pipeline
2024-09-02 09:09:56 -04:00

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