package net.hvidtfeldts.fragapi;
import java.io.File;
import java.io.IOException;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
import javax.media.opengl.GL;
import javax.media.opengl.GL2ES2;
import javax.media.opengl.GLException;
import javax.media.opengl.GLUniformData;
import net.hvidtfeldts.utils.Logger;
import com.jogamp.opengl.util.GLArrayDataServer;
import com.jogamp.opengl.util.glsl.ShaderCode;
import com.jogamp.opengl.util.glsl.ShaderProgram;
import com.jogamp.opengl.util.glsl.ShaderState;
import com.jogamp.opengl.util.texture.Texture;
import com.jogamp.opengl.util.texture.TextureIO;
public class FrameBufferBase implements FrameBuffer {
private int[] colors;
private int[] depths;
private int[] fbos;
private GLArrayDataServer vertices;
private ShaderState shaderState;
private double relativeSize = 1.0;
private int[] absoluteSize;
private CharSequence vertexShader;
private CharSequence fragmentShader;
private boolean offscreenBuffer = true;
private GLUniformData tilesUniform;
private final List<FrameBuffer> previousBuffers = new ArrayList<>();
private final FloatBuffer fb = FloatBuffer.allocate(2);
private int counter;
private final List<TextureData> textureData = new ArrayList<>();
private Camera camera;
public FrameBufferBase() {
}
@Override
public FrameBuffer createCopy() {
FrameBufferBase c = new FrameBufferBase();
c.relativeSize = relativeSize;
c.absoluteSize = absoluteSize;
c.vertexShader = vertexShader;
c.fragmentShader = fragmentShader;
c.camera = camera;
return c;
}
public static FrameBuffer create(String fileName) {
FrameBuffer fb = new FrameBufferBase();
fb.setVertexShader(Files.read("PostFXShader.vp"));
fb.setFragmentShader(Files.read(fileName));
return fb;
}
public static FrameBuffer create3D(String fileName) {
FrameBuffer fb = new FrameBufferBase();
fb.setVertexShader(Files.read("Simple3D.vp"));
fb.setFragmentShader(Files.read(fileName));
fb.addCamera(new Camera());
return fb;
}
@Override
public FrameBuffer setRelativeSize(double relativeSize) {
this.relativeSize = relativeSize;
absoluteSize = null;
return this;
}
@Override
public FrameBuffer setAbsoluteSize(int x, int y) {
this.relativeSize = 0;
absoluteSize = new int[] { x, y };
return this;
}
@Override
public int getWidth() {
if (absoluteSize != null)
return absoluteSize[0];
return (int) (Defaults.getWidth() * relativeSize);
}
@Override
public int getHeight() {
if (absoluteSize != null)
return absoluteSize[1];
return (int) (Defaults.getHeight() * relativeSize);
}
@Override
public FrameBuffer setVertexShader(CharSequence shader) {
this.vertexShader = shader;
return this;
}
@Override
public FrameBuffer setFragmentShader(CharSequence shader) {
this.fragmentShader = shader;
return this;
}
private void internalInit(GL2ES2 gl) {
colors = new int[] { -1 };
depths = new int[] { -1 };
fbos = new int[] { -1 };
if (offscreenBuffer) {
createFBO(gl);
}
float[] ver = new float[] {
-1, -1, 0, /* */1, -1, 0, /* */1, 1, 0,
1, 1, 0, /* */-1, 1, 0, /* */-1, -1, 0,
};
shaderState = new ShaderState();
shaderState.setVerbose(true);
// Allocate Vertex Array
vertices = GLArrayDataServer.createGLSL("vertex", 3, GL.GL_FLOAT, false, ver.length / 3, GL.GL_STATIC_DRAW);
for (int i = 0; i < ver.length; i++) {
vertices.putf(ver[i]);
}
vertices.seal(gl, true);
this.shaderState.ownAttribute(vertices, true);
CharSequence[][] vertexSource = new CharSequence[1][1];
vertexSource[0][0] = vertexShader;
final ShaderCode vp1 = new ShaderCode(GL2ES2.GL_VERTEX_SHADER, 1, vertexSource);
CharSequence[][] fragmentSource = new CharSequence[1][1];
fragmentSource[0][0] = fragmentShader;
final ShaderCode fp1 = new ShaderCode(GL2ES2.GL_FRAGMENT_SHADER, 1, fragmentSource);
if (!vertexShader.toString().startsWith("#version"))
vp1.defaultShaderCustomization(gl, true, true);
if (!fragmentShader.toString().startsWith("#version"))
fp1.defaultShaderCustomization(gl, true, true);
final ShaderProgram sp1 = new ShaderProgram();
check(sp1.add(gl, vp1, Logger.getLoggerWarnStream()));
check(sp1.add(gl, fp1, Logger.getLoggerWarnStream()));
check(shaderState.attachShaderProgram(gl, sp1, false));
gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0);
if (camera != null) {
camera.setupRaytracerMatrixStack(gl);
}
}
private void createFBO(GL2ES2 gl) {
gl.glGenTextures(1, colors, 0);
int width = getWidth();
int height = getHeight();
gl.glBindTexture(GL.GL_TEXTURE_2D, colors[0]);
gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST);
gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST);
gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA, width, height, 0, GL.GL_BGRA, GL.GL_UNSIGNED_BYTE, null);
gl.glBindTexture(GL.GL_TEXTURE_2D, 0);
gl.glGenRenderbuffers(1, depths, 0);
gl.glBindRenderbuffer(GL.GL_RENDERBUFFER, depths[0]);
gl.glRenderbufferStorage(GL.GL_RENDERBUFFER, GL2ES2.GL_DEPTH_COMPONENT, width, height);
gl.glBindRenderbuffer(GL.GL_RENDERBUFFER, 0);
gl.glGenFramebuffers(1, fbos, 0);
gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, fbos[0]);
gl.glFramebufferTexture2D(GL.GL_FRAMEBUFFER, GL.GL_COLOR_ATTACHMENT0, GL.GL_TEXTURE_2D, colors[0], 0);
gl.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER, GL.GL_DEPTH_ATTACHMENT, GL.GL_RENDERBUFFER, depths[0]);
Logger.log("Created FBO: Color=" + colors[0] + " depth=" + depths[0] + " fbo=" + fbos[0]);
}
private void check(boolean returnValue) {
if (!returnValue) {
throw new IllegalStateException();
}
}
@Override
public void draw(GL2ES2 gl) {
for (FrameBuffer fb : previousBuffers) {
fb.draw(gl);
}
internalDraw(gl);
}
private void internalDraw(GL2ES2 gl) {
gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, offscreenBuffer ? fbos[0] : 0);
gl.glViewport(0, 0, getWidth(), getHeight());
shaderState.useProgram(gl, true);
if (camera != null) {
camera.setUniforms(gl, shaderState);
}
int textureCounter = 0;
for (TextureData td : textureData) {
String uniformName = td.getUniformName();
int textureID = td.getTextureID();
gl.glActiveTexture(GL.GL_TEXTURE0 + textureCounter);
gl.glBindTexture(GL.GL_TEXTURE_2D, textureID);
int i = shaderState.getUniformLocation(gl, uniformName);
gl.glUniform1i(i, textureCounter);
textureCounter++;
}
if (tilesUniform == null) {
// tilesUniform = new GLUniformData("tiles", 2, fb);
// tilesUniform.setData(fb);
}
fb.array()[0] = counter++ % 9;
fb.array()[1] = 3;
// shaderState.uniform(gl, tilesUniform);
gl.glClearColor(0, 0, 1, 1);
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
gl.glDisable(GL.GL_DEPTH_TEST);
vertices.enableBuffer(gl, true);
gl.glDrawArrays(GL.GL_TRIANGLES, 0, vertices.getElementCount());
vertices.enableBuffer(gl, false);
shaderState.useProgram(gl, false);
}
@Override
public int getTexture() {
return colors[0];
}
@Override
public ShaderState getShaderState() {
return shaderState;
}
@Override
public FrameBuffer setAsOutputBuffer() {
offscreenBuffer = false;
return this;
}
@Override
public FrameBuffer setSampler2D(String uniformName, FrameBuffer fp, boolean requireRedraw) {
if (requireRedraw) {
previousBuffers.add(fp);
}
textureData.add(new TextureData(uniformName, fp));
return this;
}
public FrameBuffer setSampler2D(String uniformName) {
textureData.add(new TextureData(uniformName, this));
return this;
}
@Override
public FrameBuffer setSampler2D(String uniformName, String textureFileName) {
textureData.add(new TextureData(uniformName, textureFileName));
return this;
}
private boolean isInitialized;
@Override
public void init(GL2ES2 gl) {
if (isInitialized)
return;
isInitialized = true;
for (FrameBuffer fb : previousBuffers) {
fb.init(gl);
}
internalInit(gl);
}
@Override
public List<FrameBuffer> getPreviousBuffers() {
return previousBuffers;
}
@Override
public void show() {
FrameBufferWindow.show(this);
}
private class TextureData {
private final String uniformName;
private int textureID = -1;
private FrameBuffer frameBuffer;
private Texture texture;
private String fileName;
public TextureData(String uniformName, FrameBuffer fp) {
this.uniformName = uniformName;
this.frameBuffer = fp;
}
public TextureData(String uniformName, String textureFileName) {
this.uniformName = uniformName;
this.fileName = textureFileName;
}
public String getUniformName() {
return uniformName;
}
public int getTextureID() {
if (textureID == -1) {
if (frameBuffer != null) {
textureID = frameBuffer.getTexture();
Logger.log("TextureID from framebuffer:" + textureID);
}
else if (texture == null) {
try {
texture = TextureIO.newTexture(new File(fileName), false);
}
catch (GLException | IOException e) {
throw new RuntimeException(e);
}
textureID = texture.getTextureObject();
Logger.log("TextureID from texture:" + textureID);
}
}
return textureID;
}
}
@Override
public FrameBuffer addCamera(Camera c) {
this.camera = c;
return this;
}
}