LWJGL Forum

Programming => OpenGL => Topic started by: Turakar on June 23, 2016, 14:08:27

Title: Render to Texture giving fatal JRE exceptions
Post by: Turakar on June 23, 2016, 14:08:27
Hello,

I'm trying to write an example for rendering to texture with LWJGL 3.0.0. However, I do not manage to get it to work. That's the code:
package de.thoffbauer.cameraexample;

import static org.lwjgl.glfw.Callbacks.glfwFreeCallbacks;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL13.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;
import static org.lwjgl.opengl.GL32.*;
import static org.lwjgl.system.MemoryUtil.NULL;

import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;

import javax.imageio.ImageIO;

import org.lwjgl.BufferUtils;
import org.lwjgl.Version;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.glfw.GLFWVidMode;
import org.lwjgl.opengl.GL;


public class CameraExample {

    // The window handle
    private long window;
   
    private int triangleVao;
    private int triangleVbo;
   
    private int framebuffer;
    private int renderedTexture;
   
    private int screenVao;
    private int screenVbo;

    private int program;
    private int vertexShader;
    private int fragmentShader;
   
    public void run() {
        System.out.println("Hello LWJGL " + Version.getVersion() + "!");

        try {
            init();
            loop();

            // Free the window callbacks and destroy the window
            glfwFreeCallbacks(window);
            glfwDestroyWindow(window);
        } finally {
            // Terminate GLFW and free the error callback
            glfwTerminate();
            glfwSetErrorCallback(null).free();
        }
    }
    private void init() {
        // Setup an error callback. The default implementation
        // will print the error message in System.err.
        GLFWErrorCallback.createPrint(System.err).set();

        // Initialize GLFW. Most GLFW functions will not work before doing this.
        if ( !glfwInit() )
            throw new IllegalStateException("Unable to initialize GLFW");

        // Configure our window
        glfwDefaultWindowHints(); // optional, the current window hints are already the default
        glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // the window will stay hidden after creation
        glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // the window will be resizable

        int WIDTH = 800;
        int HEIGHT = 600;

        // Create the window
        window = glfwCreateWindow(WIDTH, HEIGHT, "Camera Example", NULL, NULL);
        if ( window == NULL )
            throw new RuntimeException("Failed to create the GLFW window");

        // Setup a key callback. It will be called every time a key is pressed, repeated or released.
        glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> {
            if ( key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE )
                glfwSetWindowShouldClose(window, true); // We will detect this in our rendering loop
        });

        // Get the resolution of the primary monitor
        GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
        // Center our window
        glfwSetWindowPos(
            window,
            (vidmode.width() - WIDTH) / 2,
            (vidmode.height() - HEIGHT) / 2
        );

        // Make the OpenGL context current
        glfwMakeContextCurrent(window);
        // Enable v-sync
        glfwSwapInterval(1);

        // Make the window visible
        glfwShowWindow(window);
       
        // This line is critical for LWJGL's interoperation with GLFW's
        // OpenGL context, or any context that is managed externally.
        // LWJGL detects the context that is current in the current thread,
        // creates the GLCapabilities instance and makes the OpenGL
        // bindings available for use.
        GL.createCapabilities();

        // Set the clear color
        glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
       
        program = glCreateProgram();
        attachVertexShader("quadtextured.vs");
        attachFragmentShader("quadtextured.fs");
        glBindAttribLocation(program, 0, "in_Position");
        glBindAttribLocation(program, 1, "in_TextureCoord");
        linkProgram();
       
        // Enable depth test
    glEnable(GL_DEPTH_TEST);
    // Accept fragment if it closer to the camera than the former one
    glDepthFunc(GL_LESS);
       
        initCamera();
        initScreen();
        initTriangle();
    }
    private void loop() {
        // Run the rendering loop until the user has attempted to close
        // the window or has pressed the ESCAPE key.
    int i = 0;
        while ( !glfwWindowShouldClose(window) ) {
       
        renderToCamera();

            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear the framebuffer
        glUseProgram(program);
           
            renderTriangle();
            renderScreen();
           
//            if(i == 100) {
//            try {
// saveCamera("framebuffer.png");
// } catch (IOException e) {
// e.printStackTrace();
// }
//            }
           
        glUseProgram(0);

            glfwSwapBuffers(window); // swap the color buffers

            // Poll for window events. The key callback above will only be
            // invoked during this call.
            glfwPollEvents();
            i++;
        }
    }

    private void initTriangle() {
    triangleVao = glGenVertexArrays();
    glBindVertexArray(triangleVao);
   
    float[] vertices = new float[] {
    0, 0.8f, 0,
    -0.8f, -0.8f, 0,
    0.8f, -0.8f, 0
    };
   
    FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(vertices.length);
    verticesBuffer.put(vertices).flip();
   
    triangleVbo = glGenBuffers();
    glBindBuffer(GL_ARRAY_BUFFER, triangleVbo);
    glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW);
   
    glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);
    glBindVertexArray(0);
    }
    private void renderTriangle() {
    glBindVertexArray(triangleVao);
    glEnableVertexAttribArray(0);
   
    glDrawArrays(GL_TRIANGLES, 0, 3);
   
    glDisableVertexAttribArray(0);
    glBindVertexArray(0);
    }
   
    private void initCamera() {
    framebuffer = glGenFramebuffers();
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
   
    renderedTexture = glGenTextures();
    glBindTexture(GL_TEXTURE_2D, renderedTexture);
   
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
   
    int depthRenderBuffer = glGenRenderbuffers();
    glBindRenderbuffer(GL_RENDERBUFFER, depthRenderBuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, 800, 600);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderBuffer);
   
    glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, renderedTexture, 0);
   
    glDrawBuffers(GL_COLOR_ATTACHMENT0);
   
    if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    System.err.println("Framebuffer not complete!");
   
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }
    private void renderToCamera() {
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glViewport(0, 0, 800, 600);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glUseProgram(program);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }
   
    private void initScreen() {
    screenVao = glGenVertexArrays();
    glBindVertexArray(screenVao);
   
    float[] vertices = new float[] {
    // Vertices
    -1, -1, 0,
    0, -1, 0,
    -1, 0, 0,
    -1, 0, 0,
    0, -1, 0,
    0, 0, 0
    // UV
    -1, -1,
    1, -1,
    -1, 1,
    -1, 1,
    1, -1,
    1, 1
    };
   
    FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(vertices.length);
    verticesBuffer.put(vertices).flip();
   
    screenVbo = glGenBuffers();
    glBindBuffer(GL_ARRAY_BUFFER, screenVbo);
    glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW);
   
    glVertexAttribPointer(0, vertices.length, GL_FLOAT, false, 0, 0);
    glVertexAttribPointer(1, vertices.length, GL_FLOAT, false, 0, 3 * 6);
   
    glBindBuffer(GL_VERTEX_ARRAY, 0);
    }
    private void renderScreen() {
//    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, renderedTexture);
   
    glBindVertexArray(screenVao);
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
   
    glDrawArrays(GL_TRIANGLES, 0, 3);
   
    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
    glBindVertexArray(0);
   
    glBindTexture(GL_TEXTURE_2D, 0);
    }
    private void saveCamera(String name) throws IOException {
    glBindTexture(GL_TEXTURE_2D, renderedTexture);
    ByteBuffer buffer = ByteBuffer.allocate(800 * 600 * 3);
    glPixelStorei(GL_PACK_ALIGNMENT, 1);
    glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);
    BufferedImage image = new BufferedImage(800, 600, BufferedImage.TYPE_INT_RGB);
    int width = 800;
    int height = 600;
    for(int x = 0; x < width; x++) {
        for(int y = 0; y < height; y++) {
            int i = (x + (width * y)) * 3;
            int r = buffer.get(i) & 0xFF;
            int g = buffer.get(i + 1) & 0xFF;
            int b = buffer.get(i + 2) & 0xFF;
            image.setRGB(x, height - (y + 1), (0xFF << 24) | (r << 16) | (g << 8) | b);
        }
    }
    ImageIO.write(image, "PNG", new File(name));
    }
   
private String readShader(String name) {
StringBuilder source = new StringBuilder();
    try
    {
        BufferedReader reader = new BufferedReader(new FileReader("shaders/" + name));

        String line;
        while ((line = reader.readLine()) != null)
        {
            source.append(line).append("\n");
        }

        reader.close();
    }
    catch (Exception e)
    {
        System.err.println("Error loading source code: " + name);
        e.printStackTrace();
    }

    return source.toString();
}
private void attachVertexShader(String name) {
String source = readShader(name);

vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, source);

glCompileShader(vertexShader);

if(glGetShaderi(vertexShader, GL_COMPILE_STATUS) == GL_FALSE)
System.err.println("Error creating vertexShader\n" + glGetShaderInfoLog(vertexShader));

glAttachShader(program, vertexShader);
}
private void attachFragmentShader(String name) {
String source = readShader(name);

fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, source);

glCompileShader(fragmentShader);

if(glGetShaderi(fragmentShader, GL_COMPILE_STATUS) == GL_FALSE)
System.err.println("Error creating fragmentShader\n" + glGetShaderInfoLog(fragmentShader));

glAttachShader(program, fragmentShader);
}
    private void linkProgram() {
    glLinkProgram(program);
   
    if(glGetProgrami(program, GL_LINK_STATUS) == GL_FALSE) {
    System.err.println("Unable to link shader program");
    System.err.println(glGetProgramInfoLog(program));
    }
    }

    public static void main(String[] args) {
        new CameraExample().run();
    }

}


The shaders (first vertex, then fragment):
#version 150 core

in vec4 in_Position;
in vec2 in_TextureCoord;

out vec2 pass_TextureCoord;

void main(void) {
    gl_Position = in_Position;
   
    pass_TextureCoord = in_TextureCoord;
}

#version 150 core

uniform sampler2D texture_diffuse;

in vec2 pass_TextureCoord;

out vec4 out_Color;

void main(void) {
    out_Color = texture(texture_diffuse, pass_TextureCoord);
}


And the output:
WARNING in native method: JNI call made without checking exceptions when required to from CallStaticObjectMethod
Hello LWJGL 3.0.0 build 90!
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x0000000041030f41, pid=7622, tid=139991872743168
#
# JRE version: Java(TM) SE Runtime Environment (8.0_77-b03) (build 1.8.0_77-b03)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.77-b03 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# C  0x0000000041030f41
#
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# /eclipseworkspace/CameraExample/hs_err_pid7622.log
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.java.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#


If you need this hs_err_pid I can publish it, but I does not seem to be human-readable. (Btw, I'm really disappointed of those informative error descriptions...)
With the help of the eclipse debugger I was able to find out that the JRE exception arises at glDrawArrays in renderScreen(), that's line 273.
I'm using an Nvidia GTX 960 with Linux x64 (Fedora). I have the propietary drivers installed.

Thank you in advance for any help,
Turakar
Title: Re: Render to Texture giving fatal JRE exceptions
Post by: Kai on June 23, 2016, 14:25:02
Please look at the documentation of glVertexAttribPointer: https://www.opengl.org/sdk/docs/man/html/glVertexAttribPointer.xhtml
You supply invalid values for three of its parameters (size, stride and pointer) in the calls at line 260-261.
Title: Re: Render to Texture giving fatal JRE exceptions
Post by: Turakar on June 23, 2016, 16:08:22
Thank you for that hint. I also made some other changes to get it to work:
- unbind with glVertexArray() in initScreen
- set the texture to CLAMP_TO_BORDER
- different glClearColors to see the difference better
- set last parameter to 6 in glDrawArrays in renderScreen (prior rendered just one triangle)

The full code now: (no changes in the shaders)
package de.thoffbauer.cameraexample;

import static org.lwjgl.glfw.Callbacks.glfwFreeCallbacks;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL13.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;
import static org.lwjgl.opengl.GL32.*;
import static org.lwjgl.system.MemoryUtil.NULL;

import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;

import javax.imageio.ImageIO;

import org.lwjgl.BufferUtils;
import org.lwjgl.Version;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.glfw.GLFWVidMode;
import org.lwjgl.opengl.GL;


public class CameraExample {

private final int width = 1024;
private final int height = 1024;

    // The window handle
    private long window;
   
    private int triangleVao;
    private int triangleVbo;
   
    private int framebuffer;
    private int renderedTexture;
   
    private int screenVao;
    private int screenVbo;

    private int program;
    private int vertexShader;
    private int fragmentShader;
   
    public void run() {
        System.out.println("Hello LWJGL " + Version.getVersion() + "!");

        try {
            init();
            loop();

            // Free the window callbacks and destroy the window
            glfwFreeCallbacks(window);
            glfwDestroyWindow(window);
        } finally {
            // Terminate GLFW and free the error callback
            glfwTerminate();
            glfwSetErrorCallback(null).free();
        }
    }
    private void init() {
        // Setup an error callback. The default implementation
        // will print the error message in System.err.
        GLFWErrorCallback.createPrint(System.err).set();

        // Initialize GLFW. Most GLFW functions will not work before doing this.
        if ( !glfwInit() )
            throw new IllegalStateException("Unable to initialize GLFW");

        // Configure our window
        glfwDefaultWindowHints(); // optional, the current window hints are already the default
        glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // the window will stay hidden after creation
        glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // the window will be resizable

        // Create the window
        window = glfwCreateWindow(width, height, "Camera Example", NULL, NULL);
        if ( window == NULL )
            throw new RuntimeException("Failed to create the GLFW window");

        // Setup a key callback. It will be called every time a key is pressed, repeated or released.
        glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> {
            if ( key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE )
                glfwSetWindowShouldClose(window, true); // We will detect this in our rendering loop
        });

        // Get the resolution of the primary monitor
        GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
        // Center our window
        glfwSetWindowPos(
            window,
            (vidmode.width() - width) / 2,
            (vidmode.height() - height) / 2
        );

        // Make the OpenGL context current
        glfwMakeContextCurrent(window);
        // Enable v-sync
        glfwSwapInterval(1);

        // Make the window visible
        glfwShowWindow(window);
       
        // This line is critical for LWJGL's interoperation with GLFW's
        // OpenGL context, or any context that is managed externally.
        // LWJGL detects the context that is current in the current thread,
        // creates the GLCapabilities instance and makes the OpenGL
        // bindings available for use.
        GL.createCapabilities();
       
        program = glCreateProgram();
        attachVertexShader("quadtextured.vs");
        attachFragmentShader("quadtextured.fs");
        glBindAttribLocation(program, 0, "in_Position");
        glBindAttribLocation(program, 1, "in_TextureCoord");
        linkProgram();
       
        // Enable depth test
    glEnable(GL_DEPTH_TEST);
    // Accept fragment if it closer to the camera than the former one
    glDepthFunc(GL_LESS);
   
        checkError("init");
       
        initCamera();
        initScreen();
        initTriangle();

    }
private void checkError(String where) {
int glError = glGetError();
if(glError != 0) {
        System.out.println("[" + where + "] GL error: " + glError);
        }
}
    private void loop() {
        // Run the rendering loop until the user has attempted to close
        // the window or has pressed the ESCAPE key.
    int i = 0;
        while ( !glfwWindowShouldClose(window) ) {

            glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
        renderToCamera();

            glClearColor(0.0f, 1.0f, 0.0f, 0.0f);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear the framebuffer
        glUseProgram(program);
           
            renderTriangle();
            renderScreen();
           
//            if(i == 100) {
//            try {
// saveCamera("framebuffer.png");
// } catch (IOException e) {
// e.printStackTrace();
// }
//            }
           
        glUseProgram(0);

            glfwSwapBuffers(window); // swap the color buffers

            // Poll for window events. The key callback above will only be
            // invoked during this call.
            glfwPollEvents();
            i++;
        }
    }

    private void initTriangle() {
    triangleVao = glGenVertexArrays();
    glBindVertexArray(triangleVao);
   
    float[] vertices = new float[] {
    0, 0.8f, -1,
    -0.8f, -0.8f, -1,
    0.8f, -0.8f, -1
    };
   
    FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(vertices.length);
    verticesBuffer.put(vertices).flip();
   
    triangleVbo = glGenBuffers();
    glBindBuffer(GL_ARRAY_BUFFER, triangleVbo);
    glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW);
   
    glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);
    glBindVertexArray(0);
        checkError("initTriangle");
    }
    private void renderTriangle() {
    glBindVertexArray(triangleVao);
    glEnableVertexAttribArray(0);
   
    glDrawArrays(GL_TRIANGLES, 0, 3);
   
    glDisableVertexAttribArray(0);
    glBindVertexArray(0);
        checkError("renderTriangle");
    }
   
    private void initCamera() {
    framebuffer = glGenFramebuffers();
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
   
    renderedTexture = glGenTextures();
    glBindTexture(GL_TEXTURE_2D, renderedTexture);
   
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
   
    int depthRenderBuffer = glGenRenderbuffers();
    glBindRenderbuffer(GL_RENDERBUFFER, depthRenderBuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderBuffer);
   
    glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, renderedTexture, 0);
   
    glDrawBuffers(GL_COLOR_ATTACHMENT0);
   
    if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    System.err.println("Framebuffer not complete!");
   
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
        checkError("initCamera");
    }
    private void renderToCamera() {
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glViewport(0, 0, width, height);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glUseProgram(program);
   
    renderTriangle();
   
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
        checkError("renderToCamera");
    }
   
    private void initScreen() {
    screenVao = glGenVertexArrays();
    glBindVertexArray(screenVao);
   
    float[] vertices = new float[] {
    // Vertices  UV
    -1, -1, 0,   1, 0,
    0, -1, 0,    0, 0,
    -1, 0, 0,    1, 1,
    -1, 0, 0,    1, 1,
    0, -1, 0,    0, 0,
    0, 0, 0,     0, 1
    };
   
    FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(vertices.length);
    verticesBuffer.put(vertices).flip();
   
    screenVbo = glGenBuffers();
    glBindBuffer(GL_ARRAY_BUFFER, screenVbo);
    glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, false, (3 + 2) * 4, 0);
    glVertexAttribPointer(1, 2, GL_FLOAT, false, (3 + 2) * 4, 3 * 4);
   
        glBindVertexArray(0);
        checkError("initScreen");
    }
    private void renderScreen() {
//    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, renderedTexture);
   
    glBindVertexArray(screenVao);
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
   
    glDrawArrays(GL_TRIANGLES, 0, 6);
   
    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
    glBindVertexArray(0);
   
    glBindTexture(GL_TEXTURE_2D, 0);
        checkError("renderScreen");
    }
    private void saveCamera(String name) throws IOException {
    glBindTexture(GL_TEXTURE_2D, renderedTexture);
    ByteBuffer buffer = ByteBuffer.allocate(width * height * 3);
    glPixelStorei(GL_PACK_ALIGNMENT, 1);
    glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);
    BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    for(int x = 0; x < width; x++) {
        for(int y = 0; y < height; y++) {
            int i = (x + (width * y)) * 3;
            int r = buffer.get(i) & 0xFF;
            int g = buffer.get(i + 1) & 0xFF;
            int b = buffer.get(i + 2) & 0xFF;
            image.setRGB(x, height - (y + 1), (0xFF << 24) | (r << 16) | (g << 8) | b);
        }
    }
    ImageIO.write(image, "PNG", new File(name));
    }
   
private String readShader(String name) {
StringBuilder source = new StringBuilder();
    try
    {
        BufferedReader reader = new BufferedReader(new FileReader("shaders/" + name));

        String line;
        while ((line = reader.readLine()) != null)
        {
            source.append(line).append("\n");
        }

        reader.close();
    }
    catch (Exception e)
    {
        System.err.println("Error loading source code: " + name);
        e.printStackTrace();
    }

    return source.toString();
}
private void attachVertexShader(String name) {
String source = readShader(name);

vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, source);

glCompileShader(vertexShader);

if(glGetShaderi(vertexShader, GL_COMPILE_STATUS) == GL_FALSE)
System.err.println("Error creating vertexShader\n" + glGetShaderInfoLog(vertexShader));

glAttachShader(program, vertexShader);
        checkError("attachVertexShader");
}
private void attachFragmentShader(String name) {
String source = readShader(name);

fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, source);

glCompileShader(fragmentShader);

if(glGetShaderi(fragmentShader, GL_COMPILE_STATUS) == GL_FALSE)
System.err.println("Error creating fragmentShader\n" + glGetShaderInfoLog(fragmentShader));

glAttachShader(program, fragmentShader);
        checkError("attachFragmentShader");
}
    private void linkProgram() {
    glLinkProgram(program);
   
    if(glGetProgrami(program, GL_LINK_STATUS) == GL_FALSE) {
    System.err.println("Unable to link shader program");
    System.err.println(glGetProgramInfoLog(program));
    }
        checkError("linkProgram");
    }
   
    public static void main(String[] args) {
        new CameraExample().run();
    }

}